// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { BrowserContext } from '../browserContext'; import { Page } from '../page'; import { Protocol } from './protocol'; import { WKPageProxySession, WKPageProxySessionEvents, WKTargetSession } from './wkConnection'; import { WKPage } from './wkPage'; import { RegisteredListener, helper, assert, debugError } from '../helper'; import { Events } from '../events'; export class WKPageProxy { private readonly _pageProxySession: WKPageProxySession; readonly _browserContext: BrowserContext; private _pagePromise: Promise | null = null; private _wkPage: WKPage | null = null; private readonly _firstTargetPromise: Promise; private _firstTargetCallback: () => void; private readonly _targetSessions = new Map(); private readonly _eventListeners: RegisteredListener[]; constructor(session: WKPageProxySession, browserContext: BrowserContext) { this._pageProxySession = session; this._browserContext = browserContext; this._firstTargetPromise = new Promise(r => this._firstTargetCallback = r); this._eventListeners = [ helper.addEventListener(this._pageProxySession, WKPageProxySessionEvents.TargetCreated, this._onTargetCreated.bind(this)), helper.addEventListener(this._pageProxySession, WKPageProxySessionEvents.TargetDestroyed, this._onTargetDestroyed.bind(this)), helper.addEventListener(this._pageProxySession, WKPageProxySessionEvents.DidCommitProvisionalTarget, this._onProvisionalTargetCommitted.bind(this)) ]; // Intercept provisional targets during cross-process navigation. this._pageProxySession.send('Target.setPauseOnStart', { pauseOnStart: true }).catch(e => { if (this._pageProxySession.isClosed()) return; debugError(e); throw e; }); } dispose() { helper.removeEventListeners(this._eventListeners); } async page(): Promise { if (!this._pagePromise) this._pagePromise = this._initializeWKPage(); return this._pagePromise; } onPopupCreated(popupPageProxy: WKPageProxy) { if (!this._wkPage) return; if (!this._wkPage._page.listenerCount(Events.Page.Popup)) return; popupPageProxy.page().then(page => this._wkPage._page.emit(Events.Page.Popup, page)); } private async _initializeWKPage(): Promise { await this._firstTargetPromise; let session: WKTargetSession; for (const targetSession of this._targetSessions.values()) { if (!targetSession.isProvisional()) { session = targetSession; break; } } assert(session, 'One non-provisional target session must exist'); this._wkPage = new WKPage(this._browserContext, this._pageProxySession); this._wkPage.setSession(session); await Promise.all([ this._wkPage._initializePageProxySession(), this._wkPage._initializeSession(session) ]); return this._wkPage._page; } private _onTargetCreated(session: WKTargetSession, targetInfo: Protocol.Target.TargetInfo) { assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type); this._targetSessions.set(targetInfo.targetId, session); if (this._firstTargetCallback) { this._firstTargetCallback(); this._firstTargetCallback = null; } if (targetInfo.isProvisional && this._wkPage) this._wkPage._initializeSession(session); if (targetInfo.isPaused) this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError); } private _onTargetDestroyed({targetId, crashed}) { const targetSession = this._targetSessions.get(targetId); this._targetSessions.delete(targetId); if (!this._wkPage) return; if (this._wkPage._session === targetSession) this._wkPage.didClose(crashed); } private _onProvisionalTargetCommitted({oldTargetId, newTargetId}) { const newTargetSession = this._targetSessions.get(newTargetId); this._wkPage.setSession(newTargetSession); } }