feat(webkit): introduce WKPageProxy and use it instead of WKTarget (#394)

This commit is contained in:
Yury Semikhatsky 2020-01-07 10:39:01 -08:00 committed by GitHub
parent f14409cea9
commit 52c175f001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 282 additions and 211 deletions

View File

@ -10,7 +10,7 @@
"playwright": {
"chromium_revision": "724623",
"firefox_revision": "1009",
"webkit_revision": "1063"
"webkit_revision": "1066"
},
"scripts": {
"unit": "node test/test.js",

View File

@ -15,46 +15,39 @@
* limitations under the License.
*/
import { helper, RegisteredListener, debugError, assert } from '../helper';
import * as browser from '../browser';
import * as network from '../network';
import * as types from '../types';
import { WKConnection, WKConnectionEvents, WKTargetSession } from './wkConnection';
import { Page } from '../page';
import { WKTarget } from './wkTarget';
import { Protocol } from './protocol';
import { Events } from '../events';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { assert, debugError, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import { Page } from '../page';
import { ConnectionTransport } from '../transport';
import * as types from '../types';
import { Protocol } from './protocol';
import { WKConnection, WKConnectionEvents, WKPageProxySession } from './wkConnection';
import { WKPageProxy } from './wkPageProxy';
export class WKBrowser extends browser.Browser {
readonly _connection: WKConnection;
private readonly _defaultContext: BrowserContext;
private readonly _contexts = new Map<string, BrowserContext>();
private readonly _targets = new Map<string, WKTarget>();
private readonly _pageProxies = new Map<string, WKPageProxy>();
private readonly _eventListeners: RegisteredListener[];
private _firstTargetCallback?: () => void;
private readonly _firstTargetPromise: Promise<void>;
private _firstPageProxyCallback?: () => void;
private readonly _firstPageProxyPromise: Promise<void>;
constructor(transport: ConnectionTransport) {
super();
this._connection = new WKConnection(transport);
/** @type {!Map<string, !WKTarget>} */
this._targets = new Map();
this._defaultContext = this._createBrowserContext(undefined, {});
/** @type {!Map<string, !BrowserContext>} */
this._contexts = new Map();
this._eventListeners = [
helper.addEventListener(this._connection, WKConnectionEvents.TargetCreated, this._onTargetCreated.bind(this)),
helper.addEventListener(this._connection, WKConnectionEvents.TargetDestroyed, this._onTargetDestroyed.bind(this)),
helper.addEventListener(this._connection, WKConnectionEvents.DidCommitProvisionalTarget, this._onProvisionalTargetCommitted.bind(this)),
helper.addEventListener(this._connection, WKConnectionEvents.PageProxyCreated, this._onPageProxyCreated.bind(this)),
helper.addEventListener(this._connection, WKConnectionEvents.PageProxyDestroyed, this._onPageProxyDestroyed.bind(this))
];
this._firstTargetPromise = new Promise<void>(resolve => this._firstTargetCallback = resolve);
this._firstPageProxyPromise = new Promise<void>(resolve => this._firstPageProxyCallback = resolve);
// Intercept provisional targets during cross-process navigation.
this._connection.send('Target.setPauseOnStart', { pauseOnStart: true }).catch(e => {
@ -81,65 +74,40 @@ export class WKBrowser extends browser.Browser {
}
async _waitForFirstPageTarget(timeout: number): Promise<void> {
assert(!this._targets.size);
await helper.waitWithTimeout(this._firstTargetPromise, 'target', timeout);
assert(!this._pageProxies.size);
await helper.waitWithTimeout(this._firstPageProxyPromise, 'firstPageProxy', timeout);
}
_onTargetCreated(session: WKTargetSession, targetInfo: Protocol.Target.TargetInfo) {
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
_onPageProxyCreated(session: WKPageProxySession, pageProxyInfo: Protocol.Browser.PageProxyInfo) {
let context = null;
if (targetInfo.browserContextId) {
if (pageProxyInfo.browserContextId) {
// FIXME: we don't know about the default context id, so assume that all targets from
// unknown contexts are created in the 'default' context which can in practice be represented
// by multiple actual contexts in WebKit. Solving this properly will require adding context
// lifecycle events.
context = this._contexts.get(targetInfo.browserContextId);
// if (!context)
// throw new Error(`Target ${targetId} created in unknown browser context ${browserContextId}.`);
context = this._contexts.get(pageProxyInfo.browserContextId);
}
if (!context)
context = this._defaultContext;
const target = new WKTarget(this, session, targetInfo, context);
this._targets.set(targetInfo.targetId, target);
if (targetInfo.isProvisional) {
const oldTarget = this._targets.get(targetInfo.oldTargetId);
if (oldTarget)
oldTarget._initializeSession(session);
const pageProxy = new WKPageProxy(this, session, context);
this._pageProxies.set(pageProxyInfo.pageProxyId, pageProxy);
if (pageProxyInfo.openerId) {
const opener = this._pageProxies.get(pageProxyInfo.openerId);
if (opener)
opener.onPopupCreated(pageProxy);
}
if (this._firstTargetCallback) {
this._firstTargetCallback();
this._firstTargetCallback = null;
if (this._firstPageProxyCallback) {
this._firstPageProxyCallback();
this._firstPageProxyCallback = null;
}
if (!targetInfo.oldTargetId && targetInfo.openerId) {
const opener = this._targets.get(targetInfo.openerId);
if (!opener)
return;
const openerPage = opener._wkPage ? opener._wkPage._page : null;
if (!openerPage || !openerPage.listenerCount(Events.Page.Popup))
return;
target.page().then(page => openerPage.emit(Events.Page.Popup, page));
}
if (targetInfo.isPaused)
this._connection.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
}
_onTargetDestroyed({targetId, crashed}) {
const target = this._targets.get(targetId);
this._targets.delete(targetId);
target._didClose(crashed);
}
_closePage(targetId: string, runBeforeUnload: boolean) {
this._connection.send('Target.close', {
targetId,
runBeforeUnload
}).catch(debugError);
}
async _onProvisionalTargetCommitted({oldTargetId, newTargetId}) {
const oldTarget = this._targets.get(oldTargetId);
const newTarget = this._targets.get(newTargetId);
newTarget._swapWith(oldTarget);
_onPageProxyDestroyed(pageProxyId: Protocol.Browser.PageProxyID) {
const pageProxy = this._pageProxies.get(pageProxyId);
pageProxy.dispose();
this._pageProxies.delete(pageProxyId);
}
disconnect() {
@ -158,15 +126,14 @@ export class WKBrowser extends browser.Browser {
_createBrowserContext(browserContextId: string | undefined, options: BrowserContextOptions): BrowserContext {
const context = new BrowserContext({
pages: async (): Promise<Page[]> => {
const targets = Array.from(this._targets.values()).filter(target => target._browserContext === context && !target._session.isProvisional());
const pages = await Promise.all(targets.map(target => target.page()));
return pages.filter(page => !!page);
const pageProxies = Array.from(this._pageProxies.values()).filter(proxy => proxy._browserContext === context);
return await Promise.all(pageProxies.map(proxy => proxy.page()));
},
newPage: async (): Promise<Page> => {
const { targetId } = await this._connection.send('Browser.createPage', { browserContextId });
const target = this._targets.get(targetId);
return await target.page();
const { pageProxyId } = await this._connection.send('Browser.createPage', { browserContextId });
const pageProxy = this._pageProxies.get(pageProxyId);
return await pageProxy.page();
},
close: async (): Promise<void> => {

View File

@ -25,18 +25,23 @@ const debugProtocol = debug('playwright:protocol');
const debugWrappedMessage = require('debug')('wrapped');
export const WKConnectionEvents = {
TargetCreated: Symbol('ConnectionEvents.TargetCreated'),
TargetDestroyed: Symbol('Connection.TargetDestroyed'),
DidCommitProvisionalTarget: Symbol('Connection.DidCommitProvisionalTarget')
PageProxyCreated: Symbol('ConnectionEvents.PageProxyCreated'),
PageProxyDestroyed: Symbol('Connection.PageProxyDestroyed')
};
export const WKPageProxySessionEvents = {
TargetCreated: Symbol('PageProxyEvents.TargetCreated'),
TargetDestroyed: Symbol('PageProxyEvents.TargetDestroyed'),
DidCommitProvisionalTarget: Symbol('PageProxyEvents.DidCommitProvisionalTarget'),
};
export class WKConnection extends EventEmitter {
_lastId = 0;
private _lastId = 0;
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _transport: ConnectionTransport;
private readonly _sessions = new Map<string, WKTargetSession>();
private readonly _pageProxySessions = new Map<string, WKPageProxySession>();
_closed = false;
private _closed = false;
constructor(transport: ConnectionTransport) {
super();
@ -45,18 +50,23 @@ export class WKConnection extends EventEmitter {
this._transport.onclose = this._onClose.bind(this);
}
nextMessageId(): number {
return ++this._lastId;
}
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
params?: Protocol.CommandParameters[T],
pageProxyId?: string
): Promise<Protocol.CommandReturnValues[T]> {
const id = this._rawSend({method, params});
const id = this._rawSend({pageProxyId, method, params});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
});
}
_rawSend(message: any): number {
const id = ++this._lastId;
const id = this.nextMessageId();
message = JSON.stringify(Object.assign({}, message, {id}));
debugProtocol('SEND ► ' + message);
this._transport.send(message);
@ -66,7 +76,7 @@ export class WKConnection extends EventEmitter {
private _dispatchMessage(message: string) {
debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
this._dispatchTargetMessageToSession(object, message);
this._dispatchPageProxyMessage(object, message);
if (object.id) {
const callback = this._callbacks.get(object.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
@ -84,40 +94,21 @@ export class WKConnection extends EventEmitter {
}
}
_dispatchTargetMessageToSession(object: {method: string, params: any}, wrappedMessage: string) {
if (object.method === 'Target.targetCreated') {
const targetInfo = object.params.targetInfo as Protocol.Target.TargetInfo;
const session = new WKTargetSession(this, targetInfo);
this._sessions.set(session._sessionId, session);
Promise.resolve().then(() => this.emit(WKConnectionEvents.TargetCreated, session, object.params.targetInfo));
} else if (object.method === 'Target.targetDestroyed') {
const session = this._sessions.get(object.params.targetId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.targetId);
}
Promise.resolve().then(() => this.emit(WKConnectionEvents.TargetDestroyed, { targetId: object.params.targetId, crashed: object.params.crashed }));
} else if (object.method === 'Target.dispatchMessageFromTarget') {
const {targetId, message} = object.params as Protocol.Target.dispatchMessageFromTargetPayload;
const session = this._sessions.get(targetId);
if (!session)
throw new Error('Unknown target: ' + targetId);
if (session.isProvisional())
session._addProvisionalMessage(message);
else
session._dispatchMessageFromTarget(message);
} else if (object.method === 'Target.didCommitProvisionalTarget') {
const {oldTargetId, newTargetId} = object.params as Protocol.Target.didCommitProvisionalTargetPayload;
Promise.resolve().then(() => this.emit(WKConnectionEvents.DidCommitProvisionalTarget, { oldTargetId, newTargetId }));
const newSession = this._sessions.get(newTargetId);
if (!newSession)
throw new Error('Unknown new target: ' + newTargetId);
const oldSession = this._sessions.get(oldTargetId);
if (!oldSession)
throw new Error('Unknown old target: ' + oldTargetId);
oldSession._swappedOut = true;
for (const message of newSession._takeProvisionalMessagesAndCommit())
newSession._dispatchMessageFromTarget(message);
_dispatchPageProxyMessage(object: {method: string, params: any, id?: string, pageProxyId?: string}, message: string) {
if (object.method === 'Browser.pageProxyCreated') {
const pageProxyId = object.params.pageProxyInfo.pageProxyId;
const pageProxySession = new WKPageProxySession(this, pageProxyId);
this._pageProxySessions.set(pageProxyId, pageProxySession);
Promise.resolve().then(() => this.emit(WKConnectionEvents.PageProxyCreated, pageProxySession, object.params.pageProxyInfo));
} else if (object.method === 'Browser.pageProxyDestroyed') {
const pageProxyId = object.params.pageProxyId as string;
const pageProxySession = this._pageProxySessions.get(pageProxyId);
this._pageProxySessions.delete(pageProxyId);
pageProxySession.dispose();
Promise.resolve().then(() => this.emit(WKConnectionEvents.PageProxyDestroyed, pageProxyId));
} else if (!object.id && object.pageProxyId) {
const pageProxySession = this._pageProxySessions.get(object.pageProxyId);
pageProxySession._dispatchEvent(object, message);
}
}
@ -130,9 +121,10 @@ export class WKConnection extends EventEmitter {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
this._callbacks.clear();
for (const session of this._sessions.values())
session._onClosed();
this._sessions.clear();
for (const pageProxySession of this._pageProxySessions.values())
pageProxySession.dispose();
this._pageProxySessions.clear();
}
dispose() {
@ -145,11 +137,89 @@ export const WKTargetSessionEvents = {
Disconnected: Symbol('TargetSessionEvents.Disconnected')
};
export class WKPageProxySession extends EventEmitter {
_connection: WKConnection;
private readonly _sessions = new Map<string, WKTargetSession>();
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _pageProxyId: string;
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: WKConnection, pageProxyId: string) {
super();
this._connection = connection;
this._pageProxyId = pageProxyId;
}
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (!this._connection)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the pageProxy has been closed.`));
return this._connection.send(method, params, this._pageProxyId).catch(e => {
// There is a possible race of the connection closure. We may have received
// targetDestroyed notification before response for the command, in that
// case it's safe to swallow the exception.
}) as Promise<Protocol.CommandReturnValues[T]>;
}
_dispatchEvent(object: {method: string, params: any, pageProxyId?: string}, wrappedMessage: string) {
if (object.method === 'Target.targetCreated') {
const targetInfo = object.params.targetInfo as Protocol.Target.TargetInfo;
const session = new WKTargetSession(this, targetInfo);
this._sessions.set(session._sessionId, session);
Promise.resolve().then(() => this.emit(WKPageProxySessionEvents.TargetCreated, session, object.params.targetInfo));
} else if (object.method === 'Target.targetDestroyed') {
const session = this._sessions.get(object.params.targetId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.targetId);
}
Promise.resolve().then(() => this.emit(WKPageProxySessionEvents.TargetDestroyed, { targetId: object.params.targetId, crashed: object.params.crashed }));
} else if (object.method === 'Target.dispatchMessageFromTarget') {
const {targetId, message} = object.params as Protocol.Target.dispatchMessageFromTargetPayload;
const session = this._sessions.get(targetId);
if (!session)
throw new Error('Unknown target: ' + targetId);
if (session.isProvisional())
session._addProvisionalMessage(message);
else
session._dispatchMessageFromTarget(message);
} else if (object.method === 'Target.didCommitProvisionalTarget') {
const {oldTargetId, newTargetId} = object.params as Protocol.Target.didCommitProvisionalTargetPayload;
Promise.resolve().then(() => this.emit(WKPageProxySessionEvents.DidCommitProvisionalTarget, { oldTargetId, newTargetId }));
const newSession = this._sessions.get(newTargetId);
if (!newSession)
throw new Error('Unknown new target: ' + newTargetId);
const oldSession = this._sessions.get(oldTargetId);
if (!oldSession)
throw new Error('Unknown old target: ' + oldTargetId);
oldSession._swappedOut = true;
for (const message of newSession._takeProvisionalMessagesAndCommit())
newSession._dispatchMessageFromTarget(message);
} else {
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
dispose() {
for (const session of this._sessions.values())
session._onClosed();
this._sessions.clear();
this._connection = null;
}
}
export class WKTargetSession extends EventEmitter {
private _connection: WKConnection;
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private _targetType: string;
_sessionId: string;
_pageProxySession: WKPageProxySession;
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _targetType: string;
readonly _sessionId: string;
_swappedOut = false;
private _provisionalMessages?: string[];
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
@ -158,10 +228,10 @@ export class WKTargetSession extends EventEmitter {
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: WKConnection, targetInfo: Protocol.Target.TargetInfo) {
constructor(pageProxySession: WKPageProxySession, targetInfo: Protocol.Target.TargetInfo) {
super();
const {targetId, type, isProvisional} = targetInfo;
this._connection = connection;
this._pageProxySession = pageProxySession;
this._targetType = type;
this._sessionId = targetId;
if (isProvisional)
@ -172,13 +242,17 @@ export class WKTargetSession extends EventEmitter {
return !!this._provisionalMessages;
}
isClosed(): boolean {
return !this._pageProxySession;
}
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (!this._connection)
if (!this._pageProxySession)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const innerId = ++this._connection._lastId;
const innerId = this._pageProxySession._connection.nextMessageId();
const messageObj = {
id: innerId,
method,
@ -190,7 +264,7 @@ export class WKTargetSession extends EventEmitter {
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(innerId, {resolve, reject, error: new Error(), method});
});
this._connection.send('Target.sendMessageToTarget', {
this._pageProxySession.send('Target.sendMessageToTarget', {
message: message, targetId: this._sessionId
}).catch(e => {
// There is a possible race of the connection closure. We may have received
@ -238,7 +312,7 @@ export class WKTargetSession extends EventEmitter {
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
}
this._callbacks.clear();
this._connection = null;
this._pageProxySession = null;
Promise.resolve().then(() => this.emit(WKTargetSessionEvents.Disconnected));
}
}

View File

@ -367,7 +367,10 @@ export class WKPage implements PageDelegate {
}
async closePage(runBeforeUnload: boolean): Promise<void> {
this._browser._closePage(this._session._sessionId, runBeforeUnload);
this._session._pageProxySession.send('Target.close', {
targetId: this._session._sessionId,
runBeforeUnload
}).catch(debugError);
}
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null> {

108
src/webkit/wkPageProxy.ts Normal file
View File

@ -0,0 +1,108 @@
// 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 { WKBrowser } from './wkBrowser';
import { RegisteredListener, helper, assert, debugError } from '../helper';
import { Events } from '../events';
export class WKPageProxy {
private readonly _browser: WKBrowser;
private readonly _pageProxySession: WKPageProxySession;
readonly _browserContext: BrowserContext;
private _pagePromise: Promise<Page> | null = null;
private _wkPage: WKPage | null = null;
private readonly _firstTargetPromise: Promise<void>;
private _firstTargetCallback: () => void;
private readonly _targetSessions = new Map<string, WKTargetSession>();
private readonly _eventListeners: RegisteredListener[];
constructor(browser: WKBrowser, session: WKPageProxySession, browserContext: BrowserContext) {
this._browser = browser;
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))
];
}
dispose() {
helper.removeEventListeners(this._eventListeners);
}
async page(): Promise<Page> {
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<Page> {
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._browser, this._browserContext);
this._wkPage.setSession(session);
await this._initializeSession(session);
return this._wkPage._page;
}
private _initializeSession(session: WKTargetSession) : Promise<void> {
return this._wkPage._initializeSession(session).catch(e => {
if (session.isClosed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._wkPage._session === session)
throw e;
});
}
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._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);
}
}

View File

@ -1,81 +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.
*/
import { BrowserContext } from '../browserContext';
import { Page } from '../page';
import { Protocol } from './protocol';
import { WKTargetSession } from './wkConnection';
import { WKPage } from './wkPage';
import { WKBrowser } from './wkBrowser';
export class WKTarget {
readonly _browserContext: BrowserContext;
readonly _targetId: string;
readonly _session: WKTargetSession;
private _pagePromise: Promise<Page> | null = null;
private _browser: WKBrowser;
_wkPage: WKPage | null = null;
constructor(browser: WKBrowser, session: WKTargetSession, targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext) {
this._browser = browser;
this._session = session;
this._browserContext = browserContext;
this._targetId = targetInfo.targetId;
/** @type {?Promise<!Page>} */
this._pagePromise = null;
}
_didClose(crashed: boolean) {
if (this._wkPage)
this._wkPage.didClose(crashed);
}
async _initializeSession(session: WKTargetSession) {
if (!this._wkPage)
return;
await this._wkPage._initializeSession(session).catch(e => {
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._wkPage)
throw e;
});
}
async _swapWith(oldTarget: WKTarget) {
if (!oldTarget._pagePromise)
return;
this._pagePromise = oldTarget._pagePromise;
this._wkPage = oldTarget._wkPage;
// 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._wkPage = null;
this._wkPage.setSession(this._session);
}
async page(): Promise<Page> {
if (!this._pagePromise) {
this._wkPage = new WKPage(this._browser, this._browserContext);
this._wkPage.setSession(this._session);
// Reference local page variable as |this._frameManager| may be
// cleared on swap.
const page = this._wkPage._page;
this._pagePromise = this._initializeSession(this._session).then(() => page);
}
return this._pagePromise;
}
}