summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/parent/ext-contextualIdentities.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/parent/ext-contextualIdentities.js')
-rw-r--r--toolkit/components/extensions/parent/ext-contextualIdentities.js319
1 files changed, 319 insertions, 0 deletions
diff --git a/toolkit/components/extensions/parent/ext-contextualIdentities.js b/toolkit/components/extensions/parent/ext-contextualIdentities.js
new file mode 100644
index 0000000000..2cd70f9539
--- /dev/null
+++ b/toolkit/components/extensions/parent/ext-contextualIdentities.js
@@ -0,0 +1,319 @@
+/* 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, {
+ ContextualIdentityService:
+ "resource://gre/modules/ContextualIdentityService.sys.mjs",
+});
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "containersEnabled",
+ "privacy.userContext.enabled"
+);
+
+var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
+);
+
+var { ExtensionError } = ExtensionUtils;
+
+const CONTAINER_PREF_INSTALL_DEFAULTS = {
+ "privacy.userContext.enabled": true,
+ "privacy.userContext.ui.enabled": true,
+ "privacy.usercontext.about_newtab_segregation.enabled": true,
+ "privacy.userContext.extension": undefined,
+};
+
+const CONTAINERS_ENABLED_SETTING_NAME = "privacy.containers";
+
+const CONTAINER_COLORS = new Map([
+ ["blue", "#37adff"],
+ ["turquoise", "#00c79a"],
+ ["green", "#51cd00"],
+ ["yellow", "#ffcb00"],
+ ["orange", "#ff9f00"],
+ ["red", "#ff613d"],
+ ["pink", "#ff4bda"],
+ ["purple", "#af51f5"],
+ ["toolbar", "#7c7c7d"],
+]);
+
+const CONTAINER_ICONS = new Set([
+ "briefcase",
+ "cart",
+ "circle",
+ "dollar",
+ "fence",
+ "fingerprint",
+ "gift",
+ "vacation",
+ "food",
+ "fruit",
+ "pet",
+ "tree",
+ "chill",
+]);
+
+function getContainerIcon(iconName) {
+ if (!CONTAINER_ICONS.has(iconName)) {
+ throw new ExtensionError(`Invalid icon ${iconName} for container`);
+ }
+ return `resource://usercontext-content/${iconName}.svg`;
+}
+
+function getContainerColor(colorName) {
+ if (!CONTAINER_COLORS.has(colorName)) {
+ throw new ExtensionError(`Invalid color name ${colorName} for container`);
+ }
+ return CONTAINER_COLORS.get(colorName);
+}
+
+const convertIdentity = identity => {
+ let result = {
+ name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
+ icon: identity.icon,
+ iconUrl: getContainerIcon(identity.icon),
+ color: identity.color,
+ colorCode: getContainerColor(identity.color),
+ cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
+ };
+
+ return result;
+};
+
+const checkAPIEnabled = () => {
+ if (!containersEnabled) {
+ throw new ExtensionError("Contextual identities are currently disabled");
+ }
+};
+
+const convertIdentityFromObserver = wrappedIdentity => {
+ let identity = wrappedIdentity.wrappedJSObject;
+ let iconUrl, colorCode;
+ try {
+ iconUrl = getContainerIcon(identity.icon);
+ colorCode = getContainerColor(identity.color);
+ } catch (e) {
+ return null;
+ }
+
+ let result = {
+ name: identity.name,
+ icon: identity.icon,
+ iconUrl,
+ color: identity.color,
+ colorCode,
+ cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
+ };
+
+ return result;
+};
+
+ExtensionPreferencesManager.addSetting(CONTAINERS_ENABLED_SETTING_NAME, {
+ prefNames: Object.keys(CONTAINER_PREF_INSTALL_DEFAULTS),
+
+ setCallback(value) {
+ if (value !== true) {
+ return {
+ ...CONTAINER_PREF_INSTALL_DEFAULTS,
+ "privacy.userContext.extension": value,
+ };
+ }
+ return {};
+ },
+});
+
+this.contextualIdentities = class extends ExtensionAPIPersistent {
+ eventRegistrar(eventName) {
+ return ({ fire }) => {
+ let observer = (subject, topic) => {
+ let convertedIdentity = convertIdentityFromObserver(subject);
+ if (convertedIdentity) {
+ fire.async({ contextualIdentity: convertedIdentity });
+ }
+ };
+
+ Services.obs.addObserver(observer, eventName);
+ return {
+ unregister() {
+ Services.obs.removeObserver(observer, eventName);
+ },
+ convert(_fire) {
+ fire = _fire;
+ },
+ };
+ };
+ }
+
+ PERSISTENT_EVENTS = {
+ onCreated: this.eventRegistrar("contextual-identity-created"),
+ onUpdated: this.eventRegistrar("contextual-identity-updated"),
+ onRemoved: this.eventRegistrar("contextual-identity-deleted"),
+ };
+
+ onStartup() {
+ let { extension } = this;
+
+ if (extension.hasPermission("contextualIdentities")) {
+ ExtensionPreferencesManager.setSetting(
+ extension.id,
+ CONTAINERS_ENABLED_SETTING_NAME,
+ extension.id
+ );
+ }
+ }
+
+ getAPI(context) {
+ let self = {
+ contextualIdentities: {
+ async get(cookieStoreId) {
+ checkAPIEnabled();
+ let containerId = getContainerForCookieStoreId(cookieStoreId);
+ if (!containerId) {
+ throw new ExtensionError(
+ `Invalid contextual identity: ${cookieStoreId}`
+ );
+ }
+
+ let identity =
+ ContextualIdentityService.getPublicIdentityFromId(containerId);
+ return convertIdentity(identity);
+ },
+
+ async query(details) {
+ checkAPIEnabled();
+ let identities = [];
+ ContextualIdentityService.getPublicIdentities().forEach(identity => {
+ if (
+ details.name &&
+ ContextualIdentityService.getUserContextLabel(
+ identity.userContextId
+ ) != details.name
+ ) {
+ return;
+ }
+
+ identities.push(convertIdentity(identity));
+ });
+
+ return identities;
+ },
+
+ async create(details) {
+ // Lets prevent making containers that are not valid
+ getContainerIcon(details.icon);
+ getContainerColor(details.color);
+
+ let identity = ContextualIdentityService.create(
+ details.name,
+ details.icon,
+ details.color
+ );
+ return convertIdentity(identity);
+ },
+
+ async update(cookieStoreId, details) {
+ checkAPIEnabled();
+ let containerId = getContainerForCookieStoreId(cookieStoreId);
+ if (!containerId) {
+ throw new ExtensionError(
+ `Invalid contextual identity: ${cookieStoreId}`
+ );
+ }
+
+ let identity =
+ ContextualIdentityService.getPublicIdentityFromId(containerId);
+ if (!identity) {
+ throw new ExtensionError(
+ `Invalid contextual identity: ${cookieStoreId}`
+ );
+ }
+
+ if (details.name !== null) {
+ identity.name = details.name;
+ }
+
+ if (details.color !== null) {
+ getContainerColor(details.color);
+ identity.color = details.color;
+ }
+
+ if (details.icon !== null) {
+ getContainerIcon(details.icon);
+ identity.icon = details.icon;
+ }
+
+ if (
+ !ContextualIdentityService.update(
+ identity.userContextId,
+ identity.name,
+ identity.icon,
+ identity.color
+ )
+ ) {
+ throw new ExtensionError(
+ `Contextual identity failed to update: ${cookieStoreId}`
+ );
+ }
+
+ return convertIdentity(identity);
+ },
+
+ async remove(cookieStoreId) {
+ checkAPIEnabled();
+ let containerId = getContainerForCookieStoreId(cookieStoreId);
+ if (!containerId) {
+ throw new ExtensionError(
+ `Invalid contextual identity: ${cookieStoreId}`
+ );
+ }
+
+ let identity =
+ ContextualIdentityService.getPublicIdentityFromId(containerId);
+ if (!identity) {
+ throw new ExtensionError(
+ `Invalid contextual identity: ${cookieStoreId}`
+ );
+ }
+
+ // We have to create the identity object before removing it.
+ let convertedIdentity = convertIdentity(identity);
+
+ if (!ContextualIdentityService.remove(identity.userContextId)) {
+ throw new ExtensionError(
+ `Contextual identity failed to remove: ${cookieStoreId}`
+ );
+ }
+
+ return convertedIdentity;
+ },
+
+ onCreated: new EventManager({
+ context,
+ module: "contextualIdentities",
+ event: "onCreated",
+ extensionApi: this,
+ }).api(),
+
+ onUpdated: new EventManager({
+ context,
+ module: "contextualIdentities",
+ event: "onUpdated",
+ extensionApi: this,
+ }).api(),
+
+ onRemoved: new EventManager({
+ context,
+ module: "contextualIdentities",
+ event: "onRemoved",
+ extensionApi: this,
+ }).api(),
+ },
+ };
+
+ return self;
+ }
+};