Skip to content
Snippets Groups Projects
ExtensionParent.jsm 66.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
    /* vim: set sts=2 sw=2 et tw=80: */
    
    /* 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";
    
    /**
     * This module contains code for managing APIs that need to run in the
     * parent process, and handles the parent side of operations that need
     * to be proxied from ExtensionChild.jsm.
     */
    
    /* exported ExtensionParent */
    
    
    const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
    const { XPCOMUtils } = ChromeUtils.import(
      "resource://gre/modules/XPCOMUtils.jsm"
    );
    
    XPCOMUtils.defineLazyModuleGetters(this, {
    
      AddonManager: "resource://gre/modules/AddonManager.jsm",
    
      AppConstants: "resource://gre/modules/AppConstants.jsm",
    
      AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
    
      BroadcastConduit: "resource://gre/modules/ConduitsParent.jsm",
    
      DeferredTask: "resource://gre/modules/DeferredTask.jsm",
    
      DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
    
      ExtensionData: "resource://gre/modules/Extension.jsm",
    
      ExtensionActivityLog: "resource://gre/modules/ExtensionActivityLog.jsm",
    
      GeckoViewConnection: "resource://gre/modules/GeckoViewWebExtension.jsm",
    
      MessageManagerProxy: "resource://gre/modules/MessageManagerProxy.jsm",
    
      NativeApp: "resource://gre/modules/NativeMessaging.jsm",
    
      PerformanceCounters: "resource://gre/modules/PerformanceCounters.jsm",
    
      PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
      Schemas: "resource://gre/modules/Schemas.jsm",
    
      getErrorNameForTelemetry: "resource://gre/modules/ExtensionTelemetry.jsm",
    
    });
    
    XPCOMUtils.defineLazyServiceGetters(this, {
    
      aomStartup: [
        "@mozilla.org/addons/addon-manager-startup;1",
        "amIAddonManagerStartup",
      ],
    
    // We're using the pref to avoid loading PerformanceCounters.jsm for nothing.
    
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "gTimingEnabled",
      "extensions.webextensions.enablePerformanceCounters",
      false
    );
    const { ExtensionCommon } = ChromeUtils.import(
      "resource://gre/modules/ExtensionCommon.jsm"
    );
    const { ExtensionUtils } = ChromeUtils.import(
      "resource://gre/modules/ExtensionUtils.jsm"
    );
    
      promiseDocumentLoaded,
      promiseEvent,
      promiseObserved,
    
    const ERROR_NO_RECEIVERS =
      "Could not establish connection. Receiving end does not exist.";
    
    
    const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
    
    const CATEGORY_EXTENSION_MODULES = "webextension-modules";
    
    const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
    const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
    
    let schemaURLs = new Set();
    
    
    schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
    
    function verifyActorForContext(actor, context) {
    
      if (JSWindowActorParent.isInstance(actor)) {
    
        let target = actor.browsingContext.top.embedderElement;
        if (context.parentMessageManager !== target.messageManager) {
          throw new Error("Got message on unexpected message manager");
        }
    
      } else if (JSProcessActorParent.isInstance(actor)) {
    
        if (actor.manager.remoteType !== context.extension.remoteType) {
          throw new Error("Got message from unexpected process");
        }
      }
    }
    
    
    // This object loads the ext-*.js scripts that define the extension API.
    
    let apiManager = new (class extends SchemaAPIManager {
    
        /* eslint-disable mozilla/balanced-listeners */
        this.on("startup", (e, extension) => {
    
          return extension.apiManager.onStartup(extension);
    
        this.on("update", async (e, { id, resourceURI, isPrivileged }) => {
    
          let modules = this.eventModules.get("update");
          if (modules.size == 0) {
            return;
          }
    
    
          let extension = new ExtensionData(resourceURI, isPrivileged);
    
          return Promise.all(
            Array.from(modules).map(async apiName => {
              let module = await this.asyncLoadModule(apiName);
              module.onUpdate(id, extension.manifest);
            })
          );
    
        this.on("uninstall", (e, { id }) => {
    
          let modules = this.eventModules.get("uninstall");
    
          return Promise.all(
            Array.from(modules).map(async apiName => {
              let module = await this.asyncLoadModule(apiName);
              return module.onUninstall(id);
            })
          );
    
        });
        /* eslint-enable mozilla/balanced-listeners */
    
    
        // Handle any changes that happened during startup
    
        let disabledIds = AddonManager.getStartupChanges(
          AddonManager.STARTUP_CHANGE_DISABLED
        );
    
          this._callHandlers(disabledIds, "disable", "onDisable");
        }
    
    
        let uninstalledIds = AddonManager.getStartupChanges(
          AddonManager.STARTUP_CHANGE_UNINSTALLED
        );
    
          this._callHandlers(uninstalledIds, "uninstall", "onUninstall");
        }
    
        return Array.from(
          Services.catMan.enumerateCategory(CATEGORY_EXTENSION_MODULES),
          ({ value }) => value
        );
    
      // Loads all the ext-*.js scripts currently registered.
      lazyInit() {
        if (this.initialized) {
          return this.initialized;
        }
    
    
        let modulesPromise = StartupCache.other.get(["parentModules"], () =>
          this.loadModuleJSON(this.getModuleJSONURLs())
        );
    
        for (let { value } of Services.catMan.enumerateCategory(
          CATEGORY_EXTENSION_SCRIPTS
        )) {
    
          let scripts = await Promise.all(
            scriptURLs.map(url => ChromeUtils.compileScript(url))
          );
    
          for (let script of scripts) {
            script.executeInGlobal(this.global);
          }
    
          // Load order matters here. The base manifest defines types which are
          // extended by other schemas, so needs to be loaded first.
    
            for (let { value } of Services.catMan.enumerateCategory(
              CATEGORY_EXTENSION_SCHEMAS
            )) {
    
            for (let [url, { content }] of this.schemaURLs) {
    
            }
            for (let url of schemaURLs) {
              promises.push(Schemas.load(url));
            }
    
            return Promise.all(promises).then(() => {
              Schemas.updateSharedSchemas();
            });
    
        Services.mm.addMessageListener("Extension:GetFrameData", this);
    
        this.initialized = promise;
        return this.initialized;
      }
    
    
      receiveMessage({ target }) {
        let data = GlobalManager.frameData.get(target) || {};
        Object.assign(data, this.global.tabTracker.getBrowserData(target));
        return data;
    
    
      // Call static handlers for the given event on the given extension ids,
      // and set up a shutdown blocker to ensure they all complete.
      _callHandlers(ids, event, method) {
        let promises = Array.from(this.eventModules.get(event))
    
          .map(async modName => {
            let module = await this.asyncLoadModule(modName);
            return ids.map(id => module[method](id));
          })
          .flat();
    
        if (event === "disable") {
          promises.push(...ids.map(id => this.emit("disable", id)));
        }
    
        if (event === "enabling") {
          promises.push(...ids.map(id => this.emit("enabling", id)));
        }
    
    
        AsyncShutdown.profileBeforeChange.addBlocker(
          `Extension API ${event} handlers for ${ids.join(",")}`,
    
    // Receives messages related to the extension messaging API and forwards them
    // to relevant child messengers.  Also handles Native messaging and GeckoView.
    const ProxyMessenger = {
    
      /**
       * @typedef {object} ParentPort
       * @prop {function(StructuredCloneHolder)} onPortMessage
       * @prop {function()} onPortDisconnect
       */
      /** @type Map<number, ParentPort> */
      ports: new Map(),
    
    
      _torRuntimeMessageListeners: [],
    
    
        this.conduit = new BroadcastConduit(ProxyMessenger, {
          id: "ProxyMessenger",
    
          recv: ["PortConnect", "PortMessage", "NativeMessage", "RuntimeMessage"],
          cast: ["PortConnect", "PortMessage", "PortDisconnect", "RuntimeMessage"],
    
        });
      },
    
      openNative(nativeApp, sender) {
        let context = ParentAPIManager.getContextById(sender.childId);
    
        if (context.extension.hasPermission("geckoViewAddons")) {
    
            sender.actor.browsingContext.top.embedderElement,
    
            context.extension.hasPermission("nativeMessagingFromContent")
    
        } else if (sender.verified) {
          return new NativeApp(context, nativeApp);
        }
    
        sender = this.getSender(context.extension, sender);
    
        throw new Error(`Native messaging not allowed: ${JSON.stringify(sender)}`);
      },
    
      recvNativeMessage({ nativeApp, holder }, { sender }) {
        return this.openNative(nativeApp, sender).sendMessage(holder);
      },
    
    
        let sender = {
          contextId: source.id,
          id: source.extensionId,
          envType: source.envType,
    
        if (JSWindowActorParent.isInstance(source.actor)) {
    
          let browser = source.actor.browsingContext.top.embedderElement;
          let data =
            browser && apiManager.global.tabTracker.getBrowserData(browser);
          if (data?.tabId > 0) {
            sender.tab = extension.tabManager.get(data.tabId, null)?.convert();
    
            // frameId is documented to only be set if sender.tab is set.
            sender.frameId = source.frameId;
    
      getTopBrowsingContextId(tabId) {
        // If a tab alredy has content scripts, no need to check private browsing.
        let tab = apiManager.global.tabTracker.getTab(tabId, null);
    
        if (!tab || (tab.browser || tab).getAttribute("pending") === "true") {
    
          // No receivers in discarded tabs, so bail early to keep the browser lazy.
          throw new ExtensionError(ERROR_NO_RECEIVERS);
    
        let browser = tab.linkedBrowser || tab.browser;
        return browser.browsingContext.id;
      },
    
      // TODO: Rework/simplify this and getSender/getTopBC after bug 1580766.
      async normalizeArgs(arg, sender) {
        arg.extensionId = arg.extensionId || sender.extensionId;
        let extension = GlobalManager.extensionMap.get(arg.extensionId);
    
        if (!extension) {
          return Promise.reject({ message: ERROR_NO_RECEIVERS });
        }
    
        arg.sender = this.getSender(extension, sender);
    
        arg.topBC = arg.tabId && this.getTopBrowsingContextId(arg.tabId);
        return arg.tabId ? "tab" : "messenger";
      },
    
        // We need to listen to some extension messages in Tor Browser
        for (const listener of this._torRuntimeMessageListeners) {
          listener(arg);
        }
    
        arg.firstResponse = true;
        let kind = await this.normalizeArgs(arg, sender);
        let result = await this.conduit.castRuntimeMessage(kind, arg);
    
        if (!result) {
          // "throw new ExtensionError" cannot be used because then the stack of the
          // sendMessage call would not be added to the error object generated by
          // context.normalizeError. Test coverage by test_ext_error_location.js.
          return Promise.reject({ message: ERROR_NO_RECEIVERS });
        }
        return result.value;
    
      async recvPortConnect(arg, { sender }) {
        if (arg.native) {
          let port = this.openNative(arg.name, sender).onConnect(arg.portId, this);
          this.ports.set(arg.portId, port);
    
        // PortMessages that follow will need to wait for the port to be opened.
        let resolvePort;
        this.ports.set(arg.portId, new Promise(res => (resolvePort = res)));
    
        let kind = await this.normalizeArgs(arg, sender);
        let all = await this.conduit.castPortConnect(kind, arg);
        resolvePort();
    
        // If there are no active onConnect listeners.
        if (!all.some(x => x.value)) {
          throw new ExtensionError(ERROR_NO_RECEIVERS);
        }
    
      },
    
      async recvPortMessage({ holder }, { sender }) {
        if (sender.native) {
          return this.ports.get(sender.portId).onPortMessage(holder);
    
        await this.ports.get(sender.portId);
        this.sendPortMessage(sender.portId, holder, !sender.source);
    
      recvConduitClosed(sender) {
        let app = this.ports.get(sender.portId);
        if (this.ports.delete(sender.portId) && sender.native) {
          return app.onPortDisconnect();
        }
        this.sendPortDisconnect(sender.portId, null, !sender.source);
    
      sendPortMessage(portId, holder, source = true) {
        this.conduit.castPortMessage("port", { portId, source, holder });
    
      sendPortDisconnect(portId, error, source = true) {
        this.conduit.castPortDisconnect("port", { portId, source, error });
    
    
    // Responsible for loading extension APIs into the right globals.
    GlobalManager = {
      // Map[extension ID -> Extension]. Determines which extension is
      // responsible for content under a particular extension ID.
      extensionMap: new Map(),
      initialized: false,
    
    
      /** @type {WeakMap<Browser, object>} Extension Context init data. */
      frameData: new WeakMap(),
    
    
      init(extension) {
        if (this.extensionMap.size == 0) {
          apiManager.on("extension-browser-inserted", this._onExtensionBrowser);
          this.initialized = true;
    
          Services.ppmm.addMessageListener(
            "Extension:SendPerformanceCounter",
            this
          );
    
        }
        this.extensionMap.set(extension.id, extension);
      },
    
      uninit(extension) {
        this.extensionMap.delete(extension.id);
    
        if (this.extensionMap.size == 0 && this.initialized) {
          apiManager.off("extension-browser-inserted", this._onExtensionBrowser);
          this.initialized = false;
    
          Services.ppmm.removeMessageListener(
            "Extension:SendPerformanceCounter",
            this
          );
    
      async receiveMessage({ name, data }) {
    
        switch (name) {
          case "Extension:SendPerformanceCounter":
            PerformanceCounters.merge(data.counters);
            break;
    
      _onExtensionBrowser(type, browser, data = {}) {
        data.viewType = browser.getAttribute("webextension-view-type");
        if (data.viewType) {
          GlobalManager.frameData.set(browser, data);
    
      },
    
      getExtension(extensionId) {
        return this.extensionMap.get(extensionId);
      },
    };
    
    
    /**
     * The proxied parent side of a context in ExtensionChild.jsm, for the
     * parent side of a proxied API.
     */
    class ProxyContextParent extends BaseContext {
    
      constructor(envType, extension, params, xulBrowser, principal) {
        super(envType, extension);
    
    
        this.uri = Services.io.newURI(params.url);
    
        // This message manager is used by ParentAPIManager to send messages and to
        // close the ProxyContext if the underlying message manager closes. This
        // message manager object may change when `xulBrowser` swaps docshells, e.g.
        // when a tab is moved to a different window.
    
        this.messageManagerProxy =
          xulBrowser && new MessageManagerProxy(xulBrowser);
    
          value: principal,
          enumerable: true,
          configurable: true,
    
      /**
       * Call the `callable` parameter with `context.callContextData` set to the value passed
       * as the first parameter of this method.
       *
       * `context.callContextData` is expected to:
       * - don't be set when context.withCallContextData is being called
       * - be set back to null right after calling the `callable` function, without
       *   awaiting on any async code that the function may be running internally
       *
       * The callable method itself is responsabile of eventually retrieve the value initially set
       * on the `context.callContextData` before any code executed asynchronously (e.g. from a
       * callback or after awaiting internally on a promise if the `callable` function was async).
       *
       * @param {object} callContextData
       * @param {boolean} callContextData.isHandlingUserInput
       * @param {Function} callable
       *
       * @returns {any} Returns the value returned by calling the `callable` method.
       */
      withCallContextData({ isHandlingUserInput }, callable) {
        if (this.callContextData) {
          Cu.reportError(
            `Unexpected pre-existing callContextData on "${this.extension?.policy.debugName}" contextId ${this.contextId}`
          );
        }
    
        try {
          this.callContextData = {
            isHandlingUserInput,
          };
          return callable();
        } finally {
          this.callContextData = null;
        }
      }
    
    
      async withPendingBrowser(browser, callable) {
    
        let savedBrowser = this.pendingEventBrowser;
        this.pendingEventBrowser = browser;
        try {
    
          let result = await callable();
          return result;
    
      logActivity(type, name, data) {
        // The base class will throw so we catch any subclasses that do not implement.
        // We do not want to throw here, but we also do not log here.
      }
    
    
        // There's no need to clone when calling listeners for a proxied
        // context.
    
        return this.applySafeWithoutClone(callback, args);
    
        return this.messageManagerProxy?.eventTarget;
    
        return this.messageManagerProxy?.messageManager;
    
        super.unload();
        apiManager.emit("proxy-context-unload", this);
      }
    }
    
    
    defineLazyGetter(ProxyContextParent.prototype, "apiCan", function() {
    
      let can = new CanOfAPIs(this, this.extension.apiManager, obj);
    
      return can;
    });
    
    defineLazyGetter(ProxyContextParent.prototype, "apiObj", function() {
      return this.apiCan.root;
    
    defineLazyGetter(ProxyContextParent.prototype, "sandbox", function() {
    
      // NOTE: the required Blob and URL globals are used in the ext-registerContentScript.js
      // API module to convert JS and CSS data into blob URLs.
      return Cu.Sandbox(this.principal, {
        sandboxName: this.uri.spec,
        wantGlobalProperties: ["Blob", "URL"],
      });
    
    /**
     * The parent side of proxied API context for extension content script
     * running in ExtensionContent.jsm.
     */
    
    class ContentScriptContextParent extends ProxyContextParent {}
    
    
    /**
     * The parent side of proxied API context for extension page, such as a
     * background script, a tab page, or a popup, running in
     * ExtensionChild.jsm.
     */
    class ExtensionPageContextParent extends ProxyContextParent {
    
      constructor(envType, extension, params, xulBrowser) {
        super(envType, extension, params, xulBrowser, extension.principal);
    
        this.viewType = params.viewType;
    
        extension.emit("extension-proxy-context-load", this);
    
      }
    
      // The window that contains this context. This may change due to moving tabs.
    
        let win = this.xulBrowser.ownerGlobal;
    
        return win.browsingContext.topChromeWindow;
    
      get currentWindow() {
        if (this.viewType !== "background") {
    
        let { tabTracker } = apiManager.global;
    
        let data = tabTracker.getBrowserData(this.xulBrowser);
        if (data.tabId >= 0) {
          return data.tabId;
    
        }
      }
    
      onBrowserChange(browser) {
        super.onBrowserChange(browser);
        this.xulBrowser = browser;
      }
    
    
      unload() {
        super.unload();
        this.extension.views.delete(this);
      }
    
    
      shutdown() {
        apiManager.emit("page-shutdown", this);
        super.shutdown();
      }
    }
    
    
    /**
     * The parent side of proxied API context for devtools extension page, such as a
     * devtools pages and panels running in ExtensionChild.jsm.
     */
    class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
    
      constructor(...params) {
        super(...params);
    
        // Set all attributes that are lazily defined to `null` here.
        //
        // Note that we can't do that for `this._devToolsToolbox` because it will
        // be defined when calling our parent constructor and so would override it back to `null`.
        this._devToolsCommands = null;
    
        this._onNavigatedListeners = null;
    
        this._onResourceAvailable = this._onResourceAvailable.bind(this);
    
      set devToolsToolbox(toolbox) {
        if (this._devToolsToolbox) {
          throw new Error("Cannot set the context DevTools toolbox twice");
        }
    
        this._devToolsToolbox = toolbox;
      }
    
      get devToolsToolbox() {
        return this._devToolsToolbox;
      }
    
    
      async addOnNavigatedListener(listener) {
        if (!this._onNavigatedListeners) {
          this._onNavigatedListeners = new Set();
    
    
          await this.devToolsToolbox.resourceCommand.watchResources(
            [this.devToolsToolbox.resourceCommand.TYPES.DOCUMENT_EVENT],
    
            {
              onAvailable: this._onResourceAvailable,
              ignoreExistingResources: true,
            }
          );
        }
    
        this._onNavigatedListeners.add(listener);
      }
    
      removeOnNavigatedListener(listener) {
        if (this._onNavigatedListeners) {
          this._onNavigatedListeners.delete(listener);
        }
      }
    
    
       * The returned "commands" object, exposing modules implemented from devtools/shared/commands.
       * Each attribute being a static interface to communicate with the server backend.
       *
       * @returns {Promise<Object>}
    
      async getDevToolsCommands() {
        // Ensure that we try to instantiate a commands only once,
        // even if createCommandsForTabForWebExtension is async.
        if (this._devToolsCommandsPromise) {
          return this._devToolsCommandsPromise;
        }
        if (this._devToolsCommands) {
          return this._devToolsCommands;
    
        this._devToolsCommandsPromise = (async () => {
          const commands = await DevToolsShim.createCommandsForTabForWebExtension(
            this.devToolsToolbox.descriptorFront.localTab
          );
          await commands.targetCommand.startListening();
          this._devToolsCommands = commands;
          this._devToolsCommandsPromise = null;
          return commands;
        })();
        return this._devToolsCommandsPromise;
    
      unload() {
        // Bail if the toolbox reference was already cleared.
        if (!this.devToolsToolbox) {
          return;
        }
    
    
          this.devToolsToolbox.resourceCommand.unwatchResources(
            [this.devToolsToolbox.resourceCommand.TYPES.DOCUMENT_EVENT],
    
        if (this._devToolsCommands) {
          this._devToolsCommands.destroy();
          this._devToolsCommands = null;
    
        if (this._onNavigatedListeners) {
          this._onNavigatedListeners.clear();
          this._onNavigatedListeners = null;
        }
    
    
      async _onResourceAvailable(resources) {
        for (const resource of resources) {
          const { targetFront } = resource;
          if (targetFront.isTopLevel && resource.name === "dom-complete") {
            const url = targetFront.localTab.linkedBrowser.currentURI.spec;
            for (const listener of this._onNavigatedListeners) {
              listener(url);
            }
    
    /**
     * The parent side of proxied API context for extension background service
     * worker script.
     */
    class BackgroundWorkerContextParent extends ProxyContextParent {
      constructor(envType, extension, params) {
        // TODO: split out from ProxyContextParent a base class that
        // doesn't expect a xulBrowser and one for contexts that are
        // expected to have a xulBrowser associated.
        super(envType, extension, params, null, extension.principal);
    
        this.viewType = params.viewType;
        this.workerDescriptorId = params.workerDescriptorId;
    
        this.extension.views.add(this);
    
        extension.emit("extension-proxy-context-load", this);
      }
    }
    
    
    ParentAPIManager = {
      proxyContexts: new Map(),
    
      init() {
    
        // TODO: Bug 1595186 - remove/replace all usage of MessageManager below.
    
        Services.obs.addObserver(this, "message-manager-close");
    
        this.conduit = new BroadcastConduit(this, {
          id: "ParentAPIManager",
    
          recv: [
            "CreateProxyContext",
            "ContextLoaded",
            "APICall",
            "AddListener",
            "RemoveListener",
          ],
    
          query: ["RunListener", "StreamFilterSuspendCancel"],
    
      attachMessageManager(extension, processMessageManager) {
        extension.parentMessageManager = processMessageManager;
      },
    
      async observe(subject, topic, data) {
    
        if (topic === "message-manager-close") {
          let mm = subject;
          for (let [childId, context] of this.proxyContexts) {
    
            if (context.parentMessageManager === mm) {
    
    
          // Reset extension message managers when their child processes shut down.
          for (let extension of GlobalManager.extensionMap.values()) {
            if (extension.parentMessageManager === mm) {
              extension.parentMessageManager = null;
            }
          }
    
      shutdownExtension(extensionId, reason) {
        if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(reason)) {
          apiManager._callHandlers([extensionId], "disable", "onDisable");
        }
    
    
        for (let [childId, context] of this.proxyContexts) {
          if (context.extension.id == extensionId) {
            context.shutdown();
            this.proxyContexts.delete(childId);
          }
        }
      },
    
    
      queryStreamFilterSuspendCancel(childId) {
        return this.conduit.queryStreamFilterSuspendCancel(childId);
      },
    
    
      recvCreateProxyContext(data, { actor, sender }) {
    
        let { envType, extensionId, childId, principal } = data;
    
        let target = actor.browsingContext?.top.embedderElement;
    
          throw new Error(
            "A WebExtension context with the given ID already exists!"
          );
    
        }
    
        let extension = GlobalManager.getExtension(extensionId);
        if (!extension) {
          throw new Error(`No WebExtension found with ID ${extensionId}`);
        }
    
        let context;
    
        if (envType == "addon_parent" || envType == "devtools_parent") {
    
          if (!sender.verified) {
            throw new Error(`Bad sender context envType: ${sender.envType}`);
          }
    
    
          if (JSWindowActorParent.isInstance(actor)) {
    
            let processMessageManager =
              target.messageManager.processMessageManager ||
              Services.ppmm.getChildAt(0);
    
            if (!extension.parentMessageManager) {
              if (target.remoteType === extension.remoteType) {
                this.attachMessageManager(extension, processMessageManager);
              }
    
            if (processMessageManager !== extension.parentMessageManager) {
              throw new Error(
                "Attempt to create privileged extension parent from incorrect child process"
              );
            }
    
          } else if (JSProcessActorParent.isInstance(actor)) {
    
            if (actor.manager.remoteType !== extension.remoteType) {
              throw new Error(
                "Attempt to create privileged extension parent from incorrect child process"
              );
            }
    
            if (envType !== "addon_parent") {
              throw new Error(
                `Unexpected envType ${envType} on an extension process actor`
              );
            }
    
          if (envType == "addon_parent" && data.viewType === "background_worker") {
            context = new BackgroundWorkerContextParent(envType, extension, data);
          } else if (envType == "addon_parent") {
    
            context = new ExtensionPageContextParent(
              envType,
              extension,
              data,
              target
            );
    
          } else if (envType == "devtools_parent") {
    
            context = new DevToolsExtensionPageContextParent(
              envType,
              extension,
              data,
              target
            );
    
        } else if (envType == "content_parent") {
    
          context = new ContentScriptContextParent(
            envType,
            extension,
            data,
            target,
            principal
          );
    
        } else {
          throw new Error(`Invalid WebExtension context envType: ${envType}`);
        }
        this.proxyContexts.set(childId, context);
      },
    
    
      recvContextLoaded(data, { actor, sender }) {
        let context = this.getContextById(data.childId);
        verifyActorForContext(actor, context);
        const { extension } = context;
        extension.emit("extension-proxy-context-load:completed", context);
      },
    
    
      recvConduitClosed(sender) {
        this.closeProxyContext(sender.id);
      },
    
    
      closeProxyContext(childId) {
        let context = this.proxyContexts.get(childId);
        if (context) {
          context.unload();
          this.proxyContexts.delete(childId);
        }
      },
    
    
      async retrievePerformanceCounters() {
        // getting the parent counters
        return PerformanceCounters.getData();
    
      /**
       * Call the given function and also log the call as appropriate
       * (i.e., with PerformanceCounters and/or activity logging)
       *
       * @param {BaseContext} context The context making this call.
       * @param {object} data Additional data about the call.
       * @param {function} callable The actual implementation to invoke.
       */
      async callAndLog(context, data, callable) {
        let { id } = context.extension;
        // If we were called via callParentAsyncFunction we don't want
        // to log again, check for the flag.
        const { alreadyLogged } = data.options || {};
        if (!alreadyLogged) {
          ExtensionActivityLog.log(id, context.viewType, "api_call", data.path, {
            args: data.args,
          });
    
        try {
          return callable();
        } finally {
    
          ChromeUtils.addProfilerMarker(
            "ExtensionParent",
            { startTime: start },
            `${id}, api_call: ${data.path}`
          );
    
          if (gTimingEnabled) {
            let end = Cu.now() * 1000;