forked from mirrors/gecko-dev
Bug 1897113 - Require match_origin_as_fallback for blob:-URLs r=rpl
Differential Revision: https://phabricator.services.mozilla.com/D210638
This commit is contained in:
parent
634f9902ab
commit
cded1a802f
8 changed files with 200 additions and 12 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<const URLInfo> mPrincipalURL;
|
||||
mutable Maybe<bool> mRequiresMatchOriginAsFallback;
|
||||
|
||||
mutable Maybe<bool> mIsTopLevel;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ skip-if = [
|
|||
"http2",
|
||||
]
|
||||
|
||||
["test_ext_contentscript_blob.html"]
|
||||
|
||||
["test_ext_contentscript_cache.html"]
|
||||
skip-if = [
|
||||
"os == 'linux' && debug",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test content scripts at blob:-URLs</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script src="head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
function loadTestExtension({ manifest_version }) {
|
||||
return ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
name: "MV" + manifest_version,
|
||||
manifest_version,
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ["*://mochi.test/*"],
|
||||
// match_origin_as_fallback: false, // false by default
|
||||
js: ["moaf_false.js"],
|
||||
all_frames: true,
|
||||
run_at: "document_start",
|
||||
},
|
||||
{
|
||||
matches: ["*://mochi.test/*"],
|
||||
match_origin_as_fallback: true,
|
||||
js: ["moaf_true.js"],
|
||||
all_frames: true,
|
||||
run_at: "document_start",
|
||||
},
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"moaf_false.js": () => {
|
||||
if (location.protocol == "blob:") {
|
||||
const { name } = browser.runtime.getManifest();
|
||||
browser.test.log(`moaf_false.js: Ran ${name} at ${document.URL}`);
|
||||
browser.test.sendMessage(name + ":moaf_false:" + document.URL);
|
||||
}
|
||||
},
|
||||
"moaf_true.js": () => {
|
||||
if (location.protocol == "blob:") {
|
||||
const { name } = browser.runtime.getManifest();
|
||||
browser.test.log(`moaf_true.js: Ran ${name} at ${document.URL}`);
|
||||
browser.test.sendMessage(name + ":moaf_true:" + document.URL);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createBlobURL() {
|
||||
function blobScript() {
|
||||
window.onload = () => {
|
||||
console.log(`Web page ${document.URL} loaded at origin ${origin}`);
|
||||
parent.postMessage(document.URL, "*");
|
||||
};
|
||||
}
|
||||
const html = `<!DOCTYPE html><script>(${blobScript})()<\/script>`;
|
||||
return URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
||||
}
|
||||
|
||||
async function createFrameAndAwaitLoad(blobUrl, sandboxed) {
|
||||
let { promise, resolve } = Promise.withResolvers();
|
||||
let f = document.createElement("iframe");
|
||||
f.src = blobUrl;
|
||||
if (sandboxed) {
|
||||
f.sandbox = "allow-scripts";
|
||||
}
|
||||
|
||||
function onmessage(event) {
|
||||
if (event.source === f.contentWindow) {
|
||||
is(event.data, blobUrl, "Got message from frame");
|
||||
is(event.origin, sandboxed ? "null" : origin, "Frame has correct origin");
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
window.addEventListener("message", onmessage);
|
||||
document.body.append(f);
|
||||
await promise;
|
||||
window.removeEventListener("message", onmessage);
|
||||
f.remove();
|
||||
}
|
||||
|
||||
async function test_contentscript_at_blob(legacy) {
|
||||
// TODO bug 1899134: Drop the pref and legacy=true case.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.script_blob_without_match_origin_as_fallback", legacy]],
|
||||
});
|
||||
|
||||
const extension2 = loadTestExtension({ manifest_version: 2 });
|
||||
const extension3 = loadTestExtension({ manifest_version: 3 });
|
||||
|
||||
await extension2.startup();
|
||||
await extension3.startup();
|
||||
|
||||
const blobUrlSameOrigin = createBlobURL();
|
||||
info(`Expecting content scripts at blobUrlSameOrigin:${blobUrlSameOrigin}`);
|
||||
await createFrameAndAwaitLoad(blobUrlSameOrigin, /* sandboxed */ false);
|
||||
await Promise.all([
|
||||
await extension2.awaitMessage("MV2:moaf_true:" + blobUrlSameOrigin),
|
||||
await extension3.awaitMessage("MV3:moaf_true:" + blobUrlSameOrigin),
|
||||
]);
|
||||
if (legacy) {
|
||||
await extension2.awaitMessage("MV2:moaf_false:" + blobUrlSameOrigin);
|
||||
}
|
||||
// MV3:moaf_false should never be observed because match_origin_as_fallback
|
||||
// is required in order to execute content scripts in blob:-URLs.
|
||||
|
||||
const blobUrlNullOrigin = createBlobURL();
|
||||
info(`Expecting content scripts at blobUrlNullOrigin:${blobUrlNullOrigin}`);
|
||||
await createFrameAndAwaitLoad(blobUrlNullOrigin, /* sandboxed */ true);
|
||||
await Promise.all([
|
||||
await extension2.awaitMessage("MV2:moaf_true:" + blobUrlNullOrigin),
|
||||
await extension3.awaitMessage("MV3:moaf_true:" + blobUrlNullOrigin),
|
||||
]);
|
||||
if (legacy) {
|
||||
await extension2.awaitMessage("MV2:moaf_false:" + blobUrlNullOrigin);
|
||||
}
|
||||
|
||||
await extension2.unload();
|
||||
await extension3.unload();
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
|
||||
add_task(async function test_contentscript_at_blob_default() {
|
||||
await test_contentscript_at_blob(/* legacy */ false);
|
||||
});
|
||||
|
||||
// Exactly the same as test_contentscript_at_blob_default, except
|
||||
// manifest_version 2 also run at blob: when match_origin_as_fallback is false.
|
||||
add_task(async function test_contentscript_at_blob_legacy_behavior() {
|
||||
await test_contentscript_at_blob(/* legacy */ true);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue