1
0
Fork 0
firefox/toolkit/components/extensions/parent/ext-permissions.js
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

263 lines
7.9 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
Schemas: "resource://gre/modules/Schemas.sys.mjs",
});
var { ExtensionError } = ExtensionUtils;
XPCOMUtils.defineLazyPreferenceGetter(
this,
"promptsEnabled",
"extensions.webextOptionalPermissionPrompts"
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"dataCollectionPermissionsEnabled",
"extensions.dataCollectionPermissions.enabled",
false
);
ChromeUtils.defineLazyGetter(this, "OPTIONAL_ONLY_PERMISSIONS", () => {
// Schemas.getPermissionNames() depends on API schemas to have been loaded.
// This is always the case here - extension APIs can only be called when an
// extension has started. And as part of startup, extension schemas are
// always parsed, via extension.loadManifest at:
// https://searchfox.org/mozilla-central/rev/2deb9bcf801f9de83d4f30c890d442072b9b6595/toolkit/components/extensions/Extension.sys.mjs#2094
return new Set(Schemas.getPermissionNames(["OptionalOnlyPermission"]));
});
function normalizePermissions(perms) {
perms = { ...perms };
perms.permissions = perms.permissions.filter(
perm => !perm.startsWith("internal:") && perm !== "<all_urls>"
);
if (!dataCollectionPermissionsEnabled) {
delete perms.data_collection;
}
return perms;
}
this.permissions = class extends ExtensionAPIPersistent {
PERSISTENT_EVENTS = {
onAdded({ fire }) {
let { extension } = this;
let callback = (event, change) => {
if (change.extensionId == extension.id && change.added) {
let perms = normalizePermissions(change.added);
if (
perms.permissions.length ||
perms.origins.length ||
(dataCollectionPermissionsEnabled && perms.data_collection.length)
) {
fire.async(perms);
}
}
};
extensions.on("change-permissions", callback);
return {
unregister() {
extensions.off("change-permissions", callback);
},
convert(_fire) {
fire = _fire;
},
};
},
onRemoved({ fire }) {
let { extension } = this;
let callback = (event, change) => {
if (change.extensionId == extension.id && change.removed) {
let perms = normalizePermissions(change.removed);
if (
perms.permissions.length ||
perms.origins.length ||
(dataCollectionPermissionsEnabled && perms.data_collection.length)
) {
fire.async(perms);
}
}
};
extensions.on("change-permissions", callback);
return {
unregister() {
extensions.off("change-permissions", callback);
},
convert(_fire) {
fire = _fire;
},
};
},
};
getAPI(context) {
let { extension } = context;
return {
permissions: {
async request(perms) {
let { permissions, origins, data_collection } = perms;
let { optionalPermissions } = context.extension;
for (let perm of permissions) {
if (!optionalPermissions.includes(perm)) {
throw new ExtensionError(
`Cannot request permission ${perm} since it was not declared in optional_permissions`
);
}
if (
OPTIONAL_ONLY_PERMISSIONS.has(perm) &&
(permissions.length > 1 ||
origins.length ||
(dataCollectionPermissionsEnabled && data_collection.length))
) {
throw new ExtensionError(
`Cannot request permission ${perm} with another permission`
);
}
}
let optionalOrigins = context.extension.optionalOrigins;
for (let origin of origins) {
if (!optionalOrigins.subsumes(new MatchPattern(origin))) {
throw new ExtensionError(
`Cannot request origin permission for ${origin} since it was not declared in the manifest`
);
}
}
if (dataCollectionPermissionsEnabled) {
let { optionalDataCollectionPermissions } = context.extension;
for (let perm of data_collection) {
if (!optionalDataCollectionPermissions.includes(perm)) {
throw new ExtensionError(
`Cannot request data collection permission ${perm} since it ` +
"was not declared in data_collection_permissions.optional"
);
}
}
}
if (promptsEnabled) {
permissions = permissions.filter(
perm => !context.extension.hasPermission(perm)
);
origins = origins.filter(
origin =>
!context.extension.allowedOrigins.subsumes(
new MatchPattern(origin)
)
);
data_collection = data_collection.filter(
perm => !context.extension.dataCollectionPermissions.has(perm)
);
if (
!permissions.length &&
!origins.length &&
!data_collection.length
) {
return true;
}
let browser = context.pendingEventBrowser || context.xulBrowser;
let allowPromise = new Promise(resolve => {
let subject = {
wrappedJSObject: {
browser,
name: context.extension.name,
id: context.extension.id,
icon: context.extension.getPreferredIcon(32),
permissions: { permissions, origins, data_collection },
resolve,
},
};
Services.obs.notifyObservers(
subject,
"webextension-optional-permission-prompt"
);
});
if (context.isBackgroundContext) {
extension.emit("background-script-idle-waituntil", {
promise: allowPromise,
reason: "permissions_request",
});
}
if (!(await allowPromise)) {
return false;
}
}
await ExtensionPermissions.add(extension.id, perms, extension);
return true;
},
async getAll() {
let perms = normalizePermissions(context.extension.activePermissions);
delete perms.apis;
return perms;
},
async contains(permissions) {
for (let perm of permissions.permissions) {
if (!context.extension.hasPermission(perm)) {
return false;
}
}
for (let origin of permissions.origins) {
if (
!context.extension.allowedOrigins.subsumes(
new MatchPattern(origin)
)
) {
return false;
}
}
if (dataCollectionPermissionsEnabled) {
for (let perm of permissions.data_collection) {
if (!context.extension.dataCollectionPermissions.has(perm)) {
return false;
}
}
}
return true;
},
async remove(permissions) {
await ExtensionPermissions.remove(
extension.id,
permissions,
extension
);
return true;
},
onAdded: new EventManager({
context,
module: "permissions",
event: "onAdded",
extensionApi: this,
}).api(),
onRemoved: new EventManager({
context,
module: "permissions",
event: "onRemoved",
extensionApi: this,
}).api(),
},
};
}
};