Bug 1470651 - Support cookieStoreId option in contentScripts.register r=rpl,robwu

Differential Revision: https://phabricator.services.mozilla.com/D124537
This commit is contained in:
Richa Sharma 2021-12-13 12:30:26 +00:00
Родитель ddfe25fb93
Коммит fca992af50
6 изменённых файлов: 312 добавлений и 1 удалений

Просмотреть файл

@ -86,6 +86,12 @@ interface MozDocumentMatcher {
[Cached, Constant, Frozen]
readonly attribute sequence<MatchGlob>? excludeGlobs;
/**
* The originAttributesPattern for which this script should be enabled for.
*/
[Constant, Throws]
readonly attribute any originAttributesPatterns;
/**
* The policy object for the extension that this matcher belongs to.
*/
@ -96,6 +102,8 @@ interface MozDocumentMatcher {
dictionary MozDocumentMatcherInit {
boolean allFrames = false;
sequence<OriginAttributesPatternDictionary>? originAttributesPatterns = null;
boolean matchAboutBlank = false;
unsigned long long? frameID = null;

Просмотреть файл

@ -144,6 +144,10 @@ class MozDocumentMatcher : public nsISupports, public nsWrapperCache {
Nullable<uint64_t> GetFrameID() const { return mFrameID; }
void GetOriginAttributesPatterns(JSContext* aCx,
JS::MutableHandle<JS::Value> aVal,
ErrorResult& aError) const;
WebExtensionPolicy* GetParentObject() const { return mExtension; }
virtual JSObject* WrapObject(JSContext* aCx,
JS::HandleObject aGivenProto) override;
@ -171,6 +175,7 @@ class MozDocumentMatcher : public nsISupports, public nsWrapperCache {
bool mAllFrames;
Nullable<uint64_t> mFrameID;
bool mMatchAboutBlank;
Nullable<dom::Sequence<OriginAttributesPattern>> mOriginAttributesPatterns;
private:
template <typename T, typename U>

Просмотреть файл

@ -667,6 +667,17 @@ MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
return;
}
}
if (!aInit.mOriginAttributesPatterns.IsNull()) {
Sequence<OriginAttributesPattern>& arr =
mOriginAttributesPatterns.SetValue();
for (const auto& pattern : aInit.mOriginAttributesPatterns.Value()) {
if (!arr.AppendElement(OriginAttributesPattern(pattern), fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
}
}
WebExtensionContentScript::WebExtensionContentScript(
@ -698,6 +709,21 @@ bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
return false;
}
if (loadContext && !mOriginAttributesPatterns.IsNull()) {
OriginAttributes docShellAttrs;
loadContext->GetOriginAttributes(docShellAttrs);
bool patternMatch = false;
for (const auto& pattern : mOriginAttributesPatterns.Value()) {
if (pattern.Matches(docShellAttrs)) {
patternMatch = true;
break;
}
}
if (!patternMatch) {
return false;
}
}
if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
return false;
}
@ -760,6 +786,14 @@ bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow) const {
return Matches(inner->GetOuterWindow());
}
void MozDocumentMatcher::GetOriginAttributesPatterns(
JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
ErrorResult& aError) const {
if (!ToJSValue(aCx, mOriginAttributesPatterns, aVal)) {
aError.NoteJSContextException(aCx);
}
}
JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx,
JS::HandleObject aGivenProto) {
return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto);

Просмотреть файл

@ -15,6 +15,30 @@ var { ExtensionUtils } = ChromeUtils.import(
var { ExtensionError, getUniqueId } = ExtensionUtils;
function getOriginAttributesPatternForCookieStoreId(cookieStoreId) {
if (isDefaultCookieStoreId(cookieStoreId)) {
return {
userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
privateBrowsingId:
Ci.nsIScriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID,
};
}
if (isPrivateCookieStoreId(cookieStoreId)) {
return {
userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
privateBrowsingId: 1,
};
}
if (isContainerCookieStoreId(cookieStoreId)) {
let userContextId = getContainerForCookieStoreId(cookieStoreId);
if (userContextId !== null) {
return { userContextId };
}
}
throw new ExtensionError("Invalid cookieStoreId");
}
/**
* Represents (in the main browser process) a content script registered
* programmatically (instead of being included in the addon manifest).
@ -74,8 +98,18 @@ class ContentScriptParent {
runAt: details.runAt || "document_idle",
jsPaths: [],
cssPaths: [],
originAttributesPatterns: null,
};
if (details.cookieStoreId != null) {
const cookieStoreIds = Array.isArray(details.cookieStoreId)
? details.cookieStoreId
: [details.cookieStoreId];
options.originAttributesPatterns = cookieStoreIds.map(cookieStoreId =>
getOriginAttributesPatternForCookieStoreId(cookieStoreId)
);
}
const convertCodeToURL = (data, mime) => {
const blob = new context.cloneScope.Blob(data, { type: mime });
const blobURL = context.cloneScope.URL.createObjectURL(blob);

Просмотреть файл

@ -51,6 +51,20 @@
"$ref": "extensionTypes.RunAt",
"optional": true,
"description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
},
"cookieStoreId": {
"choices": [
{
"type": "array",
"minItems": 1,
"items": { "type": "string" }
},
{
"type": "string"
}
],
"optional": true,
"description": "limit the set of matched tabs to those that belong to the given cookie store id"
}
}
},

Просмотреть файл

@ -380,6 +380,12 @@ add_task(async function test_contentscripts_register_js() {
js: [{ code: `(${textScriptCodeIdle})()` }],
runAt: "document_idle",
},
{
matches: ["http://localhost/*/file_sample.html"],
js: [{ code: `(${textScriptCodeIdle})()` }],
runAt: "document_idle",
cookieStoreId: "firefox-container-1",
},
// Extension URLs.
{
matches: ["http://localhost/*/file_sample.html"],
@ -401,6 +407,12 @@ add_task(async function test_contentscripts_register_js() {
js: [{ file: "content_script.js" }],
// "runAt" is not specified here to ensure that it defaults to document_idle when missing.
},
{
matches: ["http://localhost/*/file_sample.html"],
js: [{ file: "content_script_idle.js" }],
runAt: "document_idle",
cookieStoreId: "firefox-container-1",
},
];
const expectedAPIs = ["unregister"];
@ -552,7 +564,14 @@ add_task(async function test_contentscripts_register_all_options() {
);
const script = policy.contentScripts[0];
let { allFrames, cssPaths, jsPaths, matchAboutBlank, runAt } = script;
let {
allFrames,
cssPaths,
jsPaths,
matchAboutBlank,
runAt,
originAttributesPatterns,
} = script;
deepEqual(
{
@ -561,6 +580,7 @@ add_task(async function test_contentscripts_register_all_options() {
jsPaths,
matchAboutBlank,
runAt,
originAttributesPatterns,
},
{
allFrames: true,
@ -568,6 +588,7 @@ add_task(async function test_contentscripts_register_all_options() {
jsPaths: [`${baseExtURL}/content_script.js`],
matchAboutBlank: true,
runAt: "document_start",
originAttributesPatterns: null,
},
"Got the expected content script properties"
);
@ -589,3 +610,198 @@ add_task(async function test_contentscripts_register_all_options() {
await extension.unload();
});
add_task(async function test_contentscripts_register_cookieStoreId() {
async function background() {
let cookieStoreIdCSSArray = [
{ id: null, color: "rgb(123, 45, 67)" },
{ id: "firefox-private", color: "rgb(255,255,0)" },
{ id: "firefox-default", color: "red" },
{ id: "firefox-container-1", color: "green" },
{ id: "firefox-container-2", color: "blue" },
{
id: ["firefox-container-3", "firefox-container-4"],
color: "rgb(100,100,0)",
},
];
const matches = ["http://localhost/*/file_sample_registered_styles.html"];
for (let { id, color } of cookieStoreIdCSSArray) {
await browser.contentScripts.register({
css: [
{
code: `#registered-extension-text-style {
background-color: ${color}}`,
},
],
matches,
runAt: "document_start",
cookieStoreId: id,
});
}
await browser.test.assertRejects(
browser.contentScripts.register({
css: [{ code: `body {}` }],
matches,
cookieStoreId: "not_a_valid_cookieStoreId",
}),
/Invalid cookieStoreId/,
"contentScript.register with an invalid cookieStoreId"
);
if (!navigator.userAgent.includes("Android")) {
await browser.test.assertRejects(
browser.contentScripts.register({
css: [{ code: `body {}` }],
matches,
cookieStoreId: "firefox-container-999",
}),
/Invalid cookieStoreId/,
"contentScript.register with an invalid cookieStoreId"
);
} else {
// On Android, any firefox-container-... is treated as valid, so it doesn't
// result in an error.
// TODO bug 1743616: Fix implementation and remove this branch.
await browser.contentScripts.register({
css: [{ code: `body {}` }],
matches,
cookieStoreId: "firefox-container-999",
});
}
await browser.test.assertRejects(
browser.contentScripts.register({
css: [{ code: `body {}` }],
matches,
cookieStoreId: "",
}),
/Invalid cookieStoreId/,
"contentScript.register with an invalid cookieStoreId"
);
browser.test.sendMessage("background_ready");
}
const extensionData = {
manifest: {
permissions: [
"http://localhost/*/file_sample_registered_styles.html",
"<all_urls>",
],
content_scripts: [
{
matches: ["http://localhost/*/file_sample_registered_styles.html"],
run_at: "document_idle",
js: ["check_applied_styles.js"],
},
],
},
background,
files: {
"check_applied_styles.js": check_applied_styles,
},
};
const extension = ExtensionTestUtils.loadExtension({
...extensionData,
incognitoOverride: "spanning",
});
await extension.startup();
await extension.awaitMessage("background_ready");
// Index 0 is the one from manifest.json.
let contentScriptMatchTests = [
{
contentPageOptions: { userContextId: 5 },
expectedStyles: "rgb(123, 45, 67)",
originAttributesPatternExpected: null,
contentScriptIndex: 1,
},
{
contentPageOptions: { privateBrowsing: true },
expectedStyles: "rgb(255, 255, 0)",
originAttributesPatternExpected: [
{
privateBrowsingId: 1,
userContextId: 0,
},
],
contentScriptIndex: 2,
},
{
contentPageOptions: { userContextId: 0 },
expectedStyles: "rgb(255, 0, 0)",
originAttributesPatternExpected: [
{
privateBrowsingId: 0,
userContextId: 0,
},
],
contentScriptIndex: 3,
},
{
contentPageOptions: { userContextId: 1 },
expectedStyles: "rgb(0, 128, 0)",
originAttributesPatternExpected: [{ userContextId: 1 }],
contentScriptIndex: 4,
},
{
contentPageOptions: { userContextId: 2 },
expectedStyles: "rgb(0, 0, 255)",
originAttributesPatternExpected: [{ userContextId: 2 }],
contentScriptIndex: 5,
},
{
contentPageOptions: { userContextId: 3 },
expectedStyles: "rgb(100, 100, 0)",
originAttributesPatternExpected: [
{ userContextId: 3 },
{ userContextId: 4 },
],
contentScriptIndex: 6,
},
{
contentPageOptions: { userContextId: 4 },
expectedStyles: "rgb(100, 100, 0)",
originAttributesPatternExpected: [
{ userContextId: 3 },
{ userContextId: 4 },
],
contentScriptIndex: 6,
},
];
const policy = WebExtensionPolicy.getByID(extension.id);
for (const testCase of contentScriptMatchTests) {
const {
contentPageOptions,
expectedStyles,
originAttributesPatternExpected,
contentScriptIndex,
} = testCase;
const script = policy.contentScripts[contentScriptIndex];
deepEqual(script.originAttributesPatterns, originAttributesPatternExpected);
let contentPage = await ExtensionTestUtils.loadContentPage(
`about:blank`,
contentPageOptions
);
await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`);
let registeredStylesResults = await extension.awaitMessage(
"registered-styles-results"
);
equal(
registeredStylesResults.registeredExtensionBlobStyleBG,
expectedStyles,
`Expected styles applied on content page loaded with options
${JSON.stringify(contentPageOptions)}`
);
await contentPage.close();
}
await extension.unload();
});