chore: various cleanup (#266)

This commit is contained in:
Dmitry Gozman 2019-12-16 16:43:28 -08:00 committed by Andrey Lushnikov
parent f9f7d5c55a
commit 03e2336d49
12 changed files with 121 additions and 160 deletions

View File

@ -310,7 +310,7 @@
- [class: TimeoutError](#class-timeouterror)
- [class: Selector](#class-selector)
* [selector.selector](#selectorselector)
* [selector.visible](#selectorvisible)
* [selector.visibility](#selectorvisibility)
<!-- GEN:stop -->
### Overview

View File

@ -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<number, js.ExecutionContext>();
private _contextIdToContext = new Map<number, dom.FrameExecutionContext>();
private _isolatedWorlds = new Set<string>();
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<network.Response | null> {
@ -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));
}
}
_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);
}

View File

@ -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<string, string>();
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();

View File

@ -36,7 +36,7 @@ export class Target {
private _ignoreHTTPSErrors: boolean;
private _defaultViewport: types.Viewport;
private _pagePromise: Promise<Page> | null = null;
private _page: Page | null = null;
private _frameManager: FrameManager | null = null;
private _workerPromise: Promise<Worker> | null = null;
_initializedPromise: Promise<boolean>;
_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<Page | null> {
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);

View File

@ -219,7 +219,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
export class Target {
_pagePromise?: Promise<Page>;
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);

View File

@ -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<string, js.ExecutionContext>;
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
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));
}
}
_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);
}
_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 = {}) {

View File

@ -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',

View File

@ -40,8 +40,6 @@ export interface PageDelegate {
exposeBinding(name: string, bindingFunction: string): Promise<void>;
evaluateOnNewDocument(source: string): Promise<void>;
closePage(runBeforeUnload: boolean): Promise<void>;
// TODO: reverse didClose call sequence.
didClose(): void;
navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise<network.Response | null>;
waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null>;
@ -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();
}

View File

@ -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));

View File

@ -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<number, js.ExecutionContext>;
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _isolatedWorlds: Set<string>;
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;
}
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
@ -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);

View File

@ -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)) {

View File

@ -30,7 +30,7 @@ export class Target {
readonly _type: 'page' | 'service-worker' | 'worker';
private readonly _session: TargetSession;
private _pagePromise: Promise<Page> | 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<Page> {
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);