Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Tue, 21 Aug 2018 01:04:16 +0300
changeset 432477 97b4c0e1a0144bb026949fc75afc78b972c86b0d
parent 432450 ddb665c7fad04681b7c449509a1cea2ce9d84e02 (current diff)
parent 432476 d0d2e0f4b33cd28bc05c353c185873256f7f926e (diff)
child 432478 574377aa45865d04eed2713f331c1f553f7f12f7
push id106748
push userncsoregi@mozilla.com
push dateMon, 20 Aug 2018 22:04:41 +0000
treeherdermozilla-inbound@97b4c0e1a014 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
browser/base/content/tabbrowser.js
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/TabAttributes.jsm
browser/components/sessionstore/TabState.jsm
browser/components/sessionstore/test/browser.ini
browser/extensions/followonsearch/.eslintrc.js
browser/extensions/followonsearch/bootstrap.js
browser/extensions/followonsearch/content/followonsearch-fs.js
browser/extensions/followonsearch/install.rdf
browser/extensions/followonsearch/jar.mn
browser/extensions/followonsearch/moz.build
browser/extensions/followonsearch/test/browser/.eslintrc.js
browser/extensions/followonsearch/test/browser/browser.ini
browser/extensions/followonsearch/test/browser/browser_followOnTelemetry.js
browser/extensions/followonsearch/test/browser/test.html
browser/extensions/followonsearch/test/browser/test2.html
browser/extensions/followonsearch/test/browser/testEngine.xml
--- a/.eslintignore
+++ b/.eslintignore
@@ -75,19 +75,18 @@ browser/base/content/test/urlbar/file_bl
 browser/components/sessionstore/test/unit/data/sessionstore_valid.js
 browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
 # This file is split into two in order to keep it as a valid json file
 # for documentation purposes (policies.json) but to be accessed by the
 # code as a .jsm (schema.jsm)
 browser/components/enterprisepolicies/schemas/schema.jsm
 # generated & special files in cld2
 browser/components/translation/cld2/**
-# Screenshots and Follow-on search are imported as a system add-on and have
+# Screenshots is imported as a system add-on and has
 # their own lint rules currently.
-browser/extensions/followonsearch/**
 browser/extensions/screenshots/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 # Activity Stream has incompatible eslintrc. `npm run lint` from its directory
 browser/components/newtab/**
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1046,26 +1046,26 @@ pref("security.sandbox.content.level", 3
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
 // Prefs for controlling whether and how the Mac NPAPI Flash plugin process is
 // sandboxed. On Mac these levels are:
 // 0 - "no sandbox"
 // 1 - "global read access, limited write access for Flash functionality"
 // 2 - "read access triggered by file dialog activity, limited read/write"
 //     "access for Flash functionality"
 // 3 - "limited read/write access for Flash functionality"
-pref("dom.ipc.plugins.sandbox-level.flash", 2);
+pref("dom.ipc.plugins.sandbox-level.flash", 1);
 // Controls the level used on older OS X versions. Is overriden when the
 // "dom.ipc.plugins.sandbox-level.flash" is set to 0.
 pref("dom.ipc.plugins.sandbox-level.flash.legacy", 1);
 // The max OS minor version where we use the above legacy sandbox level.
 pref("dom.ipc.plugins.sandbox-level.flash.max-legacy-os-minor", 10);
 // Controls the sandbox level used by plugins other than Flash. On Mac,
 // no other plugins are supported and this pref is only used for test
 // plugins used in automated tests.
-pref("dom.ipc.plugins.sandbox-level.default", 2);
+pref("dom.ipc.plugins.sandbox-level.default", 1);
 #endif
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is introduced as part of bug 742434, the naming is inspired from
 // its Windows/Mac counterpart, but on Linux it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "content sandbox using seccomp-bpf when available" + ipc restrictions
 // 2 -> "seccomp-bpf + write file broker"
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -31,16 +31,17 @@ const startupPhases = {
       "MainProcessSingleton.js",
 
       // Bugs to fix: The following components shouldn't be initialized that early.
       "PushComponents.js", // bug 1369436
     ]),
     modules: new Set([
       "resource://gre/modules/AppConstants.jsm",
       "resource://gre/modules/ActorManagerParent.jsm",
+      "resource://gre/modules/CustomElementsListener.jsm",
       "resource://gre/modules/ExtensionUtils.jsm",
       "resource://gre/modules/XPCOMUtils.jsm",
       "resource://gre/modules/Services.jsm",
     ])
   }},
 
   // For the following phases of startup we have only a black list for now
 
--- a/browser/components/extensions/parent/ext-menus.js
+++ b/browser/components/extensions/parent/ext-menus.js
@@ -575,16 +575,17 @@ MenuItem.prototype = {
   },
 
   setDefaults() {
     this.setProps({
       type: "normal",
       checked: false,
       contexts: ["all"],
       enabled: true,
+      visible: true,
     });
   },
 
   set id(id) {
     if (this.hasOwnProperty("_id")) {
       throw new ExtensionError("ID of a MenuItem cannot be changed");
     }
     let isIdUsed = gMenuMap.get(this.extension).has(id);
@@ -703,16 +704,19 @@ MenuItem.prototype = {
       info.checked = this.checked;
       info.wasChecked = wasChecked;
     }
 
     return info;
   },
 
   enabledForContext(contextData) {
+    if (!this.visible) {
+      return false;
+    }
     let contexts = getMenuContexts(contextData);
     if (!this.contexts.some(n => contexts.has(n))) {
       return false;
     }
 
     if (contextData.onBookmark) {
       return this.extension.hasPermission("bookmarks");
     }
--- a/browser/components/extensions/schemas/menus.json
+++ b/browser/components/extensions/schemas/menus.json
@@ -192,16 +192,21 @@
                 "type": "array",
                 "items": {
                   "$ref": "ContextType"
                 },
                 "minItems": 1,
                 "optional": true,
                 "description": "List of contexts this menu item will appear in. Defaults to ['page'] if not specified."
               },
+              "visible": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the item is visible in the menu."
+              },
               "onclick": {
                 "type": "function",
                 "optional": true,
                 "description": "A function that will be called back when the menu item is clicked. Event pages cannot use this; instead, they should register a listener for $(ref:contextMenus.onClicked).",
                 "parameters": [
                   {
                     "name": "info",
                     "$ref": "OnClickData",
@@ -289,16 +294,21 @@
               "contexts": {
                 "type": "array",
                 "items": {
                   "$ref": "ContextType"
                 },
                 "minItems": 1,
                 "optional": true
               },
+              "visible": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the item is visible in the menu."
+              },
               "onclick": {
                 "type": "function",
                 "optional": "omit-key-if-missing",
                 "parameters": [
                   {
                     "name": "info",
                     "$ref": "OnClickData"
                   },
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -110,16 +110,17 @@ skip-if = (verify && (os == 'linux' || o
 [browser_ext_menus_activeTab.js]
 [browser_ext_menus_errors.js]
 [browser_ext_menus_event_order.js]
 [browser_ext_menus_events.js]
 [browser_ext_menus_refresh.js]
 [browser_ext_menus_targetElement.js]
 [browser_ext_menus_targetElement_extension.js]
 [browser_ext_menus_targetElement_shadow.js]
+[browser_ext_menus_visible.js]
 [browser_ext_omnibox.js]
 [browser_ext_openPanel.js]
 skip-if = (verify && !debug && (os == 'linux' || os == 'mac'))
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_modals.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 skip-if = (verify && !debug && (os == 'linux'))
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_menus_visible.js
@@ -0,0 +1,86 @@
+/* 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";
+
+const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
+
+add_task(async function visible_false() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+  async function background() {
+    browser.menus.onShown.addListener(info => {
+      browser.test.assertEq("[]", JSON.stringify(info.menuIds), "Expected no menu items");
+      browser.test.sendMessage("done");
+    });
+    browser.menus.create({
+      id: "create-visible-false",
+      title: "invisible menu item",
+      visible: false,
+    });
+    browser.menus.create({
+      id: "update-without-params",
+      title: "invisible menu item",
+      visible: false,
+    });
+    await browser.menus.update("update-without-params", {});
+    browser.menus.create({
+      id: "update-visible-to-false",
+      title: "initially visible menu item",
+    });
+    await browser.menus.update("update-visible-to-false", {visible: false});
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+  await openContextMenu();
+  await extension.awaitMessage("done");
+  await closeContextMenu();
+
+  await extension.unload();
+
+  BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function visible_true() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+  async function background() {
+    browser.menus.onShown.addListener(info => {
+      browser.test.assertEq(`["update-to-true"]`, JSON.stringify(info.menuIds), "Expected no menu items");
+      browser.test.sendMessage("done");
+    });
+    browser.menus.create({
+      id: "update-to-true",
+      title: "invisible menu item",
+      visible: false,
+    });
+    await browser.menus.update("update-to-true", {visible: true});
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+  await openContextMenu();
+  await extension.awaitMessage("done");
+  await closeContextMenu();
+
+  await extension.unload();
+
+  BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/search/test/browser_searchEngine_behaviors.js
+++ b/browser/components/search/test/browser_searchEngine_behaviors.js
@@ -98,16 +98,17 @@ async function testSearchEngine(engineDe
   Services.search.currentEngine = engine;
   engine.alias = engineDetails.alias;
 
   let base = engineDetails.baseURL;
 
   // Test search URLs (including purposes).
   let url = engine.getSubmission("foo").uri.spec;
   Assert.equal(url, base + engineDetails.codes.submission, "Check search URL for 'foo'");
+  let sb = BrowserSearch.searchBar;
 
   let engineTests = [
     {
       name: "context menu search",
       searchURL: base + engineDetails.codes.context,
       run() {
         // Simulate a contextmenu search
         // FIXME: This is a bit "low-level"...
@@ -131,22 +132,18 @@ async function testSearchEngine(engineDe
         gURLBar.focus();
         EventUtils.synthesizeKey("KEY_Enter");
       }
     },
     {
       name: "search bar search",
       searchURL: base + engineDetails.codes.submission,
       run() {
-        let sb = BrowserSearch.searchBar;
         sb.focus();
         sb.value = "foo";
-        registerCleanupFunction(function() {
-          sb.value = "";
-        });
         EventUtils.synthesizeKey("KEY_Enter");
       }
     },
     {
       name: "new tab search",
       searchURL: base + engineDetails.codes.newTab,
       async preTest(tab) {
         let browser = tab.linkedBrowser;
@@ -180,10 +177,11 @@ async function testSearchEngine(engineDe
     await test.run(tab);
 
     let receivedURI = await stateChangePromise;
 
     Assert.equal(receivedURI, test.searchURL);
   }
 
   engine.alias = undefined;
+  sb.value = "";
   BrowserTestUtils.removeTab(tab);
 }
deleted file mode 100644
--- a/browser/extensions/followonsearch/.eslintrc.js
+++ /dev/null
@@ -1,8 +0,0 @@
-"use strict";
-
-module.exports = {
-  "env": {
-    "browser": true,
-    "node": false
-  }
-};
deleted file mode 100644
--- a/browser/extensions/followonsearch/bootstrap.js
+++ /dev/null
@@ -1,192 +0,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/. */
-
-/* global APP_SHUTDOWN:false */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Timer.jsm");
-
-// Preferences this add-on uses.
-const kPrefPrefix = "extensions.followonsearch.";
-const PREF_LOGGING = `${kPrefPrefix}logging`;
-
-const kExtensionID = "followonsearch@mozilla.com";
-const kSaveTelemetryMsg = `${kExtensionID}:save-telemetry`;
-const kShutdownMsg = `${kExtensionID}:shutdown`;
-
-const frameScript = `chrome://followonsearch/content/followonsearch-fs.js?q=${Math.random()}`;
-
-const validSearchTypes = [
-  // A search is a follow-on search from an SAP.
-  "follow-on",
-  // The search is a "search access point".
-  "sap",
-];
-
-var gLoggingEnabled = false;
-var gTelemetryActivated = false;
-
-/**
- * Logs a message to the console if logging is enabled.
- *
- * @param {String} message The message to log.
- */
-function log(message) {
-  if (gLoggingEnabled) {
-    console.log("Follow-On Search", message);
-  }
-}
-
-/**
- * Handles receiving a message from the content process to save telemetry.
- *
- * @param {Object} message The message received.
- */
-function handleSaveTelemetryMsg(message) {
-  if (message.name != kSaveTelemetryMsg) {
-    throw new Error(`Unexpected message received: ${message.name}`);
-  }
-
-  let info = message.data;
-
-  if (!validSearchTypes.includes(info.type)) {
-    throw new Error("Unexpected type!");
-  }
-
-  log(info);
-
-  let histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
-  let payload = `${info.sap}.${info.type}:unknown:${info.code}`;
-  if (info.extra) {
-    payload += `:${info.extra}`
-  }
-  histogram.add(payload);
-}
-
-/**
- * Activates recording of telemetry if it isn't already activated.
- */
-function activateTelemetry() {
-  if (gTelemetryActivated) {
-    return;
-  }
-
-  gTelemetryActivated = true;
-
-  Services.mm.addMessageListener(kSaveTelemetryMsg, handleSaveTelemetryMsg);
-  Services.mm.loadFrameScript(frameScript, true);
-}
-
-/**
- * Deactivites recording of telemetry if it isn't already deactivated.
- */
-function deactivateTelemetry() {
-  if (!gTelemetryActivated) {
-    return;
-  }
-
-  Services.mm.removeMessageListener(kSaveTelemetryMsg, handleSaveTelemetryMsg);
-  Services.mm.removeDelayedFrameScript(frameScript);
-  Services.mm.broadcastAsyncMessage(kShutdownMsg);
-
-  gTelemetryActivated = false;
-}
-
-/**
- * cohortManager is used to decide which users to enable the add-on for.
- */
-var cohortManager = {
-  // Indicates whether the telemetry should be enabled.
-  enableForUser: false,
-
-  // Records if we've already run init.
-  _definedThisSession: false,
-
-  /**
-   * Initialises the manager, working out if telemetry should be enabled
-   * for the user.
-   */
-  init() {
-    if (this._definedThisSession) {
-      return;
-    }
-
-    this._definedThisSession = true;
-    this.enableForUser = false;
-
-    try {
-      let distId = Services.prefs.getCharPref("distribution.id", "");
-      if (distId) {
-        log("It is a distribution, not setting up nor enabling telemetry.");
-        return;
-      }
-    } catch (e) {}
-
-    log("Enabling telemetry for user");
-    this.enableForUser = true;
-  },
-};
-
-/**
- * Called when the add-on is installed.
- *
- * @param {Object} data Data about the add-on.
- * @param {Number} reason Indicates why the extension is being installed.
- */
-function install(data, reason) {
-  // Nothing specifically to do, startup will set everything up for us.
-}
-
-/**
- * Called when the add-on is uninstalled.
- *
- * @param {Object} data Data about the add-on.
- * @param {Number} reason Indicates why the extension is being uninstalled.
- */
-function uninstall(data, reason) {
-  // Nothing specifically to do, shutdown does what we need.
-}
-
-/**
- * Called when the add-on starts up.
- *
- * @param {Object} data Data about the add-on.
- * @param {Number} reason Indicates why the extension is being started.
- */
-function startup(data, reason) {
-  try {
-    gLoggingEnabled = Services.prefs.getBoolPref(PREF_LOGGING, false);
-  } catch (e) {
-    // Needed until Firefox 54
-  }
-
-  cohortManager.init();
-
-  if (cohortManager.enableForUser) {
-    // Workaround for bug 1202125
-    // We need to delay our loading so that when we are upgraded,
-    // our new script doesn't get the shutdown message.
-    setTimeout(() => {
-      activateTelemetry();
-    }, 1000);
-  }
-}
-
-/**
- * Called when the add-on shuts down.
- *
- * @param {Object} data Data about the add-on.
- * @param {Number} reason Indicates why the extension is being shut down.
- */
-function shutdown(data, reason) {
-  // If we're shutting down, skip the cleanup to save time.
-  if (reason === APP_SHUTDOWN) {
-    return;
-  }
-
-  deactivateTelemetry();
-}
deleted file mode 100644
--- a/browser/extensions/followonsearch/content/followonsearch-fs.js
+++ /dev/null
@@ -1,308 +0,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/. */
-
-/* eslint-env mozilla/frame-script */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyGlobalGetters(this, ["URLSearchParams"]);
-
-const kExtensionID = "followonsearch@mozilla.com";
-const kSaveTelemetryMsg = `${kExtensionID}:save-telemetry`;
-const kShutdownMsg = `${kExtensionID}:shutdown`;
-const kLastSearchQueueDepth = 10;
-
-/**
- * A map of search domains with their expected codes.
- */
-let searchDomains = [{
-  "domains": [ "search.yahoo.co.jp" ],
-  "search": "p",
-  "followOnSearch": "ai",
-  "prefix": ["fr"],
-  "codes": ["mozff"],
-  "sap": "yahoo",
-}, {
-  "domains": [ "www.bing.com" ],
-  "search": "q",
-  "prefix": ["pc"],
-  "reportPrefix": "form",
-  "codes": ["MOZI", "MOZD", "MZSL01", "MZSL02", "MZSL03", "MOZ2"],
-  "sap": "bing",
-}, {
-  // The Google domains.
-  "domains": [
-    "www.google.com", "www.google.ac", "www.google.ad", "www.google.ae",
-    "www.google.com.af", "www.google.com.ag", "www.google.com.ai",
-    "www.google.al", "www.google.am", "www.google.co.ao", "www.google.com.ar",
-    "www.google.as", "www.google.at", "www.google.com.au", "www.google.az",
-    "www.google.ba", "www.google.com.bd", "www.google.be", "www.google.bf",
-    "www.google.bg", "www.google.com.bh", "www.google.bi", "www.google.bj",
-    "www.google.com.bn", "www.google.com.bo", "www.google.com.br",
-    "www.google.bs", "www.google.bt", "www.google.co.bw", "www.google.by",
-    "www.google.com.bz", "www.google.ca", "www.google.com.kh", "www.google.cc",
-    "www.google.cd", "www.google.cf", "www.google.cat", "www.google.cg",
-    "www.google.ch", "www.google.ci", "www.google.co.ck", "www.google.cl",
-    "www.google.cm", "www.google.cn", "www.google.com.co", "www.google.co.cr",
-    "www.google.com.cu", "www.google.cv", "www.google.cx", "www.google.com.cy",
-    "www.google.cz", "www.google.de", "www.google.dj", "www.google.dk",
-    "www.google.dm", "www.google.com.do", "www.google.dz", "www.google.com.ec",
-    "www.google.ee", "www.google.com.eg", "www.google.es", "www.google.com.et",
-    "www.google.eu", "www.google.fi", "www.google.com.fj", "www.google.fm",
-    "www.google.fr", "www.google.ga", "www.google.ge", "www.google.gf",
-    "www.google.gg", "www.google.com.gh", "www.google.com.gi", "www.google.gl",
-    "www.google.gm", "www.google.gp", "www.google.gr", "www.google.com.gt",
-    "www.google.gy", "www.google.com.hk", "www.google.hn", "www.google.hr",
-    "www.google.ht", "www.google.hu", "www.google.co.id", "www.google.iq",
-    "www.google.ie", "www.google.co.il", "www.google.im", "www.google.co.in",
-    "www.google.io", "www.google.is", "www.google.it", "www.google.je",
-    "www.google.com.jm", "www.google.jo", "www.google.co.jp", "www.google.co.ke",
-    "www.google.ki", "www.google.kg", "www.google.co.kr", "www.google.com.kw",
-    "www.google.kz", "www.google.la", "www.google.com.lb", "www.google.com.lc",
-    "www.google.li", "www.google.lk", "www.google.co.ls", "www.google.lt",
-    "www.google.lu", "www.google.lv", "www.google.com.ly", "www.google.co.ma",
-    "www.google.md", "www.google.me", "www.google.mg", "www.google.mk",
-    "www.google.ml", "www.google.com.mm", "www.google.mn", "www.google.ms",
-    "www.google.com.mt", "www.google.mu", "www.google.mv", "www.google.mw",
-    "www.google.com.mx", "www.google.com.my", "www.google.co.mz",
-    "www.google.com.na", "www.google.ne", "www.google.nf", "www.google.com.ng",
-    "www.google.com.ni", "www.google.nl", "www.google.no", "www.google.com.np",
-    "www.google.nr", "www.google.nu", "www.google.co.nz", "www.google.com.om",
-    "www.google.com.pk", "www.google.com.pa", "www.google.com.pe",
-    "www.google.com.ph", "www.google.pl", "www.google.com.pg", "www.google.pn",
-    "www.google.com.pr", "www.google.ps", "www.google.pt", "www.google.com.py",
-    "www.google.com.qa", "www.google.ro", "www.google.rs", "www.google.ru",
-    "www.google.rw", "www.google.com.sa", "www.google.com.sb", "www.google.sc",
-    "www.google.se", "www.google.com.sg", "www.google.sh", "www.google.si",
-    "www.google.sk", "www.google.com.sl", "www.google.sn", "www.google.sm",
-    "www.google.so", "www.google.st", "www.google.sr", "www.google.com.sv",
-    "www.google.td", "www.google.tg", "www.google.co.th", "www.google.com.tj",
-    "www.google.tk", "www.google.tl", "www.google.tm", "www.google.to",
-    "www.google.tn", "www.google.com.tr", "www.google.tt", "www.google.com.tw",
-    "www.google.co.tz", "www.google.com.ua", "www.google.co.ug",
-    "www.google.co.uk", "www.google.us", "www.google.com.uy", "www.google.co.uz",
-    "www.google.com.vc", "www.google.co.ve", "www.google.vg", "www.google.co.vi",
-    "www.google.com.vn", "www.google.vu", "www.google.ws", "www.google.co.za",
-    "www.google.co.zm", "www.google.co.zw",
-  ],
-  "search": "q",
-  "prefix": ["client"],
-  "followOnSearch": "oq",
-  "codes": ["firefox-b-ab", "firefox-b", "firefox-b-1-ab", "firefox-b-1"],
-  "sap": "google",
-}, {
-  // This is intended only for tests.
-  "domains": [ "mochi.test" ],
-  "search": "m",
-  "prefix": ["mt"],
-  "followOnSearch": "mtfo",
-  "reportPrefix": "form",
-  "codes": ["TEST"],
-  "sap": "mochitest"
-}];
-
-function getSearchDomainCodes(host) {
-  for (let domainInfo of searchDomains) {
-    if (domainInfo.domains.includes(host)) {
-      return domainInfo;
-    }
-  }
-  return null;
-}
-
-/**
- * Used for debugging to log messages.
- *
- * @param {String} message The message to log.
- */
-function log(message) {
-  // console.log(message);
-}
-
-// Hack to handle the most common reload/back/forward case.
-// If gLastSearchQueue includes the current URL, ignore the search.
-// This also prevents us from handling reloads with hashes twice
-let gLastSearchQueue = [];
-gLastSearchQueue.push = function(...args) {
-  if (this.length >= kLastSearchQueueDepth) {
-    this.shift();
-  }
-  return Array.prototype.push.apply(this, args);
-};
-
-// Track if we are in the middle of a Google session
-// that started from Firefox
-let searchingGoogle = false;
-
-/**
- * Since most codes are in the URL, we can handle them via
- * a progress listener.
- */
-var webProgressListener = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
-  onLocationChange(aWebProgress, aRequest, aLocation, aFlags)
-  {
-    if (aWebProgress.DOMWindow && (aWebProgress.DOMWindow != content)) {
-      return;
-    }
-    try {
-      if (!aWebProgress.isTopLevel ||
-          // Not a URL
-          (!aLocation.schemeIs("http") && !aLocation.schemeIs("https")) ||
-          // Doesn't have a query string or a ref
-          (!aLocation.query && !aLocation.ref)) {
-        searchingGoogle = false;
-        return;
-      }
-      if (gLastSearchQueue.includes(aLocation.spec)) {
-        // If it's a recent search, just return. We
-        // don't reset searchingGoogle though because
-        // we might still be doing that.
-        return;
-      }
-      let domainInfo = getSearchDomainCodes(aLocation.host);
-      if (!domainInfo) {
-        searchingGoogle = false;
-        return;
-      }
-
-      let queries = new URLSearchParams(aLocation.query);
-      // Yahoo has switched to Unified search so we can get
-      // different codes on the same domain. Hack for now
-      // to allow two different prefixes for codes
-      let code = queries.get(domainInfo.prefix[0]);
-      if (!code && domainInfo.prefix.length > 1) {
-        code = queries.get(domainInfo.prefix[1]);
-      }
-      // Special case Google so we can track searches
-      // without codes from the browser.
-      if (domainInfo.sap == "google") {
-        if (aLocation.filePath == "/search") {
-          gLastSearchQueue.push(aLocation.spec);
-          // Our engine currently sends oe and ie - no one else does
-          if (queries.get("oe") && queries.get("ie")) {
-            sendSaveTelemetryMsg(code ? code : "none", code, "sap");
-            searchingGoogle = true;
-          } else {
-            // The tbm value is the specific type of search (Books, Images, News, etc).
-            // These are referred to as vertical searches.
-            let tbm = queries.get("tbm");
-            if (searchingGoogle) {
-              sendSaveTelemetryMsg(code ? code : "none", code, "follow-on", tbm ? `vertical-${tbm}` : null);
-            } else if (code) {
-              // Trying to do the right thing for back button to existing entries
-              sendSaveTelemetryMsg(code, domainInfo.sap, "follow-on", tbm ? `vertical-${tbm}` : null);
-            }
-          }
-        }
-        // Special case all Google. Otherwise our code can
-        // show up in maps
-        return;
-      }
-      searchingGoogle = false;
-      if (queries.get(domainInfo.search)) {
-        if (domainInfo.codes.includes(code)) {
-          if (domainInfo.reportPrefix &&
-              queries.get(domainInfo.reportPrefix)) {
-            code = queries.get(domainInfo.reportPrefix);
-          }
-          if (queries.get(domainInfo.followOnSearch)) {
-            log(`${aLocation.host} search with code ${code} - Follow on`);
-            sendSaveTelemetryMsg(code, domainInfo.sap, "follow-on");
-          } else {
-            log(`${aLocation.host} search with code ${code} - First search via Firefox`);
-            sendSaveTelemetryMsg(code, domainInfo.sap, "sap");
-          }
-          gLastSearchQueue.push(aLocation.spec);
-        }
-      }
-    } catch (e) {
-      console.error(e);
-    }
-  },
-};
-
-/**
- * Parses a cookie string into separate parts.
- *
- * @param {String} cookieString The string to parse.
- * @param {Object} [params] An optional object to append the parameters to.
- * @return {Object} An object containing the query keys and values.
- */
-function parseCookies(cookieString, params = {}) {
-  var cookies = cookieString.split(/;\s*/);
-
-  for (var i in cookies) {
-    var kvp = cookies[i].split(/=(.+)/);
-    params[kvp[0]] = kvp[1];
-  }
-
-  return params;
-}
-
-/**
- * Page load listener to handle loads www.bing.com only.
- * We have to use a page load listener because we need
- * to check cookies.
- * @param {Object} event The page load event.
- */
-function onPageLoad(event) {
-  var doc = event.target;
-  var win = doc.defaultView;
-  if (win != win.top) {
-    return;
-  }
-  var uri = doc.documentURIObject;
-  if (!(uri instanceof Ci.nsIStandardURL) ||
-      (!uri.schemeIs("http") && !uri.schemeIs("https")) ||
-       uri.host != "www.bing.com" ||
-      !doc.location.search ||
-      gLastSearchQueue.includes(uri.spec)) {
-    return;
-  }
-  var queries = new URLSearchParams(doc.location.search.toLowerCase());
-  // For Bing, QBRE form code is used for all follow-on search
-  if (queries.get("form") != "qbre") {
-    return;
-  }
-  if (parseCookies(doc.cookie).SRCHS == "PC=MOZI") {
-    log(`${uri.host} search with code MOZI - Follow on`);
-    sendSaveTelemetryMsg("MOZI", "bing", "follow-on");
-    gLastSearchQueue.push(uri.spec);
-  }
-}
-
-/**
- * Sends a message to the process that added this script to tell it to save
- * telemetry.
- *
- * @param {String} code The codes used for the search engine.
- * @param {String} sap The SAP code.
- * @param {String} type The type of search (sap/follow-on).
- * @param {String} extra Any additional parameters (Optional)
- */
-function sendSaveTelemetryMsg(code, sap, type, extra) {
-  sendAsyncMessage(kSaveTelemetryMsg, {
-    code,
-    sap,
-    type,
-    extra,
-  });
-}
-
-addEventListener("DOMContentLoaded", onPageLoad, false);
-docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress)
-        .addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_LOCATION);
-
-let gDisabled = false;
-
-addMessageListener(kShutdownMsg, () => {
-  if (!gDisabled) {
-    removeEventListener("DOMContentLoaded", onPageLoad, false);
-    docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress)
-            .removeProgressListener(webProgressListener);
-    gDisabled = true;
-  }
-});
deleted file mode 100644
--- a/browser/extensions/followonsearch/install.rdf
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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/. -->
-<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>followonsearch@mozilla.com</em:id>
-    <em:name>Follow-on Search Telemetry</em:name>
-    <em:version>0.9.7</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>52.0</em:minVersion>
-        <em:maxVersion>66.*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/browser/extensions/followonsearch/jar.mn
+++ /dev/null
@@ -1,7 +0,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/.
-
-[features/followonsearch@mozilla.com] chrome.jar:
-% content followonsearch %content/
-  content/  (content/*)
deleted file mode 100644
--- a/browser/extensions/followonsearch/moz.build
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- 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/.
-
-with Files("**"):
-    BUG_COMPONENT = ("Firefox", "Search")
-
-FINAL_TARGET_FILES.features['followonsearch@mozilla.com'] += [
-  'bootstrap.js',
-  'install.rdf',
-]
-
-BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
-
-JAR_MANIFESTS += ['jar.mn']
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "plugin:mozilla/browser-test",
-  ],
-};
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/browser.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-
-[browser_followOnTelemetry.js]
-support-files =
-  test.html
-  test2.html
-  testEngine.xml
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/browser_followOnTelemetry.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.defineModuleGetter(this, "SearchTestUtils",
-  "resource://testing-common/SearchTestUtils.jsm");
-
-SearchTestUtils.init(Assert, registerCleanupFunction);
-
-const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/followonsearch/test/browser/";
-const TEST_ENGINE_BASENAME = "testEngine.xml";
-
-add_task(async function test_followOnSearchTelemetry() {
-  let engine = await SearchTestUtils.promiseNewSearchEngine(
-    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
-    registerCleanupFunction);
-  let oldCurrentEngine = Services.search.currentEngine;
-  Services.search.currentEngine = engine;
-
-  let histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
-  histogram.clear();
-
-  registerCleanupFunction(() => Services.search.currentEngine = oldCurrentEngine);
-
-  await BrowserTestUtils.withNewTab({gBrowser}, async browser => {
-    // Open the initial search page via entering a search on the URL bar.
-    let loadPromise = BrowserTestUtils.waitForLocationChange(gBrowser,
-      `${BASE_URL}test.html?searchm=test&mt=TEST`);
-
-    gURLBar.focus();
-    EventUtils.sendString("test");
-    EventUtils.sendKey("return");
-
-    await loadPromise;
-
-    // Perform a follow-on search, selecting the form in the page.
-    loadPromise = BrowserTestUtils.waitForLocationChange(gBrowser,
-      `${BASE_URL}test2.html?mtfo=followonsearchtest&mt=TEST&m=test`);
-
-    await ContentTask.spawn(browser, null, function() {
-      content.document.getElementById("submit").click();
-    });
-
-    await loadPromise;
-
-    let snapshot;
-
-    // We have to wait for the snapshot to come in, as there's async functionality
-    // in the extension.
-    await TestUtils.waitForCondition(() => {
-      snapshot = histogram.snapshot();
-      return "mochitest.follow-on:unknown:TEST" in snapshot;
-    });
-    Assert.ok("mochitest.follow-on:unknown:TEST" in snapshot,
-      "Histogram should have an entry for the follow-on search.");
-    Assert.deepEqual(snapshot["mochitest.follow-on:unknown:TEST"], {
-      counts: [ 1, 0, 0 ],
-      histogram_type: 4,
-      max: 2,
-      min: 1,
-      ranges: [ 0, 1, 2 ],
-      sum: 1,
-    }, "Histogram should have the correct snapshot data");
-  });
-});
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/test.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8" />
-  <title>Follow-on Search Test</title>
-</head>
-<body>
-  <form method="get" action="test2.html">
-    <input type="text" name="mtfo" value="followonsearchtest"/>
-    <input type="text" name="mt" value="TEST"/>
-    <input type="text" name="m" value="test"/>
-    <input type="submit" id="submit" value="submit"/>
-  </form>
-</body>
-</html>
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/test2.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8" />
-  <title>Follow-on Search Test Final Page</title>
-</head>
-<body></body>
-</html>
deleted file mode 100644
--- a/browser/extensions/followonsearch/test/browser/testEngine.xml
+++ /dev/null
@@ -1,15 +0,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/. -->
-
-<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
-                       xmlns:moz="http://www.mozilla.org/2006/browser/search/">
-  <ShortName>Mochitest</ShortName>
-  <Description>Mochitest Engine</Description>
-  <InputEncoding>UTF-8</InputEncoding>
-  <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
-  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/extensions/followonsearch/test/browser/test.html?search" rel="searchform">
-    <Param name="m" value="{searchTerms}"/>
-    <Param name="mt" value="TEST"/>
-  </Url>
-</OpenSearchDescription>
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -284,16 +284,38 @@ def rust_tests(enable_rust_tests, rustdo
 
 set_config('MOZ_RUST_TESTS', rust_tests)
 
 # cbindgen is needed by the style system build.
 cbindgen = check_prog('CBINDGEN', add_rustup_path('cbindgen'), paths=toolchain_search_path,
                       when=depends(build_project)
                       (lambda build_project: build_project != 'js'))
 
+
+@depends_if(cbindgen)
+@checking('cbindgen version')
+@imports(_from='textwrap', _import='dedent')
+def cbindgen_version(cbindgen):
+    cbindgen_min_version = Version('0.6.1')
+
+    # cbindgen x.y.z
+    version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
+
+    if version < cbindgen_min_version:
+        die(dedent('''\
+        cbindgen version {} is too old. At least version {} is required.
+
+        Please update using 'cargo install cbindgen --force' or running
+        './mach bootstrap', after removing the existing executable located at
+        {}.
+        '''.format(version, cbindgen_min_version, cbindgen)))
+
+    return version
+
+
 # Bindgen can use rustfmt to format Rust file, but it's not required.
 js_option(env='RUSTFMT', nargs=1, help='Path to the rustfmt program')
 
 rustfmt = check_prog('RUSTFMT', add_rustup_path('rustfmt'),
                      input='RUSTFMT', allow_missing=True)
 
 js_option(env='WIN64_LINK', nargs=1, help='Path to link.exe that targets win64')
 js_option(env='WIN64_LIB', nargs=1, help='Paths to libraries for the win64 linker')
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -742,20 +742,22 @@ CreateTrackInfoWithMIMETypeAndContainerT
 {
   UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
   if (trackInfo) {
     VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
     if (videoInfo) {
       Maybe<int32_t> maybeWidth = aContainerType.ExtendedType().GetWidth();
       if (maybeWidth && *maybeWidth > 0) {
         videoInfo->mImage.width = *maybeWidth;
+        videoInfo->mDisplay.width = *maybeWidth;
       }
       Maybe<int32_t> maybeHeight = aContainerType.ExtendedType().GetHeight();
       if (maybeHeight && *maybeHeight > 0) {
         videoInfo->mImage.height = *maybeHeight;
+        videoInfo->mDisplay.height = *maybeHeight;
       }
     } else if (trackInfo->GetAsAudioInfo()) {
       AudioInfo* audioInfo = trackInfo->GetAsAudioInfo();
       Maybe<int32_t> maybeChannels =
         aContainerType.ExtendedType().GetChannels();
       if (maybeChannels && *maybeChannels > 0) {
         audioInfo->mChannels = *maybeChannels;
       }
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -203,16 +203,18 @@ public:
                                       mJavaCallbacks,
                                       mDrmStubId);
     if (mJavaDecoder == nullptr) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                           __func__);
     }
     mIsCodecSupportAdaptivePlayback =
       mJavaDecoder->IsAdaptivePlaybackSupported();
+    mIsHardwareAccelerated =
+      mJavaDecoder->IsHardwareAccelerated();
     return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
   }
 
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override
   {
     RefPtr<RemoteVideoDecoder> self = this;
     return RemoteDataDecoder::Flush()->Then(
       mTaskQueue,
@@ -267,22 +269,29 @@ public:
       return false;
     }
 
     mSeekTarget.reset();
     mLatestOutputTime = Some(endTime);
     return true;
   }
 
+  bool IsHardwareAccelerated(nsACString& aFailureReason) const override
+  {
+    return mIsHardwareAccelerated;
+  }
+
 private:
   const VideoInfo mConfig;
   GeckoSurface::GlobalRef mSurface;
   AndroidSurfaceTextureHandle mSurfaceHandle;
   // Only accessed on reader's task queue.
   bool mIsCodecSupportAdaptivePlayback = false;
+  // Can be accessed on any thread, but only written on during init.
+  bool mIsHardwareAccelerated = false;
   // Accessed on mTaskQueue, reader's TaskQueue and Java callback tread.
   // SimpleMap however is thread-safe, so it's okay to do so.
   SimpleMap<InputInfo> mInputInfos;
   // Only accessed on the TaskQueue.
   Maybe<TimeUnit> mSeekTarget;
   Maybe<TimeUnit> mLatestOutputTime;
 };
 
@@ -452,18 +461,18 @@ already_AddRefed<MediaDataDecoder>
 RemoteDataDecoder::CreateVideoDecoder(const CreateDecoderParams& aParams,
                                       const nsString& aDrmStubId,
                                       CDMProxy* aProxy)
 {
   const VideoInfo& config = aParams.VideoConfig();
   MediaFormat::LocalRef format;
   NS_ENSURE_SUCCESS(
     MediaFormat::CreateVideoFormat(TranslateMimeType(config.mMimeType),
-                                   config.mDisplay.width,
-                                   config.mDisplay.height,
+                                   config.mImage.width,
+                                   config.mImage.height,
                                    &format),
     nullptr);
 
   RefPtr<MediaDataDecoder> decoder = new RemoteVideoDecoder(
     config, format, aDrmStubId, aParams.mTaskQueue);
   if (aProxy) {
     decoder = new EMEMediaDataDecoderProxy(aParams, decoder.forget(), aProxy);
   }
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2525,16 +2525,53 @@ DrawBackgroundColor(nsCSSRendering::Imag
 
   RefPtr<Path> roundedRect =
     MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
   aCtx->SetPath(roundedRect);
   aCtx->Fill();
   aCtx->Restore();
 }
 
+static Maybe<nscolor>
+CalcScrollbarColor(nsIFrame* aFrame, StyleComplexColor nsStyleUI::* aColor)
+{
+  ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
+  auto color = scrollbarStyle->StyleUI()->*aColor;
+  if (color.IsAuto()) {
+    return Nothing();
+  }
+  return Some(color.CalcColor(scrollbarStyle));
+}
+
+static nscolor
+GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aComputedStyle)
+{
+  Maybe<nscolor> overrideColor = Nothing();
+  switch (aComputedStyle->StyleDisplay()->mAppearance) {
+    case StyleAppearance::ScrollbarthumbVertical:
+    case StyleAppearance::ScrollbarthumbHorizontal:
+      overrideColor =
+        CalcScrollbarColor(aFrame, &nsStyleUI::mScrollbarFaceColor);
+      break;
+    case StyleAppearance::ScrollbarVertical:
+    case StyleAppearance::ScrollbarHorizontal:
+    case StyleAppearance::Scrollcorner:
+      overrideColor =
+        CalcScrollbarColor(aFrame, &nsStyleUI::mScrollbarTrackColor);
+      break;
+    default:
+      break;
+  }
+  if (overrideColor.isSome()) {
+    return *overrideColor;
+  }
+  return aComputedStyle->
+    GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+}
+
 nscolor
 nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
                                          ComputedStyle* aComputedStyle,
                                          nsIFrame* aFrame,
                                          bool& aDrawBackgroundImage,
                                          bool& aDrawBackgroundColor)
 {
   aDrawBackgroundImage = true;
@@ -2546,18 +2583,17 @@ nsCSSRendering::DetermineBackgroundColor
       aFrame->HonorPrintBackgroundSettings()) {
     aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
     aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
   }
 
   const nsStyleBackground *bg = aComputedStyle->StyleBackground();
   nscolor bgColor;
   if (aDrawBackgroundColor) {
-    bgColor = aComputedStyle->
-      GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
+    bgColor = GetBackgroundColor(aFrame, aComputedStyle);
     if (NS_GET_A(bgColor) == 0) {
       aDrawBackgroundColor = false;
     }
   } else {
     // If GetBackgroundColorDraw() is false, we are still expected to
     // draw color in the background of any frame that's not completely
     // transparent, but we are expected to use white instead of whatever
     // color was specified.
--- a/layout/xul/nsBox.cpp
+++ b/layout/xul/nsBox.cpp
@@ -517,16 +517,33 @@ nsIFrame::AddXULPrefSize(nsIFrame* aBox,
               nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
             aHeightSet = true;
         }
     }
 
     return (aWidthSet && aHeightSet);
 }
 
+// This returns the scrollbar width we want to use when either native
+// theme is disabled, or the native theme claims that it doesn't support
+// scrollbar.
+static nscoord
+GetScrollbarWidthNoTheme(nsIFrame* aBox)
+{
+    ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aBox);
+    switch (scrollbarStyle->StyleUIReset()->mScrollbarWidth) {
+      default:
+      case StyleScrollbarWidth::Auto:
+        return 16 * AppUnitsPerCSSPixel();
+      case StyleScrollbarWidth::Thin:
+        return 8 * AppUnitsPerCSSPixel();
+      case StyleScrollbarWidth::None:
+        return 0;
+    }
+}
 
 bool
 nsIFrame::AddXULMinSize(nsBoxLayoutState& aState, nsIFrame* aBox, nsSize& aSize,
                       bool &aWidthSet, bool &aHeightSet)
 {
     aWidthSet = false;
     aHeightSet = false;
 
@@ -543,16 +560,29 @@ nsIFrame::AddXULMinSize(nsBoxLayoutState
         if (size.width) {
           aSize.width = aState.PresContext()->DevPixelsToAppUnits(size.width);
           aWidthSet = true;
         }
         if (size.height) {
           aSize.height = aState.PresContext()->DevPixelsToAppUnits(size.height);
           aHeightSet = true;
         }
+      } else {
+        switch (display->mAppearance) {
+          case StyleAppearance::ScrollbarVertical:
+            aSize.width = GetScrollbarWidthNoTheme(aBox);
+            aWidthSet = true;
+            break;
+          case StyleAppearance::ScrollbarHorizontal:
+            aSize.height = GetScrollbarWidthNoTheme(aBox);
+            aHeightSet = true;
+            break;
+          default:
+            break;
+        }
       }
     }
 
     // add in the css min, max, pref
     const nsStylePosition* position = aBox->StylePosition();
 
     // same for min size. Unfortunately min size is always set to 0. So for now
     // we will assume 0 (as a coord) means not set.
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodec.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodec.aidl
@@ -10,16 +10,17 @@ import org.mozilla.gecko.gfx.GeckoSurfac
 import org.mozilla.gecko.media.FormatParam;
 import org.mozilla.gecko.media.ICodecCallbacks;
 import org.mozilla.gecko.media.Sample;
 
 interface ICodec {
     void setCallbacks(in ICodecCallbacks callbacks);
     boolean configure(in FormatParam format, in GeckoSurface surface, in int flags, in String drmStubId);
     boolean isAdaptivePlaybackSupported();
+    boolean isHardwareAccelerated();
     boolean isTunneledPlaybackSupported();
     void start();
     void stop();
     void flush();
     void release();
 
     Sample dequeueInput(int size);
     oneway void queueInput(in Sample sample);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1042,26 +1042,16 @@ public class GeckoAppShell
                 sScreenDepth = 24;
             }
         }
 
         return sScreenDepth;
     }
 
     @WrapForJNI(calledFrom = "gecko")
-    private static synchronized void setScreenDepthOverride(int aScreenDepth) {
-        if (sScreenDepth != 0) {
-            Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
-            throw new IllegalStateException();
-        }
-
-        sScreenDepth = aScreenDepth;
-    }
-
-    @WrapForJNI(calledFrom = "gecko")
     private static void performHapticFeedback(boolean aIsLongPress) {
         // Don't perform haptic feedback if a vibration is currently playing,
         // because the haptic feedback will nuke the vibration.
         if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
             getHapticFeedbackDelegate().performHapticFeedback(
                     aIsLongPress ? HapticFeedbackConstants.LONG_PRESS
                                  : HapticFeedbackConstants.VIRTUAL_KEY);
             sVibrationMaybePlaying = false;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -345,16 +345,17 @@ import org.mozilla.gecko.gfx.GeckoSurfac
 
     private volatile ICodecCallbacks mCallbacks;
     private AsyncCodec mCodec;
     private InputProcessor mInputProcessor;
     private OutputProcessor mOutputProcessor;
     private SamplePool mSamplePool;
     // Values will be updated after configure called.
     private volatile boolean mIsAdaptivePlaybackSupported = false;
+    private volatile boolean mIsHardwareAccelerated = false;
     private boolean mIsTunneledPlaybackSupported = false;
 
     public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException {
         mCallbacks = callbacks;
         callbacks.asBinder().linkToDeath(this, 0);
     }
 
     // IBinder.DeathRecipient
@@ -394,16 +395,17 @@ import org.mozilla.gecko.gfx.GeckoSurfac
 
         final List<String> found = findMatchingCodecNames(mime, flags == MediaCodec.CONFIGURE_FLAG_ENCODE);
         for (final String name : found) {
             final AsyncCodec codec = configureCodec(name, fmt, surface, flags, drmStubId);
             if (codec == null) {
                 Log.w(LOGTAG, "unable to configure " + name + ". Try next.");
                 continue;
             }
+            mIsHardwareAccelerated = !name.startsWith("OMX.google.");
             mCodec = codec;
             mInputProcessor = new InputProcessor();
             final boolean renderToSurface = surface != null;
             mOutputProcessor = new OutputProcessor(renderToSurface);
             mSamplePool = new SamplePool(name, renderToSurface);
             if (renderToSurface) {
                 mIsTunneledPlaybackSupported = mCodec.isTunneledPlaybackSupported(mime);
             }
@@ -473,16 +475,21 @@ import org.mozilla.gecko.gfx.GeckoSurfac
     }
 
     @Override
     public synchronized boolean isAdaptivePlaybackSupported() {
         return mIsAdaptivePlaybackSupported;
     }
 
     @Override
+    public synchronized boolean isHardwareAccelerated() {
+        return mIsHardwareAccelerated;
+    }
+
+    @Override
     public synchronized boolean isTunneledPlaybackSupported() {
         return mIsTunneledPlaybackSupported;
     }
 
     @Override
     public synchronized void start() throws RemoteException {
         if (DEBUG) { Log.d(LOGTAG, "start " + this); }
         mInputProcessor.start();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -187,16 +187,31 @@ public final class CodecProxy {
             return mRemote.isAdaptivePlaybackSupported();
         } catch (RemoteException e) {
             e.printStackTrace();
             return false;
         }
     }
 
     @WrapForJNI
+    public synchronized boolean isHardwareAccelerated()
+    {
+      if (mRemote == null) {
+          Log.e(LOGTAG, "cannot check isHardwareAccelerated with an ended codec");
+          return false;
+      }
+      try {
+            return mRemote.isHardwareAccelerated();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    @WrapForJNI
     public synchronized boolean isTunneledPlaybackSupported()
     {
       if (mRemote == null) {
           Log.e(LOGTAG, "cannot check isTunneledPlaybackSupported with an ended codec");
           return false;
       }
       try {
             return mRemote.isTunneledPlaybackSupported();
--- a/netwerk/base/BackgroundFileSaver.cpp
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -485,20 +485,26 @@ BackgroundFileSaver::ProcessStateChange(
         // Move the file.  If this fails, we still reference the original file
         // in mActualTarget, so that it is deleted if requested.  If this
         // succeeds, the nsIFile instance referenced by mActualTarget mutates
         // and starts pointing to the new file, but we'll discard the reference.
         rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Now we can update the actual target file name.
-      mActualTarget = renamedTarget;
-      mActualTargetKeepPartial = renamedTargetKeepPartial;
+      // We should not only update the mActualTarget with renameTarget when
+      // they point to the different files.
+      // In this way, if mActualTarget and renamedTarget point to the same file
+      // with different addresses, "CheckCompletion()" will return false forever.
     }
+
+    // Update mActualTarget with renameTarget,
+    // even if they point to the same file.
+    mActualTarget = renamedTarget;
+    mActualTargetKeepPartial = renamedTargetKeepPartial;
   }
 
   // Notify if the target file name actually changed.
   if (!equalToCurrent) {
     // We must clone the nsIFile instance because mActualTarget is not
     // immutable, it may change if the target is renamed later.
     nsCOMPtr<nsIFile> actualTargetToNotify;
     rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -1,11 +1,12 @@
 job-defaults:
     max-run-time: 1800
     suite: raptor
+    workdir: /home/cltbld
     tier:
         by-test-platform:
             windows10-64-ccov/.*: 3
             linux64-ccov/.*: 3
             default: 2
     virtualization:
         by-test-platform:
             windows10-64-ccov/.*: virtual
--- a/taskcluster/scripts/misc/build-cbindgen.sh
+++ b/taskcluster/scripts/misc/build-cbindgen.sh
@@ -1,11 +1,13 @@
 #!/bin/bash
 set -x -e -v
 
+# If you update this, make sure to update the minimum version in rust.configure
+# as well.
 CBINDGEN_VERSION=v0.6.1
 TARGET="$1"
 
 case "$(uname -s)" in
 Linux)
     WORKSPACE=$HOME/workspace
     UPLOAD_DIR=$HOME/artifacts
     COMPRESS_EXT=xz
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -14,16 +14,18 @@ import yaml
 
 from .generator import TaskGraphGenerator
 from .create import create_tasks
 from .parameters import Parameters, get_version, get_app_version
 from .taskgraph import TaskGraph
 from .try_option_syntax import parse_message
 from .actions import render_actions_json
 from taskgraph.util.partials import populate_release_history
+from taskgraph.util.yaml import load_yaml
+
 
 logger = logging.getLogger(__name__)
 
 ARTIFACTS_DIR = 'artifacts'
 
 # For each project, this gives a set of parameters specific to the project.
 # See `taskcluster/docs/parameters.rst` for information on parameters.
 PER_PROJECT_PARAMETERS = {
@@ -315,18 +317,17 @@ def write_artifact(filename, data):
             f.write(json.dumps(data))
     else:
         raise TypeError("Don't know how to write to {}".format(filename))
 
 
 def read_artifact(filename):
     path = os.path.join(ARTIFACTS_DIR, filename)
     if filename.endswith('.yml'):
-        with open(path, 'r') as f:
-            return yaml.load(f)
+        return load_yaml(path, filename)
     elif filename.endswith('.json'):
         with open(path, 'r') as f:
             return json.load(f)
     elif filename.endswith('.gz'):
         import gzip
         with gzip.open(path, 'rb') as f:
             return json.load(f)
     else:
--- a/taskcluster/taskgraph/transforms/beetmover_geckoview.py
+++ b/taskcluster/taskgraph/transforms/beetmover_geckoview.py
@@ -64,17 +64,17 @@ def make_task_description(config, jobs):
         attributes = dep_job.attributes
 
         treeherder = job.get('treeherder', {})
         treeherder.setdefault('symbol', 'BM-gv')
         dep_th_platform = dep_job.task.get('extra', {}).get(
             'treeherder', {}).get('machine', {}).get('platform', '')
         treeherder.setdefault('platform',
                               '{}/opt'.format(dep_th_platform))
-        treeherder.setdefault('tier', 3)
+        treeherder.setdefault('tier', 2)
         treeherder.setdefault('kind', 'build')
         label = job['label']
         description = (
             "Beetmover submission for geckoview"
             "{build_platform}/{build_type}'".format(
                 build_platform=attributes.get('build_platform'),
                 build_type=attributes.get('build_type')
             )
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -332,31 +332,32 @@ def mozharness_test_on_native_engine(con
     ]]
 
     if test['reboot']:
         worker['reboot'] = test['reboot']
 
     if test['max-run-time']:
         worker['max-run-time'] = test['max-run-time']
 
-    worker['env'] = env = {
+    env = worker.setdefault('env', {})
+    env.update({
         'GECKO_HEAD_REPOSITORY': config.params['head_repository'],
         'GECKO_HEAD_REV': config.params['head_rev'],
         'MOZHARNESS_CONFIG': ' '.join(mozharness['config']),
         'MOZHARNESS_SCRIPT': mozharness['script'],
         'MOZHARNESS_URL': {'task-reference': mozharness_url},
         'MOZILLA_BUILD_URL': {'task-reference': installer_url},
         "MOZ_NO_REMOTE": '1',
         "NO_EM_RESTART": '1',
         "XPCOM_DEBUG_BREAK": 'warn',
         "NO_FAIL_ON_TEST_ERRORS": '1',
         "MOZ_HIDE_RESULTS_TABLE": '1',
         "MOZ_NODE_PATH": "/usr/local/bin/node",
         'MOZ_AUTOMATION': '1',
-    }
+    })
     # talos tests don't need Xvfb
     if is_talos:
         env['NEED_XVFB'] = 'false'
 
     script = 'test-macosx.sh' if is_macosx else 'test-linux.sh'
     worker['context'] = '{}/raw-file/{}/taskcluster/scripts/tester/{}'.format(
         config.params['head_repository'], config.params['head_rev'], script
     )
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -141,16 +141,19 @@ test_description_schema = Schema({
     # description of the suite, for the task metadata
     'description': basestring,
 
     # test suite name, or <suite>/<flavor>
     Required('suite'): optionally_keyed_by(
         'test-platform',
         basestring),
 
+    # base work directory used to set up the task.
+    Optional('workdir'): basestring,
+
     # the name by which this test suite is addressed in try syntax; defaults to
     # the test-name.  This will translate to the `unittest_try_name` or
     # `talos_try_name` attribute.
     Optional('try-name'): basestring,
 
     # additional tags to mark up this type of test
     Optional('tags'): {basestring: object},
 
@@ -1092,16 +1095,19 @@ def make_job_description(config, tests):
         else:
             # otherwise just use skip-unless-schedules
             jobdesc['optimization'] = {'skip-unless-schedules': schedules}
 
         run = jobdesc['run'] = {}
         run['using'] = 'mozharness-test'
         run['test'] = test
 
+        if 'workdir' in test:
+            run['workdir'] = test.pop('workdir')
+
         jobdesc['worker-type'] = test.pop('worker-type')
         if test.get('fetches'):
             jobdesc['fetches'] = test.pop('fetches')
 
         yield jobdesc
 
 
 def normpath(path):
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/mozharness/mozilla/fetches.py
@@ -0,0 +1,59 @@
+# 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/.
+
+import os
+from distutils.spawn import find_executable
+
+import mozfile
+
+ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{task}/artifacts/{artifact}'
+
+
+class FetchesMixin(object):
+    """Utility class to download artifacts via `MOZ_FETCHES` and the
+    `fetch-content` script."""
+
+    @property
+    def fetch_script(self):
+        if getattr(self, '_fetch_script', None):
+            return self._fetch_script
+
+        self._fetch_script = find_executable('fetch-content')
+        if not self._fetch_script and 'GECKO_PATH' in os.environ:
+            self._fetch_script = os.path.join(os.environ['GECKO_PATH'],
+                'taskcluster', 'script', 'misc', 'fetch-content')
+        return self._fetch_script
+
+    def fetch_content(self):
+        if not os.environ.get('MOZ_FETCHES'):
+            self.warning('no fetches to download')
+            return
+
+        fetches = os.environ['MOZ_FETCHES'].split()
+
+        if not self.fetch_script or not os.path.isfile(self.fetch_script):
+            self.warning("fetch-content script not found, downloading manually")
+            self._download_fetches(fetches)
+            return
+
+        cmd = [self.fetch_script, 'task-artifacts'] + fetches
+        self.run_command(cmd, env=os.environ, throw_exception=True)
+
+    def _download_fetches(self, fetches):
+        # TODO: make sure fetch-content script is available everywhere
+        #       so this isn't needed
+        for word in fetches:
+            artifact, task = word.split('@', 1)
+            extdir = os.environ['MOZ_FETCHES_DIR']
+
+            if '>' in artifact:
+                artifact, subdir = artifact.rsplit('>', 1)
+                extdir = os.path.join(extdir, subdir)
+
+            url = ARTIFACT_URL.format(artifact=artifact, task=task)
+            self.download_file(url)
+
+            filename = os.path.basename(artifact)
+            mozfile.extract(filename, extdir)
+            os.remove(filename)
--- a/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
+++ b/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
@@ -3,17 +3,16 @@
 # 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/.
 
 import json
 import os
 import posixpath
 import shutil
 import sys
-import tarfile
 import tempfile
 import zipfile
 import uuid
 
 import mozinfo
 from mozharness.base.script import (
     PreScriptAction,
     PostScriptAction,
@@ -139,41 +138,24 @@ class CodeCoverageMixin(SingleTestMixin)
         with zipfile.ZipFile(classfiles_zip_path, 'r') as z:
             z.extractall(self.classfiles_dir)
         os.remove(classfiles_zip_path)
 
         # Create the directory where the emulator coverage file will be placed.
         self.java_coverage_output_path = os.path.join(tempfile.mkdtemp(),
                                                       'junit-coverage.ec')
 
-    def _download_grcov(self):
-        fetches_dir = os.environ.get('MOZ_FETCHES_DIR')
-        if fetches_dir and os.path.isfile(os.path.join(fetches_dir, 'grcov')):
-            self.grcov_dir = fetches_dir
-        else:
-            # Create the grcov directory, then download it.
-            # TODO: use the fetch-content script to download artifacts.
-            self.grcov_dir = tempfile.mkdtemp()
-            ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{task}/artifacts/{artifact}'
-            for word in os.getenv('MOZ_FETCHES').split():
-                artifact, task = word.split('@', 1)
-                filename = os.path.basename(artifact)
-                url = ARTIFACT_URL.format(artifact=artifact, task=task)
-                self.download_file(url, parent_dir=self.grcov_dir)
-
-                with tarfile.open(os.path.join(self.grcov_dir, filename), 'r') as tar:
-                    tar.extractall(self.grcov_dir)
-                os.remove(os.path.join(self.grcov_dir, filename))
-
     @PostScriptAction('download-and-extract')
     def setup_coverage_tools(self, action, success=None):
         if not self.code_coverage_enabled and not self.java_code_coverage_enabled:
             return
 
-        self._download_grcov()
+        self.grcov_dir = os.environ['MOZ_FETCHES_DIR']
+        if not os.path.isfile(os.path.join(self.grcov_dir, 'grcov')):
+            self.fetch_content()
 
         if self.code_coverage_enabled:
             self._setup_cpp_js_coverage_tools()
 
         if self.java_code_coverage_enabled:
             self._setup_java_coverage_tools()
 
     @PostScriptAction('download-and-extract')
--- a/testing/mozharness/mozharness/mozilla/testing/per_test_base.py
+++ b/testing/mozharness/mozharness/mozilla/testing/per_test_base.py
@@ -8,18 +8,20 @@
 import math
 import os
 import posixpath
 import re
 import sys
 import mozinfo
 from manifestparser import TestManifest
 
+from mozharness.mozilla.fetches import FetchesMixin
 
-class SingleTestMixin(object):
+
+class SingleTestMixin(FetchesMixin):
     """Utility functions for per-test testing like test verification and per-test coverage."""
 
     def __init__(self):
         self.suites = {}
         self.tests_downloaded = False
         self.reftest_test_dir = None
         self.jsreftest_test_dir = None
         # Map from full test path on the test machine to a relative path in the source checkout.
--- a/testing/mozharness/mozharness/mozilla/testing/raptor.py
+++ b/testing/mozharness/mozharness/mozilla/testing/raptor.py
@@ -294,16 +294,18 @@ class Raptor(TestingMixin, MercurialScri
         )
         if self.config.get('run_local'):
             self.raptor_path = os.path.join(self.repo_path, 'testing', 'raptor')
 
     # Action methods. {{{1
     # clobber defined in BaseScript
 
     def download_and_extract(self, extract_dirs=None, suite_categories=None):
+        if 'MOZ_FETCHES' in os.environ:
+            self.fetch_content()
         return super(Raptor, self).download_and_extract(
             suite_categories=['common', 'raptor']
         )
 
     def create_virtualenv(self, **kwargs):
         """VirtualenvMixin.create_virtualenv() assuemes we're using
         self.config['virtualenv_modules']. Since we are installing
         raptor from its source, we have to wrap that method here."""
--- a/testing/mozharness/mozharness/mozilla/testing/testbase.py
+++ b/testing/mozharness/mozharness/mozilla/testing/testbase.py
@@ -15,16 +15,17 @@ from urlparse import urlparse, ParseResu
 from mozharness.base.errors import BaseErrorList
 from mozharness.base.log import FATAL, WARNING
 from mozharness.base.python import (
     ResourceMonitoringMixin,
     VirtualenvMixin,
     virtualenv_config_options,
 )
 from mozharness.mozilla.automation import AutomationMixin, TBPL_WARNING
+from mozharness.mozilla.fetches import FetchesMixin
 from mozharness.mozilla.structuredlog import StructuredOutputParser
 from mozharness.mozilla.testing.unittest import DesktopUnittestOutputParser
 from mozharness.mozilla.testing.try_tools import TryToolsMixin, try_config_options
 from mozharness.mozilla.testing.verify_tools import VerifyToolsMixin, verify_config_options
 from mozharness.mozilla.tooltool import TooltoolMixin
 
 from mozharness.lib.python.authentication import get_credentials
 
@@ -97,17 +98,17 @@ testing_config_options = [
       }],
 ] + copy.deepcopy(virtualenv_config_options) \
   + copy.deepcopy(try_config_options) \
   + copy.deepcopy(verify_config_options)
 
 
 # TestingMixin {{{1
 class TestingMixin(VirtualenvMixin, AutomationMixin, ResourceMonitoringMixin,
-                   TooltoolMixin, TryToolsMixin, VerifyToolsMixin):
+                   TooltoolMixin, TryToolsMixin, VerifyToolsMixin, FetchesMixin):
     """
     The steps to identify + download the proper bits for [browser] unit
     tests and Talos.
     """
 
     installer_url = None
     installer_path = None
     binary_path = None
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -6,16 +6,19 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["ExtensionTestUtils"];
 
 ChromeUtils.import("resource://gre/modules/ActorManagerParent.jsm");
 ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+// Windowless browsers can create documents that rely on XUL Custom Elements:
+ChromeUtils.import("resource://gre/modules/CustomElementsListener.jsm", null);
+
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonTestUtils",
                                "resource://testing-common/AddonTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ContentTask",
                                "resource://testing-common/ContentTask.jsm");
 ChromeUtils.defineModuleGetter(this, "Extension",
                                "resource://gre/modules/Extension.jsm");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/processsingleton/CustomElementsListener.jsm
@@ -0,0 +1,22 @@
+/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/Services.jsm");
+
+// Set up Custom Elements for XUL and XHTML documents before anything else
+// happens. Anything loaded here should be considered part of core XUL functionality.
+// Any window-specific elements can be registered via <script> tags at the
+// top of individual documents.
+Services.obs.addObserver({
+  observe(doc) {
+    if (doc.nodePrincipal.isSystemPrincipal && (
+      doc.contentType == "application/vnd.mozilla.xul+xml" ||
+      doc.contentType == "application/xhtml+xml"
+    )) {
+      Services.scriptloader.loadSubScript(
+        "chrome://global/content/customElements.js", doc.ownerGlobal);
+    }
+  }
+}, "document-element-inserted");
--- a/toolkit/components/processsingleton/MainProcessSingleton.js
+++ b/toolkit/components/processsingleton/MainProcessSingleton.js
@@ -55,44 +55,28 @@ MainProcessSingleton.prototype = {
 
       Services.search.addEngine(engineURL.spec, null, iconURL ? iconURL.spec : null, true);
     });
   },
 
   observe(subject, topic, data) {
     switch (topic) {
     case "app-startup": {
+      ChromeUtils.import("resource://gre/modules/CustomElementsListener.jsm", null);
       Services.obs.addObserver(this, "xpcom-shutdown");
-      Services.obs.addObserver(this, "document-element-inserted");
 
       // Load this script early so that console.* is initialized
       // before other frame scripts.
       Services.mm.loadFrameScript("chrome://global/content/browser-content.js", true, true);
       Services.ppmm.loadProcessScript("chrome://global/content/process-content.js", true, true);
       Services.mm.addMessageListener("Search:AddEngine", this.addSearchEngine);
       Services.ppmm.loadProcessScript("resource:///modules/ContentObservers.js", true);
       break;
     }
 
-    case "document-element-inserted":
-      // Set up Custom Elements for XUL and XHTML documents before anything else
-      // happens. Anything loaded here should be considered part of core XUL functionality.
-      // Any window-specific elements can be registered via <script> tags at the
-      // top of individual documents.
-      const doc = subject;
-      if (doc.nodePrincipal.isSystemPrincipal && (
-            doc.contentType == "application/vnd.mozilla.xul+xml" ||
-            doc.contentType == "application/xhtml+xml"
-        )) {
-        Services.scriptloader.loadSubScript(
-          "chrome://global/content/customElements.js", doc.ownerGlobal);
-      }
-      break;
-
     case "xpcom-shutdown":
       Services.mm.removeMessageListener("Search:AddEngine", this.addSearchEngine);
-      Services.obs.removeObserver(this, "document-element-inserted");
       break;
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MainProcessSingleton]);
--- a/toolkit/components/processsingleton/moz.build
+++ b/toolkit/components/processsingleton/moz.build
@@ -7,8 +7,12 @@
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 EXTRA_COMPONENTS += [
     'ContentProcessSingleton.js',
     'MainProcessSingleton.js',
     'ProcessSingleton.manifest',
 ]
+
+EXTRA_JS_MODULES += [
+    'CustomElementsListener.jsm',
+]
--- a/toolkit/content/TopLevelVideoDocument.js
+++ b/toolkit/content/TopLevelVideoDocument.js
@@ -1,46 +1,55 @@
 /* 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";
 
-// <video> is used for top-level audio documents as well
-let videoElement = document.getElementsByTagName("video")[0];
+// Hide our variables from the web content, even though the spec allows them
+// (and the DOM) to be accessible (see bug 1474832)
+{
+  // <video> is used for top-level audio documents as well
+  let videoElement = document.getElementsByTagName("video")[0];
 
-// Redirect focus to the video element whenever the document receives
-// focus.
-document.addEventListener("focus", (e) => {
-  // We don't want to retarget focus if it goes to the controls in
-  // the video element. Because they're anonymous content, the target
-  // will be the video element in that case. Avoid calling .focus()
-  // for those events:
-  if (e.target == videoElement) {
-    return;
-  }
-  videoElement.focus();
-}, true);
-
-// Handle fullscreen mode
-document.addEventListener("keypress", ev => {
-  // Maximize the standalone video when pressing F11,
-  // but ignore audio elements
-  if (ev.key == "F11" && videoElement.videoWidth != 0 && videoElement.videoHeight != 0) {
-    // If we're in browser fullscreen mode, it means the user pressed F11
-    // while browser chrome or another tab had focus.
-    // Don't break leaving that mode, so do nothing here.
-    if (window.fullScreen) {
+  let setFocusToVideoElement = function(e) {
+    // We don't want to retarget focus if it goes to the controls in
+    // the video element. Because they're anonymous content, the target
+    // will be the video element in that case. Avoid calling .focus()
+    // for those events:
+    if (e && e.target == videoElement) {
       return;
     }
+    videoElement.focus();
+  };
 
-    // If we're not in browser fullscreen mode, prevent entering into that,
-    // so we don't end up there after pressing Esc.
-    ev.preventDefault();
-    ev.stopPropagation();
+  // Redirect focus to the video element whenever the document receives
+  // focus.
+  document.addEventListener("focus", setFocusToVideoElement, true);
+
+  // Focus on the video in the newly created document.
+  setFocusToVideoElement();
 
-    if (!document.mozFullScreenElement) {
-      videoElement.mozRequestFullScreen();
-    } else {
-      document.mozCancelFullScreen();
+  // Handle fullscreen mode
+  document.addEventListener("keypress", ev => {
+    // Maximize the standalone video when pressing F11,
+    // but ignore audio elements
+    if (ev.key == "F11" && videoElement.videoWidth != 0 && videoElement.videoHeight != 0) {
+      // If we're in browser fullscreen mode, it means the user pressed F11
+      // while browser chrome or another tab had focus.
+      // Don't break leaving that mode, so do nothing here.
+      if (window.fullScreen) {
+        return;
+      }
+
+      // If we're not in browser fullscreen mode, prevent entering into that,
+      // so we don't end up there after pressing Esc.
+      ev.preventDefault();
+      ev.stopPropagation();
+
+      if (!document.mozFullScreenElement) {
+        videoElement.mozRequestFullScreen();
+      } else {
+        document.mozCancelFullScreen();
+      }
     }
-  }
-});
+  });
+}
--- a/toolkit/content/tests/widgets/test_videocontrols_standalone.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_standalone.html
@@ -19,16 +19,19 @@ const videoHeight = 240;
 
 function getMediaElement(aWindow) {
   return aWindow.document.getElementsByTagName("video")[0];
 }
 
 var popup = window.open("seek_with_sound.ogg");
 popup.addEventListener("load", function() {
   var video = getMediaElement(popup);
+
+  is(popup.document.activeElement, video, "Document should load with focus moved to the video element.");
+
   if (!video.paused)
     runTestVideo(video);
   else {
     video.addEventListener("play", function() {
       runTestVideo(video);
     }, {once: true});
   }
 }, {once: true});
@@ -47,16 +50,19 @@ function runTestVideo(aVideo) {
     runTestAudioPre();
   }, "The media element should eventually be resized to match the intrinsic size of the video.");
 }
 
 function runTestAudioPre() {
   popup = window.open("audio.ogg");
   popup.addEventListener("load", function() {
     var audio = getMediaElement(popup);
+
+    is(popup.document.activeElement, audio, "Document should load with focus moved to the video element.");
+
     if (!audio.paused)
       runTestAudio(audio);
     else {
       audio.addEventListener("play", function() {
         runTestAudio(audio);
       }, {once: true});
     }
   }, {once: true});
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1873,17 +1873,20 @@ var gHeader = {
       var query = aEvent.target.value;
       if (query.length == 0)
         return;
 
       let url = AddonRepository.getSearchURL(query);
 
       let browser = getBrowserElement();
       let chromewin = browser.ownerGlobal;
-      chromewin.openLinkIn(url, "tab", {fromChrome: true});
+      chromewin.openLinkIn(url, "tab", {
+        fromChrome: true,
+        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+      });
     });
   },
 
   focusSearchBox() {
     this._search.focus();
   },
 
   onKeyPress(aEvent) {
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -582,21 +582,16 @@ nsAppShell::Observe(nsISupports* aSubjec
         return NS_OK;
 
     } else if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
         NS_CreateServicesFromCategory("browser-delayed-startup-finished", nullptr,
                                       "browser-delayed-startup-finished");
 
     } else if (!strcmp(aTopic, "profile-after-change")) {
         if (jni::IsAvailable()) {
-            // See if we want to force 16-bit color before doing anything
-            if (Preferences::GetBool("gfx.android.rgb16.force", false)) {
-                java::GeckoAppShell::SetScreenDepthOverride(16);
-            }
-
             java::GeckoThread::SetState(
                     java::GeckoThread::State::PROFILE_READY());
 
             // Gecko on Android follows the Android app model where it never
             // stops until it is killed by the system or told explicitly to
             // quit. Therefore, we should *not* exit Gecko when there is no
             // window or the last window is closed. nsIAppStartup::Quit will
             // still force Gecko to exit.