mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 05:37:20 +03:00
chore: remove some usage of client from Page (#163)
This brings us closer to reusing Page between browsers.
This commit is contained in:
parent
349ce22565
commit
14f078308d
@ -29,6 +29,10 @@ import { LifecycleWatcher } from './LifecycleWatcher';
|
||||
import { NetworkManager } from './NetworkManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { Events } from './events';
|
||||
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './protocolHelper';
|
||||
import * as dialog from '../dialog';
|
||||
import * as console from '../console';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
@ -64,15 +68,24 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
||||
this._timeoutSettings = timeoutSettings;
|
||||
|
||||
this._client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
||||
this._client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
|
||||
this._client.on('Page.domContentEventFired', event => page.emit(Events.Page.DOMContentLoaded));
|
||||
this._client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event));
|
||||
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
||||
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
|
||||
this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url));
|
||||
this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
|
||||
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
|
||||
this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId));
|
||||
this._client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
||||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
|
||||
this._client.on('Page.loadEventFired', event => page.emit(Events.Page.Load));
|
||||
this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url));
|
||||
this._client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
||||
this._client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
||||
this._client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
|
||||
this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context));
|
||||
this._client.on('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId));
|
||||
this._client.on('Runtime.executionContextsCleared', event => this._onExecutionContextsCleared());
|
||||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
@ -82,6 +95,8 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
]);
|
||||
this._handleFrameTree(frameTree);
|
||||
await Promise.all([
|
||||
this._client.send('Log.enable', {}),
|
||||
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}),
|
||||
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
|
||||
this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
|
||||
this._networkManager.initialize(),
|
||||
@ -357,6 +372,72 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
this._frames.delete(this._frameData(frame).id);
|
||||
this.emit(FrameManagerEvents.FrameDetached, frame);
|
||||
}
|
||||
|
||||
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
|
||||
if (event.executionContextId === 0) {
|
||||
// DevTools protocol stores the last 1000 console messages. These
|
||||
// messages are always reported even for removed execution contexts. In
|
||||
// this case, they are marked with executionContextId = 0 and are
|
||||
// reported upon enabling Runtime agent.
|
||||
//
|
||||
// Ignore these messages since:
|
||||
// - there's no execution context we can use to operate with message
|
||||
// arguments
|
||||
// - these messages are reported before Playwright clients can subscribe
|
||||
// to the 'console'
|
||||
// page event.
|
||||
//
|
||||
// @see https://github.com/GoogleChrome/puppeteer/issues/3865
|
||||
return;
|
||||
}
|
||||
const context = this.executionContextById(event.executionContextId);
|
||||
const values = event.args.map(arg => context._createHandle(arg));
|
||||
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
|
||||
}
|
||||
|
||||
async _exposeBinding(name: string, bindingFunction: string) {
|
||||
await this._client.send('Runtime.addBinding', {name: name});
|
||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: bindingFunction});
|
||||
await Promise.all(this.frames().map(frame => frame.evaluate(bindingFunction).catch(debugError)));
|
||||
}
|
||||
|
||||
_onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
|
||||
const context = this.executionContextById(event.executionContextId);
|
||||
this._page._onBindingCalled(event.payload, context);
|
||||
}
|
||||
|
||||
_onDialog(event : Protocol.Page.javascriptDialogOpeningPayload) {
|
||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
async (accept: boolean, promptText?: string) => {
|
||||
await this._client.send('Page.handleJavaScriptDialog', { accept, promptText });
|
||||
},
|
||||
event.defaultPrompt));
|
||||
}
|
||||
|
||||
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
||||
this._page.emit(Events.Page.PageError, exceptionToError(exceptionDetails));
|
||||
}
|
||||
|
||||
_onTargetCrashed() {
|
||||
this._page.emit('error', new Error('Page crashed!'));
|
||||
}
|
||||
|
||||
_onLogEntryAdded(event: Protocol.Log.entryAddedPayload) {
|
||||
const {level, text, args, source, url, lineNumber} = event.entry;
|
||||
if (args)
|
||||
args.map(arg => releaseObject(this._client, arg));
|
||||
if (source !== 'worker')
|
||||
this._page.emit(Events.Page.Console, new console.ConsoleMessage(level, text, [], {url, lineNumber}));
|
||||
}
|
||||
|
||||
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
||||
const frame = this.frame(event.frameId);
|
||||
const utilityWorld = await frame._utilityDOMWorld();
|
||||
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
||||
this._page._onFileChooserOpened(handle);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoLegacyNavigationOptions(options) {
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as console from '../console';
|
||||
import * as dialog from '../dialog';
|
||||
import * as dom from '../dom';
|
||||
import * as frames from '../frames';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
@ -30,7 +29,7 @@ import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import * as types from '../types';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserContext } from './BrowserContext';
|
||||
import { CDPSession, CDPSessionEvents } from './Connection';
|
||||
import { CDPSession } from './Connection';
|
||||
import { EmulationManager } from './EmulationManager';
|
||||
import { Events } from './events';
|
||||
import { Accessibility } from './features/accessibility';
|
||||
@ -41,20 +40,20 @@ import { PDF } from './features/pdf';
|
||||
import { Workers } from './features/workers';
|
||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
||||
import { DOMWorldDelegate } from './JSHandle';
|
||||
import { NetworkManagerEvents } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { getExceptionMessage, releaseObject } from './protocolHelper';
|
||||
import { CRScreenshotDelegate } from './Screenshotter';
|
||||
|
||||
export class Page extends EventEmitter {
|
||||
private _closed = false;
|
||||
private _closedCallback: () => void;
|
||||
private _closedPromise: Promise<void>;
|
||||
private _disconnected = false;
|
||||
private _disconnectedCallback: (e: Error) => void;
|
||||
private _disconnectedPromise: Promise<Error>;
|
||||
_client: CDPSession;
|
||||
private _browserContext: BrowserContext;
|
||||
private _keyboard: input.Keyboard;
|
||||
private _mouse: input.Mouse;
|
||||
readonly keyboard: input.Keyboard;
|
||||
readonly mouse: input.Mouse;
|
||||
private _timeoutSettings: TimeoutSettings;
|
||||
private _frameManager: FrameManager;
|
||||
private _emulationManager: EmulationManager;
|
||||
@ -69,12 +68,11 @@ export class Page extends EventEmitter {
|
||||
private _viewport: types.Viewport | null = null;
|
||||
_screenshotter: Screenshotter;
|
||||
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
|
||||
private _disconnectPromise: Promise<Error> | undefined;
|
||||
private _emulatedMediaType: string | undefined;
|
||||
|
||||
static async create(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean, defaultViewport: types.Viewport | null): Promise<Page> {
|
||||
const page = new Page(client, browserContext, ignoreHTTPSErrors);
|
||||
await page._initialize();
|
||||
await page._frameManager.initialize();
|
||||
if (defaultViewport)
|
||||
await page.setViewport(defaultViewport);
|
||||
return page;
|
||||
@ -84,30 +82,21 @@ export class Page extends EventEmitter {
|
||||
super();
|
||||
this._client = client;
|
||||
this._closedPromise = new Promise(f => this._closedCallback = f);
|
||||
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
|
||||
this._browserContext = browserContext;
|
||||
this._keyboard = new input.Keyboard(new RawKeyboardImpl(client));
|
||||
this._mouse = new input.Mouse(new RawMouseImpl(client), this._keyboard);
|
||||
this.keyboard = new input.Keyboard(new RawKeyboardImpl(client));
|
||||
this.mouse = new input.Mouse(new RawMouseImpl(client), this.keyboard);
|
||||
this._timeoutSettings = new TimeoutSettings();
|
||||
this.accessibility = new Accessibility(client);
|
||||
this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings);
|
||||
this._emulationManager = new EmulationManager(client);
|
||||
this.coverage = new Coverage(client);
|
||||
this.pdf = new PDF(client);
|
||||
this.workers = new Workers(client, this._addConsoleMessage.bind(this), this._handleException.bind(this));
|
||||
this.workers = new Workers(client, this._addConsoleMessage.bind(this), error => this.emit(Events.Page.PageError, error));
|
||||
this.overrides = new Overrides(client);
|
||||
this.interception = new Interception(this._frameManager.networkManager());
|
||||
this._screenshotter = new Screenshotter(this, new CRScreenshotDelegate(this._client), browserContext.browser());
|
||||
|
||||
client.on('Target.attachedToTarget', event => {
|
||||
if (event.targetInfo.type !== 'worker') {
|
||||
// If we don't detach from service workers, they will never die.
|
||||
client.send('Target.detachFromTarget', {
|
||||
sessionId: event.sessionId
|
||||
}).catch(debugError);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event));
|
||||
this._frameManager.on(FrameManagerEvents.FrameDetached, event => this.emit(Events.Page.FrameDetached, event));
|
||||
this._frameManager.on(FrameManagerEvents.FrameNavigated, event => this.emit(Events.Page.FrameNavigated, event));
|
||||
@ -117,16 +106,6 @@ export class Page extends EventEmitter {
|
||||
networkManager.on(NetworkManagerEvents.Response, event => this.emit(Events.Page.Response, event));
|
||||
networkManager.on(NetworkManagerEvents.RequestFailed, event => this.emit(Events.Page.RequestFailed, event));
|
||||
networkManager.on(NetworkManagerEvents.RequestFinished, event => this.emit(Events.Page.RequestFinished, event));
|
||||
|
||||
client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded));
|
||||
client.on('Page.loadEventFired', event => this.emit(Events.Page.Load));
|
||||
client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
||||
client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
||||
client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
||||
client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
|
||||
client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
||||
client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
|
||||
client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event));
|
||||
}
|
||||
|
||||
_didClose() {
|
||||
@ -136,22 +115,17 @@ export class Page extends EventEmitter {
|
||||
this._closedCallback();
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
await Promise.all([
|
||||
this._frameManager.initialize(),
|
||||
this._client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}),
|
||||
this._client.send('Performance.enable', {}),
|
||||
this._client.send('Log.enable', {}),
|
||||
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true})
|
||||
]);
|
||||
_didDisconnect() {
|
||||
assert(!this._disconnected, 'Page disconnected twice');
|
||||
this._disconnected = true;
|
||||
this._disconnectedCallback(new Error('Target closed'));
|
||||
}
|
||||
|
||||
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
||||
if (!this._fileChooserInterceptors.size)
|
||||
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||
if (!this._fileChooserInterceptors.size) {
|
||||
await handle.dispose();
|
||||
return;
|
||||
const frame = this._frameManager.frame(event.frameId);
|
||||
const utilityWorld = await frame._utilityDOMWorld();
|
||||
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
||||
}
|
||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||
this._fileChooserInterceptors.clear();
|
||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||
@ -182,26 +156,10 @@ export class Page extends EventEmitter {
|
||||
return this._browserContext;
|
||||
}
|
||||
|
||||
_onTargetCrashed() {
|
||||
this.emit('error', new Error('Page crashed!'));
|
||||
}
|
||||
|
||||
_onLogEntryAdded(event: Protocol.Log.entryAddedPayload) {
|
||||
const {level, text, args, source, url, lineNumber} = event.entry;
|
||||
if (args)
|
||||
args.map(arg => releaseObject(this._client, arg));
|
||||
if (source !== 'worker')
|
||||
this.emit(Events.Page.Console, new console.ConsoleMessage(level, text, [], {url, lineNumber}));
|
||||
}
|
||||
|
||||
mainFrame(): frames.Frame {
|
||||
return this._frameManager.mainFrame();
|
||||
}
|
||||
|
||||
get keyboard(): input.Keyboard {
|
||||
return this._keyboard;
|
||||
}
|
||||
|
||||
frames(): frames.Frame[] {
|
||||
return this._frameManager.frames();
|
||||
}
|
||||
@ -251,11 +209,7 @@ export class Page extends EventEmitter {
|
||||
if (this._pageBindings.has(name))
|
||||
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
||||
this._pageBindings.set(name, playwrightFunction);
|
||||
|
||||
const expression = helper.evaluationString(addPageBinding, name);
|
||||
await this._client.send('Runtime.addBinding', {name: name});
|
||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression});
|
||||
await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
|
||||
await this._frameManager._exposeBinding(name, helper.evaluationString(addPageBinding, name));
|
||||
|
||||
function addPageBinding(bindingName: string) {
|
||||
const binding = window[bindingName];
|
||||
@ -283,37 +237,8 @@ export class Page extends EventEmitter {
|
||||
return this._frameManager.networkManager().setUserAgent(userAgent);
|
||||
}
|
||||
|
||||
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
||||
const message = getExceptionMessage(exceptionDetails);
|
||||
const err = new Error(message);
|
||||
err.stack = ''; // Don't report clientside error with a node stack attached
|
||||
this.emit(Events.Page.PageError, err);
|
||||
}
|
||||
|
||||
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
|
||||
if (event.executionContextId === 0) {
|
||||
// DevTools protocol stores the last 1000 console messages. These
|
||||
// messages are always reported even for removed execution contexts. In
|
||||
// this case, they are marked with executionContextId = 0 and are
|
||||
// reported upon enabling Runtime agent.
|
||||
//
|
||||
// Ignore these messages since:
|
||||
// - there's no execution context we can use to operate with message
|
||||
// arguments
|
||||
// - these messages are reported before Playwright clients can subscribe
|
||||
// to the 'console'
|
||||
// page event.
|
||||
//
|
||||
// @see https://github.com/GoogleChrome/puppeteer/issues/3865
|
||||
return;
|
||||
}
|
||||
const context = this._frameManager.executionContextById(event.executionContextId);
|
||||
const values = event.args.map(arg => context._createHandle(arg));
|
||||
this._addConsoleMessage(event.type, values, event.stackTrace);
|
||||
}
|
||||
|
||||
async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
|
||||
const {name, seq, args} = JSON.parse(event.payload);
|
||||
async _onBindingCalled(payload: string, context: js.ExecutionContext) {
|
||||
const {name, seq, args} = JSON.parse(payload);
|
||||
let expression = null;
|
||||
try {
|
||||
const result = await this._pageBindings.get(name)(...args);
|
||||
@ -324,7 +249,7 @@ export class Page extends EventEmitter {
|
||||
else
|
||||
expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
||||
}
|
||||
this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
|
||||
context.evaluate(expression).catch(debugError);
|
||||
|
||||
function deliverResult(name: string, seq: number, result: any) {
|
||||
window[name]['callbacks'].get(seq).resolve(result);
|
||||
@ -344,43 +269,28 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_addConsoleMessage(type: string, args: js.JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) {
|
||||
_addConsoleMessage(type: string, args: js.JSHandle[], location: console.ConsoleMessageLocation) {
|
||||
if (!this.listenerCount(Events.Page.Console)) {
|
||||
args.forEach(arg => arg.dispose());
|
||||
return;
|
||||
}
|
||||
const location = stackTrace && stackTrace.callFrames.length ? {
|
||||
url: stackTrace.callFrames[0].url,
|
||||
lineNumber: stackTrace.callFrames[0].lineNumber,
|
||||
columnNumber: stackTrace.callFrames[0].columnNumber,
|
||||
} : {};
|
||||
this.emit(Events.Page.Console, new console.ConsoleMessage(type, undefined, args, location));
|
||||
}
|
||||
|
||||
_onDialog(event : Protocol.Page.javascriptDialogOpeningPayload) {
|
||||
this.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
async (accept: boolean, promptText?: string) => {
|
||||
await this._client.send('Page.handleJavaScriptDialog', { accept, promptText });
|
||||
},
|
||||
event.defaultPrompt));
|
||||
}
|
||||
|
||||
url(): string {
|
||||
return this.mainFrame().url();
|
||||
}
|
||||
|
||||
async content(): Promise<string> {
|
||||
return await this._frameManager.mainFrame().content();
|
||||
return await this.mainFrame().content();
|
||||
}
|
||||
|
||||
async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } | undefined) {
|
||||
await this._frameManager.mainFrame().setContent(html, options);
|
||||
await this.mainFrame().setContent(html, options);
|
||||
}
|
||||
|
||||
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
||||
return await this._frameManager.mainFrame().goto(url, options);
|
||||
return await this.mainFrame().goto(url, options);
|
||||
}
|
||||
|
||||
async reload(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
||||
@ -392,39 +302,33 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
async waitForNavigation(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
||||
return await this._frameManager.mainFrame().waitForNavigation(options);
|
||||
}
|
||||
|
||||
_sessionClosePromise() {
|
||||
if (!this._disconnectPromise)
|
||||
this._disconnectPromise = new Promise(fulfill => this._client.once(CDPSessionEvents.Disconnected, () => fulfill(new Error('Target closed'))));
|
||||
return this._disconnectPromise;
|
||||
return await this.mainFrame().waitForNavigation(options);
|
||||
}
|
||||
|
||||
async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Request> {
|
||||
const {
|
||||
timeout = this._timeoutSettings.timeout(),
|
||||
} = options;
|
||||
return helper.waitForEvent(this._frameManager.networkManager(), NetworkManagerEvents.Request, request => {
|
||||
return helper.waitForEvent(this, Events.Page.Request, (request: network.Request) => {
|
||||
if (helper.isString(urlOrPredicate))
|
||||
return (urlOrPredicate === request.url());
|
||||
if (typeof urlOrPredicate === 'function')
|
||||
return !!(urlOrPredicate(request));
|
||||
return false;
|
||||
}, timeout, this._sessionClosePromise());
|
||||
}, timeout, this._disconnectedPromise);
|
||||
}
|
||||
|
||||
async waitForResponse(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<network.Response> {
|
||||
const {
|
||||
timeout = this._timeoutSettings.timeout(),
|
||||
} = options;
|
||||
return helper.waitForEvent(this._frameManager.networkManager(), NetworkManagerEvents.Response, response => {
|
||||
return helper.waitForEvent(this, Events.Page.Response, (response: network.Response) => {
|
||||
if (helper.isString(urlOrPredicate))
|
||||
return (urlOrPredicate === response.url());
|
||||
if (typeof urlOrPredicate === 'function')
|
||||
return !!(urlOrPredicate(response));
|
||||
return false;
|
||||
}, timeout, this._sessionClosePromise());
|
||||
}, timeout, this._disconnectedPromise);
|
||||
}
|
||||
|
||||
async goBack(options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
||||
@ -488,7 +392,7 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||
return this._frameManager.mainFrame().evaluate(pageFunction, ...args as any);
|
||||
return this.mainFrame().evaluate(pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
|
||||
@ -509,7 +413,7 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
async close(options: { runBeforeUnload: (boolean | undefined); } = {runBeforeUnload: undefined}) {
|
||||
assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
||||
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
||||
const runBeforeUnload = !!options.runBeforeUnload;
|
||||
if (runBeforeUnload) {
|
||||
await this._client.send('Page.close');
|
||||
@ -523,10 +427,6 @@ export class Page extends EventEmitter {
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
get mouse(): input.Mouse {
|
||||
return this._mouse;
|
||||
}
|
||||
|
||||
click(selector: string | types.Selector, options?: ClickOptions) {
|
||||
return this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
@ -18,11 +18,12 @@
|
||||
import * as types from '../types';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserContext } from './BrowserContext';
|
||||
import { CDPSession } from './Connection';
|
||||
import { CDPSession, CDPSessionEvents } from './Connection';
|
||||
import { Events } from './events';
|
||||
import { Worker } from './features/workers';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { debugError } from '../helper';
|
||||
|
||||
const targetSymbol = Symbol('target');
|
||||
|
||||
@ -83,6 +84,14 @@ export class Target {
|
||||
this._pagePromise = this._sessionFactory().then(async client => {
|
||||
const page = await Page.create(client, this._browserContext, this._ignoreHTTPSErrors, this._defaultViewport);
|
||||
page[targetSymbol] = this;
|
||||
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
|
||||
client.on('Target.attachedToTarget', event => {
|
||||
if (event.targetInfo.type !== 'worker') {
|
||||
// If we don't detach from service workers, they will never die.
|
||||
client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError);
|
||||
}
|
||||
});
|
||||
await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { CDPSession, Connection } from '../Connection';
|
||||
import { debugError } from '../../helper';
|
||||
@ -21,10 +22,12 @@ import { Protocol } from '../protocol';
|
||||
import { Events } from '../events';
|
||||
import * as types from '../../types';
|
||||
import * as js from '../../javascript';
|
||||
import * as console from '../../console';
|
||||
import { ExecutionContextDelegate } from '../ExecutionContext';
|
||||
import { toConsoleMessageLocation, exceptionToError } from '../protocolHelper';
|
||||
|
||||
type AddToConsoleCallback = (type: string, args: js.JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void;
|
||||
type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void;
|
||||
type AddToConsoleCallback = (type: string, args: js.JSHandle[], location: console.ConsoleMessageLocation) => void;
|
||||
type HandleExceptionCallback = (error: Error) => void;
|
||||
|
||||
export class Workers extends EventEmitter {
|
||||
private _workers = new Map<string, Worker>();
|
||||
@ -74,8 +77,8 @@ export class Worker extends EventEmitter {
|
||||
// This might fail if the target is closed before we recieve all execution contexts.
|
||||
this._client.send('Runtime.enable', {}).catch(debugError);
|
||||
|
||||
this._client.on('Runtime.consoleAPICalled', event => addToConsole(event.type, event.args.map(jsHandleFactory), event.stackTrace));
|
||||
this._client.on('Runtime.exceptionThrown', exception => handleException(exception.exceptionDetails));
|
||||
this._client.on('Runtime.consoleAPICalled', event => addToConsole(event.type, event.args.map(jsHandleFactory), toConsoleMessageLocation(event.stackTrace)));
|
||||
this._client.on('Runtime.exceptionThrown', exception => handleException(exceptionToError(exception.exceptionDetails)));
|
||||
}
|
||||
|
||||
url(): string {
|
||||
|
@ -94,4 +94,17 @@ export async function readProtocolStream(client: CDPSession, handle: string, pat
|
||||
}
|
||||
}
|
||||
|
||||
export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace | undefined) {
|
||||
return stackTrace && stackTrace.callFrames.length ? {
|
||||
url: stackTrace.callFrames[0].url,
|
||||
lineNumber: stackTrace.callFrames[0].lineNumber,
|
||||
columnNumber: stackTrace.callFrames[0].columnNumber,
|
||||
} : {};
|
||||
}
|
||||
|
||||
export function exceptionToError(exceptionDetails: Protocol.Runtime.ExceptionDetails): Error {
|
||||
const message = getExceptionMessage(exceptionDetails);
|
||||
const err = new Error(message);
|
||||
err.stack = ''; // Don't report clientside error with a node stack attached
|
||||
return err;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as js from './javascript';
|
||||
|
||||
type ConsoleMessageLocation = {
|
||||
export type ConsoleMessageLocation = {
|
||||
url?: string,
|
||||
lineNumber?: number,
|
||||
columnNumber?: number,
|
||||
|
Loading…
Reference in New Issue
Block a user