Bug 1317101 - Part 6: Remove or refactor code that prevents extensions from running in the child process. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 12 Nov 2016 17:09:19 -0800
changeset 438072 0de312c933cf2e929b8c4c8208dd517570a522a4
parent 438071 95f17737b12c457a9aece9159a6cf1e4c2a98174
child 438073 14201484d276bef8b0a741218b5ed47dd9963320
push id35614
push usermaglione.k@gmail.com
push dateSun, 13 Nov 2016 03:28:59 +0000
reviewersaswan
bugs1317101
milestone52.0a1
Bug 1317101 - Part 6: Remove or refactor code that prevents extensions from running in the child process. r?aswan MozReview-Commit-ID: 7v5eIBOKA2v
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ExtensionParent.jsm
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -17,16 +17,18 @@ this.EXPORTED_SYMBOLS = ["ExtensionChild
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
+                                  "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                   "resource://gre/modules/NativeMessaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
@@ -727,22 +729,16 @@ class ExtensionPageContextChild extends 
    * @param {string} params.viewType One of "background", "popup" or "tab".
    *     "background" and "tab" are used by `browser.extension.getViews`.
    *     "popup" is only used internally to identify page action and browser
    *     action popups and options_ui pages.
    * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
    */
   constructor(extension, params) {
     super("addon_child", extension);
-    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
-      // This check is temporary. It should be removed once the proxy creation
-      // is asynchronous.
-      throw new Error("ExtensionPageContextChild cannot be created in child processes");
-    }
-
     let {viewType, uri, contentWindow, tabId} = params;
     this.viewType = viewType;
     this.uri = uri || extension.baseURI;
 
     this.setContentWindow(contentWindow);
 
     // This is the MessageSender property passed to extension.
     // It can be augmented by the "page-open" hook.
@@ -918,65 +914,73 @@ class ContentGlobal {
       // Do not confuse it with the innerWindowID of DOMWindows!
       this.windowId = windowId;
     }
     this.initialized = true;
   }
 }
 
 ExtensionChild = {
+  ChildAPIManager,
+  Messenger,
+  Port,
+
   // Map<nsIContentFrameMessageManager, ContentGlobal>
   contentGlobals: new Map(),
 
   // Map<innerWindowId, ExtensionPageContextChild>
   extensionContexts: new Map(),
 
   initOnce() {
     // This initializes the default message handler for messages targeted at
     // an addon process, in case the addon process receives a message before
     // its Messenger has been instantiated. For example, if a content script
     // sends a message while there is no background page.
     MessageChannel.setupMessageManagers([Services.cpmm]);
   },
 
   init(global) {
+    if (!ExtensionManagement.isExtensionProcess) {
+      throw new Error("Cannot init extension page global in current process");
+    }
+
     this.contentGlobals.set(global, new ContentGlobal(global));
   },
 
   uninit(global) {
     this.contentGlobals.get(global).uninit();
     this.contentGlobals.delete(global);
   },
 
   /**
    * Create a privileged context at document-element-inserted.
    *
    * @param {BrowserExtensionContent} extension
    *     The extension for which the context should be created.
    * @param {nsIDOMWindow} contentWindow The global of the page.
    */
   createExtensionContext(extension, contentWindow) {
+    if (!ExtensionManagement.isExtensionProcess) {
+      throw new Error("Cannot create an extension page context in current process");
+    }
+
     let windowId = getInnerWindowID(contentWindow);
     let context = this.extensionContexts.get(windowId);
     if (context) {
       if (context.extension !== extension) {
-        // Oops. This should never happen.
-        Cu.reportError("A different extension context already exists in this frame!");
-      } else {
-        // This should not happen either.
-        Cu.reportError("The extension context was already initialized in this frame.");
+        throw new Error("A different extension context already exists for this frame");
       }
-      return;
+      throw new Error("An extension context was already initialized for this frame");
     }
 
-    let mm = contentWindow
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDocShell)
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIContentFrameMessageManager);
+    let mm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell)
+                          .QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIContentFrameMessageManager);
+
     let {viewType, tabId} = this.contentGlobals.get(mm).ensureInitialized();
 
     let uri = contentWindow.document.documentURIObject;
 
     context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId});
     this.extensionContexts.set(windowId, context);
   },
 
@@ -997,25 +1001,8 @@ ExtensionChild = {
     for (let [windowId, context] of this.extensionContexts) {
       if (context.extension.id == extensionId) {
         context.shutdown();
         this.extensionContexts.delete(windowId);
       }
     }
   },
 };
-
-// TODO(robwu): Change this condition when addons move to a separate process.
-if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
-  Object.keys(ExtensionChild).forEach(function(key) {
-    if (typeof ExtensionChild[key] == "function") {
-      // :/
-      ExtensionChild[key] = () => {};
-    }
-  });
-}
-
-Object.assign(ExtensionChild, {
-  ChildAPIManager,
-  Messenger,
-  Port,
-});
-
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -1013,21 +1013,25 @@ class ExtensionGlobal {
   }
 }
 
 this.ExtensionContent = {
   globals: new Map(),
 
   init(global) {
     this.globals.set(global, new ExtensionGlobal(global));
-    ExtensionChild.init(global);
+    if (ExtensionManagement.isExtensionProcess) {
+      ExtensionChild.init(global);
+    }
   },
 
   uninit(global) {
-    ExtensionChild.uninit(global);
+    if (ExtensionManagement.isExtensionProcess) {
+      ExtensionChild.uninit(global);
+    }
     this.globals.get(global).uninit();
     this.globals.delete(global);
   },
 
   // This helper is exported to be integrated in the devtools RDP actors,
   // that can use it to retrieve the existent WebExtensions ContentScripts
   // of a target window and be able to show the ContentScripts source in the
   // DevTools Debugger panel.
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -20,16 +20,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());
 
 XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => {
   let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {});
   return UUIDMap;
 });
 
+var ExtensionManagement;
+
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
 // Keep track of frame IDs for content windows. Mostly we can just use
 // the outer window ID as the frame ID. However, the API specifies
 // that top-level windows have a frame ID of 0. So we need to keep
@@ -203,27 +205,30 @@ var Service = {
   // Checks whether a given extension can load this URI (typically via
   // an XML HTTP request). The manifest.json |permissions| directive
   // determines this.
   checkAddonMayLoad(extension, uri) {
     return extension.whiteListedHosts.matchesIgnoringPath(uri);
   },
 
   generateBackgroundPageUrl(extension) {
-    let background_scripts = extension.manifest.background &&
-      extension.manifest.background.scripts;
+    let background_scripts = (extension.manifest.background &&
+                              extension.manifest.background.scripts);
+
     if (!background_scripts) {
       return;
     }
-    let html = "<!DOCTYPE html>\n<body>\n";
+
+    let html = "<!DOCTYPE html>\n<html>\n<body>\n";
     for (let script of background_scripts) {
       script = script.replace(/"/g, "&quot;");
       html += `<script src="${script}"></script>\n`;
     }
     html += "</body>\n</html>\n";
+
     return "data:text/html;charset=utf-8," + encodeURIComponent(html);
   },
 
   // Finds the add-on ID associated with a given moz-extension:// URI.
   // This is used to set the addonId on the originAttributes for the
   // nsIPrincipal attached to the URI.
   extensionURIToAddonID(uri) {
     let uuid = uri.host;
@@ -254,19 +259,17 @@ function getAPILevelForWindow(window, ad
   const {NO_PRIVILEGES, CONTENTSCRIPT_PRIVILEGES, FULL_PRIVILEGES} = API_LEVELS;
 
   // Non WebExtension URLs and WebExtension URLs from a different extension
   // has no access to APIs.
   if (!addonId || getAddonIdForWindow(window) != addonId) {
     return NO_PRIVILEGES;
   }
 
-  // Extension pages running in the content process always defaults to
-  // "content script API level privileges".
-  if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+  if (!ExtensionManagement.isExtensionProcess) {
     return CONTENTSCRIPT_PRIVILEGES;
   }
 
   let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIDocShell);
 
   // Handling of ExtensionPages running inside sub-frames.
   if (docShell.sameTypeParent) {
@@ -295,17 +298,21 @@ function getAPILevelForWindow(window, ad
     // (see Bug 1214658 for rationale)
     return CONTENTSCRIPT_PRIVILEGES;
   }
 
   // WebExtension URLs loaded into top frames UI could have full API level privileges.
   return FULL_PRIVILEGES;
 }
 
-this.ExtensionManagement = {
+ExtensionManagement = {
+  get isExtensionProcess() {
+    return Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
+  },
+
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   registerAPI: APIs.register.bind(APIs),
   unregisterAPI: APIs.unregister.bind(APIs),
 
   getFrameId: Frames.getId.bind(Frames),
   getParentFrameId: Frames.getParentId.bind(Frames),
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -434,22 +434,16 @@ ParentAPIManager = {
 
     let extension = GlobalManager.getExtension(extensionId);
     if (!extension) {
       throw new Error(`No WebExtension found with ID ${extensionId}`);
     }
 
     let context;
     if (envType == "addon_parent") {
-      // Privileged addon contexts can only be loaded in documents whose main
-      // frame is also the same addon.
-      if (principal.URI.prePath !== extension.baseURI.prePath ||
-          !target.contentPrincipal.subsumes(principal)) {
-        throw new Error(`Refused to create privileged WebExtension context for ${principal.URI.spec}`);
-      }
       context = new ExtensionPageContextParent(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);
   },
@@ -539,19 +533,17 @@ ParentAPIManager = {
     let context = this.getContextById(data.childId);
     let listener = context.listenerProxies.get(data.path);
     findPathInObject(context.apiObj, data.path).removeListener(listener);
   },
 
   getContextById(childId) {
     let context = this.proxyContexts.get(childId);
     if (!context) {
-      let error = new Error("WebExtension context not found!");
-      Cu.reportError(error);
-      throw error;
+      throw new Error("WebExtension context not found!");
     }
     return context;
   },
 };
 
 ParentAPIManager.init();