| /* | 
 |  * 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; |