Bug 1708238 - Stop relying on messagemanager in ExtensionPolicyService r=robwu,geckoview-reviewers,agi
authorTomislav Jovanovic <tomica@gmail.com>
Mon, 10 May 2021 16:55:09 +0000
changeset 579275 9c92eba1dfa08544829d8564525604a0784ee589
parent 579274 b981fd4a5f48a947b2d1b90dfb9ae340b1650e99
child 579276 4d24540ca38d21b37793b925e52127a8d0abe369
push id142883
push usertjovanovic@mozilla.com
push dateMon, 10 May 2021 16:57:30 +0000
treeherderautoland@9c92eba1dfa0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobwu, geckoview-reviewers, agi
bugs1708238
milestone90.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 1708238 - Stop relying on messagemanager in ExtensionPolicyService r=robwu,geckoview-reviewers,agi Patch mostly by Nika Layzell; test, some tweaks (and all bugs) by me. Differential Revision: https://phabricator.services.mozilla.com/D114060
mobile/android/chrome/geckoview/geckoview.js
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/ExtensionPolicyService.cpp
toolkit/components/extensions/ExtensionPolicyService.h
toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html
toolkit/mozapps/extensions/content/aboutaddons.js
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -495,16 +495,17 @@ function createBrowser() {
 
   browser.setAttribute("nodefaultsrc", "true");
   browser.setAttribute("type", "content");
   browser.setAttribute("primary", "true");
   browser.setAttribute("flex", "1");
   browser.setAttribute("maychangeremoteness", "true");
   browser.setAttribute("remote", "true");
   browser.setAttribute("remoteType", E10SUtils.DEFAULT_REMOTE_TYPE);
+  browser.setAttribute("messagemanagergroup", "browsers");
 
   return browser;
 }
 
 function InitLater(fn, object, name) {
   return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
 }
 
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -1167,16 +1167,17 @@ class HiddenXULWindow {
 
     await this.waitInitialized;
 
     const chromeDoc = this.chromeDocument;
 
     const browser = chromeDoc.createXULElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
+    browser.setAttribute("messagemanagergroup", "webext-browsers");
 
     for (const [name, value] of Object.entries(xulAttributes)) {
       if (value != null) {
         browser.setAttribute(name, value);
       }
     }
 
     let awaitFrameLoader = Promise.resolve();
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -10,45 +10,43 @@
 
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
 #include "mozilla/SimpleEnumerator.h"
 #include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.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 "nsDocShell.h"
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
-#include "nsIDocShell.h"
 #include "mozilla/dom/Document.h"
 #include "nsGlobalWindowOuter.h"
 #include "nsILoadInfo.h"
 #include "nsIXULRuntime.h"
 #include "nsImportModule.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 
 namespace mozilla {
 
 using namespace extensions;
 
 using dom::AutoJSAPI;
-using dom::ContentFrameMessageManager;
 using dom::Document;
 using dom::Promise;
 
 #define DEFAULT_CSP_PREF \
   "extensions.webextensions.default-content-security-policy"
 #define DEFAULT_DEFAULT_CSP "script-src 'self'; object-src 'self';"
 
 #define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
@@ -202,28 +200,26 @@ ExtensionPolicyService::CollectReports(n
 }
 
 /*****************************************************************************
  * Content script management
  *****************************************************************************/
 
 void ExtensionPolicyService::RegisterObservers() {
   mObs->AddObserver(this, kDocElementInserted, false);
-  mObs->AddObserver(this, "tab-content-frameloader-created", false);
   if (XRE_IsContentProcess()) {
     mObs->AddObserver(this, "http-on-opening-request", false);
     mObs->AddObserver(this, "document-on-opening-request", false);
   }
 
   Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF);
 }
 
 void ExtensionPolicyService::UnregisterObservers() {
   mObs->RemoveObserver(this, kDocElementInserted);
-  mObs->RemoveObserver(this, "tab-content-frameloader-created");
   if (XRE_IsContentProcess()) {
     mObs->RemoveObserver(this, "http-on-opening-request");
     mObs->RemoveObserver(this, "document-on-opening-request");
   }
 
   Preferences::RemoveObserver(this, DEFAULT_CSP_PREF);
 }
 
@@ -236,55 +232,26 @@ nsresult ExtensionPolicyService::Observe
       CheckDocument(doc);
     }
   } else if (!strcmp(aTopic, "http-on-opening-request") ||
              !strcmp(aTopic, "document-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.Insert(mm);
-
-    mm->AddSystemEventListener(u"unload"_ns, this, false, false);
   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     const nsCString converted = NS_ConvertUTF16toUTF8(aData);
     const char* pref = converted.get();
     if (!strcmp(pref, DEFAULT_CSP_PREF)) {
       mDefaultCSP.SetIsVoid(true);
     }
   }
   return NS_OK;
 }
 
-nsresult ExtensionPolicyService::HandleEvent(dom::Event* aEvent) {
-  RefPtr<ContentFrameMessageManager> mm = do_QueryObject(aEvent->GetTarget());
-  MOZ_ASSERT(mm);
-  if (mm) {
-    mMessageManagers.Remove(mm);
-  }
-  return NS_OK;
-}
-
-nsresult ForEachDocShell(
-    nsIDocShell* aDocShell,
-    const std::function<nsresult(nsIDocShell*)>& aCallback) {
-  nsTArray<RefPtr<nsIDocShell>> docShells;
-  MOZ_TRY(aDocShell->GetAllDocShellsInSubtree(
-      nsIDocShell::typeContent, nsIDocShell::ENUMERATE_FORWARDS, docShells));
-
-  for (auto& docShell : docShells) {
-    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));
@@ -302,78 +269,98 @@ RefPtr<Promise> ExtensionPolicyService::
     }
   }
 
   RefPtr<Promise> promise = Promise::All(aCx, promises, IgnoreErrors());
   MOZ_RELEASE_ASSERT(promise);
   return promise;
 }
 
+// Use browser's MessageManagerGroup to decide if we care about it, to inject
+// extension APIs or content scripts.  Tabs use "browsers", and all custom
+// extension browsers use "webext-browsers", including popups & sidebars,
+// background & options pages, and xpcshell tests.
+static bool IsTabOrExtensionBrowser(dom::BrowsingContext* aBC) {
+  const auto& group = aBC->Top()->GetMessageManagerGroup();
+  return group == u"browsers"_ns || group == u"webext-browsers"_ns;
+}
+
+static nsTArray<RefPtr<dom::BrowsingContext>> GetAllInProcessContentBCs() {
+  nsTArray<RefPtr<dom::BrowsingContext>> contentBCs;
+  nsTArray<RefPtr<dom::BrowsingContextGroup>> groups;
+  dom::BrowsingContextGroup::GetAllGroups(groups);
+  for (const auto& group : groups) {
+    for (const auto& toplevel : group->Toplevels()) {
+      if (!toplevel->IsContent() || toplevel->IsDiscarded() ||
+          !IsTabOrExtensionBrowser(toplevel)) {
+        continue;
+      }
+
+      toplevel->PreOrderWalk([&](dom::BrowsingContext* aContext) {
+        contentBCs.AppendElement(aContext);
+      });
+    }
+  }
+  return contentBCs;
+}
+
 nsresult ExtensionPolicyService::InjectContentScripts(
     WebExtensionPolicy* aExtension) {
   AutoJSAPI jsapi;
   MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
 
-  for (ContentFrameMessageManager* mm : mMessageManagers) {
-    nsCOMPtr<nsIDocShell> docShell = mm->GetDocShell(IgnoreErrors());
-    NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+  auto contentBCs = GetAllInProcessContentBCs();
+  for (dom::BrowsingContext* bc : contentBCs) {
+    auto* win = bc->GetDOMWindow();
 
-    auto result =
-        ForEachDocShell(docShell, [&](nsIDocShell* aDocShell) -> nsresult {
-          nsCOMPtr<nsPIDOMWindowOuter> win = aDocShell->GetWindow();
-          if (!win->GetDocumentURI()) {
-            return NS_OK;
-          }
-          DocInfo docInfo(win);
+    if (bc->Top()->IsDiscarded() || !win || !win->GetDocumentURI()) {
+      continue;
+    }
+    DocInfo docInfo(win);
 
-          using RunAt = dom::ContentScriptRunAt;
-          namespace RunAtValues = dom::ContentScriptRunAtValues;
-          using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
-
-          Scripts scripts[RunAtValues::Count];
+    using RunAt = dom::ContentScriptRunAt;
+    namespace RunAtValues = dom::ContentScriptRunAtValues;
+    using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
 
-          auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
-            static_assert(sizeof(aRunAt) == 1, "Our cast is wrong");
-            return std::move(scripts[uint8_t(aRunAt)]);
-          };
+    Scripts scripts[RunAtValues::Count];
 
-          for (const auto& script : aExtension->ContentScripts()) {
-            if (script->Matches(docInfo)) {
-              GetScripts(script->RunAt()).AppendElement(script);
-            }
-          }
+    auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
+      static_assert(sizeof(aRunAt) == 1, "Our cast is wrong");
+      return std::move(scripts[uint8_t(aRunAt)]);
+    };
 
-          nsCOMPtr<nsPIDOMWindowInner> inner = win->GetCurrentInnerWindow();
+    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);
+    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;
 }
 
 // Checks a request for matching content scripts, and begins pre-loading them
 // if necessary.
 void ExtensionPolicyService::CheckRequest(nsIChannel* aChannel) {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
@@ -433,30 +420,17 @@ static bool CheckParentFrames(nsPIDOMWin
 }
 
 // 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(Document* aDocument) {
   nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
   if (win) {
-    nsIDocShell* docShell = win->GetDocShell();
-    RefPtr<ContentFrameMessageManager> mm = docShell->GetMessageManager();
-    nsString group = win->GetBrowsingContext()->Top()->GetMessageManagerGroup();
-
-    // Currently, we use frame scripts to select specific kinds of browsers
-    // where we want to run content scripts.
-    if ((!mm || !mMessageManagers.Contains(mm)) &&
-        // With Fission, OOP iframes don't have a frame message manager, so we
-        // use the browser's MessageManagerGroup attribute to decide if content
-        // scripts should run.  The "browsers" group includes iframes from tabs,
-        // and the "webext-browsers" group includes custom browsers for
-        // extension popups/sidebars and xpcshell tests.
-        !group.EqualsLiteral("browsers") &&
-        !group.EqualsLiteral("webext-browsers")) {
+    if (!IsTabOrExtensionBrowser(win->GetBrowsingContext())) {
       return;
     }
 
     if (win->GetDocumentURI()) {
       CheckContentScripts(win.get(), false);
     }
 
     nsIPrincipal* principal = aDocument->NodePrincipal();
@@ -622,17 +596,16 @@ nsresult ExtensionPolicyService::Extensi
 }
 
 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,56 +8,52 @@
 
 #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 "nsTHashSet.h"
 
 class nsIChannel;
 class nsIObserverService;
 
 class nsIPIDOMWindowInner;
 class nsIPIDOMWindowOuter;
 
 namespace mozilla {
 namespace dom {
-class ContentFrameMessageManager;
 class Promise;
 }  // namespace dom
 namespace extensions {
 class DocInfo;
 class DocumentObserver;
 class WebExtensionContentScript;
 }  // namespace extensions
 
 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());
   }
 
@@ -109,18 +105,16 @@ class ExtensionPolicyService final : pub
 
   RefPtr<dom::Promise> ExecuteContentScripts(
       JSContext* aCx, nsPIDOMWindowInner* aWindow,
       const nsTArray<RefPtr<extensions::WebExtensionContentScript>>& aScripts);
 
   nsRefPtrHashtable<nsPtrHashKey<const nsAtom>, WebExtensionPolicy> mExtensions;
   nsRefPtrHashtable<nsCStringHashKey, WebExtensionPolicy> mExtensionHosts;
 
-  nsTHashSet<RefPtr<dom::ContentFrameMessageManager>> mMessageManagers;
-
   nsRefPtrHashtable<nsPtrHashKey<const extensions::DocumentObserver>,
                     extensions::DocumentObserver>
       mObservers;
 
   nsCOMPtr<nsIObserverService> mObs;
 
   nsString mDefaultCSP;
 };
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html
@@ -6,17 +6,17 @@
   <script src="head.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <script>
 "use strict";
 
 add_task(async function test_content_script_cross_origin_frame() {
 
-  const extension = ExtensionTestUtils.loadExtension({
+  const extensionData = {
     manifest: {
       content_scripts: [{
         matches: ["http://example.net/*/file_sample.html"],
         all_frames: true,
         js: ["cs.js"],
       }],
       permissions: ["http://example.net/"],
     },
@@ -79,22 +79,31 @@ add_task(async function test_content_scr
         });
         port.onDisconnect.addListener(() => {
           browser.test.assertEq(response, 21, "Got correct response");
           browser.test.notifyPass();
         });
         port.postMessage(7);
       },
     },
-  });
+  };
 
-  await extension.startup();
+  info("Load first extension");
+  let ext1 = ExtensionTestUtils.loadExtension(extensionData);
+  await ext1.startup();
 
+  info("Load a page, test content scripts in new frame with extension loaded");
   let base = "http://example.org/tests/toolkit/components/extensions/test";
   let win = window.open(`${base}/mochitest/file_with_xorigin_frame.html`);
 
-  await extension.awaitFinish();
+  await ext1.awaitFinish();
+  await ext1.unload();
+
+  info("Load second extension, test content scripts in existing frame");
+  let ext2 = ExtensionTestUtils.loadExtension(extensionData);
+  await ext2.startup();
+  await ext2.awaitFinish();
+
   win.close();
-
-  await extension.unload();
+  await ext2.unload();
 });
 
 </script>
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -2374,16 +2374,17 @@ class InlineOptionsBrowser extends HTMLE
     let { addon } = this;
     if (!addon) {
       throw new Error("addon required to create inline options");
     }
 
     let browser = document.createXULElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
+    browser.setAttribute("messagemanagergroup", "webext-browsers");
     browser.setAttribute("id", "addon-inline-options");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("forcemessagemanager", "true");
     browser.setAttribute("selectmenulist", "ContentSelectDropdown");
     browser.setAttribute("autocompletepopup", "PopupAutoComplete");
 
     // The outer about:addons document listens for key presses to focus
     // the search box when / is pressed.  But if we're focused inside an