mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-01 09:47:56 +08:00
For all comments that contained both the unrestricted annotation and any other potential annotation, remove only the unrestricted annotation. R=szuend@chromium.org No-Presubmit: True Bug: 1011811 Change-Id: I27dd67f657cdb1a9233d768eea81cabe2591f5f7 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2569759 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org>
410 lines
9.7 KiB
JavaScript
410 lines
9.7 KiB
JavaScript
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import * as Common from '../common/common.js'; // eslint-disable-line no-unused-vars
|
|
import * as Host from '../host/host.js';
|
|
import * as Root from '../root/root.js';
|
|
|
|
export class ServiceManager {
|
|
/**
|
|
* @param {string} serviceName
|
|
* @return {!Promise<?Service>}
|
|
*/
|
|
createRemoteService(serviceName) {
|
|
if (!this._remoteConnection) {
|
|
const url = Root.Runtime.Runtime.queryParam('service-backend');
|
|
if (!url) {
|
|
console.error('No endpoint address specified');
|
|
return /** @type {!Promise<?Service>} */ (Promise.resolve(null));
|
|
}
|
|
this._remoteConnection = new Connection(new RemoteServicePort(url));
|
|
}
|
|
return this._remoteConnection._createService(serviceName);
|
|
}
|
|
|
|
/**
|
|
* @param {string} appName
|
|
* @param {string} serviceName
|
|
* @return {!Promise<?Service>}
|
|
*/
|
|
createAppService(appName, serviceName) {
|
|
let url = appName + '.js';
|
|
const remoteBase = Root.Runtime.Runtime.queryParam('remoteBase');
|
|
const debugFrontend = Root.Runtime.Runtime.queryParam('debugFrontend');
|
|
const isUnderTest = Host.InspectorFrontendHost.isUnderTest();
|
|
|
|
const queryParams = [];
|
|
if (remoteBase) {
|
|
queryParams.push('remoteBase=' + remoteBase);
|
|
}
|
|
if (debugFrontend) {
|
|
queryParams.push('debugFrontend=' + debugFrontend);
|
|
}
|
|
if (isUnderTest) {
|
|
queryParams.push('isUnderTest=true');
|
|
}
|
|
|
|
if (queryParams.length) {
|
|
url += `?${queryParams.join('&')}`;
|
|
}
|
|
|
|
const worker = new Worker(url, {type: 'module'});
|
|
const connection = new Connection(new WorkerServicePort(worker));
|
|
return connection._createService(serviceName);
|
|
}
|
|
}
|
|
|
|
export class Connection {
|
|
/**
|
|
* @param {!ServicePort} port
|
|
*/
|
|
constructor(port) {
|
|
this._port = port;
|
|
this._port.setHandlers(this._onMessage.bind(this), this._connectionClosed.bind(this));
|
|
|
|
this._lastId = 1;
|
|
/** @type {!Map<number, function(?Object):void>}*/
|
|
this._callbacks = new Map();
|
|
/** @type {!Map<string, !Service>}*/
|
|
this._services = new Map();
|
|
}
|
|
|
|
/**
|
|
* @param {string} serviceName
|
|
* @return {!Promise<?Service>}
|
|
*/
|
|
_createService(serviceName) {
|
|
return this._sendCommand(serviceName + '.create').then(result => {
|
|
if (!result) {
|
|
console.error('Could not initialize service: ' + serviceName);
|
|
return null;
|
|
}
|
|
const service = new Service(this, serviceName, result.id);
|
|
this._services.set(serviceName + ':' + result.id, service);
|
|
return service;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {!Service} service
|
|
*/
|
|
_serviceDisposed(service) {
|
|
this._services.delete(service._serviceName + ':' + service._objectId);
|
|
if (!this._services.size) {
|
|
// Terminate the connection since it is no longer used.
|
|
this._port.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} method
|
|
* @param {!Object=} params
|
|
* @return {!Promise<?Object<string, ?>>}
|
|
*/
|
|
_sendCommand(method, params) {
|
|
const id = this._lastId++;
|
|
const message = JSON.stringify({id: id, method: method, params: params || {}});
|
|
return this._port.send(message).then(success => {
|
|
if (!success) {
|
|
return Promise.resolve(null);
|
|
}
|
|
return new Promise(fulfill => this._callbacks.set(id, fulfill));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} data
|
|
*/
|
|
_onMessage(data) {
|
|
let object;
|
|
try {
|
|
object = JSON.parse(data);
|
|
} catch (e) {
|
|
console.error(e);
|
|
return;
|
|
}
|
|
if (object.id) {
|
|
if (object.error) {
|
|
console.error('Service error: ' + object.error);
|
|
}
|
|
const callback = this._callbacks.get(object.id);
|
|
if (callback) {
|
|
callback(object.error ? null : object.result);
|
|
this._callbacks.delete(object.id);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const tokens = object.method.split('.');
|
|
const serviceName = tokens[0];
|
|
const methodName = tokens[1];
|
|
const service = this._services.get(serviceName + ':' + object.params.id);
|
|
if (!service) {
|
|
console.error('Unable to lookup stub for ' + serviceName + ':' + object.params.id);
|
|
return;
|
|
}
|
|
service._dispatchNotification(methodName, object.params);
|
|
}
|
|
|
|
_connectionClosed() {
|
|
for (const callback of this._callbacks.values()) {
|
|
callback(null);
|
|
}
|
|
this._callbacks.clear();
|
|
for (const service of this._services.values()) {
|
|
service._dispatchNotification('disposed');
|
|
}
|
|
this._services.clear();
|
|
}
|
|
}
|
|
|
|
export class Service {
|
|
/**
|
|
* @param {!Connection} connection
|
|
* @param {string} serviceName
|
|
* @param {string} objectId
|
|
*/
|
|
constructor(connection, serviceName, objectId) {
|
|
this._connection = connection;
|
|
this._serviceName = serviceName;
|
|
this._objectId = objectId;
|
|
/** @type {!Map<string, function(!Object=):void>}*/
|
|
this._notificationHandlers = new Map();
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<?>}
|
|
*/
|
|
dispose() {
|
|
const params = {id: this._objectId};
|
|
return this._connection._sendCommand(this._serviceName + '.dispose', params).then(() => {
|
|
this._connection._serviceDisposed(this);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} methodName
|
|
* @param {function(!Object=):void} handler
|
|
*/
|
|
on(methodName, handler) {
|
|
this._notificationHandlers.set(methodName, handler);
|
|
}
|
|
|
|
/**
|
|
* @param {string} methodName
|
|
* @param {!Object<string, ?>=} params
|
|
* @return {!Promise<?>}
|
|
*/
|
|
send(methodName, params) {
|
|
params = params || {};
|
|
params.id = this._objectId;
|
|
return this._connection._sendCommand(this._serviceName + '.' + methodName, params);
|
|
}
|
|
|
|
/**
|
|
* @param {string} methodName
|
|
* @param {!Object=} params
|
|
*/
|
|
_dispatchNotification(methodName, params) {
|
|
const handler = this._notificationHandlers.get(methodName);
|
|
if (!handler) {
|
|
console.error('Could not report notification \'' + methodName + '\' on \'' + this._objectId + '\'');
|
|
return;
|
|
}
|
|
handler(params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements {ServicePort}
|
|
*/
|
|
export class RemoteServicePort {
|
|
/**
|
|
* @param {string} url
|
|
*/
|
|
constructor(url) {
|
|
this._url = url;
|
|
/** @type {?WebSocket} */
|
|
this._socket;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {function(string):void} messageHandler
|
|
* @param {function():void} closeHandler
|
|
*/
|
|
setHandlers(messageHandler, closeHandler) {
|
|
this._messageHandler = messageHandler;
|
|
this._closeHandler = closeHandler;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
_open() {
|
|
if (!this._connectionPromise) {
|
|
this._connectionPromise = new Promise(promiseBody.bind(this));
|
|
}
|
|
return this._connectionPromise;
|
|
|
|
/**
|
|
* @param {function(boolean):void} fulfill
|
|
* @this {RemoteServicePort}
|
|
*/
|
|
function promiseBody(fulfill) {
|
|
/** @type {?WebSocket} */
|
|
let socket;
|
|
try {
|
|
socket = new WebSocket(/** @type {string} */ (this._url));
|
|
socket.onmessage = onMessage.bind(this);
|
|
socket.onclose = onClose.bind(this);
|
|
socket.onopen = onConnect.bind(this);
|
|
} catch (e) {
|
|
fulfill(false);
|
|
}
|
|
|
|
/**
|
|
* @this {RemoteServicePort}
|
|
*/
|
|
function onConnect() {
|
|
this._socket = socket;
|
|
fulfill(true);
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.EventTarget.EventTargetEvent} event
|
|
* @this {RemoteServicePort}
|
|
*/
|
|
function onMessage(event) {
|
|
if (this._messageHandler) {
|
|
this._messageHandler(event.data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @this {RemoteServicePort}
|
|
*/
|
|
function onClose() {
|
|
if (!this._socket) {
|
|
fulfill(false);
|
|
}
|
|
this._socketClosed(!!this._socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {string} message
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
send(message) {
|
|
return this._open().then(() => {
|
|
if (this._socket) {
|
|
this._socket.send(message);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<?>}
|
|
*/
|
|
close() {
|
|
return this._open().then(() => {
|
|
if (this._socket) {
|
|
this._socket.close();
|
|
this._socketClosed(true);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {boolean=} notifyClient
|
|
*/
|
|
_socketClosed(notifyClient) {
|
|
this._socket = null;
|
|
delete this._connectionPromise;
|
|
if (notifyClient && this._closeHandler) {
|
|
this._closeHandler();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements {ServicePort}
|
|
*/
|
|
export class WorkerServicePort {
|
|
/**
|
|
* @param {!Worker} worker
|
|
*/
|
|
constructor(worker) {
|
|
this._worker = worker;
|
|
|
|
/** @type {function(boolean):void} */
|
|
let fulfill;
|
|
this._workerPromise = new Promise(resolve => {
|
|
fulfill = resolve;
|
|
});
|
|
|
|
this._worker.onmessage = onMessage.bind(this);
|
|
|
|
/**
|
|
* @param {!MessageEvent} event
|
|
* @this {WorkerServicePort}
|
|
*/
|
|
function onMessage(event) {
|
|
if (event.data === 'workerReady') {
|
|
fulfill(true);
|
|
return;
|
|
}
|
|
if (this._messageHandler) {
|
|
this._messageHandler(event.data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {function(string):void} messageHandler
|
|
* @param {function():void} closeHandler
|
|
*/
|
|
setHandlers(messageHandler, closeHandler) {
|
|
this._messageHandler = messageHandler;
|
|
this._closeHandler = closeHandler;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {string} message
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
send(message) {
|
|
return this._workerPromise.then(() => {
|
|
try {
|
|
this._worker.postMessage(message);
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<?>}
|
|
*/
|
|
close() {
|
|
return this._workerPromise.then(() => {
|
|
if (this._worker) {
|
|
this._worker.terminate();
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|