2020-06-25 16:05:36 -07:00
|
|
|
/**
|
|
|
|
* 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-06-30 22:21:17 -07:00
|
|
|
import { Browser } from './browser';
|
|
|
|
import { BrowserContext } from './browserContext';
|
|
|
|
import { BrowserType } from './browserType';
|
|
|
|
import { ChannelOwner } from './channelOwner';
|
|
|
|
import { ElementHandle } from './elementHandle';
|
|
|
|
import { Frame } from './frame';
|
|
|
|
import { JSHandle } from './jsHandle';
|
|
|
|
import { Request, Response, Route } from './network';
|
|
|
|
import { Page, BindingCall } from './page';
|
|
|
|
import { Worker } from './worker';
|
2020-06-25 16:05:36 -07:00
|
|
|
import debug = require('debug');
|
2020-06-30 22:21:17 -07:00
|
|
|
import { ConsoleMessage } from './consoleMessage';
|
|
|
|
import { Dialog } from './dialog';
|
|
|
|
import { Download } from './download';
|
|
|
|
import { parseError } from '../serializers';
|
|
|
|
import { BrowserServer } from './browserServer';
|
2020-07-07 18:47:00 -07:00
|
|
|
import { CDPSession } from './cdpSession';
|
2020-07-08 18:42:04 -07:00
|
|
|
import { Playwright } from './playwright';
|
2020-07-13 21:46:59 -07:00
|
|
|
import { Electron, ElectronApplication } from './electron';
|
2020-07-10 15:11:47 -07:00
|
|
|
import { Channel } from '../channels';
|
2020-07-13 15:26:09 -07:00
|
|
|
import { ChromiumBrowser } from './chromiumBrowser';
|
|
|
|
import { ChromiumBrowserContext } from './chromiumBrowserContext';
|
2020-07-13 17:47:15 -07:00
|
|
|
import { Selectors } from './selectors';
|
2020-07-14 10:51:37 -07:00
|
|
|
import { Stream } from './stream';
|
2020-07-24 15:16:33 -07:00
|
|
|
import { createScheme, Validator, ValidationError } from '../validator';
|
2020-07-26 21:27:09 -07:00
|
|
|
import { WebKitBrowser } from './webkitBrowser';
|
|
|
|
import { FirefoxBrowser } from './firefoxBrowser';
|
2020-07-10 15:11:47 -07:00
|
|
|
|
|
|
|
class Root extends ChannelOwner<Channel, {}> {
|
|
|
|
constructor(connection: Connection) {
|
2020-07-27 10:21:39 -07:00
|
|
|
super(connection, '', '', {});
|
2020-07-10 15:11:47 -07:00
|
|
|
}
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
|
|
|
|
export class Connection {
|
2020-07-10 15:11:47 -07:00
|
|
|
readonly _objects = new Map<string, ChannelOwner>();
|
|
|
|
private _waitingForObject = new Map<string, any>();
|
2020-07-13 08:31:20 -07:00
|
|
|
onmessage = (message: object): void => {};
|
2020-06-27 11:10:07 -07:00
|
|
|
private _lastId = 0;
|
|
|
|
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void }>();
|
2020-07-10 15:11:47 -07:00
|
|
|
private _rootObject: ChannelOwner;
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-07-01 13:55:29 -07:00
|
|
|
constructor() {
|
2020-07-10 15:11:47 -07:00
|
|
|
this._rootObject = new Root(this);
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:55:29 -07:00
|
|
|
async waitForObjectWithKnownName(guid: string): Promise<any> {
|
|
|
|
if (this._objects.has(guid))
|
|
|
|
return this._objects.get(guid)!;
|
2020-06-26 12:28:27 -07:00
|
|
|
return new Promise(f => this._waitingForObject.set(guid, f));
|
|
|
|
}
|
|
|
|
|
2020-07-30 10:22:28 -07:00
|
|
|
getObjectWithKnownName(guid: string): any {
|
|
|
|
return this._objects.get(guid)!;
|
|
|
|
}
|
|
|
|
|
2020-07-22 18:05:07 -07:00
|
|
|
async sendMessageToServer(type: string, guid: string, method: string, params: any): Promise<any> {
|
2020-06-27 11:10:07 -07:00
|
|
|
const id = ++this._lastId;
|
2020-07-22 18:05:07 -07:00
|
|
|
const validated = method === 'debugScopeState' ? params : validateParams(type, method, params);
|
|
|
|
const converted = { id, guid, method, params: validated };
|
2020-06-25 16:05:36 -07:00
|
|
|
debug('pw:channel:command')(converted);
|
2020-07-13 08:31:20 -07:00
|
|
|
this.onmessage(converted);
|
2020-06-27 11:10:07 -07:00
|
|
|
return new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject }));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:55:29 -07:00
|
|
|
_debugScopeState(): any {
|
2020-07-10 15:11:47 -07:00
|
|
|
return this._rootObject._debugScopeState();
|
2020-07-01 13:55:29 -07:00
|
|
|
}
|
|
|
|
|
2020-07-13 08:31:20 -07:00
|
|
|
dispatch(message: object) {
|
|
|
|
const { id, guid, method, params, result, error } = message as any;
|
2020-06-27 11:10:07 -07:00
|
|
|
if (id) {
|
2020-07-13 08:31:20 -07:00
|
|
|
debug('pw:channel:response')(message);
|
2020-06-27 11:10:07 -07:00
|
|
|
const callback = this._callbacks.get(id)!;
|
|
|
|
this._callbacks.delete(id);
|
|
|
|
if (error)
|
|
|
|
callback.reject(parseError(error));
|
|
|
|
else
|
|
|
|
callback.resolve(this._replaceGuidsWithChannels(result));
|
|
|
|
return;
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-07-13 08:31:20 -07:00
|
|
|
debug('pw:channel:event')(message);
|
2020-06-25 16:05:36 -07:00
|
|
|
if (method === '__create__') {
|
2020-07-10 15:11:47 -07:00
|
|
|
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
return;
|
|
|
|
}
|
2020-07-27 10:21:39 -07:00
|
|
|
if (method === '__dispose__') {
|
|
|
|
this._objects.get(guid)!._dispose();
|
|
|
|
return;
|
|
|
|
}
|
2020-07-01 13:55:29 -07:00
|
|
|
const object = this._objects.get(guid)!;
|
|
|
|
object._channel.emit(method, this._replaceGuidsWithChannels(params));
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-10 15:11:47 -07:00
|
|
|
private _replaceGuidsWithChannels(payload: any): any {
|
2020-06-25 16:05:36 -07:00
|
|
|
if (!payload)
|
|
|
|
return payload;
|
|
|
|
if (Array.isArray(payload))
|
|
|
|
return payload.map(p => this._replaceGuidsWithChannels(p));
|
2020-07-01 13:55:29 -07:00
|
|
|
if (payload.guid && this._objects.has(payload.guid))
|
|
|
|
return this._objects.get(payload.guid)!._channel;
|
2020-06-26 17:24:21 -07:00
|
|
|
if (typeof payload === 'object') {
|
|
|
|
const result: any = {};
|
|
|
|
for (const key of Object.keys(payload))
|
|
|
|
result[key] = this._replaceGuidsWithChannels(payload[key]);
|
|
|
|
return result;
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
return payload;
|
|
|
|
}
|
2020-07-01 13:55:29 -07:00
|
|
|
|
2020-07-10 15:11:47 -07:00
|
|
|
private _createRemoteObject(parentGuid: string, type: string, guid: string, initializer: any): any {
|
|
|
|
const parent = this._objects.get(parentGuid)!;
|
2020-07-01 13:55:29 -07:00
|
|
|
let result: ChannelOwner<any, any>;
|
2020-07-10 15:11:47 -07:00
|
|
|
initializer = this._replaceGuidsWithChannels(initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
switch (type) {
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'BindingCall':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new BindingCall(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Browser':
|
2020-07-13 15:26:09 -07:00
|
|
|
if ((parent as BrowserType).name() === 'chromium')
|
|
|
|
result = new ChromiumBrowser(parent, type, guid, initializer);
|
2020-07-26 21:27:09 -07:00
|
|
|
else if ((parent as BrowserType).name() === 'webkit')
|
|
|
|
result = new WebKitBrowser(parent, type, guid, initializer);
|
|
|
|
else if ((parent as BrowserType).name() === 'firefox')
|
|
|
|
result = new FirefoxBrowser(parent, type, guid, initializer);
|
2020-07-13 15:26:09 -07:00
|
|
|
else
|
|
|
|
result = new Browser(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'BrowserContext':
|
2020-07-13 21:46:59 -07:00
|
|
|
let browserName = '';
|
2020-07-24 16:22:20 -07:00
|
|
|
if (parent instanceof ElectronApplication) {
|
|
|
|
// Launching electron produces ElectronApplication parent for BrowserContext.
|
2020-07-13 21:46:59 -07:00
|
|
|
browserName = 'electron';
|
|
|
|
} else if (parent instanceof Browser) {
|
|
|
|
// Launching a browser produces Browser parent for BrowserContext.
|
|
|
|
browserName = parent._browserType.name();
|
|
|
|
} else {
|
|
|
|
// Launching persistent context produces BrowserType parent for BrowserContext.
|
|
|
|
browserName = (parent as BrowserType).name();
|
|
|
|
}
|
|
|
|
if (browserName === 'chromium')
|
2020-07-13 15:26:09 -07:00
|
|
|
result = new ChromiumBrowserContext(parent, type, guid, initializer);
|
|
|
|
else
|
2020-07-13 21:46:59 -07:00
|
|
|
result = new BrowserContext(parent, type, guid, initializer, browserName);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'BrowserServer':
|
|
|
|
result = new BrowserServer(parent, type, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'BrowserType':
|
|
|
|
result = new BrowserType(parent, type, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'CDPSession':
|
|
|
|
result = new CDPSession(parent, type, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'ConsoleMessage':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new ConsoleMessage(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Dialog':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Dialog(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Download':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Download(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Electron':
|
2020-07-13 21:46:59 -07:00
|
|
|
result = new Electron(parent, type, guid, initializer);
|
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'ElectronApplication':
|
2020-07-13 21:46:59 -07:00
|
|
|
result = new ElectronApplication(parent, type, guid, initializer);
|
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'ElementHandle':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new ElementHandle(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Frame':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Frame(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'JSHandle':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new JSHandle(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Page':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Page(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Playwright':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Playwright(parent, type, guid, initializer);
|
2020-07-08 18:42:04 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Request':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Request(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Stream':
|
2020-07-14 10:51:37 -07:00
|
|
|
result = new Stream(parent, type, guid, initializer);
|
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Response':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Response(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Route':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Route(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Selectors':
|
2020-07-13 17:47:15 -07:00
|
|
|
result = new Selectors(parent, type, guid, initializer);
|
|
|
|
break;
|
2020-07-22 10:37:21 -07:00
|
|
|
case 'Worker':
|
2020-07-10 18:00:10 -07:00
|
|
|
result = new Worker(parent, type, guid, initializer);
|
2020-07-01 13:55:29 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error('Missing type ' + type);
|
|
|
|
}
|
2020-07-10 15:11:47 -07:00
|
|
|
const callback = this._waitingForObject.get(guid);
|
2020-07-01 13:55:29 -07:00
|
|
|
if (callback) {
|
|
|
|
callback(result);
|
2020-07-10 15:11:47 -07:00
|
|
|
this._waitingForObject.delete(guid);
|
2020-07-01 13:55:29 -07:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
2020-07-24 15:16:33 -07:00
|
|
|
|
|
|
|
const tChannel = (name: string): Validator => {
|
|
|
|
return (arg: any, path: string) => {
|
|
|
|
if (arg._object instanceof ChannelOwner && (name === '*' || arg._object._type === name))
|
|
|
|
return { guid: arg._object._guid };
|
|
|
|
throw new ValidationError(`${path}: expected ${name}`);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const scheme = createScheme(tChannel);
|
|
|
|
|
|
|
|
function validateParams(type: string, method: string, params: any): any {
|
|
|
|
const name = type + method[0].toUpperCase() + method.substring(1) + 'Params';
|
|
|
|
if (!scheme[name])
|
|
|
|
throw new ValidationError(`Uknown scheme for ${type}.${method}`);
|
|
|
|
return scheme[name](params, '');
|
|
|
|
}
|