diff --git a/src/browserContext.ts b/src/browserContext.ts index e7a361b974..d9eec0aa0e 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -21,12 +21,13 @@ import { Page, PageBinding } from './page'; import { TimeoutSettings } from './timeoutSettings'; import * as types from './types'; import { Events } from './events'; -import { ExtendedEventEmitter } from './extendedEventEmitter'; import { Download } from './download'; import { BrowserBase } from './browser'; import { InnerLogger, Logger } from './logger'; import { FunctionWithSource } from './frames'; import * as debugSupport from './debug/debugSupport'; +import { EventEmitter } from 'events'; +import { ProgressController } from './progress'; type CommonContextOptions = { viewport?: types.Size | null, @@ -76,13 +77,13 @@ export interface BrowserContext { close(): Promise; } -export abstract class BrowserContextBase extends ExtendedEventEmitter implements BrowserContext { +export abstract class BrowserContextBase extends EventEmitter implements BrowserContext { readonly _timeoutSettings = new TimeoutSettings(); readonly _pageBindings = new Map(); readonly _options: BrowserContextOptions; _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; _closed = false; - private readonly _closePromise: Promise; + readonly _closePromise: Promise; private _closePromiseFulfill: ((error: Error) => void) | undefined; readonly _permissions = new Map(); readonly _downloads = new Set(); @@ -101,16 +102,12 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements await debugSupport.installConsoleHelpers(this); } - protected _abortPromiseForEvent(event: string) { - return event === Events.BrowserContext.Close ? super._abortPromiseForEvent(event) : this._closePromise; - } - - protected _getLogger(): InnerLogger { - return this._logger; - } - - protected _getTimeoutSettings(): TimeoutSettings { - return this._timeoutSettings; + async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { + const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; + const progressController = new ProgressController(this._logger, this._timeoutSettings.timeout(options)); + if (event !== Events.BrowserContext.Close) + this._closePromise.then(error => progressController.abort(error)); + return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate)); } _browserClosed() { diff --git a/src/extendedEventEmitter.ts b/src/extendedEventEmitter.ts deleted file mode 100644 index 7db61c437a..0000000000 --- a/src/extendedEventEmitter.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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. - */ - -import { EventEmitter } from 'events'; -import { helper, RegisteredListener } from './helper'; -import { ProgressController } from './progress'; -import { InnerLogger } from './logger'; -import { TimeoutSettings } from './timeoutSettings'; - -export abstract class ExtendedEventEmitter extends EventEmitter { - protected _abortPromiseForEvent(event: string) { - return new Promise(() => void 0); - } - protected abstract _getLogger(): InnerLogger; - protected abstract _getTimeoutSettings(): TimeoutSettings; - - async waitForEvent(event: string, optionsOrPredicate: Function | { predicate?: Function, timeout?: number } = {}): Promise { - const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; - const { predicate = () => true } = options; - - const progressController = new ProgressController(this._getLogger(), this._getTimeoutSettings().timeout(options)); - this._abortPromiseForEvent(event).then(error => progressController.abort(error)); - - return progressController.run(async progress => { - const listeners: RegisteredListener[] = []; - const promise = new Promise((resolve, reject) => { - listeners.push(helper.addEventListener(this, event, eventArg => { - try { - if (!predicate(eventArg)) - return; - resolve(eventArg); - } catch (e) { - reject(e); - } - })); - }); - progress.cleanupWhenAborted(() => helper.removeEventListeners(listeners)); - - const result = await promise; - helper.removeEventListeners(listeners); - return result; - }); - } -} diff --git a/src/helper.ts b/src/helper.ts index 58ca016724..e71dbe4652 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -21,6 +21,8 @@ import * as fs from 'fs'; import * as removeFolder from 'rimraf'; import * as util from 'util'; import * as types from './types'; +import { Progress } from './progress'; + const removeFolderAsync = util.promisify(removeFolder); export type RegisteredListener = { @@ -278,6 +280,25 @@ class Helper { return removeFolderAsync(dir).catch((err: Error) => console.error(err)); })); } + + static async waitForEvent(progress: Progress, emitter: EventEmitter, event: string, predicate?: Function): Promise { + const listeners: RegisteredListener[] = []; + const promise = new Promise((resolve, reject) => { + listeners.push(helper.addEventListener(emitter, event, eventArg => { + try { + if (predicate && !predicate(eventArg)) + return; + resolve(eventArg); + } catch (e) { + reject(e); + } + })); + }); + progress.cleanupWhenAborted(() => helper.removeEventListeners(listeners)); + const result = await promise; + helper.removeEventListeners(listeners); + return result; + } } export function assert(value: any, message?: string): asserts value { diff --git a/src/page.ts b/src/page.ts index 9ca36dd010..136896b95d 100644 --- a/src/page.ts +++ b/src/page.ts @@ -28,10 +28,10 @@ import { Events } from './events'; import { BrowserContext, BrowserContextBase } from './browserContext'; import { ConsoleMessage, ConsoleMessageLocation } from './console'; import * as accessibility from './accessibility'; -import { ExtendedEventEmitter } from './extendedEventEmitter'; import { EventEmitter } from 'events'; import { FileChooser } from './fileChooser'; import { logError, InnerLogger } from './logger'; +import { ProgressController } from './progress'; export interface PageDelegate { readonly rawMouse: input.RawMouse; @@ -88,7 +88,7 @@ type PageState = { extraHTTPHeaders: network.Headers | null; }; -export class Page extends ExtendedEventEmitter { +export class Page extends EventEmitter { private _closed = false; private _closedCallback: () => void; private _closedPromise: Promise; @@ -138,18 +138,6 @@ export class Page extends ExtendedEventEmitter { this.coverage = delegate.coverage ? delegate.coverage() : null; } - protected _abortPromiseForEvent(event: string) { - return this._disconnectedPromise; - } - - protected _getLogger(): InnerLogger { - return this._logger; - } - - protected _getTimeoutSettings(): TimeoutSettings { - return this._timeoutSettings; - } - _didClose() { assert(!this._closed, 'Page closed twice'); this._closed = true; @@ -337,6 +325,13 @@ export class Page extends ExtendedEventEmitter { return this.waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }); } + async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { + const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; + const progressController = new ProgressController(this._logger, this._timeoutSettings.timeout(options)); + this._disconnectedPromise.then(error => progressController.abort(error)); + return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate)); + } + async goBack(options?: types.NavigateOptions): Promise { const waitPromise = this.waitForNavigation(options); const result = await this._delegate.goBack(); diff --git a/src/server/electron.ts b/src/server/electron.ts index dca1779311..cc61bac125 100644 --- a/src/server/electron.ts +++ b/src/server/electron.ts @@ -19,7 +19,6 @@ import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser'; import { CRConnection, CRSession } from '../chromium/crConnection'; import { CRExecutionContext } from '../chromium/crExecutionContext'; import { Events } from '../events'; -import { ExtendedEventEmitter } from '../extendedEventEmitter'; import * as js from '../javascript'; import { InnerLogger, Logger } from '../logger'; import { Page } from '../page'; @@ -30,7 +29,9 @@ import { BrowserServer } from './browserServer'; import { launchProcess, waitForLine } from './processLauncher'; import { BrowserContext } from '../browserContext'; import type {BrowserWindow} from 'electron'; -import { runAbortableTask } from '../progress'; +import { runAbortableTask, ProgressController } from '../progress'; +import { EventEmitter } from 'events'; +import { helper } from '../helper'; type ElectronLaunchOptions = { args?: string[], @@ -55,7 +56,7 @@ interface ElectronPage extends Page { _browserWindowId: number; } -export class ElectronApplication extends ExtendedEventEmitter { +export class ElectronApplication extends EventEmitter { private _logger: InnerLogger; private _browserContext: CRBrowserContext; private _nodeConnection: CRConnection; @@ -128,6 +129,14 @@ export class ElectronApplication extends ExtendedEventEmitter { this._nodeConnection.close(); } + async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { + const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate; + const progressController = new ProgressController(this._logger, this._timeoutSettings.timeout(options)); + if (event !== ElectronEvents.ElectronApplication.Close) + this._browserContext._closePromise.then(error => progressController.abort(error)); + return progressController.run(progress => helper.waitForEvent(progress, this, event, options.predicate)); + } + async _init() { this._nodeSession.once('Runtime.executionContextCreated', event => { this._nodeExecutionContext = new js.ExecutionContext(new CRExecutionContext(this._nodeSession, event.context)); @@ -152,14 +161,6 @@ export class ElectronApplication extends ExtendedEventEmitter { async evaluateHandle(pageFunction: types.FuncOn, arg: Arg): Promise> { return this._nodeElectronHandle!.evaluateHandle(pageFunction, arg); } - - protected _getLogger(): InnerLogger { - return this._logger; - } - - protected _getTimeoutSettings(): TimeoutSettings { - return this._timeoutSettings; - } } export class Electron { diff --git a/src/types.ts b/src/types.ts index 9fe159a577..5ec5560f71 100644 --- a/src/types.ts +++ b/src/types.ts @@ -185,4 +185,6 @@ export type ProxySettings = { bypass?: string, username?: string, password?: string -} +}; + +export type WaitForEventOptions = Function | { predicate?: Function, timeout?: number };