Bug 1338586 - Ship the WebCompat GoFaster extension to Fennec draft
authorDennis Schubert <dschubert@mozilla.com>
Fri, 03 Aug 2018 13:34:47 +0200
changeset 826278 a1416c5cf18c69051e74bb098e9b2df91dd5c3f5
parent 825858 a2d65d03e46a9a42b5bee5c2a7864d3f987a8ca7
push id118276
push userbmo:dschubert@mozilla.com
push dateFri, 03 Aug 2018 11:44:43 +0000
bugs1338586
milestone63.0a1
Bug 1338586 - Ship the WebCompat GoFaster extension to Fennec
mobile/android/extensions/moz.build
mobile/android/extensions/webcompat/bootstrap.js
mobile/android/extensions/webcompat/content/data/ua_overrides.jsm
mobile/android/extensions/webcompat/content/lib/ua_overrider.jsm
mobile/android/extensions/webcompat/install.rdf.in
mobile/android/extensions/webcompat/jar.mn
mobile/android/extensions/webcompat/moz.build
mobile/android/extensions/webcompat/test/.eslintrc.js
mobile/android/extensions/webcompat/test/browser.ini
mobile/android/extensions/webcompat/test/browser_check_installed.js
mobile/android/extensions/webcompat/test/browser_overrider.js
mobile/android/extensions/webcompat/webextension/background.js
mobile/android/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
mobile/android/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
mobile/android/extensions/webcompat/webextension/manifest.json
--- a/mobile/android/extensions/moz.build
+++ b/mobile/android/extensions/moz.build
@@ -2,13 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox for Android', 'General')
 
+DIRS += [
+    'webcompat',
+]
+
 # Only include the following system add-ons if building Nightly
 if CONFIG['NIGHTLY_BUILD']:
     DIRS += [
         'gws-and-facebook-chrome-spoof',
     ]
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/bootstrap.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const PREF_BRANCH = "extensions.webcompat.";
+const PREF_DEFAULTS = {
+  perform_injections: true,
+  perform_ua_overrides: true
+};
+
+const INJECTIONS_ENABLE_PREF_NAME = "extensions.webcompat.perform_injections";
+
+const BROWSER_STARTUP_FINISHED_TOPIC = "browser-delayed-startup-finished";
+
+const UA_OVERRIDES_INIT_TOPIC = "useragentoverrides-initialized";
+const UA_ENABLE_PREF_NAME = "extensions.webcompat.perform_ua_overrides";
+
+ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
+ChromeUtils.defineModuleGetter(this, "UAOverrides", "chrome://webcompat/content/data/ua_overrides.jsm");
+
+let overrider;
+let webextensionPort;
+
+function InjectionsEnablePrefObserver() {
+  let isEnabled = Services.prefs.getBoolPref(INJECTIONS_ENABLE_PREF_NAME);
+  webextensionPort.postMessage({
+    type: "injection-pref-changed",
+    prefState: isEnabled
+  });
+}
+
+function UAEnablePrefObserver() {
+  let isEnabled = Services.prefs.getBoolPref(UA_ENABLE_PREF_NAME);
+  overrider.setShouldOverride(isEnabled);
+}
+
+function setDefaultPrefs() {
+  const branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
+  for (const [key, val] of Object.entries(PREF_DEFAULTS)) {
+    // If someone beat us to setting a default, don't overwrite it.
+    if (branch.getPrefType(key) !== branch.PREF_INVALID) {
+      continue;
+    }
+
+    switch (typeof val) {
+      case "boolean":
+        branch.setBoolPref(key, val);
+        break;
+      case "number":
+        branch.setIntPref(key, val);
+        break;
+      case "string":
+        branch.setCharPref(key, val);
+        break;
+    }
+  }
+}
+
+this.install = function() {};
+this.uninstall = function() {};
+
+this.startup = function({webExtension}) {
+  setDefaultPrefs();
+
+  // Intentionally reset the preference on every browser restart to avoid site
+  // breakage by accidentally toggled preferences or by leaving it off after
+  // debugging a site.
+  Services.prefs.clearUserPref(INJECTIONS_ENABLE_PREF_NAME);
+  Services.prefs.addObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
+
+  Services.prefs.clearUserPref(UA_ENABLE_PREF_NAME);
+  Services.prefs.addObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
+
+  // Listen to the useragentoverrides-initialized notification we get and
+  // initialize our overrider there. This is done to avoid slowing down the
+  // apparent startup process, since we avoid loading anything before the first
+  // window is visible to the user. See bug 1371442 for details.
+  let uaStartupObserver = {
+    observe(aSubject, aTopic, aData) {
+      if (aTopic !== UA_OVERRIDES_INIT_TOPIC) {
+        return;
+      }
+
+      Services.obs.removeObserver(this, UA_OVERRIDES_INIT_TOPIC);
+      overrider = new UAOverrider(UAOverrides);
+      overrider.init();
+    }
+  };
+  Services.obs.addObserver(uaStartupObserver, UA_OVERRIDES_INIT_TOPIC);
+
+  // Observe browser-delayed-startup-finished and only initialize our embedded
+  // WebExtension after that. Otherwise, we'd try to initialize as soon as the
+  // browser starts up, which adds a heavy startup penalty.
+  let appStartupObserver = {
+    observe(aSubject, aTopic, aData) {
+      webExtension.startup().then((api) => {
+        api.browser.runtime.onConnect.addListener((port) => {
+          webextensionPort = port;
+        });
+
+        return Promise.resolve();
+      }).catch((ex) => {
+        console.error(ex);
+      });
+      Services.obs.removeObserver(this, BROWSER_STARTUP_FINISHED_TOPIC);
+    }
+  };
+  Services.obs.addObserver(appStartupObserver, BROWSER_STARTUP_FINISHED_TOPIC);
+};
+
+this.shutdown = function() {
+  Services.prefs.removeObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
+  Services.prefs.removeObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
+};
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/content/data/ua_overrides.jsm
@@ -0,0 +1,65 @@
+/* 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/. */
+
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const UAOverrides = [
+
+  /*
+   * This is a dummy override that applies a Chrome UA to a dummy site that
+   * blocks all browsers but Chrome.
+   *
+   * This was only put in place to allow QA to test this system addon on an
+   * actual site, since we were not able to find a proper override in time.
+   */
+  {
+    baseDomain: "schub.io",
+    applications: ["firefox", "fennec"],
+    uriMatcher: (uri) => uri.includes("webcompat-addon-testcases.schub.io"),
+    uaTransformer: (originalUA) => {
+      let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+      return `${prefix} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36`;
+    }
+  },
+
+  /*
+   * Bug 1480710 - m.imgur.com - Build UA override
+   * WebCompat issue #13154 - https://webcompat.com/issues/13154
+   *
+   * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
+   * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
+   * receive the correct CSS and JS files.
+   */
+  {
+    baseDomain: "imgur.com",
+    applications: ["fennec"],
+    uriMatcher: (uri) => uri.includes("m.imgur.com"),
+    uaTransformer: (originalUA) => {
+      let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+      return prefix + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36";
+    }
+  },
+
+  /*
+   * Bug 755590 - sites.google.com - top bar doesn't show up in Firefox for Android
+   *
+   * Google Sites does show a different top bar template based on the User Agent.
+   * For Fennec, this results in a broken top bar. Appending Chrome and Mobile Safari
+   * identifiers to the UA results in a correct rendering.
+   */
+  {
+    baseDomain: "google.com",
+    applications: ["fennec"],
+    uriMatcher: (uri) => uri.includes("sites.google.com"),
+    uaTransformer: (originalUA) => {
+      return originalUA + " Chrome/68.0.3440.85 Mobile Safari/537.366";
+    }
+  }
+];
+
+var EXPORTED_SYMBOLS = ["UAOverrides"]; /* exported UAOverrides */
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/content/lib/ua_overrider.jsm
@@ -0,0 +1,127 @@
+/* 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/. */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "UserAgentOverrides", "resource://gre/modules/UserAgentOverrides.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "eTLDService", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService");
+
+class UAOverrider {
+  constructor(overrides) {
+    this._overrides = {};
+    this._shouldOverride = true;
+
+    this.initOverrides(overrides);
+  }
+
+  initOverrides(overrides) {
+    // on xpcshell tests, there is no impleentation for nsIXULAppInfo, so this
+    // might fail there. To have all of our test cases running at all times,
+    // assume they are on Desktop for now.
+    let currentApplication = "firefox";
+    try {
+      currentApplication = Services.appinfo.name.toLowerCase();
+    } catch (_) {}
+
+    for (let override of overrides) {
+      // Firefox for Desktop is the default application for all overrides.
+      if (!override.applications) {
+        override.applications = ["firefox"];
+      }
+
+      // If the current application is not targeted by the override in question,
+      // we can skip adding the override to our checks entirely.
+      if (!override.applications.includes(currentApplication)) {
+        continue;
+      }
+
+      if (!this._overrides[override.baseDomain]) {
+        this._overrides[override.baseDomain] = [];
+      }
+
+      if (!override.uriMatcher) {
+        override.uriMatcher = () => true;
+      }
+
+      this._overrides[override.baseDomain].push(override);
+    }
+  }
+
+  /**
+   * Used for disabling overrides when the pref has been flipped to false.
+   *
+   * Since we no longer use our own event handlers, we check this bool in our
+   * override callback and simply return early if we are not supposed to do
+   * anything.
+   */
+  setShouldOverride(newState) {
+    this._shouldOverride = newState;
+  }
+
+  init() {
+    UserAgentOverrides.addComplexOverride(this.overrideCallback.bind(this));
+  }
+
+  overrideCallback(channel, defaultUA) {
+    if (!this._shouldOverride) {
+      return false;
+    }
+
+    let uaOverride = this.lookupUAOverride(channel.URI, defaultUA);
+    if (uaOverride) {
+      console.log("The user agent has been overridden for compatibility reasons.");
+      return uaOverride;
+    }
+
+    return false;
+  }
+
+  /**
+   * Try to use the eTLDService to get the base domain (will return example.com
+   * for http://foo.bar.example.com/foo/bar).
+   *
+   * However, the eTLDService is a bit picky and throws whenever we pass a
+   * blank host name or an IP into it, see bug 1337785. Since we do not plan on
+   * override UAs for such cases, we simply catch everything and return false.
+   */
+  getBaseDomainFromURI(uri) {
+    try {
+      return eTLDService.getBaseDomain(uri);
+    } catch (_) {
+      return false;
+    }
+  }
+
+  /**
+   * This function returns a User Agent based on the URI passed into. All
+   * override rules are defined in data/ua_overrides.jsm and the required format
+   * is explained there.
+   *
+   * Since it is expected and designed to have more than one override per base
+   * domain, we have to loop over this._overrides[baseDomain], which contains
+   * all available overrides.
+   *
+   * If the uriMatcher function returns true, the uaTransformer function gets
+   * called and its result will be used as the Use Agent for the current
+   * request.
+   *
+   * If there are more than one possible overrides, that is if two or more
+   * uriMatchers would return true, the first one gets applied.
+   */
+  lookupUAOverride(uri, defaultUA) {
+    let baseDomain = this.getBaseDomainFromURI(uri);
+    if (baseDomain && this._overrides[baseDomain]) {
+      for (let uaOverride of this._overrides[baseDomain]) {
+        if (uaOverride.uriMatcher(uri.specIgnoringRef)) {
+          return uaOverride.uaTransformer(defaultUA);
+        }
+      }
+    }
+
+    return false;
+  }
+}
+
+var EXPORTED_SYMBOLS = ["UAOverrider"]; /* exported UAOverrider */
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/install.rdf.in
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>webcompat@mozilla.org</em:id>
+    <em:version>2.0.1</em:version>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+    <em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
+
+    <!-- Firefox Desktop -->
+    <em:targetApplication>
+      <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Firefox for Android -->
+    <em:targetApplication>
+      <Description>
+        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
+        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Web Compat</em:name>
+    <em:description>Urgent post-release fixes for web compatibility.</em:description>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/jar.mn
@@ -0,0 +1,3 @@
+[features/webcompat@mozilla.org] chrome.jar:
+% content webcompat %content/
+  content/ (content/*)
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['webcompat@mozilla.org'] += [
+  'bootstrap.js'
+]
+
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension'] += [
+  'webextension/background.js',
+  'webextension/manifest.json'
+]
+
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['css'] += [
+  'webextension/injections/css/bug0000000-dummy-css-injection.css'
+]
+
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['js'] += [
+  'webextension/injections/js/bug0000000-dummy-js-injection.js',
+  'webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js',
+  'webextension/injections/js/bug1457335-histography.io-ua-change.js',
+  'webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js',
+  'webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js'
+]
+
+FINAL_TARGET_PP_FILES.features['webcompat@mozilla.org'] += [
+  'install.rdf.in'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+  BUG_COMPONENT = ('Web Compatibility Tools', 'Go Faster')
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/test/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+[browser_check_installed.js]
+[browser_overrider.js]
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/test/browser_check_installed.js
@@ -0,0 +1,15 @@
+/* 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";
+
+add_task(async function installed() {
+  let addon = await AddonManager.getAddonByID("webcompat@mozilla.org");
+  isnot(addon, null, "Webcompat addon should exist");
+  is(addon.name, "Web Compat");
+  ok(addon.isCompatible, "Webcompat addon is compatible with Firefox");
+  ok(!addon.appDisabled, "Webcompat addon is not app disabled");
+  ok(addon.isActive, "Webcompat addon is active");
+  is(addon.type, "extension", "Webcompat addon is type extension");
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/test/browser_overrider.js
@@ -0,0 +1,38 @@
+/* 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";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "IOService", "@mozilla.org/network/io-service;1", "nsIIOService");
+
+function getnsIURI(uri) {
+  return IOService.newURI(uri, "utf-8");
+}
+
+add_task(function test() {
+  let overrider = new UAOverrider([
+    {
+      baseDomain: "example.org",
+      uaTransformer: () => "Test UA"
+    }
+  ]);
+
+  let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
+  is(finalUA, "Test UA", "Overrides the UA without a matcher function");
+});
+
+add_task(function test() {
+  let overrider = new UAOverrider([
+    {
+      baseDomain: "example.org",
+      uriMatcher: () => false,
+      uaTransformer: () => "Test UA"
+    }
+  ]);
+
+  let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
+  isnot(finalUA, "Test UA", "Does not override the UA with the matcher returning false");
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/background.js
@@ -0,0 +1,88 @@
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const contentScripts = {
+  universal: [
+    {
+      matches: ["*://webcompat-addon-testcases.schub.io/*"],
+      css: [{file: "injections/css/bug0000000-dummy-css-injection.css"}],
+      js: [{file: "injections/js/bug0000000-dummy-js-injection.js"}],
+      runAt: "document_start"
+    }
+  ],
+  desktop: [
+    {
+      matches: ["https://ib.absa.co.za/*"],
+      js: [{file: "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js"}],
+      runAt: "document_start"
+    },
+    {
+      matches: ["http://histography.io/*"],
+      js: [{file: "injections/js/bug1457335-histography.io-ua-change.js"}],
+      runAt: "document_start"
+    },
+    {
+      matches: ["*://*.bankofamerica.com/*"],
+      js: [{file: "injections/js/bug1472075-bankofamerica.com-ua-change.js"}],
+      runAt: "document_start"
+    },
+    {
+      matches: ["http://202.166.205.141/bbvrs/*"],
+      js: [{file: "injections/js/bug1472081-election.gov.np-window.sidebar-shim.js"}],
+      runAt: "document_start",
+      allFrames: true
+    }
+  ],
+  fennec: []
+};
+
+/* globals browser */
+
+let port = browser.runtime.connect();
+let registeredContentScripts = [];
+
+async function registerContentScripts() {
+  let platform = "desktop";
+  let platformInfo = await browser.runtime.getPlatformInfo();
+  if (platformInfo.os == "android") {
+    platform = "fennec";
+  }
+
+  let targetContentScripts = contentScripts.universal.concat(contentScripts[platform]);
+  targetContentScripts.forEach(async (contentScript) => {
+    try {
+      let handle = await browser.contentScripts.register(contentScript);
+      registeredContentScripts.push(handle);
+    } catch (ex) {
+      console.error("Registering WebCompat GoFaster content scripts failed: ", ex);
+    }
+  });
+}
+
+function unregisterContentScripts() {
+  registeredContentScripts.forEach((contentScript) => {
+    contentScript.unregister();
+  });
+}
+
+port.onMessage.addListener((message) => {
+  switch (message.type) {
+    case "injection-pref-changed":
+      if (message.prefState) {
+        registerContentScripts();
+      } else {
+        unregisterContentScripts();
+      }
+      break;
+  }
+});
+
+/**
+ * Note that we reset all preferences on extension startup, so the injections will
+ * never be disabled when this loads up. Because of that, we can simply register
+ * right away.
+ */
+registerContentScripts();
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
@@ -0,0 +1,3 @@
+#css-injection.red {
+  background-color: #0f0;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
@@ -0,0 +1,11 @@
+"use strict";
+
+/* globals exportFunction */
+
+Object.defineProperty(window.wrappedJSObject, "isTestFeatureSupported", {
+  get: exportFunction(function() {
+    return true;
+  }, window),
+
+  set: exportFunction(function() {}, window)
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
@@ -0,0 +1,25 @@
+"use strict";
+
+/**
+ * Bug 1452707 - Build site patch for ib.absa.co.za
+ * WebCompat issue #16401 - https://webcompat.com/issues/16401
+ *
+ * The online banking at ib.absa.co.za detect if window.controllers is a
+ * non-falsy value to detect if the current browser is Firefox or something
+ * else. In bug 1448045, this shim has been disabled for Firefox Nightly 61+,
+ * which breaks the UA detection on this site and results in a "Browser
+ * unsuppored" error message.
+ *
+ * This site patch simply sets window.controllers to a string, resulting in
+ * their check to work again.
+ */
+
+/* globals exportFunction */
+
+Object.defineProperty(window.wrappedJSObject, "controllers", {
+  get: exportFunction(function() {
+    return "window.controllers has been shimmed with a string to get this site's browser detection to work in Firefox 61+.";
+  }, window),
+
+  set: exportFunction(function() {}, window)
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
@@ -0,0 +1,30 @@
+"use strict";
+
+/**
+ * Bug 1457335 - histography.io - Override UA & navigator.vendor
+ * WebCompat issue #1804 - https://webcompat.com/issues/1804
+ *
+ * This site is using a strict matching of navigator.userAgent and
+ * navigator.vendor to allow access for Safari or Chrome. Here, we set the
+ * values appropriately so we get recognized as Chrome.
+ */
+
+/* globals exportFunction */
+
+const CHROME_UA = navigator.userAgent + " Chrome for WebCompat";
+
+Object.defineProperty(window.wrappedJSObject.navigator, "userAgent", {
+  get: exportFunction(function() {
+    return CHROME_UA;
+  }, window),
+
+  set: exportFunction(function() {}, window)
+});
+
+Object.defineProperty(window.wrappedJSObject.navigator, "vendor", {
+  get: exportFunction(function() {
+    return "Google Inc.";
+  }, window),
+
+  set: exportFunction(function() {}, window)
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/**
+ * Bug 1472075 - Build UA override for Bank of America for OSX & Linux
+ * WebCompat issue #2787 - https://webcompat.com/issues/2787
+ *
+ * BoA is showing a red warning to Linux and macOS users, while accepting
+ * Windows users without warning. From our side, there is no difference here
+ * and we receive a lot of user complains about the warnings, so we spoof
+ * as Firefox on Windows in those cases.
+ */
+
+/* globals exportFunction */
+
+if (!navigator.platform.includes("Win") && !navigator.userAgent.includes("Android")) {
+  const WINDOWS_UA = navigator.userAgent.replace(/\(.*; rv:/i, "(Windows NT 10.0; Win64; x64; rv:");
+
+  Object.defineProperty(window.wrappedJSObject.navigator, "userAgent", {
+    get: exportFunction(function() {
+      return WINDOWS_UA;
+    }, window),
+
+    set: exportFunction(function() {}, window)
+  });
+
+  Object.defineProperty(window.wrappedJSObject.navigator, "appVersion", {
+    get: exportFunction(function() {
+      return "appVersion";
+    }, window),
+
+    set: exportFunction(function() {}, window)
+  });
+
+  Object.defineProperty(window.wrappedJSObject.navigator, "platform", {
+    get: exportFunction(function() {
+      return "Win64";
+    }, window),
+
+    set: exportFunction(function() {}, window)
+  });
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
@@ -0,0 +1,21 @@
+"use strict";
+
+/**
+ * Bug 1472081 - election.gov.np - Override window.sidebar with something falsey
+ * WebCompat issue #11622 - https://webcompat.com/issues/11622
+ *
+ * This site is blocking onmousedown and onclick if window.sidebar is something
+ * that evaluates to true, rendering the form fields unusable. This patch
+ * overrides window.sidebar with false, so the blockign event handlers won't
+ * get registered.
+ */
+
+/* globals exportFunction */
+
+Object.defineProperty(window.wrappedJSObject, "sidebar", {
+  get: exportFunction(function() {
+    return false;
+  }, window),
+
+  set: exportFunction(function() {}, window)
+});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/webextension/manifest.json
@@ -0,0 +1,23 @@
+{
+  "manifest_version": 2,
+  "name": "Web Compat",
+  "description": "Urgent post-release fixes for web compatibility.",
+  "version": "2.0",
+
+  "applications": {
+    "gecko": {
+      "id": "webcompat@mozilla.org",
+      "strict_min_version": "59.0b5"
+    }
+  },
+
+  "permissions": [
+    "<all_urls>"
+  ],
+
+  "background": {
+    "scripts": [
+      "background.js"
+    ]
+  }
+}