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-06-25 16:05:36 -07:00
|
|
|
|
|
|
|
export class Connection {
|
2020-07-01 13:55:29 -07:00
|
|
|
readonly _objects = new Map<string, ChannelOwner<any, any>>();
|
|
|
|
readonly _waitingForObject = new Map<string, any>();
|
2020-06-27 11:32:27 -07:00
|
|
|
onmessage = (message: string): 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-01 13:55:29 -07:00
|
|
|
readonly _scopes = new Map<string, ConnectionScope>();
|
|
|
|
private _rootScript: ConnectionScope;
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-07-01 13:55:29 -07:00
|
|
|
constructor() {
|
|
|
|
this._rootScript = this.createScope('');
|
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-06-27 11:10:07 -07:00
|
|
|
async sendMessageToServer(message: { guid: string, method: string, params: any }): Promise<any> {
|
|
|
|
const id = ++this._lastId;
|
|
|
|
const converted = { id, ...message, params: this._replaceChannelsWithGuids(message.params) };
|
2020-06-25 16:05:36 -07:00
|
|
|
debug('pw:channel:command')(converted);
|
2020-06-27 11:32:27 -07:00
|
|
|
this.onmessage(JSON.stringify(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 {
|
|
|
|
const scopeState: any = {};
|
|
|
|
scopeState.objects = [...this._objects.keys()];
|
|
|
|
scopeState.scopes = [...this._scopes.values()].map(scope => ({
|
|
|
|
_guid: scope._guid,
|
|
|
|
objects: [...scope._objects.keys()]
|
|
|
|
}));
|
|
|
|
return scopeState;
|
|
|
|
}
|
|
|
|
|
2020-06-30 22:21:17 -07:00
|
|
|
dispatch(message: string) {
|
2020-06-27 11:10:07 -07:00
|
|
|
const parsedMessage = JSON.parse(message);
|
|
|
|
const { id, guid, method, params, result, error } = parsedMessage;
|
|
|
|
if (id) {
|
|
|
|
debug('pw:channel:response')(parsedMessage);
|
|
|
|
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-06-27 11:10:07 -07:00
|
|
|
debug('pw:channel:event')(parsedMessage);
|
2020-06-25 16:05:36 -07:00
|
|
|
if (method === '__create__') {
|
2020-07-01 13:55:29 -07:00
|
|
|
const scopeObject = this._objects.get(guid);
|
|
|
|
const scope = scopeObject ? scopeObject._scope : this._rootScript;
|
|
|
|
scope.createRemoteObject(params.type, params.guid, params.initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private _replaceChannelsWithGuids(payload: any): any {
|
|
|
|
if (!payload)
|
|
|
|
return payload;
|
|
|
|
if (Array.isArray(payload))
|
|
|
|
return payload.map(p => this._replaceChannelsWithGuids(p));
|
2020-07-01 18:36:09 -07:00
|
|
|
if (payload._object instanceof ChannelOwner)
|
|
|
|
return { guid: payload._object.guid };
|
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._replaceChannelsWithGuids(payload[key]);
|
|
|
|
return result;
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
return payload;
|
|
|
|
}
|
|
|
|
|
2020-07-01 13:55:29 -07:00
|
|
|
_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
|
|
|
|
|
|
|
createScope(guid: string): ConnectionScope {
|
|
|
|
const scope = new ConnectionScope(this, guid);
|
|
|
|
this._scopes.set(guid, scope);
|
|
|
|
return scope;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ConnectionScope {
|
|
|
|
private _connection: Connection;
|
|
|
|
readonly _objects = new Map<string, ChannelOwner<any, any>>();
|
|
|
|
private _children = new Set<ConnectionScope>();
|
|
|
|
private _parent: ConnectionScope | undefined;
|
|
|
|
readonly _guid: string;
|
|
|
|
|
|
|
|
constructor(connection: Connection, guid: string) {
|
|
|
|
this._connection = connection;
|
|
|
|
this._guid = guid;
|
|
|
|
}
|
|
|
|
|
|
|
|
createChild(guid: string): ConnectionScope {
|
|
|
|
const scope = this._connection.createScope(guid);
|
|
|
|
this._children.add(scope);
|
|
|
|
scope._parent = this;
|
|
|
|
return scope;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
// Take care of hierarchy.
|
|
|
|
for (const child of [...this._children])
|
|
|
|
child.dispose();
|
|
|
|
this._children.clear();
|
|
|
|
|
|
|
|
// Delete self from scopes and objects.
|
|
|
|
this._connection._scopes.delete(this._guid);
|
|
|
|
this._connection._objects.delete(this._guid);
|
|
|
|
|
|
|
|
// Delete all of the objects from connection.
|
|
|
|
for (const guid of this._objects.keys())
|
|
|
|
this._connection._objects.delete(guid);
|
|
|
|
|
|
|
|
// Clean up from parent.
|
|
|
|
if (this._parent) {
|
|
|
|
this._parent._objects.delete(this._guid);
|
|
|
|
this._parent._children.delete(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async sendMessageToServer(message: { guid: string, method: string, params: any }): Promise<any> {
|
|
|
|
return this._connection.sendMessageToServer(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
createRemoteObject(type: string, guid: string, initializer: any): any {
|
|
|
|
let result: ChannelOwner<any, any>;
|
|
|
|
initializer = this._connection._replaceGuidsWithChannels(initializer);
|
|
|
|
switch (type) {
|
|
|
|
case 'bindingCall':
|
|
|
|
result = new BindingCall(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'browser':
|
|
|
|
result = new Browser(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'browserServer':
|
|
|
|
result = new BrowserServer(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'browserType':
|
|
|
|
result = new BrowserType(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'context':
|
|
|
|
result = new BrowserContext(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'consoleMessage':
|
|
|
|
result = new ConsoleMessage(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'dialog':
|
|
|
|
result = new Dialog(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'download':
|
|
|
|
result = new Download(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'elementHandle':
|
|
|
|
result = new ElementHandle(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'frame':
|
|
|
|
result = new Frame(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'jsHandle':
|
|
|
|
result = new JSHandle(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'page':
|
|
|
|
result = new Page(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'request':
|
|
|
|
result = new Request(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'response':
|
|
|
|
result = new Response(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'route':
|
|
|
|
result = new Route(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
case 'worker':
|
|
|
|
result = new Worker(this, guid, initializer);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error('Missing type ' + type);
|
|
|
|
}
|
|
|
|
this._connection._objects.set(guid, result);
|
|
|
|
this._objects.set(guid, result);
|
|
|
|
const callback = this._connection._waitingForObject.get(guid);
|
|
|
|
if (callback) {
|
|
|
|
callback(result);
|
|
|
|
this._connection._waitingForObject.delete(guid);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|