1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* globals browser, module */
10 constructor(availableOverrides) {
11 this.OVERRIDE_PREF = "perform_ua_overrides";
13 this._overridesEnabled = true;
15 this._availableOverrides = availableOverrides;
16 this._activeListeners = new Map();
19 bindAboutCompatBroker(broker) {
20 this._aboutCompatBroker = broker;
24 browser.aboutConfigPrefs.onPrefChange.addListener(() => {
25 this.checkOverridePref();
26 }, this.OVERRIDE_PREF);
27 this.checkOverridePref();
31 browser.aboutConfigPrefs.getPref(this.OVERRIDE_PREF).then(value => {
32 if (value === undefined) {
33 browser.aboutConfigPrefs.setPref(this.OVERRIDE_PREF, true);
34 } else if (value === false) {
35 this.unregisterUAOverrides();
37 this.registerUAOverrides();
42 getAvailableOverrides() {
43 return this._availableOverrides;
47 return this._overridesEnabled;
50 enableOverride(override) {
51 if (override.active) {
55 const { blocks, matches, telemetryKey, uaTransformer } = override.config;
56 const listener = details => {
57 // We set the "used" telemetry key if the user would have had the
58 // override applied, regardless of whether it is actually applied.
59 if (!details.frameId && override.shouldSendDetailedTelemetry) {
60 // For now, we only care about Telemetry on Fennec, where telemetry
61 // is sent in Java code (as part of the core ping). That code must
62 // be aware of each key we send, which we send as a SharedPreference.
63 browser.sharedPreferences.setBoolPref(`${telemetryKey}Used`, true);
66 // Don't actually override the UA for an experiment if the user is not
67 // part of the experiment (unless they force-enabed the override).
69 !override.config.experiment ||
70 override.experimentActive ||
71 override.permanentPrefEnabled === true
73 for (const header of details.requestHeaders) {
74 if (header.name.toLowerCase() === "user-agent") {
75 header.value = uaTransformer(header.value);
79 return { requestHeaders: details.requestHeaders };
82 browser.webRequest.onBeforeSendHeaders.addListener(
85 ["blocking", "requestHeaders"]
88 const listeners = { onBeforeSendHeaders: listener };
90 const blistener = details => {
91 return { cancel: true };
94 browser.webRequest.onBeforeRequest.addListener(
100 listeners.onBeforeRequest = blistener;
102 this._activeListeners.set(override, listeners);
103 override.active = true;
105 // If telemetry is being collected, note the addon version.
107 const { version } = browser.runtime.getManifest();
108 browser.sharedPreferences.setCharPref(`${telemetryKey}Version`, version);
111 // If collecting detailed telemetry on the override, note that it was activated.
112 if (override.shouldSendDetailedTelemetry) {
113 browser.sharedPreferences.setBoolPref(`${telemetryKey}Ready`, true);
117 onOverrideConfigChanged(override) {
118 // Check whether the override should be hidden from about:compat.
119 override.hidden = override.config.hidden;
121 // Also hide if the override is in an experiment the user is not part of.
122 if (override.config.experiment && !override.experimentActive) {
123 override.hidden = true;
126 // Setting the override's permanent pref overrules whether it is hidden.
127 if (override.permanentPrefEnabled !== undefined) {
128 override.hidden = !override.permanentPrefEnabled;
131 // Also check whether the override should be active.
132 let shouldBeActive = true;
134 // Overrides can be force-deactivated by their permanent preference.
135 if (override.permanentPrefEnabled === false) {
136 shouldBeActive = false;
139 // Only send detailed telemetry if the user is actively in an experiment or
140 // has opted into an experimental feature.
141 override.shouldSendDetailedTelemetry =
142 override.config.telemetryKey &&
143 (override.experimentActive || override.permanentPrefEnabled);
145 // Overrides gated behind an experiment the user is not part of do not
146 // have to be activated, unless they are gathering telemetry, or the
147 // user has force-enabled them with their permanent pref.
149 override.config.experiment &&
150 !override.experimentActive &&
151 !override.config.telemetryKey &&
152 override.permanentPrefEnabled !== true
154 shouldBeActive = false;
157 if (shouldBeActive) {
158 this.enableOverride(override);
160 this.disableOverride(override);
163 if (this._overridesEnabled) {
164 this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
165 overridesChanged: this._aboutCompatBroker.filterOverrides(
166 this._availableOverrides
172 async registerUAOverrides() {
173 const platformMatches = ["all"];
174 let platformInfo = await browser.runtime.getPlatformInfo();
175 platformMatches.push(platformInfo.os == "android" ? "android" : "desktop");
177 for (const override of this._availableOverrides) {
178 if (platformMatches.includes(override.platform)) {
179 override.availableOnPlatform = true;
181 // Note whether the user is actively in the override's experiment (if any).
182 override.experimentActive = false;
183 const experiment = override.config.experiment;
185 // We expect the definition to have either one string for 'experiment'
186 // (just one branch) or an array of strings (multiple branches). So
187 // here we turn the string case into a one-element array for the loop.
188 const branches = Array.isArray(experiment)
191 for (const branch of branches) {
192 if (await browser.experiments.isActive(branch)) {
193 override.experimentActive = true;
199 // If there is a specific about:config preference governing
200 // this override, monitor its state.
201 const pref = override.config.permanentPref;
202 override.permanentPrefEnabled =
203 pref && (await browser.aboutConfigPrefs.getPref(pref));
205 const checkOverridePref = () => {
206 browser.aboutConfigPrefs.getPref(pref).then(value => {
207 override.permanentPrefEnabled = value;
208 this.onOverrideConfigChanged(override);
211 browser.aboutConfigPrefs.onPrefChange.addListener(
217 this.onOverrideConfigChanged(override);
221 this._overridesEnabled = true;
222 this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
223 overridesChanged: this._aboutCompatBroker.filterOverrides(
224 this._availableOverrides
229 unregisterUAOverrides() {
230 for (const override of this._availableOverrides) {
231 this.disableOverride(override);
234 this._overridesEnabled = false;
235 this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
236 overridesChanged: false,
240 disableOverride(override) {
241 if (!override.active) {
245 const listeners = this._activeListeners.get(override);
246 for (const [name, listener] of Object.entries(listeners)) {
247 browser.webRequest[name].removeListener(listener);
249 override.active = false;
250 this._activeListeners.delete(override);
254 module.exports = UAOverrides;