From 03e2336d49479d286c6eae83fc1e515d5605b20b Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 16 Dec 2019 16:43:28 -0800 Subject: [PATCH] chore: various cleanup (#266) --- docs/api.md | 2 +- src/chromium/FrameManager.ts | 86 +++++++++++++++------------------- src/chromium/NetworkManager.ts | 21 ++++++--- src/chromium/Target.ts | 13 +++-- src/firefox/Browser.ts | 13 +++-- src/firefox/FrameManager.ts | 46 +++++++----------- src/firefox/Launcher.ts | 2 - src/page.ts | 3 -- src/webkit/Browser.ts | 2 +- src/webkit/FrameManager.ts | 58 ++++++++--------------- src/webkit/NetworkManager.ts | 4 ++ src/webkit/Target.ts | 31 ++++++------ 12 files changed, 121 insertions(+), 160 deletions(-) diff --git a/docs/api.md b/docs/api.md index 6f35dba09a..e76afbdeb0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -310,7 +310,7 @@ - [class: TimeoutError](#class-timeouterror) - [class: Selector](#class-selector) * [selector.selector](#selectorselector) - * [selector.visible](#selectorvisible) + * [selector.visibility](#selectorvisibility) ### Overview diff --git a/src/chromium/FrameManager.ts b/src/chromium/FrameManager.ts index e5da6b4973..f8f5617694 100644 --- a/src/chromium/FrameManager.ts +++ b/src/chromium/FrameManager.ts @@ -15,11 +15,9 @@ * limitations under the License. */ -import { EventEmitter } from 'events'; import * as dom from '../dom'; import * as frames from '../frames'; -import { assert, debugError } from '../helper'; -import * as js from '../javascript'; +import { debugError, helper, RegisteredListener } from '../helper'; import * as network from '../network'; import { CDPSession } from './Connection'; import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext'; @@ -45,17 +43,17 @@ import { ConsoleMessage } from '../console'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; -export class FrameManager extends EventEmitter implements PageDelegate { +export class FrameManager implements PageDelegate { _client: CDPSession; private _page: Page; private _networkManager: NetworkManager; - private _contextIdToContext = new Map(); + private _contextIdToContext = new Map(); private _isolatedWorlds = new Set(); + private _eventListeners: RegisteredListener[]; rawMouse: RawMouseImpl; rawKeyboard: RawKeyboardImpl; constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) { - super(); this._client = client; this.rawKeyboard = new RawKeyboardImpl(client); this.rawMouse = new RawMouseImpl(client); @@ -68,22 +66,24 @@ export class FrameManager extends EventEmitter implements PageDelegate { (this._page as any).overrides = new Overrides(client); (this._page as any).interception = new Interception(this._networkManager); - this._client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); - this._client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); - 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.frameDetached', event => this._onFrameDetached(event.frameId)); - this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)); - 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.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._eventListeners = [ + helper.addEventListener(client, 'Inspector.targetCrashed', event => this._onTargetCrashed()), + helper.addEventListener(client, 'Log.entryAdded', event => this._onLogEntryAdded(event)), + helper.addEventListener(client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), + helper.addEventListener(client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)), + helper.addEventListener(client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)), + helper.addEventListener(client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)), + helper.addEventListener(client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)), + helper.addEventListener(client, 'Page.javascriptDialogOpening', event => this._onDialog(event)), + helper.addEventListener(client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)), + helper.addEventListener(client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)), + helper.addEventListener(client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)), + helper.addEventListener(client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)), + helper.addEventListener(client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)), + helper.addEventListener(client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)), + helper.addEventListener(client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)), + helper.addEventListener(client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()), + ]; } async initialize() { @@ -102,11 +102,9 @@ export class FrameManager extends EventEmitter implements PageDelegate { } didClose() { - // TODO: remove listeners. - } - - networkManager(): NetworkManager { - return this._networkManager; + helper.removeEventListeners(this._eventListeners); + this._networkManager.dispose(); + this._page._didClose(); } async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise { @@ -243,21 +241,18 @@ export class FrameManager extends EventEmitter implements PageDelegate { } _onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription) { - const frameId = contextPayload.auxData ? contextPayload.auxData.frameId : null; - const frame = this._page._frameManager.frame(frameId); + const frame = this._page._frameManager.frame(contextPayload.auxData ? contextPayload.auxData.frameId : null); + if (!frame) + return; if (contextPayload.auxData && contextPayload.auxData.type === 'isolated') this._isolatedWorlds.add(contextPayload.name); const delegate = new ExecutionContextDelegate(this._client, contextPayload); - if (frame) { - const context = new dom.FrameExecutionContext(delegate, frame); - if (contextPayload.auxData && !!contextPayload.auxData.isDefault) - frame._contextCreated('main', context); - else if (contextPayload.name === UTILITY_WORLD_NAME) - frame._contextCreated('utility', context); - this._contextIdToContext.set(contextPayload.id, context); - } else { - this._contextIdToContext.set(contextPayload.id, new js.ExecutionContext(delegate)); - } + const context = new dom.FrameExecutionContext(delegate, frame); + if (contextPayload.auxData && !!contextPayload.auxData.isDefault) + frame._contextCreated('main', context); + else if (contextPayload.name === UTILITY_WORLD_NAME) + frame._contextCreated('utility', context); + this._contextIdToContext.set(contextPayload.id, context); } _onExecutionContextDestroyed(executionContextId: number) { @@ -265,8 +260,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { if (!context) return; this._contextIdToContext.delete(executionContextId); - if (context.frame()) - context.frame()._contextDestroyed(context as dom.FrameExecutionContext); + context.frame()._contextDestroyed(context); } _onExecutionContextsCleared() { @@ -274,12 +268,6 @@ export class FrameManager extends EventEmitter implements PageDelegate { this._onExecutionContextDestroyed(contextId); } - executionContextById(contextId: number): js.ExecutionContext { - const context = this._contextIdToContext.get(contextId); - assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId); - return context; - } - async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) { if (event.executionContextId === 0) { // DevTools protocol stores the last 1000 console messages. These @@ -297,7 +285,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { // @see https://github.com/GoogleChrome/puppeteer/issues/3865 return; } - const context = this.executionContextById(event.executionContextId); + const context = this._contextIdToContext.get(event.executionContextId); const values = event.args.map(arg => context._createHandle(arg)); this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); } @@ -309,7 +297,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { } _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) { - const context = this.executionContextById(event.executionContextId); + const context = this._contextIdToContext.get(event.executionContextId); this._page._onBindingCalled(event.payload, context); } diff --git a/src/chromium/NetworkManager.ts b/src/chromium/NetworkManager.ts index 6d91e66466..b20756deb5 100644 --- a/src/chromium/NetworkManager.ts +++ b/src/chromium/NetworkManager.ts @@ -17,7 +17,7 @@ import { CDPSession } from './Connection'; import { Page } from '../page'; -import { assert, debugError, helper } from '../helper'; +import { assert, debugError, helper, RegisteredListener } from '../helper'; import { Protocol } from './protocol'; import * as network from '../network'; import * as frames from '../frames'; @@ -36,18 +36,21 @@ export class NetworkManager { private _protocolRequestInterceptionEnabled = false; private _userCacheDisabled = false; private _requestIdToInterceptionId = new Map(); + private _eventListeners: RegisteredListener[]; constructor(client: CDPSession, ignoreHTTPSErrors: boolean, page: Page) { this._client = client; this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._page = page; - this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); - this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); - this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); - this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); - this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this)); - this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); + this._eventListeners = [ + helper.addEventListener(client, 'Fetch.requestPaused', this._onRequestPaused.bind(this)), + helper.addEventListener(client, 'Fetch.authRequired', this._onAuthRequired.bind(this)), + helper.addEventListener(client, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)), + helper.addEventListener(client, 'Network.responseReceived', this._onResponseReceived.bind(this)), + helper.addEventListener(client, 'Network.loadingFinished', this._onLoadingFinished.bind(this)), + helper.addEventListener(client, 'Network.loadingFailed', this._onLoadingFailed.bind(this)), + ]; } async initialize() { @@ -56,6 +59,10 @@ export class NetworkManager { await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true}); } + dispose() { + helper.removeEventListeners(this._eventListeners); + } + async authenticate(credentials: { username: string; password: string; } | null) { this._credentials = credentials; await this._updateProtocolRequestInterception(); diff --git a/src/chromium/Target.ts b/src/chromium/Target.ts index d1737b97a1..98f46b069e 100644 --- a/src/chromium/Target.ts +++ b/src/chromium/Target.ts @@ -36,7 +36,7 @@ export class Target { private _ignoreHTTPSErrors: boolean; private _defaultViewport: types.Viewport; private _pagePromise: Promise | null = null; - private _page: Page | null = null; + private _frameManager: FrameManager | null = null; private _workerPromise: Promise | null = null; _initializedPromise: Promise; _initializedCallback: (value?: unknown) => void; @@ -77,16 +77,15 @@ export class Target { } _didClose() { - if (this._page) - this._page._didClose(); + if (this._frameManager) + this._frameManager.didClose(); } async page(): Promise { if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { this._pagePromise = this._sessionFactory().then(async client => { - const frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors); - const page = frameManager.page(); - this._page = page; + this._frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors); + const page = this._frameManager.page(); (page as any)[targetSymbol] = this; client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect()); client.on('Target.attachedToTarget', event => { @@ -95,7 +94,7 @@ export class Target { client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError); } }); - await frameManager.initialize(); + await this._frameManager.initialize(); await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}); if (this._defaultViewport) await page.setViewport(this._defaultViewport); diff --git a/src/firefox/Browser.ts b/src/firefox/Browser.ts index a5dc691438..18ddc44be0 100644 --- a/src/firefox/Browser.ts +++ b/src/firefox/Browser.ts @@ -219,7 +219,7 @@ export class Browser extends EventEmitter implements BrowserInterface { export class Target { _pagePromise?: Promise; - private _page: Page | null = null; + private _frameManager: FrameManager | null = null; private _browser: Browser; _context: BrowserContext; private _connection: Connection; @@ -239,8 +239,8 @@ export class Target { } _didClose() { - if (this._page) - this._page._didClose(); + if (this._frameManager) + this._frameManager.didClose(); } opener(): Target | null { @@ -263,11 +263,10 @@ export class Target { if (this._type === 'page' && !this._pagePromise) { this._pagePromise = new Promise(async f => { const session = await this._connection.createSession(this._targetId); - const frameManager = new FrameManager(session, this._context); - const page = frameManager._page; - this._page = page; + this._frameManager = new FrameManager(session, this._context); + const page = this._frameManager._page; session.once(JugglerSessionEvents.Disconnected, () => page._didDisconnect()); - await frameManager._initialize(); + await this._frameManager._initialize(); if (this._browser._defaultViewport) await page.setViewport(this._browser._defaultViewport); f(page); diff --git a/src/firefox/FrameManager.ts b/src/firefox/FrameManager.ts index 93a32defec..6d5a357b0d 100644 --- a/src/firefox/FrameManager.ts +++ b/src/firefox/FrameManager.ts @@ -15,10 +15,8 @@ * limitations under the License. */ -import { EventEmitter } from 'events'; import * as frames from '../frames'; import { assert, helper, RegisteredListener, debugError } from '../helper'; -import * as js from '../javascript'; import * as dom from '../dom'; import { JugglerSession } from './Connection'; import { ExecutionContextDelegate } from './ExecutionContext'; @@ -35,17 +33,16 @@ import { Accessibility } from './features/accessibility'; import * as network from '../network'; import * as types from '../types'; -export class FrameManager extends EventEmitter implements PageDelegate { +export class FrameManager implements PageDelegate { readonly rawMouse: RawMouseImpl; readonly rawKeyboard: RawKeyboardImpl; readonly _session: JugglerSession; readonly _page: Page; private readonly _networkManager: NetworkManager; - private readonly _contextIdToContext: Map; + private readonly _contextIdToContext: Map; private _eventListeners: RegisteredListener[]; constructor(session: JugglerSession, browserContext: BrowserContext) { - super(); this._session = session; this.rawKeyboard = new RawKeyboardImpl(session); this.rawMouse = new RawMouseImpl(session); @@ -81,22 +78,15 @@ export class FrameManager extends EventEmitter implements PageDelegate { ]); } - executionContextById(executionContextId) { - return this._contextIdToContext.get(executionContextId) || null; - } - _onExecutionContextCreated({executionContextId, auxData}) { - const frameId = auxData ? auxData.frameId : null; - const frame = this._page._frameManager.frame(frameId); + const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null); + if (!frame) + return; const delegate = new ExecutionContextDelegate(this._session, executionContextId); - if (frame) { - const context = new dom.FrameExecutionContext(delegate, frame); - frame._contextCreated('main', context); - frame._contextCreated('utility', context); - this._contextIdToContext.set(executionContextId, context); - } else { - this._contextIdToContext.set(executionContextId, new js.ExecutionContext(delegate)); - } + const context = new dom.FrameExecutionContext(delegate, frame); + frame._contextCreated('main', context); + frame._contextCreated('utility', context); + this._contextIdToContext.set(executionContextId, context); } _onExecutionContextDestroyed({executionContextId}) { @@ -104,14 +94,13 @@ export class FrameManager extends EventEmitter implements PageDelegate { if (!context) return; this._contextIdToContext.delete(executionContextId); - if (context.frame()) - context.frame()._contextDestroyed(context as dom.FrameExecutionContext); + context.frame()._contextDestroyed(context as dom.FrameExecutionContext); } - _onNavigationStarted(params) { + _onNavigationStarted() { } - _onNavigationAborted(params) { + _onNavigationAborted(params: Protocol.Page.navigationAbortedPayload) { const frame = this._page._frameManager.frame(params.frameId); for (const watcher of this._page._frameManager._lifecycleWatchers) watcher._onAbortedNewDocumentNavigation(frame, params.navigationId, params.errorText); @@ -140,18 +129,18 @@ export class FrameManager extends EventEmitter implements PageDelegate { this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded'); } - _onUncaughtError(params) { + _onUncaughtError(params: Protocol.Page.uncaughtErrorPayload) { const error = new Error(params.message); error.stack = params.stack; this._page.emit(Events.Page.PageError, error); } _onConsole({type, args, executionContextId, location}) { - const context = this.executionContextById(executionContextId); + const context = this._contextIdToContext.get(executionContextId); this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location); } - _onDialogOpened(params) { + _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { this._page.emit(Events.Page.Dialog, new dialog.Dialog( params.type as dialog.DialogType, params.message, @@ -162,12 +151,12 @@ export class FrameManager extends EventEmitter implements PageDelegate { } _onBindingCalled(event: Protocol.Page.bindingCalledPayload) { - const context = this.executionContextById(event.executionContextId); + const context = this._contextIdToContext.get(event.executionContextId); this._page._onBindingCalled(event.payload, context); } async _onFileChooserOpened({executionContextId, element}) { - const context = this.executionContextById(executionContextId); + const context = this._contextIdToContext.get(executionContextId); const handle = context._createHandle(element).asElement()!; this._page._onFileChooserOpened(handle); } @@ -181,6 +170,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { didClose() { helper.removeEventListeners(this._eventListeners); this._networkManager.dispose(); + this._page._didClose(); } async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) { diff --git a/src/firefox/Launcher.ts b/src/firefox/Launcher.ts index 2008a60ded..a56167f644 100644 --- a/src/firefox/Launcher.ts +++ b/src/firefox/Launcher.ts @@ -30,8 +30,6 @@ import { launchProcess, waitForLine } from '../processLauncher'; const mkdtempAsync = util.promisify(fs.mkdtemp); const writeFileAsync = util.promisify(fs.writeFile); -const FIREFOX_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_firefox_profile-'); - const DEFAULT_ARGS = [ '-no-remote', '-foreground', diff --git a/src/page.ts b/src/page.ts index 6fd4bf5609..5d2fc4cab9 100644 --- a/src/page.ts +++ b/src/page.ts @@ -40,8 +40,6 @@ export interface PageDelegate { exposeBinding(name: string, bindingFunction: string): Promise; evaluateOnNewDocument(source: string): Promise; closePage(runBeforeUnload: boolean): Promise; - // TODO: reverse didClose call sequence. - didClose(): void; navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise; waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise; @@ -130,7 +128,6 @@ export class Page extends EventEmitter { _didClose() { assert(!this._closed, 'Page closed twice'); this._closed = true; - this._delegate.didClose(); this.emit(Events.Page.Close); this._closedCallback(); } diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index 2dea19cc41..9f05685990 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -175,7 +175,7 @@ export class Browser extends EventEmitter implements BrowserInterface { const opener = this._targets.get(targetInfo.openerId); if (!opener) return; - const openerPage = opener._page; + const openerPage = opener._frameManager ? opener._frameManager._page : null; if (!openerPage || !openerPage.listenerCount(Events.Page.Popup)) return; target.page().then(page => openerPage.emit(Events.Page.Popup, page)); diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 038414b3ef..01c982472b 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -15,10 +15,8 @@ * limitations under the License. */ -import * as EventEmitter from 'events'; import * as frames from '../frames'; -import { assert, debugError, helper, RegisteredListener } from '../helper'; -import * as js from '../javascript'; +import { debugError, helper, RegisteredListener } from '../helper'; import * as dom from '../dom'; import * as network from '../network'; import { TargetSession } from './Connection'; @@ -39,19 +37,18 @@ import { PNG } from 'pngjs'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const BINDING_CALL_MESSAGE = '__playwright_binding_call__'; -export class FrameManager extends EventEmitter implements PageDelegate { +export class FrameManager implements PageDelegate { readonly rawMouse: RawMouseImpl; readonly rawKeyboard: RawKeyboardImpl; _session: TargetSession; readonly _page: Page; private readonly _networkManager: NetworkManager; - private readonly _contextIdToContext: Map; + private readonly _contextIdToContext: Map; private _isolatedWorlds: Set; private _sessionListeners: RegisteredListener[] = []; private readonly _bootstrapScripts: string[] = []; constructor(browserContext: BrowserContext) { - super(); this.rawKeyboard = new RawKeyboardImpl(); this.rawMouse = new RawMouseImpl(); this._contextIdToContext = new Map(); @@ -102,7 +99,9 @@ export class FrameManager extends EventEmitter implements PageDelegate { didClose() { helper.removeEventListeners(this._sessionListeners); + this._networkManager.dispose(); this.disconnectFromTarget(); + this._page._didClose(); } _addSessionListeners() { @@ -124,14 +123,9 @@ export class FrameManager extends EventEmitter implements PageDelegate { disconnectFromTarget() { for (const context of this._contextIdToContext.values()) { (context._delegate as ExecutionContextDelegate)._dispose(); - if (context.frame()) - context.frame()._contextDestroyed(context as dom.FrameExecutionContext); + context.frame()._contextDestroyed(context); } - // this._mainFrame = null; - } - - networkManager(): NetworkManager { - return this._networkManager; + this._contextIdToContext.clear(); } _onFrameStoppedLoading(frameId: string) { @@ -158,12 +152,11 @@ export class FrameManager extends EventEmitter implements PageDelegate { _onFrameNavigated(framePayload: Protocol.Page.Frame, initial: boolean) { const frame = this._page._frameManager.frame(framePayload.id); - for (const context of this._contextIdToContext.values()) { + for (const [contextId, context] of this._contextIdToContext) { if (context.frame() === frame) { - const delegate = context._delegate as ExecutionContextDelegate; - delegate._dispose(); - this._contextIdToContext.delete(delegate._contextId); - frame._contextDestroyed(context as dom.FrameExecutionContext); + (context._delegate as ExecutionContextDelegate)._dispose(); + this._contextIdToContext.delete(contextId); + frame._contextDestroyed(context); } } // Append session id to avoid cross-process loaderId clash. @@ -182,29 +175,16 @@ export class FrameManager extends EventEmitter implements PageDelegate { _onExecutionContextCreated(contextPayload : Protocol.Runtime.ExecutionContextDescription) { if (this._contextIdToContext.has(contextPayload.id)) return; - const frameId = contextPayload.frameId; - // If the frame was attached manually there is no navigation event. - // FIXME: support frameAttached event in WebKit protocol. - const frame = this._page._frameManager.frame(frameId); + const frame = this._page._frameManager.frame(contextPayload.frameId); if (!frame) return; const delegate = new ExecutionContextDelegate(this._session, contextPayload); - if (frame) { - const context = new dom.FrameExecutionContext(delegate, frame); - if (contextPayload.isPageContext) - frame._contextCreated('main', context); - else if (contextPayload.name === UTILITY_WORLD_NAME) - frame._contextCreated('utility', context); - this._contextIdToContext.set(contextPayload.id, context); - } else { - this._contextIdToContext.set(contextPayload.id, new js.ExecutionContext(delegate)); - } - } - - executionContextById(contextId: number): js.ExecutionContext { - const context = this._contextIdToContext.get(contextId); - assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId); - return context; + const context = new dom.FrameExecutionContext(delegate, frame); + if (contextPayload.isPageContext) + frame._contextCreated('main', context); + else if (contextPayload.name === UTILITY_WORLD_NAME) + frame._contextCreated('utility', context); + this._contextIdToContext.set(contextPayload.id, context); } async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise { @@ -279,7 +259,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { const mainFrameContext = await this._page.mainFrame().executionContext(); const handles = (parameters || []).map(p => { - let context: js.ExecutionContext | null = null; + let context: dom.FrameExecutionContext | null = null; if (p.objectId) { const objectId = JSON.parse(p.objectId); context = this._contextIdToContext.get(objectId.injectedScriptId); diff --git a/src/webkit/NetworkManager.ts b/src/webkit/NetworkManager.ts index 48be67c2dd..12335dada4 100644 --- a/src/webkit/NetworkManager.ts +++ b/src/webkit/NetworkManager.ts @@ -53,6 +53,10 @@ export class NetworkManager { ]); } + dispose() { + helper.removeEventListeners(this._sessionListeners); + } + async setExtraHTTPHeaders(extraHTTPHeaders: { [s: string]: string; }) { this._extraHTTPHeaders = {}; for (const key of Object.keys(extraHTTPHeaders)) { diff --git a/src/webkit/Target.ts b/src/webkit/Target.ts index 0bd26f24c0..b174d70f89 100644 --- a/src/webkit/Target.ts +++ b/src/webkit/Target.ts @@ -30,7 +30,7 @@ export class Target { readonly _type: 'page' | 'service-worker' | 'worker'; private readonly _session: TargetSession; private _pagePromise: Promise | null = null; - _page: Page | null = null; + _frameManager: FrameManager | null = null; static fromPage(page: Page): Target { return (page as any)[targetSymbol]; @@ -47,17 +47,17 @@ export class Target { } _didClose() { - if (this._page) - this._page._didClose(); + if (this._frameManager) + this._frameManager.didClose(); } async _initializeSession(session: TargetSession) { - if (!this._page) + if (!this._frameManager) return; - await (this._page._delegate as FrameManager)._initializeSession(session).catch(e => { + await this._frameManager._initializeSession(session).catch(e => { // Swallow initialization errors due to newer target swap in, // since we will reinitialize again. - if (this._page) + if (this._frameManager) throw e; }); } @@ -66,32 +66,31 @@ export class Target { if (!oldTarget._pagePromise) return; this._pagePromise = oldTarget._pagePromise; - this._page = oldTarget._page; + this._frameManager = oldTarget._frameManager; // Swapped out target should not be accessed by anyone. Reset page promise so that // old target does not close the page on connection reset. oldTarget._pagePromise = null; - oldTarget._page = null; + oldTarget._frameManager = null; this._adoptPage(); } private _adoptPage() { - (this._page as any)[targetSymbol] = this; + (this._frameManager._page as any)[targetSymbol] = this; this._session.once(TargetSessionEvents.Disconnected, () => { // Once swapped out, we reset _page and won't call _didDisconnect for old session. - if (this._page) - this._page._didDisconnect(); + if (this._frameManager) + this._frameManager._page._didDisconnect(); }); - (this._page._delegate as FrameManager).setSession(this._session); + this._frameManager.setSession(this._session); } async page(): Promise { if (this._type === 'page' && !this._pagePromise) { const browser = this._browserContext.browser() as Browser; - // Reference local page variable as _page may be + this._frameManager = new FrameManager(this._browserContext); + // Reference local page variable as |this._frameManager| may be // cleared on swap. - const frameManager = new FrameManager(this._browserContext); - const page = frameManager._page; - this._page = page; + const page = this._frameManager._page; this._pagePromise = new Promise(async f => { this._adoptPage(); await this._initializeSession(this._session);