Files
react-native-devtools-frontend/front_end/services/ServiceManager.js
T
Tim van der Lippe e4d64e7ae0 Remove unrestricted from comments that include other annotations
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>
2020-12-02 15:31:52 +00:00

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;
});
}
}