chore(webkit): move target management to WKPageProxy (#437)

This allows to remove WKTargetSession and use WKSession instead.
This commit is contained in:
Dmitry Gozman 2020-01-09 11:02:55 -08:00 committed by GitHub
parent 5a69cd0068
commit 6e06472988
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 132 deletions

View file

@ -14,10 +14,10 @@
* limitations under the License.
*/
import * as accessibility from '../accessibility';
import { WKTargetSession } from './wkConnection';
import { WKSession } from './wkConnection';
import { Protocol } from './protocol';
export async function getAccessibilityTree(session: WKTargetSession) {
export async function getAccessibilityTree(session: WKSession) {
const {axNode} = await session.send('Page.accessibilitySnapshot');
return new WKAXNode(axNode);
}

View file

@ -29,12 +29,6 @@ export const WKConnectionEvents = {
PageProxyDestroyed: Symbol('Connection.PageProxyDestroyed')
};
export const WKPageProxySessionEvents = {
TargetCreated: Symbol('PageProxyEvents.TargetCreated'),
TargetDestroyed: Symbol('PageProxyEvents.TargetDestroyed'),
DidCommitProvisionalTarget: Symbol('PageProxyEvents.DidCommitProvisionalTarget'),
};
export const kBrowserCloseMessageId = -9999;
export class WKConnection extends platform.EventEmitter {
@ -110,7 +104,7 @@ export class WKConnection extends platform.EventEmitter {
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);
Promise.resolve().then(() => pageProxySession.emit(object.method, object.params));
}
}
@ -142,8 +136,7 @@ export const WKSessionEvents = {
export class WKPageProxySession extends platform.EventEmitter {
_connection: WKConnection;
private readonly _sessions = new Map<string, WKTargetSession>();
private readonly _pageProxyId: string;
readonly _pageProxyId: string;
private readonly _closePromise: Promise<void>;
private _closePromiseCallback: () => void;
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;
@ -171,67 +164,23 @@ export class WKPageProxySession extends platform.EventEmitter {
]);
}
_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.dispose();
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.dispatchMessage(JSON.parse(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);
// TODO: make some calls like screenshot catch swapped out error and retry.
oldSession.errorText = 'Target was swapped out.';
assert(newSession.isProvisional());
for (const message of newSession._takeProvisionalMessagesAndCommit())
newSession.dispatchMessage(JSON.parse(message));
} else {
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
isClosed() {
return !this._connection;
}
dispose() {
for (const session of this._sessions.values())
session.dispose();
this._sessions.clear();
this._closePromiseCallback();
this._connection = null;
}
}
export class WKSession extends platform.EventEmitter {
connection: WKConnection | null;
readonly sessionId: string;
private _rawSend: (message: any) => void;
connection?: WKConnection;
errorText: string;
readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
readonly sessionId: string;
private readonly _rawSend: (message: any) => void;
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: 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;
@ -271,8 +220,8 @@ export class WKSession extends platform.EventEmitter {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${this.errorText}`));
this._callbacks.clear();
this.connection = null;
Promise.resolve().then(() => this.emit(WKSessionEvents.Disconnected));
this.connection = undefined;
this.emit(WKSessionEvents.Disconnected);
}
dispatchMessage(object: any) {
@ -293,36 +242,6 @@ export class WKSession extends platform.EventEmitter {
}
}
export class WKTargetSession extends WKSession {
private _provisionalMessages?: string[];
constructor(pageProxySession: WKPageProxySession, targetInfo: Protocol.Target.TargetInfo) {
super(pageProxySession._connection, targetInfo.targetId, `The ${targetInfo.type} has been closed.`, (message: any) => {
pageProxySession.send('Target.sendMessageToTarget', {
message: JSON.stringify(message), targetId: targetInfo.targetId
}).catch(e => {
this.dispatchMessage({ id: message.id, error: { message: e.message } });
});
});
if (targetInfo.isProvisional)
this._provisionalMessages = [];
}
isProvisional() : boolean {
return !!this._provisionalMessages;
}
_addProvisionalMessage(message: string) {
this._provisionalMessages.push(message);
}
_takeProvisionalMessagesAndCommit() : string[] {
const messages = this._provisionalMessages;
this._provisionalMessages = undefined;
return messages;
}
}
export function createProtocolError(error: Error, method: string, object: { error: { message: string; data: any; }; }): Error {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error)

View file

@ -18,7 +18,7 @@
import * as input from '../input';
import { helper } from '../helper';
import { macEditingCommands } from '../usKeyboardLayout';
import { WKPageProxySession, WKTargetSession } from './wkConnection';
import { WKPageProxySession, WKSession } from './wkConnection';
function toModifiersMask(modifiers: Set<input.Modifier>): number {
// From Source/WebKit/Shared/WebEvent.h
@ -36,13 +36,13 @@ function toModifiersMask(modifiers: Set<input.Modifier>): number {
export class RawKeyboardImpl implements input.RawKeyboard {
private readonly _pageProxySession: WKPageProxySession;
private _session: WKTargetSession;
private _session: WKSession;
constructor(session: WKPageProxySession) {
this._pageProxySession = session;
}
setSession(session: WKTargetSession) {
setSession(session: WKSession) {
this._session = session;
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { WKTargetSession, WKPageProxySession } from './wkConnection';
import { WKSession, WKPageProxySession } from './wkConnection';
import { Page } from '../page';
import { helper, RegisteredListener, assert } from '../helper';
import { Protocol } from './protocol';
@ -27,7 +27,7 @@ import * as platform from '../platform';
export class WKNetworkManager {
private readonly _page: Page;
private readonly _pageProxySession: WKPageProxySession;
private _session: WKTargetSession;
private _session: WKSession;
private readonly _requestIdToRequest = new Map<string, InterceptableRequest>();
private _userCacheDisabled = false;
private _sessionListeners: RegisteredListener[] = [];
@ -41,7 +41,7 @@ export class WKNetworkManager {
await this.authenticate(credentials);
}
setSession(session: WKTargetSession) {
setSession(session: WKSession) {
helper.removeEventListeners(this._sessionListeners);
this._session = session;
this._sessionListeners = [
@ -53,7 +53,7 @@ export class WKNetworkManager {
];
}
async initializeSession(session: WKTargetSession, interceptNetwork: boolean | null, offlineMode: boolean | null) {
async initializeSession(session: WKSession, interceptNetwork: boolean | null, offlineMode: boolean | null) {
const promises = [];
promises.push(session.send('Network.enable'));
if (interceptNetwork)
@ -188,14 +188,14 @@ const errorReasons: { [reason: string]: string } = {
};
class InterceptableRequest implements network.RequestDelegate {
private _session: WKTargetSession;
private _session: WKSession;
readonly request: network.Request;
_requestId: string;
_documentId: string | undefined;
_interceptedCallback: () => void;
private _interceptedPromise: Promise<unknown>;
constructor(session: WKTargetSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) {
constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) {
this._session = session;
this._requestId = event.requestId;
this._documentId = documentId;

View file

@ -19,7 +19,7 @@ import * as frames from '../frames';
import { debugError, helper, RegisteredListener } from '../helper';
import * as dom from '../dom';
import * as network from '../network';
import { WKTargetSession, WKSessionEvents, WKPageProxySession } from './wkConnection';
import { WKSession, WKSessionEvents, WKPageProxySession } from './wkConnection';
import { Events } from '../events';
import { WKExecutionContext, EVALUATION_SCRIPT_URL } from './wkExecutionContext';
import { WKNetworkManager } from './wkNetworkManager';
@ -40,7 +40,7 @@ const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
export class WKPage implements PageDelegate {
readonly rawMouse: RawMouseImpl;
readonly rawKeyboard: RawKeyboardImpl;
_session: WKTargetSession;
_session: WKSession;
readonly _page: Page;
private readonly _pageProxySession: WKPageProxySession;
private readonly _networkManager: WKNetworkManager;
@ -74,7 +74,7 @@ export class WKPage implements PageDelegate {
await Promise.all(promises);
}
setSession(session: WKTargetSession) {
setSession(session: WKSession) {
helper.removeEventListeners(this._sessionListeners);
this.disconnectFromTarget();
this._session = session;
@ -91,7 +91,7 @@ export class WKPage implements PageDelegate {
// This method is called for provisional targets as well. The session passed as the parameter
// may be different from the current session and may be destroyed without becoming current.
async _initializeSession(session: WKTargetSession) {
async _initializeSession(session: WKSession, isProvisional: boolean) {
const promises : Promise<any>[] = [
// Page agent must be enabled before Runtime.
session.send('Page.enable'),
@ -108,7 +108,7 @@ export class WKPage implements PageDelegate {
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
if (this._page._state.mediaType || this._page._state.colorScheme)
promises.push(this._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
if (session.isProvisional())
if (isProvisional)
promises.push(this._setBootstrapScripts(session));
if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
@ -288,11 +288,11 @@ export class WKPage implements PageDelegate {
});
}
private async _setExtraHTTPHeaders(session: WKTargetSession, headers: network.Headers): Promise<void> {
private async _setExtraHTTPHeaders(session: WKSession, headers: network.Headers): Promise<void> {
await session.send('Network.setExtraHTTPHeaders', { headers });
}
private async _setEmulateMedia(session: WKTargetSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void> {
private async _setEmulateMedia(session: WKSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void> {
const promises = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' }));
if (colorScheme !== null) {
@ -371,7 +371,7 @@ export class WKPage implements PageDelegate {
await this._setBootstrapScripts(this._session);
}
private async _setBootstrapScripts(session: WKTargetSession) {
private async _setBootstrapScripts(session: WKSession) {
const source = this._bootstrapScripts.join(';');
await session.send('Page.setBootstrapScript', { source });
}

View file

@ -5,11 +5,16 @@
import { BrowserContext } from '../browserContext';
import { Page } from '../page';
import { Protocol } from './protocol';
import { WKPageProxySession, WKPageProxySessionEvents, WKTargetSession } from './wkConnection';
import { WKPageProxySession, WKSession } from './wkConnection';
import { WKPage } from './wkPage';
import { RegisteredListener, helper, assert, debugError } from '../helper';
import { Events } from '../events';
// We keep provisional messages on the session instace until provisional
// target is committed. Non-provisional target (there should be just one)
// has undefined instead.
const provisionalMessagesSymbol = Symbol('provisionalMessages');
export class WKPageProxy {
private readonly _pageProxySession: WKPageProxySession;
readonly _browserContext: BrowserContext;
@ -17,7 +22,7 @@ export class WKPageProxy {
private _wkPage: WKPage | null = null;
private readonly _firstTargetPromise: Promise<void>;
private _firstTargetCallback: () => void;
private readonly _targetSessions = new Map<string, WKTargetSession>();
private readonly _sessions = new Map<string, WKSession>();
private readonly _eventListeners: RegisteredListener[];
constructor(session: WKPageProxySession, browserContext: BrowserContext) {
@ -25,9 +30,10 @@ export class WKPageProxy {
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))
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)),
];
// Intercept provisional targets during cross-process navigation.
@ -41,6 +47,9 @@ export class WKPageProxy {
dispose() {
helper.removeEventListeners(this._eventListeners);
for (const session of this._sessions.values())
session.dispose();
this._sessions.clear();
}
async page(): Promise<Page> {
@ -59,10 +68,10 @@ export class WKPageProxy {
private async _initializeWKPage(): Promise<Page> {
await this._firstTargetPromise;
let session: WKTargetSession;
for (const targetSession of this._targetSessions.values()) {
if (!targetSession.isProvisional()) {
session = targetSession;
let session: WKSession;
for (const anySession of this._sessions.values()) {
if (!(anySession as any)[provisionalMessagesSymbol]) {
session = anySession;
break;
}
}
@ -71,35 +80,70 @@ export class WKPageProxy {
this._wkPage.setSession(session);
await Promise.all([
this._wkPage._initializePageProxySession(),
this._wkPage._initializeSession(session)
this._wkPage._initializeSession(session, false),
]);
return this._wkPage._page;
}
private _onTargetCreated(session: WKTargetSession, targetInfo: Protocol.Target.TargetInfo) {
private _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
const { targetInfo } = event;
const session = new WKSession(this._pageProxySession._connection, targetInfo.targetId, `The ${targetInfo.type} has been closed.`, (message: any) => {
this._pageProxySession.send('Target.sendMessageToTarget', {
message: JSON.stringify(message), targetId: targetInfo.targetId
}).catch(e => {
session.dispatchMessage({ id: message.id, error: { message: e.message } });
});
});
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
this._targetSessions.set(targetInfo.targetId, session);
this._sessions.set(targetInfo.targetId, session);
if (this._firstTargetCallback) {
this._firstTargetCallback();
this._firstTargetCallback = null;
}
if (targetInfo.isProvisional)
(session as any)[provisionalMessagesSymbol] = [];
if (targetInfo.isProvisional && this._wkPage)
this._wkPage._initializeSession(session);
this._wkPage._initializeSession(session, true);
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);
private _onTargetDestroyed(event: Protocol.Target.targetDestroyedPayload) {
const { targetId, crashed } = event;
const session = this._sessions.get(targetId);
if (session)
session.dispose();
this._sessions.delete(targetId);
if (!this._wkPage)
return;
if (this._wkPage._session === targetSession)
if (this._wkPage._session === session)
this._wkPage.didClose(crashed);
}
private _onProvisionalTargetCommitted({oldTargetId, newTargetId}) {
const newTargetSession = this._targetSessions.get(newTargetId);
this._wkPage.setSession(newTargetSession);
private _onDispatchMessageFromTarget(event: Protocol.Target.dispatchMessageFromTargetPayload) {
const { targetId, message } = event;
const session = this._sessions.get(targetId);
assert(session, 'Unknown target: ' + targetId);
const provisionalMessages = (session as any)[provisionalMessagesSymbol];
if (provisionalMessages)
provisionalMessages.push(message);
else
session.dispatchMessage(JSON.parse(message));
}
private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) {
const { oldTargetId, newTargetId } = event;
const newSession = this._sessions.get(newTargetId);
assert(newSession, 'Unknown new target: ' + newTargetId);
const oldSession = this._sessions.get(oldTargetId);
assert(oldSession, 'Unknown old target: ' + oldTargetId);
// TODO: make some calls like screenshot catch swapped out error and retry.
oldSession.errorText = 'Target was swapped out.';
const provisionalMessages = (newSession as any)[provisionalMessagesSymbol];
assert(provisionalMessages, 'Committing target must be provisional');
(newSession as any)[provisionalMessagesSymbol] = undefined;
for (const message of provisionalMessages)
newSession.dispatchMessage(JSON.parse(message));
this._wkPage.setSession(newSession);
}
}

View file

@ -17,7 +17,7 @@
import { helper, RegisteredListener } from '../helper';
import { Page, Worker } from '../page';
import { Protocol } from './protocol';
import { WKSession, WKTargetSession } from './wkConnection';
import { WKSession } from './wkConnection';
import { WKExecutionContext } from './wkExecutionContext';
export class WKWorkers {
@ -29,7 +29,7 @@ export class WKWorkers {
this._page = page;
}
setSession(session: WKTargetSession) {
setSession(session: WKSession) {
helper.removeEventListeners(this._sessionListeners);
this._page._clearWorkers();
this._workerSessions.clear();
@ -73,7 +73,7 @@ export class WKWorkers {
];
}
async initializeSession(session: WKTargetSession) {
async initializeSession(session: WKSession) {
await session.send('Worker.enable');
}