summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat/lib/ua_overrides.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/webcompat/lib/ua_overrides.js')
-rw-r--r--browser/extensions/webcompat/lib/ua_overrides.js210
1 files changed, 210 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/lib/ua_overrides.js b/browser/extensions/webcompat/lib/ua_overrides.js
new file mode 100644
index 0000000000..2426293f3f
--- /dev/null
+++ b/browser/extensions/webcompat/lib/ua_overrides.js
@@ -0,0 +1,210 @@
+/* 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/. */
+
+"use strict";
+
+/* globals browser, module */
+
+class UAOverrides {
+ constructor(availableOverrides) {
+ this.OVERRIDE_PREF = "perform_ua_overrides";
+
+ this._overridesEnabled = true;
+
+ this._availableOverrides = availableOverrides;
+ this._activeListeners = new Map();
+ }
+
+ bindAboutCompatBroker(broker) {
+ this._aboutCompatBroker = broker;
+ }
+
+ bootup() {
+ browser.aboutConfigPrefs.onPrefChange.addListener(() => {
+ this.checkOverridePref();
+ }, this.OVERRIDE_PREF);
+ this.checkOverridePref();
+ }
+
+ checkOverridePref() {
+ browser.aboutConfigPrefs.getPref(this.OVERRIDE_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(this.OVERRIDE_PREF, true);
+ } else if (value === false) {
+ this.unregisterUAOverrides();
+ } else {
+ this.registerUAOverrides();
+ }
+ });
+ }
+
+ getAvailableOverrides() {
+ return this._availableOverrides;
+ }
+
+ isEnabled() {
+ return this._overridesEnabled;
+ }
+
+ enableOverride(override) {
+ if (override.active) {
+ return;
+ }
+
+ const { blocks, matches, uaTransformer } = override.config;
+ const listener = details => {
+ // Don't actually override the UA for an experiment if the user is not
+ // part of the experiment (unless they force-enabed the override).
+ if (
+ !override.config.experiment ||
+ override.permanentPrefEnabled === true
+ ) {
+ for (const header of details.requestHeaders) {
+ if (header.name.toLowerCase() === "user-agent") {
+ // Don't override the UA if we're on a mobile device that has the
+ // "Request Desktop Site" mode enabled. The UA for the desktop mode
+ // is set inside Gecko with a simple string replace, so we can use
+ // that as a check, see https://searchfox.org/mozilla-central/rev/89d33e1c3b0a57a9377b4815c2f4b58d933b7c32/mobile/android/chrome/geckoview/GeckoViewSettingsChild.js#23-28
+ let isMobileWithDesktopMode =
+ override.currentPlatform == "android" &&
+ header.value.includes("X11; Linux x86_64");
+
+ if (!isMobileWithDesktopMode) {
+ header.value = uaTransformer(header.value);
+ }
+ }
+ }
+ }
+ return { requestHeaders: details.requestHeaders };
+ };
+
+ browser.webRequest.onBeforeSendHeaders.addListener(
+ listener,
+ { urls: matches },
+ ["blocking", "requestHeaders"]
+ );
+
+ const listeners = { onBeforeSendHeaders: listener };
+ if (blocks) {
+ const blistener = details => {
+ return { cancel: true };
+ };
+
+ browser.webRequest.onBeforeRequest.addListener(
+ blistener,
+ { urls: blocks },
+ ["blocking"]
+ );
+
+ listeners.onBeforeRequest = blistener;
+ }
+ this._activeListeners.set(override, listeners);
+ override.active = true;
+ }
+
+ onOverrideConfigChanged(override) {
+ // Check whether the override should be hidden from about:compat.
+ override.hidden = override.config.hidden;
+
+ // Setting the override's permanent pref overrules whether it is hidden.
+ if (override.permanentPrefEnabled !== undefined) {
+ override.hidden = !override.permanentPrefEnabled;
+ }
+
+ // Also check whether the override should be active.
+ let shouldBeActive = true;
+
+ // Overrides can be force-deactivated by their permanent preference.
+ if (override.permanentPrefEnabled === false) {
+ shouldBeActive = false;
+ }
+
+ // Overrides gated behind an experiment the user is not part of do not
+ // have to be activated, unless they are gathering telemetry, or the
+ // user has force-enabled them with their permanent pref.
+ if (override.config.experiment && override.permanentPrefEnabled !== true) {
+ shouldBeActive = false;
+ }
+
+ if (shouldBeActive) {
+ this.enableOverride(override);
+ } else {
+ this.disableOverride(override);
+ }
+
+ if (this._overridesEnabled) {
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: this._aboutCompatBroker.filterOverrides(
+ this._availableOverrides
+ ),
+ });
+ }
+ }
+
+ async registerUAOverrides() {
+ const platformMatches = ["all"];
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ platformMatches.push(platformInfo.os == "android" ? "android" : "desktop");
+
+ for (const override of this._availableOverrides) {
+ if (platformMatches.includes(override.platform)) {
+ override.availableOnPlatform = true;
+ override.currentPlatform = platformInfo.os;
+
+ // If there is a specific about:config preference governing
+ // this override, monitor its state.
+ const pref = override.config.permanentPref;
+ override.permanentPrefEnabled =
+ pref && (await browser.aboutConfigPrefs.getPref(pref));
+ if (pref) {
+ const checkOverridePref = () => {
+ browser.aboutConfigPrefs.getPref(pref).then(value => {
+ override.permanentPrefEnabled = value;
+ this.onOverrideConfigChanged(override);
+ });
+ };
+ browser.aboutConfigPrefs.onPrefChange.addListener(
+ checkOverridePref,
+ pref
+ );
+ }
+
+ this.onOverrideConfigChanged(override);
+ }
+ }
+
+ this._overridesEnabled = true;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: this._aboutCompatBroker.filterOverrides(
+ this._availableOverrides
+ ),
+ });
+ }
+
+ unregisterUAOverrides() {
+ for (const override of this._availableOverrides) {
+ this.disableOverride(override);
+ }
+
+ this._overridesEnabled = false;
+ this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
+ overridesChanged: false,
+ });
+ }
+
+ disableOverride(override) {
+ if (!override.active) {
+ return;
+ }
+
+ const listeners = this._activeListeners.get(override);
+ for (const [name, listener] of Object.entries(listeners)) {
+ browser.webRequest[name].removeListener(listener);
+ }
+ override.active = false;
+ this._activeListeners.delete(override);
+ }
+}
+
+module.exports = UAOverrides;