Bug 1308271 - Import sources of the WebCompat Go Faster add-on V1. r?felipe
MozReview-Commit-ID: 58iV4MqTeKA
--- a/browser/extensions/webcompat/bootstrap.js
+++ b/browser/extensions/webcompat/bootstrap.js
@@ -1,9 +1,73 @@
/* 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";
-function startup() {}
-function shutdown() {}
-function install() {}
-function uninstall() {}
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const PREF_BRANCH = "extensions.webcompat.";
+const PREF_DEFAULTS = {perform_ua_overrides: true};
+
+const UA_ENABLE_PREF_NAME = "extensions.webcompat.perform_ua_overrides";
+
+XPCOMUtils.defineLazyModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UAOverrides", "chrome://webcompat/content/data/ua_overrides.jsm");
+
+let overrider;
+
+function UAEnablePrefObserver() {
+ let isEnabled = Services.prefs.getBoolPref(UA_ENABLE_PREF_NAME);
+ if (isEnabled && !overrider) {
+ overrider = new UAOverrider(UAOverrides);
+ overrider.init();
+ } else if (!isEnabled && overrider) {
+ overrider.uninit();
+ overrider = null;
+ }
+}
+
+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(UA_ENABLE_PREF_NAME);
+ Services.prefs.addObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver, false);
+
+ overrider = new UAOverrider(UAOverrides);
+ overrider.init();
+};
+
+this.shutdown = function() {
+ Services.prefs.removeObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
+
+ if (overrider) {
+ overrider.uninit();
+ }
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/content/data/ua_overrides.jsm
@@ -0,0 +1,60 @@
+/* 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 is an array of objects that specify user agent overrides. Each object
+ * can have three attributes:
+ *
+ * * `baseDomain`, required: The base domain that further checks and user
+ * agents override are applied to. This does not include subdomains.
+ * * `uriMatcher`: Function that gets the requested URI passed in the first
+ * argument and needs to return boolean whether or not the override should
+ * be applied. If not provided, the user agent override will be applied
+ * every time.
+ * * `uaTransformer`, required: Function that gets the original Firefox user
+ * agent passed as its first argument and needs to return a string that
+ * will be used as the the user agent for this URI.
+ *
+ * Examples:
+ *
+ * Gets applied for all requests to mozilla.org and subdomains:
+ *
+ * ```
+ * {
+ * baseDomain: "mozilla.org",
+ * uaTransformer: (originalUA) => `Ohai Mozilla, it's me, ${originalUA}`
+ * }
+ * ```
+ *
+ * Applies to *.example.com/app/*:
+ *
+ * ```
+ * {
+ * baseDomain: "example.com",
+ * uriMatcher: (uri) => uri.includes("/app/"),
+ * uaTransformer: (originalUA) => originalUA.replace("Firefox", "Otherfox")
+ * }
+ * ```
+ */
+
+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",
+ uriMatcher: (uri) => uri.includes("webcompat-ua-dummy.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`;
+ }
+ }
+];
+
+this.EXPORTED_SYMBOLS = ["UAOverrides"]; /* exported UAOverrides */
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/content/lib/ua_overrider.jsm
@@ -0,0 +1,122 @@
+/* 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/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Console.jsm");
+
+const DefaultUA = Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).userAgent;
+const NS_HTTP_ON_USERAGENT_REQUEST_TOPIC = "http-on-useragent-request";
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "eTLDService", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService");
+
+class UAOverrider {
+ constructor(overrides) {
+ this._overrides = {};
+ this._overrideForURICache = new Map();
+
+ this.initOverrides(overrides);
+ }
+
+ initOverrides(overrides) {
+ for (let override of overrides) {
+ if (!this._overrides[override.baseDomain]) {
+ this._overrides[override.baseDomain] = [];
+ }
+
+ if (!override.uriMatcher) {
+ override.uriMatcher = () => true;
+ }
+
+ this._overrides[override.baseDomain].push(override);
+ }
+ }
+
+ init() {
+ Services.obs.addObserver(this, NS_HTTP_ON_USERAGENT_REQUEST_TOPIC, false);
+ }
+
+ uninit() {
+ Services.obs.removeObserver(this, NS_HTTP_ON_USERAGENT_REQUEST_TOPIC);
+ }
+
+ observe(subject, topic) {
+ if (topic !== NS_HTTP_ON_USERAGENT_REQUEST_TOPIC) {
+ return;
+ }
+
+ let channel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+ let uaOverride = this.getUAForURI(channel.URI);
+
+ if (uaOverride) {
+ console.log("The user agent has been overridden for compatibility reasons.");
+ channel.setRequestHeader("User-Agent", uaOverride, false);
+ }
+ }
+
+ getUAForURI(uri) {
+ let bareUri = uri.specIgnoringRef;
+ if (this._overrideForURICache.has(bareUri)) {
+ // Although the cache could have an entry to a bareUri, `false` is also
+ // a value that could be cached. A `false` cache entry means that there
+ // is no override for this URI.
+ // We cache these to avoid having to walk through all overrides to see
+ // if a domain matches.
+ return this._overrideForURICache.get(bareUri);
+ }
+
+ let finalUA = this.lookupUAOverride(uri);
+ this._overrideForURICache.set(bareUri, finalUA);
+
+ return finalUA;
+ }
+
+ /**
+ * This function gets called from within the embedded webextension to check
+ * if the current site has been overriden or not. We only check the cached
+ * URI list here, but that's safe in our case since the tabUpdateHandler will
+ * always run after our message observer.
+ */
+ hasUAForURIInCache(uri) {
+ let bareUri = uri.specIgnoringRef;
+ if (this._overrideForURICache.has(bareUri)) {
+ return !!this._overrideForURICache.get(bareUri);
+ }
+
+ 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) {
+ let baseDomain = eTLDService.getBaseDomain(uri);
+ if (this._overrides[baseDomain]) {
+ for (let uaOverride of this._overrides[baseDomain]) {
+ if (uaOverride.uriMatcher(uri.specIgnoringRef)) {
+ return uaOverride.uaTransformer(DefaultUA);
+ }
+ }
+ }
+
+ return false;
+ }
+}
+
+this.EXPORTED_SYMBOLS = ["UAOverrider"]; /* exported UAOverrider */
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/jar.mn
@@ -0,0 +1,3 @@
+[features/webcompat@mozilla.org] chrome.jar:
+% content webcompat %content/
+ content/ (content/*)
--- a/browser/extensions/webcompat/moz.build
+++ b/browser/extensions/webcompat/moz.build
@@ -10,9 +10,10 @@ DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['
FINAL_TARGET_FILES.features['webcompat@mozilla.org'] += [
'bootstrap.js'
]
FINAL_TARGET_PP_FILES.features['webcompat@mozilla.org'] += [
'install.rdf.in'
]
-BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+JAR_MANIFESTS += ['jar.mn']
rename from browser/extensions/webcompat/test/browser/browser.ini
rename to browser/extensions/webcompat/test/browser.ini
--- a/browser/extensions/webcompat/test/browser/browser.ini
+++ b/browser/extensions/webcompat/test/browser.ini
@@ -1,3 +1,4 @@
[DEFAULT]
[browser_check_installed.js]
+[browser_overrider.js]
deleted file mode 100644
--- a/browser/extensions/webcompat/test/browser/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
- "extends": [
- "../../../../../testing/mochitest/browser.eslintrc.js"
- ]
-};
rename from browser/extensions/webcompat/test/browser/browser_check_installed.js
rename to browser/extensions/webcompat/test/browser_check_installed.js
--- a/browser/extensions/webcompat/test/browser/browser_check_installed.js
+++ b/browser/extensions/webcompat/test/browser_check_installed.js
@@ -1,13 +1,19 @@
+/* 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/. */
+
+/* global AddonManager */
+
"use strict";
-add_task(function* test_enabled() {
+add_task(function* installed() {
let addon = yield new Promise(
- resolve => AddonManager.getAddonByID("webcompat@mozilla.org", resolve)
+ (resolve) => AddonManager.getAddonByID("webcompat@mozilla.org", resolve)
);
- isnot(addon, null, "Check addon exists");
- is(addon.version, "1.0", "Check version");
- is(addon.name, "Web Compat", "Check name");
- ok(addon.isCompatible, "Check application compatibility");
- ok(!addon.appDisabled, "Check not app disabled");
- ok(addon.isActive, "Check addon is active");
+ 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/browser/extensions/webcompat/test/browser_overrider.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+/* globals XPCOMUtils, UAOverrider, IOService */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(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.getUAForURI(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.getUAForURI(getnsIURI("http://www.example.org/foobar/"));
+ isnot(finalUA, "Test UA", "Does not override the UA with the matcher returning false");
+});