chore: simplify WKSession by providing a rawSend method (#434)

This commit is contained in:
Dmitry Gozman 2020-01-08 16:34:45 -08:00 committed by Yury Semikhatsky
parent f161a36a16
commit 9c90eed90c
5 changed files with 87 additions and 143 deletions

View File

@ -17,8 +17,8 @@ import * as accessibility from '../accessibility';
import { WKTargetSession } from './wkConnection';
import { Protocol } from './protocol';
export async function getAccessibilityTree(sesssion: WKTargetSession) {
const {axNode} = await sesssion.send('Page.accessibilitySnapshot');
export async function getAccessibilityTree(session: WKTargetSession) {
const {axNode} = await session.send('Page.accessibilitySnapshot');
return new WKAXNode(axNode);
}

View File

@ -136,8 +136,8 @@ export class WKConnection extends platform.EventEmitter {
}
}
export const WKTargetSessionEvents = {
Disconnected: Symbol('TargetSessionEvents.Disconnected')
export const WKSessionEvents = {
Disconnected: Symbol('WKSessionEvents.Disconnected')
};
export class WKPageProxySession extends platform.EventEmitter {
@ -175,12 +175,12 @@ export class WKPageProxySession extends platform.EventEmitter {
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);
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();
session.dispose();
this._sessions.delete(object.params.targetId);
}
Promise.resolve().then(() => this.emit(WKPageProxySessionEvents.TargetDestroyed, { targetId: object.params.targetId, crashed: object.params.crashed }));
@ -192,7 +192,7 @@ export class WKPageProxySession extends platform.EventEmitter {
if (session.isProvisional())
session._addProvisionalMessage(message);
else
session._dispatchMessageFromTarget(message);
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 }));
@ -202,9 +202,11 @@ export class WKPageProxySession extends platform.EventEmitter {
const oldSession = this._sessions.get(oldTargetId);
if (!oldSession)
throw new Error('Unknown old target: ' + oldTargetId);
oldSession._swappedOut = true;
// 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._dispatchMessageFromTarget(message);
newSession.dispatchMessage(JSON.parse(message));
} else {
Promise.resolve().then(() => this.emit(object.method, object.params));
}
@ -216,7 +218,7 @@ export class WKPageProxySession extends platform.EventEmitter {
dispose() {
for (const session of this._sessions.values())
session._onClosed();
session.dispose();
this._sessions.clear();
this._closePromiseCallback();
@ -225,22 +227,55 @@ export class WKPageProxySession extends platform.EventEmitter {
}
export class WKSession extends platform.EventEmitter {
connection: WKConnection | null;
readonly sessionId: string;
private _rawSend: (message: any) => void;
errorText: string;
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;
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, sessionId: string, errorText: string, rawSend: (message: any) => void) {
super();
this.connection = connection;
this.sessionId = sessionId;
this._rawSend = rawSend;
this.errorText = errorText;
}
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
throw new Error('Not implemented');
if (!this.connection)
return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`));
const id = this.connection.nextMessageId();
const messageObj = { id, method, params };
debugWrappedMessage('SEND ► ' + JSON.stringify(messageObj, null, 2));
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
});
this._rawSend(messageObj);
return result;
}
protected _dispatchMessage(message: string) {
const object = JSON.parse(message);
isDisposed(): boolean {
return !this.connection;
}
dispose() {
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));
}
dispatchMessage(object: any) {
debugWrappedMessage('◀ RECV ' + JSON.stringify(object, null, 2));
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
@ -249,27 +284,27 @@ export class WKSession extends platform.EventEmitter {
callback.reject(createProtocolError(callback.error, callback.method, object));
else
callback.resolve(object.result);
} else if (object.id) {
// Response might come after session has been disposed and rejected all callbacks.
assert(this.isDisposed());
} else {
assert(!object.id);
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
}
export class WKTargetSession extends WKSession {
_pageProxySession: WKPageProxySession;
private readonly _targetType: string;
readonly _sessionId: string;
_swappedOut = false;
private _provisionalMessages?: string[];
constructor(pageProxySession: WKPageProxySession, targetInfo: Protocol.Target.TargetInfo) {
super();
const {targetId, type, isProvisional} = targetInfo;
this._pageProxySession = pageProxySession;
this._targetType = type;
this._sessionId = targetId;
if (isProvisional)
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 = [];
}
@ -277,40 +312,6 @@ export class WKTargetSession extends WKSession {
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._pageProxySession)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const innerId = this._pageProxySession._connection.nextMessageId();
const messageObj = {
id: innerId,
method,
params
};
debugWrappedMessage('SEND ► ' + JSON.stringify(messageObj, null, 2));
// Serialize message before adding callback in case JSON throws.
const message = JSON.stringify(messageObj);
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(innerId, {resolve, reject, error: new Error(), method});
});
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
// targetDestroyed notification before response for the command, in that
// case it's safe to swallow the exception.
const callback = this._callbacks.get(innerId);
assert(!callback, 'Callback was not rejected when target was destroyed.');
});
return result;
}
_addProvisionalMessage(message: string) {
this._provisionalMessages.push(message);
}
@ -320,24 +321,6 @@ export class WKTargetSession extends WKSession {
this._provisionalMessages = undefined;
return messages;
}
_dispatchMessageFromTarget(message: string) {
console.assert(!this.isProvisional());
this._dispatchMessage(message);
}
_onClosed() {
for (const callback of this._callbacks.values()) {
// TODO: make some calls like screenshot catch swapped out error and retry.
if (this._swappedOut)
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target was swapped out.`));
else
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
}
this._callbacks.clear();
this._pageProxySession = null;
Promise.resolve().then(() => this.emit(WKTargetSessionEvents.Disconnected));
}
}
export function createProtocolError(error: Error, method: string, object: { error: { message: string; data: any; }; }): Error {

View File

@ -95,7 +95,7 @@ export class WKNetworkManager {
const frame = this._page._frameManager.frame(event.frameId);
// TODO(einbinder) this will fail if we are an XHR document request
const isNavigationRequest = event.type === 'Document';
const documentId = isNavigationRequest ? this._session._sessionId + '::' + event.loaderId : undefined;
const documentId = isNavigationRequest ? this._session.sessionId + '::' + event.loaderId : undefined;
const request = new InterceptableRequest(this._session, this._page._state.interceptNetwork, frame, event, redirectChain, documentId);
this._requestIdToRequest.set(event.requestId, request);
this._page._frameManager.requestStarted(request.request);

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, WKTargetSessionEvents, WKPageProxySession } from './wkConnection';
import { WKTargetSession, WKSessionEvents, WKPageProxySession } from './wkConnection';
import { Events } from '../events';
import { WKExecutionContext, EVALUATION_SCRIPT_URL } from './wkExecutionContext';
import { WKNetworkManager } from './wkNetworkManager';
@ -82,7 +82,6 @@ export class WKPage implements PageDelegate {
this._addSessionListeners();
this._networkManager.setSession(session);
this._workers.setSession(session);
this._page._clearWorkers();
this._isolatedWorlds = new Set();
// New bootstrap scripts may have been added during provisional load, push them
// again to be on the safe side.
@ -116,7 +115,7 @@ export class WKPage implements PageDelegate {
if (this._page._state.extraHTTPHeaders !== null)
promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
await Promise.all(promises).catch(e => {
if (session.isClosed())
if (session.isDisposed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
@ -148,7 +147,7 @@ export class WKPage implements PageDelegate {
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
helper.addEventListener(this._pageProxySession, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
helper.addEventListener(this._session, WKTargetSessionEvents.Disconnected, event => this._page._didDisconnect()),
helper.addEventListener(this._session, WKSessionEvents.Disconnected, event => this._page._didDisconnect()),
];
}
@ -192,7 +191,7 @@ export class WKPage implements PageDelegate {
}
}
// Append session id to avoid cross-process loaderId clash.
const documentId = this._session._sessionId + '::' + framePayload.loaderId;
const documentId = this._session.sessionId + '::' + framePayload.loaderId;
this._page._frameManager.frameCommittedNewDocumentNavigation(framePayload.id, framePayload.url, framePayload.name || '', documentId, initial);
}
@ -377,8 +376,8 @@ export class WKPage implements PageDelegate {
}
async closePage(runBeforeUnload: boolean): Promise<void> {
this._session._pageProxySession.send('Target.close', {
targetId: this._session._sessionId,
this._pageProxySession.send('Target.close', {
targetId: this._session.sessionId,
runBeforeUnload
}).catch(debugError);
}

View File

@ -14,15 +14,16 @@
* limitations under the License.
*/
import { assert, helper, RegisteredListener } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { Page, Worker } from '../page';
import { Protocol } from './protocol';
import { rewriteError, WKSession, WKTargetSession } from './wkConnection';
import { WKSession, WKTargetSession } from './wkConnection';
import { WKExecutionContext } from './wkExecutionContext';
export class WKWorkers {
private _sessionListeners: RegisteredListener[] = [];
private _page: Page;
private _workerSessions = new Map<string, WKSession>();
constructor(page: Page) {
this._page = page;
@ -30,10 +31,20 @@ export class WKWorkers {
setSession(session: WKTargetSession) {
helper.removeEventListeners(this._sessionListeners);
this._page._clearWorkers();
this._workerSessions.clear();
this._sessionListeners = [
helper.addEventListener(session, 'Worker.workerCreated', async (event: Protocol.Worker.workerCreatedPayload) => {
const worker = new Worker(event.url);
const workerSession = new WKWorkerSession(session, event.workerId);
const workerSession = new WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message: any) => {
session.send('Worker.sendMessageToWorker', {
workerId: event.workerId,
message: JSON.stringify(message)
}).catch(e => {
workerSession.dispatchMessage({ id: message.id, error: { message: e.message } });
});
});
this._workerSessions.set(event.workerId, workerSession);
worker._createExecutionContext(new WKExecutionContext(workerSession, undefined));
this._page._addWorker(event.workerId, worker);
workerSession.on('Console.messageAdded', event => this._onConsoleMessage(worker, event));
@ -49,7 +60,14 @@ export class WKWorkers {
// Worker can go as we are initializing it.
}
}),
helper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => {
const workerSession = this._workerSessions.get(event.workerId);
workerSession.dispatchMessage(JSON.parse(event.message));
}),
helper.addEventListener(session, 'Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => {
const workerSession = this._workerSessions.get(event.workerId);
workerSession.dispose();
this._workerSessions.delete(event.workerId);
this._page._removeWorker(event.workerId);
})
];
@ -73,59 +91,3 @@ export class WKWorkers {
this._page._addConsoleMessage(derivedType, handles, { url, lineNumber: lineNumber - 1, columnNumber: columnNumber - 1 }, handles.length ? undefined : text);
}
}
export class WKWorkerSession extends WKSession {
private _targetSession: WKTargetSession | null;
private _workerId: string;
private _lastId = 1001;
constructor(targetSession: WKTargetSession, workerId: string) {
super();
this._targetSession = targetSession;
this._workerId = workerId;
this._targetSession.on('Worker.dispatchMessageFromWorker', event => {
if (event.workerId === workerId)
this._dispatchMessage(event.message);
});
this._targetSession.on('Worker.workerTerminated', event => {
if (event.workerId === workerId)
this._workerTerminated();
});
}
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (!this._targetSession)
return Promise.reject(new Error(`Protocol error (${method}): Most likely the worker has been closed.`));
const innerId = ++this._lastId;
const messageObj = {
id: innerId,
method,
params
};
const message = JSON.stringify(messageObj);
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(innerId, {resolve, reject, error: new Error(), method});
});
this._targetSession.send('Worker.sendMessageToWorker', {
workerId: this._workerId,
message: message
}).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.
const callback = this._callbacks.get(innerId);
assert(!callback, 'Callback was not rejected when worker was terminated.');
});
return result;
}
_workerTerminated() {
for (const callback of this._callbacks.values())
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Worker terminated.`));
this._callbacks.clear();
this._targetSession = null;
}
}