text: /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  
  "use strict";
  // Note: this file should be loadabale with eval() into worker environment.
  // Avoid Components.*, ChromeUtils and global const variables.
  
  if (!this.Debugger) {
    // Worker has a Debugger defined already.
    const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {});
    addDebuggerToGlobal(Components.utils.getGlobalForObject(this));
  }
  
  let lastId = 0;
  function generateId() {
    return 'id-' + (++lastId);
  }
  
  const consoleLevelToProtocolType = {
    'dir': 'dir',
    'log': 'log',
    'debug': 'debug',
    'info': 'info',
    'error': 'error',
    'warn': 'warning',
    'dirxml': 'dirxml',
    'table': 'table',
    'trace': 'trace',
    'clear': 'clear',
    'group': 'startGroup',
    'groupCollapsed': 'startGroupCollapsed',
    'groupEnd': 'endGroup',
    'assert': 'assert',
    'profile': 'profile',
    'profileEnd': 'profileEnd',
    'count': 'count',
    'countReset': 'countReset',
    'time': null,
    'timeLog': 'timeLog',
    'timeEnd': 'timeEnd',
    'timeStamp': 'timeStamp',
  };
  
  const disallowedMessageCategories = new Set([
    'XPConnect JavaScript',
    'component javascript',
    'chrome javascript',
    'chrome registration',
    'XBL',
    'XBL Prototype Handler',
    'XBL Content Sink',
    'xbl javascript',
  ]);
  
  class Runtime {
    constructor(isWorker = false) {
      this._debugger = new Debugger();
      this._pendingPromises = new Map();
      this._executionContexts = new Map();
      this._windowToExecutionContext = new Map();
      this._eventListeners = [];
      if (isWorker) {
        this._registerWorkerConsoleHandler();
      } else {
        const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
        this._registerConsoleServiceListener(Services);
        this._registerConsoleObserver(Services);
      }
      // We can't use event listener here to be compatible with Worker Global Context.
      // Use plain callbacks instead.
      this.events = {
        onConsoleMessage: createEvent(),
        onErrorFromWorker: createEvent(),
        onExecutionContextCreated: createEvent(),
        onExecutionContextDestroyed: createEvent(),
      };
    }
  
    executionContexts() {
      return [...this._executionContexts.values()];
    }
  
    async evaluate({executionContextId, expression, returnByValue}) {
      const executionContext = this.findExecutionContext(executionContextId);
      if (!executionContext)
        throw new Error('Failed to find execution context with id = ' + executionContextId);
      const exceptionDetails = {};
      let result = await executionContext.evaluateScript(expression, exceptionDetails);
      if (!result)
        return {exceptionDetails};
      if (returnByValue)
        result = executionContext.ensureSerializedToValue(result);
      return {result};
    }
  
    async callFunction({executionContextId, functionDeclaration, args, returnByValue}) {
      const executionContext = this.findExecutionContext(executionContextId);
      if (!executionContext)
        throw new Error('Failed to find execution context with id = ' + executionContextId);
      const exceptionDetails = {};
      let result = await executionContext.evaluateFunction(functionDeclaration, args, exceptionDetails);
      if (!result)
        return {exceptionDetails};
      if (returnByValue)
        result = executionContext.ensureSerializedToValue(result);
      return {result};
    }
  
    async getObjectProperties({executionContextId, objectId}) {
      const executionContext = this.findExecutionContext(executionContextId);
      if (!executionContext)
        throw new Error('Failed to find execution context with id = ' + executionContextId);
      return {properties: executionContext.getObjectProperties(objectId)};
    }
  
    async disposeObject({executionContextId, objectId}) {
      const executionContext = this.findExecutionContext(executionContextId);
      if (!executionContext)
        throw new Error('Failed to find execution context with id = ' + executionContextId);
      return executionContext.disposeObject(objectId);
    }
  
    _registerConsoleServiceListener(Services) {
      const Ci = Components.interfaces;
      const consoleServiceListener = {
        QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
  
        observe: message => {
          if (!(message instanceof Ci.nsIScriptError) || !message.outerWindowID ||
              !message.category || disallowedMessageCategories.has(message.category)) {
            return;
          }
          const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
          if (message.category === 'Web Worker' && (message.flags & Ci.nsIScriptError.exceptionFlag)) {
            emitEvent(this.events.onErrorFromWorker, errorWindow, message.message, '' + message.stack);
            return;
          }
          const executionContext = this._windowToExecutionContext.get(errorWindow);
          if (!executionContext)
            return;
          const typeNames = {
            [Ci.nsIConsoleMessage.debug]: 'debug',
            [Ci.nsIConsoleMessage.info]: 'info',
            [Ci.nsIConsoleMessage.warn]: 'warn',
            [Ci.nsIConsoleMessage.error]: 'error',
          };
          emitEvent(this.events.onConsoleMessage, {
            args: [{
              value: message.message,
            }],
            type: typeNames[message.logLevel],
            executionContextId: executionContext.id(),
            location: {
              lineNumber: message.lineNumber,
              columnNumber: message.columnNumber,
              url: message.sourceName,
            },
          });
        },
      };
      Services.console.registerListener(consoleServiceListener);
      this._eventListeners.push(() => Services.console.unregisterListener(consoleServiceListener));
    }
  
    _registerConsoleObserver(Services) {
      const consoleObserver = ({wrappedJSObject}, topic, data) => {
        const executionContext = Array.from(this._executionContexts.values()).find(context => {
          const domWindow = context._domWindow;
          return domWindow && domWindow.windowUtils.currentInnerWindowID === wrappedJSObject.innerID;
        });
        if (!executionContext)
          return;
        this._onConsoleMessage(executionContext, wrappedJSObject);
      };
      Services.obs.addObserver(consoleObserver, "console-api-log-event");
      this._eventListeners.push(() => Services.obs.removeObserver(consoleObserver, "console-api-log-event"));
    }
  
    _registerWorkerConsoleHandler() {
      setConsoleEventHandler(message => {
        const executionContext = Array.from(this._executionContexts.values())[0];
        this._onConsoleMessage(executionContext, message);
      });
      this._eventListeners.push(() => setConsoleEventHandler(null));
    }
  
    _onConsoleMessage(executionContext, message) {
      const type = consoleLevelToProtocolType[message.level];
      if (!type)
        return;
      const args = message.arguments.map(arg => executionContext.rawValueToRemoteObject(arg));
      emitEvent(this.events.onConsoleMessage, {
        args,
        type,
        executionContextId: executionContext.id(),
        location: {
          lineNumber: message.lineNumber - 1,
          columnNumber: message.columnNumber - 1,
          url: message.filename,
        },
      });
    }
  
    dispose() {
      for (const tearDown of this._eventListeners)
        tearDown.call(null);
      this._eventListeners = [];
    }
  
    async _awaitPromise(executionContext, obj, exceptionDetails = {}) {
      if (obj.promiseState === 'fulfilled')
        return {success: true, obj: obj.promiseValue};
      if (obj.promiseState === 'rejected') {
        const global = executionContext._global;
        exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
        exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
        return {success: false, obj: null};
      }
      let resolve, reject;
      const promise = new Promise((a, b) => {
        resolve = a;
        reject = b;
      });
      this._pendingPromises.set(obj.promiseID, {resolve, reject, executionContext, exceptionDetails});
      if (this._pendingPromises.size === 1)
        this._debugger.onPromiseSettled = this._onPromiseSettled.bind(this);
      return await promise;
    }
  
    _onPromiseSettled(obj) {
      const pendingPromise = this._pendingPromises.get(obj.promiseID);
      if (!pendingPromise)
        return;
      this._pendingPromises.delete(obj.promiseID);
      if (!this._pendingPromises.size)
        this._debugger.onPromiseSettled = undefined;
  
      if (obj.promiseState === 'fulfilled') {
        pendingPromise.resolve({success: true, obj: obj.promiseValue});
        return;
      };
      const global = pendingPromise.executionContext._global;
      pendingPromise.exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return;
      pendingPromise.exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return;
      pendingPromise.resolve({success: false, obj: null});
    }
  
    createExecutionContext(domWindow, contextGlobal, auxData) {
      // Note: domWindow is null for workers.
      const context = new ExecutionContext(this, domWindow, contextGlobal, this._debugger.addDebuggee(contextGlobal), auxData);
      this._executionContexts.set(context._id, context);
      if (domWindow)
        this._windowToExecutionContext.set(domWindow, context);
      emitEvent(this.events.onExecutionContextCreated, context);
      return context;
    }
  
    findExecutionContext(executionContextId) {
      const executionContext = this._executionContexts.get(executionContextId);
      if (!executionContext)
        throw new Error('Failed to find execution context with id = ' + executionContextId);
      return executionContext;
    }
  
    destroyExecutionContext(destroyedContext) {
      for (const [promiseID, {reject, executionContext}] of this._pendingPromises) {
        if (executionContext === destroyedContext) {
          reject(new Error('Execution context was destroyed!'));
          this._pendingPromises.delete(promiseID);
        }
      }
      if (!this._pendingPromises.size)
        this._debugger.onPromiseSettled = undefined;
      this._debugger.removeDebuggee(destroyedContext._contextGlobal);
      this._executionContexts.delete(destroyedContext._id);
      if (destroyedContext._domWindow)
        this._windowToExecutionContext.delete(destroyedContext._domWindow);
      emitEvent(this.events.onExecutionContextDestroyed, destroyedContext);
    }
  }
  
  class ExecutionContext {
    constructor(runtime, domWindow, contextGlobal, global, auxData) {
      this._runtime = runtime;
      this._domWindow = domWindow;
      this._contextGlobal = contextGlobal;
      this._global = global;
      this._remoteObjects = new Map();
      this._id = generateId();
      this._auxData = auxData;
      this._jsonStringifyObject = this._global.executeInGlobal(`((stringify, dateProto, object) => {
        const oldToJson = dateProto.toJSON;
        dateProto.toJSON = undefined;
        let hasSymbol = false;
        const result = stringify(object, (key, value) => {
          if (typeof value === 'symbol')
            hasSymbol = true;
          return value;
        });
        dateProto.toJSON = oldToJson;
        return hasSymbol ? undefined : result;
      }).bind(null, JSON.stringify.bind(JSON), Date.prototype)`).return;
    }
  
    id() {
      return this._id;
    }
  
    auxData() {
      return this._auxData;
    }
  
    async evaluateScript(script, exceptionDetails = {}) {
      const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
      if (this._domWindow && this._domWindow.document)
        this._domWindow.document.notifyUserGestureActivation();
  
      let {success, obj} = this._getResult(this._global.executeInGlobal(script), exceptionDetails);
      userInputHelper && userInputHelper.destruct();
      if (!success)
        return null;
      if (obj && obj.isPromise) {
        const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails);
        if (!awaitResult.success)
          return null;
        obj = awaitResult.obj;
      }
      return this._createRemoteObject(obj);
    }
  
    async evaluateFunction(functionText, args, exceptionDetails = {}) {
      const funEvaluation = this._getResult(this._global.executeInGlobal('(' + functionText + ')'), exceptionDetails);
      if (!funEvaluation.success)
        return null;
      if (!funEvaluation.obj.callable)
        throw new Error('functionText does not evaluate to a function!');
      args = args.map(arg => {
        if (arg.objectId) {
          if (!this._remoteObjects.has(arg.objectId))
            throw new Error('Cannot find object with id = ' + arg.objectId);
          return this._remoteObjects.get(arg.objectId);
        }
        switch (arg.unserializableValue) {
          case 'Infinity': return Infinity;
          case '-Infinity': return -Infinity;
          case '-0': return -0;
          case 'NaN': return NaN;
          default: return this._toDebugger(arg.value);
        }
      });
      const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
      if (this._domWindow && this._domWindow.document)
        this._domWindow.document.notifyUserGestureActivation();
      let {success, obj} = this._getResult(funEvaluation.obj.apply(null, args), exceptionDetails);
      userInputHelper && userInputHelper.destruct();
      if (!success)
        return null;
      if (obj && obj.isPromise) {
        const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails);
        if (!awaitResult.success)
          return null;
        obj = awaitResult.obj;
      }
      return this._createRemoteObject(obj);
    }
  
    unsafeObject(objectId) {
      if (!this._remoteObjects.has(objectId))
        return;
      return { object: this._remoteObjects.get(objectId).unsafeDereference() };
    }
  
    rawValueToRemoteObject(rawValue) {
      const debuggerObj = this._global.makeDebuggeeValue(rawValue);
      return this._createRemoteObject(debuggerObj);
    }
  
    _instanceOf(debuggerObj, rawObj, className) {
      if (this._domWindow)
        return rawObj instanceof this._domWindow[className];
      return this._global.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._global.makeDebuggeeValue(className)}).return;
    }
  
    _createRemoteObject(debuggerObj) {
      if (debuggerObj instanceof Debugger.Object) {
        const objectId = generateId();
        this._remoteObjects.set(objectId, debuggerObj);
        const rawObj = debuggerObj.unsafeDereference();
        const type = typeof rawObj;
        let subtype = undefined;
        if (debuggerObj.isProxy)
          subtype = 'proxy';
        else if (Array.isArray(rawObj))
          subtype = 'array';
        else if (Object.is(rawObj, null))
          subtype = 'null';
        else if (this._instanceOf(debuggerObj, rawObj, 'Node'))
          subtype = 'node';
        else if (this._instanceOf(debuggerObj, rawObj, 'RegExp'))
          subtype = 'regexp';
        else if (this._instanceOf(debuggerObj, rawObj, 'Date'))
          subtype = 'date';
        else if (this._instanceOf(debuggerObj, rawObj, 'Map'))
          subtype = 'map';
        else if (this._instanceOf(debuggerObj, rawObj, 'Set'))
          subtype = 'set';
        else if (this._instanceOf(debuggerObj, rawObj, 'WeakMap'))
          subtype = 'weakmap';
        else if (this._instanceOf(debuggerObj, rawObj, 'WeakSet'))
          subtype = 'weakset';
        else if (this._instanceOf(debuggerObj, rawObj, 'Error'))
          subtype = 'error';
        else if (this._instanceOf(debuggerObj, rawObj, 'Promise'))
          subtype = 'promise';
        else if ((this._instanceOf(debuggerObj, rawObj, 'Int8Array')) || (this._instanceOf(debuggerObj, rawObj, 'Uint8Array')) ||
                 (this._instanceOf(debuggerObj, rawObj, 'Uint8ClampedArray')) || (this._instanceOf(debuggerObj, rawObj, 'Int16Array')) ||
                 (this._instanceOf(debuggerObj, rawObj, 'Uint16Array')) || (this._instanceOf(debuggerObj, rawObj, 'Int32Array')) ||
                 (this._instanceOf(debuggerObj, rawObj, 'Uint32Array')) || (this._instanceOf(debuggerObj, rawObj, 'Float32Array')) ||
                 (this._instanceOf(debuggerObj, rawObj, 'Float64Array'))) {
          subtype = 'typedarray';
        }
        return {objectId, type, subtype};
      }
      if (typeof debuggerObj === 'symbol') {
        const objectId = generateId();
        this._remoteObjects.set(objectId, debuggerObj);
        return {objectId, type: 'symbol'};
      }
  
      let unserializableValue = undefined;
      if (Object.is(debuggerObj, NaN))
        unserializableValue = 'NaN';
      else if (Object.is(debuggerObj, -0))
        unserializableValue = '-0';
      else if (Object.is(debuggerObj, Infinity))
        unserializableValue = 'Infinity';
      else if (Object.is(debuggerObj, -Infinity))
        unserializableValue = '-Infinity';
      return unserializableValue ? {unserializableValue} : {value: debuggerObj};
    }
  
    ensureSerializedToValue(protocolObject) {
      if (!protocolObject.objectId)
        return protocolObject;
      const obj = this._remoteObjects.get(protocolObject.objectId);
      this._remoteObjects.delete(protocolObject.objectId);
      return {value: this._serialize(obj)};
    }
  
    _toDebugger(obj) {
      if (typeof obj !== 'object')
        return obj;
      if (obj === null)
        return obj;
      const properties = {};
      for (let [key, value] of Object.entries(obj)) {
        properties[key] = {
          configurable: true,
          writable: true,
          enumerable: true,
          value: this._toDebugger(value),
        };
      }
      const baseObject = Array.isArray(obj) ? '([])' : '({})';
      const debuggerObj = this._global.executeInGlobal(baseObject).return;
      debuggerObj.defineProperties(properties);
      return debuggerObj;
    }
  
    _serialize(obj) {
      const result = this._global.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject});
      if (result.throw)
        throw new Error('Object is not serializable');
      return result.return === undefined ? undefined : JSON.parse(result.return);
    }
  
    disposeObject(objectId) {
      this._remoteObjects.delete(objectId);
    }
  
    getObjectProperties(objectId) {
      if (!this._remoteObjects.has(objectId))
        throw new Error('Cannot find object with id = ' + arg.objectId);
      const result = [];
      for (let obj = this._remoteObjects.get(objectId); obj; obj = obj.proto) {
        for (const propertyName of obj.getOwnPropertyNames()) {
          const descriptor = obj.getOwnPropertyDescriptor(propertyName);
          if (!descriptor.enumerable)
            continue;
          result.push({
            name: propertyName,
            value: this._createRemoteObject(descriptor.value),
          });
        }
      }
      return result;
    }
  
    _getResult(completionValue, exceptionDetails = {}) {
      if (!completionValue) {
        exceptionDetails.text = 'Evaluation terminated!';
        exceptionDetails.stack = '';
        return {success: false, obj: null};
      }
      if (completionValue.throw) {
        if (this._global.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) {
          exceptionDetails.text = this._global.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return;
          exceptionDetails.stack = this._global.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return;
        } else {
          exceptionDetails.value = this._serialize(completionValue.throw);
        }
        return {success: false, obj: null};
      }
      return {success: true, obj: completionValue.return};
    }
  }
  
  const listenersSymbol = Symbol('listeners');
  
  function createEvent() {
    const listeners = new Set();
    const subscribeFunction = listener => {
      listeners.add(listener);
      return () => listeners.delete(listener);
    }
    subscribeFunction[listenersSymbol] = listeners;
    return subscribeFunction;
  }
  
  function emitEvent(event, ...args) {
    let listeners = event[listenersSymbol];
    if (!listeners || !listeners.size)
      return;
    listeners = new Set(listeners);
    for (const listener of listeners)
      listener.call(null, ...args);
  }
  
  var EXPORTED_SYMBOLS = ['Runtime'];
  this.Runtime = Runtime;
  