From cded1a802fd1699d81a9ea30a59cbb9bc9363c63 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Mon, 27 May 2024 11:49:15 +0000 Subject: [PATCH] Bug 1897113 - Require match_origin_as_fallback for blob:-URLs r=rpl Differential Revision: https://phabricator.services.mozilla.com/D210638 --- modules/libpref/init/StaticPrefList.yaml | 8 + .../extensions/WebExtensionContentScript.h | 5 + .../extensions/WebExtensionPolicy.cpp | 28 +++- .../test/mochitest/mochitest-common.toml | 2 + .../test_ext_contentscript_about_blank.html | 14 +- .../test_ext_contentscript_blob.html | 144 ++++++++++++++++++ .../test_ext_contentscript_preloading.js | 10 +- xpcom/ds/StaticAtoms.py | 1 + 8 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_blob.html diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 43be8b4f7e7c..49fa662824a8 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -5080,6 +5080,14 @@ value: false mirror: always +# When true, content scripts of MV2 extensions can run in blob:-documents without +# requiring match_origin_as_fallback to be set, to revert bug 1897113. +# TODO bug 1899134: Remove this pref. +- name: extensions.script_blob_without_match_origin_as_fallback + type: bool + value: false + mirror: always + # Legacy behavior on filterResponse calls on intercepted sw script requests. - name: extensions.filterResponseServiceWorkerScript.disabled type: bool diff --git a/toolkit/components/extensions/WebExtensionContentScript.h b/toolkit/components/extensions/WebExtensionContentScript.h index 1deb2ee90fa8..c8bf615688c8 100644 --- a/toolkit/components/extensions/WebExtensionContentScript.h +++ b/toolkit/components/extensions/WebExtensionContentScript.h @@ -57,6 +57,10 @@ class MOZ_STACK_CLASS DocInfo final { // In all other cases, URL() is returned. const URLInfo& PrincipalURL() const; + // Whether match_origin_as_fallback must be set in order for PrincipalURL() + // to be eligible for matching the document. + bool RequiresMatchOriginAsFallback() const; + bool IsTopLevel() const; bool IsSameOriginWithTop() const; bool ShouldMatchActiveTabPermission() const; @@ -94,6 +98,7 @@ class MOZ_STACK_CLASS DocInfo final { const URLInfo mURL; mutable Maybe mPrincipalURL; + mutable Maybe mRequiresMatchOriginAsFallback; mutable Maybe mIsTopLevel; diff --git a/toolkit/components/extensions/WebExtensionPolicy.cpp b/toolkit/components/extensions/WebExtensionPolicy.cpp index a63f863fbe15..8dca237eb739 100644 --- a/toolkit/components/extensions/WebExtensionPolicy.cpp +++ b/toolkit/components/extensions/WebExtensionPolicy.cpp @@ -836,9 +836,19 @@ bool MozDocumentMatcher::Matches(const DocInfo& aDoc, // with a precursor may result in a match with the specific pattern. } - if (!mMatchOriginAsFallback && aDoc.Principal() && - aDoc.Principal()->GetIsNullPrincipal() && !aDoc.URL().IsNonOpaqueURL()) { - return false; + if (!mMatchOriginAsFallback && aDoc.RequiresMatchOriginAsFallback()) { + // TODO bug 1899134: We should unconditionally return false here. But we + // had accidental support for matching blob:-URLs (by the content + // principal's URL) for a long time, so we have a temporary pref to fall + // back to the original behavior if needed. + if (aDoc.URL().Scheme() != nsGkAtoms::blob || !mExtension || + mExtension->ManifestVersion() != 2 || + !StaticPrefs:: + extensions_script_blob_without_match_origin_as_fallback()) { + return false; + } + // Fall-through implies that we have a MV2 extension and a blob:-URL, with + // extensions.script_blob_without_match_origin_as_fallback set to true. } if (mRestricted && WebExtensionPolicy::IsRestrictedDoc(aDoc)) { @@ -1169,5 +1179,17 @@ const URLInfo& DocInfo::PrincipalURL() const { return mPrincipalURL.ref(); } +bool DocInfo::RequiresMatchOriginAsFallback() const { + if (mRequiresMatchOriginAsFallback.isNothing()) { + mRequiresMatchOriginAsFallback.emplace( + // Special-case blob:-URLs because their principal is indistinguishable + // from the principals that created them. + URL().Scheme() == nsGkAtoms::blob || + (Principal() && Principal()->GetIsNullPrincipal() && + !URL().IsNonOpaqueURL())); + } + return mRequiresMatchOriginAsFallback.ref(); +} + } // namespace extensions } // namespace mozilla diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.toml b/toolkit/components/extensions/test/mochitest/mochitest-common.toml index a1ed655726e8..78168dc83788 100644 --- a/toolkit/components/extensions/test/mochitest/mochitest-common.toml +++ b/toolkit/components/extensions/test/mochitest/mochitest-common.toml @@ -180,6 +180,8 @@ skip-if = [ "http2", ] +["test_ext_contentscript_blob.html"] + ["test_ext_contentscript_cache.html"] skip-if = [ "os == 'linux' && debug", diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html index 8227a749f7ee..277d42a99b9b 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html @@ -15,7 +15,7 @@ // Tests that match_about_blank matches at the expected URLs: // - about:blank and about:srcdoc (as documented) // - javascript:-URL (loaded in about:blank document) -// - blob: (not documented, not supported by Chrome, historical behavior) +// - blob: (not documented, not supported by Chrome, legacy behavior) add_task(async function test_contentscript_about_blank() { const manifest = { content_scripts: [ @@ -154,19 +154,21 @@ add_task(async function test_contentscript_about_blank() { info(`Opening blob:-URL: ${blobUrl} from ${win.document.URL}`); let blobWin = window.open(blobUrl); + // blob:-URLs should not have content scripts because + // match_origin_as_fallback is not set. await Promise.all([ - extension.awaitMessage("all:" + blobUrl), extension.awaitMessage("all:about:blank"), extension.awaitMessage("all:about:srcdoc"), - extension.awaitMessage("mochi_without:" + blobUrl), - extension.awaitMessage("mochi_with:" + blobUrl), extension.awaitMessage("mochi_with:about:blank"), extension.awaitMessage("mochi_with:about:srcdoc"), ]); - is(count, 7, "exactly 7 more scripts ran for blob:-URL"); + is(count, 4, "exactly 4 more scripts ran for blob:-URL"); + count = 0; + + // Test coverage for execution on blob:-URLs is at + // toolkit/components/extensions/test/mochitest/test_ext_contentscript_blob.html blobWin.close(); - win.close(); await extension.unload(); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_blob.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_blob.html new file mode 100644 index 000000000000..f9eba501ca79 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_blob.html @@ -0,0 +1,144 @@ + + + + Test content scripts at blob:-URLs + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_preloading.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_preloading.js index b5515ffea306..7d2cdc259a92 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_preloading.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_preloading.js @@ -408,10 +408,14 @@ add_task(async function test_no_preload_at_blob_url_iframe() { manifest: { content_scripts: [ { + // Note: match_origin_as_fallback is supposed to only work when + // "matches" has a wildcard path. In our implementation, blob:-URLs + // have a principal URL that may include a path rather than just the + // origin, so we can match blob:-URLs created from specific paths. + // This behavior is NOT documented, but relied upon for convenience + // here. matches: ["*://example.com/dummy?with_blob_url"], - // Note: we currently match at blob:-URLs even without - // match_origin_as_fallback (or even match_about_blank). In Chrome, - // blob:-URLs can only be matched with match_origin_as_fallback:true. + match_origin_as_fallback: true, all_frames: true, js: ["done.js"], run_at: "document_end", diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index 96699da0ed58..8fc867076e0a 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -2007,6 +2007,7 @@ STATIC_ATOMS = [ Atom("tabs", "tabs"), Atom("webRequestBlocking", "webRequestBlocking"), Atom("webRequestFilterResponse_serviceWorkerScript", "webRequestFilterResponse.serviceWorkerScript"), + Atom("blob", "blob"), Atom("http", "http"), Atom("https", "https"), Atom("view_source", "view-source"),