Bug 1484373: Part 4 - Move more content script injection logic into policy service. r=mixedpuppy
authorKris Maglione <maglione.k@gmail.com>
Fri, 17 Aug 2018 22:09:23 -0700
changeset 433220 a7848dcb339e399ee108590e413a548dfea4a1c3
parent 433219 010feaaca7deeddb0ae13e5e899988af089ae241
child 433221 03cdd81e991d9675c5166e39a825b99fabded292
push id34501
push usertoros@mozilla.com
push dateFri, 24 Aug 2018 09:45:02 +0000
treeherdermozilla-central@190b827aaa2b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy
bugs1484373
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484373: Part 4 - Move more content script injection logic into policy service. r=mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D3694
dom/chrome-webidl/WebExtensionPolicy.webidl
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/mozIExtensionProcessScript.idl
--- a/dom/chrome-webidl/WebExtensionPolicy.webidl
+++ b/dom/chrome-webidl/WebExtensionPolicy.webidl
@@ -130,16 +130,22 @@ interface WebExtensionPolicy {
 
   /**
    * Unregister a content script.
    */
   [Throws]
   void unregisterContentScript(WebExtensionContentScript script);
 
   /**
+   * Injects the extension's content script into all existing matching windows.
+   */
+  [Throws]
+  void injectContentScripts();
+
+  /**
    * Returns the list of currently active extension policies.
    */
   static sequence<WebExtensionPolicy> getActiveExtensions();
 
   /**
    * Returns the currently-active policy for the extension with the given ID,
    * or null if no policy is active for that ID.
    */
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -7,34 +7,44 @@
 #include "mozilla/extensions/DocumentObserver.h"
 #include "mozilla/extensions/WebExtensionContentScript.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
+#include "mozilla/SimpleEnumerator.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
 #include "mozIExtensionProcessScript.h"
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
+#include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsILoadInfo.h"
 #include "nsIXULRuntime.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULAppAPI.h"
+#include "nsQueryObject.h"
 
 namespace mozilla {
 
 using namespace extensions;
 
+using dom::AutoJSAPI;
+using dom::ContentFrameMessageManager;
+using dom::Promise;
+
 #define DEFAULT_BASE_CSP \
     "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
     "object-src 'self' https://* moz-extension: blob: filesystem:;"
 
 #define DEFAULT_DEFAULT_CSP \
     "script-src 'self'; object-src 'self';"
 
 
@@ -246,26 +256,28 @@ ExtensionPolicyService::CollectReports(n
  * Content script management
  *****************************************************************************/
 
 void
 ExtensionPolicyService::RegisterObservers()
 {
   mObs->AddObserver(this, "content-document-global-created", false);
   mObs->AddObserver(this, "document-element-inserted", false);
+  mObs->AddObserver(this, "tab-content-frameloader-created", false);
   if (XRE_IsContentProcess()) {
     mObs->AddObserver(this, "http-on-opening-request", false);
   }
 }
 
 void
 ExtensionPolicyService::UnregisterObservers()
 {
   mObs->RemoveObserver(this, "content-document-global-created");
   mObs->RemoveObserver(this, "document-element-inserted");
+  mObs->RemoveObserver(this, "tab-content-frameloader-created");
   if (XRE_IsContentProcess()) {
     mObs->RemoveObserver(this, "http-on-opening-request");
   }
 }
 
 nsresult
 ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
 {
@@ -279,16 +291,139 @@ ExtensionPolicyService::Observe(nsISuppo
     if (doc) {
       CheckDocument(doc);
     }
   } else if (!strcmp(aTopic, "http-on-opening-request")) {
     nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject);
     if (chan) {
       CheckRequest(chan);
     }
+  } else if (!strcmp(aTopic, "tab-content-frameloader-created")) {
+    RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aSubject);
+    NS_ENSURE_TRUE(mm, NS_ERROR_UNEXPECTED);
+
+    mMessageManagers.PutEntry(mm);
+
+    mm->AddSystemEventListener(NS_LITERAL_STRING("unload"), this,
+                               false, false);
+  }
+  return NS_OK;
+}
+
+nsresult
+ExtensionPolicyService::HandleEvent(dom::Event* aEvent)
+{
+  RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aEvent->GetTarget());
+  MOZ_ASSERT(mm);
+  if (mm) {
+    mMessageManagers.RemoveEntry(mm);
+  }
+  return NS_OK;
+}
+
+nsresult
+ForEachDocShell(nsIDocShell* aDocShell,
+                const std::function<nsresult(nsIDocShell*)>& aCallback)
+{
+  nsCOMPtr<nsISimpleEnumerator> iter;
+  MOZ_TRY(aDocShell->GetDocShellEnumerator(nsIDocShell::typeContent,
+                                           nsIDocShell::ENUMERATE_FORWARDS,
+                                           getter_AddRefs(iter)));
+
+  for (nsIDocShell& docShell : SimpleEnumerator<nsIDocShell>(iter)) {
+    MOZ_TRY(aCallback(&docShell));
+  }
+  return NS_OK;
+}
+
+
+already_AddRefed<Promise>
+ExtensionPolicyService::ExecuteContentScript(nsPIDOMWindowInner* aWindow,
+                                             WebExtensionContentScript& aScript)
+{
+  if (!aWindow->IsCurrentInnerWindow()) {
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise;
+  ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise));
+  return promise.forget();
+}
+
+RefPtr<Promise>
+ExtensionPolicyService::ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+                                              const nsTArray<RefPtr<WebExtensionContentScript>>& aScripts)
+{
+  AutoTArray<RefPtr<Promise>, 8> promises;
+
+  for (auto& script : aScripts) {
+    promises.AppendElement(ExecuteContentScript(aWindow, *script));
+  }
+
+  RefPtr<Promise> promise = Promise::All(aCx, promises, IgnoreErrors());
+  MOZ_RELEASE_ASSERT(promise);
+  return promise;
+}
+
+nsresult
+ExtensionPolicyService::InjectContentScripts(WebExtensionPolicy* aExtension)
+{
+  AutoJSAPI jsapi;
+  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+
+  for (auto iter = mMessageManagers.ConstIter(); !iter.Done(); iter.Next()) {
+    ContentFrameMessageManager* mm = iter.Get()->GetKey();
+
+    nsCOMPtr<nsIDocShell> docShell = mm->GetDocShell(IgnoreErrors());
+    NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+
+    auto result = ForEachDocShell(docShell, [&](nsIDocShell* aDocShell) -> nsresult {
+      nsCOMPtr<nsPIDOMWindowOuter> win = aDocShell->GetWindow();
+      DocInfo docInfo(win);
+
+      using RunAt = dom::ContentScriptRunAt;
+      using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
+
+      constexpr uint8_t n = uint8_t(RunAt::EndGuard_);
+      Scripts scripts[n];
+
+      auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
+        return std::move(scripts[uint8_t(aRunAt)]);
+      };
+
+      for (const auto& script : aExtension->ContentScripts()) {
+        if (script->Matches(docInfo)) {
+          GetScripts(script->RunAt()).AppendElement(script);
+        }
+      }
+
+      nsCOMPtr<nsPIDOMWindowInner> inner = win->GetCurrentInnerWindow();
+
+      MOZ_TRY(ExecuteContentScripts(jsapi.cx(), inner, GetScripts(RunAt::Document_start))
+        ->ThenWithCycleCollectedArgs([](JSContext* aCx, JS::HandleValue aValue,
+                                        ExtensionPolicyService* aSelf,
+                                        nsPIDOMWindowInner* aInner,
+                                        Scripts&& aScripts) {
+          return aSelf->ExecuteContentScripts(aCx, aInner, aScripts).forget();
+        },
+        this, inner, GetScripts(RunAt::Document_end))
+        .andThen([&](auto aPromise) {
+          return aPromise->ThenWithCycleCollectedArgs([](JSContext* aCx,
+                                                         JS::HandleValue aValue,
+                                                         ExtensionPolicyService* aSelf,
+                                                         nsPIDOMWindowInner* aInner,
+                                                         Scripts&& aScripts) {
+            return aSelf->ExecuteContentScripts(aCx, aInner, aScripts).forget();
+          },
+          this, inner, GetScripts(RunAt::Document_idle));
+        }));
+
+      return NS_OK;
+    });
+    MOZ_TRY(result);
   }
   return NS_OK;
 }
 
 // Checks a request for matching content scripts, and begins pre-loading them
 // if necessary.
 void
 ExtensionPolicyService::CheckRequest(nsIChannel* aChannel)
@@ -315,16 +450,22 @@ ExtensionPolicyService::CheckRequest(nsI
 // Checks a document, just after the document element has been inserted, for
 // matching content scripts or extension principals, and loads them if
 // necessary.
 void
 ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
 {
   nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
   if (win) {
+    nsIDocShell* docShell = win->GetDocShell();
+    RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager();
+    if (!mm || !mMessageManagers.Contains(mm)) {
+      return;
+    }
+
     if (win->GetDocumentURI()) {
       CheckContentScripts(win.get(), false);
     }
 
     nsIPrincipal* principal = aDocument->NodePrincipal();
 
     RefPtr<WebExtensionPolicy> policy = BasePrincipal::Cast(principal)->AddonPolicy();
     if (policy) {
@@ -350,31 +491,43 @@ ExtensionPolicyService::CheckWindow(nsPI
 
   nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
   nsCOMPtr<nsIURI> uri;
   if (!docUri || NS_FAILED(NS_GetURIWithoutRef(docUri, getter_AddRefs(uri))) ||
       !NS_IsAboutBlank(uri)) {
     return;
   }
 
-  CheckContentScripts(aWindow, false);
+  nsIDocShell* docShell = aWindow->GetDocShell();
+  if (RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager()) {
+    if (mMessageManagers.Contains(mm)) {
+      CheckContentScripts(aWindow, false);
+    }
+  }
 }
 
 void
 ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
 {
+  nsCOMPtr<nsPIDOMWindowInner> win = aDocInfo.GetWindow()->GetCurrentInnerWindow();
+
   for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
+    if (!win->IsCurrentInnerWindow()) {
+      break;
+    }
+
     RefPtr<WebExtensionPolicy> policy = iter.Data();
 
     for (auto& script : policy->ContentScripts()) {
       if (script->Matches(aDocInfo)) {
         if (aIsPreload) {
           ProcessScript().PreloadContentScript(script);
         } else {
-          ProcessScript().LoadContentScript(script, aDocInfo.GetWindow());
+          RefPtr<Promise> promise;
+          ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise));
         }
       }
     }
   }
 
   for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) {
     RefPtr<DocumentObserver> observer = iter.Data();
 
@@ -498,16 +651,17 @@ ExtensionPolicyService::ExtensionURIToAd
 
 
 NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts,
                          mObservers)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
   NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
 
 } // namespace mozilla
--- a/toolkit/components/extensions/ExtensionPolicyService.h
+++ b/toolkit/components/extensions/ExtensionPolicyService.h
@@ -8,47 +8,57 @@
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsHashKeys.h"
 #include "nsIAddonPolicyService.h"
 #include "nsAtom.h"
+#include "nsIDOMEventListener.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsISupports.h"
 #include "nsPointerHashKeys.h"
 #include "nsRefPtrHashtable.h"
+#include "nsTHashtable.h"
 
 class nsIChannel;
 class nsIObserverService;
 class nsIDocument;
+class nsIPIDOMWindowInner;
 class nsIPIDOMWindowOuter;
 
 namespace mozilla {
+namespace dom {
+  class ContentFrameMessageManager;
+  class Promise;
+}
 namespace extensions {
   class DocInfo;
   class DocumentObserver;
+  class WebExtensionContentScript;
 }
 
 using extensions::DocInfo;
 using extensions::WebExtensionPolicy;
 
 class ExtensionPolicyService final : public nsIAddonPolicyService
                                    , public nsIObserver
+                                   , public nsIDOMEventListener
                                    , public nsIMemoryReporter
 {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ExtensionPolicyService,
                                            nsIAddonPolicyService)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIADDONPOLICYSERVICE
   NS_DECL_NSIOBSERVER
+  NS_DECL_NSIDOMEVENTLISTENER
   NS_DECL_NSIMEMORYREPORTER
 
   static ExtensionPolicyService& GetSingleton();
 
   static already_AddRefed<ExtensionPolicyService> GetInstance()
   {
     return do_AddRef(&GetSingleton());
   }
@@ -81,34 +91,46 @@ public:
   bool UnregisterObserver(extensions::DocumentObserver& aPolicy);
 
   void BaseCSP(nsAString& aDefaultCSP) const;
   void DefaultCSP(nsAString& aDefaultCSP) const;
 
   bool UseRemoteExtensions() const;
   bool IsExtensionProcess() const;
 
+  nsresult InjectContentScripts(WebExtensionPolicy* aExtension);
+
 protected:
   virtual ~ExtensionPolicyService();
 
 private:
   ExtensionPolicyService();
 
   void RegisterObservers();
   void UnregisterObservers();
 
   void CheckRequest(nsIChannel* aChannel);
   void CheckDocument(nsIDocument* aDocument);
   void CheckWindow(nsPIDOMWindowOuter* aWindow);
 
   void CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload);
 
+  already_AddRefed<dom::Promise>
+  ExecuteContentScript(nsPIDOMWindowInner* aWindow,
+                       extensions::WebExtensionContentScript& aScript);
+
+  RefPtr<dom::Promise>
+  ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+                        const nsTArray<RefPtr<extensions::WebExtensionContentScript>>& aScripts);
+
   nsRefPtrHashtable<nsPtrHashKey<const nsAtom>, WebExtensionPolicy> mExtensions;
   nsRefPtrHashtable<nsCStringHashKey, WebExtensionPolicy> mExtensionHosts;
 
+  nsTHashtable<nsRefPtrHashKey<dom::ContentFrameMessageManager>> mMessageManagers;
+
   nsRefPtrHashtable<nsPtrHashKey<const extensions::DocumentObserver>,
                     extensions::DocumentObserver> mObservers;
 
   nsCOMPtr<nsIObserverService> mObs;
 
   static bool sRemoteExtensions;
 };
 
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -253,16 +253,25 @@ WebExtensionPolicy::UnregisterContentScr
   if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) {
     aRv.Throw(NS_ERROR_INVALID_ARG);
     return;
   }
 
   WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
 }
 
+void
+WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv)
+{
+  nsresult rv = EPS().InjectContentScripts(this);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
 /* static */ bool
 WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal)
 {
   return EPS().UseRemoteExtensions();
 }
 
 /* static */ bool
 WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal)
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -62,16 +62,18 @@ public:
   Result<nsString, nsresult> GetURL(const nsAString& aPath) const;
 
   void RegisterContentScript(WebExtensionContentScript& script,
                              ErrorResult& aRv);
 
   void UnregisterContentScript(const WebExtensionContentScript& script,
                                ErrorResult& aRv);
 
+  void InjectContentScripts(ErrorResult& aRv);
+
   bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false, bool aCheckRestricted = true) const
   {
     return (!aCheckRestricted || !IsRestrictedURI(aURI)) &&
             mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
   }
 
   bool IsPathWebAccessible(const nsAString& aPath) const
   {
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -167,64 +167,33 @@ class ExtensionGlobal {
 // Responsible for creating ExtensionContexts and injecting content
 // scripts into them when new documents are created.
 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
+    // eslint-disable-next-line mozilla/balanced-listeners
+    Services.obs.addObserver((subject) => this.initGlobal(subject),
+                             "tab-content-frameloader-created");
   },
 
   // Initialize a frame script global which extension contexts may be loaded
   // into.
   initGlobal(global) {
     this.globals.set(global, new ExtensionGlobal(global));
     // eslint-disable-next-line mozilla/balanced-listeners
     global.addEventListener("unload", () => {
       this.globals.delete(global);
     });
   },
 
-  initExtension(policy) {
-    this.injectExtensionScripts(policy);
-  },
-
-  // Listeners
-
-  observe(subject, topic, data) {
-    if (topic == "tab-content-frameloader-created") {
-      this.initGlobal(subject);
-    }
-  },
-
   // Script loading
 
-  injectExtensionScripts(policy) {
-    for (let window of this.enumerateWindows()) {
-      let runAt = {document_start: [], document_end: [], document_idle: []};
-
-      for (let script of policy.contentScripts) {
-        if (script.matchesWindow(window)) {
-          runAt[script.runAt].push(script);
-        }
-      }
-
-      let inject = matcher => contentScripts.get(matcher).injectInto(window);
-      let injectAll = matchers => Promise.all(matchers.map(inject));
-
-      // Intentionally using `.then` instead of `await`, we only need to
-      // chain injecting other scripts into *this* window, not all windows.
-      injectAll(runAt.document_start)
-        .then(() => injectAll(runAt.document_end))
-        .then(() => injectAll(runAt.document_idle));
-    }
-  },
-
   /**
    * Checks that all parent frames for the given withdow either have the
    * same add-on ID, or are special chrome-privileged documents such as
    * about:addons or developer tools panels.
    *
    * @param {Window} window
    *        The window to check.
    * @param {string} addonId
@@ -260,33 +229,16 @@ DocumentManager = {
       // 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);
     }
   },
-
-  // Helpers
-
-  * enumerateWindows(docShell) {
-    if (docShell) {
-      let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
-                                                 docShell.ENUMERATE_FORWARDS);
-
-      for (let docShell of XPCOMUtils.IterSimpleEnumerator(enum_, Ci.nsIDocShell)) {
-        yield docShell.domWindow;
-      }
-    } else {
-      for (let global of this.globals.keys()) {
-        yield* this.enumerateWindows(global.docShell);
-      }
-    }
-  },
 };
 
 ExtensionManager = {
   // WeakMap<WebExtensionPolicy, Map<string, WebExtensionContentScript>>
   registeredContentScripts: new DefaultWeakMap((policy) => new Map()),
 
   init() {
     MessageChannel.setupMessageManagers([Services.cpmm]);
@@ -365,17 +317,17 @@ ExtensionManager = {
   },
 
   initExtension(data) {
     if (typeof data === "string") {
       data = getData({id: data});
     }
     let policy = this.initExtensionPolicy(data);
 
-    DocumentManager.initExtension(policy);
+    policy.injectContentScripts();
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "Extension:Startup": {
         this.initExtension(data);
 
         Services.cpmm.sendAsyncMessage("Extension:StartupComplete");
@@ -492,18 +444,16 @@ ExtensionProcessScript.prototype = {
     }
   },
 
   preloadContentScript(contentScript) {
     contentScripts.get(contentScript).preload();
   },
 
   loadContentScript(contentScript, window) {
-    if (DocumentManager.globals.has(window.docShell.messageManager)) {
-      contentScripts.get(contentScript).injectInto(window);
-    }
+    return contentScripts.get(contentScript).injectInto(window);
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExtensionProcessScript]);
 
 DocumentManager.earlyInit();
 ExtensionManager.init();
--- a/toolkit/components/extensions/mozIExtensionProcessScript.idl
+++ b/toolkit/components/extensions/mozIExtensionProcessScript.idl
@@ -1,18 +1,20 @@
 /* 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 mozIDOMWindow;
 webidl Document;
+webidl WebExtensionContentScript;
 
 [scriptable,uuid(6b09dc51-6caa-4ca7-9d6d-30c87258a630)]
 interface mozIExtensionProcessScript : nsISupports
 {
   void preloadContentScript(in nsISupports contentScript);
 
-  void loadContentScript(in nsISupports contentScript, in mozIDOMWindowProxy window);
+  Promise loadContentScript(in WebExtensionContentScript contentScript,
+                            in mozIDOMWindow window);
 
   void initExtensionDocument(in nsISupports extension, in Document doc);
 };