Files
tubestation/toolkit/components/resistfingerprinting/FingerprintingWebCompatService.sys.mjs
Cristina Horotan b163893208 Backed out 7 changesets (bug 1954188) for causing bc failures at browser_preferences_usage.js
Backed out changeset dae6e2ec0b2d (bug 1954188)
Backed out changeset 47f683e9f0d6 (bug 1954188)
Backed out changeset 14ed1c70e1ec (bug 1954188)
Backed out changeset 291590143ed9 (bug 1954188)
Backed out changeset fbb4e968f0f1 (bug 1954188)
Backed out changeset 4c21e081561e (bug 1954188)
Backed out changeset 33e6088d8317 (bug 1954188)
2025-04-03 03:21:46 +03:00

257 lines
7.4 KiB
JavaScript

// -*- 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, {
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
return console.createInstance({
prefix: "FingerprintingWebCompatService",
maxLogLevelPref:
"privacy.fingerprintingProtection.WebCompatService.logLevel",
});
});
const SCHEMA = `{
"type": "object",
"title": "Fingerprinting Overrides",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"firstPartyDomain",
"overrides"
],
"properties": {
"overrides": {
"type": "string",
"pattern": "^[+-][A-Za-z]+(?:,[+-][A-Za-z]+)*$",
"description": "The fingerprinting overrides. See https://searchfox.org/mozilla-central/source/toolkit/components/resistfingerprinting/RFPTargets.inc for details."
},
"firstPartyDomain": {
"type": "string",
"pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
"description": "The first-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed."
},
"thirdPartyDomain": {
"type": "string",
"pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
"description": "The third-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed. Leave this field empty if the override is only for the first-party context."
}
}
}`;
const COLLECTION_NAME = "fingerprinting-protection-overrides";
const PREF_GRANULAR_OVERRIDES =
"privacy.fingerprintingProtection.granularOverrides";
const PREF_REMOTE_OVERRIDES_ENABLED =
"privacy.fingerprintingProtection.remoteOverrides.enabled";
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"granularOverridesPref",
PREF_GRANULAR_OVERRIDES
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"remoteOverridesEnabled",
PREF_REMOTE_OVERRIDES_ENABLED
);
/**
* The object represents a fingerprinting override.
*/
export class FingerprintingOverride {
classID = Components.ID("{07f45442-1806-44be-9230-12eb79de9bac}");
QueryInterface = ChromeUtils.generateQI(["nsIFingerprintingOverride"]);
constructor(firstPartyDomain, thirdPartyDomain, overrides) {
this.firstPartyDomain = firstPartyDomain;
this.thirdPartyDomain = thirdPartyDomain;
this.overrides = overrides;
}
}
/**
* The singleton service that is responsible for the WebCompat of the
* fingerprinting protection. It gets fingerprinting overrides from remote
* settings and the local test pref.
*/
export class FingerprintingWebCompatService {
classId = Components.ID("{e7b1da06-2594-4670-aea4-131070baca4c}");
QueryInterface = ChromeUtils.generateQI([
"nsIFingerprintingWebCompatService",
]);
#initialized = false;
#remoteOverrides;
#granularOverrides;
#rs;
#validator;
#isParentProcess;
constructor() {
this.#isParentProcess =
Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
this.#remoteOverrides = new Set();
this.#granularOverrides = new Set();
this.#rs = lazy.RemoteSettings(COLLECTION_NAME);
this.#validator = new lazy.JsonSchema.Validator(SCHEMA);
}
async init() {
lazy.logConsole.debug("init");
// We can only access remote settings from the parent process. So, never
// init it in the content process.
if (!this.#isParentProcess) {
throw new Error(
"Shouldn't init FingerprintingWebCompatService in content processes."
);
}
// Return if we have initiated.
if (this.#initialized) {
return;
}
this.#initialized = true;
// Register listener to import overrides when the overrides pref changes.
Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES, this);
// Register the sync event for the remote settings updates.
this.#rs.on("sync", event => {
let {
data: { current },
} = event;
this.#onRemoteUpdate(current);
this.#populateOverrides();
});
// Get the remote overrides from the remote settings.
await this.#importRemoteSettingsOverrides();
// Get the granular overrides from the pref.
this.#importPrefOverrides();
// Populate the overrides to the nsRFPService.
this.#populateOverrides();
lazy.logConsole.debug("Init completes");
}
// Import fingerprinting overrides from the local granular pref.
#importPrefOverrides() {
lazy.logConsole.debug("importLocalGranularOverrides");
// Clear overrides before we update.
this.#granularOverrides.clear();
let overrides;
try {
overrides = JSON.parse(lazy.granularOverridesPref || "[]");
} catch (error) {
lazy.logConsole.error(
`Failed to parse granular override JSON string: Not a valid JSON.`,
error
);
return;
}
// Ensure we have an array we can iterate over and not an object.
if (!Array.isArray(overrides)) {
lazy.logConsole.error(
"Failed to parse granular overrides JSON String: Not an array."
);
return;
}
for (let override of overrides) {
// Validate the override.
let { valid, errors } = this.#validator.validate(override);
if (!valid) {
lazy.logConsole.debug("Override validation error", override, errors);
continue;
}
this.#granularOverrides.add(
this.#createFingerprintingOverrideFrom(override)
);
}
}
// Import fingerprinting overrides from the remote settings.
async #importRemoteSettingsOverrides() {
lazy.logConsole.debug("importRemoteSettingsOverrides");
let entries;
try {
entries = await this.#rs.get();
} catch (e) {}
this.#onRemoteUpdate(entries || []);
}
#onRemoteUpdate(entries) {
lazy.logConsole.debug("onUpdateEntries", { entries });
if (!lazy.remoteOverridesEnabled) {
lazy.logConsole.debug("Abort remote overrides");
return;
}
// Clear all overrides before we update the overrides.
this.#remoteOverrides.clear();
for (let entry of entries) {
this.#remoteOverrides.add(this.#createFingerprintingOverrideFrom(entry));
}
}
#createFingerprintingOverrideFrom(entry) {
return new FingerprintingOverride(
entry.firstPartyDomain,
entry.thirdPartyDomain,
entry.overrides
);
}
#populateOverrides() {
lazy.logConsole.debug("populateOverrides");
// Create the array that contains all overrides. We explicitly concat the
// overrides from testing pref after the ones from remote settings to ensure
// that the testing pref will take precedence.
let overrides = Array.from(this.#remoteOverrides).concat(
Array.from(this.#granularOverrides)
);
// Set the remote override to the RFP service.
Services.rfp.setFingerprintingOverrides(Array.from(overrides));
}
observe(subject, topic, prefName) {
if (prefName != PREF_GRANULAR_OVERRIDES) {
return;
}
this.#importPrefOverrides();
this.#populateOverrides();
}
shutdown() {
lazy.logConsole.debug("shutdown");
Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES, this);
}
}