diff options
Diffstat (limited to 'toolkit/components/extensions/parent/ext-contextualIdentities.js')
-rw-r--r-- | toolkit/components/extensions/parent/ext-contextualIdentities.js | 319 |
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; + } +}; |