UserContextManager.sys.mjs (7612B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 ContextualIdentityService: 9 "resource://gre/modules/ContextualIdentityService.sys.mjs", 10 EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", 11 12 ContextualIdentityListener: 13 "chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs", 14 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", 15 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", 16 }); 17 18 const DEFAULT_CONTEXT_ID = "default"; 19 const DEFAULT_INTERNAL_ID = 0; 20 21 /** 22 * A UserContextManager instance keeps track of all public user contexts and 23 * maps their internal platform. 24 * 25 * This class is exported for test purposes. Otherwise the UserContextManager 26 * singleton should be used. 27 * 28 * @fires UserContextManagerClass#"user-context-created" 29 * - {string} userContextId 30 * The UUID of the user context which was just created. 31 * @fires UserContextManagerClass#"user-context-deleted" 32 * - {string} userContextId 33 * The UUID of the user context which was just deleted. 34 */ 35 export class UserContextManagerClass { 36 #contextualIdentityListener; 37 #userContextIds; 38 39 constructor() { 40 lazy.EventEmitter.decorate(this); 41 42 // Map from internal ids (numbers) from the ContextualIdentityService to 43 // opaque UUIDs (string). 44 this.#userContextIds = new Map(); 45 46 // The default user context is always using 0 as internal user context id 47 // and should be exposed as "default" instead of a randomly generated id. 48 this.#userContextIds.set(DEFAULT_INTERNAL_ID, DEFAULT_CONTEXT_ID); 49 50 // Register other (non-default) public contexts. 51 lazy.ContextualIdentityService.getPublicIdentities().forEach(identity => 52 this.#registerIdentity(identity) 53 ); 54 55 this.#contextualIdentityListener = new lazy.ContextualIdentityListener(); 56 this.#contextualIdentityListener.on("created", this.#onIdentityCreated); 57 this.#contextualIdentityListener.on("deleted", this.#onIdentityDeleted); 58 this.#contextualIdentityListener.startListening(); 59 } 60 61 destroy() { 62 this.#contextualIdentityListener.off("created", this.#onIdentityCreated); 63 this.#contextualIdentityListener.off("deleted", this.#onIdentityDeleted); 64 this.#contextualIdentityListener.destroy(); 65 66 this.#userContextIds = null; 67 } 68 69 /** 70 * Retrieve the user context id corresponding to the default user context. 71 * 72 * @returns {string} 73 * The default user context id. 74 */ 75 get defaultUserContextId() { 76 return DEFAULT_CONTEXT_ID; 77 } 78 79 /** 80 * Creates a new user context. 81 * 82 * @param {string} prefix 83 * The prefix to use for the name of the user context. 84 * 85 * @returns {string} 86 * The user context id of the new user context. 87 */ 88 createContext(prefix = "remote") { 89 // Prepare a unique name. 90 const name = `${prefix}-${lazy.generateUUID()}`; 91 92 // Create the user context. 93 const identity = lazy.ContextualIdentityService.create(name); 94 95 // An id has been set already by the contextual-identity-created observer. 96 return this.#userContextIds.get(identity.userContextId); 97 } 98 99 /** 100 * Retrieve the user context id corresponding to the provided browsing context. 101 * 102 * @param {BrowsingContext} browsingContext 103 * The browsing context to get the user context id from. 104 * 105 * @returns {string} 106 * The corresponding user context id. 107 * 108 * @throws {TypeError} 109 * If `browsingContext` is not a CanonicalBrowsingContext instance. 110 */ 111 getIdByBrowsingContext(browsingContext) { 112 if (!CanonicalBrowsingContext.isInstance(browsingContext)) { 113 throw new TypeError( 114 `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}` 115 ); 116 } 117 118 return this.getIdByInternalId( 119 browsingContext.originAttributes.userContextId 120 ); 121 } 122 123 /** 124 * Retrieve the user context id corresponding to the provided internal id. 125 * 126 * @param {number} internalId 127 * The internal user context id. 128 * 129 * @returns {string|null} 130 * The corresponding user context id or null if the user context does not 131 * exist. 132 */ 133 getIdByInternalId(internalId) { 134 if (this.#userContextIds.has(internalId)) { 135 return this.#userContextIds.get(internalId); 136 } 137 return null; 138 } 139 140 /** 141 * Retrieve the internal id corresponding to the provided user 142 * context id. 143 * 144 * @param {string} userContextId 145 * The user context id. 146 * 147 * @returns {number|null} 148 * The internal user context id or null if the user context does not 149 * exist. 150 */ 151 getInternalIdById(userContextId) { 152 for (const [internalId, id] of this.#userContextIds) { 153 if (userContextId == id) { 154 return internalId; 155 } 156 } 157 return null; 158 } 159 160 /** 161 * Returns an array of tabs related 162 * to the provided internal user context id. 163 * 164 * @param {string} internalId 165 * The internal user context id. 166 * 167 * @returns {Array<Tab>} 168 * The array of tabs. 169 */ 170 getTabsForUserContext(internalId) { 171 return lazy.TabManager.allTabs.filter(tab => { 172 return ( 173 (tab.hasAttribute("usercontextid") && 174 parseInt(tab.getAttribute("usercontextid"), 10) == internalId) || 175 (!tab.hasAttribute("usercontextid") && internalId === 0) 176 ); 177 }); 178 } 179 180 /** 181 * Returns an array of all known user context ids. 182 * 183 * @returns {Array<string>} 184 * The array of user context ids. 185 */ 186 getUserContextIds() { 187 return Array.from(this.#userContextIds.values()); 188 } 189 190 /** 191 * Checks if the provided user context id is known by this UserContextManager. 192 * 193 * @param {string} userContextId 194 * The id of the user context to check. 195 */ 196 hasUserContextId(userContextId) { 197 return this.getUserContextIds().includes(userContextId); 198 } 199 200 /** 201 * Removes a user context and closes all related container tabs. 202 * 203 * Note: When closing the related container tabs possible "beforeunload" 204 * prompts will be ignored. 205 * 206 * @param {string} userContextId 207 * The id of the user context to remove. 208 * @param {object=} options 209 * @param {boolean=} options.closeContextTabs 210 * Pass true if the tabs owned by the user context should also be closed. 211 * Defaults to false. 212 */ 213 removeUserContext(userContextId, options = {}) { 214 const { closeContextTabs = false } = options; 215 216 if (!this.hasUserContextId(userContextId)) { 217 return; 218 } 219 220 const internalId = this.getInternalIdById(userContextId); 221 if (closeContextTabs) { 222 lazy.ContextualIdentityService.closeContainerTabs(internalId, { 223 skipPermitUnload: true, 224 }); 225 } 226 lazy.ContextualIdentityService.remove(internalId); 227 } 228 229 #onIdentityCreated = (eventName, data) => { 230 this.#registerIdentity(data.identity); 231 }; 232 233 #onIdentityDeleted = (eventName, data) => { 234 const userContextId = this.#userContextIds.get(data.identity.userContextId); 235 this.#userContextIds.delete(data.identity.userContextId); 236 this.emit("user-context-deleted", { userContextId }); 237 }; 238 239 #registerIdentity(identity) { 240 const userContextId = lazy.generateUUID(); 241 this.#userContextIds.set(identity.userContextId, userContextId); 242 this.emit("user-context-created", { userContextId }); 243 } 244 } 245 246 // Expose a shared singleton. 247 export const UserContextManager = new UserContextManagerClass();