diff --git a/dom/chrome-webidl/WebExtensionPolicy.webidl b/dom/chrome-webidl/WebExtensionPolicy.webidl index b65757c34620..07f12730ae6f 100644 --- a/dom/chrome-webidl/WebExtensionPolicy.webidl +++ b/dom/chrome-webidl/WebExtensionPolicy.webidl @@ -134,6 +134,12 @@ interface WebExtensionPolicy { [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. */ diff --git a/toolkit/components/extensions/ExtensionPolicyService.cpp b/toolkit/components/extensions/ExtensionPolicyService.cpp index 3ad8915c3f64..7a70d9867f95 100644 --- a/toolkit/components/extensions/ExtensionPolicyService.cpp +++ b/toolkit/components/extensions/ExtensionPolicyService.cpp @@ -12,24 +12,34 @@ #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:;" @@ -251,6 +261,7 @@ 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); } @@ -261,6 +272,7 @@ 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"); } @@ -284,6 +296,129 @@ ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const if (chan) { CheckRequest(chan); } + } else if (!strcmp(aTopic, "tab-content-frameloader-created")) { + RefPtr 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 mm = do_QueryObject(aEvent->GetTarget()); + MOZ_ASSERT(mm); + if (mm) { + mMessageManagers.RemoveEntry(mm); + } + return NS_OK; +} + +nsresult +ForEachDocShell(nsIDocShell* aDocShell, + const std::function& aCallback) +{ + nsCOMPtr iter; + MOZ_TRY(aDocShell->GetDocShellEnumerator(nsIDocShell::typeContent, + nsIDocShell::ENUMERATE_FORWARDS, + getter_AddRefs(iter))); + + for (nsIDocShell& docShell : SimpleEnumerator(iter)) { + MOZ_TRY(aCallback(&docShell)); + } + return NS_OK; +} + + +already_AddRefed +ExtensionPolicyService::ExecuteContentScript(nsPIDOMWindowInner* aWindow, + WebExtensionContentScript& aScript) +{ + if (!aWindow->IsCurrentInnerWindow()) { + return nullptr; + } + + RefPtr promise; + ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise)); + return promise.forget(); +} + +RefPtr +ExtensionPolicyService::ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow, + const nsTArray>& aScripts) +{ + AutoTArray, 8> promises; + + for (auto& script : aScripts) { + promises.AppendElement(ExecuteContentScript(aWindow, *script)); + } + + RefPtr 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 docShell = mm->GetDocShell(IgnoreErrors()); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + + auto result = ForEachDocShell(docShell, [&](nsIDocShell* aDocShell) -> nsresult { + nsCOMPtr win = aDocShell->GetWindow(); + DocInfo docInfo(win); + + using RunAt = dom::ContentScriptRunAt; + using Scripts = AutoTArray, 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 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; } @@ -320,6 +455,12 @@ ExtensionPolicyService::CheckDocument(nsIDocument* aDocument) { nsCOMPtr win = aDocument->GetWindow(); if (win) { + nsIDocShell* docShell = win->GetDocShell(); + RefPtr mm = docShell->GetMessageManager(); + if (!mm || !mMessageManagers.Contains(mm)) { + return; + } + if (win->GetDocumentURI()) { CheckContentScripts(win.get(), false); } @@ -355,13 +496,24 @@ ExtensionPolicyService::CheckWindow(nsPIDOMWindowOuter* aWindow) return; } - CheckContentScripts(aWindow, false); + nsIDocShell* docShell = aWindow->GetDocShell(); + if (RefPtr mm = docShell->GetMessageManager()) { + if (mMessageManagers.Contains(mm)) { + CheckContentScripts(aWindow, false); + } + } } void ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload) { + nsCOMPtr win = aDocInfo.GetWindow()->GetCurrentInnerWindow(); + for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) { + if (!win->IsCurrentInnerWindow()) { + break; + } + RefPtr policy = iter.Data(); for (auto& script : policy->ContentScripts()) { @@ -369,7 +521,8 @@ ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPre if (aIsPreload) { ProcessScript().PreloadContentScript(script); } else { - ProcessScript().LoadContentScript(script, aDocInfo.GetWindow()); + RefPtr promise; + ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise)); } } } @@ -503,6 +656,7 @@ NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts, 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 diff --git a/toolkit/components/extensions/ExtensionPolicyService.h b/toolkit/components/extensions/ExtensionPolicyService.h index 1cf41df1537c..0dadc5b44871 100644 --- a/toolkit/components/extensions/ExtensionPolicyService.h +++ b/toolkit/components/extensions/ExtensionPolicyService.h @@ -13,22 +13,30 @@ #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; @@ -36,6 +44,7 @@ using extensions::WebExtensionPolicy; class ExtensionPolicyService final : public nsIAddonPolicyService , public nsIObserver + , public nsIDOMEventListener , public nsIMemoryReporter { public: @@ -44,6 +53,7 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIADDONPOLICYSERVICE NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER NS_DECL_NSIMEMORYREPORTER static ExtensionPolicyService& GetSingleton(); @@ -86,6 +96,8 @@ public: bool UseRemoteExtensions() const; bool IsExtensionProcess() const; + nsresult InjectContentScripts(WebExtensionPolicy* aExtension); + protected: virtual ~ExtensionPolicyService(); @@ -101,9 +113,19 @@ private: void CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload); + already_AddRefed + ExecuteContentScript(nsPIDOMWindowInner* aWindow, + extensions::WebExtensionContentScript& aScript); + + RefPtr + ExecuteContentScripts(JSContext* aCx, nsPIDOMWindowInner* aWindow, + const nsTArray>& aScripts); + nsRefPtrHashtable, WebExtensionPolicy> mExtensions; nsRefPtrHashtable mExtensionHosts; + nsTHashtable> mMessageManagers; + nsRefPtrHashtable, extensions::DocumentObserver> mObservers; diff --git a/toolkit/components/extensions/WebExtensionPolicy.cpp b/toolkit/components/extensions/WebExtensionPolicy.cpp index 1d63e073d8be..a992d200ba79 100644 --- a/toolkit/components/extensions/WebExtensionPolicy.cpp +++ b/toolkit/components/extensions/WebExtensionPolicy.cpp @@ -258,6 +258,15 @@ WebExtensionPolicy::UnregisterContentScript(const WebExtensionContentScript& scr 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) { diff --git a/toolkit/components/extensions/WebExtensionPolicy.h b/toolkit/components/extensions/WebExtensionPolicy.h index 3265036740a4..15edc9854897 100644 --- a/toolkit/components/extensions/WebExtensionPolicy.h +++ b/toolkit/components/extensions/WebExtensionPolicy.h @@ -67,6 +67,8 @@ public: 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)) && diff --git a/toolkit/components/extensions/extension-process-script.js b/toolkit/components/extensions/extension-process-script.js index 2fadc239ff67..b8e82914edeb 100644 --- a/toolkit/components/extensions/extension-process-script.js +++ b/toolkit/components/extensions/extension-process-script.js @@ -172,7 +172,9 @@ DocumentManager = { // 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 @@ -185,41 +187,8 @@ DocumentManager = { }); }, - 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 @@ -265,23 +234,6 @@ DocumentManager = { 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 = { @@ -370,7 +322,7 @@ ExtensionManager = { } let policy = this.initExtensionPolicy(data); - DocumentManager.initExtension(policy); + policy.injectContentScripts(); }, receiveMessage({name, data}) { @@ -497,9 +449,7 @@ ExtensionProcessScript.prototype = { }, loadContentScript(contentScript, window) { - if (DocumentManager.globals.has(window.docShell.messageManager)) { - contentScripts.get(contentScript).injectInto(window); - } + return contentScripts.get(contentScript).injectInto(window); }, }; diff --git a/toolkit/components/extensions/mozIExtensionProcessScript.idl b/toolkit/components/extensions/mozIExtensionProcessScript.idl index 968d997decfc..b7fc8387b262 100644 --- a/toolkit/components/extensions/mozIExtensionProcessScript.idl +++ b/toolkit/components/extensions/mozIExtensionProcessScript.idl @@ -4,15 +4,17 @@ #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); };