Bug 1368102: Part 8 - Move extension page matching into C++. r?billm,mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 25 May 2017 23:06:16 -0700
changeset 585217 56bcff99abdd98b20aa79f977737945c4ada1503
parent 585216 2acc1c9f2bc623d32a34044f15a1fc88abe9ef1c
child 630670 f176f37833cad1e3d20fe810acf35b49b2f9fe52
push id61052
push usermaglione.k@gmail.com
push dateFri, 26 May 2017 17:14:32 +0000
reviewersbillm, mixedpuppy
bugs1368102
milestone55.0a1
Bug 1368102: Part 8 - Move extension page matching into C++. r?billm,mixedpuppy Bill, can you please review the WebIDL change, and Shane the rest? MozReview-Commit-ID: 6N3sGrAsHzs
dom/webidl/WebExtensionPolicy.webidl
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ExtensionPolicyService.cpp
toolkit/components/extensions/ExtensionPolicyService.h
toolkit/components/extensions/WebExtensionPolicy.cpp
toolkit/components/extensions/WebExtensionPolicy.h
toolkit/components/extensions/extension-process-script.js
toolkit/components/extensions/moz.build
toolkit/components/extensions/mozIExtensionProcessScript.idl
--- a/dom/webidl/WebExtensionPolicy.webidl
+++ b/dom/webidl/WebExtensionPolicy.webidl
@@ -72,16 +72,19 @@ interface WebExtensionPolicy {
    * Only one extension policy with a given ID or hostname may be active at a
    * time. Attempting to activate a policy while a conflicting policy is
    * active will raise an error.
    */
   [Affects=Everything, SetterThrows]
   attribute boolean active;
 
 
+  static readonly attribute boolean isExtensionProcess;
+
+
   /**
    * Returns true if the extension has cross-origin access to the given URI.
    */
   boolean canAccessURI(URI uri);
 
   /**
    * Returns true if the extension currently has the given permission.
    */
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -17,19 +17,16 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                   "resource:///modules/E10SUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => {
   let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {});
   return UUIDMap;
 });
 
-const {appinfo} = Services;
-const isParentProcess = appinfo.processType === appinfo.PROCESS_TYPE_DEFAULT;
-
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
 function parseScriptOptions(options) {
   return {
     allFrames: options.all_frames,
@@ -83,20 +80,17 @@ function onCacheInvalidate() {
 Services.obs.addObserver(onCacheInvalidate, "startupcache-invalidate");
 
 var ExtensionManagement = {
   get cacheInvalidated() {
     return cacheInvalidated;
   },
 
   get isExtensionProcess() {
-    if (this.useRemoteWebExtensions) {
-      return appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE;
-    }
-    return isParentProcess;
+    return WebExtensionPolicy.isExtensionProcess;
   },
 
   // Called when a new extension is loaded.
   startupExtension(uuid, uri, extension) {
     let policy = new WebExtensionPolicy({
       id: extension.id,
       mozExtensionHostname: uuid,
       baseURL: uri.spec,
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -4,21 +4,24 @@
 
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/extensions/WebExtensionContentScript.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozIExtensionProcessScript.h"
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
+#include "nsIDOMDocument.h"
 #include "nsIDocument.h"
 #include "nsILoadInfo.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULAppAPI.h"
 
 namespace mozilla {
 
@@ -48,16 +51,18 @@ ProcessScript()
   }
   return *sProcessScript;
 }
 
 /*****************************************************************************
  * ExtensionPolicyService
  *****************************************************************************/
 
+/* static */ bool ExtensionPolicyService::sRemoteExtensions;
+
 /* static */ ExtensionPolicyService&
 ExtensionPolicyService::GetSingleton()
 {
   static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
 
   if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
     sExtensionPolicyService = new ExtensionPolicyService();
     ClearOnShutdown(&sExtensionPolicyService);
@@ -65,20 +70,33 @@ ExtensionPolicyService::GetSingleton()
   return *sExtensionPolicyService.get();
 }
 
 ExtensionPolicyService::ExtensionPolicyService()
 {
   mObs = services::GetObserverService();
   MOZ_RELEASE_ASSERT(mObs);
 
+  Preferences::AddBoolVarCache(&sRemoteExtensions, "extensions.webextensions.remote", false);
+
   RegisterObservers();
 }
 
 
+bool
+ExtensionPolicyService::IsExtensionProcess() const
+{
+  if (sRemoteExtensions) {
+    auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
+    return remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE);
+  }
+  return XRE_IsParentProcess();
+}
+
+
 WebExtensionPolicy*
 ExtensionPolicyService::GetByURL(const URLInfo& aURL)
 {
   if (aURL.Scheme() == nsGkAtoms::moz_extension) {
     return GetByHost(aURL.Host());
   }
   return nullptr;
 }
@@ -221,16 +239,27 @@ ExtensionPolicyService::CheckRequest(nsI
 // matching content scripts or extension principals, and loads them if
 // necessary.
 void
 ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
 {
   nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
   if (win) {
     CheckContentScripts(win.get(), false);
+
+    nsIPrincipal* principal = aDocument->NodePrincipal();
+
+    nsAutoString addonId;
+    Unused << principal->GetAddonId(addonId);
+
+    RefPtr<WebExtensionPolicy> policy = GetByID(addonId);
+    if (policy) {
+      nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
+      ProcessScript().InitExtensionDocument(policy, doc);
+    }
   }
 }
 
 // Checks for loads of about:blank into new window globals, and loads any
 // matching content scripts. about:blank loads do not trigger document element
 // inserted events, so they're the only load type that are special cased this
 // way.
 void
--- a/toolkit/components/extensions/ExtensionPolicyService.h
+++ b/toolkit/components/extensions/ExtensionPolicyService.h
@@ -71,16 +71,18 @@ public:
   void GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult);
 
   bool RegisterExtension(WebExtensionPolicy& aPolicy);
   bool UnregisterExtension(WebExtensionPolicy& aPolicy);
 
   void BaseCSP(nsAString& aDefaultCSP) const;
   void DefaultCSP(nsAString& aDefaultCSP) const;
 
+  bool IsExtensionProcess() const;
+
 protected:
   virtual ~ExtensionPolicyService() = default;
 
 private:
   ExtensionPolicyService();
 
   void RegisterObservers();
   void UnregisterObservers();
@@ -90,13 +92,15 @@ private:
   void CheckWindow(nsPIDOMWindowOuter* aWindow);
 
   void CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload);
 
   nsRefPtrHashtable<nsPtrHashKey<const nsIAtom>, WebExtensionPolicy> mExtensions;
   nsRefPtrHashtable<nsCStringHashKey, WebExtensionPolicy> mExtensionHosts;
 
   nsCOMPtr<nsIObserverService> mObs;
+
+  static bool sRemoteExtensions;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ExtensionPolicyService_h
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -236,16 +236,22 @@ WebExtensionPolicy::GetURL(const nsAStri
   nsCOMPtr<nsIURI> uri;
   NS_TRY(NS_NewURI(getter_AddRefs(uri), spec));
 
   NS_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
 
   return NS_ConvertUTF8toUTF16(spec);
 }
 
+/* static */ bool
+WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal)
+{
+  return EPS().IsExtensionProcess();
+}
+
 nsCString
 WebExtensionPolicy::BackgroundPageHTML() const
 {
   nsAutoCString result;
 
   if (mBackgroundScripts.IsNull()) {
     result.SetIsVoid(true);
     return result;
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -126,16 +126,19 @@ public:
 
   static already_AddRefed<WebExtensionPolicy>
   GetByHostname(dom::GlobalObject& aGlobal, const nsACString& aHostname);
 
   static already_AddRefed<WebExtensionPolicy>
   GetByURI(dom::GlobalObject& aGlobal, nsIURI* aURI);
 
 
+  static bool IsExtensionProcess(dom::GlobalObject& aGlobal);
+
+
   nsISupports* GetParentObject() const { return mParent; }
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
 
 protected:
   virtual ~WebExtensionPolicy() = default;
 
 private:
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -10,18 +10,16 @@
  * after startup, in *every* browser process live outside of this file.
  */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 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, "ExtensionChild",
                                   "resource://gre/modules/ExtensionChild.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionContent",
                                   "resource://gre/modules/ExtensionContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPageChild",
@@ -88,19 +86,16 @@ class ExtensionGlobal {
 
     MessageChannel.addListener(global, "Extension:Capture", this);
     MessageChannel.addListener(global, "Extension:DetectLanguage", this);
     MessageChannel.addListener(global, "Extension:Execute", this);
     MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
     MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
   }
 
-  uninit() {
-  }
-
   get messageFilterStrict() {
     return {
       innerWindowID: getInnerWindowID(this.global.content),
     };
   }
 
   receiveMessage({target, messageName, recipient, data}) {
     switch (messageName) {
@@ -138,27 +133,19 @@ DocumentManager = {
   globals: new Map(),
 
   // Initialize listeners that we need regardless of whether extensions are
   // enabled.
   earlyInit() {
     Services.obs.addObserver(this, "tab-content-frameloader-created"); // eslint-disable-line mozilla/balanced-listeners
   },
 
-  // Initialize listeners that we need when any extension is enabled.
-  init() {
-    Services.obs.addObserver(this, "document-element-inserted");
-  },
-  uninit() {
-    Services.obs.removeObserver(this, "document-element-inserted");
-  },
-
   extensionProcessInitialized: false,
   initExtensionProcess() {
-    if (this.extensionProcessInitialized || !ExtensionManagement.isExtensionProcess) {
+    if (this.extensionProcessInitialized || !WebExtensionPolicy.isExtensionProcess) {
       return;
     }
     this.extensionProcessInitialized = true;
 
     for (let global of this.globals.keys()) {
       ExtensionPageChild.init(global);
     }
   },
@@ -167,69 +154,39 @@ DocumentManager = {
   // into.
   initGlobal(global) {
     // Note: {once: true} does not work as expected here.
     global.addEventListener("unload", event => { // eslint-disable-line mozilla/balanced-listeners
       this.uninitGlobal(global);
     });
 
     this.globals.set(global, new ExtensionGlobal(global));
-    this.initExtensionProcess();
-    if (this.extensionProcessInitialized && ExtensionManagement.isExtensionProcess) {
+    if (this.extensionProcessInitialized && WebExtensionPolicy.isExtensionProcess) {
       ExtensionPageChild.init(global);
     }
   },
   uninitGlobal(global) {
     if (this.extensionProcessInitialized) {
       ExtensionPageChild.uninit(global);
     }
-    this.globals.get(global).uninit();
     this.globals.delete(global);
   },
 
   initExtension(extension) {
-    if (this.extensionCount === 0) {
-      this.init();
-      this.initExtensionProcess();
-    }
-    this.extensionCount++;
+    this.initExtensionProcess();
 
     this.injectExtensionScripts(extension);
   },
-  uninitExtension(extension) {
-    this.extensionCount--;
-    if (this.extensionCount === 0) {
-      this.uninit();
-    }
-  },
-
-  extensionCount: 0,
 
   // Listeners
 
-  observers: {
-    "document-element-inserted"(document) {
-      let window = document.defaultView;
-      if (!document.location || !window ||
-          // Make sure we only load into frames that belong to tabs, or other
-          // special areas that we want to load content scripts into.
-          !this.globals.has(getMessageManager(window))) {
-        return;
-      }
-
-      this.loadInto(window);
-    },
-
-    "tab-content-frameloader-created"(global) {
-      this.initGlobal(global);
-    },
-  },
-
   observe(subject, topic, data) {
-    this.observers[topic].call(this, subject, topic, data);
+    if (topic == "tab-content-frameloader-created") {
+      this.initGlobal(subject);
+    }
   },
 
   // Script loading
 
   injectExtensionScripts(extension) {
     for (let window of this.enumerateWindows()) {
       for (let script of extension.contentScripts) {
         if (script.matchesWindow(window)) {
@@ -276,29 +233,19 @@ DocumentManager = {
       if (principal.addonId !== addonId) {
         return false;
       }
     }
 
     return true;
   },
 
-  loadInto(window) {
-    let {addonId} = Cu.getObjectPrincipal(window);
-    if (!addonId) {
-      return;
-    }
-
-    let policy = WebExtensionPolicy.getByID(addonId);
-    if (!policy) {
-      throw new Error(`No registered extension for ID ${addonId}`);
-    }
-
+  loadInto(policy, window) {
     let extension = extensions.get(policy);
-    if (this.checkParentFrames(window, addonId) && ExtensionManagement.isExtensionProcess) {
+    if (this.checkParentFrames(window, policy.id) && WebExtensionPolicy.isExtensionProcess) {
       // We're in a top-level extension frame, or a sub-frame thereof,
       // in the extension process. Inject the full extension page API.
       ExtensionPageChild.initExtensionContext(extension, window);
     } else {
       // We're in a content sub-frame or not in the extension process.
       // Only inject a minimal content script API.
       ExtensionContent.initExtensionContext(extension, window);
     }
@@ -390,18 +337,16 @@ ExtensionManager = {
 
       case "Extension:Shutdown": {
         let policy = WebExtensionPolicy.getByID(data.id);
 
         if (extensions.has(policy)) {
           extensions.get(policy).shutdown();
         }
 
-        DocumentManager.uninitExtension(policy);
-
         if (isContentProcess) {
           policy.active = false;
         }
         break;
       }
 
       case "Extension:FlushJarCache": {
         ExtensionUtils.flushJarCache(data.path);
@@ -425,16 +370,22 @@ function ExtensionProcessScript() {
 }
 
 ExtensionProcessScript.singleton = null;
 
 ExtensionProcessScript.prototype = {
   classID: Components.ID("{21f9819e-4cdf-49f9-85a0-850af91a5058}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIExtensionProcessScript]),
 
+  initExtensionDocument(policy, doc) {
+    if (DocumentManager.globals.has(getMessageManager(doc.defaultView))) {
+      DocumentManager.loadInto(policy, doc.defaultView);
+    }
+  },
+
   preloadContentScript(contentScript) {
     contentScripts.get(contentScript).preload();
   },
 
   loadContentScript(contentScript, window) {
     if (DocumentManager.globals.has(getMessageManager(window))) {
       contentScripts.get(contentScript).injectInto(window);
     }
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -82,8 +82,10 @@ MOCHITEST_MANIFESTS += [
     'test/mochitest/mochitest.ini'
 ]
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/native_messaging.ini',
     'test/xpcshell/xpcshell-remote.ini',
     'test/xpcshell/xpcshell.ini',
 ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
--- a/toolkit/components/extensions/mozIExtensionProcessScript.idl
+++ b/toolkit/components/extensions/mozIExtensionProcessScript.idl
@@ -1,16 +1,18 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
 interface mozIDOMWindowProxy;
+interface nsIDOMDocument;
 
 [scriptable,uuid(6b09dc51-6caa-4ca7-9d6d-30c87258a630)]
 interface mozIExtensionProcessScript : nsISupports
 {
   void preloadContentScript(in nsISupports contentScript);
 
   void loadContentScript(in nsISupports contentScript, in mozIDOMWindowProxy window);
 
+  void initExtensionDocument(in nsISupports extension, in nsIDOMDocument doc);
 };