2019-12-19 16:53:24 -08:00
|
|
|
/**
|
|
|
|
* Copyright 2017 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.
|
|
|
|
*/
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
import { EventEmitter } from 'events';
|
2020-08-23 15:39:03 -07:00
|
|
|
import { assert } from '../../utils/utils';
|
2020-08-24 06:51:51 -07:00
|
|
|
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
2019-12-19 16:53:24 -08:00
|
|
|
import { Protocol } from './protocol';
|
2020-08-23 15:39:03 -07:00
|
|
|
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
2020-12-08 09:35:28 -08:00
|
|
|
import { debugLogger, RecentLogsCollector } from '../../utils/debugLogger';
|
2020-11-11 15:12:10 -08:00
|
|
|
import { ProtocolLogger } from '../types';
|
2020-12-08 09:35:28 -08:00
|
|
|
import { helper } from '../helper';
|
2019-12-19 16:53:24 -08:00
|
|
|
|
2020-01-23 14:40:37 -08:00
|
|
|
// WKPlaywright uses this special id to issue Browser.close command which we
|
2020-01-09 15:14:35 -08:00
|
|
|
// should ignore.
|
2020-01-08 13:55:38 -08:00
|
|
|
export const kBrowserCloseMessageId = -9999;
|
2020-01-08 07:13:51 -08:00
|
|
|
|
2020-01-09 15:14:35 -08:00
|
|
|
// We emulate kPageProxyMessageReceived message to unify it with Browser.pageProxyCreated
|
|
|
|
// and Browser.pageProxyDestroyed for easier management.
|
|
|
|
export const kPageProxyMessageReceived = 'kPageProxyMessageReceived';
|
|
|
|
export type PageProxyMessageReceivedPayload = { pageProxyId: string, message: any };
|
|
|
|
|
|
|
|
export class WKConnection {
|
2019-12-19 16:53:24 -08:00
|
|
|
private readonly _transport: ConnectionTransport;
|
2020-03-09 16:53:33 -07:00
|
|
|
private readonly _onDisconnect: () => void;
|
2020-11-11 15:12:10 -08:00
|
|
|
private readonly _protocolLogger: ProtocolLogger;
|
2020-12-08 09:35:28 -08:00
|
|
|
readonly _browserLogsCollector: RecentLogsCollector;
|
2020-03-09 16:53:33 -07:00
|
|
|
private _lastId = 0;
|
2020-01-07 10:39:01 -08:00
|
|
|
private _closed = false;
|
2020-01-09 15:14:35 -08:00
|
|
|
readonly browserSession: WKSession;
|
|
|
|
|
2020-12-08 09:35:28 -08:00
|
|
|
constructor(transport: ConnectionTransport, onDisconnect: () => void, protocolLogger: ProtocolLogger, browserLogsCollector: RecentLogsCollector) {
|
2019-12-19 16:53:24 -08:00
|
|
|
this._transport = transport;
|
|
|
|
this._transport.onmessage = this._dispatchMessage.bind(this);
|
|
|
|
this._transport.onclose = this._onClose.bind(this);
|
2020-01-09 15:14:35 -08:00
|
|
|
this._onDisconnect = onDisconnect;
|
2020-11-11 15:12:10 -08:00
|
|
|
this._protocolLogger = protocolLogger;
|
2020-12-08 09:35:28 -08:00
|
|
|
this._browserLogsCollector = browserLogsCollector;
|
2020-01-09 15:14:35 -08:00
|
|
|
this.browserSession = new WKSession(this, '', 'Browser has been closed.', (message: any) => {
|
|
|
|
this.rawSend(message);
|
|
|
|
});
|
2019-12-19 16:53:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-07 10:39:01 -08:00
|
|
|
nextMessageId(): number {
|
|
|
|
return ++this._lastId;
|
|
|
|
}
|
|
|
|
|
2020-03-27 15:18:34 -07:00
|
|
|
rawSend(message: ProtocolRequest) {
|
2020-11-11 15:12:10 -08:00
|
|
|
this._protocolLogger('send', message);
|
2020-03-26 23:30:55 -07:00
|
|
|
this._transport.send(message);
|
2019-12-19 16:53:24 -08:00
|
|
|
}
|
|
|
|
|
2020-03-27 15:18:34 -07:00
|
|
|
private _dispatchMessage(message: ProtocolResponse) {
|
2020-11-11 15:12:10 -08:00
|
|
|
this._protocolLogger('receive', message);
|
2020-03-26 23:30:55 -07:00
|
|
|
if (message.id === kBrowserCloseMessageId)
|
2020-01-09 15:14:35 -08:00
|
|
|
return;
|
2020-03-26 23:30:55 -07:00
|
|
|
if (message.pageProxyId) {
|
|
|
|
const payload: PageProxyMessageReceivedPayload = { message: message, pageProxyId: message.pageProxyId };
|
2020-01-09 15:14:35 -08:00
|
|
|
this.browserSession.dispatchMessage({ method: kPageProxyMessageReceived, params: payload });
|
|
|
|
return;
|
2020-01-07 10:39:01 -08:00
|
|
|
}
|
2020-03-26 23:30:55 -07:00
|
|
|
this.browserSession.dispatchMessage(message);
|
2020-01-07 10:39:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
_onClose() {
|
|
|
|
this._closed = true;
|
2020-01-13 13:33:25 -08:00
|
|
|
this._transport.onmessage = undefined;
|
|
|
|
this._transport.onclose = undefined;
|
2020-12-08 09:35:28 -08:00
|
|
|
this.browserSession.dispose(true);
|
2020-01-09 15:14:35 -08:00
|
|
|
this._onDisconnect();
|
2020-01-07 10:39:01 -08:00
|
|
|
}
|
|
|
|
|
2020-01-22 17:42:10 -08:00
|
|
|
isClosed() {
|
|
|
|
return this._closed;
|
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
|
|
|
if (!this._closed)
|
|
|
|
this._transport.close();
|
2020-01-07 10:39:01 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
export class WKSession extends EventEmitter {
|
2020-01-13 13:33:25 -08:00
|
|
|
connection: WKConnection;
|
2020-01-08 16:34:45 -08:00
|
|
|
errorText: string;
|
2020-01-09 11:02:55 -08:00
|
|
|
readonly sessionId: string;
|
|
|
|
|
2020-01-13 13:33:25 -08:00
|
|
|
private _disposed = false;
|
2020-01-09 11:02:55 -08:00
|
|
|
private readonly _rawSend: (message: any) => void;
|
2020-02-07 13:36:49 -08:00
|
|
|
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
2020-04-15 00:04:35 -07:00
|
|
|
private _crashed: boolean = false;
|
2020-01-08 16:34:45 -08:00
|
|
|
|
2019-12-19 16:53:24 -08:00
|
|
|
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;
|
|
|
|
|
2020-01-08 16:34:45 -08:00
|
|
|
constructor(connection: WKConnection, sessionId: string, errorText: string, rawSend: (message: any) => void) {
|
|
|
|
super();
|
|
|
|
this.connection = connection;
|
|
|
|
this.sessionId = sessionId;
|
|
|
|
this._rawSend = rawSend;
|
|
|
|
this.errorText = errorText;
|
2020-01-13 13:33:25 -08:00
|
|
|
|
|
|
|
this.on = super.on;
|
|
|
|
this.off = super.removeListener;
|
|
|
|
this.addListener = super.addListener;
|
|
|
|
this.removeListener = super.removeListener;
|
|
|
|
this.once = super.once;
|
2020-01-08 16:34:45 -08:00
|
|
|
}
|
|
|
|
|
2020-01-30 17:30:47 -08:00
|
|
|
async send<T extends keyof Protocol.CommandParameters>(
|
2020-01-07 12:59:01 -08:00
|
|
|
method: T,
|
|
|
|
params?: Protocol.CommandParameters[T]
|
|
|
|
): Promise<Protocol.CommandReturnValues[T]> {
|
2020-04-15 00:04:35 -07:00
|
|
|
if (this._crashed)
|
|
|
|
throw new Error('Target crashed');
|
2020-01-13 13:33:25 -08:00
|
|
|
if (this._disposed)
|
2020-01-30 17:30:47 -08:00
|
|
|
throw new Error(`Protocol error (${method}): ${this.errorText}`);
|
2020-01-08 16:34:45 -08:00
|
|
|
const id = this.connection.nextMessageId();
|
|
|
|
const messageObj = { id, method, params };
|
2020-01-30 17:30:47 -08:00
|
|
|
this._rawSend(messageObj);
|
|
|
|
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
|
2020-01-08 16:34:45 -08:00
|
|
|
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
|
|
|
});
|
2020-01-07 12:59:01 -08:00
|
|
|
}
|
|
|
|
|
2020-06-05 07:50:26 -07:00
|
|
|
sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
|
2020-08-17 14:12:31 -07:00
|
|
|
return this.send(method, params).catch(error => debugLogger.log('error', error));
|
2020-06-05 07:50:26 -07:00
|
|
|
}
|
|
|
|
|
2020-04-15 00:04:35 -07:00
|
|
|
markAsCrashed() {
|
|
|
|
this._crashed = true;
|
|
|
|
}
|
|
|
|
|
2020-01-08 16:34:45 -08:00
|
|
|
isDisposed(): boolean {
|
2020-01-13 13:33:25 -08:00
|
|
|
return this._disposed;
|
2020-01-08 16:34:45 -08:00
|
|
|
}
|
|
|
|
|
2020-12-08 09:35:28 -08:00
|
|
|
dispose(disconnected: boolean) {
|
|
|
|
if (disconnected)
|
|
|
|
this.errorText = 'Browser closed.' + helper.formatBrowserLogs(this.connection._browserLogsCollector.recentLogs());
|
2020-01-08 16:34:45 -08:00
|
|
|
for (const callback of this._callbacks.values())
|
2020-05-28 16:33:31 -07:00
|
|
|
callback.reject(rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): ${this.errorText}`));
|
2020-01-08 16:34:45 -08:00
|
|
|
this._callbacks.clear();
|
2020-01-13 13:33:25 -08:00
|
|
|
this._disposed = true;
|
2020-01-08 16:34:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
dispatchMessage(object: any) {
|
2020-01-07 12:59:01 -08:00
|
|
|
if (object.id && this._callbacks.has(object.id)) {
|
2020-01-13 13:33:25 -08:00
|
|
|
const callback = this._callbacks.get(object.id)!;
|
2020-01-07 12:59:01 -08:00
|
|
|
this._callbacks.delete(object.id);
|
|
|
|
if (object.error)
|
2020-03-26 23:30:55 -07:00
|
|
|
callback.reject(createProtocolError(callback.error, callback.method, object.error));
|
2020-01-07 12:59:01 -08:00
|
|
|
else
|
|
|
|
callback.resolve(object.result);
|
2020-06-10 13:36:45 -07:00
|
|
|
} else if (object.id && !object.error) {
|
2020-01-08 16:34:45 -08:00
|
|
|
// Response might come after session has been disposed and rejected all callbacks.
|
|
|
|
assert(this.isDisposed());
|
2020-01-07 12:59:01 -08:00
|
|
|
} else {
|
|
|
|
Promise.resolve().then(() => this.emit(object.method, object.params));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-26 23:30:55 -07:00
|
|
|
export function createProtocolError(error: Error, method: string, protocolError: { message: string; data: any; }): Error {
|
|
|
|
let message = `Protocol error (${method}): ${protocolError.message}`;
|
|
|
|
if ('data' in protocolError)
|
|
|
|
message += ` ${JSON.stringify(protocolError.data)}`;
|
2020-05-28 16:33:31 -07:00
|
|
|
return rewriteErrorMessage(error, message);
|
2019-12-19 16:53:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function isSwappedOutError(e: Error) {
|
|
|
|
return e.message.includes('Target was swapped out.');
|
|
|
|
}
|