Bug 1954522 - Add support for data collection permissions in browser.permissions.getAll(). r=rpl

Differential Revision: https://phabricator.services.mozilla.com/D242830
This commit is contained in:
William Durand
2025-04-11 19:14:21 +00:00
parent e3a0c89f6d
commit 6a9e56a639
4 changed files with 216 additions and 3 deletions

View File

@@ -1380,6 +1380,10 @@ export class ExtensionData {
apis: [...this.apiNames],
};
if (lazy.dataCollectionPermissionsEnabled) {
result.data_collection = Array.from(this.dataCollectionPermissions);
}
const EXP_PATTERN = /^experiments\.\w+/;
result.permissions = [...this.permissions].filter(
p => !result.origins.includes(p) && !EXP_PATTERN.test(p)
@@ -2010,7 +2014,7 @@ export class ExtensionData {
if (lazy.dataCollectionPermissionsEnabled) {
const { required } = this.getDataCollectionPermissions(manifest);
for (const permission of required) {
for (const permission of required.filter(perm => perm !== "none")) {
dataCollectionPermissions.add(permission);
}
}
@@ -2032,6 +2036,9 @@ export class ExtensionData {
for (let origin of perms.origins) {
originPermissions.add(origin);
}
for (let perm of perms.data_collection) {
dataCollectionPermissions.add(perm);
}
}
for (let api of apiNames) {
@@ -3272,6 +3279,9 @@ export class Extension extends ExtensionData {
for (let perm of permissions.permissions) {
this.permissions.add(perm);
}
for (let perm of permissions.data_collection) {
this.dataCollectionPermissions.add(perm);
}
this.policy.permissions = Array.from(this.permissions);
updateAllowedOrigins(this.policy, permissions.origins, /* isAdd */ true);
@@ -3296,6 +3306,9 @@ export class Extension extends ExtensionData {
for (let perm of permissions.permissions) {
this.permissions.delete(perm);
}
for (let perm of permissions.data_collection) {
this.dataCollectionPermissions.delete(perm);
}
this.policy.permissions = Array.from(this.permissions);
updateAllowedOrigins(this.policy, permissions.origins, /* isAdd */ false);
@@ -3478,6 +3491,7 @@ export class Extension extends ExtensionData {
pat => pat.pattern
);
manifestData.permissions = this.permissions;
manifestData.dataCollectionPermissions = this.dataCollectionPermissions;
return StartupCache.manifests.set(this.manifestCacheKey, manifestData);
}

View File

@@ -52,7 +52,11 @@ this.permissions = class extends ExtensionAPIPersistent {
let callback = (event, change) => {
if (change.extensionId == extension.id && change.added) {
let perms = normalizePermissions(change.added);
if (perms.permissions.length || perms.origins.length) {
if (
perms.permissions.length ||
perms.origins.length ||
(dataCollectionPermissionsEnabled && perms.data_collection.length)
) {
fire.async(perms);
}
}
@@ -73,7 +77,11 @@ this.permissions = class extends ExtensionAPIPersistent {
let callback = (event, change) => {
if (change.extensionId == extension.id && change.removed) {
let perms = normalizePermissions(change.removed);
if (perms.permissions.length || perms.origins.length) {
if (
perms.permissions.length ||
perms.origins.length ||
(dataCollectionPermissionsEnabled && perms.data_collection.length)
) {
fire.async(perms);
}
}

View File

@@ -22,6 +22,12 @@
"items": { "$ref": "manifest.MatchPattern" },
"optional": true,
"default": []
},
"data_collection": {
"type": "array",
"items": { "$ref": "manifest.OptionalDataCollectionPermission" },
"optional": true,
"default": []
}
}
},
@@ -45,6 +51,12 @@
"items": { "$ref": "manifest.MatchPattern" },
"optional": true,
"default": []
},
"data_collection": {
"type": "array",
"items": { "$ref": "manifest.OptionalDataCollectionPermission" },
"optional": true,
"default": []
}
}
}

View File

@@ -536,3 +536,182 @@ add_task(
await extension.unload();
}
);
add_task(
{ pref_set: [["extensions.dataCollectionPermissions.enabled", true]] },
async function test_getAll_with_data_collection() {
async function background() {
browser.test.onMessage.addListener(async msg => {
browser.test.assertEq("getAll", msg, "expected correct message");
const permissions = await browser.permissions.getAll();
browser.test.sendMessage("all", permissions);
});
browser.permissions.onAdded.addListener(details => {
browser.test.sendMessage("added", details);
});
browser.permissions.onRemoved.addListener(details => {
browser.test.sendMessage("removed", details);
});
browser.test.sendMessage("ready");
}
const extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 3,
permissions: ["bookmarks"],
browser_specific_settings: {
gecko: {
data_collection_permissions: {
// "none" shouldn't be added to the list of data collection
// permissions returned by `getAll()`.
required: ["none"],
optional: ["technicalAndInteraction", "locationInfo"],
},
},
},
},
background,
});
await extension.startup();
await extension.awaitMessage("ready");
const getAllAndVerifyDataCollection = async expected => {
extension.sendMessage("getAll");
const permissions = await extension.awaitMessage("all");
Assert.deepEqual(
permissions,
{
permissions: ["bookmarks"],
origins: [],
data_collection: expected,
},
"expected permissions with data collection"
);
};
// Pretend the T&I permission was granted at install time.
let perms = {
permissions: [],
origins: [],
data_collection: ["technicalAndInteraction"],
};
await ExtensionPermissions.add(extension.id, perms, extension.extension);
let added = await extension.awaitMessage("added");
Assert.deepEqual(
added,
{
permissions: [],
origins: [],
data_collection: ["technicalAndInteraction"],
},
"expected new permissions granted"
);
await getAllAndVerifyDataCollection(["technicalAndInteraction"]);
// Grant another optional data collection permission.
perms = {
permissions: [],
origins: [],
data_collection: ["technicalAndInteraction", "locationInfo"],
};
await ExtensionPermissions.add(extension.id, perms, extension.extension);
added = await extension.awaitMessage("added");
Assert.deepEqual(
added,
{
permissions: [],
origins: [],
data_collection: ["locationInfo"],
},
"expected new permissions granted"
);
await getAllAndVerifyDataCollection([
"technicalAndInteraction",
"locationInfo",
]);
// Revoke all optional data collection permissions.
await ExtensionPermissions.remove(extension.id, perms, extension.extension);
const removed = await extension.awaitMessage("removed");
Assert.deepEqual(
removed,
{
permissions: [],
origins: [],
data_collection: ["technicalAndInteraction", "locationInfo"],
},
"expected permissions revoked"
);
await getAllAndVerifyDataCollection([]);
await extension.unload();
}
);
add_task(
{ pref_set: [["extensions.dataCollectionPermissions.enabled", true]] },
async function test_getAll_with_required_data_collection() {
async function background() {
browser.test.onMessage.addListener(async msg => {
browser.test.assertEq("getAll", msg, "expected correct message");
const permissions = await browser.permissions.getAll();
browser.test.sendMessage("all", permissions);
});
browser.test.sendMessage("ready");
}
const extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 3,
permissions: ["bookmarks"],
browser_specific_settings: {
gecko: {
data_collection_permissions: {
required: ["bookmarksInfo"],
optional: ["technicalAndInteraction", "locationInfo"],
},
},
},
},
background,
});
await extension.startup();
await extension.awaitMessage("ready");
extension.sendMessage("getAll");
let permissions = await extension.awaitMessage("all");
Assert.deepEqual(
permissions,
{
permissions: ["bookmarks"],
origins: [],
data_collection: ["bookmarksInfo"],
},
"expected permissions with required data collection"
);
let perms = {
permissions: [],
origins: [],
data_collection: ["technicalAndInteraction"],
};
await ExtensionPermissions.add(extension.id, perms, extension.extension);
extension.sendMessage("getAll");
permissions = await extension.awaitMessage("all");
Assert.deepEqual(
permissions,
{
permissions: ["bookmarks"],
origins: [],
data_collection: ["bookmarksInfo", "technicalAndInteraction"],
},
"expected permissions with newly added data collection"
);
await extension.unload();
}
);