chore: refactor impl-side events to be per-class (#3569)
This commit is contained in:
parent
d4dac04212
commit
57e8617474
|
|
@ -19,7 +19,6 @@ import { BrowserContext } from './browserContext';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Download } from './download';
|
import { Download } from './download';
|
||||||
import { Events } from './events';
|
|
||||||
import { ProxySettings } from './types';
|
import { ProxySettings } from './types';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
|
|
||||||
|
|
@ -40,6 +39,10 @@ export type BrowserOptions = types.UIOptions & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class Browser extends EventEmitter {
|
export abstract class Browser extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Disconnected: 'disconnected',
|
||||||
|
};
|
||||||
|
|
||||||
readonly _options: BrowserOptions;
|
readonly _options: BrowserOptions;
|
||||||
private _downloads = new Map<string, Download>();
|
private _downloads = new Map<string, Download>();
|
||||||
_defaultContext: BrowserContext | null = null;
|
_defaultContext: BrowserContext | null = null;
|
||||||
|
|
@ -87,7 +90,7 @@ export abstract class Browser extends EventEmitter {
|
||||||
context._browserClosed();
|
context._browserClosed();
|
||||||
if (this._defaultContext)
|
if (this._defaultContext)
|
||||||
this._defaultContext._browserClosed();
|
this._defaultContext._browserClosed();
|
||||||
this.emit(Events.Browser.Disconnected);
|
this.emit(Browser.Events.Disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
|
@ -96,7 +99,7 @@ export abstract class Browser extends EventEmitter {
|
||||||
await this._options.browserProcess.close();
|
await this._options.browserProcess.close();
|
||||||
}
|
}
|
||||||
if (this.isConnected())
|
if (this.isConnected())
|
||||||
await new Promise(x => this.once(Events.Browser.Disconnected, x));
|
await new Promise(x => this.once(Browser.Events.Disconnected, x));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { Page, PageBinding } from './page';
|
||||||
import { TimeoutSettings } from './timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import * as frames from './frames';
|
import * as frames from './frames';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { Events } from './events';
|
|
||||||
import { Download } from './download';
|
import { Download } from './download';
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
@ -40,6 +39,13 @@ export class Screencast {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BrowserContext extends EventEmitter {
|
export abstract class BrowserContext extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Close: 'close',
|
||||||
|
Page: 'page',
|
||||||
|
ScreencastStarted: 'screencaststarted',
|
||||||
|
ScreencastStopped: 'screencaststopped',
|
||||||
|
};
|
||||||
|
|
||||||
readonly _timeoutSettings = new TimeoutSettings();
|
readonly _timeoutSettings = new TimeoutSettings();
|
||||||
readonly _pageBindings = new Map<string, PageBinding>();
|
readonly _pageBindings = new Map<string, PageBinding>();
|
||||||
readonly _options: types.BrowserContextOptions;
|
readonly _options: types.BrowserContextOptions;
|
||||||
|
|
@ -81,7 +87,7 @@ export abstract class BrowserContext extends EventEmitter {
|
||||||
this._closedStatus = 'closed';
|
this._closedStatus = 'closed';
|
||||||
this._downloads.clear();
|
this._downloads.clear();
|
||||||
this._closePromiseFulfill!(new Error('Context closed'));
|
this._closePromiseFulfill!(new Error('Context closed'));
|
||||||
this.emit(Events.BrowserContext.Close);
|
this.emit(BrowserContext.Events.Close);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BrowserContext methods.
|
// BrowserContext methods.
|
||||||
|
|
@ -160,7 +166,7 @@ export abstract class BrowserContext extends EventEmitter {
|
||||||
|
|
||||||
async _loadDefaultContext(progress: Progress) {
|
async _loadDefaultContext(progress: Progress) {
|
||||||
if (!this.pages().length) {
|
if (!this.pages().length) {
|
||||||
const waitForEvent = helper.waitForEvent(progress, this, Events.BrowserContext.Page);
|
const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page);
|
||||||
progress.cleanupWhenAborted(() => waitForEvent.dispose);
|
progress.cleanupWhenAborted(() => waitForEvent.dispose);
|
||||||
await waitForEvent.promise;
|
await waitForEvent.promise;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import { Browser, BrowserOptions } from '../browser';
|
import { Browser, BrowserOptions } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
||||||
import { Events as CommonEvents } from '../events';
|
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding, Worker } from '../page';
|
import { Page, PageBinding, Worker } from '../page';
|
||||||
|
|
@ -26,7 +25,6 @@ import * as types from '../types';
|
||||||
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
|
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
|
||||||
import { CRPage } from './crPage';
|
import { CRPage } from './crPage';
|
||||||
import { readProtocolStream } from './crProtocolHelper';
|
import { readProtocolStream } from './crProtocolHelper';
|
||||||
import { Events } from './events';
|
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { CRExecutionContext } from './crExecutionContext';
|
import { CRExecutionContext } from './crExecutionContext';
|
||||||
import { CRDevTools } from './crDevTools';
|
import { CRDevTools } from './crDevTools';
|
||||||
|
|
@ -151,7 +149,7 @@ export class CRBrowser extends Browser {
|
||||||
const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, false);
|
const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, false);
|
||||||
this._backgroundPages.set(targetInfo.targetId, backgroundPage);
|
this._backgroundPages.set(targetInfo.targetId, backgroundPage);
|
||||||
backgroundPage.pageOrError().then(() => {
|
backgroundPage.pageOrError().then(() => {
|
||||||
context!.emit(Events.ChromiumBrowserContext.BackgroundPage, backgroundPage._page);
|
context!.emit(CRBrowserContext.CREvents.BackgroundPage, backgroundPage._page);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -164,11 +162,11 @@ export class CRBrowser extends Browser {
|
||||||
const page = crPage._page;
|
const page = crPage._page;
|
||||||
if (pageOrError instanceof Error)
|
if (pageOrError instanceof Error)
|
||||||
page._setIsError();
|
page._setIsError();
|
||||||
context!.emit(CommonEvents.BrowserContext.Page, page);
|
context!.emit(BrowserContext.Events.Page, page);
|
||||||
if (opener) {
|
if (opener) {
|
||||||
opener.pageOrError().then(openerPage => {
|
opener.pageOrError().then(openerPage => {
|
||||||
if (openerPage instanceof Page && !openerPage.isClosed())
|
if (openerPage instanceof Page && !openerPage.isClosed())
|
||||||
openerPage.emit(CommonEvents.Page.Popup, page);
|
openerPage.emit(Page.Events.Popup, page);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -178,7 +176,7 @@ export class CRBrowser extends Browser {
|
||||||
if (targetInfo.type === 'service_worker') {
|
if (targetInfo.type === 'service_worker') {
|
||||||
const serviceWorker = new CRServiceWorker(context, session, targetInfo.url);
|
const serviceWorker = new CRServiceWorker(context, session, targetInfo.url);
|
||||||
this._serviceWorkers.set(targetInfo.targetId, serviceWorker);
|
this._serviceWorkers.set(targetInfo.targetId, serviceWorker);
|
||||||
context.emit(Events.ChromiumBrowserContext.ServiceWorker, serviceWorker);
|
context.emit(CRBrowserContext.CREvents.ServiceWorker, serviceWorker);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +200,7 @@ export class CRBrowser extends Browser {
|
||||||
const serviceWorker = this._serviceWorkers.get(targetId);
|
const serviceWorker = this._serviceWorkers.get(targetId);
|
||||||
if (serviceWorker) {
|
if (serviceWorker) {
|
||||||
this._serviceWorkers.delete(targetId);
|
this._serviceWorkers.delete(targetId);
|
||||||
serviceWorker.emit(CommonEvents.Worker.Close);
|
serviceWorker.emit(Worker.Events.Close);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,6 +278,11 @@ class CRServiceWorker extends Worker {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CRBrowserContext extends BrowserContext {
|
export class CRBrowserContext extends BrowserContext {
|
||||||
|
static CREvents = {
|
||||||
|
BackgroundPage: 'backgroundpage',
|
||||||
|
ServiceWorker: 'serviceworker',
|
||||||
|
};
|
||||||
|
|
||||||
readonly _browser: CRBrowser;
|
readonly _browser: CRBrowser;
|
||||||
readonly _browserContextId: string | null;
|
readonly _browserContextId: string | null;
|
||||||
readonly _evaluateOnNewDocumentSources: string[];
|
readonly _evaluateOnNewDocumentSources: string[];
|
||||||
|
|
@ -432,7 +435,7 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
// asynchronously and we get detached from them later.
|
// asynchronously and we get detached from them later.
|
||||||
// To avoid the wrong order of notifications, we manually fire
|
// To avoid the wrong order of notifications, we manually fire
|
||||||
// "close" event here and forget about the serivce worker.
|
// "close" event here and forget about the serivce worker.
|
||||||
serviceWorker.emit(CommonEvents.Worker.Close);
|
serviceWorker.emit(Worker.Events.Close);
|
||||||
this._browser._serviceWorkers.delete(targetId);
|
this._browser._serviceWorkers.delete(targetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { CRExecutionContext } from './crExecutionContext';
|
||||||
import { CRNetworkManager } from './crNetworkManager';
|
import { CRNetworkManager } from './crNetworkManager';
|
||||||
import { Page, Worker, PageBinding } from '../page';
|
import { Page, Worker, PageBinding } from '../page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { Events } from '../events';
|
|
||||||
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './crProtocolHelper';
|
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './crProtocolHelper';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import { PageDelegate } from '../page';
|
import { PageDelegate } from '../page';
|
||||||
|
|
@ -590,7 +589,7 @@ class FrameSession {
|
||||||
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
|
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
|
||||||
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
|
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
|
||||||
});
|
});
|
||||||
session.on('Runtime.exceptionThrown', exception => this._page.emit(Events.Page.PageError, exceptionToError(exception.exceptionDetails)));
|
session.on('Runtime.exceptionThrown', exception => this._page.emit(Page.Events.PageError, exceptionToError(exception.exceptionDetails)));
|
||||||
// TODO: attribute workers to the right frame.
|
// TODO: attribute workers to the right frame.
|
||||||
this._networkManager.instrumentNetworkEvents(session, this._page._frameManager.frame(this._targetId)!);
|
this._networkManager.instrumentNetworkEvents(session, this._page._frameManager.frame(this._targetId)!);
|
||||||
}
|
}
|
||||||
|
|
@ -664,7 +663,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
|
_onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||||
event.type,
|
event.type,
|
||||||
event.message,
|
event.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
|
|
@ -674,7 +673,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
||||||
this._page.emit(Events.Page.PageError, exceptionToError(exceptionDetails));
|
this._page.emit(Page.Events.PageError, exceptionToError(exceptionDetails));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onTargetCrashed() {
|
async _onTargetCrashed() {
|
||||||
|
|
@ -692,7 +691,7 @@ class FrameSession {
|
||||||
lineNumber: lineNumber || 0,
|
lineNumber: lineNumber || 0,
|
||||||
columnNumber: 0,
|
columnNumber: 0,
|
||||||
};
|
};
|
||||||
this._page.emit(Events.Page.Console, new ConsoleMessage(level, text, [], location));
|
this._page.emit(Page.Events.Console, new ConsoleMessage(level, text, [], location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2019 Google Inc. All rights reserved.
|
|
||||||
* Modifications copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const Events = {
|
|
||||||
ChromiumBrowserContext: {
|
|
||||||
BackgroundPage: 'backgroundpage',
|
|
||||||
ServiceWorker: 'serviceworker',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { Events } from '../events';
|
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
|
|
@ -23,10 +22,10 @@ import DebugScript from './injected/debugScript';
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(context: BrowserContext) {
|
constructor(context: BrowserContext) {
|
||||||
context.on(Events.BrowserContext.Page, (page: Page) => {
|
context.on(BrowserContext.Events.Page, (page: Page) => {
|
||||||
for (const frame of page.frames())
|
for (const frame of page.frames())
|
||||||
this.ensureInstalledInFrame(frame);
|
this.ensureInstalledInFrame(frame);
|
||||||
page.on(Events.Page.FrameNavigated, frame => this.ensureInstalledInFrame(frame));
|
page.on(Page.Events.FrameNavigated, frame => this.ensureInstalledInFrame(frame));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { Events } from './events';
|
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import { assert, mkdirIfNeeded } from './helper';
|
import { assert, mkdirIfNeeded } from './helper';
|
||||||
|
|
||||||
|
|
@ -47,13 +46,13 @@ export class Download {
|
||||||
page._browserContext._downloads.add(this);
|
page._browserContext._downloads.add(this);
|
||||||
this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads;
|
this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads;
|
||||||
if (suggestedFilename !== undefined)
|
if (suggestedFilename !== undefined)
|
||||||
this._page.emit(Events.Page.Download, this);
|
this._page.emit(Page.Events.Download, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_filenameSuggested(suggestedFilename: string) {
|
_filenameSuggested(suggestedFilename: string) {
|
||||||
assert(this._suggestedFilename === undefined);
|
assert(this._suggestedFilename === undefined);
|
||||||
this._suggestedFilename = suggestedFilename;
|
this._suggestedFilename = suggestedFilename;
|
||||||
this._page.emit(Events.Page.Download, this);
|
this._page.emit(Page.Events.Download, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
url(): string {
|
url(): string {
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2019 Google Inc. All rights reserved.
|
|
||||||
* Modifications copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const Events = {
|
|
||||||
Browser: {
|
|
||||||
Disconnected: 'disconnected'
|
|
||||||
},
|
|
||||||
|
|
||||||
BrowserContext: {
|
|
||||||
Close: 'close',
|
|
||||||
Page: 'page',
|
|
||||||
ScreencastStarted: 'screencaststarted',
|
|
||||||
ScreencastStopped: 'screencaststopped',
|
|
||||||
},
|
|
||||||
|
|
||||||
BrowserServer: {
|
|
||||||
Close: 'close',
|
|
||||||
},
|
|
||||||
|
|
||||||
Page: {
|
|
||||||
Close: 'close',
|
|
||||||
Crash: 'crash',
|
|
||||||
Console: 'console',
|
|
||||||
Dialog: 'dialog',
|
|
||||||
Download: 'download',
|
|
||||||
FileChooser: 'filechooser',
|
|
||||||
DOMContentLoaded: 'domcontentloaded',
|
|
||||||
// Can't use just 'error' due to node.js special treatment of error events.
|
|
||||||
// @see https://nodejs.org/api/events.html#events_error_events
|
|
||||||
PageError: 'pageerror',
|
|
||||||
Request: 'request',
|
|
||||||
Response: 'response',
|
|
||||||
RequestFailed: 'requestfailed',
|
|
||||||
RequestFinished: 'requestfinished',
|
|
||||||
FrameAttached: 'frameattached',
|
|
||||||
FrameDetached: 'framedetached',
|
|
||||||
FrameNavigated: 'framenavigated',
|
|
||||||
Load: 'load',
|
|
||||||
Popup: 'popup',
|
|
||||||
Worker: 'worker',
|
|
||||||
},
|
|
||||||
|
|
||||||
Worker: {
|
|
||||||
Close: 'close',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import { Browser, BrowserOptions } from '../browser';
|
import { Browser, BrowserOptions } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
||||||
import { Events } from '../events';
|
|
||||||
import { assert, helper, RegisteredListener } from '../helper';
|
import { assert, helper, RegisteredListener } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding } from '../page';
|
import { Page, PageBinding } from '../page';
|
||||||
|
|
@ -129,12 +128,12 @@ export class FFBrowser extends Browser {
|
||||||
const page = ffPage._page;
|
const page = ffPage._page;
|
||||||
if (pageOrError instanceof Error)
|
if (pageOrError instanceof Error)
|
||||||
page._setIsError();
|
page._setIsError();
|
||||||
context.emit(Events.BrowserContext.Page, page);
|
context.emit(BrowserContext.Events.Page, page);
|
||||||
if (!opener)
|
if (!opener)
|
||||||
return;
|
return;
|
||||||
const openerPage = await opener.pageOrError();
|
const openerPage = await opener.pageOrError();
|
||||||
if (openerPage instanceof Page && !openerPage.isClosed())
|
if (openerPage instanceof Page && !openerPage.isClosed())
|
||||||
openerPage.emit(Events.Page.Popup, page);
|
openerPage.emit(Page.Events.Popup, page);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import { Events } from '../events';
|
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { assert, helper, RegisteredListener } from '../helper';
|
import { assert, helper, RegisteredListener } from '../helper';
|
||||||
import { Page, PageBinding, PageDelegate, Worker } from '../page';
|
import { Page, PageBinding, PageDelegate, Worker } from '../page';
|
||||||
|
|
@ -32,7 +31,7 @@ import { FFNetworkManager } from './ffNetworkManager';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { selectors } from '../selectors';
|
import { selectors } from '../selectors';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
import { Screencast } from '../browserContext';
|
import { Screencast, BrowserContext } from '../browserContext';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ export class FFPage implements PageDelegate {
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
this._page = new Page(this, browserContext);
|
this._page = new Page(this, browserContext);
|
||||||
this._networkManager = new FFNetworkManager(session, this._page);
|
this._networkManager = new FFNetworkManager(session, this._page);
|
||||||
this._page.on(Events.Page.FrameDetached, frame => this._removeContextsForFrame(frame));
|
this._page.on(Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
|
||||||
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
|
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
|
helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
|
||||||
|
|
@ -179,7 +178,7 @@ export class FFPage implements PageDelegate {
|
||||||
const message = params.message.startsWith('Error: ') ? params.message.substring(7) : params.message;
|
const message = params.message.startsWith('Error: ') ? params.message.substring(7) : params.message;
|
||||||
const error = new Error(message);
|
const error = new Error(message);
|
||||||
error.stack = params.stack;
|
error.stack = params.stack;
|
||||||
this._page.emit(Events.Page.PageError, error);
|
this._page.emit(Page.Events.PageError, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onConsole(payload: Protocol.Runtime.consolePayload) {
|
_onConsole(payload: Protocol.Runtime.consolePayload) {
|
||||||
|
|
@ -189,7 +188,7 @@ export class FFPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||||
params.type,
|
params.type,
|
||||||
params.message,
|
params.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
|
|
@ -262,7 +261,7 @@ export class FFPage implements PageDelegate {
|
||||||
_onScreencastStarted(event: Protocol.Page.screencastStartedPayload) {
|
_onScreencastStarted(event: Protocol.Page.screencastStartedPayload) {
|
||||||
const screencast = new Screencast(event.file, this._page);
|
const screencast = new Screencast(event.file, this._page);
|
||||||
this._idToScreencast.set(event.uid, screencast);
|
this._idToScreencast.set(event.uid, screencast);
|
||||||
this._browserContext.emit(Events.BrowserContext.ScreencastStarted, screencast);
|
this._browserContext.emit(BrowserContext.Events.ScreencastStarted, screencast);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onScreencastStopped(event: Protocol.Page.screencastStoppedPayload) {
|
_onScreencastStopped(event: Protocol.Page.screencastStoppedPayload) {
|
||||||
|
|
@ -270,7 +269,7 @@ export class FFPage implements PageDelegate {
|
||||||
if (!screencast)
|
if (!screencast)
|
||||||
return;
|
return;
|
||||||
this._idToScreencast.delete(event.uid);
|
this._idToScreencast.delete(event.uid);
|
||||||
this._browserContext.emit(Events.BrowserContext.ScreencastStopped, screencast);
|
this._browserContext.emit(BrowserContext.Events.ScreencastStopped, screencast);
|
||||||
}
|
}
|
||||||
|
|
||||||
async exposeBinding(binding: PageBinding) {
|
async exposeBinding(binding: PageBinding) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import { ConsoleMessage } from './console';
|
import { ConsoleMessage } from './console';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import { Events } from './events';
|
|
||||||
import { assert, helper, RegisteredListener, debugLogger } from './helper';
|
import { assert, helper, RegisteredListener, debugLogger } from './helper';
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
|
|
@ -50,7 +49,6 @@ type ConsoleTagHandler = () => void;
|
||||||
|
|
||||||
export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame}, ...args: any) => any;
|
export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame}, ...args: any) => any;
|
||||||
|
|
||||||
export const kNavigationEvent = Symbol('navigation');
|
|
||||||
export type NavigationEvent = {
|
export type NavigationEvent = {
|
||||||
// New frame url after navigation.
|
// New frame url after navigation.
|
||||||
url: string,
|
url: string,
|
||||||
|
|
@ -63,8 +61,6 @@ export type NavigationEvent = {
|
||||||
// the navigation did not commit.
|
// the navigation did not commit.
|
||||||
error?: Error,
|
error?: Error,
|
||||||
};
|
};
|
||||||
export const kAddLifecycleEvent = Symbol('addLifecycle');
|
|
||||||
export const kRemoveLifecycleEvent = Symbol('removeLifecycle');
|
|
||||||
|
|
||||||
export class FrameManager {
|
export class FrameManager {
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
|
|
@ -120,7 +116,7 @@ export class FrameManager {
|
||||||
assert(!this._frames.has(frameId));
|
assert(!this._frames.has(frameId));
|
||||||
const frame = new Frame(this._page, frameId, parentFrame);
|
const frame = new Frame(this._page, frameId, parentFrame);
|
||||||
this._frames.set(frameId, frame);
|
this._frames.set(frameId, frame);
|
||||||
this._page.emit(Events.Page.FrameAttached, frame);
|
this._page.emit(Page.Events.FrameAttached, frame);
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,10 +193,10 @@ export class FrameManager {
|
||||||
|
|
||||||
frame._onClearLifecycle();
|
frame._onClearLifecycle();
|
||||||
const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument };
|
const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument };
|
||||||
frame._eventEmitter.emit(kNavigationEvent, navigationEvent);
|
frame.emit(Frame.Events.Navigation, navigationEvent);
|
||||||
if (!initial) {
|
if (!initial) {
|
||||||
debugLogger.log('api', ` navigated to "${url}"`);
|
debugLogger.log('api', ` navigated to "${url}"`);
|
||||||
this._page.emit(Events.Page.FrameNavigated, frame);
|
this._page.emit(Page.Events.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
// Restore pending if any - see comments above about keepPending.
|
// Restore pending if any - see comments above about keepPending.
|
||||||
frame._pendingDocument = keepPending;
|
frame._pendingDocument = keepPending;
|
||||||
|
|
@ -212,9 +208,9 @@ export class FrameManager {
|
||||||
return;
|
return;
|
||||||
frame._url = url;
|
frame._url = url;
|
||||||
const navigationEvent: NavigationEvent = { url, name: frame._name };
|
const navigationEvent: NavigationEvent = { url, name: frame._name };
|
||||||
frame._eventEmitter.emit(kNavigationEvent, navigationEvent);
|
frame.emit(Frame.Events.Navigation, navigationEvent);
|
||||||
debugLogger.log('api', ` navigated to "${url}"`);
|
debugLogger.log('api', ` navigated to "${url}"`);
|
||||||
this._page.emit(Events.Page.FrameNavigated, frame);
|
this._page.emit(Page.Events.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
frameAbortedNavigation(frameId: string, errorText: string, documentId?: string) {
|
frameAbortedNavigation(frameId: string, errorText: string, documentId?: string) {
|
||||||
|
|
@ -230,7 +226,7 @@ export class FrameManager {
|
||||||
error: new Error(errorText),
|
error: new Error(errorText),
|
||||||
};
|
};
|
||||||
frame._pendingDocument = undefined;
|
frame._pendingDocument = undefined;
|
||||||
frame._eventEmitter.emit(kNavigationEvent, navigationEvent);
|
frame.emit(Frame.Events.Navigation, navigationEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
frameDetached(frameId: string) {
|
frameDetached(frameId: string) {
|
||||||
|
|
@ -266,13 +262,13 @@ export class FrameManager {
|
||||||
|
|
||||||
requestReceivedResponse(response: network.Response) {
|
requestReceivedResponse(response: network.Response) {
|
||||||
if (!response.request()._isFavicon)
|
if (!response.request()._isFavicon)
|
||||||
this._page.emit(Events.Page.Response, response);
|
this._page.emit(Page.Events.Response, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestFinished(request: network.Request) {
|
requestFinished(request: network.Request) {
|
||||||
this._inflightRequestFinished(request);
|
this._inflightRequestFinished(request);
|
||||||
if (!request._isFavicon)
|
if (!request._isFavicon)
|
||||||
this._page.emit(Events.Page.RequestFinished, request);
|
this._page.emit(Page.Events.RequestFinished, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestFailed(request: network.Request, canceled: boolean) {
|
requestFailed(request: network.Request, canceled: boolean) {
|
||||||
|
|
@ -285,7 +281,7 @@ export class FrameManager {
|
||||||
this.frameAbortedNavigation(frame._id, errorText, frame._pendingDocument.documentId);
|
this.frameAbortedNavigation(frame._id, errorText, frame._pendingDocument.documentId);
|
||||||
}
|
}
|
||||||
if (!request._isFavicon)
|
if (!request._isFavicon)
|
||||||
this._page.emit(Events.Page.RequestFailed, request);
|
this._page.emit(Page.Events.RequestFailed, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeChildFramesRecursively(frame: Frame) {
|
removeChildFramesRecursively(frame: Frame) {
|
||||||
|
|
@ -297,7 +293,7 @@ export class FrameManager {
|
||||||
this.removeChildFramesRecursively(frame);
|
this.removeChildFramesRecursively(frame);
|
||||||
frame._onDetached();
|
frame._onDetached();
|
||||||
this._frames.delete(frame._id);
|
this._frames.delete(frame._id);
|
||||||
this._page.emit(Events.Page.FrameDetached, frame);
|
this._page.emit(Page.Events.FrameDetached, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _inflightRequestFinished(request: network.Request) {
|
private _inflightRequestFinished(request: network.Request) {
|
||||||
|
|
@ -333,8 +329,13 @@ export class FrameManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Frame {
|
export class Frame extends EventEmitter {
|
||||||
readonly _eventEmitter: EventEmitter;
|
static Events = {
|
||||||
|
Navigation: 'navigation',
|
||||||
|
AddLifecycle: 'addlifecycle',
|
||||||
|
RemoveLifecycle: 'removelifecycle',
|
||||||
|
};
|
||||||
|
|
||||||
_id: string;
|
_id: string;
|
||||||
private _firedLifecycleEvents = new Set<types.LifecycleEvent>();
|
private _firedLifecycleEvents = new Set<types.LifecycleEvent>();
|
||||||
_subtreeLifecycleEvents = new Set<types.LifecycleEvent>();
|
_subtreeLifecycleEvents = new Set<types.LifecycleEvent>();
|
||||||
|
|
@ -354,8 +355,8 @@ export class Frame {
|
||||||
private _detachedCallback = () => {};
|
private _detachedCallback = () => {};
|
||||||
|
|
||||||
constructor(page: Page, id: string, parentFrame: Frame | null) {
|
constructor(page: Page, id: string, parentFrame: Frame | null) {
|
||||||
this._eventEmitter = new EventEmitter();
|
super();
|
||||||
this._eventEmitter.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._page = page;
|
this._page = page;
|
||||||
this._parentFrame = parentFrame;
|
this._parentFrame = parentFrame;
|
||||||
|
|
@ -406,18 +407,18 @@ export class Frame {
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
// Checking whether we have already notified about this event.
|
// Checking whether we have already notified about this event.
|
||||||
if (!this._subtreeLifecycleEvents.has(event)) {
|
if (!this._subtreeLifecycleEvents.has(event)) {
|
||||||
this._eventEmitter.emit(kAddLifecycleEvent, event);
|
this.emit(Frame.Events.AddLifecycle, event);
|
||||||
if (this === mainFrame && this._url !== 'about:blank')
|
if (this === mainFrame && this._url !== 'about:blank')
|
||||||
debugLogger.log('api', ` "${event}" event fired`);
|
debugLogger.log('api', ` "${event}" event fired`);
|
||||||
if (this === mainFrame && event === 'load')
|
if (this === mainFrame && event === 'load')
|
||||||
this._page.emit(Events.Page.Load);
|
this._page.emit(Page.Events.Load);
|
||||||
if (this === mainFrame && event === 'domcontentloaded')
|
if (this === mainFrame && event === 'domcontentloaded')
|
||||||
this._page.emit(Events.Page.DOMContentLoaded);
|
this._page.emit(Page.Events.DOMContentLoaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const event of this._subtreeLifecycleEvents) {
|
for (const event of this._subtreeLifecycleEvents) {
|
||||||
if (!events.has(event))
|
if (!events.has(event))
|
||||||
this._eventEmitter.emit(kRemoveLifecycleEvent, event);
|
this.emit(Frame.Events.RemoveLifecycle, event);
|
||||||
}
|
}
|
||||||
this._subtreeLifecycleEvents = events;
|
this._subtreeLifecycleEvents = events;
|
||||||
}
|
}
|
||||||
|
|
@ -436,13 +437,13 @@ export class Frame {
|
||||||
}
|
}
|
||||||
url = helper.completeUserURL(url);
|
url = helper.completeUserURL(url);
|
||||||
|
|
||||||
const sameDocument = helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (e: NavigationEvent) => !e.newDocument);
|
const sameDocument = helper.waitForEvent(progress, this, Frame.Events.Navigation, (e: NavigationEvent) => !e.newDocument);
|
||||||
const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
|
const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
|
||||||
|
|
||||||
let event: NavigationEvent;
|
let event: NavigationEvent;
|
||||||
if (navigateResult.newDocumentId) {
|
if (navigateResult.newDocumentId) {
|
||||||
sameDocument.dispose();
|
sameDocument.dispose();
|
||||||
event = await helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (event: NavigationEvent) => {
|
event = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
|
||||||
// We are interested either in this specific document, or any other document that
|
// We are interested either in this specific document, or any other document that
|
||||||
// did commit and replaced the expected document.
|
// did commit and replaced the expected document.
|
||||||
return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
|
return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
|
||||||
|
|
@ -460,7 +461,7 @@ export class Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
||||||
await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
||||||
|
|
||||||
const request = event.newDocument ? event.newDocument.request : undefined;
|
const request = event.newDocument ? event.newDocument.request : undefined;
|
||||||
const response = request ? request._finalRequest().response() : null;
|
const response = request ? request._finalRequest().response() : null;
|
||||||
|
|
@ -474,7 +475,7 @@ export class Frame {
|
||||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
progress.log(`waiting for navigation until "${waitUntil}"`);
|
progress.log(`waiting for navigation until "${waitUntil}"`);
|
||||||
|
|
||||||
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (event: NavigationEvent) => {
|
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
|
||||||
// Any failed navigation results in a rejection.
|
// Any failed navigation results in a rejection.
|
||||||
if (event.error)
|
if (event.error)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -485,7 +486,7 @@ export class Frame {
|
||||||
throw navigationEvent.error;
|
throw navigationEvent.error;
|
||||||
|
|
||||||
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
||||||
await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
||||||
|
|
||||||
const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
|
const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined;
|
||||||
return request ? request._finalRequest().response() : null;
|
return request ? request._finalRequest().response() : null;
|
||||||
|
|
@ -499,7 +500,7 @@ export class Frame {
|
||||||
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
|
async _waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
|
||||||
const waitUntil = verifyLifecycle('state', state);
|
const waitUntil = verifyLifecycle('state', state);
|
||||||
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
||||||
await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async frameElement(): Promise<dom.ElementHandle> {
|
async frameElement(): Promise<dom.ElementHandle> {
|
||||||
|
|
@ -752,7 +753,7 @@ export class Frame {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
const errorPromise = new Promise(resolve => {
|
const errorPromise = new Promise(resolve => {
|
||||||
listeners.push(helper.addEventListener(this._page, Events.Page.Console, (message: ConsoleMessage) => {
|
listeners.push(helper.addEventListener(this._page, Page.Events.Console, (message: ConsoleMessage) => {
|
||||||
if (message.type() === 'error' && message.text().includes('Content Security Policy')) {
|
if (message.type() === 'error' && message.text().includes('Content Security Policy')) {
|
||||||
cspMessage = message;
|
cspMessage = message;
|
||||||
resolve();
|
resolve();
|
||||||
|
|
@ -1045,7 +1046,7 @@ class SignalBarrier {
|
||||||
|
|
||||||
async addFrameNavigation(frame: Frame) {
|
async addFrameNavigation(frame: Frame) {
|
||||||
this.retain();
|
this.retain();
|
||||||
const waiter = helper.waitForEvent(null, frame._eventEmitter, kNavigationEvent, (e: NavigationEvent) => {
|
const waiter = helper.waitForEvent(null, frame, Frame.Events.Navigation, (e: NavigationEvent) => {
|
||||||
if (!e.error && this._progress)
|
if (!e.error && this._progress)
|
||||||
this._progress.log(` navigated to "${frame._url}"`);
|
this._progress.log(` navigated to "${frame._url}"`);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
48
src/page.ts
48
src/page.ts
|
|
@ -24,7 +24,6 @@ import * as network from './network';
|
||||||
import { Screenshotter } from './screenshotter';
|
import { Screenshotter } from './screenshotter';
|
||||||
import { TimeoutSettings } from './timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { Events } from './events';
|
|
||||||
import { BrowserContext } from './browserContext';
|
import { BrowserContext } from './browserContext';
|
||||||
import { ConsoleMessage } from './console';
|
import { ConsoleMessage } from './console';
|
||||||
import * as accessibility from './accessibility';
|
import * as accessibility from './accessibility';
|
||||||
|
|
@ -91,6 +90,29 @@ type PageState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Page extends EventEmitter {
|
export class Page extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Close: 'close',
|
||||||
|
Crash: 'crash',
|
||||||
|
Console: 'console',
|
||||||
|
Dialog: 'dialog',
|
||||||
|
Download: 'download',
|
||||||
|
FileChooser: 'filechooser',
|
||||||
|
DOMContentLoaded: 'domcontentloaded',
|
||||||
|
// Can't use just 'error' due to node.js special treatment of error events.
|
||||||
|
// @see https://nodejs.org/api/events.html#events_error_events
|
||||||
|
PageError: 'pageerror',
|
||||||
|
Request: 'request',
|
||||||
|
Response: 'response',
|
||||||
|
RequestFailed: 'requestfailed',
|
||||||
|
RequestFinished: 'requestfinished',
|
||||||
|
FrameAttached: 'frameattached',
|
||||||
|
FrameDetached: 'framedetached',
|
||||||
|
FrameNavigated: 'framenavigated',
|
||||||
|
Load: 'load',
|
||||||
|
Popup: 'popup',
|
||||||
|
Worker: 'worker',
|
||||||
|
};
|
||||||
|
|
||||||
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
||||||
private _closedCallback: () => void;
|
private _closedCallback: () => void;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
|
|
@ -154,13 +176,13 @@ export class Page extends EventEmitter {
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
assert(this._closedState !== 'closed', 'Page closed twice');
|
assert(this._closedState !== 'closed', 'Page closed twice');
|
||||||
this._closedState = 'closed';
|
this._closedState = 'closed';
|
||||||
this.emit(Events.Page.Close);
|
this.emit(Page.Events.Close);
|
||||||
this._closedCallback();
|
this._closedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
_didCrash() {
|
_didCrash() {
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
this.emit(Events.Page.Crash);
|
this.emit(Page.Events.Crash);
|
||||||
this._crashedCallback(new Error('Page crashed'));
|
this._crashedCallback(new Error('Page crashed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,12 +201,12 @@ export class Page extends EventEmitter {
|
||||||
|
|
||||||
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||||
const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
|
const multiple = await handle.evaluate(element => !!(element as HTMLInputElement).multiple);
|
||||||
if (!this.listenerCount(Events.Page.FileChooser)) {
|
if (!this.listenerCount(Page.Events.FileChooser)) {
|
||||||
handle.dispose();
|
handle.dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fileChooser = new FileChooser(this, handle, multiple);
|
const fileChooser = new FileChooser(this, handle, multiple);
|
||||||
this.emit(Events.Page.FileChooser, fileChooser);
|
this.emit(Page.Events.FileChooser, fileChooser);
|
||||||
}
|
}
|
||||||
|
|
||||||
context(): BrowserContext {
|
context(): BrowserContext {
|
||||||
|
|
@ -235,10 +257,10 @@ export class Page extends EventEmitter {
|
||||||
_addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) {
|
_addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) {
|
||||||
const message = new ConsoleMessage(type, text, args, location);
|
const message = new ConsoleMessage(type, text, args, location);
|
||||||
const intercepted = this._frameManager.interceptConsoleMessage(message);
|
const intercepted = this._frameManager.interceptConsoleMessage(message);
|
||||||
if (intercepted || !this.listenerCount(Events.Page.Console))
|
if (intercepted || !this.listenerCount(Page.Events.Console))
|
||||||
args.forEach(arg => arg.dispose());
|
args.forEach(arg => arg.dispose());
|
||||||
else
|
else
|
||||||
this.emit(Events.Page.Console, message);
|
this.emit(Page.Events.Console, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(options?: types.NavigateOptions): Promise<network.Response | null> {
|
async reload(options?: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
|
@ -315,7 +337,7 @@ export class Page extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestStarted(request: network.Request) {
|
_requestStarted(request: network.Request) {
|
||||||
this.emit(Events.Page.Request, request);
|
this.emit(Page.Events.Request, request);
|
||||||
const route = request._route();
|
const route = request._route();
|
||||||
if (!route)
|
if (!route)
|
||||||
return;
|
return;
|
||||||
|
|
@ -362,20 +384,20 @@ export class Page extends EventEmitter {
|
||||||
|
|
||||||
_addWorker(workerId: string, worker: Worker) {
|
_addWorker(workerId: string, worker: Worker) {
|
||||||
this._workers.set(workerId, worker);
|
this._workers.set(workerId, worker);
|
||||||
this.emit(Events.Page.Worker, worker);
|
this.emit(Page.Events.Worker, worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeWorker(workerId: string) {
|
_removeWorker(workerId: string) {
|
||||||
const worker = this._workers.get(workerId);
|
const worker = this._workers.get(workerId);
|
||||||
if (!worker)
|
if (!worker)
|
||||||
return;
|
return;
|
||||||
worker.emit(Events.Worker.Close, worker);
|
worker.emit(Worker.Events.Close, worker);
|
||||||
this._workers.delete(workerId);
|
this._workers.delete(workerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearWorkers() {
|
_clearWorkers() {
|
||||||
for (const [workerId, worker] of this._workers) {
|
for (const [workerId, worker] of this._workers) {
|
||||||
worker.emit(Events.Worker.Close, worker);
|
worker.emit(Worker.Events.Close, worker);
|
||||||
this._workers.delete(workerId);
|
this._workers.delete(workerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -386,6 +408,10 @@ export class Page extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Worker extends EventEmitter {
|
export class Worker extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Close: 'close',
|
||||||
|
};
|
||||||
|
|
||||||
private _url: string;
|
private _url: string;
|
||||||
private _executionContextPromise: Promise<js.ExecutionContext>;
|
private _executionContextPromise: Promise<js.ExecutionContext>;
|
||||||
private _executionContextCallback: (value?: js.ExecutionContext) => void;
|
private _executionContextCallback: (value?: js.ExecutionContext) => void;
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { Events } from '../../events';
|
|
||||||
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
|
||||||
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
|
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
|
||||||
import * as channels from '../channels';
|
import * as channels from '../channels';
|
||||||
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||||
import { CRBrowserContext } from '../../chromium/crBrowser';
|
import { CRBrowserContext } from '../../chromium/crBrowser';
|
||||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||||
import { Events as ChromiumEvents } from '../../chromium/events';
|
|
||||||
|
|
||||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
|
@ -33,8 +31,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
|
|
||||||
for (const page of context.pages())
|
for (const page of context.pages())
|
||||||
this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) });
|
this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) });
|
||||||
context.on(Events.BrowserContext.Page, page => this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) }));
|
context.on(BrowserContext.Events.Page, page => this._dispatchEvent('page', { page: new PageDispatcher(this._scope, page) }));
|
||||||
context.on(Events.BrowserContext.Close, () => {
|
context.on(BrowserContext.Events.Close, () => {
|
||||||
this._dispatchEvent('close');
|
this._dispatchEvent('close');
|
||||||
this._dispose();
|
this._dispose();
|
||||||
});
|
});
|
||||||
|
|
@ -42,10 +40,10 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
if (context._browser._options.name === 'chromium') {
|
if (context._browser._options.name === 'chromium') {
|
||||||
for (const page of (context as CRBrowserContext).backgroundPages())
|
for (const page of (context as CRBrowserContext).backgroundPages())
|
||||||
this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) });
|
this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) });
|
||||||
context.on(ChromiumEvents.ChromiumBrowserContext.BackgroundPage, page => this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) }));
|
context.on(CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('crBackgroundPage', { page: new PageDispatcher(this._scope, page) }));
|
||||||
for (const serviceWorker of (context as CRBrowserContext).serviceWorkers())
|
for (const serviceWorker of (context as CRBrowserContext).serviceWorkers())
|
||||||
this._dispatchEvent('crServiceWorker', new WorkerDispatcher(this._scope, serviceWorker));
|
this._dispatchEvent('crServiceWorker', new WorkerDispatcher(this._scope, serviceWorker));
|
||||||
context.on(ChromiumEvents.ChromiumBrowserContext.ServiceWorker, serviceWorker => this._dispatchEvent('crServiceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) }));
|
context.on(CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('crServiceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Browser } from '../../browser';
|
import { Browser } from '../../browser';
|
||||||
import { Events } from '../../events';
|
|
||||||
import * as channels from '../channels';
|
import * as channels from '../channels';
|
||||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||||
|
|
@ -26,7 +25,7 @@ import { PageDispatcher } from './pageDispatcher';
|
||||||
export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserInitializer> implements channels.BrowserChannel {
|
export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserInitializer> implements channels.BrowserChannel {
|
||||||
constructor(scope: DispatcherScope, browser: Browser, guid?: string) {
|
constructor(scope: DispatcherScope, browser: Browser, guid?: string) {
|
||||||
super(scope, browser, 'Browser', { version: browser.version() }, true, guid);
|
super(scope, browser, 'Browser', { version: browser.version() }, true, guid);
|
||||||
browser.on(Events.Browser.Disconnected, () => this._didClose());
|
browser.on(Browser.Events.Disconnected, () => this._didClose());
|
||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
|
||||||
import { Electron, ElectronApplication, ElectronEvents, ElectronPage } from '../../server/electron';
|
import { Electron, ElectronApplication, ElectronPage } from '../../server/electron';
|
||||||
import * as channels from '../channels';
|
import * as channels from '../channels';
|
||||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||||
import { PageDispatcher } from './pageDispatcher';
|
import { PageDispatcher } from './pageDispatcher';
|
||||||
|
|
@ -37,11 +37,11 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
|
||||||
constructor(scope: DispatcherScope, electronApplication: ElectronApplication) {
|
constructor(scope: DispatcherScope, electronApplication: ElectronApplication) {
|
||||||
super(scope, electronApplication, 'ElectronApplication', {}, true);
|
super(scope, electronApplication, 'ElectronApplication', {}, true);
|
||||||
this._dispatchEvent('context', { context: new BrowserContextDispatcher(this._scope, electronApplication.context()) });
|
this._dispatchEvent('context', { context: new BrowserContextDispatcher(this._scope, electronApplication.context()) });
|
||||||
electronApplication.on(ElectronEvents.ElectronApplication.Close, () => {
|
electronApplication.on(ElectronApplication.Events.Close, () => {
|
||||||
this._dispatchEvent('close');
|
this._dispatchEvent('close');
|
||||||
this._dispose();
|
this._dispose();
|
||||||
});
|
});
|
||||||
electronApplication.on(ElectronEvents.ElectronApplication.Window, (page: ElectronPage) => {
|
electronApplication.on(ElectronApplication.Events.Window, (page: ElectronPage) => {
|
||||||
this._dispatchEvent('window', {
|
this._dispatchEvent('window', {
|
||||||
page: lookupDispatcher<PageDispatcher>(page),
|
page: lookupDispatcher<PageDispatcher>(page),
|
||||||
browserWindow: createHandle(this._scope, page.browserWindow),
|
browserWindow: createHandle(this._scope, page.browserWindow),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent, kNavigationEvent, NavigationEvent } from '../../frames';
|
import { Frame, NavigationEvent } from '../../frames';
|
||||||
import * as types from '../../types';
|
import * as types from '../../types';
|
||||||
import * as channels from '../channels';
|
import * as channels from '../channels';
|
||||||
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
|
||||||
|
|
@ -38,13 +38,13 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
|
||||||
loadStates: Array.from(frame._subtreeLifecycleEvents),
|
loadStates: Array.from(frame._subtreeLifecycleEvents),
|
||||||
});
|
});
|
||||||
this._frame = frame;
|
this._frame = frame;
|
||||||
frame._eventEmitter.on(kAddLifecycleEvent, (event: types.LifecycleEvent) => {
|
frame.on(Frame.Events.AddLifecycle, (event: types.LifecycleEvent) => {
|
||||||
this._dispatchEvent('loadstate', { add: event });
|
this._dispatchEvent('loadstate', { add: event });
|
||||||
});
|
});
|
||||||
frame._eventEmitter.on(kRemoveLifecycleEvent, (event: types.LifecycleEvent) => {
|
frame.on(Frame.Events.RemoveLifecycle, (event: types.LifecycleEvent) => {
|
||||||
this._dispatchEvent('loadstate', { remove: event });
|
this._dispatchEvent('loadstate', { remove: event });
|
||||||
});
|
});
|
||||||
frame._eventEmitter.on(kNavigationEvent, (event: NavigationEvent) => {
|
frame.on(Frame.Events.Navigation, (event: NavigationEvent) => {
|
||||||
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined };
|
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined };
|
||||||
if (event.newDocument)
|
if (event.newDocument)
|
||||||
(params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) };
|
(params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) };
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { Events } from '../../events';
|
|
||||||
import { Frame } from '../../frames';
|
import { Frame } from '../../frames';
|
||||||
import { Request } from '../../network';
|
import { Request } from '../../network';
|
||||||
import { Page, Worker } from '../../page';
|
import { Page, Worker } from '../../page';
|
||||||
|
|
@ -44,29 +43,29 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
|
||||||
isClosed: page.isClosed()
|
isClosed: page.isClosed()
|
||||||
});
|
});
|
||||||
this._page = page;
|
this._page = page;
|
||||||
page.on(Events.Page.Close, () => this._dispatchEvent('close'));
|
page.on(Page.Events.Close, () => this._dispatchEvent('close'));
|
||||||
page.on(Events.Page.Console, message => this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(this._scope, message) }));
|
page.on(Page.Events.Console, message => this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(this._scope, message) }));
|
||||||
page.on(Events.Page.Crash, () => this._dispatchEvent('crash'));
|
page.on(Page.Events.Crash, () => this._dispatchEvent('crash'));
|
||||||
page.on(Events.Page.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded'));
|
page.on(Page.Events.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded'));
|
||||||
page.on(Events.Page.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this._scope, dialog) }));
|
page.on(Page.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this._scope, dialog) }));
|
||||||
page.on(Events.Page.Download, dialog => this._dispatchEvent('download', { download: new DownloadDispatcher(this._scope, dialog) }));
|
page.on(Page.Events.Download, dialog => this._dispatchEvent('download', { download: new DownloadDispatcher(this._scope, dialog) }));
|
||||||
this._page.on(Events.Page.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
|
this._page.on(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
|
||||||
element: new ElementHandleDispatcher(this._scope, fileChooser.element()),
|
element: new ElementHandleDispatcher(this._scope, fileChooser.element()),
|
||||||
isMultiple: fileChooser.isMultiple()
|
isMultiple: fileChooser.isMultiple()
|
||||||
}));
|
}));
|
||||||
page.on(Events.Page.FrameAttached, frame => this._onFrameAttached(frame));
|
page.on(Page.Events.FrameAttached, frame => this._onFrameAttached(frame));
|
||||||
page.on(Events.Page.FrameDetached, frame => this._onFrameDetached(frame));
|
page.on(Page.Events.FrameDetached, frame => this._onFrameDetached(frame));
|
||||||
page.on(Events.Page.Load, () => this._dispatchEvent('load'));
|
page.on(Page.Events.Load, () => this._dispatchEvent('load'));
|
||||||
page.on(Events.Page.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) }));
|
page.on(Page.Events.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) }));
|
||||||
page.on(Events.Page.Popup, page => this._dispatchEvent('popup', { page: lookupDispatcher<PageDispatcher>(page) }));
|
page.on(Page.Events.Popup, page => this._dispatchEvent('popup', { page: lookupDispatcher<PageDispatcher>(page) }));
|
||||||
page.on(Events.Page.Request, request => this._dispatchEvent('request', { request: RequestDispatcher.from(this._scope, request) }));
|
page.on(Page.Events.Request, request => this._dispatchEvent('request', { request: RequestDispatcher.from(this._scope, request) }));
|
||||||
page.on(Events.Page.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', {
|
page.on(Page.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', {
|
||||||
request: RequestDispatcher.from(this._scope, request),
|
request: RequestDispatcher.from(this._scope, request),
|
||||||
failureText: request._failureText
|
failureText: request._failureText
|
||||||
}));
|
}));
|
||||||
page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', { request: RequestDispatcher.from(scope, request) }));
|
page.on(Page.Events.RequestFinished, request => this._dispatchEvent('requestFinished', { request: RequestDispatcher.from(scope, request) }));
|
||||||
page.on(Events.Page.Response, response => this._dispatchEvent('response', { response: new ResponseDispatcher(this._scope, response) }));
|
page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: new ResponseDispatcher(this._scope, response) }));
|
||||||
page.on(Events.Page.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
|
page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultNavigationTimeoutNoReply(params: channels.PageSetDefaultNavigationTimeoutNoReplyParams): Promise<void> {
|
async setDefaultNavigationTimeoutNoReply(params: channels.PageSetDefaultNavigationTimeoutNoReplyParams): Promise<void> {
|
||||||
|
|
@ -232,7 +231,7 @@ export class WorkerDispatcher extends Dispatcher<Worker, channels.WorkerInitiali
|
||||||
super(scope, worker, 'Worker', {
|
super(scope, worker, 'Worker', {
|
||||||
url: worker.url()
|
url: worker.url()
|
||||||
});
|
});
|
||||||
worker.on(Events.Worker.Close, () => this._dispatchEvent('close'));
|
worker.on(Worker.Events.Close, () => this._dispatchEvent('close'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpression(params: channels.WorkerEvaluateExpressionParams): Promise<channels.WorkerEvaluateExpressionResult> {
|
async evaluateExpression(params: channels.WorkerEvaluateExpressionParams): Promise<channels.WorkerEvaluateExpressionResult> {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import * as path from 'path';
|
||||||
import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser';
|
import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser';
|
||||||
import { CRConnection, CRSession } from '../chromium/crConnection';
|
import { CRConnection, CRSession } from '../chromium/crConnection';
|
||||||
import { CRExecutionContext } from '../chromium/crExecutionContext';
|
import { CRExecutionContext } from '../chromium/crExecutionContext';
|
||||||
import { Events } from '../events';
|
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { TimeoutSettings } from '../timeoutSettings';
|
import { TimeoutSettings } from '../timeoutSettings';
|
||||||
|
|
@ -42,19 +41,17 @@ export type ElectronLaunchOptionsBase = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ElectronEvents = {
|
|
||||||
ElectronApplication: {
|
|
||||||
Close: 'close',
|
|
||||||
Window: 'window',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ElectronPage extends Page {
|
export interface ElectronPage extends Page {
|
||||||
browserWindow: js.JSHandle<BrowserWindow>;
|
browserWindow: js.JSHandle<BrowserWindow>;
|
||||||
_browserWindowId: number;
|
_browserWindowId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronApplication extends EventEmitter {
|
export class ElectronApplication extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
Close: 'close',
|
||||||
|
Window: 'window',
|
||||||
|
};
|
||||||
|
|
||||||
private _browserContext: CRBrowserContext;
|
private _browserContext: CRBrowserContext;
|
||||||
private _nodeConnection: CRConnection;
|
private _nodeConnection: CRConnection;
|
||||||
private _nodeSession: CRSession;
|
private _nodeSession: CRSession;
|
||||||
|
|
@ -67,11 +64,11 @@ export class ElectronApplication extends EventEmitter {
|
||||||
constructor(browser: CRBrowser, nodeConnection: CRConnection) {
|
constructor(browser: CRBrowser, nodeConnection: CRConnection) {
|
||||||
super();
|
super();
|
||||||
this._browserContext = browser._defaultContext as CRBrowserContext;
|
this._browserContext = browser._defaultContext as CRBrowserContext;
|
||||||
this._browserContext.on(Events.BrowserContext.Close, () => {
|
this._browserContext.on(BrowserContext.Events.Close, () => {
|
||||||
// Emit application closed after context closed.
|
// Emit application closed after context closed.
|
||||||
Promise.resolve().then(() => this.emit(ElectronEvents.ElectronApplication.Close));
|
Promise.resolve().then(() => this.emit(ElectronApplication.Events.Close));
|
||||||
});
|
});
|
||||||
this._browserContext.on(Events.BrowserContext.Page, event => this._onPage(event));
|
this._browserContext.on(BrowserContext.Events.Page, event => this._onPage(event));
|
||||||
this._nodeConnection = nodeConnection;
|
this._nodeConnection = nodeConnection;
|
||||||
this._nodeSession = nodeConnection.rootSession;
|
this._nodeSession = nodeConnection.rootSession;
|
||||||
}
|
}
|
||||||
|
|
@ -85,13 +82,13 @@ export class ElectronApplication extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
page.browserWindow = handle;
|
page.browserWindow = handle;
|
||||||
page._browserWindowId = windowId;
|
page._browserWindowId = windowId;
|
||||||
page.on(Events.Page.Close, () => {
|
page.on(Page.Events.Close, () => {
|
||||||
page.browserWindow.dispose();
|
page.browserWindow.dispose();
|
||||||
this._windows.delete(page);
|
this._windows.delete(page);
|
||||||
});
|
});
|
||||||
this._windows.add(page);
|
this._windows.add(page);
|
||||||
await page.mainFrame().waitForLoadState('domcontentloaded').catch(e => {}); // can happen after detach
|
await page.mainFrame().waitForLoadState('domcontentloaded').catch(e => {}); // can happen after detach
|
||||||
this.emit(ElectronEvents.ElectronApplication.Window, page);
|
this.emit(ElectronApplication.Events.Window, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
async newBrowserWindow(options: any): Promise<Page> {
|
async newBrowserWindow(options: any): Promise<Page> {
|
||||||
|
|
@ -106,7 +103,7 @@ export class ElectronApplication extends EventEmitter {
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this._waitForEvent(ElectronEvents.ElectronApplication.Window, (page: ElectronPage) => page._browserWindowId === windowId);
|
return await this._waitForEvent(ElectronApplication.Events.Window, (page: ElectronPage) => page._browserWindowId === windowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
context(): BrowserContext {
|
context(): BrowserContext {
|
||||||
|
|
@ -114,7 +111,7 @@ export class ElectronApplication extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
const closed = this._waitForEvent(ElectronEvents.ElectronApplication.Close);
|
const closed = this._waitForEvent(ElectronApplication.Events.Close);
|
||||||
await this._nodeElectronHandle!.evaluate(({ app }) => app.quit());
|
await this._nodeElectronHandle!.evaluate(({ app }) => app.quit());
|
||||||
this._nodeConnection.close();
|
this._nodeConnection.close();
|
||||||
await closed;
|
await closed;
|
||||||
|
|
@ -122,7 +119,7 @@ export class ElectronApplication extends EventEmitter {
|
||||||
|
|
||||||
private async _waitForEvent(event: string, predicate?: Function): Promise<any> {
|
private async _waitForEvent(event: string, predicate?: Function): Promise<any> {
|
||||||
const progressController = new ProgressController(this._timeoutSettings.timeout({}));
|
const progressController = new ProgressController(this._timeoutSettings.timeout({}));
|
||||||
if (event !== ElectronEvents.ElectronApplication.Close)
|
if (event !== ElectronApplication.Events.Close)
|
||||||
this._browserContext._closePromise.then(error => progressController.abort(error));
|
this._browserContext._closePromise.then(error => progressController.abort(error));
|
||||||
return progressController.run(progress => helper.waitForEvent(progress, this, event, predicate).promise);
|
return progressController.run(progress => helper.waitForEvent(progress, this, event, predicate).promise);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import { Browser, BrowserOptions } from '../browser';
|
import { Browser, BrowserOptions } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
||||||
import { Events } from '../events';
|
|
||||||
import { helper, RegisteredListener, assert } from '../helper';
|
import { helper, RegisteredListener, assert } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding } from '../page';
|
import { Page, PageBinding } from '../page';
|
||||||
|
|
@ -150,13 +149,13 @@ export class WKBrowser extends Browser {
|
||||||
const page = wkPage._page;
|
const page = wkPage._page;
|
||||||
if (pageOrError instanceof Error)
|
if (pageOrError instanceof Error)
|
||||||
page._setIsError();
|
page._setIsError();
|
||||||
context!.emit(Events.BrowserContext.Page, page);
|
context!.emit(BrowserContext.Events.Page, page);
|
||||||
if (!opener)
|
if (!opener)
|
||||||
return;
|
return;
|
||||||
await opener.pageOrError();
|
await opener.pageOrError();
|
||||||
const openerPage = opener._page;
|
const openerPage = opener._page;
|
||||||
if (!openerPage.isClosed())
|
if (!openerPage.isClosed())
|
||||||
openerPage.emit(Events.Page.Popup, page);
|
openerPage.emit(Page.Events.Popup, page);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Screencast } from '../browserContext';
|
import { Screencast, BrowserContext } from '../browserContext';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { helper, RegisteredListener, assert, debugAssert } from '../helper';
|
import { helper, RegisteredListener, assert, debugAssert } from '../helper';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { WKSession } from './wkConnection';
|
import { WKSession } from './wkConnection';
|
||||||
import { Events } from '../events';
|
|
||||||
import { WKExecutionContext } from './wkExecutionContext';
|
import { WKExecutionContext } from './wkExecutionContext';
|
||||||
import { WKInterceptableRequest } from './wkInterceptableRequest';
|
import { WKInterceptableRequest } from './wkInterceptableRequest';
|
||||||
import { WKWorkers } from './wkWorkers';
|
import { WKWorkers } from './wkWorkers';
|
||||||
|
|
@ -81,7 +80,7 @@ export class WKPage implements PageDelegate {
|
||||||
this._workers = new WKWorkers(this._page);
|
this._workers = new WKWorkers(this._page);
|
||||||
this._session = undefined as any as WKSession;
|
this._session = undefined as any as WKSession;
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
this._page.on(Events.Page.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
|
this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
||||||
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
|
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
|
||||||
|
|
@ -474,7 +473,7 @@ export class WKPage implements PageDelegate {
|
||||||
} else {
|
} else {
|
||||||
error.stack = '';
|
error.stack = '';
|
||||||
}
|
}
|
||||||
this._page.emit(Events.Page.PageError, error);
|
this._page.emit(Page.Events.PageError, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,7 +523,7 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(Page.Events.Dialog, new dialog.Dialog(
|
||||||
event.type as dialog.DialogType,
|
event.type as dialog.DialogType,
|
||||||
event.message,
|
event.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
|
|
@ -718,7 +717,7 @@ export class WKPage implements PageDelegate {
|
||||||
height: options.height,
|
height: options.height,
|
||||||
scale: options.scale,
|
scale: options.scale,
|
||||||
});
|
});
|
||||||
this._browserContext.emit(Events.BrowserContext.ScreencastStarted, new Screencast(options.outputFile, this._initializedPage!));
|
this._browserContext.emit(BrowserContext.Events.ScreencastStarted, new Screencast(options.outputFile, this._initializedPage!));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._recordingVideoFile = null;
|
this._recordingVideoFile = null;
|
||||||
throw e;
|
throw e;
|
||||||
|
|
@ -731,7 +730,7 @@ export class WKPage implements PageDelegate {
|
||||||
const fileName = this._recordingVideoFile;
|
const fileName = this._recordingVideoFile;
|
||||||
this._recordingVideoFile = null;
|
this._recordingVideoFile = null;
|
||||||
await this._pageProxySession.send('Screencast.stopVideoRecording');
|
await this._pageProxySession.send('Screencast.stopVideoRecording');
|
||||||
this._browserContext.emit(Events.BrowserContext.ScreencastStopped, new Screencast(fileName, this._initializedPage!));
|
this._browserContext.emit(BrowserContext.Events.ScreencastStopped, new Screencast(fileName, this._initializedPage!));
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||||
|
|
|
||||||
|
|
@ -61,32 +61,11 @@ function traceAPICoverage(apiCoverage, api, events) {
|
||||||
* @param {string} browserName
|
* @param {string} browserName
|
||||||
*/
|
*/
|
||||||
function apiForBrowser(browserName) {
|
function apiForBrowser(browserName) {
|
||||||
const BROWSER_CONFIGS = [
|
const events = require('../lib/rpc/client/events').Events;
|
||||||
{
|
const api = require('../lib/rpc/client/api');
|
||||||
name: 'Firefox',
|
const otherBrowsers = ['chromium', 'webkit', 'firefox'].filter(name => name.toLowerCase() !== browserName.toLowerCase());
|
||||||
events: require('../lib/events').Events,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'WebKit',
|
|
||||||
events: require('../lib/events').Events,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Chromium',
|
|
||||||
events: {
|
|
||||||
...require('../lib/events').Events,
|
|
||||||
...require('../lib/chromium/events').Events,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const browserConfig = BROWSER_CONFIGS.find(config => config.name.toLowerCase() === browserName);
|
|
||||||
const events = browserConfig.events;
|
|
||||||
// TODO: we should rethink our api.ts approach to ensure coverage and async stacks.
|
|
||||||
const api = {
|
|
||||||
...require('../lib/rpc/client/api'),
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredKeys = Object.keys(api).filter(apiName => {
|
const filteredKeys = Object.keys(api).filter(apiName => {
|
||||||
return !BROWSER_CONFIGS.some(config => apiName.startsWith(config.name)) || apiName.startsWith(browserConfig.name);
|
return !otherBrowsers.some(otherName => apiName.toLowerCase().startsWith(otherName));
|
||||||
});
|
});
|
||||||
const filteredAPI = {};
|
const filteredAPI = {};
|
||||||
for (const key of filteredKeys)
|
for (const key of filteredKeys)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue