Source code

Revision control

Copy as Markdown

Other Tools

/* 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<string>}
* 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<undefined>}
*/
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();