/* 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/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { ContextIdComponent } from "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustContextId.sys.mjs"; const CONTEXT_ID_PREF = "browser.contextual-services.contextId"; const CONTEXT_ID_TIMESTAMP_PREF = "browser.contextual-services.contextId.timestamp-in-seconds"; const CONTEXT_ID_ROTATION_DAYS_PREF = "browser.contextual-services.contextId.rotation-in-days"; const CONTEXT_ID_RUST_COMPONENT_ENABLED_PREF = "browser.contextual-services.contextId.rust-component.enabled"; const TOPIC_APP_QUIT = "quit-application"; const lazy = {}; XPCOMUtils.defineLazyPreferenceGetter( lazy, "CURRENT_CONTEXT_ID", CONTEXT_ID_PREF, "" ); /** * A class that manages and (optionally) rotates the context ID, which is a * a unique identifier used by Contextual Services. */ export class _ContextId extends EventTarget { #comp = null; #rotationDays = 0; #rustComponentEnabled = false; #observer = null; constructor() { super(); this.#rustComponentEnabled = Services.prefs.getBoolPref( CONTEXT_ID_RUST_COMPONENT_ENABLED_PREF, false ); if (this.#rustComponentEnabled) { // We intentionally read this once at construction, and cache the result. // This is because enabling or disabling rotation may affect external // uses of _ContextId which (for example) send the context_id UUID to // Shredder in the context-id-deletion-request ping (which we only want to // do when rotation is disabled), and that sort of thing tends to get set // once during startup. this.#rotationDays = Services.prefs.getIntPref( CONTEXT_ID_ROTATION_DAYS_PREF, 0 ); this.#comp = ContextIdComponent.init( lazy.CURRENT_CONTEXT_ID, Services.prefs.getIntPref(CONTEXT_ID_TIMESTAMP_PREF, 0), Cu.isInAutomation, { persist: (newContextId, creationTimestamp) => { Services.prefs.setCharPref(CONTEXT_ID_PREF, newContextId); Services.prefs.setIntPref( CONTEXT_ID_TIMESTAMP_PREF, creationTimestamp ); this.dispatchEvent(new CustomEvent("ContextId:Persisted")); }, rotated: oldContextId => { GleanPings.contextIdDeletionRequest.setEnabled(true); Glean.contextualServices.contextId.set(oldContextId); GleanPings.contextIdDeletionRequest.submit(); }, } ); this.#observer = (subject, topic, data) => { this.observe(subject, topic, data); }; Services.obs.addObserver(this.#observer, TOPIC_APP_QUIT); } } /** * nsIObserver implementation. * * @param {nsISupports} _subject * @param {string} topic * @param {string} _data */ observe(_subject, topic, _data) { if (topic == TOPIC_APP_QUIT) { // Unregister ourselves as the callback to avoid leak assertions. this.#comp.unsetCallback(); Services.obs.removeObserver(this.#observer, TOPIC_APP_QUIT); } } /** * Returns the stored context ID for this profile, if one exists. If one * doesn't exist, one is generated and then returned. In the event that * context ID rotation is in effect, then this may return a different * context ID if we've determined it's time to rotate. This means that * consumers _should not_ cache the context ID, but always request it. * * @returns {Promise} * The context ID for this profile. */ async request() { if (this.#rustComponentEnabled) { return this.#comp.request(this.#rotationDays); } // Fallback to the legacy behaviour of just returning the pref, or // generating / returning a UUID if the pref is false-y. if (!lazy.CURRENT_CONTEXT_ID) { let _contextId = Services.uuid.generateUUID().toString(); Services.prefs.setStringPref(CONTEXT_ID_PREF, _contextId); } return Promise.resolve(lazy.CURRENT_CONTEXT_ID); } /** * Forces the rotation of the context ID. This should be used by callers when * some surface that uses the context ID is disabled. This is only supported * with the Rust backend, and is a no-op when the Rust backend is not enabled. * * @returns {Promise} */ async forceRotation() { if (this.#rustComponentEnabled) { return this.#comp.forceRotation(); } return Promise.resolve(); } /** * Returns true if context ID rotation is enabled. * * @returns {boolean} */ get rotationEnabled() { return this.#rustComponentEnabled && this.#rotationDays > 0; } /** * A compatibility shim that only works if rotationEnabled is false which * returns the context ID synchronously. This will throw if rotationEnabled * is true - so callers should ensure that rotationEnabled is false before * using this. This will eventually be removed. */ requestSynchronously() { if (this.rotationEnabled) { throw new Error( "Cannot request context ID synchronously when rotation is enabled." ); } return lazy.CURRENT_CONTEXT_ID; } } export const ContextId = new _ContextId();