summaryrefslogtreecommitdiffstats
path: root/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs209
1 files changed, 209 insertions, 0 deletions
diff --git a/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs b/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs
new file mode 100644
index 0000000000..d24a2eae33
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs
@@ -0,0 +1,209 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "console", () => {
+ return console.createInstance({
+ prefix: "UserCharacteristicsPage",
+ maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
+ });
+});
+
+const BACKGROUND_WIDTH = 1024;
+const BACKGROUND_HEIGHT = 768;
+
+/**
+ * A manager for hidden browsers. Responsible for creating and destroying a
+ * hidden frame to hold them.
+ * All of this is copied from PageDataService.sys.mjs
+ */
+class HiddenBrowserManager {
+ /**
+ * The hidden frame if one has been created.
+ *
+ * @type {HiddenFrame | null}
+ */
+ #frame = null;
+ /**
+ * The number of hidden browser elements currently in use.
+ *
+ * @type {number}
+ */
+ #browsers = 0;
+
+ /**
+ * Creates and returns a new hidden browser.
+ *
+ * @returns {Browser}
+ */
+ async #acquireBrowser() {
+ this.#browsers++;
+ if (!this.#frame) {
+ this.#frame = new lazy.HiddenFrame();
+ }
+
+ let frame = await this.#frame.get();
+ let doc = frame.document;
+ let browser = doc.createXULElement("browser");
+ browser.setAttribute("remote", "true");
+ browser.setAttribute("type", "content");
+ browser.setAttribute(
+ "style",
+ `
+ width: ${BACKGROUND_WIDTH}px;
+ min-width: ${BACKGROUND_WIDTH}px;
+ height: ${BACKGROUND_HEIGHT}px;
+ min-height: ${BACKGROUND_HEIGHT}px;
+ `
+ );
+ browser.setAttribute("maychangeremoteness", "true");
+ doc.documentElement.appendChild(browser);
+
+ return browser;
+ }
+
+ /**
+ * Releases the given hidden browser.
+ *
+ * @param {Browser} browser
+ * The hidden browser element.
+ */
+ #releaseBrowser(browser) {
+ browser.remove();
+
+ this.#browsers--;
+ if (this.#browsers == 0) {
+ this.#frame.destroy();
+ this.#frame = null;
+ }
+ }
+
+ /**
+ * Calls a callback function with a new hidden browser.
+ * This function will return whatever the callback function returns.
+ *
+ * @param {Callback} callback
+ * The callback function will be called with the browser element and may
+ * be asynchronous.
+ * @returns {T}
+ */
+ async withHiddenBrowser(callback) {
+ let browser = await this.#acquireBrowser();
+ try {
+ return await callback(browser);
+ } finally {
+ this.#releaseBrowser(browser);
+ }
+ }
+}
+
+export class UserCharacteristicsPageService {
+ classId = Components.ID("{ce3e9659-e311-49fb-b18b-7f27c6659b23}");
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIUserCharacteristicsPageService",
+ ]);
+
+ _initialized = false;
+ _isParentProcess = false;
+
+ /**
+ * A manager for hidden browsers.
+ *
+ * @type {HiddenBrowserManager}
+ */
+ _browserManager = new HiddenBrowserManager();
+
+ /**
+ * A map of hidden browsers to a resolve function that should be passed the
+ * actor that was created for the browser.
+ *
+ * @type {WeakMap<Browser, function(PageDataParent): void>}
+ */
+ _backgroundBrowsers = new WeakMap();
+
+ constructor() {
+ lazy.console.debug("Init");
+
+ if (
+ Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT
+ ) {
+ throw new Error(
+ "Shouldn't init UserCharacteristicsPage in content processes."
+ );
+ }
+
+ // Return if we have initiated.
+ if (this._initialized) {
+ lazy.console.warn("preventing re-initilization...");
+ return;
+ }
+ this._initialized = true;
+ }
+
+ shutdown() {}
+
+ createContentPage() {
+ lazy.console.debug("called createContentPage");
+ return this._browserManager.withHiddenBrowser(async browser => {
+ lazy.console.debug(`In withHiddenBrowser`);
+ try {
+ let { promise, resolve } = Promise.withResolvers();
+ this._backgroundBrowsers.set(browser, resolve);
+
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+ let loadURIOptions = {
+ triggeringPrincipal: principal,
+ };
+
+ let userCharacteristicsPageURI = Services.io.newURI(
+ "about:fingerprinting"
+ );
+
+ browser.loadURI(userCharacteristicsPageURI, loadURIOptions);
+
+ let data = await promise;
+ if (data.debug) {
+ lazy.console.debug(`Debugging Output:`);
+ for (let line of data.debug) {
+ lazy.console.debug(line);
+ }
+ lazy.console.debug(`(debugging output done)`);
+ }
+ lazy.console.debug(`Data:`, data.output);
+
+ lazy.console.debug("Populating Glean metrics...");
+ Glean.characteristics.timezone.set(data.output.foo);
+
+ lazy.console.debug("Unregistering actor");
+ Services.obs.notifyObservers(
+ null,
+ "user-characteristics-populating-data-done"
+ );
+ } finally {
+ this._backgroundBrowsers.delete(browser);
+ }
+ });
+ }
+
+ async pageLoaded(browsingContext, data) {
+ lazy.console.debug(
+ `pageLoaded browsingContext=${browsingContext} data=${data}`
+ );
+
+ let browser = browsingContext.embedderElement;
+
+ let backgroundResolve = this._backgroundBrowsers.get(browser);
+ if (backgroundResolve) {
+ backgroundResolve(data);
+ return;
+ }
+ throw new Error(`No backround resolve for ${browser} found`);
+ }
+}