| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @typedef {string} |
| * @suppress {checkTypes} |
| */ |
| export const ProtocolError = Symbol('Protocol.Error'); |
| export const DevToolsStubErrorCode = -32015; |
| // TODO(dgozman): we are not reporting generic errors in tests, but we should |
| // instead report them and just have some expected errors in test expectations. |
| const _GenericError = -32000; |
| const _ConnectionClosedErrorCode = -32001; |
| |
| /** |
| * @unrestricted |
| */ |
| export default class InspectorBackend { |
| constructor() { |
| /** @type {!Map<string, !_AgentPrototype>} */ |
| this._agentPrototypes = new Map(); |
| /** @type {!Map<string, !_DispatcherPrototype>} */ |
| this._dispatcherPrototypes = new Map(); |
| this._initialized = false; |
| } |
| |
| /** |
| * @param {string} error |
| * @param {!Object} messageObject |
| */ |
| static reportProtocolError(error, messageObject) { |
| console.error(error + ': ' + JSON.stringify(messageObject)); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isInitialized() { |
| return this._initialized; |
| } |
| |
| /** |
| * @param {string} domain |
| */ |
| _addAgentGetterMethodToProtocolTargetPrototype(domain) { |
| let upperCaseLength = 0; |
| while (upperCaseLength < domain.length && domain[upperCaseLength].toLowerCase() !== domain[upperCaseLength]) { |
| ++upperCaseLength; |
| } |
| |
| const methodName = domain.substr(0, upperCaseLength).toLowerCase() + domain.slice(upperCaseLength) + 'Agent'; |
| |
| /** |
| * @this {TargetBase} |
| */ |
| function agentGetter() { |
| return this._agents[domain]; |
| } |
| |
| TargetBase.prototype[methodName] = agentGetter; |
| |
| /** |
| * @this {TargetBase} |
| */ |
| function registerDispatcher(dispatcher) { |
| this.registerDispatcher(domain, dispatcher); |
| } |
| |
| TargetBase.prototype['register' + domain + 'Dispatcher'] = registerDispatcher; |
| } |
| |
| /** |
| * @param {string} domain |
| * @return {!_AgentPrototype} |
| */ |
| _agentPrototype(domain) { |
| if (!this._agentPrototypes.has(domain)) { |
| this._agentPrototypes.set(domain, new _AgentPrototype(domain)); |
| this._addAgentGetterMethodToProtocolTargetPrototype(domain); |
| } |
| |
| return this._agentPrototypes.get(domain); |
| } |
| |
| /** |
| * @param {string} domain |
| * @return {!_DispatcherPrototype} |
| */ |
| _dispatcherPrototype(domain) { |
| if (!this._dispatcherPrototypes.has(domain)) { |
| this._dispatcherPrototypes.set(domain, new _DispatcherPrototype()); |
| } |
| return this._dispatcherPrototypes.get(domain); |
| } |
| |
| /** |
| * @param {string} method |
| * @param {!Array.<!Object>} signature |
| * @param {!Array.<string>} replyArgs |
| * @param {boolean} hasErrorData |
| */ |
| registerCommand(method, signature, replyArgs, hasErrorData) { |
| const domainAndMethod = method.split('.'); |
| this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1], signature, replyArgs, hasErrorData); |
| this._initialized = true; |
| } |
| |
| /** |
| * @param {string} type |
| * @param {!Object} values |
| */ |
| registerEnum(type, values) { |
| const domainAndName = type.split('.'); |
| const domain = domainAndName[0]; |
| if (!Protocol[domain]) { |
| Protocol[domain] = {}; |
| } |
| |
| Protocol[domain][domainAndName[1]] = values; |
| this._initialized = true; |
| } |
| |
| /** |
| * @param {string} eventName |
| * @param {!Object} params |
| */ |
| registerEvent(eventName, params) { |
| const domain = eventName.split('.')[0]; |
| this._dispatcherPrototype(domain).registerEvent(eventName, params); |
| this._initialized = true; |
| } |
| |
| /** |
| * @param {function(T)} clientCallback |
| * @param {string} errorPrefix |
| * @param {function(new:T,S)=} constructor |
| * @param {T=} defaultValue |
| * @return {function(?string, S)} |
| * @template T,S |
| */ |
| wrapClientCallback(clientCallback, errorPrefix, constructor, defaultValue) { |
| /** |
| * @param {?string} error |
| * @param {S} value |
| * @template S |
| */ |
| function callbackWrapper(error, value) { |
| if (error) { |
| console.error(errorPrefix + error); |
| clientCallback(defaultValue); |
| return; |
| } |
| if (constructor) { |
| clientCallback(new constructor(value)); |
| } else { |
| clientCallback(value); |
| } |
| } |
| return callbackWrapper; |
| } |
| } |
| |
| /** @type {function():!Connection} */ |
| let _factory; |
| |
| /** |
| * @interface |
| */ |
| export class Connection { |
| constructor() { |
| /** @type {?function(!Object)} */ |
| this._onMessage; |
| } |
| |
| /** |
| * @param {function((!Object|string))} onMessage |
| */ |
| setOnMessage(onMessage) { |
| } |
| |
| /** |
| * @param {function(string)} onDisconnect |
| */ |
| setOnDisconnect(onDisconnect) { |
| } |
| |
| /** |
| * @param {string} message |
| */ |
| sendRawMessage(message) { |
| } |
| |
| /** |
| * @return {!Promise} |
| */ |
| disconnect() { |
| } |
| |
| /** |
| * @param {function():!Connection} factory |
| */ |
| static setFactory(factory) { |
| _factory = factory; |
| } |
| |
| /** |
| * @return {function():!Connection} |
| */ |
| static getFactory() { |
| return _factory; |
| } |
| } |
| |
| const test = { |
| /** |
| * This will get called for every protocol message. |
| * Protocol.test.dumpProtocol = console.log |
| * @type {?function(string)} |
| */ |
| dumpProtocol: null, |
| |
| /** |
| * Runs a function when no protocol activity is present. |
| * Protocol.test.deprecatedRunAfterPendingDispatches(() => console.log('done')) |
| * @type {?function(function()=)} |
| */ |
| deprecatedRunAfterPendingDispatches: null, |
| |
| /** |
| * Sends a raw message over main connection. |
| * Protocol.test.sendRawMessage('Page.enable', {}, console.log) |
| */ |
| sendRawMessage: null, |
| |
| /** |
| * Set to true to not log any errors. |
| */ |
| suppressRequestErrors: false, |
| |
| /** |
| * Set to get notified about any messages sent over protocol. |
| * @type {?function({domain: string, method: string, params: !Object, id: number}, ?TargetBase)} |
| */ |
| onMessageSent: null, |
| |
| /** |
| * Set to get notified about any messages received over protocol. |
| * @type {?function(!Object, ?TargetBase)} |
| */ |
| onMessageReceived: null, |
| }; |
| |
| class SessionRouter { |
| /** |
| * @param {!Connection} connection |
| */ |
| constructor(connection) { |
| this._connection = connection; |
| this._lastMessageId = 1; |
| this._pendingResponsesCount = 0; |
| this._domainToLogger = new Map(); |
| |
| /** @type {!Map<string, {target: !TargetBase, callbacks: !Map<number, !Protocol._Callback>, proxyConnection: ?Connection}>} */ |
| this._sessions = new Map(); |
| |
| /** @type {!Array<function()>} */ |
| this._pendingScripts = []; |
| |
| test.deprecatedRunAfterPendingDispatches = this._deprecatedRunAfterPendingDispatches.bind(this); |
| test.sendRawMessage = this._sendRawMessageForTesting.bind(this); |
| |
| this._connection.setOnMessage(this._onMessage.bind(this)); |
| |
| this._connection.setOnDisconnect(reason => { |
| const session = this._sessions.get(''); |
| if (session) { |
| session.target.dispose(reason); |
| } |
| }); |
| } |
| |
| /** |
| * @param {!TargetBase} target |
| * @param {string} sessionId |
| * @param {?Connection} proxyConnection |
| */ |
| registerSession(target, sessionId, proxyConnection) { |
| // Only the Audits panel uses proxy connections. If it is ever possible to have multiple active at the |
| // same time, it should be tested thoroughly. |
| if (proxyConnection) { |
| for (const session of this._sessions.values()) { |
| if (session.proxyConnection) { |
| console.error('Multiple simultaneous proxy connections are currently unsupported'); |
| break; |
| } |
| } |
| } |
| |
| this._sessions.set(sessionId, {target, callbacks: new Map(), proxyConnection}); |
| } |
| |
| /** |
| * @param {string} sessionId |
| */ |
| unregisterSession(sessionId) { |
| const session = this._sessions.get(sessionId); |
| for (const callback of session.callbacks.values()) { |
| SessionRouter.dispatchConnectionError(callback); |
| } |
| this._sessions.delete(sessionId); |
| } |
| |
| /** |
| * @param {string} sessionId |
| * @return {?TargetBase} |
| */ |
| _getTargetBySessionId(sessionId) { |
| const session = this._sessions.get(sessionId ? sessionId : ''); |
| if (!session) { |
| return null; |
| } |
| return session.target; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _nextMessageId() { |
| return this._lastMessageId++; |
| } |
| |
| /** |
| * @return {!Connection} |
| */ |
| connection() { |
| return this._connection; |
| } |
| |
| /** |
| * @param {string} sessionId |
| * @param {string} domain |
| * @param {string} method |
| * @param {?Object} params |
| * @param {!Protocol._Callback} callback |
| */ |
| sendMessage(sessionId, domain, method, params, callback) { |
| const messageObject = {}; |
| const messageId = this._nextMessageId(); |
| messageObject.id = messageId; |
| messageObject.method = method; |
| if (params) { |
| messageObject.params = params; |
| } |
| if (sessionId) { |
| messageObject.sessionId = sessionId; |
| } |
| |
| if (test.dumpProtocol) { |
| test.dumpProtocol('frontend: ' + JSON.stringify(messageObject)); |
| } |
| |
| if (test.onMessageSent) { |
| const paramsObject = JSON.parse(JSON.stringify(params || {})); |
| test.onMessageSent( |
| {domain, method, params: /** @type {!Object} */ (paramsObject), id: messageId}, |
| this._getTargetBySessionId(sessionId)); |
| } |
| |
| ++this._pendingResponsesCount; |
| this._sessions.get(sessionId).callbacks.set(messageId, callback); |
| this._connection.sendRawMessage(JSON.stringify(messageObject)); |
| } |
| |
| /** |
| * @param {string} method |
| * @param {?Object} params |
| * @param {?function(...*)} callback |
| */ |
| _sendRawMessageForTesting(method, params, callback) { |
| const domain = method.split('.')[0]; |
| this.sendMessage('', domain, method, params, callback || (() => {})); |
| } |
| |
| /** |
| * @param {!Object|string} message |
| */ |
| _onMessage(message) { |
| if (test.dumpProtocol) { |
| test.dumpProtocol('backend: ' + ((typeof message === 'string') ? message : JSON.stringify(message))); |
| } |
| |
| if (test.onMessageReceived) { |
| const messageObjectCopy = JSON.parse((typeof message === 'string') ? message : JSON.stringify(message)); |
| test.onMessageReceived( |
| /** @type {!Object} */ (messageObjectCopy), this._getTargetBySessionId(messageObjectCopy.sessionId)); |
| } |
| |
| const messageObject = /** @type {!Object} */ ((typeof message === 'string') ? JSON.parse(message) : message); |
| |
| // Send all messages to proxy connections. |
| let suppressUnknownMessageErrors = false; |
| for (const session of this._sessions.values()) { |
| if (!session.proxyConnection) { |
| continue; |
| } |
| |
| if (!session.proxyConnection._onMessage) { |
| Protocol.InspectorBackend.reportProtocolError( |
| 'Protocol Error: the session has a proxyConnection with no _onMessage', messageObject); |
| continue; |
| } |
| |
| session.proxyConnection._onMessage(messageObject); |
| suppressUnknownMessageErrors = true; |
| } |
| |
| const sessionId = messageObject.sessionId || ''; |
| const session = this._sessions.get(sessionId); |
| if (!session) { |
| if (!suppressUnknownMessageErrors) { |
| Protocol.InspectorBackend.reportProtocolError( |
| 'Protocol Error: the message with wrong session id', messageObject); |
| } |
| return; |
| } |
| |
| // If this message is directly for the target controlled by the proxy connection, don't handle it. |
| if (session.proxyConnection) { |
| return; |
| } |
| |
| if (session.target._needsNodeJSPatching) { |
| Protocol.NodeURL.patch(messageObject); |
| } |
| |
| if ('id' in messageObject) { // just a response for some request |
| const callback = session.callbacks.get(messageObject.id); |
| session.callbacks.delete(messageObject.id); |
| if (!callback) { |
| if (!suppressUnknownMessageErrors) { |
| Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message with wrong id', messageObject); |
| } |
| return; |
| } |
| |
| callback(messageObject.error, messageObject.result); |
| --this._pendingResponsesCount; |
| |
| if (this._pendingScripts.length && !this._pendingResponsesCount) { |
| this._deprecatedRunAfterPendingDispatches(); |
| } |
| } else { |
| if (!('method' in messageObject)) { |
| Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message without method', messageObject); |
| return; |
| } |
| |
| const method = messageObject.method.split('.'); |
| const domainName = method[0]; |
| if (!(domainName in session.target._dispatchers)) { |
| Protocol.InspectorBackend.reportProtocolError( |
| `Protocol Error: the message ${messageObject.method} is for non-existing domain '${domainName}'`, |
| messageObject); |
| return; |
| } |
| session.target._dispatchers[domainName].dispatch(method[1], messageObject); |
| } |
| } |
| |
| /** |
| * @param {function()=} script |
| */ |
| _deprecatedRunAfterPendingDispatches(script) { |
| if (script) { |
| this._pendingScripts.push(script); |
| } |
| |
| // Execute all promises. |
| setTimeout(() => { |
| if (!this._pendingResponsesCount) { |
| this._executeAfterPendingDispatches(); |
| } else { |
| this._deprecatedRunAfterPendingDispatches(); |
| } |
| }, 0); |
| } |
| |
| _executeAfterPendingDispatches() { |
| if (!this._pendingResponsesCount) { |
| const scripts = this._pendingScripts; |
| this._pendingScripts = []; |
| for (let id = 0; id < scripts.length; ++id) { |
| scripts[id](); |
| } |
| } |
| } |
| |
| /** |
| * @param {!Protocol._Callback} callback |
| */ |
| static dispatchConnectionError(callback) { |
| const error = { |
| message: 'Connection is closed, can\'t dispatch pending call', |
| code: _ConnectionClosedErrorCode, |
| data: null |
| }; |
| setTimeout(() => callback(error, null), 0); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class TargetBase { |
| /** |
| * @param {boolean} needsNodeJSPatching |
| * @param {?TargetBase} parentTarget |
| * @param {string} sessionId |
| * @param {?Connection} connection |
| */ |
| constructor(needsNodeJSPatching, parentTarget, sessionId, connection) { |
| this._needsNodeJSPatching = needsNodeJSPatching; |
| this._sessionId = sessionId; |
| |
| if ((!parentTarget && connection) || (!parentTarget && sessionId) || (connection && sessionId)) { |
| throw new Error('Either connection or sessionId (but not both) must be supplied for a child target'); |
| } |
| if (sessionId) { |
| this._router = parentTarget._router; |
| } else if (connection) { |
| this._router = new SessionRouter(connection); |
| } else { |
| this._router = new SessionRouter(_factory()); |
| } |
| |
| this._router.registerSession(this, this._sessionId); |
| |
| this._agents = {}; |
| for (const [domain, agentPrototype] of Protocol.inspectorBackend._agentPrototypes) { |
| this._agents[domain] = Object.create(/** @type {!_AgentPrototype} */ (agentPrototype)); |
| this._agents[domain]._target = this; |
| } |
| |
| this._dispatchers = {}; |
| for (const [domain, dispatcherPrototype] of Protocol.inspectorBackend._dispatcherPrototypes) { |
| this._dispatchers[domain] = Object.create(/** @type {!_DispatcherPrototype} */ (dispatcherPrototype)); |
| this._dispatchers[domain]._dispatchers = []; |
| } |
| } |
| |
| /** |
| * @param {string} domain |
| * @param {!Object} dispatcher |
| */ |
| registerDispatcher(domain, dispatcher) { |
| if (!this._dispatchers[domain]) { |
| return; |
| } |
| this._dispatchers[domain].addDomainDispatcher(dispatcher); |
| } |
| |
| /** |
| * @param {string} reason |
| */ |
| dispose(reason) { |
| this._router.unregisterSession(this._sessionId); |
| this._router = null; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isDisposed() { |
| return !this._router; |
| } |
| |
| markAsNodeJSForTest() { |
| this._needsNodeJSPatching = true; |
| } |
| |
| /** |
| * @return {!SessionRouter} |
| */ |
| router() { |
| return this._router; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| class _AgentPrototype { |
| /** |
| * @param {string} domain |
| */ |
| constructor(domain) { |
| this._replyArgs = {}; |
| this._hasErrorData = {}; |
| this._domain = domain; |
| } |
| |
| /** |
| * @param {string} methodName |
| * @param {!Array.<!Object>} signature |
| * @param {!Array.<string>} replyArgs |
| * @param {boolean} hasErrorData |
| */ |
| registerCommand(methodName, signature, replyArgs, hasErrorData) { |
| const domainAndMethod = this._domain + '.' + methodName; |
| |
| /** |
| * @param {...*} vararg |
| * @this {_AgentPrototype} |
| * @return {!Promise.<*>} |
| */ |
| function sendMessagePromise(vararg) { |
| const params = Array.prototype.slice.call(arguments); |
| return _AgentPrototype.prototype._sendMessageToBackendPromise.call(this, domainAndMethod, signature, params); |
| } |
| |
| this[methodName] = sendMessagePromise; |
| |
| /** |
| * @param {!Object} request |
| * @return {!Promise} |
| * @this {_AgentPrototype} |
| */ |
| function invoke(request) { |
| return this._invoke(domainAndMethod, request); |
| } |
| |
| this['invoke_' + methodName] = invoke; |
| |
| this._replyArgs[domainAndMethod] = replyArgs; |
| if (hasErrorData) { |
| this._hasErrorData[domainAndMethod] = true; |
| } |
| } |
| |
| /** |
| * @param {string} method |
| * @param {!Array.<!Object>} signature |
| * @param {!Array.<*>} args |
| * @param {function(string)} errorCallback |
| * @return {?Object} |
| */ |
| _prepareParameters(method, signature, args, errorCallback) { |
| const params = {}; |
| let hasParams = false; |
| |
| for (const param of signature) { |
| const paramName = param['name']; |
| const typeName = param['type']; |
| const optionalFlag = param['optional']; |
| |
| if (!args.length && !optionalFlag) { |
| errorCallback( |
| `Protocol Error: Invalid number of arguments for method '${method}' call. ` + |
| `It must have the following arguments ${JSON.stringify(signature)}'.`); |
| return null; |
| } |
| |
| const value = args.shift(); |
| if (optionalFlag && typeof value === 'undefined') { |
| continue; |
| } |
| |
| if (typeof value !== typeName) { |
| errorCallback( |
| `Protocol Error: Invalid type of argument '${paramName}' for method '${method}' call. ` + |
| `It must be '${typeName}' but it is '${typeof value}'.`); |
| return null; |
| } |
| |
| params[paramName] = value; |
| hasParams = true; |
| } |
| |
| if (args.length) { |
| errorCallback(`Protocol Error: Extra ${args.length} arguments in a call to method '${method}'.`); |
| return null; |
| } |
| |
| return hasParams ? params : null; |
| } |
| |
| /** |
| * @param {string} method |
| * @param {!Array<!Object>} signature |
| * @param {!Array<*>} args |
| * @return {!Promise<?>} |
| */ |
| _sendMessageToBackendPromise(method, signature, args) { |
| let errorMessage; |
| /** |
| * @param {string} message |
| */ |
| function onError(message) { |
| console.error(message); |
| errorMessage = message; |
| } |
| const params = this._prepareParameters(method, signature, args, onError); |
| if (errorMessage) { |
| return Promise.resolve(null); |
| } |
| |
| return new Promise((resolve, reject) => { |
| const callback = (error, result) => { |
| if (error) { |
| if (!test.suppressRequestErrors && error.code !== Protocol.DevToolsStubErrorCode && |
| error.code !== _GenericError && error.code !== _ConnectionClosedErrorCode) { |
| console.error('Request ' + method + ' failed. ' + JSON.stringify(error)); |
| reject(error); |
| } else { |
| resolve(null); |
| } |
| |
| return; |
| } |
| |
| const args = this._replyArgs[method]; |
| resolve(result && args.length ? result[args[0]] : undefined); |
| }; |
| |
| if (!this._target._router) { |
| SessionRouter.dispatchConnectionError(callback); |
| } else { |
| this._target._router.sendMessage(this._target._sessionId, this._domain, method, params, callback); |
| } |
| }); |
| } |
| |
| /** |
| * @param {string} method |
| * @param {?Object} request |
| * @return {!Promise<!Object>} |
| */ |
| _invoke(method, request) { |
| return new Promise(fulfill => { |
| const callback = (error, result) => { |
| if (error && !test.suppressRequestErrors && error.code !== Protocol.DevToolsStubErrorCode && |
| error.code !== _GenericError && error.code !== _ConnectionClosedErrorCode) { |
| console.error('Request ' + method + ' failed. ' + JSON.stringify(error)); |
| } |
| |
| |
| if (!result) { |
| result = {}; |
| } |
| if (error) { |
| result[Protocol.Error] = error.message; |
| } |
| fulfill(result); |
| }; |
| |
| if (!this._target._router) { |
| SessionRouter.dispatchConnectionError(callback); |
| } else { |
| this._target._router.sendMessage(this._target._sessionId, this._domain, method, request, callback); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| class _DispatcherPrototype { |
| constructor() { |
| this._eventArgs = {}; |
| } |
| |
| /** |
| * @param {string} eventName |
| * @param {!Object} params |
| */ |
| registerEvent(eventName, params) { |
| this._eventArgs[eventName] = params; |
| } |
| |
| /** |
| * @param {!Object} dispatcher |
| */ |
| addDomainDispatcher(dispatcher) { |
| this._dispatchers.push(dispatcher); |
| } |
| |
| /** |
| * @param {string} functionName |
| * @param {!Object} messageObject |
| */ |
| dispatch(functionName, messageObject) { |
| if (!this._dispatchers.length) { |
| return; |
| } |
| |
| if (!this._eventArgs[messageObject.method]) { |
| Protocol.InspectorBackend.reportProtocolError( |
| `Protocol Error: Attempted to dispatch an unspecified method '${messageObject.method}'`, messageObject); |
| return; |
| } |
| |
| const params = []; |
| if (messageObject.params) { |
| const paramNames = this._eventArgs[messageObject.method]; |
| for (let i = 0; i < paramNames.length; ++i) { |
| params.push(messageObject.params[paramNames[i]]); |
| } |
| } |
| |
| for (let index = 0; index < this._dispatchers.length; ++index) { |
| const dispatcher = this._dispatchers[index]; |
| if (functionName in dispatcher) { |
| dispatcher[functionName].apply(dispatcher, params); |
| } |
| } |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.Protocol = self.Protocol || {}; |
| |
| /* Legacy exported object */ |
| Protocol = Protocol || {}; |
| |
| Protocol.DevToolsStubErrorCode = DevToolsStubErrorCode; |
| |
| Protocol.SessionRouter = SessionRouter; |
| |
| /** @constructor */ |
| Protocol.InspectorBackend = InspectorBackend; |
| |
| /** @interface */ |
| Protocol.Connection = Connection; |
| |
| /** @type {!InspectorBackend} */ |
| Protocol.inspectorBackend = new InspectorBackend(); |
| |
| Protocol.test = test; |
| |
| /** @constructor */ |
| Protocol.TargetBase = TargetBase; |
| |
| /** |
| * Takes error and result. |
| * @typedef {function(?Object, ?Object)} |
| */ |
| Protocol._Callback; |
| |
| /** @typedef {string} */ |
| Protocol.Error = ProtocolError; |