// -*- 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} */ _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`); } }