<commit_before>/*
 * 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} */
Protocol.Error = Symbol('Protocol.Error');

/**
 * @unrestricted
 */
Protocol.InspectorBackend = class {
  constructor() {
    /** @type {!Map<string, !Protocol.InspectorBackend._AgentPrototype>} */
    this._agentPrototypes = new Map();
    /** @type {!Map<string, !Protocol.InspectorBackend._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 {Protocol.TargetBase}
     */
    function agentGetter() {
      return this._agents[domain];
    }

    Protocol.TargetBase.prototype[methodName] = agentGetter;

    /**
     * @this {Protocol.TargetBase}
     */
    function registerDispatcher(dispatcher) {
      this.registerDispatcher(domain, dispatcher);
    }

    Protocol.TargetBase.prototype['register' + domain + 'Dispatcher'] = registerDispatcher;
  }

  /**
   * @param {string} domain
   * @return {!Protocol.InspectorBackend._AgentPrototype}
   */
  _agentPrototype(domain) {
    if (!this._agentPrototypes.has(domain)) {
      this._agentPrototypes.set(domain, new Protocol.InspectorBackend._AgentPrototype(domain));
      this._addAgentGetterMethodToProtocolTargetPrototype(domain);
    }

    return this._agentPrototypes.get(domain);
  }

  /**
   * @param {string} domain
   * @return {!Protocol.InspectorBackend._DispatcherPrototype}
   */
  _dispatcherPrototype(domain) {
    if (!this._dispatcherPrototypes.has(domain))
      this._dispatcherPrototypes.set(domain, new Protocol.InspectorBackend._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;
  }
};

Protocol.DevToolsStubErrorCode = -32015;

Protocol.inspectorBackend = new Protocol.InspectorBackend();

/**
 * @interface
 */
Protocol.Connection = function() {};

Protocol.Connection.prototype = {
  /**
   * @param {function((!Object|string))} onMessage
   */
  setOnMessage(onMessage) {},

  /**
   * @param {function(string)} onDisconnect
   */
  setOnDisconnect(onDisconnect) {},

  /**
   * @param {string} message
   */
  sendRawMessage(message) {},

  /**
   * @return {!Promise}
   */
  disconnect() {},
};

Protocol.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})}
   */
  onMessageSent: null,

  /**
   * Set to get notified about any messages received over protocol.
   * @type {?function(!Object)}
   */
  onMessageReceived: null,
};

/**
 * @param {function():!Protocol.Connection} factory
 */
Protocol.Connection.setFactory = function(factory) {
  Protocol.Connection._factory = factory;
};


/** @type {function():!Protocol.Connection} */
Protocol.Connection._factory;

/**
 * Takes error and result.
 * @typedef {function(?Object, ?Object)}
 */
Protocol._Callback;

// 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.
Protocol._GenericError = -32000;
Protocol._ConnectionClosedErrorCode = -32001;

Protocol.SessionRouter = class {
  constructor() {
    this._connection = Protocol.Connection._factory();
    this._lastMessageId = 1;
    this._pendingResponsesCount = 0;
    this._domainToLogger = new Map();

    /** @type {!Map<string, {target: !Protocol.TargetBase, callbacks: !Map<number, !Protocol._Callback>}>} */
    this._sessions = new Map();

    /** @type {!Array<function()>} */
    this._pendingScripts = [];

    Protocol.test.deprecatedRunAfterPendingDispatches = this._deprecatedRunAfterPendingDispatches.bind(this);
    Protocol.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 {!Protocol.TargetBase} target
   * @param {string} sessionId
   */
  registerSession(target, sessionId) {
    this._sessions.set(sessionId, {target, callbacks: new Map()});
  }

  /**
   * @param {string} sessionId
   */
  unregisterSession(sessionId) {
    const session = this._sessions.get(sessionId);
    for (const callback of session.callbacks.values())
      Protocol.SessionRouter.dispatchConnectionError(callback);
    this._sessions.delete(sessionId);
  }

  /**
   * @return {number}
   */
  _nextMessageId() {
    return this._lastMessageId++;
  }

  /**
   * @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 (Protocol.test.dumpProtocol)
      Protocol.test.dumpProtocol('frontend: ' + JSON.stringify(messageObject));

    if (Protocol.test.onMessageSent) {
      const paramsObject = JSON.parse(JSON.stringify(params || {}));
      Protocol.test.onMessageSent({domain, method, params: /** @type {!Object} */ (paramsObject), id: messageId});
    }

    ++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 (Protocol.test.dumpProtocol)
      Protocol.test.dumpProtocol('backend: ' + ((typeof message === 'string') ? message : JSON.stringify(message)));

    if (Protocol.test.onMessageReceived) {
      const messageObjectCopy = JSON.parse((typeof message === 'string') ? message : JSON.stringify(message));
      Protocol.test.onMessageReceived(/** @type {!Object} */ (messageObjectCopy));
    }

    const messageObject = /** @type {!Object} */ ((typeof message === 'string') ? JSON.parse(message) : message);

    const sessionId = messageObject.sessionId || '';
    const session = this._sessions.get(sessionId);
    if (!session) {
      Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message with wrong session id', messageObject);
      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) {
        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]();<commit_msg>DevTools: remove support for handleless operations from DOMAgent.<commit_after>/*
 * 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} */
Protocol.Error = Symbol('Protocol.Error');

/**
 * @unrestricted
 */
Protocol.InspectorBackend = class {
  constructor() {
    /** @type {!Map<string, !Protocol.InspectorBackend._AgentPrototype>} */
    this._agentPrototypes = new Map();
    /** @type {!Map<string, !Protocol.InspectorBackend._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 {Protocol.TargetBase}
     */
    function agentGetter() {
      return this._agents[domain];
    }

    Protocol.TargetBase.prototype[methodName] = agentGetter;

    /**
     * @this {Protocol.TargetBase}
     */
    function registerDispatcher(dispatcher) {
      this.registerDispatcher(domain, dispatcher);
    }

    Protocol.TargetBase.prototype['register' + domain + 'Dispatcher'] = registerDispatcher;
  }

  /**
   * @param {string} domain
   * @return {!Protocol.InspectorBackend._AgentPrototype}
   */
  _agentPrototype(domain) {
    if (!this._agentPrototypes.has(domain)) {
      this._agentPrototypes.set(domain, new Protocol.InspectorBackend._AgentPrototype(domain));
      this._addAgentGetterMethodToProtocolTargetPrototype(domain);
    }

    return this._agentPrototypes.get(domain);
  }

  /**
   * @param {string} domain
   * @return {!Protocol.InspectorBackend._DispatcherPrototype}
   */
  _dispatcherPrototype(domain) {
    if (!this._dispatcherPrototypes.has(domain))
      this._dispatcherPrototypes.set(domain, new Protocol.InspectorBackend._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;
  }
};

Protocol.DevToolsStubErrorCode = -32015;

Protocol.inspectorBackend = new Protocol.InspectorBackend();

/**
 * @interface
 */
Protocol.Connection = function() {};

Protocol.Connection.prototype = {
  /**
   * @param {function((!Object|string))} onMessage
   */
  setOnMessage(onMessage) {},

  /**
   * @param {function(string)} onDisconnect
   */
  setOnDisconnect(onDisconnect) {},

  /**
   * @param {string} message
   */
  sendRawMessage(message) {},

  /**
   * @return {!Promise}
   */
  disconnect() {},
};

Protocol.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})}
   */
  onMessageSent: null,

  /**
   * Set to get notified about any messages received over protocol.
   * @type {?function(!Object)}
   */
  onMessageReceived: null,
};

/**
 * @param {function():!Protocol.Connection} factory
 */
Protocol.Connection.setFactory = function(factory) {
  Protocol.Connection._factory = factory;
};


/** @type {function():!Protocol.Connection} */
Protocol.Connection._factory;

/**
 * Takes error and result.
 * @typedef {function(?Object, ?Object)}
 */
Protocol._Callback;

// 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.
Protocol._GenericError = -32000;
Protocol._ConnectionClosedErrorCode = -32001;

Protocol.SessionRouter = class {
  constructor() {
    this._connection = Protocol.Connection._factory();
    this._lastMessageId = 1;
    this._pendingResponsesCount = 0;
    this._domainToLogger = new Map();

    /** @type {!Map<string, {target: !Protocol.TargetBase, callbacks: !Map<number, !Protocol._Callback>}>} */
    this._sessions = new Map();

    /** @type {!Array<function()>} */
    this._pendingScripts = [];

    Protocol.test.deprecatedRunAfterPendingDispatches = this._deprecatedRunAfterPendingDispatches.bind(this);
    Protocol.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 {!Protocol.TargetBase} target
   * @param {string} sessionId
   */
  registerSession(target, sessionId) {
    this._sessions.set(sessionId, {target, callbacks: new Map()});
  }

  /**
   * @param {string} sessionId
   */
  unregisterSession(sessionId) {
    const session = this._sessions.get(sessionId);
    for (const callback of session.callbacks.values())
      Protocol.SessionRouter.dispatchConnectionError(callback);
    this._sessions.delete(sessionId);
  }

  /**
   * @return {number}
   */
  _nextMessageId() {
    return this._lastMessageId++;
  }

  /**
   * @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 (Protocol.test.dumpProtocol)
      Protocol.test.dumpProtocol('frontend: ' + JSON.stringify(messageObject));

    if (Protocol.test.onMessageSent) {
      const paramsObject = JSON.parse(JSON.stringify(params || {}));
      Protocol.test.onMessageSent({domain, method, params: /** @type {!Object} */ (paramsObject), id: messageId});
    }

    ++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 (Protocol.test.dumpProtocol)
      Protocol.test.dumpProtocol('backend: ' + ((typeof message === 'string') ? message : JSON.stringify(message)));

    if (Protocol.test.onMessageReceived) {
      const messageObjectCopy = JSON.parse((typeof message === 'string') ? message : JSON.stringify(message));
      Protocol.test.onMessageReceived(/** @type {!Object} */ (messageObjectCopy));
    }

    const messageObject = /** @type {!Object} */ ((typeof message === 'string') ? JSON.parse(message) : message);

    const sessionId = messageObject.sessionId || '';
    const session = this._sessions.get(sessionId);
    if (!session) {
      Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message with wrong session id', messageObject);
      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) {
        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]();
{'fraction_non_alphanumeric': 0.1297585092222856, 'fraction_numerical': 0.0025099828864803193, 'mean_word_length': 3.529107819497072, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2256795', 'n_tokens_mistral': 7648, 'n_tokens_neox': 7479, 'n_words': 1786}