fune/browser/components/doh/DoHConfig.sys.mjs
Cristian Tuns b3bf09cc0d Backed out 6 changesets (bug 1816934, bug 1817182, bug 1817179, bug 1817183) for causing dt failures in browser_jsterm_autocomplete_null.js CLOSED TREE
Backed out changeset 17d4c013ed92 (bug 1817183)
Backed out changeset cfed8d9c23f3 (bug 1817183)
Backed out changeset 62fe2f589efe (bug 1817182)
Backed out changeset 557bd773fb85 (bug 1817179)
Backed out changeset 7f8a7865868b (bug 1816934)
Backed out changeset d6c1d4c0d2a0 (bug 1816934)
2023-02-17 10:51:33 -05:00

343 lines
9.8 KiB
JavaScript

/* 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/. */
/*
* This module provides an interface to access DoH configuration - e.g. whether
* DoH is enabled, whether capabilities are enabled, etc. The configuration is
* sourced from either Remote Settings or pref values, with Remote Settings
* being preferred.
*/
const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Preferences: "resource://gre/modules/Preferences.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
});
const kGlobalPrefBranch = "doh-rollout";
var kRegionPrefBranch;
const kConfigPrefs = {
kEnabledPref: "enabled",
kProvidersPref: "provider-list",
kTRRSelectionEnabledPref: "trr-selection.enabled",
kTRRSelectionProvidersPref: "trr-selection.provider-list",
kTRRSelectionCommitResultPref: "trr-selection.commit-result",
kProviderSteeringEnabledPref: "provider-steering.enabled",
kProviderSteeringListPref: "provider-steering.provider-list",
};
const kPrefChangedTopic = "nsPref:changed";
const gProvidersCollection = RemoteSettings("doh-providers");
const gConfigCollection = RemoteSettings("doh-config");
function getPrefValueRegionFirst(prefName) {
let regionalPrefName = `${kRegionPrefBranch}.${prefName}`;
let regionalPrefValue = lazy.Preferences.get(regionalPrefName);
if (regionalPrefValue !== undefined) {
return regionalPrefValue;
}
return lazy.Preferences.get(`${kGlobalPrefBranch}.${prefName}`);
}
function getProviderListFromPref(prefName) {
let prefVal = getPrefValueRegionFirst(prefName);
if (prefVal) {
try {
return JSON.parse(prefVal);
} catch (e) {
console.error(`DoH provider list not a valid JSON array: ${prefName}`);
}
}
return undefined;
}
// Generate a base config object with getters that return pref values. When
// Remote Settings values become available, a new config object will be
// generated from this and specific fields will be replaced by the RS value.
// If we use a class to store base config and instantiate new config objects
// from it, we lose the ability to override getters because they are defined
// as non-configureable properties on class instances. So just use a function.
function makeBaseConfigObject() {
function makeConfigProperty({
obj,
propName,
defaultVal,
prefName,
isProviderList,
}) {
let prefFn = isProviderList
? getProviderListFromPref
: getPrefValueRegionFirst;
let overridePropName = "_" + propName;
Object.defineProperty(obj, propName, {
get() {
// If a pref value exists, it gets top priority. Otherwise, if it has an
// explicitly set value (from Remote Settings), we return that.
let prefVal = prefFn(prefName);
if (prefVal !== undefined) {
return prefVal;
}
if (this[overridePropName] !== undefined) {
return this[overridePropName];
}
return defaultVal;
},
set(val) {
this[overridePropName] = val;
},
});
}
let newConfig = {
get fallbackProviderURI() {
return this.providerList[0]?.uri;
},
trrSelection: {},
providerSteering: {},
};
makeConfigProperty({
obj: newConfig,
propName: "enabled",
defaultVal: false,
prefName: kConfigPrefs.kEnabledPref,
isProviderList: false,
});
makeConfigProperty({
obj: newConfig,
propName: "providerList",
defaultVal: [],
prefName: kConfigPrefs.kProvidersPref,
isProviderList: true,
});
makeConfigProperty({
obj: newConfig.trrSelection,
propName: "enabled",
defaultVal: false,
prefName: kConfigPrefs.kTRRSelectionEnabledPref,
isProviderList: false,
});
makeConfigProperty({
obj: newConfig.trrSelection,
propName: "commitResult",
defaultVal: false,
prefName: kConfigPrefs.kTRRSelectionCommitResultPref,
isProviderList: false,
});
makeConfigProperty({
obj: newConfig.trrSelection,
propName: "providerList",
defaultVal: [],
prefName: kConfigPrefs.kTRRSelectionProvidersPref,
isProviderList: true,
});
makeConfigProperty({
obj: newConfig.providerSteering,
propName: "enabled",
defaultVal: false,
prefName: kConfigPrefs.kProviderSteeringEnabledPref,
isProviderList: false,
});
makeConfigProperty({
obj: newConfig.providerSteering,
propName: "providerList",
defaultVal: [],
prefName: kConfigPrefs.kProviderSteeringListPref,
isProviderList: true,
});
return newConfig;
}
export const DoHConfigController = {
initComplete: null,
_resolveInitComplete: null,
// This field always contains the current config state, for
// consumer use.
currentConfig: makeBaseConfigObject(),
// Loads the client's region via Region.sys.mjs. This might mean waiting
// until the region is available.
async loadRegion() {
await new Promise(resolve => {
let homeRegion = lazy.Preferences.get(`${kGlobalPrefBranch}.home-region`);
if (homeRegion) {
kRegionPrefBranch = `${kGlobalPrefBranch}.${homeRegion.toLowerCase()}`;
resolve();
return;
}
let updateRegionAndResolve = () => {
kRegionPrefBranch = `${kGlobalPrefBranch}.${lazy.Region.home.toLowerCase()}`;
lazy.Preferences.set(
`${kGlobalPrefBranch}.home-region`,
lazy.Region.home
);
resolve();
};
if (lazy.Region.home) {
updateRegionAndResolve();
return;
}
Services.obs.addObserver(function obs(sub, top, data) {
Services.obs.removeObserver(obs, lazy.Region.REGION_TOPIC);
updateRegionAndResolve();
}, lazy.Region.REGION_TOPIC);
});
// Finally, reload config.
await this.updateFromRemoteSettings();
},
async init() {
await this.loadRegion();
Services.prefs.addObserver(`${kGlobalPrefBranch}.`, this, true);
gProvidersCollection.on("sync", this.updateFromRemoteSettings);
gConfigCollection.on("sync", this.updateFromRemoteSettings);
this._resolveInitComplete();
},
// Useful for tests to set prior state before init()
async _uninit() {
await this.initComplete;
Services.prefs.removeObserver(`${kGlobalPrefBranch}`, this);
gProvidersCollection.off("sync", this.updateFromRemoteSettings);
gConfigCollection.off("sync", this.updateFromRemoteSettings);
this.initComplete = new Promise(resolve => {
this._resolveInitComplete = resolve;
});
},
observe(subject, topic, data) {
switch (topic) {
case kPrefChangedTopic:
let allowedPrefs = Object.getOwnPropertyNames(kConfigPrefs).map(
k => kConfigPrefs[k]
);
if (
!allowedPrefs.some(pref =>
[
`${kRegionPrefBranch}.${pref}`,
`${kGlobalPrefBranch}.${pref}`,
].includes(data)
)
) {
break;
}
this.notifyNewConfig();
break;
}
},
QueryInterface: ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]),
// Creates new config object from currently available
// Remote Settings values.
async updateFromRemoteSettings() {
let providers = await gProvidersCollection.get();
let config = await gConfigCollection.get();
let providersById = new Map();
providers.forEach(p => providersById.set(p.id, p));
let configByRegion = new Map();
config.forEach(c => {
c.id = c.id.toLowerCase();
configByRegion.set(c.id, c);
});
let homeRegion = lazy.Preferences.get(`${kGlobalPrefBranch}.home-region`);
let localConfig =
configByRegion.get(homeRegion?.toLowerCase()) ||
configByRegion.get("global");
// Make a new config object first, mutate it as needed, then synchronously
// replace the currentConfig object at the end to ensure atomicity.
let newConfig = makeBaseConfigObject();
if (!localConfig) {
DoHConfigController.currentConfig = newConfig;
DoHConfigController.notifyNewConfig();
return;
}
if (localConfig.rolloutEnabled) {
newConfig.enabled = true;
}
let parseProviderList = (list, checkFn) => {
let parsedList = [];
list?.split(",")?.forEach(p => {
p = p.trim();
if (!p.length) {
return;
}
p = providersById.get(p);
if (!p || (checkFn && !checkFn(p))) {
return;
}
parsedList.push(p);
});
return parsedList;
};
let regionalProviders = parseProviderList(localConfig.providers);
if (regionalProviders?.length) {
newConfig.providerList = regionalProviders;
}
if (localConfig.steeringEnabled) {
let steeringProviders = parseProviderList(
localConfig.steeringProviders,
p => p.canonicalName?.length
);
if (steeringProviders?.length) {
newConfig.providerSteering.providerList = steeringProviders;
newConfig.providerSteering.enabled = true;
}
}
if (localConfig.autoDefaultEnabled) {
let defaultProviders = parseProviderList(
localConfig.autoDefaultProviders
);
if (defaultProviders?.length) {
newConfig.trrSelection.providerList = defaultProviders;
newConfig.trrSelection.enabled = true;
}
}
// Finally, update the currentConfig object synchronously.
DoHConfigController.currentConfig = newConfig;
DoHConfigController.notifyNewConfig();
},
kConfigUpdateTopic: "doh-config-updated",
notifyNewConfig() {
Services.obs.notifyObservers(null, this.kConfigUpdateTopic);
},
};
DoHConfigController.initComplete = new Promise(resolve => {
DoHConfigController._resolveInitComplete = resolve;
});
DoHConfigController.init();