зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1745819 - Require origin permission for content scripts in mv3 r=robwu
Differential Revision: https://phabricator.services.mozilla.com/D141557
This commit is contained in:
Родитель
64d31d44db
Коммит
0dec35cac7
|
@ -20,13 +20,6 @@ interface MozDocumentMatcher {
|
|||
*/
|
||||
boolean matchesURI(URI uri);
|
||||
|
||||
/**
|
||||
* Returns true if the the given URI and LoadInfo objects match.
|
||||
* This should be used to determine whether to begin pre-loading a content
|
||||
* script based on network events.
|
||||
*/
|
||||
boolean matchesLoadInfo(URI uri, LoadInfo loadInfo);
|
||||
|
||||
/**
|
||||
* Returns true if the given window matches. This should be used
|
||||
* to determine whether to run a script in a window at load time.
|
||||
|
@ -39,6 +32,14 @@ interface MozDocumentMatcher {
|
|||
[Constant]
|
||||
readonly attribute boolean allFrames;
|
||||
|
||||
/**
|
||||
* If we can't check extension has permissions to access the URI upfront,
|
||||
* set the flag to perform the origin check at runtime, upon matching.
|
||||
* This is always true in MV3, where host permissions are optional.
|
||||
*/
|
||||
[Constant]
|
||||
readonly attribute boolean checkPermissions;
|
||||
|
||||
/**
|
||||
* If true, this (misleadingly-named, but inherited from Chrome) attribute
|
||||
* causes us to match frames with URLs which inherit a principal that
|
||||
|
@ -102,6 +103,8 @@ interface MozDocumentMatcher {
|
|||
dictionary MozDocumentMatcherInit {
|
||||
boolean allFrames = false;
|
||||
|
||||
boolean checkPermissions = false;
|
||||
|
||||
sequence<OriginAttributesPatternDictionary>? originAttributesPatterns = null;
|
||||
|
||||
boolean matchAboutBlank = false;
|
||||
|
|
|
@ -115,10 +115,6 @@ class MozDocumentMatcher : public nsISupports, public nsWrapperCache {
|
|||
bool Matches(const DocInfo& aDoc) const;
|
||||
bool MatchesURI(const URLInfo& aURL) const;
|
||||
|
||||
bool MatchesLoadInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo) const {
|
||||
return Matches({aURL, aLoadInfo});
|
||||
}
|
||||
|
||||
bool MatchesWindowGlobal(dom::WindowGlobalChild& aWindow) const;
|
||||
|
||||
WebExtensionPolicy* GetExtension() { return mExtension; }
|
||||
|
@ -127,6 +123,7 @@ class MozDocumentMatcher : public nsISupports, public nsWrapperCache {
|
|||
const WebExtensionPolicy* Extension() const { return mExtension; }
|
||||
|
||||
bool AllFrames() const { return mAllFrames; }
|
||||
bool CheckPermissions() const { return mCheckPermissions; }
|
||||
bool MatchAboutBlank() const { return mMatchAboutBlank; }
|
||||
|
||||
MatchPatternSet* Matches() { return mMatches; }
|
||||
|
@ -173,6 +170,7 @@ class MozDocumentMatcher : public nsISupports, public nsWrapperCache {
|
|||
Nullable<MatchGlobSet> mExcludeGlobs;
|
||||
|
||||
bool mAllFrames;
|
||||
bool mCheckPermissions;
|
||||
Nullable<uint64_t> mFrameID;
|
||||
bool mMatchAboutBlank;
|
||||
Nullable<dom::Sequence<OriginAttributesPattern>> mOriginAttributesPatterns;
|
||||
|
|
|
@ -634,6 +634,7 @@ MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
|
|||
: mHasActiveTabPermission(aInit.mHasActiveTabPermission),
|
||||
mRestricted(aRestricted),
|
||||
mAllFrames(aInit.mAllFrames),
|
||||
mCheckPermissions(aInit.mCheckPermissions),
|
||||
mFrameID(aInit.mFrameID),
|
||||
mMatchAboutBlank(aInit.mMatchAboutBlank) {
|
||||
MatchPatternOptions options;
|
||||
|
@ -690,6 +691,11 @@ WebExtensionContentScript::WebExtensionContentScript(
|
|||
mCssPaths.Assign(aInit.mCssPaths);
|
||||
mJsPaths.Assign(aInit.mJsPaths);
|
||||
mExtension = &aExtension;
|
||||
|
||||
// Origin permissions are optional in mv3, so always check them at runtime.
|
||||
if (mExtension->ManifestVersion() >= 3) {
|
||||
mCheckPermissions = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
|
||||
|
@ -738,7 +744,7 @@ bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
|
||||
if (mRestricted && mExtension && mExtension->IsRestrictedDoc(aDoc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -752,6 +758,8 @@ bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
|
|||
}
|
||||
|
||||
bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL) const {
|
||||
MOZ_ASSERT(!mRestricted && !mCheckPermissions || mExtension);
|
||||
|
||||
if (!mMatches->Matches(aURL)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -772,6 +780,11 @@ bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mCheckPermissions &&
|
||||
!mExtension->CanAccessURI(aURL, false, false, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,10 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
|
|||
// Used in `file_contains_iframe.html`
|
||||
"*://example.org/",
|
||||
],
|
||||
granted_host_permissions: true,
|
||||
...manifestProps,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
...otherProps,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,18 +7,26 @@ const { newURI } = Services.io;
|
|||
const server = createHttpServer({ hosts: ["example.com"] });
|
||||
server.registerDirectory("/data/", do_get_file("data"));
|
||||
|
||||
let policy = new WebExtensionPolicy({
|
||||
id: "foo@bar.baz",
|
||||
mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
|
||||
baseURL: "file:///foo",
|
||||
async function test_url_matching({
|
||||
manifestVersion = 2,
|
||||
allowedOrigins = [],
|
||||
checkPermissions,
|
||||
expectMatches,
|
||||
}) {
|
||||
let policy = new WebExtensionPolicy({
|
||||
id: "foo@bar.baz",
|
||||
mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
|
||||
baseURL: "file:///foo",
|
||||
|
||||
allowedOrigins: new MatchPatternSet([]),
|
||||
localizeCallback() {},
|
||||
});
|
||||
manifestVersion,
|
||||
allowedOrigins: new MatchPatternSet(allowedOrigins),
|
||||
localizeCallback() {},
|
||||
});
|
||||
|
||||
add_task(async function test_WebExtensinonContentScript_url_matching() {
|
||||
let contentScript = new WebExtensionContentScript(policy, {
|
||||
matches: new MatchPatternSet(["http://foo.com/bar", "*://bar.com/baz/*"]),
|
||||
checkPermissions,
|
||||
|
||||
matches: new MatchPatternSet(["http://*.foo.com/bar", "*://bar.com/baz/*"]),
|
||||
|
||||
excludeMatches: new MatchPatternSet(["*://bar.com/baz/quux"]),
|
||||
|
||||
|
@ -29,14 +37,16 @@ add_task(async function test_WebExtensinonContentScript_url_matching() {
|
|||
excludeGlobs: ["*glorg*"].map(glob => new MatchGlob(glob)),
|
||||
});
|
||||
|
||||
ok(
|
||||
contentScript.matchesURI(newURI("http://foo.com/bar")),
|
||||
"Simple matches include should match"
|
||||
equal(
|
||||
expectMatches,
|
||||
contentScript.matchesURI(newURI("http://www.foo.com/bar")),
|
||||
`Simple matches include should ${expectMatches ? "" : "not "} match.`
|
||||
);
|
||||
|
||||
ok(
|
||||
equal(
|
||||
expectMatches,
|
||||
contentScript.matchesURI(newURI("https://bar.com/baz/xflergx")),
|
||||
"Simple matches include should match"
|
||||
`Simple matches include should ${expectMatches ? "" : "not "} match.`
|
||||
);
|
||||
|
||||
ok(
|
||||
|
@ -53,28 +63,112 @@ add_task(async function test_WebExtensinonContentScript_url_matching() {
|
|||
!contentScript.matchesURI(newURI("https://bar.com/baz/xflergxglorgx")),
|
||||
"Excluded match glob should not match"
|
||||
);
|
||||
});
|
||||
|
||||
async function loadURL(url) {
|
||||
let requests = new Map();
|
||||
|
||||
function requestObserver(request) {
|
||||
request.QueryInterface(Ci.nsIChannel);
|
||||
if (request.isDocument) {
|
||||
requests.set(request.name, request);
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.addObserver(requestObserver, "http-on-examine-response");
|
||||
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(url);
|
||||
|
||||
Services.obs.removeObserver(requestObserver, "http-on-examine-response");
|
||||
|
||||
return { contentPage, requests };
|
||||
}
|
||||
|
||||
add_task(async function test_WebExtensinonContentScript_frame_matching() {
|
||||
add_task(function test_WebExtensionContentScript_urls_mv2() {
|
||||
return test_url_matching({ manifestVersion: 2, expectMatches: true });
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_urls_mv2_checkPermissions() {
|
||||
return test_url_matching({
|
||||
manifestVersion: 2,
|
||||
checkPermissions: true,
|
||||
expectMatches: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_urls_mv2_with_permissions() {
|
||||
return test_url_matching({
|
||||
manifestVersion: 2,
|
||||
checkPermissions: true,
|
||||
allowedOrigins: ["<all_urls>"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_urls_mv3() {
|
||||
// checkPermissions ignored here because it's forced for MV3.
|
||||
return test_url_matching({
|
||||
manifestVersion: 3,
|
||||
checkPermissions: false,
|
||||
expectMatches: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_mv3_all_urls() {
|
||||
return test_url_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["<all_urls>"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_mv3_wildcards() {
|
||||
return test_url_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["*://*.foo.com/*", "*://*.bar.com/*"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_mv3_specific() {
|
||||
return test_url_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["http://www.foo.com/*", "https://bar.com/*"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_restricted() {
|
||||
let tests = [
|
||||
{
|
||||
manifestVersion: 2,
|
||||
permissions: [],
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
manifestVersion: 2,
|
||||
permissions: ["mozillaAddons"],
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
manifestVersion: 3,
|
||||
permissions: [],
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
manifestVersion: 3,
|
||||
permissions: ["mozillaAddons"],
|
||||
expect: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (let { manifestVersion, permissions, expect } of tests) {
|
||||
let policy = new WebExtensionPolicy({
|
||||
id: "foo@bar.baz",
|
||||
mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
|
||||
baseURL: "file:///foo",
|
||||
|
||||
manifestVersion,
|
||||
permissions,
|
||||
allowedOrigins: new MatchPatternSet(["<all_urls>"]),
|
||||
localizeCallback() {},
|
||||
});
|
||||
let contentScript = new WebExtensionContentScript(policy, {
|
||||
checkPermissions: true,
|
||||
matches: new MatchPatternSet(["<all_urls>"]),
|
||||
});
|
||||
|
||||
// AMO is on the extensions.webextensions.restrictedDomains list.
|
||||
equal(
|
||||
expect,
|
||||
contentScript.matchesURI(newURI("https://addons.mozilla.org/foo")),
|
||||
`Expect extension with [${permissions}] to ${expect ? "" : "not"} match`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function test_frame_matching(meta) {
|
||||
if (AppConstants.platform == "linux") {
|
||||
// The windowless browser currently does not load correctly on Linux on
|
||||
// infra.
|
||||
|
@ -89,7 +183,7 @@ add_task(async function test_WebExtensinonContentScript_frame_matching() {
|
|||
aboutBlank: "about:blank",
|
||||
};
|
||||
|
||||
let { contentPage, requests } = await loadURL(urls.topLevel);
|
||||
let contentPage = await ExtensionTestUtils.loadContentPage(urls.topLevel);
|
||||
|
||||
let tests = [
|
||||
{
|
||||
|
@ -149,7 +243,9 @@ add_task(async function test_WebExtensinonContentScript_frame_matching() {
|
|||
];
|
||||
|
||||
// matchesWindowGlobal tests against content frames
|
||||
await contentPage.spawn({ tests, urls }, args => {
|
||||
await contentPage.spawn({ tests, urls, meta }, args => {
|
||||
let { manifestVersion = 2, allowedOrigins = [], expectMatches } = args.meta;
|
||||
|
||||
this.windows = new Map();
|
||||
this.windows.set(this.content.location.href, this.content);
|
||||
for (let c of Array.from(this.content.frames)) {
|
||||
|
@ -160,7 +256,8 @@ add_task(async function test_WebExtensinonContentScript_frame_matching() {
|
|||
mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2",
|
||||
baseURL: "file:///foo",
|
||||
|
||||
allowedOrigins: new MatchPatternSet([]),
|
||||
manifestVersion,
|
||||
allowedOrigins: new MatchPatternSet(allowedOrigins),
|
||||
localizeCallback() {},
|
||||
});
|
||||
|
||||
|
@ -175,35 +272,50 @@ add_task(async function test_WebExtensinonContentScript_frame_matching() {
|
|||
let wgc = this.windows.get(url).windowGlobalChild;
|
||||
Assert.equal(
|
||||
test.script.matchesWindowGlobal(wgc),
|
||||
test[frame],
|
||||
test[frame] && expectMatches,
|
||||
`Script ${i} ${should} match the ${frame} frame`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Parent tests against loadInfo
|
||||
tests = tests.map(t => {
|
||||
t.contentScript.matches = new MatchPatternSet(t.matches);
|
||||
t.script = new WebExtensionContentScript(policy, t.contentScript);
|
||||
return t;
|
||||
});
|
||||
|
||||
for (let [i, test] of tests.entries()) {
|
||||
for (let [frame, url] of Object.entries(urls)) {
|
||||
let should = test[frame] ? "should" : "should not";
|
||||
|
||||
if (url.startsWith("http")) {
|
||||
let request = requests.get(url);
|
||||
|
||||
equal(
|
||||
test.script.matchesLoadInfo(request.URI, request.loadInfo),
|
||||
test[frame],
|
||||
`Script ${i} ${should} match the request LoadInfo for ${frame} frame`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await contentPage.close();
|
||||
}
|
||||
|
||||
add_task(function test_WebExtensionContentScript_frames_mv2() {
|
||||
return test_frame_matching({
|
||||
manifestVersion: 2,
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_frames_mv3() {
|
||||
return test_frame_matching({
|
||||
manifestVersion: 3,
|
||||
expectMatches: false,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_frames_mv3_all_urls() {
|
||||
return test_frame_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["<all_urls>"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_frames_mv3_wildcards() {
|
||||
return test_frame_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["*://*.example.com/*"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_WebExtensionContentScript_frames_mv3_specific() {
|
||||
return test_frame_matching({
|
||||
manifestVersion: 3,
|
||||
allowedOrigins: ["http://example.com/*"],
|
||||
expectMatches: true,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ const makeExtension = ({ background, manifest }) => {
|
|||
permissions:
|
||||
manifest.manifest_version === 3 ? ["scripting"] : ["http://*/*/*.html"],
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
background,
|
||||
files: {
|
||||
"script.js": () => {
|
||||
|
@ -132,6 +133,8 @@ add_task(
|
|||
let extension = makeExtension({
|
||||
manifest: {
|
||||
manifest_version: 3,
|
||||
host_permissions: ["<all_urls>"],
|
||||
granted_host_permissions: true,
|
||||
},
|
||||
async background() {
|
||||
const script = {
|
||||
|
|
|
@ -1344,8 +1344,12 @@ add_task(async function test_extension_contentscript_csp() {
|
|||
manifest: {
|
||||
...EXTENSION_DATA.manifest,
|
||||
manifest_version: 3,
|
||||
host_permissions: ["http://example.com/*"],
|
||||
granted_host_permissions: true,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
};
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(data);
|
||||
await extension.startup();
|
||||
|
||||
|
|
|
@ -16,8 +16,11 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
|
|||
manifest: {
|
||||
manifest_version: 3,
|
||||
permissions: ["scripting"],
|
||||
host_permissions: ["http://localhost/*"],
|
||||
granted_host_permissions: true,
|
||||
...manifestProps,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
...otherProps,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -16,8 +16,11 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
|
|||
manifest: {
|
||||
manifest_version: 3,
|
||||
permissions: ["scripting"],
|
||||
host_permissions: ["http://localhost/*"],
|
||||
granted_host_permissions: true,
|
||||
...manifestProps,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
...otherProps,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -15,8 +15,11 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
|
|||
manifest: {
|
||||
manifest_version: 3,
|
||||
permissions: ["scripting"],
|
||||
host_permissions: ["<all_urls>"],
|
||||
granted_host_permissions: true,
|
||||
...manifestProps,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
...otherProps,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -16,8 +16,11 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
|
|||
manifest: {
|
||||
manifest_version: 3,
|
||||
permissions: ["scripting"],
|
||||
host_permissions: ["http://localhost/*"],
|
||||
granted_host_permissions: true,
|
||||
...manifestProps,
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
...otherProps,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -78,6 +78,8 @@ add_task(async function test_web_accessible_resources() {
|
|||
run_at: "document_idle",
|
||||
},
|
||||
],
|
||||
host_permissions: ["http://example.com/*", "http://example.org/*"],
|
||||
granted_host_permissions: true,
|
||||
|
||||
web_accessible_resources: [
|
||||
{
|
||||
|
@ -86,6 +88,7 @@ add_task(async function test_web_accessible_resources() {
|
|||
},
|
||||
],
|
||||
},
|
||||
temporarilyInstalled: true,
|
||||
|
||||
files: {
|
||||
"content_script.js": contentScript,
|
||||
|
|
Загрузка…
Ссылка в новой задаче