diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs | 209 |
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`); + } +} |