merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Wed, 13 Sep 2017 23:56:31 +0200
changeset 430118 0b3646aa9cbf3f77ee574a553ade9685e9229ab5
parent 430074 0e706b53052c86b247bb8037d1a3fbf2e913045e (current diff)
parent 430117 8b156b6abe6137e1d0e0c97f848a7b5d0058530f (diff)
child 430208 8645a74bbbd06b67699317df1abf3897db0e43d5
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.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 autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: 4cAKMj5aTV5
devtools/client/responsivedesign/responsivedesign.jsm
--- a/.eslintignore
+++ b/.eslintignore
@@ -92,17 +92,16 @@ devtools/client/framework/**
 !devtools/client/framework/toolbox*
 devtools/client/inspector/markup/test/doc_markup_events_*.html
 devtools/client/inspector/rules/test/doc_media_queries.html
 devtools/client/memory/test/chrome/*.html
 devtools/client/performance/components/test/test_jit_optimizations_01.html
 devtools/client/projecteditor/**
 devtools/client/responsive.html/test/browser/touch.html
 devtools/client/responsivedesign/**
-!devtools/client/responsivedesign/responsivedesign.jsm
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/*.jsm
 devtools/client/shared/components/reps/reps.js
 devtools/client/shared/components/reps/test/mochitest/*.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -8503,22 +8503,16 @@ XPCOMUtils.defineLazyGetter(Scratchpad, 
 });
 
 var ResponsiveUI = {
   toggle: function RUI_toggle() {
     this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab);
   }
 };
 
-XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
-  let tmp = {};
-  Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", tmp);
-  return tmp.ResponsiveUIManager;
-});
-
 var MousePosTracker = {
   _listeners: new Set(),
   _x: 0,
   _y: 0,
   get _windowUtils() {
     delete this._windowUtils;
     return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
   },
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -7319,17 +7319,17 @@
             case "scrollbutton-up":
               pixelsToScroll = tabStrip.scrollIncrement * -1;
               break;
             case "scrollbutton-down":
               pixelsToScroll = tabStrip.scrollIncrement;
               break;
           }
           if (pixelsToScroll)
-            tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
+            tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll, true);
         }
 
         if (effects == "move" &&
             this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
           ind.collapsed = true;
           this._animateTabMove(event);
           return;
         }
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2725,16 +2725,17 @@ file, You can obtain one at http://mozil
                   class="click-to-play-plugins-notification-button-container"
                   pack="center" align="center">
           <xul:button anonid="primarybutton"
                       class="click-to-play-popup-button popup-notification-button"
                       oncommand="document.getBindingParent(this)._onButton(this)"
                       flex="1"/>
           <xul:button anonid="secondarybutton"
                       default="true"
+                      highlight="true"
                       class="click-to-play-popup-button popup-notification-button"
                       oncommand="document.getBindingParent(this)._onButton(this);"
                       flex="1"/>
         </xul:hbox>
         <xul:box hidden="true">
           <children/>
         </xul:box>
       </xul:vbox>
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -432,16 +432,17 @@
                      label="&updateAvailable.header.message;"
                      buttonlabel="&updateAvailable.acceptButton.label;"
                      buttonaccesskey="&updateAvailable.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateAvailable.cancelButton.label;"
                      secondarybuttonaccesskey="&updateAvailable.cancelButton.accesskey;"
                      dropmarkerhidden="true"
                      checkboxhidden="true"
+                     buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-available-notification-content" orient="vertical">
       <description id="update-available-description">&updateAvailable.message;
         <label id="update-available-whats-new" class="text-link" value="&updateAvailable.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
@@ -450,16 +451,17 @@
                      label="&updateManual.header.message;"
                      buttonlabel="&updateManual.acceptButton.label;"
                      buttonaccesskey="&updateManual.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateManual.cancelButton.label;"
                      secondarybuttonaccesskey="&updateManual.cancelButton.accesskey;"
                      dropmarkerhidden="true"
                      checkboxhidden="true"
+                     buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-manual-notification-content" orient="vertical">
       <description id="update-manual-description">&updateManual.message;
         <label id="update-manual-whats-new" class="text-link" value="&updateManual.whatsnew.label;" />
       </description>
     </popupnotificationcontent>
   </popupnotification>
 
@@ -468,16 +470,17 @@
                      label="&updateRestart.header.message2;"
                      buttonlabel="&updateRestart.acceptButton.label;"
                      buttonaccesskey="&updateRestart.acceptButton.accesskey;"
                      closebuttonhidden="true"
                      secondarybuttonlabel="&updateRestart.cancelButton.label;"
                      secondarybuttonaccesskey="&updateRestart.cancelButton.accesskey;"
                      dropmarkerhidden="true"
                      checkboxhidden="true"
+                     buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-restart-notification-content" orient="vertical">
       <description id="update-restart-description">&updateRestart.message2;</description>
     </popupnotificationcontent>
   </popupnotification>
 </panel>
 
 <menupopup id="customizationPaletteItemContextMenu">
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -159,43 +159,54 @@
   width: 28px;
   height: 28px;
   /* 28x28 box - 16x16 image = 12x12 padding, 6 on each side */
   padding: 6px;
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   fill-opacity: 0.6;
   color: inherit;
-  transition-property: background-color;
-  transition-duration: var(--toolbarbutton-hover-transition-duration);
 }
 
 :root[uidensity=compact] .urlbar-icon {
   width: 24px;
   height: 24px;
   /* 24x24 box - 16x16 image = 8x8 padding, 4 on each side */
   padding: 4px;
 }
 
 :root[uidensity=touch] .urlbar-icon {
   width: 30px;
   height: 30px;
   /* 30x30 box - 16x16 image = 14x14 padding, 7 on each side */
   padding: 7px;
 }
 
-.urlbar-icon:hover {
+.urlbar-icon,
+.urlbar-icon-wrapper {
+  transition-property: background-color;
+  transition-duration: var(--toolbarbutton-hover-transition-duration);
+}
+
+.urlbar-icon:hover,
+.urlbar-icon-wrapper:hover {
   background-color: hsla(0,0%,80%,.4);
 }
 
-.urlbar-icon:hover:active {
+.urlbar-icon:hover:active,
+.urlbar-icon-wrapper:hover:active {
   background-color: hsla(0,0%,80%,.45);
   transition-duration: var(--toolbarbutton-active-transition-duration);
 }
 
+.urlbar-icon-wrapper > .urlbar-icon:hover,
+.urlbar-icon-wrapper > .urlbar-icon:hover:active {
+  background-color: transparent;
+}
+
 .urlbar-go-button,
 .search-go-button {
   list-style-image: url("chrome://browser/skin/back.svg");
   width: 26px;
 }
 
 .urlbar-go-button:-moz-locale-dir(ltr),
 .search-go-button:-moz-locale-dir(ltr) {
--- a/build/moz.configure/java.configure
+++ b/build/moz.configure/java.configure
@@ -51,12 +51,12 @@ javac = check_java_tool('javac')
 @checking('for javac version')
 @imports('subprocess')
 def javac_version(javac):
     try:
         output = subprocess.check_output([javac, '-version'],
                                          stderr=subprocess.STDOUT).rstrip()
         version = Version(output.split(' ')[-1])
         if version < '1.8':
-            die('javac 1.8 or higher is required (found %s)' % version)
+            die('javac 1.8 or higher is required (found %s). Check the JAVA_HOME environment variable.' % version)
         return version
     except subprocess.CalledProcessError as e:
         die('Failed to get javac version: %s', e.output)
--- a/devtools/bootstrap.js
+++ b/devtools/bootstrap.js
@@ -205,17 +205,16 @@ function unload(reason) {
   // to be converted into regular modules
   Cu.unload("resource://devtools/client/shared/browser-loader.js");
   Cu.unload("resource://devtools/client/framework/ToolboxProcess.jsm");
   Cu.unload("resource://devtools/shared/apps/Devices.jsm");
   Cu.unload("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
   Cu.unload("resource://devtools/shared/Parser.jsm");
   Cu.unload("resource://devtools/client/shared/DOMHelpers.jsm");
   Cu.unload("resource://devtools/client/shared/widgets/VariablesView.jsm");
-  Cu.unload("resource://devtools/client/responsivedesign/responsivedesign.jsm");
   Cu.unload("resource://devtools/client/shared/widgets/AbstractTreeItem.jsm");
   Cu.unload("resource://devtools/shared/deprecated-sync-thenables.js");
 }
 
 function reload(event) {
   // We automatically reload the toolbox if we are on a browser tab
   // with a toolbox already opened
   let reloadToolbox = false;
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -22,17 +22,17 @@ loader.lazyGetter(this, "PerformancePane
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
-loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 const {MultiLocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new MultiLocalizationHelper(
   "devtools/client/locales/startup.properties",
   "devtools/shim/locales/key-shortcuts.properties"
 );
 
--- a/devtools/client/framework/components/toolbox-controller.js
+++ b/devtools/client/framework/components/toolbox-controller.js
@@ -67,29 +67,36 @@ module.exports = createClass({
 
   updateFocusedButton() {
     this.setFocusedButton(this.state.focusedButton);
   },
 
   setFocusedButton(focusedButton) {
     const {buttonIds} = this.state;
 
-    this.setState({
-      focusedButton: focusedButton && buttonIds.includes(focusedButton)
+    focusedButton = focusedButton && buttonIds.includes(focusedButton)
         ? focusedButton
-        : buttonIds[0]
-    });
+        : buttonIds[0];
+    if (this.state.focusedButton !== focusedButton) {
+      this.setState({
+        focusedButton
+      });
+    }
   },
 
   setCurrentToolId(currentToolId) {
     this.setState({currentToolId});
     // Also set the currently focused button to this tool.
     this.setFocusedButton(currentToolId);
   },
 
+  shouldComponentUpdate() {
+    return this.state.canRender;
+  },
+
   setCanRender() {
     this.setState({ canRender: true });
     this.updateButtonIds();
   },
 
   setOptionsPanel(optionsPanel) {
     this.setState({ optionsPanel });
     this.updateButtonIds();
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -20,18 +20,18 @@ const {gDevTools} = require("./devtools"
 // Load target and toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
 loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true);
 loader.lazyRequireGetter(this, "DeveloperToolbar", "devtools/client/shared/developer-toolbar", true);
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
-loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
 loader.lazyImporter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm");
 loader.lazyImporter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -496,17 +496,20 @@ Toolbox.prototype = {
       // (bug 1072764).
       let toolDef = gDevTools.getToolDefinition(this._defaultToolId);
       if (!toolDef || !toolDef.isTargetSupported(this._target)) {
         this._defaultToolId = "webconsole";
       }
 
       // Start rendering the toolbox toolbar before selecting the tool, as the tools
       // can take a few hundred milliseconds seconds to start up.
-      this.component.setCanRender();
+      // But wait for toolbar buttons to be set before updating this react component.
+      buttonsPromise.then(() => {
+        this.component.setCanRender();
+      });
 
       yield this.selectTool(this._defaultToolId);
 
       // Wait until the original tool is selected so that the split
       // console input will receive focus.
       let splitConsolePromise = promise.resolve();
       if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
         splitConsolePromise = this.openSplitConsole();
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -26,19 +26,19 @@
  * - checkbox:
  *   If true, the menuitem is prefixed by a checkbox and runtime code can
  *   toggle it.
  */
 
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
-loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 exports.menuitems = [
   { id: "menu_devToolbox",
     l10nKey: "devToolboxMenuItem",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
       gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -3,30 +3,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/old-event-emitter");
-const { startup } = require("./utils/window");
-const message = require("./utils/message");
-const { swapToInnerBrowser } = require("./browser/swap");
-const { EmulationFront } = require("devtools/shared/fronts/emulation");
-const { getStr } = require("./utils/l10n");
 
 const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
 
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "throttlingProfiles",
   "devtools/client/shared/network-throttling-profiles");
+loader.lazyRequireGetter(this, "swapToInnerBrowser",
+  "devtools/client/responsive.html/browser/swap", true);
+loader.lazyRequireGetter(this, "startup",
+  "devtools/client/responsive.html/utils/window", true);
+loader.lazyRequireGetter(this, "message",
+  "devtools/client/responsive.html/utils/message");
+loader.lazyRequireGetter(this, "getStr",
+  "devtools/client/responsive.html/utils/l10n", true);
+loader.lazyRequireGetter(this, "EmulationFront",
+  "devtools/shared/fronts/emulation", true);
 
 /**
  * ResponsiveUIManager is the external API for the browser UI, etc. to use when
  * opening and closing the responsive UI.
  *
  * While the HTML UI is in an experimental stage, the older ResponsiveUIManager
  * from devtools/client/responsivedesign/responsivedesign.jsm delegates to this
  * object when the pref "devtools.responsive.html.enabled" is true.
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -58,17 +58,17 @@ registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.devices.url");
   Services.prefs.clearUserPref("devtools.responsive.html.enabled");
   Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
   asyncStorage.removeItem("devtools.devices.url_cache");
   asyncStorage.removeItem("devtools.devices.local");
 });
 
 // This depends on the "devtools.responsive.html.enabled" pref
-const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 
 /**
  * Open responsive design mode for the given tab.
  */
 var openRDM = Task.async(function* (tab) {
   info("Opening responsive design mode");
   let manager = ResponsiveUIManager;
   let ui = yield manager.openIfNeeded(tab.ownerGlobal, tab);
--- a/devtools/client/responsivedesign/moz.build
+++ b/devtools/client/responsivedesign/moz.build
@@ -2,13 +2,14 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 DevToolsModules(
     'resize-commands.js',
     'responsivedesign-child.js',
-    'responsivedesign.jsm',
+    'responsivedesign-old.js',
+    'responsivedesign.js',
 )
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: Responsive Design Mode')
--- a/devtools/client/responsivedesign/resize-commands.js
+++ b/devtools/client/responsivedesign/resize-commands.js
@@ -1,17 +1,17 @@
 /* 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 { Cc, Ci, Cu } = require("chrome");
 
-loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 
 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"].
                          getService(Ci.nsIStringBundleService).
                          createBundle("chrome://branding/locale/brand.properties").
                          GetStringFromName("brandShortName");
 
 const Services = require("Services");
 const osString = Services.appinfo.OS;
rename from devtools/client/responsivedesign/responsivedesign.jsm
rename to devtools/client/responsivedesign/responsivedesign-old.js
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign-old.js
@@ -1,17 +1,14 @@
 /* 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 Cu = Components.utils;
-
-const { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const { Task } = require("devtools/shared/task");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 
 loader.lazyImporter(this, "SystemAppProxy",
                     "resource://gre/modules/SystemAppProxy.jsm");
 loader.lazyImporter(this, "BrowserUtils",
@@ -28,18 +25,16 @@ loader.lazyRequireGetter(this, "flags",
 loader.lazyRequireGetter(this, "EmulationFront",
                          "devtools/shared/fronts/emulation", true);
 loader.lazyRequireGetter(this, "DebuggerClient",
                          "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "DebuggerServer",
                          "devtools/server/main", true);
 loader.lazyRequireGetter(this, "system", "devtools/shared/system");
 
-this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
-
 const NEW_RDM_ENABLED = "devtools.responsive.html.enabled";
 
 const MIN_WIDTH = 50;
 const MIN_HEIGHT = 50;
 
 const MAX_WIDTH = 10000;
 const MAX_HEIGHT = 10000;
 
@@ -51,17 +46,17 @@ const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
 const SHARED_L10N = new LocalizationHelper("devtools/client/locales/shared.properties");
 
 function debug(msg) {
   // dump(`RDM UI: ${msg}\n`);
 }
 
 var ActiveTabs = new Map();
 
-var Manager = {
+var ResponsiveUIManager = {
   /**
    * Check if the a tab is in a responsive mode.
    * Leave the responsive mode if active,
    * active the responsive mode if not active.
    *
    * @param window the main window.
    * @param tab the tab targeted.
    */
@@ -132,30 +127,18 @@ var Manager = {
       case "resize toggle":
         this.toggle(window, tab);
         break;
       default:
     }
   })
 };
 
-EventEmitter.decorate(Manager);
-
-// If the new HTML RDM UI is enabled and e10s is enabled by default (e10s is required for
-// the new HTML RDM UI to function), delegate the ResponsiveUIManager API over to that
-// tool instead.  Performing this delegation here allows us to contain the pref check to a
-// single place.
-if (Services.prefs.getBoolPref(NEW_RDM_ENABLED) &&
-    Services.appinfo.browserTabsRemoteAutostart) {
-  let { ResponsiveUIManager } =
-    require("devtools/client/responsive.html/manager");
-  this.ResponsiveUIManager = ResponsiveUIManager;
-} else {
-  this.ResponsiveUIManager = Manager;
-}
+EventEmitter.decorate(ResponsiveUIManager);
+exports.ResponsiveUIManager = ResponsiveUIManager;
 
 var defaultPresets = [
   // Phones
   {key: "320x480", width: 320, height: 480},   // iPhone, B2G, with <meta viewport>
   {key: "360x640", width: 360, height: 640},   // Android 4, phones, with <meta viewport>
 
   // Tablets
   {key: "768x1024", width: 768, height: 1024}, // iPad, with <meta viewport>
copy from devtools/client/responsivedesign/responsivedesign.jsm
copy to devtools/client/responsivedesign/responsivedesign.js
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.js
@@ -1,1330 +1,22 @@
 /* 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 Cu = Components.utils;
-
-const { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const { Task } = require("devtools/shared/task");
 const Services = require("Services");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-
-loader.lazyImporter(this, "SystemAppProxy",
-                    "resource://gre/modules/SystemAppProxy.jsm");
-loader.lazyImporter(this, "BrowserUtils",
-                    "resource://gre/modules/BrowserUtils.jsm");
-loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
-loader.lazyRequireGetter(this, "showDoorhanger",
-                         "devtools/client/shared/doorhanger", true);
-loader.lazyRequireGetter(this, "gDevToolsBrowser",
-                         "devtools/client/framework/devtools-browser", true);
-loader.lazyRequireGetter(this, "TouchEventSimulator",
-                         "devtools/shared/touch/simulator", true);
-loader.lazyRequireGetter(this, "flags",
-                         "devtools/shared/flags");
-loader.lazyRequireGetter(this, "EmulationFront",
-                         "devtools/shared/fronts/emulation", true);
-loader.lazyRequireGetter(this, "DebuggerClient",
-                         "devtools/shared/client/main", true);
-loader.lazyRequireGetter(this, "DebuggerServer",
-                         "devtools/server/main", true);
-loader.lazyRequireGetter(this, "system", "devtools/shared/system");
-
-this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
 
 const NEW_RDM_ENABLED = "devtools.responsive.html.enabled";
 
-const MIN_WIDTH = 50;
-const MIN_HEIGHT = 50;
-
-const MAX_WIDTH = 10000;
-const MAX_HEIGHT = 10000;
-
-const SLOW_RATIO = 6;
-const ROUND_RATIO = 10;
-
-const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
-
-const SHARED_L10N = new LocalizationHelper("devtools/client/locales/shared.properties");
-
-function debug(msg) {
-  // dump(`RDM UI: ${msg}\n`);
-}
-
-var ActiveTabs = new Map();
-
-var Manager = {
-  /**
-   * Check if the a tab is in a responsive mode.
-   * Leave the responsive mode if active,
-   * active the responsive mode if not active.
-   *
-   * @param window the main window.
-   * @param tab the tab targeted.
-   */
-  toggle: function (window, tab) {
-    if (this.isActiveForTab(tab)) {
-      ActiveTabs.get(tab).close();
-    } else {
-      this.openIfNeeded(window, tab);
-    }
-  },
-
-  /**
-   * Launches the responsive mode.
-   *
-   * @param window the main window.
-   * @param tab the tab targeted.
-   * @returns {ResponsiveUI} the instance of ResponsiveUI for the current tab.
-   */
-  openIfNeeded: Task.async(function* (window, tab) {
-    let ui;
-    if (!this.isActiveForTab(tab)) {
-      ui = new ResponsiveUI(window, tab);
-      yield ui.inited;
-    } else {
-      ui = this.getResponsiveUIForTab(tab);
-    }
-    return ui;
-  }),
-
-  /**
-   * Returns true if responsive view is active for the provided tab.
-   *
-   * @param tab the tab targeted.
-   */
-  isActiveForTab: function (tab) {
-    return ActiveTabs.has(tab);
-  },
-
-  /**
-   * Return the responsive UI controller for a tab.
-   */
-  getResponsiveUIForTab: function (tab) {
-    return ActiveTabs.get(tab);
-  },
-
-  /**
-   * Handle gcli commands.
-   *
-   * @param window the browser window.
-   * @param tab the tab targeted.
-   * @param command the command name.
-   * @param args command arguments.
-   */
-  handleGcliCommand: Task.async(function* (window, tab, command, args) {
-    switch (command) {
-      case "resize to":
-        let ui = yield this.openIfNeeded(window, tab);
-        ui.setViewportSize(args);
-        break;
-      case "resize on":
-        this.openIfNeeded(window, tab);
-        break;
-      case "resize off":
-        if (this.isActiveForTab(tab)) {
-          yield ActiveTabs.get(tab).close();
-        }
-        break;
-      case "resize toggle":
-        this.toggle(window, tab);
-        break;
-      default:
-    }
-  })
-};
-
-EventEmitter.decorate(Manager);
-
 // If the new HTML RDM UI is enabled and e10s is enabled by default (e10s is required for
 // the new HTML RDM UI to function), delegate the ResponsiveUIManager API over to that
 // tool instead.  Performing this delegation here allows us to contain the pref check to a
 // single place.
 if (Services.prefs.getBoolPref(NEW_RDM_ENABLED) &&
     Services.appinfo.browserTabsRemoteAutostart) {
-  let { ResponsiveUIManager } =
-    require("devtools/client/responsive.html/manager");
-  this.ResponsiveUIManager = ResponsiveUIManager;
+  let { ResponsiveUIManager } = require("devtools/client/responsive.html/manager");
+  module.exports = ResponsiveUIManager;
 } else {
-  this.ResponsiveUIManager = Manager;
-}
-
-var defaultPresets = [
-  // Phones
-  {key: "320x480", width: 320, height: 480},   // iPhone, B2G, with <meta viewport>
-  {key: "360x640", width: 360, height: 640},   // Android 4, phones, with <meta viewport>
-
-  // Tablets
-  {key: "768x1024", width: 768, height: 1024}, // iPad, with <meta viewport>
-  {key: "800x1280", width: 800, height: 1280}, // Android 4, Tablet, with <meta viewport>
-
-  // Default width for mobile browsers, no <meta viewport>
-  {key: "980x1280", width: 980, height: 1280},
-
-  // Computer
-  {key: "1280x600", width: 1280, height: 600},
-  {key: "1920x900", width: 1920, height: 900},
-];
-
-function ResponsiveUI(window, tab) {
-  this.mainWindow = window;
-  this.tab = tab;
-  this.mm = this.tab.linkedBrowser.messageManager;
-  this.tabContainer = window.gBrowser.tabContainer;
-  this.browser = tab.linkedBrowser;
-  this.chromeDoc = window.document;
-  this.container = window.gBrowser.getBrowserContainer(this.browser);
-  this.stack = this.container.querySelector(".browserStack");
-  this._telemetry = new Telemetry();
-
-  // Let's bind some callbacks.
-  this.boundPresetSelected = this.presetSelected.bind(this);
-  this.boundHandleManualInput = this.handleManualInput.bind(this);
-  this.boundAddPreset = this.addPreset.bind(this);
-  this.boundRemovePreset = this.removePreset.bind(this);
-  this.boundRotate = this.rotate.bind(this);
-  this.boundScreenshot = () => this.screenshot();
-  this.boundTouch = this.toggleTouch.bind(this);
-  this.boundClose = this.close.bind(this);
-  this.boundStartResizing = this.startResizing.bind(this);
-  this.boundStopResizing = this.stopResizing.bind(this);
-  this.boundOnDrag = this.onDrag.bind(this);
-  this.boundChangeUA = this.changeUA.bind(this);
-  this.boundOnContentResize = this.onContentResize.bind(this);
-
-  this.mm.addMessageListener("ResponsiveMode:OnContentResize",
-                             this.boundOnContentResize);
-
-  // We must be ready to handle window or tab close now that we have saved
-  // ourselves in ActiveTabs.  Otherwise we risk leaking the window.
-  this.mainWindow.addEventListener("unload", this);
-  this.tab.addEventListener("TabClose", this);
-  this.tabContainer.addEventListener("TabSelect", this);
-
-  ActiveTabs.set(this.tab, this);
-
-  this.inited = this.init();
+  let { ResponsiveUIManager } = require("devtools/client/responsivedesign/responsivedesign-old");
+  module.exports = ResponsiveUIManager;
 }
-
-ResponsiveUI.prototype = {
-  _transitionsEnabled: true,
-  get transitionsEnabled() {
-    return this._transitionsEnabled;
-  },
-  set transitionsEnabled(value) {
-    this._transitionsEnabled = value;
-    if (value && !this._resizing && this.stack.hasAttribute("responsivemode")) {
-      this.stack.removeAttribute("notransition");
-    } else if (!value) {
-      this.stack.setAttribute("notransition", "true");
-    }
-  },
-
-  init: Task.async(function* () {
-    debug("INIT BEGINS");
-    let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
-    this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
-    yield ready;
-
-    yield gDevToolsBrowser.loadBrowserStyleSheet(this.mainWindow);
-
-    let requiresFloatingScrollbars =
-      !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
-    let started = this.waitForMessage("ResponsiveMode:Start:Done");
-    debug("SEND START");
-    this.mm.sendAsyncMessage("ResponsiveMode:Start", {
-      requiresFloatingScrollbars,
-      // Tests expect events on resize to yield on various size changes
-      notifyOnResize: flags.testing,
-    });
-    yield started;
-
-    // Load Presets
-    this.loadPresets();
-
-    // Setup the UI
-    this.container.setAttribute("responsivemode", "true");
-    this.stack.setAttribute("responsivemode", "true");
-    this.buildUI();
-    this.checkMenus();
-
-    // Rotate the responsive mode if needed
-    try {
-      if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
-        this.rotate();
-      }
-    } catch (e) {
-      // There is no default value defined, so errors are expected.
-    }
-
-    // Touch events support
-    this.touchEnableBefore = false;
-    this.touchEventSimulator = new TouchEventSimulator(this.browser);
-
-    yield this.connectToServer();
-    this.userAgentInput.hidden = false;
-
-    // Hook to display promotional Developer Edition doorhanger.
-    // Only displayed once.
-    showDoorhanger({
-      window: this.mainWindow,
-      type: "deveditionpromo",
-      anchor: this.chromeDoc.querySelector("#content")
-    });
-
-    this.showNewUINotification();
-
-    // Notify that responsive mode is on.
-    this._telemetry.toolOpened("responsive");
-    ResponsiveUIManager.emit("on", { tab: this.tab });
-  }),
-
-  connectToServer: Task.async(function* () {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-    this.client = new DebuggerClient(DebuggerServer.connectPipe());
-    yield this.client.connect();
-    let { tab } = yield this.client.getTab();
-    yield this.client.attachTab(tab.actor);
-    this.emulationFront = EmulationFront(this.client, tab);
-  }),
-
-  loadPresets: function () {
-    // Try to load presets from prefs
-    let presets = defaultPresets;
-    if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
-      try {
-        presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
-      } catch (e) {
-        // User pref is malformated.
-        console.error("Could not parse pref `devtools.responsiveUI.presets`: " + e);
-      }
-    }
-
-    this.customPreset = { key: "custom", custom: true };
-
-    if (Array.isArray(presets)) {
-      this.presets = [this.customPreset].concat(presets);
-    } else {
-      console.error("Presets value (devtools.responsiveUI.presets) is malformated.");
-      this.presets = [this.customPreset];
-    }
-
-    try {
-      let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
-      let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
-      this.customPreset.width = Math.min(MAX_WIDTH, width);
-      this.customPreset.height = Math.min(MAX_HEIGHT, height);
-
-      this.currentPresetKey =
-        Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
-    } catch (e) {
-      // Default size. The first preset (custom) is the one that will be used.
-      let bbox = this.stack.getBoundingClientRect();
-
-      this.customPreset.width = bbox.width - 40; // horizontal padding of the container
-      this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
-
-      this.currentPresetKey = this.presets[1].key; // most common preset
-    }
-  },
-
-  /**
-   * Destroy the nodes. Remove listeners. Reset the style.
-   */
-  close: Task.async(function* () {
-    debug("CLOSE BEGINS");
-    if (this.closing) {
-      debug("ALREADY CLOSING, ABORT");
-      return;
-    }
-    this.closing = true;
-
-    // If we're closing very fast (in tests), ensure init has finished.
-    debug("CLOSE: WAIT ON INITED");
-    yield this.inited;
-    debug("CLOSE: INITED DONE");
-
-    this.unCheckMenus();
-    // Reset style of the stack.
-    debug(`CURRENT SIZE: ${this.stack.getAttribute("style")}`);
-    let style = "max-width: none;" +
-                "min-width: 0;" +
-                "max-height: none;" +
-                "min-height: 0;";
-    debug("RESET STACK SIZE");
-    this.stack.setAttribute("style", style);
-
-    // Wait for resize message before stopping in the child when testing,
-    // but only if we should expect to still get a message.
-    if (flags.testing && this.tab.linkedBrowser.messageManager) {
-      debug("CLOSE: WAIT ON CONTENT RESIZE");
-      yield this.waitForMessage("ResponsiveMode:OnContentResize");
-      debug("CLOSE: CONTENT RESIZE DONE");
-    }
-
-    if (this.isResizing) {
-      this.stopResizing();
-    }
-
-    // Remove listeners.
-    this.menulist.removeEventListener("select", this.boundPresetSelected, true);
-    this.menulist.removeEventListener("change", this.boundHandleManualInput, true);
-    this.mainWindow.removeEventListener("unload", this);
-    this.tab.removeEventListener("TabClose", this);
-    this.tabContainer.removeEventListener("TabSelect", this);
-    this.rotatebutton.removeEventListener("command", this.boundRotate, true);
-    this.screenshotbutton.removeEventListener("command", this.boundScreenshot, true);
-    this.closebutton.removeEventListener("command", this.boundClose, true);
-    this.addbutton.removeEventListener("command", this.boundAddPreset, true);
-    this.removebutton.removeEventListener("command", this.boundRemovePreset, true);
-    this.touchbutton.removeEventListener("command", this.boundTouch, true);
-    this.userAgentInput.removeEventListener("blur", this.boundChangeUA, true);
-
-    // Removed elements.
-    this.container.removeChild(this.toolbar);
-    if (this.bottomToolbar) {
-      this.bottomToolbar.remove();
-      delete this.bottomToolbar;
-    }
-    this.stack.removeChild(this.resizer);
-    this.stack.removeChild(this.resizeBarV);
-    this.stack.removeChild(this.resizeBarH);
-
-    this.stack.classList.remove("fxos-mode");
-
-    // Unset the responsive mode.
-    this.container.removeAttribute("responsivemode");
-    this.stack.removeAttribute("responsivemode");
-
-    ActiveTabs.delete(this.tab);
-    if (this.touchEventSimulator) {
-      this.touchEventSimulator.stop();
-    }
-
-    debug("CLOSE: WAIT ON CLIENT CLOSE");
-    yield this.client.close();
-    debug("CLOSE: CLIENT CLOSE DONE");
-    this.client = this.emulationFront = null;
-
-    this._telemetry.toolClosed("responsive");
-
-    if (this.tab.linkedBrowser && this.tab.linkedBrowser.messageManager) {
-      let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
-      this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
-      debug("CLOSE: WAIT ON STOP");
-      yield stopped;
-      debug("CLOSE: STOP DONE");
-    }
-
-    this.hideNewUINotification();
-
-    debug("CLOSE: DONE, EMIT OFF");
-    this.inited = null;
-    ResponsiveUIManager.emit("off", { tab: this.tab });
-  }),
-
-  waitForMessage(message) {
-    return new Promise(resolve => {
-      let listener = () => {
-        this.mm.removeMessageListener(message, listener);
-        resolve();
-      };
-      this.mm.addMessageListener(message, listener);
-    });
-  },
-
-  /**
-   * Emit an event when the content has been resized. Only used in tests.
-   */
-  onContentResize: function (msg) {
-    ResponsiveUIManager.emit("content-resize", {
-      tab: this.tab,
-      width: msg.data.width,
-      height: msg.data.height,
-    });
-  },
-
-  /**
-   * Handle events
-   */
-  handleEvent: function (event) {
-    switch (event.type) {
-      case "TabClose":
-      case "unload":
-        this.close();
-        break;
-      case "TabSelect":
-        if (this.tab.selected) {
-          this.checkMenus();
-        } else if (!this.mainWindow.gBrowser.selectedTab.responsiveUI) {
-          this.unCheckMenus();
-        }
-        break;
-    }
-  },
-
-  getViewportBrowser() {
-    return this.browser;
-  },
-
-  /**
-   * Check the menu items.
-   */
-  checkMenus: function () {
-    this.chromeDoc.getElementById("menu_responsiveUI").setAttribute("checked", "true");
-  },
-
-  /**
-   * Uncheck the menu items.
-   */
-  unCheckMenus: function () {
-    let el = this.chromeDoc.getElementById("menu_responsiveUI");
-    if (el) {
-      el.setAttribute("checked", "false");
-    }
-  },
-
-  /**
-   * Build the toolbar and the resizers.
-   *
-   * <vbox class="browserContainer"> From tabbrowser.xml
-   *  <toolbar class="devtools-responsiveui-toolbar">
-   *    <menulist class="devtools-responsiveui-menulist"/> // presets
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
-   *                   tooltiptext="rotate"/> // rotate
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
-   *                   tooltiptext="screenshot"/> // screenshot
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
-   *                   tooltiptext="Leave Responsive Design Mode"/> // close
-   *  </toolbar>
-   *  <stack class="browserStack"> From tabbrowser.xml
-   *    <browser/>
-   *    <box class="devtools-responsiveui-resizehandle" bottom="0" right="0"/>
-   *    <box class="devtools-responsiveui-resizebarV" top="0" right="0"/>
-   *    <box class="devtools-responsiveui-resizebarH" bottom="0" left="0"/>
-   *    // Additional button in FxOS mode:
-   *    <button class="devtools-responsiveui-sleep-button" />
-   *    <vbox class="devtools-responsiveui-volume-buttons">
-   *      <button class="devtools-responsiveui-volume-up-button" />
-   *      <button class="devtools-responsiveui-volume-down-button" />
-   *    </vbox>
-   *  </stack>
-   *  <toolbar class="devtools-responsiveui-hardware-button">
-   *    <toolbarbutton class="devtools-responsiveui-home-button" />
-   *  </toolbar>
-   * </vbox>
-   */
-  buildUI: function () {
-    // Toolbar
-    this.toolbar = this.chromeDoc.createElement("toolbar");
-    this.toolbar.className = "devtools-responsiveui-toolbar";
-    this.toolbar.setAttribute("fullscreentoolbar", "true");
-
-    this.menulist = this.chromeDoc.createElement("menulist");
-    this.menulist.className = "devtools-responsiveui-menulist";
-    this.menulist.setAttribute("editable", "true");
-
-    this.menulist.addEventListener("select", this.boundPresetSelected, true);
-    this.menulist.addEventListener("change", this.boundHandleManualInput, true);
-
-    this.menuitems = new Map();
-
-    let menupopup = this.chromeDoc.createElement("menupopup");
-    this.registerPresets(menupopup);
-    this.menulist.appendChild(menupopup);
-
-    this.addbutton = this.chromeDoc.createElement("menuitem");
-    this.addbutton.setAttribute(
-      "label",
-      this.strings.GetStringFromName("responsiveUI.addPreset")
-    );
-    this.addbutton.addEventListener("command", this.boundAddPreset, true);
-
-    this.removebutton = this.chromeDoc.createElement("menuitem");
-    this.removebutton.setAttribute(
-      "label",
-      this.strings.GetStringFromName("responsiveUI.removePreset")
-    );
-    this.removebutton.addEventListener("command", this.boundRemovePreset, true);
-
-    menupopup.appendChild(this.chromeDoc.createElement("menuseparator"));
-    menupopup.appendChild(this.addbutton);
-    menupopup.appendChild(this.removebutton);
-
-    this.rotatebutton = this.chromeDoc.createElement("toolbarbutton");
-    this.rotatebutton.setAttribute("tabindex", "0");
-    this.rotatebutton.setAttribute(
-      "tooltiptext",
-      this.strings.GetStringFromName("responsiveUI.rotate2")
-    );
-    this.rotatebutton.className =
-      "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate";
-    this.rotatebutton.addEventListener("command", this.boundRotate, true);
-
-    this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton");
-    this.screenshotbutton.setAttribute("tabindex", "0");
-    this.screenshotbutton.setAttribute(
-      "tooltiptext",
-      this.strings.GetStringFromName("responsiveUI.screenshot")
-    );
-    this.screenshotbutton.className =
-      "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot";
-    this.screenshotbutton.addEventListener("command", this.boundScreenshot, true);
-
-    this.closebutton = this.chromeDoc.createElement("toolbarbutton");
-    this.closebutton.setAttribute("tabindex", "0");
-    this.closebutton.className =
-      "devtools-responsiveui-toolbarbutton devtools-responsiveui-close";
-    this.closebutton.setAttribute(
-      "tooltiptext",
-      this.strings.GetStringFromName("responsiveUI.close1")
-    );
-    this.closebutton.addEventListener("command", this.boundClose, true);
-
-    this.toolbar.appendChild(this.closebutton);
-    this.toolbar.appendChild(this.menulist);
-    this.toolbar.appendChild(this.rotatebutton);
-
-    this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
-    this.touchbutton.setAttribute("tabindex", "0");
-    this.touchbutton.setAttribute(
-      "tooltiptext",
-      this.strings.GetStringFromName("responsiveUI.touch")
-    );
-    this.touchbutton.className =
-      "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
-    this.touchbutton.addEventListener("command", this.boundTouch, true);
-    this.toolbar.appendChild(this.touchbutton);
-
-    this.toolbar.appendChild(this.screenshotbutton);
-
-    this.userAgentInput = this.chromeDoc.createElement("textbox");
-    this.userAgentInput.className = "devtools-responsiveui-textinput";
-    this.userAgentInput.setAttribute("placeholder",
-      this.strings.GetStringFromName("responsiveUI.userAgentPlaceholder"));
-    this.userAgentInput.addEventListener("blur", this.boundChangeUA, true);
-    this.userAgentInput.hidden = true;
-    this.toolbar.appendChild(this.userAgentInput);
-
-    // Resizers
-    let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
-    this.resizer = this.chromeDoc.createElement("box");
-    this.resizer.className = "devtools-responsiveui-resizehandle";
-    this.resizer.setAttribute("right", "0");
-    this.resizer.setAttribute("bottom", "0");
-    this.resizer.setAttribute("tooltiptext", resizerTooltip);
-    this.resizer.onmousedown = this.boundStartResizing;
-
-    this.resizeBarV = this.chromeDoc.createElement("box");
-    this.resizeBarV.className = "devtools-responsiveui-resizebarV";
-    this.resizeBarV.setAttribute("top", "0");
-    this.resizeBarV.setAttribute("right", "0");
-    this.resizeBarV.setAttribute("tooltiptext", resizerTooltip);
-    this.resizeBarV.onmousedown = this.boundStartResizing;
-
-    this.resizeBarH = this.chromeDoc.createElement("box");
-    this.resizeBarH.className = "devtools-responsiveui-resizebarH";
-    this.resizeBarH.setAttribute("bottom", "0");
-    this.resizeBarH.setAttribute("left", "0");
-    this.resizeBarH.setAttribute("tooltiptext", resizerTooltip);
-    this.resizeBarH.onmousedown = this.boundStartResizing;
-
-    this.container.insertBefore(this.toolbar, this.stack);
-    this.stack.appendChild(this.resizer);
-    this.stack.appendChild(this.resizeBarV);
-    this.stack.appendChild(this.resizeBarH);
-  },
-
-  // FxOS custom controls
-  buildPhoneUI: function () {
-    this.stack.classList.add("fxos-mode");
-
-    let sleepButton = this.chromeDoc.createElement("button");
-    sleepButton.className = "devtools-responsiveui-sleep-button";
-    sleepButton.setAttribute("top", 0);
-    sleepButton.setAttribute("right", 0);
-    sleepButton.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "Power" });
-    });
-    sleepButton.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "Power" });
-    });
-    this.stack.appendChild(sleepButton);
-
-    let volumeButtons = this.chromeDoc.createElement("vbox");
-    volumeButtons.className = "devtools-responsiveui-volume-buttons";
-    volumeButtons.setAttribute("top", 0);
-    volumeButtons.setAttribute("left", 0);
-
-    let volumeUp = this.chromeDoc.createElement("button");
-    volumeUp.className = "devtools-responsiveui-volume-up-button";
-    volumeUp.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "AudioVolumeUp" });
-    });
-    volumeUp.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "AudioVolumeUp" });
-    });
-
-    let volumeDown = this.chromeDoc.createElement("button");
-    volumeDown.className = "devtools-responsiveui-volume-down-button";
-    volumeDown.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "AudioVolumeDown" });
-    });
-    volumeDown.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "AudioVolumeDown" });
-    });
-
-    volumeButtons.appendChild(volumeUp);
-    volumeButtons.appendChild(volumeDown);
-    this.stack.appendChild(volumeButtons);
-
-    let bottomToolbar = this.chromeDoc.createElement("toolbar");
-    bottomToolbar.className = "devtools-responsiveui-hardware-buttons";
-    bottomToolbar.setAttribute("align", "center");
-    bottomToolbar.setAttribute("pack", "center");
-
-    let homeButton = this.chromeDoc.createElement("toolbarbutton");
-    homeButton.className =
-      "devtools-responsiveui-toolbarbutton devtools-responsiveui-home-button";
-    homeButton.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "Home" });
-    });
-    homeButton.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "Home" });
-    });
-    bottomToolbar.appendChild(homeButton);
-    this.bottomToolbar = bottomToolbar;
-    this.container.appendChild(bottomToolbar);
-  },
-
-  showNewUINotification() {
-    let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
-
-    // One reason we might be using old RDM is that the user explcitly disabled new RDM.
-    // We should encourage them to use the new one, since the old one will be removed.
-    if (Services.prefs.prefHasUserValue(NEW_RDM_ENABLED) &&
-        !Services.prefs.getBoolPref(NEW_RDM_ENABLED)) {
-      let buttons = [{
-        label: this.strings.GetStringFromName("responsiveUI.newVersionEnableAndRestart"),
-        callback: () => {
-          Services.prefs.setBoolPref(NEW_RDM_ENABLED, true);
-          BrowserUtils.restartApplication();
-        },
-      }];
-      nbox.appendNotification(
-        this.strings.GetStringFromName("responsiveUI.newVersionUserDisabled"),
-        "responsive-ui-new-version-user-disabled",
-        null,
-        nbox.PRIORITY_INFO_LOW,
-        buttons
-      );
-      return;
-    }
-
-    // Only show a notification about the new RDM UI on channels where there is an e10s
-    // switch in the preferences UI (Dev. Ed, Nightly).  On other channels, it is less
-    // clear how a user would proceed here, so don't show a message.
-    if (!system.constants.E10S_TESTING_ONLY) {
-      return;
-    }
-
-    let buttons = [{
-      label: this.strings.GetStringFromName("responsiveUI.newVersionEnableAndRestart"),
-      callback: () => {
-        Services.prefs.setBoolPref("browser.tabs.remote.autostart", true);
-        Services.prefs.setBoolPref("browser.tabs.remote.autostart.2", true);
-        BrowserUtils.restartApplication();
-      },
-    }];
-    nbox.appendNotification(
-      this.strings.GetStringFromName("responsiveUI.newVersionE10sDisabled"),
-      "responsive-ui-new-version-e10s-disabled",
-      null,
-      nbox.PRIORITY_INFO_LOW,
-      buttons
-    );
-  },
-
-  hideNewUINotification() {
-    if (!this.mainWindow.gBrowser || !this.mainWindow.gBrowser.getNotificationBox) {
-      return;
-    }
-    let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
-    let n = nbox.getNotificationWithValue("responsive-ui-new-version-user-disabled");
-    if (n) {
-      n.close();
-    }
-    n = nbox.getNotificationWithValue("responsive-ui-new-version-e10s-disabled");
-    if (n) {
-      n.close();
-    }
-  },
-
-  /**
-   * Validate and apply any user input on the editable menulist
-   */
-  handleManualInput: function () {
-    let userInput = this.menulist.inputField.value;
-    let value = INPUT_PARSER.exec(userInput);
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-
-    // In case of an invalide value, we show back the last preset
-    if (!value || value.length < 3) {
-      this.setMenuLabel(this.selectedItem, selectedPreset);
-      return;
-    }
-
-    this.rotateValue = false;
-
-    if (!selectedPreset.custom) {
-      let menuitem = this.customMenuitem;
-      this.currentPresetKey = this.customPreset.key;
-      this.menulist.selectedItem = menuitem;
-    }
-
-    let w = this.customPreset.width = parseInt(value[1], 10);
-    let h = this.customPreset.height = parseInt(value[2], 10);
-
-    this.saveCustomSize();
-    this.setViewportSize({
-      width: w,
-      height: h,
-    });
-  },
-
-  /**
-   * Build the presets list and append it to the menupopup.
-   *
-   * @param parent menupopup.
-   */
-  registerPresets: function (parent) {
-    let fragment = this.chromeDoc.createDocumentFragment();
-    let doc = this.chromeDoc;
-
-    for (let preset of this.presets) {
-      let menuitem = doc.createElement("menuitem");
-      menuitem.setAttribute("ispreset", true);
-      this.menuitems.set(menuitem, preset);
-
-      if (preset.key === this.currentPresetKey) {
-        menuitem.setAttribute("selected", "true");
-        this.selectedItem = menuitem;
-      }
-
-      if (preset.custom) {
-        this.customMenuitem = menuitem;
-      }
-
-      this.setMenuLabel(menuitem, preset);
-      fragment.appendChild(menuitem);
-    }
-    parent.appendChild(fragment);
-  },
-
-  /**
-   * Set the menuitem label of a preset.
-   *
-   * @param menuitem menuitem to edit.
-   * @param preset associated preset.
-   */
-  setMenuLabel: function (menuitem, preset) {
-    let size = SHARED_L10N.getFormatStr("dimensions",
-      Math.round(preset.width), Math.round(preset.height));
-
-    // .inputField might be not reachable yet (async XBL loading)
-    if (this.menulist.inputField) {
-      this.menulist.inputField.value = size;
-    }
-
-    if (preset.custom) {
-      size = this.strings.formatStringFromName("responsiveUI.customResolution",
-                                               [size], 1);
-    } else if (preset.name != null && preset.name !== "") {
-      size = this.strings.formatStringFromName("responsiveUI.namedResolution",
-                                               [size, preset.name], 2);
-    }
-
-    menuitem.setAttribute("label", size);
-  },
-
-  /**
-   * When a preset is selected, apply it.
-   */
-  presetSelected: function () {
-    if (this.menulist.selectedItem.getAttribute("ispreset") === "true") {
-      this.selectedItem = this.menulist.selectedItem;
-
-      this.rotateValue = false;
-      let selectedPreset = this.menuitems.get(this.selectedItem);
-      this.loadPreset(selectedPreset);
-      this.currentPresetKey = selectedPreset.key;
-      this.saveCurrentPreset();
-
-      // Update the buttons hidden status according to the new selected preset
-      if (selectedPreset == this.customPreset) {
-        this.addbutton.hidden = false;
-        this.removebutton.hidden = true;
-      } else {
-        this.addbutton.hidden = true;
-        this.removebutton.hidden = false;
-      }
-    }
-  },
-
-  /**
-   * Apply a preset.
-   */
-  loadPreset(preset) {
-    this.setViewportSize(preset);
-  },
-
-  /**
-   * Add a preset to the list and the memory
-   */
-  addPreset: function () {
-    let w = this.customPreset.width;
-    let h = this.customPreset.height;
-    let newName = {};
-
-    let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle1");
-    let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg",
-                                                    [w, h], 2);
-    let promptOk = Services.prompt.prompt(null, title, message, newName, null, {});
-
-    if (!promptOk) {
-      // Prompt has been cancelled
-      this.menulist.selectedItem = this.selectedItem;
-      return;
-    }
-
-    let newPreset = {
-      key: w + "x" + h,
-      name: newName.value,
-      width: w,
-      height: h
-    };
-
-    this.presets.push(newPreset);
-
-    // Sort the presets according to width/height ascending order
-    this.presets.sort((presetA, presetB) => {
-      // We keep custom preset at first
-      if (presetA.custom && !presetB.custom) {
-        return 1;
-      }
-      if (!presetA.custom && presetB.custom) {
-        return -1;
-      }
-
-      if (presetA.width === presetB.width) {
-        if (presetA.height === presetB.height) {
-          return 0;
-        }
-        return presetA.height > presetB.height;
-      }
-      return presetA.width > presetB.width;
-    });
-
-    this.savePresets();
-
-    let newMenuitem = this.chromeDoc.createElement("menuitem");
-    newMenuitem.setAttribute("ispreset", true);
-    this.setMenuLabel(newMenuitem, newPreset);
-
-    this.menuitems.set(newMenuitem, newPreset);
-    let idx = this.presets.indexOf(newPreset);
-    let beforeMenuitem = this.menulist.firstChild.childNodes[idx + 1];
-    this.menulist.firstChild.insertBefore(newMenuitem, beforeMenuitem);
-
-    this.menulist.selectedItem = newMenuitem;
-    this.currentPresetKey = newPreset.key;
-    this.saveCurrentPreset();
-  },
-
-  /**
-   * remove a preset from the list and the memory
-   */
-  removePreset: function () {
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-    let w = selectedPreset.width;
-    let h = selectedPreset.height;
-
-    this.presets.splice(this.presets.indexOf(selectedPreset), 1);
-    this.menulist.firstChild.removeChild(this.selectedItem);
-    this.menuitems.delete(this.selectedItem);
-
-    this.customPreset.width = w;
-    this.customPreset.height = h;
-    let menuitem = this.customMenuitem;
-    this.setMenuLabel(menuitem, this.customPreset);
-    this.menulist.selectedItem = menuitem;
-    this.currentPresetKey = this.customPreset.key;
-
-    this.setViewportSize({
-      width: w,
-      height: h,
-    });
-
-    this.savePresets();
-  },
-
-  /**
-   * Swap width and height.
-   */
-  rotate: function () {
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-    let width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
-    let height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
-
-    this.setViewportSize({
-      width: height,
-      height: width,
-    });
-
-    if (selectedPreset.custom) {
-      this.saveCustomSize();
-    } else {
-      this.rotateValue = !this.rotateValue;
-      this.saveCurrentPreset();
-    }
-  },
-
-  /**
-   * Take a screenshot of the page.
-   *
-   * @param filename name of the screenshot file (used for tests).
-   */
-  screenshot: function (filename) {
-    if (!filename) {
-      let date = new Date();
-      let month = ("0" + (date.getMonth() + 1)).substr(-2, 2);
-      let day = ("0" + date.getDate()).substr(-2, 2);
-      let dateString = [date.getFullYear(), month, day].join("-");
-      let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
-      filename =
-        this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename",
-                                          [dateString, timeString], 2);
-    }
-    let mm = this.tab.linkedBrowser.messageManager;
-    let chromeWindow = this.chromeDoc.defaultView;
-    let doc = chromeWindow.document;
-    function onScreenshot(message) {
-      mm.removeMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
-      chromeWindow.saveURL(message.data, filename + ".png", null, true, true,
-                           doc.documentURIObject, doc);
-    }
-    mm.addMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
-    mm.sendAsyncMessage("ResponsiveMode:RequestScreenshot");
-  },
-
-  /**
-   * Enable/Disable mouse -> touch events translation.
-   */
-  enableTouch: function () {
-    this.touchbutton.setAttribute("checked", "true");
-    return this.touchEventSimulator.start();
-  },
-
-  disableTouch: function () {
-    this.touchbutton.removeAttribute("checked");
-    return this.touchEventSimulator.stop();
-  },
-
-  hideTouchNotification: function () {
-    let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
-    let n = nbox.getNotificationWithValue("responsive-ui-need-reload");
-    if (n) {
-      n.close();
-    }
-  },
-
-  toggleTouch: Task.async(function* () {
-    this.hideTouchNotification();
-    if (this.touchEventSimulator.enabled) {
-      this.disableTouch();
-      return;
-    }
-
-    let isReloadNeeded = yield this.enableTouch();
-    if (!isReloadNeeded) {
-      return;
-    }
-
-    const PREF = "devtools.responsiveUI.no-reload-notification";
-    if (Services.prefs.getBoolPref(PREF)) {
-      return;
-    }
-
-    let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
-
-    let buttons = [{
-      label: this.strings.GetStringFromName("responsiveUI.notificationReload"),
-      callback: () => this.browser.reload(),
-      accessKey:
-        this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"),
-    }, {
-      label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"),
-      callback: () => Services.prefs.setBoolPref(PREF, true),
-      accessKey:
-        this.strings
-            .GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"),
-    }];
-
-    nbox.appendNotification(
-      this.strings.GetStringFromName("responsiveUI.needReload"),
-      "responsive-ui-need-reload",
-      null,
-      nbox.PRIORITY_INFO_LOW,
-      buttons
-    );
-  }),
-
-  waitForReload() {
-    return new Promise(resolve => {
-      let onNavigated = (_, { state }) => {
-        if (state != "stop") {
-          return;
-        }
-        this.client.removeListener("tabNavigated", onNavigated);
-        resolve();
-      };
-      this.client.addListener("tabNavigated", onNavigated);
-    });
-  },
-
-  /**
-   * Change the user agent string
-   */
-  changeUA: Task.async(function* () {
-    let value = this.userAgentInput.value;
-    let changed;
-    if (value) {
-      changed = yield this.emulationFront.setUserAgentOverride(value);
-      this.userAgentInput.setAttribute("attention", "true");
-    } else {
-      changed = yield this.emulationFront.clearUserAgentOverride();
-      this.userAgentInput.removeAttribute("attention");
-    }
-    if (changed) {
-      let reloaded = this.waitForReload();
-      this.tab.linkedBrowser.reload();
-      yield reloaded;
-    }
-    ResponsiveUIManager.emit("userAgentChanged", { tab: this.tab });
-  }),
-
-  /**
-   * Get the current width and height.
-   */
-  getSize() {
-    let width = Number(this.stack.style.minWidth.replace("px", ""));
-    let height = Number(this.stack.style.minHeight.replace("px", ""));
-    return {
-      width,
-      height,
-    };
-  },
-
-  /**
-   * Change the size of the viewport.
-   */
-  setViewportSize({ width, height }) {
-    debug(`SET SIZE TO ${width} x ${height}`);
-    if (this.closing) {
-      debug(`ABORT SET SIZE, CLOSING`);
-      return;
-    }
-    if (width) {
-      this.setWidth(width);
-    }
-    if (height) {
-      this.setHeight(height);
-    }
-  },
-
-  setWidth: function (width) {
-    width = Math.min(Math.max(width, MIN_WIDTH), MAX_WIDTH);
-    this.stack.style.maxWidth = this.stack.style.minWidth = width + "px";
-
-    if (!this.ignoreX) {
-      this.resizeBarH.setAttribute("left", Math.round(width / 2));
-    }
-
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-
-    if (selectedPreset.custom) {
-      selectedPreset.width = width;
-      this.setMenuLabel(this.selectedItem, selectedPreset);
-    }
-  },
-
-  setHeight: function (height) {
-    height = Math.min(Math.max(height, MIN_HEIGHT), MAX_HEIGHT);
-    this.stack.style.maxHeight = this.stack.style.minHeight = height + "px";
-
-    if (!this.ignoreY) {
-      this.resizeBarV.setAttribute("top", Math.round(height / 2));
-    }
-
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-    if (selectedPreset.custom) {
-      selectedPreset.height = height;
-      this.setMenuLabel(this.selectedItem, selectedPreset);
-    }
-  },
-  /**
-   * Start the process of resizing the browser.
-   *
-   * @param event
-   */
-  startResizing: function (event) {
-    let selectedPreset = this.menuitems.get(this.selectedItem);
-
-    if (!selectedPreset.custom) {
-      if (this.rotateValue) {
-        this.customPreset.width = selectedPreset.height;
-        this.customPreset.height = selectedPreset.width;
-      } else {
-        this.customPreset.width = selectedPreset.width;
-        this.customPreset.height = selectedPreset.height;
-      }
-
-      let menuitem = this.customMenuitem;
-      this.setMenuLabel(menuitem, this.customPreset);
-
-      this.currentPresetKey = this.customPreset.key;
-      this.menulist.selectedItem = menuitem;
-    }
-    this.mainWindow.addEventListener("mouseup", this.boundStopResizing, true);
-    this.mainWindow.addEventListener("mousemove", this.boundOnDrag, true);
-    this.container.style.pointerEvents = "none";
-
-    this._resizing = true;
-    this.stack.setAttribute("notransition", "true");
-
-    this.lastScreenX = event.screenX;
-    this.lastScreenY = event.screenY;
-
-    this.ignoreY = (event.target === this.resizeBarV);
-    this.ignoreX = (event.target === this.resizeBarH);
-
-    this.isResizing = true;
-  },
-
-  /**
-   * Resizing on mouse move.
-   *
-   * @param event
-   */
-  onDrag: function (event) {
-    let shift = event.shiftKey;
-    let ctrl = !event.shiftKey && event.ctrlKey;
-
-    let screenX = event.screenX;
-    let screenY = event.screenY;
-
-    let deltaX = screenX - this.lastScreenX;
-    let deltaY = screenY - this.lastScreenY;
-
-    if (this.ignoreY) {
-      deltaY = 0;
-    }
-    if (this.ignoreX) {
-      deltaX = 0;
-    }
-
-    if (ctrl) {
-      deltaX /= SLOW_RATIO;
-      deltaY /= SLOW_RATIO;
-    }
-
-    let width = this.customPreset.width + deltaX;
-    let height = this.customPreset.height + deltaY;
-
-    if (shift) {
-      let roundedWidth, roundedHeight;
-      roundedWidth = 10 * Math.floor(width / ROUND_RATIO);
-      roundedHeight = 10 * Math.floor(height / ROUND_RATIO);
-      screenX += roundedWidth - width;
-      screenY += roundedHeight - height;
-      width = roundedWidth;
-      height = roundedHeight;
-    }
-
-    if (width < MIN_WIDTH) {
-      width = MIN_WIDTH;
-    } else {
-      this.lastScreenX = screenX;
-    }
-
-    if (height < MIN_HEIGHT) {
-      height = MIN_HEIGHT;
-    } else {
-      this.lastScreenY = screenY;
-    }
-
-    this.setViewportSize({ width, height });
-  },
-
-  /**
-   * Stop End resizing
-   */
-  stopResizing: function () {
-    this.container.style.pointerEvents = "auto";
-
-    this.mainWindow.removeEventListener("mouseup", this.boundStopResizing, true);
-    this.mainWindow.removeEventListener("mousemove", this.boundOnDrag, true);
-
-    this.saveCustomSize();
-
-    delete this._resizing;
-    if (this.transitionsEnabled) {
-      this.stack.removeAttribute("notransition");
-    }
-    this.ignoreY = false;
-    this.ignoreX = false;
-    this.isResizing = false;
-  },
-
-  /**
-   * Store the custom size as a pref.
-   */
-  saveCustomSize: function () {
-    Services.prefs.setIntPref("devtools.responsiveUI.customWidth",
-                              this.customPreset.width);
-    Services.prefs.setIntPref("devtools.responsiveUI.customHeight",
-                              this.customPreset.height);
-  },
-
-  /**
-   * Store the current preset as a pref.
-   */
-  saveCurrentPreset: function () {
-    Services.prefs.setCharPref("devtools.responsiveUI.currentPreset",
-                               this.currentPresetKey);
-    Services.prefs.setBoolPref("devtools.responsiveUI.rotate",
-                               this.rotateValue);
-  },
-
-  /**
-   * Store the list of all registered presets as a pref.
-   */
-  savePresets: function () {
-    // We exclude the custom one
-    let registeredPresets = this.presets.filter(function (preset) {
-      return !preset.custom;
-    });
-    Services.prefs.setCharPref("devtools.responsiveUI.presets",
-                               JSON.stringify(registeredPresets));
-  },
-};
-
-loader.lazyGetter(ResponsiveUI.prototype, "strings", function () {
-  return Services.strings.createBundle("chrome://devtools/locale/responsiveUI.properties");
-});
--- a/devtools/client/responsivedesign/test/head.js
+++ b/devtools/client/responsivedesign/test/head.js
@@ -22,17 +22,17 @@ registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.responsiveUI.customHeight");
   Services.prefs.clearUserPref("devtools.responsiveUI.customWidth");
   Services.prefs.clearUserPref("devtools.responsiveUI.presets");
   Services.prefs.clearUserPref("devtools.responsiveUI.rotate");
 });
 
 SimpleTest.requestCompleteLog();
 
-const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 
 /**
  * Open the Responsive Design Mode
  * @param {Tab} The browser tab to open it into (defaults to the selected tab).
  * @param {method} The method to use to open the RDM (values: menu, keyboard)
  * @return {rdm, manager} Returns the RUI instance and the manager
  */
 var openRDM = Task.async(function* (tab = gBrowser.selectedTab,
--- a/devtools/client/shared/test/browser_telemetry_button_responsive.js
+++ b/devtools/client/shared/test/browser_telemetry_button_responsive.js
@@ -5,17 +5,17 @@
 
 const TEST_URI = "data:text/html;charset=utf-8," +
   "<p>browser_telemetry_button_responsive.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
-const { ResponsiveUIManager } = Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
 
 add_task(function* () {
   yield addTab(TEST_URI);
   let Telemetry = loadTelemetryAndRecordLogs();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = yield gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -5,17 +5,17 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["StyleEditorUI"];
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {loader, require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Services = require("Services");
 const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
 const {OS} = require("resource://gre/modules/osfile.jsm");
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {
   getString,
@@ -24,20 +24,20 @@ const {
   showFilePicker,
 } = require("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
 const {SplitView} = require("resource://devtools/client/shared/SplitView.jsm");
 const {StyleSheetEditor} = require("resource://devtools/client/styleeditor/StyleSheetEditor.jsm");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {PrefObserver} = require("devtools/client/shared/prefs");
 const csscoverage = require("devtools/shared/fronts/csscoverage");
 const {console} = require("resource://gre/modules/Console.jsm");
-const {ResponsiveUIManager} =
-  require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
+
 const LOAD_ERROR = "error-load";
 const STYLE_EDITOR_TEMPLATE = "stylesheet";
 const SELECTOR_HIGHLIGHTER_TYPE = "SelectorHighlighter";
 const PREF_MEDIA_SIDEBAR = "devtools.styleeditor.showMediaSidebar";
 const PREF_SIDEBAR_WIDTH = "devtools.styleeditor.mediaSidebarWidth";
 const PREF_NAV_WIDTH = "devtools.styleeditor.navSidebarWidth";
 const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
 
--- a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js
@@ -11,18 +11,18 @@ const asyncStorage = require("devtools/s
 Services.prefs.setCharPref("devtools.devices.url",
   "http://example.com/browser/devtools/client/responsive.html/test/browser/devices.json");
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.devices.url");
   asyncStorage.removeItem("devtools.devices.url_cache");
 });
 
-const mgr = "resource://devtools/client/responsivedesign/responsivedesign.jsm";
-const {ResponsiveUIManager} = Cu.import(mgr, {});
+loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsivedesign/responsivedesign");
+
 const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
 const responsiveModeToggleClass = ".media-responsive-mode-toggle";
 
 add_task(function* () {
   let {ui} = yield openStyleEditorForURL(TESTCASE_URI);
 
   let editor = ui.editors[1];
   yield openEditor(editor);
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #include <d3d11.h>
 #include "DXVA2Manager.h"
 #include "D3D9SurfaceImage.h"
 #include "DriverCrashGuard.h"
+#include "GfxDriverInfo.h"
 #include "ImageContainer.h"
 #include "MFTDecoder.h"
 #include "MediaTelemetryConstants.h"
 #include "gfxCrashReporterUtils.h"
 #include "gfxPrefs.h"
 #include "gfxWindowsPlatform.h"
 #include "mfapi.h"
 #include "mozilla/Telemetry.h"
@@ -69,16 +70,49 @@ static const DWORD sAMDPreUVD4[] = {
   0x6850, 0x6858, 0x6859, 0x6760, 0x6761, 0x6762, 0x6763, 0x6764, 0x6765, 0x6766, 0x6767, 0x6768, 0x6770,
   0x6771, 0x6772, 0x6778, 0x6779, 0x677b, 0x6700, 0x6701, 0x6702, 0x6703, 0x6704, 0x6705, 0x6706, 0x6707,
   0x6708, 0x6709, 0x6718, 0x6719, 0x671c, 0x671d, 0x671f, 0x9900, 0x9901, 0x9903, 0x9904, 0x9905, 0x9906,
   0x9907, 0x9908, 0x9909, 0x990a, 0x990b, 0x990c, 0x990d, 0x990e, 0x990f, 0x9910, 0x9913, 0x9917, 0x9918,
   0x9919, 0x9990, 0x9991, 0x9992, 0x9993, 0x9994, 0x9995, 0x9996, 0x9997, 0x9998, 0x9999, 0x999a, 0x999b,
   0x999c, 0x999d, 0x99a0, 0x99a2, 0x99a4
 };
 
+// List of NVidia Telsa GPU known to have broken NV12 rendering.
+static const DWORD sNVIDIABrokenNV12[] = {
+  0x0191, 0x0193, 0x0194, 0x0197, 0x019d, 0x019e, // G80
+  0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, // G84
+  0x040a, 0x040b, 0x040c, 0x040d, 0x040e, 0x040f,
+  0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, // G86
+  0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
+  0x0410, 0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607, 0x0608, // G92
+  0x0609, 0x060a, 0x060b, 0x060c, 0x060f, 0x0610, 0x0611, 0x0612, 0x0613, 0x0614,
+  0x0615, 0x0617, 0x0618, 0x0619, 0x061a, 0x061b, 0x061c, 0x061d, 0x061e, 0x061f, // G94
+  0x0621, 0x0622, 0x0623, 0x0625, 0x0626, 0x0627, 0x0628, 0x062a, 0x062b, 0x062c,
+  0x062d, 0x062e, 0x0631, 0x0635, 0x0637, 0x0638, 0x063a,
+  0x0640, 0x0641, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, // G96
+  0x064b, 0x064c, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0658, 0x0659,
+  0x065a, 0x065b, 0x065c, 0x065f,
+  0x06e0, 0x06e1, 0x06e2, 0x06e3, 0x06e4, 0x06e6, 0x06e7, 0x06e8, 0x06e9, 0x06ea, // G98
+  0x06eb, 0x06ec, 0x06ef, 0x06f1, 0x06f8, 0x06f9, 0x06fa, 0x06fb, 0x06fd, 0x06ff,
+  0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e6, 0x05e7, 0x05e9, 0x05ea, 0x05eb, 0x05ed, // G200
+  0x05ee, 0x05ef,
+  0x0840, 0x0844, 0x0845, 0x0846, 0x0847, 0x0848, 0x0849, 0x084a, 0x084b, 0x084c, // MCP77
+  0x084d, 0x084f,
+  0x0860, 0x0861, 0x0862, 0x0863, 0x0864, 0x0865, 0x0866, 0x0867, 0x0868, 0x0869, // MCP79
+  0x086a, 0x086c, 0x086d, 0x086e, 0x086f, 0x0870, 0x0871, 0x0872, 0x0873, 0x0874,
+  0x0876, 0x087a, 0x087d, 0x087e, 0x087f,
+  0x0ca0, 0x0ca2, 0x0ca3, 0x0ca2, 0x0ca4, 0x0ca5, 0x0ca7, 0x0ca9, 0x0cac, 0x0caf, // GT215
+  0x0cb0, 0x0cb1, 0x0cbc,
+  0x0a20, 0x0a22, 0x0a23, 0x0a26, 0x0a27, 0x0a28, 0x0a29, 0x0a2a, 0x0a2b, 0x0a2c, // GT216
+  0x0a2d, 0x0a32, 0x0a34, 0x0a35, 0x0a38, 0x0a3c,
+  0x0a60, 0x0a62, 0x0a63, 0x0a64, 0x0a65, 0x0a66, 0x0a67, 0x0a68, 0x0a69, 0x0a6a, // GT218
+  0x0a6c, 0x0a6e, 0x0a6f, 0x0a70, 0x0a71, 0x0a72, 0x0a73, 0x0a74, 0x0a75, 0x0a76,
+  0x0a78, 0x0a7a, 0x0a7c, 0x10c0, 0x10c3, 0x10c5, 0x10d8
+};
+
 // The size we use for our synchronization surface.
 // 16x16 is the size recommended by Microsoft (in the D3D9ExDXGISharedSurf sample) that works
 // best to avoid driver bugs.
 static const uint32_t kSyncSurfaceSize = 16;
 
 namespace mozilla {
 
 using layers::Image;
@@ -390,17 +424,18 @@ D3D9DXVA2Manager::Init(layers::KnowsComp
   D3DADAPTER_IDENTIFIER9 adapter;
   hr = d3d9Ex->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapter);
   if (!SUCCEEDED(hr)) {
     aFailureReason = nsPrintfCString(
       "IDirect3D9Ex::GetAdapterIdentifier failed with error %X", hr);
     return hr;
   }
 
-  if (adapter.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) {
+  if ((adapter.VendorId == 0x1022  || adapter.VendorId == 0x1002) &&
+      !gfxPrefs::PDMWMFSkipBlacklist()) {
     for (const auto& model : sAMDPreUVD4) {
       if (adapter.DeviceId == model) {
         mIsAMDPreUVD4 = true;
         break;
       }
     }
   }
 
@@ -869,17 +904,18 @@ D3D11DXVA2Manager::InitInternal(layers::
   DXGI_ADAPTER_DESC adapterDesc;
   hr = adapter->GetDesc(&adapterDesc);
   if (!SUCCEEDED(hr)) {
     aFailureReason =
       nsPrintfCString("IDXGIAdapter::GetDesc failed with code %X", hr);
     return hr;
   }
 
-  if (adapterDesc.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) {
+  if ((adapterDesc.VendorId == 0x1022 || adapterDesc.VendorId == 0x1002) &&
+      !gfxPrefs::PDMWMFSkipBlacklist()) {
     for (const auto& model : sAMDPreUVD4) {
       if (adapterDesc.DeviceId == model) {
         mIsAMDPreUVD4 = true;
         break;
       }
     }
   }
 
@@ -1263,9 +1299,40 @@ DXVA2Manager::IsUnsupportedResolution(co
   // AMD cards with UVD3 or earlier perform poorly trying to decode 1080p60 in
   // hardware, so use software instead. Pick 45 as an arbitrary upper bound for
   // the framerate we can handle.
   return mIsAMDPreUVD4 &&
          (aWidth >= 1920 || aHeight >= 1088) &&
          aFramerate > 45;
 }
 
+/* static */ bool
+DXVA2Manager::IsNV12Supported(uint32_t aVendorID,
+                              uint32_t aDeviceID,
+                              const nsAString& aDriverVersionString)
+{
+  if (aVendorID == 0x1022 || aVendorID == 0x1002) {
+    // AMD
+    // Block old cards regardless of driver version.
+    for (const auto& model : sAMDPreUVD4) {
+      if (aDeviceID == model) {
+        return false;
+      }
+    }
+    // AMD driver earlier than 21.19.411.0 have bugs in their handling of NV12
+    // surfaces.
+    uint64_t driverVersion;
+    if (widget::ParseDriverVersion(aDriverVersionString, &driverVersion) &&
+        driverVersion < widget::V(21, 19, 411, 0)) {
+      return false;
+    }
+  } else if (aVendorID == 0x10DE) {
+    // NVidia
+    for (const auto& model : sNVIDIABrokenNV12) {
+      if (aDeviceID == model) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 } // namespace mozilla
--- a/dom/media/platforms/wmf/DXVA2Manager.h
+++ b/dom/media/platforms/wmf/DXVA2Manager.h
@@ -66,16 +66,20 @@ public:
   virtual bool SupportsConfig(IMFMediaType* aType, float aFramerate) = 0;
 
   // When we want to decode with DXVA2 directly instead of using it by MFT, we
   // need to take responsibility for creating a decoder and handle the related
   // decoding operations by ourself.
   virtual bool CreateDXVA2Decoder(const VideoInfo& aVideoInfo,
                                   nsACString& aFailureReason) = 0;
 
+  static bool IsNV12Supported(uint32_t aVendorID,
+                              uint32_t aDeviceID,
+                              const nsAString& aDriverVersionString);
+
 protected:
   Mutex mLock;
   DXVA2Manager();
 
   bool IsUnsupportedResolution(const uint32_t& aWidth,
                                const uint32_t& aHeight,
                                const float& aFramerate) const;
 
--- a/gfx/ipc/GraphicsMessages.ipdlh
+++ b/gfx/ipc/GraphicsMessages.ipdlh
@@ -17,16 +17,17 @@ namespace gfx {
 
 struct D3D11DeviceStatus
 {
   bool isWARP;
   bool textureSharingWorks;
   uint32_t featureLevel;
   DxgiAdapterDesc adapter;
   int32_t sequenceNumber;
+  bool useNV12;
 };
 
 struct DevicePrefs
 {
   FeatureStatus hwCompositing;
   FeatureStatus d3d11Compositing;
   FeatureStatus oglCompositing;
   FeatureStatus advancedLayers;
--- a/gfx/layers/D3D11ShareHandleImage.cpp
+++ b/gfx/layers/D3D11ShareHandleImage.cpp
@@ -29,17 +29,18 @@ D3D11ShareHandleImage::D3D11ShareHandleI
    mPictureRect(aRect)
 {
 }
 
 bool
 D3D11ShareHandleImage::AllocateTexture(D3D11RecycleAllocator* aAllocator, ID3D11Device* aDevice)
 {
   if (aAllocator) {
-    if (gfxPrefs::PDMWMFUseNV12Format()) {
+    if (gfxPrefs::PDMWMFUseNV12Format() &&
+        gfx::DeviceManagerDx::Get()->CanUseNV12()) {
       mTextureClient = aAllocator->CreateOrRecycleClient(gfx::SurfaceFormat::NV12, mSize);
     } else {
       mTextureClient = aAllocator->CreateOrRecycleClient(gfx::SurfaceFormat::B8G8R8A8, mSize);
     }
     if (mTextureClient) {
       mTexture = static_cast<D3D11TextureData*>(mTextureClient->GetInternalData())->GetD3D11Texture();
       return true;
     }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -216,16 +216,17 @@ APZCTreeManager::CalculatePendingDisplay
     aFrameMetrics, aVelocity);
 }
 
 APZCTreeManager::APZCTreeManager()
     : mInputQueue(new InputQueue()),
       mTreeLock("APZCTreeLock"),
       mHitResultForInputBlock(HitNothing),
       mRetainedTouchIdentifier(-1),
+      mInScrollbarTouchDrag(false),
       mApzcTreeLog("apzctree")
 {
   RefPtr<APZCTreeManager> self(this);
   NS_DispatchToMainThread(
     NS_NewRunnableFunction("layers::APZCTreeManager::APZCTreeManager", [self] {
       self->mFlushObserver = new CheckerboardFlushObserver(self);
     }));
   AsyncPanZoomController::InitializeGlobalState();
@@ -1039,71 +1040,18 @@ APZCTreeManager::ReceiveInputEvent(Input
         }
         result = mInputQueue->ReceiveInputEvent(
           apzc, targetConfirmed,
           mouseInput, aOutInputBlockId);
 
         // If we're starting an async scrollbar drag
         if (apzDragEnabled && startsDrag && hitScrollbarNode &&
             hitScrollbarNode->IsScrollThumbNode() &&
-            hitScrollbarNode->GetScrollThumbData().mIsAsyncDraggable &&
-            mInputQueue->GetCurrentDragBlock()) {
-          DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock();
-          const ScrollThumbData& thumbData = hitScrollbarNode->GetScrollThumbData();
-
-          // Record the thumb's position at the start of the drag.
-          // We snap back to this position if, during the drag, the mouse
-          // gets sufficiently far away from the scrollbar.
-          dragBlock->SetInitialThumbPos(thumbData.mThumbStart);
-
-          // Under some conditions, we can confirm the drag block right away.
-          // Otherwise, we have to wait for a main-thread confirmation.
-          if (gfxPrefs::APZDragInitiationEnabled() &&
-              // check that the scrollbar's target scroll frame is layerized
-              hitScrollbarNode->GetScrollTargetId() == apzc->GetGuid().mScrollId &&
-              !apzc->IsScrollInfoLayer()) {
-            uint64_t dragBlockId = dragBlock->GetBlockId();
-            // AsyncPanZoomController::HandleInputEvent() will call
-            // TransformToLocal() on the event, but we need its mLocalOrigin now
-            // to compute a drag start offset for the AsyncDragMetrics.
-            mouseInput.TransformToLocal(apzc->GetTransformToThis());
-            CSSCoord dragStart = apzc->ConvertScrollbarPoint(
-                mouseInput.mLocalOrigin, thumbData);
-            // ConvertScrollbarPoint() got the drag start offset relative to
-            // the scroll track. Now get it relative to the thumb.
-            // ScrollThumbData::mThumbStart stores the offset of the thumb
-            // relative to the scroll track at the time of the last paint.
-            // Since that paint, the thumb may have acquired an async transform
-            // due to async scrolling, so look that up and apply it.
-            LayerToParentLayerMatrix4x4 thumbTransform;
-            {
-              MutexAutoLock lock(mTreeLock);
-              thumbTransform = ComputeTransformForNode(hitScrollbarNode);
-            }
-            // Only consider the translation, since we do not support both
-            // zooming and scrollbar dragging on any platform.
-            CSSCoord thumbStart = thumbData.mThumbStart
-                                + ((thumbData.mDirection == ScrollDirection::HORIZONTAL)
-                                   ? thumbTransform._41 : thumbTransform._42);
-            dragStart -= thumbStart;
-
-            // Content can't prevent scrollbar dragging with preventDefault(),
-            // so we don't need to wait for a content response. It's important
-            // to do this before calling ConfirmDragBlock() since that can
-            // potentially process and consume the block.
-            dragBlock->SetContentResponse(false);
-
-            mInputQueue->ConfirmDragBlock(
-                dragBlockId, apzc,
-                AsyncDragMetrics(apzc->GetGuid().mScrollId,
-                                 apzc->GetGuid().mPresShellId,
-                                 dragBlockId,
-                                 dragStart,
-                                 thumbData.mDirection));
-          }
+            hitScrollbarNode->GetScrollThumbData().mIsAsyncDraggable) {
+          SetupScrollbarDrag(mouseInput, hitScrollbarNode.get(), apzc.get());
         }
 
         if (result == nsEventStatus_eConsumeDoDefault) {
           // This input event is part of a drag block, so whether or not it is
           // directed at a scrollbar depends on whether the drag block started
           // on a scrollbar.
           hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar);
         }
@@ -1396,37 +1344,42 @@ ConvertToTouchBehavior(HitTestResult res
   }
   MOZ_ASSERT_UNREACHABLE("Invalid value");
   return AllowedTouchBehavior::UNKNOWN;
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                         nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors,
-                                        HitTestResult* aOutHitResult)
+                                        HitTestResult* aOutHitResult,
+                                        RefPtr<HitTestingTreeNode>* aOutHitScrollbarNode)
 {
   RefPtr<AsyncPanZoomController> apzc;
   if (aEvent.mTouches.Length() == 0) {
     return apzc.forget();
   }
 
   FlushRepaintsToClearScreenToGeckoTransform();
 
   HitTestResult hitResult;
-  apzc = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint, &hitResult);
+  apzc = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint, &hitResult,
+      aOutHitScrollbarNode);
   if (aOutTouchBehaviors) {
     aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hitResult));
   }
   for (size_t i = 1; i < aEvent.mTouches.Length(); i++) {
     RefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint, &hitResult);
     if (aOutTouchBehaviors) {
       aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hitResult));
     }
     apzc = GetMultitouchTarget(apzc, apzc2);
     APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
+    // A multi-touch gesture will not be a scrollbar drag, even if the
+    // first touch point happened to hit a scrollbar.
+    *aOutHitScrollbarNode = nullptr;
   }
 
   if (aOutHitResult) {
     // XXX we should probably be combining the hit results from the different
     // touch points somehow, instead of just using the last one.
     *aOutHitResult = hitResult;
   }
   return apzc.forget();
@@ -1434,16 +1387,17 @@ APZCTreeManager::GetTouchInputBlockAPZC(
 
 nsEventStatus
 APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
   aInput.mHandledByAPZ = true;
   nsTArray<TouchBehaviorFlags> touchBehaviors;
+  RefPtr<HitTestingTreeNode> hitScrollbarNode = nullptr;
   if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
     // If we are panned into overscroll and a second finger goes down,
     // ignore that second touch point completely. The touch-start for it is
     // dropped completely; subsequent touch events until the touch-end for it
     // will have this touch point filtered out.
     // (By contrast, if we're in overscroll but not panning, such as after
     // putting two fingers down during an overscroll animation, we process the
     // second touch and proceed to pinch.)
@@ -1452,104 +1406,254 @@ APZCTreeManager::ProcessTouchInput(Multi
         BuildOverscrollHandoffChain(mApzcForInputBlock)->HasOverscrolledApzc()) {
       if (mRetainedTouchIdentifier == -1) {
         mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier();
       }
       return nsEventStatus_eConsumeNoDefault;
     }
 
     mHitResultForInputBlock = HitNothing;
-    mApzcForInputBlock = GetTouchInputBlockAPZC(aInput, &touchBehaviors, &mHitResultForInputBlock);
+    mApzcForInputBlock = GetTouchInputBlockAPZC(aInput, &touchBehaviors,
+        &mHitResultForInputBlock, &hitScrollbarNode);
+
+    // Check if this event starts a scrollbar touch-drag. The conditions
+    // checked are similar to the ones we check for MOUSE_INPUT starting
+    // a scrollbar mouse-drag.
+    mInScrollbarTouchDrag = gfxPrefs::APZDragEnabled() &&
+                            gfxPrefs::APZTouchDragEnabled() && hitScrollbarNode &&
+                            hitScrollbarNode->IsScrollThumbNode() &&
+                            hitScrollbarNode->GetScrollThumbData().mIsAsyncDraggable;
+
     MOZ_ASSERT(touchBehaviors.Length() == aInput.mTouches.Length());
     for (size_t i = 0; i < touchBehaviors.Length(); i++) {
       APZCTM_LOG("Touch point has allowed behaviours 0x%02x\n", touchBehaviors[i]);
       if (touchBehaviors[i] == AllowedTouchBehavior::UNKNOWN) {
         // If there's any unknown items in the list, throw it out and we'll
         // wait for the main thread to send us a notification.
         touchBehaviors.Clear();
         break;
       }
     }
   } else if (mApzcForInputBlock) {
     APZCTM_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
   }
 
-  // If we receive a touch-cancel, it means all touches are finished, so we
-  // can stop ignoring any that we were ignoring.
-  if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
-    mRetainedTouchIdentifier = -1;
-  }
+  nsEventStatus result = nsEventStatus_eIgnore;
+
+  if (mInScrollbarTouchDrag) {
+    result = ProcessTouchInputForScrollbarDrag(aInput, hitScrollbarNode.get(),
+        aOutTargetGuid, aOutInputBlockId);
+  } else {
+    // If we receive a touch-cancel, it means all touches are finished, so we
+    // can stop ignoring any that we were ignoring.
+    if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
+      mRetainedTouchIdentifier = -1;
+    }
 
-  // If we are currently ignoring any touch points, filter them out from the
-  // set of touch points included in this event. Note that we modify aInput
-  // itself, so that the touch points are also filtered out when the caller
-  // passes the event on to content.
-  if (mRetainedTouchIdentifier != -1) {
-    for (size_t j = 0; j < aInput.mTouches.Length(); ++j) {
-      if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
-        aInput.mTouches.RemoveElementAt(j);
-        if (!touchBehaviors.IsEmpty()) {
-          MOZ_ASSERT(touchBehaviors.Length() > j);
-          touchBehaviors.RemoveElementAt(j);
+    // If we are currently ignoring any touch points, filter them out from the
+    // set of touch points included in this event. Note that we modify aInput
+    // itself, so that the touch points are also filtered out when the caller
+    // passes the event on to content.
+    if (mRetainedTouchIdentifier != -1) {
+      for (size_t j = 0; j < aInput.mTouches.Length(); ++j) {
+        if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) {
+          aInput.mTouches.RemoveElementAt(j);
+          if (!touchBehaviors.IsEmpty()) {
+            MOZ_ASSERT(touchBehaviors.Length() > j);
+            touchBehaviors.RemoveElementAt(j);
+          }
+          --j;
         }
-        --j;
+      }
+      if (aInput.mTouches.IsEmpty()) {
+        return nsEventStatus_eConsumeNoDefault;
       }
     }
-    if (aInput.mTouches.IsEmpty()) {
-      return nsEventStatus_eConsumeNoDefault;
-    }
-  }
+
+    if (mApzcForInputBlock) {
+      MOZ_ASSERT(mHitResultForInputBlock != HitNothing);
 
-  nsEventStatus result = nsEventStatus_eIgnore;
-  if (mApzcForInputBlock) {
-    MOZ_ASSERT(mHitResultForInputBlock != HitNothing);
+      mApzcForInputBlock->GetGuid(aOutTargetGuid);
+      uint64_t inputBlockId = 0;
+      result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock,
+          /* aTargetConfirmed = */ mHitResultForInputBlock != HitDispatchToContentRegion,
+          aInput, &inputBlockId);
+      if (aOutInputBlockId) {
+        *aOutInputBlockId = inputBlockId;
+      }
+      if (!touchBehaviors.IsEmpty()) {
+        mInputQueue->SetAllowedTouchBehavior(inputBlockId, touchBehaviors);
+      }
 
-    mApzcForInputBlock->GetGuid(aOutTargetGuid);
-    uint64_t inputBlockId = 0;
-    result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock,
-        /* aTargetConfirmed = */ mHitResultForInputBlock != HitDispatchToContentRegion,
-        aInput, &inputBlockId);
-    if (aOutInputBlockId) {
-      *aOutInputBlockId = inputBlockId;
-    }
-    if (!touchBehaviors.IsEmpty()) {
-      mInputQueue->SetAllowedTouchBehavior(inputBlockId, touchBehaviors);
-    }
+      // For computing the event to pass back to Gecko, use up-to-date transforms
+      // (i.e. not anything cached in an input block).
+      // This ensures that transformToApzc and transformToGecko are in sync.
+      ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
+      ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
+      ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
 
-    // For computing the event to pass back to Gecko, use up-to-date transforms
-    // (i.e. not anything cached in an input block).
-    // This ensures that transformToApzc and transformToGecko are in sync.
-    ScreenToParentLayerMatrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
-    ParentLayerToScreenMatrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
-    ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko;
-
-    for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
-      SingleTouchData& touchData = aInput.mTouches[i];
-      Maybe<ScreenIntPoint> untransformedScreenPoint = UntransformBy(
-          outTransform, touchData.mScreenPoint);
-      if (!untransformedScreenPoint) {
-        return nsEventStatus_eIgnore;
+      for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
+        SingleTouchData& touchData = aInput.mTouches[i];
+        Maybe<ScreenIntPoint> untransformedScreenPoint = UntransformBy(
+            outTransform, touchData.mScreenPoint);
+        if (!untransformedScreenPoint) {
+          return nsEventStatus_eIgnore;
+        }
+        touchData.mScreenPoint = *untransformedScreenPoint;
       }
-      touchData.mScreenPoint = *untransformedScreenPoint;
     }
   }
 
   mTouchCounter.Update(aInput);
 
   // If it's the end of the touch sequence then clear out variables so we
   // don't keep dangling references and leak things.
   if (mTouchCounter.GetActiveTouchCount() == 0) {
     mApzcForInputBlock = nullptr;
     mHitResultForInputBlock = HitNothing;
     mRetainedTouchIdentifier = -1;
+    mInScrollbarTouchDrag = false;
   }
 
   return result;
 }
 
+MouseInput::MouseType
+MultiTouchTypeToMouseType(MultiTouchInput::MultiTouchType aType)
+{
+  switch (aType)
+  {
+  case MultiTouchInput::MULTITOUCH_START:
+    return MouseInput::MOUSE_DOWN;
+  case MultiTouchInput::MULTITOUCH_MOVE:
+    return MouseInput::MOUSE_MOVE;
+  case MultiTouchInput::MULTITOUCH_END:
+  case MultiTouchInput::MULTITOUCH_CANCEL:
+    return MouseInput::MOUSE_UP;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid multi-touch type");
+  return MouseInput::MOUSE_NONE;
+}
+
+nsEventStatus
+APZCTreeManager::ProcessTouchInputForScrollbarDrag(MultiTouchInput& aTouchInput,
+                                                   const HitTestingTreeNode* aScrollThumbNode,
+                                                   ScrollableLayerGuid* aOutTargetGuid,
+                                                   uint64_t* aOutInputBlockId)
+{
+  MOZ_ASSERT(mRetainedTouchIdentifier == -1);
+  MOZ_ASSERT(mApzcForInputBlock);
+  MOZ_ASSERT(aTouchInput.mTouches.Length() == 1);
+
+  // Synthesize a mouse event based on the touch event, so that we can
+  // reuse code in InputQueue and APZC for handling scrollbar mouse-drags.
+  MouseInput mouseInput{MultiTouchTypeToMouseType(aTouchInput.mType),
+                        MouseInput::LEFT_BUTTON,
+                        nsIDOMMouseEvent::MOZ_SOURCE_TOUCH,
+                        WidgetMouseEvent::eLeftButtonFlag,
+                        aTouchInput.mTouches[0].mScreenPoint,
+                        aTouchInput.mTime,
+                        aTouchInput.mTimeStamp,
+                        aTouchInput.modifiers};
+  mouseInput.mHandledByAPZ = true;
+
+  // The value of |targetConfirmed| passed to InputQueue::ReceiveInputEvent()
+  // only matters for the first event, which creates the drag block. For
+  // that event, the correct value is false, since the drag block will, at the
+  // earliest, be confirmed in the subsequent SetupScrollbarDrag() call.
+  bool targetConfirmed = false;
+
+  nsEventStatus result = mInputQueue->ReceiveInputEvent(mApzcForInputBlock,
+      targetConfirmed, mouseInput, aOutInputBlockId);
+
+  // |aScrollThumbNode| is non-null iff. this is the event that starts the drag.
+  // If so, set up the drag.
+  if (aScrollThumbNode) {
+    SetupScrollbarDrag(mouseInput, aScrollThumbNode, mApzcForInputBlock.get());
+  }
+
+  mApzcForInputBlock->GetGuid(aOutTargetGuid);
+
+  // Since the input was targeted at a scrollbar:
+  //    - The original touch event (which will be sent on to content) will
+  //      not be untransformed.
+  //    - We don't want to apply the callback transform in the main thread,
+  //      so we remove the scrollid from the guid.
+  // Both of these match the behaviour of mouse events that target a scrollbar;
+  // see the code for handling mouse events in ReceiveInputEvent() for
+  // additional explanation.
+  aOutTargetGuid->mScrollId = FrameMetrics::NULL_SCROLL_ID;
+
+  return result;
+}
+
+void
+APZCTreeManager::SetupScrollbarDrag(MouseInput& aMouseInput,
+                                    const HitTestingTreeNode* aScrollThumbNode,
+                                    AsyncPanZoomController* aApzc)
+{
+  DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock();
+  if (!dragBlock) {
+    return;
+  }
+
+  const ScrollThumbData& thumbData = aScrollThumbNode->GetScrollThumbData();
+
+  // Record the thumb's position at the start of the drag.
+  // We snap back to this position if, during the drag, the mouse
+  // gets sufficiently far away from the scrollbar.
+  dragBlock->SetInitialThumbPos(thumbData.mThumbStart);
+
+  // Under some conditions, we can confirm the drag block right away.
+  // Otherwise, we have to wait for a main-thread confirmation.
+  if (gfxPrefs::APZDragInitiationEnabled() &&
+      // check that the scrollbar's target scroll frame is layerized
+      aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId &&
+      !aApzc->IsScrollInfoLayer()) {
+    uint64_t dragBlockId = dragBlock->GetBlockId();
+    // AsyncPanZoomController::HandleInputEvent() will call
+    // TransformToLocal() on the event, but we need its mLocalOrigin now
+    // to compute a drag start offset for the AsyncDragMetrics.
+    aMouseInput.TransformToLocal(aApzc->GetTransformToThis());
+    CSSCoord dragStart = aApzc->ConvertScrollbarPoint(
+        aMouseInput.mLocalOrigin, thumbData);
+    // ConvertScrollbarPoint() got the drag start offset relative to
+    // the scroll track. Now get it relative to the thumb.
+    // ScrollThumbData::mThumbStart stores the offset of the thumb
+    // relative to the scroll track at the time of the last paint.
+    // Since that paint, the thumb may have acquired an async transform
+    // due to async scrolling, so look that up and apply it.
+    LayerToParentLayerMatrix4x4 thumbTransform;
+    {
+      MutexAutoLock lock(mTreeLock);
+      thumbTransform = ComputeTransformForNode(aScrollThumbNode);
+    }
+    // Only consider the translation, since we do not support both
+    // zooming and scrollbar dragging on any platform.
+    CSSCoord thumbStart = thumbData.mThumbStart
+                        + ((thumbData.mDirection == ScrollDirection::HORIZONTAL)
+                           ? thumbTransform._41 : thumbTransform._42);
+    dragStart -= thumbStart;
+
+    // Content can't prevent scrollbar dragging with preventDefault(),
+    // so we don't need to wait for a content response. It's important
+    // to do this before calling ConfirmDragBlock() since that can
+    // potentially process and consume the block.
+    dragBlock->SetContentResponse(false);
+
+    mInputQueue->ConfirmDragBlock(
+        dragBlockId, aApzc,
+        AsyncDragMetrics(aApzc->GetGuid().mScrollId,
+                         aApzc->GetGuid().mPresShellId,
+                         dragBlockId,
+                         dragStart,
+                         thumbData.mDirection));
+  }
+}
+
 void
 APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint,
                                         EventMessage aEventMessage)
 {
   WheelBlockState* txn = mInputQueue->GetActiveWheelTransaction();
   if (!txn) {
     return;
   }
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -522,22 +522,75 @@ private:
                                          const ParentLayerPoint& aHitTestPoint,
                                          HitTestResult* aOutHitResult,
                                          HitTestingTreeNode** aOutScrollbarNode);
   AsyncPanZoomController* FindRootApzcForLayersId(uint64_t aLayersId) const;
   AsyncPanZoomController* FindRootContentApzcForLayersId(uint64_t aLayersId) const;
   AsyncPanZoomController* FindRootContentOrRootApzc() const;
   already_AddRefed<AsyncPanZoomController> GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
+  /**
+   * Perform hit testing for a touch-start event.
+   *
+   * @param aEvent The touch-start event.
+   *
+   * The remaining parameters are out-parameter used to communicate additional
+   * return values:
+   *
+   * @param aOutTouchBehaviors
+   *     The touch behaviours that should be allowed for this touch block.
+   * @param aOutHitResult The hit test result.
+   * @param aOutHitScrollbarNode
+   *     If the touch event contains a single touch point (so that it may
+   *     potentially start a scrollbar drag), and a scrollbar node was hit,
+   *     that scrollbar node, otherwise nullptr.
+   *
+   * @return The APZC that was hit.
+   */
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                                                   nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors,
-                                                                  HitTestResult* aOutHitResult);
+                                                                  HitTestResult* aOutHitResult,
+                                                                  RefPtr<HitTestingTreeNode>* aOutHitScrollbarNode);
   nsEventStatus ProcessTouchInput(MultiTouchInput& aInput,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
+  /**
+   * Given a mouse-down event that hit a scroll thumb node, set up APZ
+   * dragging of the scroll thumb.
+   *
+   * Must be called after the mouse event has been sent to InputQueue.
+   *
+   * @param aMouseInput The mouse-down event.
+   * @param aScrollThumbNode Tthe scroll thumb node that was hit.
+   * @param aApzc
+   *     The APZC for the scroll frame scrolled by the scroll thumb, if that
+   *     scroll frame is layerized. (A thumb can be layerized without its
+   *     target scroll frame being layerized.) Otherwise, an enclosing APZC.
+   */
+  void SetupScrollbarDrag(MouseInput& aMouseInput,
+                          const HitTestingTreeNode* aScrollThumbNode,
+                          AsyncPanZoomController* aApzc);
+  /**
+   * Process a touch event that's part of a scrollbar touch-drag gesture.
+   *
+   * @param aInput The touch event.
+   * @param aScrollThumbNode
+   *     If this is the touch-start event, the node representing the scroll
+   *     thumb we are starting to drag. Otherwise nullptr.
+   * @param aOutTargetGuid
+   *     The guid of the APZC for the scroll frame whose scroll thumb is
+   *     being dragged.
+   * @param aOutInputBlockId
+   *     The ID of the input block for the touch-drag gesture.
+   * @return See ReceiveInputEvent() for what the return value means.
+   */
+  nsEventStatus ProcessTouchInputForScrollbarDrag(MultiTouchInput& aInput,
+                                                  const HitTestingTreeNode* aScrollThumbNode,
+                                                  ScrollableLayerGuid* aOutTargetGuid,
+                                                  uint64_t* aOutInputBlockId);
   void FlushRepaintsToClearScreenToGeckoTransform();
 
   already_AddRefed<HitTestingTreeNode> RecycleOrCreateNode(TreeBuildingState& aState,
                                                            AsyncPanZoomController* aApzc,
                                                            uint64_t aLayersId);
   template<class ScrollNode>
   HitTestingTreeNode* PrepareNodeForLayer(const ScrollNode& aLayer,
                                           const FrameMetrics& aMetrics,
@@ -593,16 +646,23 @@ private:
    * sync with mApzcForInputBlock.
    */
   HitTestResult mHitResultForInputBlock;
   /* Sometimes we want to ignore all touches except one. In such cases, this
    * is set to the identifier of the touch we are not ignoring; in other cases,
    * this is set to -1.
    */
   int32_t mRetainedTouchIdentifier;
+  /* This tracks whether the current input block represents a touch-drag of
+   * a scrollbar. In this state, touch events are forwarded to content as touch
+   * events, but converted to mouse events before going into InputQueue and
+   * being handled by an APZC (to reuse the APZ code for scrollbar dragging
+   * with a mouse).
+   */
+  bool mInScrollbarTouchDrag;
   /* Tracks the number of touch points we are tracking that are currently on
    * the screen. */
   TouchCounter mTouchCounter;
   /* Stores the current mouse position in screen coordinates.
    */
   ScreenPoint mCurrentMousePosition;
   /* For logging the APZC tree for debugging (enabled by the apz.printtree
    * pref). */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -194,16 +194,29 @@ typedef GenericFlingAnimation FlingAnima
  * \li\b apz.displayport_expiry_ms
  * While a scrollable frame is scrolling async, we set a displayport on it
  * to make sure it is layerized. However this takes up memory, so once the
  * scrolling stops we want to remove the displayport. This pref controls how
  * long after scrolling stops the displayport is removed. A value of 0 will
  * disable the expiry behavior entirely.
  * Units: milliseconds
  *
+ * \li\b apz.drag.enabled
+ * Setting this pref to true will cause APZ to handle mouse-dragging of
+ * scrollbar thumbs.
+ *
+ * \li\b apz.drag.initial.enabled
+ * Setting this pref to true will cause APZ to try to handle mouse-dragging
+ * of scrollbar thumbs without an initial round-trip to content to start it
+ * if possible. Only has an effect if apz.drag.enabled is also true.
+ *
+ * \li\b apz.drag.touch.enabled
+ * Setting this pref to true will cause APZ to handle touch-dragging of
+ * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true.
+ *
  * \li\b apz.enlarge_displayport_when_clipped
  * Pref that enables enlarging of the displayport along one axis when the
  * generated displayport's size is beyond that of the scrollable rect on the
  * opposite axis.
  *
  * \li\b apz.fling_accel_interval_ms
  * The time that determines whether a second fling will be treated as
  * accelerated. If two flings are started within this interval, the second one
--- a/gfx/layers/apz/util/ActiveElementManager.cpp
+++ b/gfx/layers/apz/util/ActiveElementManager.cpp
@@ -118,17 +118,22 @@ ActiveElementManager::HandleTouchEndEven
 {
   AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick);
 
   // If the touch was a click, make mTarget :active right away.
   // nsEventStateManager will reset the active element when processing
   // the mouse-down event generated by the click.
   CancelTask();
   if (aWasClick) {
-    SetActive(mTarget);
+    // Scrollbar thumbs use a different mechanism for their active
+    // highlight (the "active" attribute), so don't set the active state
+    // on them because nothing will clear it.
+    if (!(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) {
+      SetActive(mTarget);
+    }
   } else {
     // We might reach here if mCanBePan was false on touch-start and
     // so we set the element active right away. Now it turns out the
     // action was not a click so we need to reset the active element.
     ResetActive();
   }
 
   ResetTouchBlockState();
--- a/gfx/thebes/D3D11Checks.cpp
+++ b/gfx/thebes/D3D11Checks.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * 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/. */
 
 #include "D3D11Checks.h"
+#include "DXVA2Manager.h"
 #include "gfxConfig.h"
 #include "GfxDriverInfo.h"
 #include "gfxPrefs.h"
 #include "gfxWindowsPlatform.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/TextureD3D11.h"
 #include "nsIGfxInfo.h"
@@ -404,10 +405,28 @@ D3D11Checks::WarnOnAdapterMismatch(ID3D1
 D3D11Checks::DoesRemotePresentWork(IDXGIAdapter* adapter)
 {
   // Remote presentation was added in DXGI 1.2, for Windows 8 and the Platform Update to Windows 7.
   RefPtr<IDXGIAdapter2> check;
   HRESULT hr = adapter->QueryInterface(__uuidof(IDXGIAdapter2), getter_AddRefs(check));
   return SUCCEEDED(hr) && check;
 }
 
+/* static */ bool
+D3D11Checks::DoesNV12Work(ID3D11Device* device)
+{
+  DXGI_ADAPTER_DESC desc;
+  PodZero(&desc);
+  if (!GetDxgiDesc(device, &desc)) {
+    // Failed to retrieve device information, assume it doesn't work
+    return false;
+  }
+
+  nsString version;
+  nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+  if (gfxInfo) {
+    gfxInfo->GetAdapterDriverVersion(version);
+  }
+  return DXVA2Manager::IsNV12Supported(desc.VendorId, desc.DeviceId, version);
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/thebes/D3D11Checks.h
+++ b/gfx/thebes/D3D11Checks.h
@@ -12,19 +12,20 @@ struct DXGI_ADAPTER_DESC;
 
 namespace mozilla {
 namespace gfx {
 
 struct D3D11Checks
 {
   static bool DoesRenderTargetViewNeedRecreating(ID3D11Device* aDevice);
   static bool DoesDeviceWork();
-  static bool DoesTextureSharingWork(ID3D11Device *device);
-  static bool DoesAlphaTextureSharingWork(ID3D11Device *device);
+  static bool DoesTextureSharingWork(ID3D11Device* device);
+  static bool DoesAlphaTextureSharingWork(ID3D11Device* device);
   static void WarnOnAdapterMismatch(ID3D11Device* device);
   static bool GetDxgiDesc(ID3D11Device* device, DXGI_ADAPTER_DESC* out);
   static bool DoesRemotePresentWork(IDXGIAdapter* adapter);
+  static bool DoesNV12Work(ID3D11Device* device);
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // mozilla_gfx_thebes_D3D11Checks_h
--- a/gfx/thebes/DeviceManagerDx.cpp
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -390,27 +390,29 @@ DeviceManagerDx::CreateCompositorDevice(
                          "RenderTargetViews need recreating");
   }
   if (XRE_IsParentProcess()) {
     // It seems like this may only happen when we're using the NVIDIA gpu
     D3D11Checks::WarnOnAdapterMismatch(device);
   }
 
   uint32_t featureLevel = device->GetFeatureLevel();
+  bool useNV12 = D3D11Checks::DoesNV12Work(device);
   {
     MutexAutoLock lock(mDeviceLock);
     mCompositorDevice = device;
 
     int32_t sequenceNumber = GetNextDeviceCounter();
     mDeviceStatus = Some(D3D11DeviceStatus(
       false,
       textureSharingWorks,
       featureLevel,
       DxgiAdapterDesc::From(desc),
-      sequenceNumber));
+      sequenceNumber,
+      useNV12));
   }
   mCompositorDevice->SetExceptionMode(0);
 }
 
 bool
 DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
                                  D3D_DRIVER_TYPE aDriverType,
                                  UINT aFlags,
@@ -496,27 +498,30 @@ DeviceManagerDx::CreateWARPCompositorDev
   if (IsWin8OrLater()) {
     textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device);
   }
 
   DxgiAdapterDesc nullAdapter;
   PodZero(&nullAdapter);
 
   int featureLevel = device->GetFeatureLevel();
+
+  bool useNV12 = D3D11Checks::DoesNV12Work(device);
   {
     MutexAutoLock lock(mDeviceLock);
     mCompositorDevice = device;
 
     int32_t sequenceNumber = GetNextDeviceCounter();
     mDeviceStatus = Some(D3D11DeviceStatus(
       true,
       textureSharingWorks,
       featureLevel,
       nullAdapter,
-      sequenceNumber));
+      sequenceNumber,
+      useNV12));
   }
   mCompositorDevice->SetExceptionMode(0);
 
   reporterWARP.SetSuccessful();
 }
 
 FeatureStatus
 DeviceManagerDx::CreateContentDevice()
@@ -996,16 +1001,26 @@ DeviceManagerDx::IsWARP()
 {
   MutexAutoLock lock(mDeviceLock);
   if (!mDeviceStatus) {
     return false;
   }
   return mDeviceStatus->isWARP();
 }
 
+bool
+DeviceManagerDx::CanUseNV12()
+{
+  MutexAutoLock lock(mDeviceLock);
+  if (!mDeviceStatus) {
+    return false;
+  }
+  return mDeviceStatus->useNV12();
+}
+
 void
 DeviceManagerDx::InitializeDirectDraw()
 {
   MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());
 
   if (mDirectDraw) {
     // Already initialized.
     return;
--- a/gfx/thebes/DeviceManagerDx.h
+++ b/gfx/thebes/DeviceManagerDx.h
@@ -59,16 +59,17 @@ public:
   RefPtr<ID3D11Device> GetContentDevice();
   RefPtr<ID3D11Device> CreateDecoderDevice();
   RefPtr<layers::MLGDevice> GetMLGDevice();
   IDirectDraw7* GetDirectDraw();
 
   unsigned GetCompositorFeatureLevel() const;
   bool TextureSharingWorks();
   bool IsWARP();
+  bool CanUseNV12();
 
   // Returns true if we can create a texture with
   // D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX and also
   // upload texture data during the CreateTexture2D
   // call. This crashes on some devices, so we might
   // need to avoid it.
   bool CanInitializeKeyedMutexTextures();
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -293,16 +293,17 @@ private:
   DECL_GFX_PREF(Live, "apz.axis_lock.mode",                    APZAxisLockMode, int32_t, 0);
   DECL_GFX_PREF(Live, "apz.content_response_timeout",          APZContentResponseTimeout, int32_t, 400);
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
   DECL_GFX_PREF(Live, "apz.danger_zone_y",                     APZDangerZoneY, int32_t, 100);
   DECL_GFX_PREF(Live, "apz.disable_for_scroll_linked_effects", APZDisableForScrollLinkedEffects, bool, false);
   DECL_GFX_PREF(Live, "apz.displayport_expiry_ms",             APZDisplayPortExpiryTime, uint32_t, 15000);
   DECL_GFX_PREF(Live, "apz.drag.enabled",                      APZDragEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.drag.initial.enabled",              APZDragInitiationEnabled, bool, false);
+  DECL_GFX_PREF(Live, "apz.drag.touch.enabled",                APZTouchDragEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped",  APZEnlargeDisplayPortWhenClipped, bool, false);
   DECL_GFX_PREF(Live, "apz.fling_accel_base_mult",             APZFlingAccelBaseMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms",           APZFlingAccelInterval, int32_t, 500);
   DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult",     APZFlingAccelSupplementalMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_min_velocity",          APZFlingAccelMinVelocity, float, 1.5f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x1",           APZCurveFunctionX1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x2",           APZCurveFunctionX2, float, 1.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -725,61 +725,61 @@ GeckoChildProcessHost::PerformAsyncLaunc
 //--------------------------------------------------
 #if defined(OS_POSIX)
   // For POSIX, we have to be extremely anal about *not* using
   // std::wstring in code compiled with Mozilla's -fshort-wchar
   // configuration, because chromium is compiled with -fno-short-wchar
   // and passing wstrings from one config to the other is unsafe.  So
   // we split the logic here.
 
-#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
+# if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
   base::environment_map newEnvVars;
   ChildPrivileges privs = mPrivileges;
   if (privs == base::PRIVILEGES_DEFAULT ||
       privs == base::PRIVILEGES_FILEREAD) {
     privs = DefaultChildPrivileges();
   }
 
-#if defined(MOZ_WIDGET_GTK)
+#  if defined(MOZ_WIDGET_GTK)
   if (mProcessType == GeckoProcessType_Content) {
     // disable IM module to avoid sandbox violation
     newEnvVars["GTK_IM_MODULE"] = "gtk-im-context-simple";
 
     // Disable ATK accessibility code in content processes because it conflicts
     // with the sandbox, and we proxy that information through the main process
     // anyway.
     newEnvVars["NO_AT_BRIDGE"] = "1";
   }
-#endif
+#  endif // defined(MOZ_WIDGET_GTK)
 
   // XPCOM may not be initialized in some subprocesses.  We don't want
   // to initialize XPCOM just for the directory service, especially
   // since LD_LIBRARY_PATH is already set correctly in subprocesses
   // (meaning that we don't need to set that up in the environment).
   if (ShouldHaveDirectoryService()) {
     MOZ_ASSERT(gGREBinPath);
     nsCString path;
     NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path);
-# if defined(OS_LINUX) || defined(OS_BSD)
+#  if defined(OS_LINUX) || defined(OS_BSD)
     const char *ld_library_path = PR_GetEnv("LD_LIBRARY_PATH");
     nsCString new_ld_lib_path(path.get());
 
-#  if (MOZ_WIDGET_GTK == 3)
+#   if (MOZ_WIDGET_GTK == 3)
     if (mProcessType == GeckoProcessType_Plugin) {
       new_ld_lib_path.Append("/gtk2:");
       new_ld_lib_path.Append(path.get());
     }
-#endif
+#   endif // (MOZ_WIDGET_GTK == 3)
     if (ld_library_path && *ld_library_path) {
       new_ld_lib_path.Append(':');
       new_ld_lib_path.Append(ld_library_path);
     }
     newEnvVars["LD_LIBRARY_PATH"] = new_ld_lib_path.get();
 
-# elif OS_MACOSX
+#  elif OS_MACOSX // defined(OS_LINUX) || defined(OS_BSD)
     newEnvVars["DYLD_LIBRARY_PATH"] = path.get();
     // XXX DYLD_INSERT_LIBRARIES should only be set when launching a plugin
     //     process, and has no effect on other subprocesses (the hooks in
     //     libplugin_child_interpose.dylib become noops).  But currently it
     //     gets set when launching any kind of subprocess.
     //
     // Trigger "dyld interposing" for the dylib that contains
     // plugin_child_interpose.mm.  This allows us to hook OS calls in the
@@ -790,24 +790,24 @@ GeckoChildProcessHost::PerformAsyncLaunc
     nsCString interpose;
     if (prevInterpose && strlen(prevInterpose) > 0) {
       interpose.Assign(prevInterpose);
       interpose.Append(':');
     }
     interpose.Append(path.get());
     interpose.AppendLiteral("/libplugin_child_interpose.dylib");
     newEnvVars["DYLD_INSERT_LIBRARIES"] = interpose.get();
-# endif  // OS_LINUX
+#  endif // defined(OS_LINUX) || defined(OS_BSD)
   }
-#endif  // OS_LINUX || OS_MACOSX
+# endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
 
   FilePath exePath;
   BinaryPathType pathType = GetPathToBinary(exePath, mProcessType);
 
-#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# if defined(XP_LINUX) && defined(MOZ_SANDBOX)
   // Preload libmozsandbox.so so that sandbox-related interpositions
   // can be defined there instead of in the executable.
   // (This could be made conditional on intent to use sandboxing, but
   // it's harmless for non-sandboxed processes.)
   {
     nsAutoCString preload;
     // Prepend this, because people can and do preload libpthread.
     // (See bug 1222500.)
@@ -816,17 +816,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
       // Doesn't matter if oldPreload is ""; extra separators are ignored.
       preload.Append(' ');
       preload.Append(oldPreload);
     }
     // Explicitly construct the std::string to make it clear that this
     // isn't retaining a pointer to the nsCString's buffer.
     newEnvVars["LD_PRELOAD"] = std::string(preload.get());
   }
-#endif
+# endif // defined(XP_LINUX) && defined(MOZ_SANDBOX)
 
   // remap the IPC socket fd to a well-known int, as the OS does for
   // STDOUT_FILENO, for example
   int srcChannelFd, dstChannelFd;
   channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
   mFileMap.push_back(std::pair<int,int>(srcChannelFd, dstChannelFd));
 
   // no need for kProcessChannelID, the child process inherits the
@@ -856,76 +856,90 @@ GeckoChildProcessHost::PerformAsyncLaunc
       childArgv.push_back("-appomni");
       childArgv.push_back(path.get());
     }
   }
 
   // Add the application directory path (-appdir path)
   AddAppDirToCommandLine(childArgv);
 
+  // Tmp dir that the GPU process should use for crash reports. This arg is
+  // always populated (but possibly with an empty value) for a GPU child process.
+  if (mProcessType == GeckoProcessType_GPU) {
+    nsCOMPtr<nsIFile> file;
+# ifdef MOZ_CRASHREPORTER
+    CrashReporter::GetChildProcessTmpDir(getter_AddRefs(file));
+# endif // MOZ_CRASHREPORTER
+    nsAutoCString path;
+    if (file) {
+      file->GetNativePath(path);
+    }
+    childArgv.push_back(path.get());
+  }
+
   childArgv.push_back(pidstring);
 
-#if defined(MOZ_CRASHREPORTER)
+# if defined(MOZ_CRASHREPORTER)
 #  if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
   int childCrashFd, childCrashRemapFd;
   if (!CrashReporter::CreateNotificationPipeForChild(
         &childCrashFd, &childCrashRemapFd))
     return false;
   if (0 <= childCrashFd) {
     mFileMap.push_back(std::pair<int,int>(childCrashFd, childCrashRemapFd));
     // "true" == crash reporting enabled
     childArgv.push_back("true");
   }
   else {
     // "false" == crash reporting disabled
     childArgv.push_back("false");
   }
-#  elif defined(MOZ_WIDGET_COCOA)
+#  elif defined(MOZ_WIDGET_COCOA) // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
   childArgv.push_back(CrashReporter::GetChildNotificationPipe());
-#  endif  // OS_LINUX || OS_BSD || OS_SOLARIS
-#endif
+#  endif  // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+# endif // defined(MOZ_CRASHREPORTER)
 
-#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# if defined(XP_LINUX) && defined(MOZ_SANDBOX)
   {
     int srcFd, dstFd;
     SandboxReporter::Singleton()
       ->GetClientFileDescriptorMapping(&srcFd, &dstFd);
     mFileMap.push_back(std::make_pair(srcFd, dstFd));
   }
-#endif
+# endif // defined(XP_LINUX) && defined(MOZ_SANDBOX)
 
-#ifdef MOZ_WIDGET_COCOA
+# ifdef MOZ_WIDGET_COCOA
   // Add a mach port to the command line so the child can communicate its
   // 'task_t' back to the parent.
   //
   // Put a random number into the channel name, so that a compromised renderer
   // can't pretend being the child that's forked off.
   std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d",
                                                   base::RandInt(0, std::numeric_limits<int>::max()));
   childArgv.push_back(mach_connection_name.c_str());
-#endif
+# endif // MOZ_WIDGET_COCOA
 
   childArgv.push_back(childProcessType);
 
-#if defined(MOZ_WIDGET_ANDROID)
+# if defined(MOZ_WIDGET_ANDROID)
   LaunchAndroidService(childProcessType, childArgv, mFileMap, &process);
-#else
+# else // goes with defined(MOZ_WIDGET_ANDROID)
   base::LaunchApp(childArgv, mFileMap,
-#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
+#  if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
                   newEnvVars, privs,
-#endif
+#  endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
                   false, &process, arch);
-#endif // defined(MOZ_WIDGET_ANDROID)
+# endif // defined(MOZ_WIDGET_ANDROID)
 
   // We're in the parent and the child was launched. Close the child FD in the
   // parent as soon as possible, which will allow the parent to detect when the
   // child closes its FD (either due to normal exit or due to crash).
   GetChannel()->CloseClientFileDescriptor();
 
-#ifdef MOZ_WIDGET_COCOA
+# ifdef MOZ_WIDGET_COCOA
   // Wait for the child process to send us its 'task_t' data.
   const int kTimeoutMs = 10000;
 
   MachReceiveMessage child_message;
   ReceivePort parent_recv_port(mach_connection_name.c_str());
   kern_return_t err = parent_recv_port.WaitForMessage(&child_message, kTimeoutMs);
   if (err != KERN_SUCCESS) {
     std::string errString = StringPrintf("0x%x %s", err, mach_error_string(err));
@@ -978,20 +992,20 @@ GeckoChildProcessHost::PerformAsyncLaunc
     std::string errString = StringPrintf("0x%x %s", err, mach_error_string(err));
     CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString;
     return false;
   }
 
   SharedMemoryBasic::SetupMachMemory(process, parent_recv_port_memory, parent_recv_port_memory_ack,
                                      parent_send_port_memory, parent_send_port_memory_ack, false);
 
-#endif
+# endif // MOZ_WIDGET_COCOA
 
 //--------------------------------------------------
-#elif defined(OS_WIN)
+#elif defined(OS_WIN) // defined(OS_POSIX)
 
   FilePath exePath;
   BinaryPathType pathType = GetPathToBinary(exePath, mProcessType);
 
   CommandLine cmdLine(exePath.ToWStringHack());
 
   if (pathType == BinaryPathType::Self) {
     cmdLine.AppendLooseValue(UTF8ToWide("-contentproc"));
@@ -1016,35 +1030,35 @@ GeckoChildProcessHost::PerformAsyncLaunc
     }
     file = Omnijar::GetPath(Omnijar::APP);
     if (file && NS_SUCCEEDED(file->GetPath(path))) {
       cmdLine.AppendLooseValue(UTF8ToWide("-appomni"));
       cmdLine.AppendLooseValue(path.get());
     }
   }
 
-#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# if defined(XP_WIN) && defined(MOZ_SANDBOX)
   bool shouldSandboxCurrentProcess = false;
 
   // XXX: Bug 1124167: We should get rid of the process specific logic for
   // sandboxing in this class at some point. Unfortunately it will take a bit
   // of reorganizing so I don't think this patch is the right time.
   switch (mProcessType) {
     case GeckoProcessType_Content:
-#if defined(MOZ_CONTENT_SANDBOX)
+#  if defined(MOZ_CONTENT_SANDBOX)
       if (mSandboxLevel > 0 &&
           !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
         // For now we treat every failure as fatal in SetSecurityLevelForContentProcess
         // and just crash there right away. Should this change in the future then we
         // should also handle the error here.
         mSandboxBroker.SetSecurityLevelForContentProcess(mSandboxLevel,
                                                          mPrivileges);
         shouldSandboxCurrentProcess = true;
       }
-#endif // MOZ_CONTENT_SANDBOX
+#  endif // defined(MOZ_CONTENT_SANDBOX)
       break;
     case GeckoProcessType_Plugin:
       if (mSandboxLevel > 0 &&
           !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) {
         bool ok = mSandboxBroker.SetSecurityLevelForPluginProcess(mSandboxLevel);
         if (!ok) {
           return false;
         }
@@ -1087,94 +1101,109 @@ GeckoChildProcessHost::PerformAsyncLaunc
 
   if (shouldSandboxCurrentProcess) {
     for (auto it = mAllowedFilesRead.begin();
          it != mAllowedFilesRead.end();
          ++it) {
       mSandboxBroker.AllowReadFile(it->c_str());
     }
   }
-#endif // XP_WIN && MOZ_SANDBOX
+# endif // defined(XP_WIN) && defined(MOZ_SANDBOX)
 
   // Add the application directory path (-appdir path)
   AddAppDirToCommandLine(cmdLine);
 
   // XXX Command line params past this point are expected to be at
   // the end of the command line string, and in a specific order.
   // See XRE_InitChildProcess in nsEmbedFunction.
 
   // Win app model id
   cmdLine.AppendLooseValue(mGroupId.get());
 
+  // Tmp dir that the GPU process should use for crash reports. This arg is
+  // always populated (but possibly with an empty value) for a GPU child process.
+  if (mProcessType == GeckoProcessType_GPU) {
+    nsCOMPtr<nsIFile> file;
+# ifdef MOZ_CRASHREPORTER
+    CrashReporter::GetChildProcessTmpDir(getter_AddRefs(file));
+# endif // MOZ_CRASHREPORTER
+    nsString path;
+    if (file) {
+      MOZ_ALWAYS_SUCCEEDS(file->GetPath(path));
+    }
+    std::wstring wpath(path.get());
+    cmdLine.AppendLooseValue(wpath);
+  }
+
   // Process id
   cmdLine.AppendLooseValue(UTF8ToWide(pidstring));
 
-#if defined(MOZ_CRASHREPORTER)
+# if defined(MOZ_CRASHREPORTER)
   cmdLine.AppendLooseValue(
     UTF8ToWide(CrashReporter::GetChildNotificationPipe()));
-#endif
+# endif // defined(MOZ_CRASHREPORTER)
 
   // Process type
   cmdLine.AppendLooseValue(UTF8ToWide(childProcessType));
 
-#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# if defined(XP_WIN) && defined(MOZ_SANDBOX)
   if (shouldSandboxCurrentProcess) {
     if (mSandboxBroker.LaunchApp(cmdLine.program().c_str(),
                                  cmdLine.command_line_string().c_str(),
                                  mProcessType,
                                  mEnableSandboxLogging,
                                  &process)) {
       EnvironmentLog("MOZ_PROCESS_LOG").print(
         "==> process %d launched child process %d (%S)\n",
         base::GetCurrentProcId(), base::GetProcId(process),
         cmdLine.command_line_string().c_str());
     }
   } else
-#endif
+# endif // defined(XP_WIN) && defined(MOZ_SANDBOX)
   {
     base::LaunchApp(cmdLine, false, false, &process);
 
-#ifdef MOZ_SANDBOX
+# ifdef MOZ_SANDBOX
     // We need to be able to duplicate handles to some types of non-sandboxed
     // child processes.
     if (mProcessType == GeckoProcessType_Content ||
         mProcessType == GeckoProcessType_GPU ||
         mProcessType == GeckoProcessType_GMPlugin) {
       if (!mSandboxBroker.AddTargetPeer(process)) {
         NS_WARNING("Failed to add content process as target peer.");
       }
     }
-#endif
+# endif // MOZ_SANDBOX
   }
 
-#else
+#else // goes with defined(OS_POSIX)
 #  error Sorry
-#endif
+#endif // defined(OS_POSIX)
 
   if (!process) {
     return false;
   }
   // NB: on OS X, we block much longer than we need to in order to
   // reach this call, waiting for the child process's task_t.  The
   // best way to fix that is to refactor this file, hard.
 #if defined(MOZ_WIDGET_COCOA)
   mChildTask = child_task;
-#endif
+#endif // defined(MOZ_WIDGET_COCOA)
 
   if (!OpenPrivilegedHandle(base::GetProcId(process))
 #ifdef XP_WIN
       // If we failed in opening the process handle, try harder by duplicating
       // one.
       && !::DuplicateHandle(::GetCurrentProcess(), process,
                             ::GetCurrentProcess(), &mChildProcessHandle,
                             PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
                             PROCESS_QUERY_INFORMATION | PROCESS_VM_READ |
                             SYNCHRONIZE,
                             FALSE, 0)
-#endif
+#endif // XP_WIN
      ) {
     MOZ_CRASH("cannot open handle to child process");
   }
   MonitorAutoLock lock(mMonitor);
   mProcessState = PROCESS_CREATED;
   lock.Notify();
 
   return true;
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -8459,16 +8459,17 @@ PresShell::DispatchTouchEventToDOM(Widge
       }
       content = capturingContent;
     }
     // copy the event
     WidgetTouchEvent newEvent(touchEvent->IsTrusted(),
                               touchEvent->mMessage, touchEvent->mWidget);
     newEvent.AssignTouchEventData(*touchEvent, false);
     newEvent.mTarget = targetPtr;
+    newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ;
 
     RefPtr<PresShell> contentPresShell;
     if (doc == mDocument) {
       contentPresShell = static_cast<PresShell*>(doc->GetShell());
       if (contentPresShell) {
         //XXXsmaug huge hack. Pushing possibly capturing content,
         //         even though event target is something else.
         contentPresShell->PushCurrentEventInfo(
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -70,16 +70,31 @@ summaryLines = [('Successful', [('pass',
                                 ('failedLoad', 'failed load'),
                                 ('exception', 'exception')]),
                 ('Known problems', [('knownFail', 'known fail'),
                                     ('knownAsserts', 'known asserts'),
                                     ('random', 'random'),
                                     ('skipped', 'skipped'),
                                     ('slow', 'slow')])]
 
+
+def update_mozinfo():
+    """walk up directories to find mozinfo.json update the info"""
+    # TODO: This should go in a more generic place, e.g. mozinfo
+
+    path = SCRIPT_DIRECTORY
+    dirs = set()
+    while path != os.path.expanduser('~'):
+        if path in dirs:
+            break
+        dirs.add(path)
+        path = os.path.split(path)[0]
+    mozinfo.find_and_update_from_json(*dirs)
+
+
 # Python's print is not threadsafe.
 printLock = threading.Lock()
 
 
 class ReftestThread(threading.Thread):
     def __init__(self, cmdargs):
         threading.Thread.__init__(self)
         self.cmdargs = cmdargs
@@ -211,17 +226,17 @@ class ReftestResolver(object):
 class RefTest(object):
     TEST_SEEN_INITIAL = 'reftest'
     TEST_SEEN_FINAL = 'Main app process exited normally'
     use_marionette = True
     oldcwd = os.getcwd()
     resolver_cls = ReftestResolver
 
     def __init__(self):
-        self.update_mozinfo()
+        update_mozinfo()
         self.lastTestSeen = self.TEST_SEEN_INITIAL
         self.haveDumpedScreen = False
         self.resolver = self.resolver_cls()
         self.log = None
 
     def _populate_logger(self, options):
         if self.log:
             return
@@ -231,29 +246,16 @@ class RefTest(object):
                                                      "benefit of legacy log parsers and"
                                                      "tools such as the reftest analyzer")
         fmt_options = {}
         if not options.log_tbpl_level and os.environ.get('MOZ_REFTEST_VERBOSE'):
             options.log_tbpl_level = fmt_options['level'] = 'debug'
         self.log = mozlog.commandline.setup_logging(
             "reftest harness", options, {"tbpl": sys.stdout}, fmt_options)
 
-    def update_mozinfo(self):
-        """walk up directories to find mozinfo.json update the info"""
-        # TODO: This should go in a more generic place, e.g. mozinfo
-
-        path = SCRIPT_DIRECTORY
-        dirs = set()
-        while path != os.path.expanduser('~'):
-            if path in dirs:
-                break
-            dirs.add(path)
-            path = os.path.split(path)[0]
-        mozinfo.find_and_update_from_json(*dirs)
-
     def getFullPath(self, path):
         "Get an absolute path relative to self.oldcwd."
         return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path)))
 
     def createReftestProfile(self, options, manifests, server='localhost', port=0,
                              profile_to_clone=None, startAfter=None):
         """Sets up a profile for reftest.
 
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/conftest.py
@@ -0,0 +1,95 @@
+# 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 json
+import os
+import pytest
+from argparse import Namespace
+from cStringIO import StringIO
+
+import mozinfo
+from manifestparser import expression
+from moztest.selftest.fixtures import binary, setup_test_harness  # noqa
+
+here = os.path.abspath(os.path.dirname(__file__))
+setup_args = [False, 'reftest', 'reftest']
+
+
+@pytest.fixture  # noqa: F811
+def parser(setup_test_harness):
+    setup_test_harness(*setup_args)
+    cmdline = pytest.importorskip('reftestcommandline')
+    return cmdline.DesktopArgumentsParser()
+
+
+@pytest.fixture  # noqa: F811
+def runtests(setup_test_harness, binary, parser):
+    setup_test_harness(*setup_args)
+    runreftest = pytest.importorskip('runreftest')
+    harness_root = runreftest.SCRIPT_DIRECTORY
+
+    buf = StringIO()
+    build = parser.build_obj
+    options = vars(parser.parse_args([]))
+    options.update({
+        'app': binary,
+        'focusFilterMode': 'non-needs-focus',
+        'log_raw': [buf],
+        'suite': 'reftest',
+    })
+
+    if not os.path.isdir(build.bindir):
+        package_root = os.path.dirname(harness_root)
+        options.update({
+            'extraProfileFiles': [os.path.join(package_root, 'bin', 'plugins')],
+            'objPath': os.environ['PYTHON_TEST_TMP'],
+            'reftestExtensionPath': os.path.join(harness_root, 'reftest'),
+            'utilityPath': os.path.join(package_root, 'bin'),
+            'workPath': here,
+        })
+    else:
+        options.update({
+            'extraProfileFiles': [os.path.join(build.topobjdir, 'dist', 'plugins')],
+            'objPath': build.topobjdir,
+            'workPath': build.topsrcdir,
+        })
+
+    def normalize(test):
+        if os.path.isabs(test):
+            return test
+        return os.path.join(here, 'files', test)
+
+    def inner(*tests, **opts):
+        assert len(tests) > 0
+        tests = map(normalize, tests)
+
+        options['tests'] = tests
+        options.update(opts)
+
+        result = runreftest.run_test_harness(parser, Namespace(**options))
+        out = json.loads('[' + ','.join(buf.getvalue().splitlines()) + ']')
+        buf.close()
+        return result, out
+    return inner
+
+
+@pytest.fixture(autouse=True)  # noqa: F811
+def skip_using_mozinfo(request, setup_test_harness):
+    """Gives tests the ability to skip based on values from mozinfo.
+
+    Example:
+        @pytest.mark.skip_mozinfo("!e10s || os == 'linux'")
+        def test_foo():
+            pass
+    """
+
+    setup_test_harness(*setup_args)
+    runreftest = pytest.importorskip('runreftest')
+    runreftest.update_mozinfo()
+
+    skip_mozinfo = request.node.get_marker('skip_mozinfo')
+    if skip_mozinfo:
+        value = skip_mozinfo.args[0]
+        if expression.parse(value, **mozinfo.info):
+            pytest.skip("skipped due to mozinfo match: \n{}".format(value))
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/files/green.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="color: green">Text</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/files/red.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="color: red">Text</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/files/reftest-fail.list
@@ -0,0 +1,3 @@
+== green.html red.html
+!= green.html green.html
+!= red.html red.html
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/files/reftest-pass.list
@@ -0,0 +1,3 @@
+== green.html green.html
+== red.html red.html
+!= green.html red.html
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/python.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+subsuite=reftest
+sequential=true
+
+[test_reftest_output.py]
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/selftest/test_reftest_output.py
@@ -0,0 +1,43 @@
+# 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/.
+
+from functools import partial
+
+import mozunit
+from moztest.selftest.output import get_mozharness_status, filter_action
+
+from mozharness.base.log import INFO, WARNING
+from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WARNING
+
+get_mozharness_status = partial(get_mozharness_status, 'reftest')
+
+
+def test_output_pass(runtests):
+    status, lines = runtests('reftest-pass.list')
+    assert status == 0
+
+    tbpl_status, log_level = get_mozharness_status(lines, status)
+    assert tbpl_status == TBPL_SUCCESS
+    assert log_level in (INFO, WARNING)
+
+    test_end = filter_action('test_end', lines)
+    assert len(test_end) == 3
+    assert all(t['status'] == 'PASS' for t in test_end)
+
+
+def test_output_fail(runtests):
+    status, lines = runtests('reftest-fail.list')
+    assert status == 0
+
+    tbpl_status, log_level = get_mozharness_status(lines, status)
+    assert tbpl_status == TBPL_WARNING
+    assert log_level == WARNING
+
+    test_end = filter_action('test_end', lines)
+    assert len(test_end) == 3
+    assert all(t['status'] == 'FAIL' for t in test_end)
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -1338,31 +1338,31 @@ nsSliderFrame::ShouldScrollForEvent(Widg
 
 bool
 nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
 {
   if (!ShouldScrollForEvent(aEvent)) {
     return false;
   }
 
-  if (aEvent->mMessage == eTouchStart) {
-    return GetScrollToClick();
-  }
-
-  if (aEvent->mMessage != eMouseDown) {
+  if (aEvent->mMessage != eMouseDown && aEvent->mMessage != eTouchStart) {
     return false;
   }
 
 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
   // On Mac and Linux, clicking the scrollbar thumb should never scroll to click.
   if (IsEventOverThumb(aEvent)) {
     return false;
   }
 #endif
 
+  if (aEvent->mMessage == eTouchStart) {
+    return GetScrollToClick();
+  }
+
   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
   if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
 #ifdef XP_MACOSX
     bool invertPref = mouseEvent->IsAlt();
 #else
     bool invertPref = mouseEvent->IsShift();
 #endif
     return GetScrollToClick() != invertPref;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1884,25 +1884,28 @@ public abstract class GeckoApp extends G
         return prefs.getString(GeckoPreferences.PREFS_RESTORE_SESSION, "always");
     }
 
     private boolean getRestartFromIntent() {
         return IntentUtils.getBooleanExtraSafe(getIntent(), "didRestart", false);
     }
 
     /**
-     * Enable Android StrictMode checks (for supported OS versions).
+     * Enable Android StrictMode checks.
      * http://developer.android.com/reference/android/os/StrictMode.html
      */
     private void enableStrictMode() {
         Log.d(LOGTAG, "Enabling Android StrictMode");
 
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                                   .detectAll()
                                   .penaltyLog()
+                                  // Match Android's default configuration - which we use on
+                                  // automation builds, including release - for network access.
+                                  .penaltyDeathOnNetwork()
                                   .build());
 
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                                .detectAll()
                                .penaltyLog()
                                .build());
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java
@@ -42,16 +42,18 @@ import java.util.Map;
     private static final String KEY_HAS_IMAGE = "hasImage";
     private static final String KEY_METADATA_JSON = "metadataJSON";
 
     private static final int MAX_METADATA_QUEUE_SIZE = 15;
 
     private final Map<String, GeckoBundle> queuedMetadata =
             Collections.synchronizedMap(new LimitedLinkedHashMap<String, GeckoBundle>());
 
+    private boolean initialized;
+
     public static GlobalPageMetadata getInstance() {
         return instance;
     }
 
     private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
         private static final long serialVersionUID = 6359725112736360244L;
 
         @Override
@@ -62,19 +64,25 @@ import java.util.Map;
             }
             return false;
         }
     }
 
     private GlobalPageMetadata() {}
 
     public void init() {
+        if (initialized) {
+            return;
+        }
+
         EventDispatcher
                 .getInstance()
                 .registerBackgroundThreadListener(this, GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY);
+
+        initialized = true;
     }
 
     public void add(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
         ThreadUtils.assertOnBackgroundThread();
 
         // NB: Other than checking that JSON is valid and trimming it,
         // we do not process metadataJSON in any way, trusting our source.
         doAddOrQueue(db, contentProviderClient, uri, hasImage, metadataJSON);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -363,16 +363,17 @@ pref("media.use-blank-decoder", false);
 pref("media.wmf.enabled", true);
 pref("media.wmf.decoder.thread-count", -1);
 pref("media.wmf.low-latency.enabled", false);
 pref("media.wmf.skip-blacklist", false);
 pref("media.wmf.vp9.enabled", true);
 pref("media.wmf.amd.vp9.enabled", true);
 pref("media.wmf.allow-unsupported-resolutions", false);
 pref("media.windows-media-foundation.allow-d3d11-dxva", true);
+pref("media.windows-media-foundation.use-nv12-format", true);
 pref("media.wmf.disable-d3d11-for-dlls", "igd11dxva64.dll: 20.19.15.4463, 20.19.15.4454, 20.19.15.4444, 20.19.15.4416, 20.19.15.4404, 20.19.15.4390, 20.19.15.4380, 20.19.15.4377, 20.19.15.4364, 20.19.15.4360, 20.19.15.4352, 20.19.15.4331, 20.19.15.4326, 20.19.15.4300; igd10iumd32.dll: 20.19.15.4444, 20.19.15.4424, 20.19.15.4409, 20.19.15.4390, 20.19.15.4380, 20.19.15.4360, 10.18.10.4358, 20.19.15.4331, 20.19.15.4312, 20.19.15.4300, 10.18.15.4281, 10.18.15.4279, 10.18.10.4276, 10.18.15.4268, 10.18.15.4256, 10.18.10.4252, 10.18.15.4248, 10.18.14.4112, 10.18.10.3958, 10.18.10.3496, 10.18.10.3431, 10.18.10.3412, 10.18.10.3355, 9.18.10.3234, 9.18.10.3071, 9.18.10.3055, 9.18.10.3006; igd10umd32.dll: 9.17.10.4229, 9.17.10.3040, 9.17.10.2884, 9.17.10.2857, 8.15.10.2274, 8.15.10.2272, 8.15.10.2246, 8.15.10.1840, 8.15.10.1808; igd10umd64.dll: 9.17.10.4229, 9.17.10.2884, 9.17.10.2857, 10.18.10.3496; isonyvideoprocessor.dll: 4.1.2247.8090, 4.1.2153.6200; tosqep.dll: 1.2.15.526, 1.1.12.201, 1.0.11.318, 1.0.11.215, 1.0.10.1224; tosqep64.dll: 1.1.12.201, 1.0.11.215; nvwgf2um.dll: 22.21.13.8253, 22.21.13.8233, 22.21.13.8205, 22.21.13.8189, 22.21.13.8178, 22.21.13.8165, 21.21.13.7892, 21.21.13.7878, 21.21.13.7866, 21.21.13.7849, 21.21.13.7654, 21.21.13.7653, 21.21.13.7633, 21.21.13.7619, 21.21.13.7563, 21.21.13.7306, 21.21.13.7290, 21.21.13.7270, 21.21.13.7254, 21.21.13.6939, 21.21.13.6926, 21.21.13.6909, 21.21.13.4201, 21.21.13.4200, 10.18.13.6881, 10.18.13.6839, 10.18.13.6510, 10.18.13.6472, 10.18.13.6143, 10.18.13.5946, 10.18.13.5923, 10.18.13.5921, 10.18.13.5891, 10.18.13.5887, 10.18.13.5582, 10.18.13.5445, 10.18.13.5382, 10.18.13.5362, 9.18.13.4788, 9.18.13.4752, 9.18.13.4725, 9.18.13.4709, 9.18.13.4195, 9.18.13.4192, 9.18.13.4144, 9.18.13.4052, 9.18.13.3788, 9.18.13.3523, 9.18.13.3235, 9.18.13.3165, 9.18.13.2723, 9.18.13.2702, 9.18.13.1422, 9.18.13.1407, 9.18.13.1106, 9.18.13.546; atidxx32.dll: 21.19.151.3, 21.19.142.257, 21.19.137.514, 21.19.137.1, 21.19.134.1, 21.19.128.7, 21.19.128.4, 20.19.0.32837, 20.19.0.32832, 8.17.10.682, 8.17.10.671, 8.17.10.661, 8.17.10.648, 8.17.10.644, 8.17.10.625, 8.17.10.605, 8.17.10.581, 8.17.10.569, 8.17.10.560, 8.17.10.545, 8.17.10.539, 8.17.10.531, 8.17.10.525, 8.17.10.520, 8.17.10.519, 8.17.10.514, 8.17.10.511, 8.17.10.494, 8.17.10.489, 8.17.10.483, 8.17.10.453, 8.17.10.451, 8.17.10.441, 8.17.10.436, 8.17.10.432, 8.17.10.425, 8.17.10.418, 8.17.10.414, 8.17.10.401, 8.17.10.395, 8.17.10.385, 8.17.10.378, 8.17.10.362, 8.17.10.355, 8.17.10.342, 8.17.10.331, 8.17.10.318, 8.17.10.310, 8.17.10.286, 8.17.10.269, 8.17.10.261, 8.17.10.247, 8.17.10.240, 8.15.10.212; atidxx64.dll: 21.19.151.3, 21.19.142.257, 21.19.137.514, 21.19.137.1, 21.19.134.1, 21.19.128.7, 21.19.128.4, 20.19.0.32832, 8.17.10.682, 8.17.10.661, 8.17.10.644, 8.17.10.625; nvumdshim.dll: 10.18.13.6822");
 pref("media.wmf.disable-d3d9-for-dlls", "igdumd64.dll: 8.15.10.2189, 8.15.10.2119, 8.15.10.2104, 8.15.10.2102, 8.771.1.0; atiumd64.dll: 7.14.10.833, 7.14.10.867, 7.14.10.885, 7.14.10.903, 7.14.10.911, 8.14.10.768, 9.14.10.1001, 9.14.10.1017, 9.14.10.1080, 9.14.10.1128, 9.14.10.1162, 9.14.10.1171, 9.14.10.1183, 9.14.10.1197, 9.14.10.945, 9.14.10.972, 9.14.10.984, 9.14.10.996");
 pref("media.wmf.deblacklisting-for-telemetry-in-gpu-process", true);
 #endif
 #if defined(MOZ_FFMPEG)
 #if defined(XP_MACOSX)
 pref("media.ffmpeg.enabled", false);
 #else
@@ -668,16 +669,17 @@ pref("apz.autoscroll.enabled", true);
 pref("apz.axis_lock.mode", 0);
 pref("apz.axis_lock.lock_angle", "0.5235987");        // PI / 6 (30 degrees)
 pref("apz.axis_lock.breakout_threshold", "0.03125");  // 1/32 inches
 pref("apz.axis_lock.breakout_angle", "0.3926991");    // PI / 8 (22.5 degrees)
 pref("apz.axis_lock.direct_pan_angle", "1.047197");   // PI / 3 (60 degrees)
 pref("apz.content_response_timeout", 400);
 pref("apz.drag.enabled", true);
 pref("apz.drag.initial.enabled", true);
+pref("apz.drag.touch.enabled", true);
 pref("apz.danger_zone_x", 50);
 pref("apz.danger_zone_y", 100);
 pref("apz.disable_for_scroll_linked_effects", false);
 pref("apz.displayport_expiry_ms", 15000);
 pref("apz.enlarge_displayport_when_clipped", false);
 pref("apz.fling_accel_base_mult", "1.0");
 pref("apz.fling_accel_interval_ms", 500);
 pref("apz.fling_accel_min_velocity", "1.5");
--- a/moz.build
+++ b/moz.build
@@ -64,16 +64,17 @@ DIRS += [
     'taskcluster',
     'testing/mozbase',
     'third_party/python',
 ]
 
 if not CONFIG['JS_STANDALONE']:
     # These python manifests are included here so they get picked up without an objdir
     PYTHON_UNITTEST_MANIFESTS += [
+        'layout/tools/reftest/selftest/python.ini',
         'testing/marionette/harness/marionette_harness/tests/harness_unit/python.ini',
         'testing/mochitest/tests/python/python.ini',
     ]
 
     CONFIGURE_SUBST_FILES += [
         'tools/update-packaging/Makefile',
     ]
     CONFIGURE_DEFINE_FILES += [
--- a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -650,17 +650,17 @@ class TestChecksConfigure(unittest.TestC
         self.assertEqual(out, textwrap.dedent('''\
              checking for java... %s
              checking for javah... %s
              checking for jar... %s
              checking for jarsigner... %s
              checking for keytool... %s
              checking for javac... %s
              checking for javac version... 
-             ERROR: javac 1.8 or higher is required (found 1.6.9)
+             ERROR: javac 1.8 or higher is required (found 1.6.9). Check the JAVA_HOME environment variable.
         ''' % (java, javah, jar, jarsigner, keytool, javac)))
 
         # Any missing tool is fatal when these checks run.
         del paths[jarsigner]
         config, out, status = self.get_result(includes=includes,
                                               extra_paths=paths,
                                               environ={
                                                   'PATH': mozpath.dirname(java)
--- a/servo/components/script/dom/dedicatedworkerglobalscope.rs
+++ b/servo/components/script/dom/dedicatedworkerglobalscope.rs
@@ -242,17 +242,17 @@ impl DedicatedWorkerGlobalScope {
                 while let Ok(event) = global.receive_event() {
                     if scope.is_closing() {
                         break;
                     }
                     // Step 3
                     global.handle_event(event);
                     // Step 6
                     let _ar = AutoWorkerReset::new(&global, worker.clone());
-                    global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint();
+                    global.upcast::<GlobalScope>().perform_a_microtask_checkpoint();
                 }
             }, reporter_name, parent_sender, CommonScriptMsg::CollectReports);
         }).expect("Thread spawning failed");
     }
 
     pub fn script_chan(&self) -> Box<ScriptChan + Send> {
         box WorkerThreadWorkerChan {
             sender: self.own_sender.clone(),
--- a/servo/components/script/dom/dissimilaroriginwindow.rs
+++ b/servo/components/script/dom/dissimilaroriginwindow.rs
@@ -40,39 +40,46 @@ pub struct DissimilarOriginWindow {
     window_proxy: JS<WindowProxy>,
 
     /// The location of this window, initialized lazily.
     location: MutNullableJS<DissimilarOriginLocation>,
 }
 
 impl DissimilarOriginWindow {
     #[allow(unsafe_code)]
-    pub fn new(global_to_clone_from: &GlobalScope, window_proxy: &WindowProxy) -> Root<DissimilarOriginWindow> {
+    pub fn new(
+        global_to_clone_from: &GlobalScope,
+        window_proxy: &WindowProxy,
+    ) -> Root<Self> {
         let cx = global_to_clone_from.get_cx();
         // Any timer events fired on this window are ignored.
         let (timer_event_chan, _) = ipc::channel().unwrap();
-        let win = box DissimilarOriginWindow {
-            globalscope: GlobalScope::new_inherited(PipelineId::new(),
-                                                    global_to_clone_from.devtools_chan().cloned(),
-                                                    global_to_clone_from.mem_profiler_chan().clone(),
-                                                    global_to_clone_from.time_profiler_chan().clone(),
-                                                    global_to_clone_from.script_to_constellation_chan().clone(),
-                                                    global_to_clone_from.scheduler_chan().clone(),
-                                                    global_to_clone_from.resource_threads().clone(),
-                                                    timer_event_chan,
-                                                    global_to_clone_from.origin().clone()),
+        let win = box Self {
+            globalscope: GlobalScope::new_inherited(
+                PipelineId::new(),
+                global_to_clone_from.devtools_chan().cloned(),
+                global_to_clone_from.mem_profiler_chan().clone(),
+                global_to_clone_from.time_profiler_chan().clone(),
+                global_to_clone_from.script_to_constellation_chan().clone(),
+                global_to_clone_from.scheduler_chan().clone(),
+                global_to_clone_from.resource_threads().clone(),
+                timer_event_chan,
+                global_to_clone_from.origin().clone(),
+                // FIXME(nox): The microtask queue is probably not important
+                // here, but this whole DOM interface is a hack anyway.
+                global_to_clone_from.microtask_queue().clone(),
+            ),
             window_proxy: JS::from_ref(window_proxy),
-            location: MutNullableJS::new(None),
+            location: Default::default(),
         };
         unsafe { DissimilarOriginWindowBinding::Wrap(cx, win) }
     }
 
-    #[allow(dead_code)]
     pub fn origin(&self) -> &MutableOrigin {
-        self.globalscope.origin()
+        self.upcast::<GlobalScope>().origin()
     }
 }
 
 impl DissimilarOriginWindowMethods for DissimilarOriginWindow {
     // https://html.spec.whatwg.org/multipage/#dom-window
     fn Window(&self) -> Root<WindowProxy> {
         Root::from_ref(&*self.window_proxy)
     }
--- a/servo/components/script/dom/globalscope.rs
+++ b/servo/components/script/dom/globalscope.rs
@@ -28,29 +28,30 @@ use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_
 use js::glue::{IsWrapper, UnwrapObject};
 use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment};
 use js::jsapi::{HandleValue, Evaluate2, JSAutoCompartment, JSContext};
 use js::jsapi::{JSObject, JS_GetContext};
 use js::jsapi::{JS_GetObjectRuntime, MutableHandleValue};
 use js::panic::maybe_resume_unwind;
 use js::rust::{CompileOptionsWrapper, Runtime, get_object_class};
 use libc;
-use microtask::Microtask;
+use microtask::{Microtask, MicrotaskQueue};
 use msg::constellation_msg::PipelineId;
 use net_traits::{CoreResourceThread, ResourceThreads, IpcSend};
 use profile_traits::{mem, time};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort};
 use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread};
 use script_traits::{MsDuration, ScriptToConstellationChan, TimerEvent};
 use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource};
 use servo_url::{MutableOrigin, ServoUrl};
 use std::cell::Cell;
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::ffi::CString;
+use std::rc::Rc;
 use task_source::file_reading::FileReadingTaskSource;
 use task_source::networking::NetworkingTaskSource;
 use task_source::performance_timeline::PerformanceTimelineTaskSource;
 use time::{Timespec, get_time};
 use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
 use timers::{OneshotTimers, TimerCallback};
 
 #[dom_struct]
@@ -94,46 +95,57 @@ pub struct GlobalScope {
     /// Associated resource threads for use by DOM objects like XMLHttpRequest,
     /// including resource_thread, filemanager_thread and storage_thread
     resource_threads: ResourceThreads,
 
     timers: OneshotTimers,
 
     /// The origin of the globalscope
     origin: MutableOrigin,
+
+    /// The microtask queue associated with this global.
+    ///
+    /// It is refcounted because windows in the same script thread share the
+    /// same microtask queue.
+    ///
+    /// https://html.spec.whatwg.org/multipage/#microtask-queue
+    #[ignore_heap_size_of = "Rc<T> is hard"]
+    microtask_queue: Rc<MicrotaskQueue>,
 }
 
 impl GlobalScope {
     pub fn new_inherited(
-            pipeline_id: PipelineId,
-            devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
-            mem_profiler_chan: mem::ProfilerChan,
-            time_profiler_chan: time::ProfilerChan,
-            script_to_constellation_chan: ScriptToConstellationChan,
-            scheduler_chan: IpcSender<TimerSchedulerMsg>,
-            resource_threads: ResourceThreads,
-            timer_event_chan: IpcSender<TimerEvent>,
-            origin: MutableOrigin)
-            -> Self {
-        GlobalScope {
+        pipeline_id: PipelineId,
+        devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
+        mem_profiler_chan: mem::ProfilerChan,
+        time_profiler_chan: time::ProfilerChan,
+        script_to_constellation_chan: ScriptToConstellationChan,
+        scheduler_chan: IpcSender<TimerSchedulerMsg>,
+        resource_threads: ResourceThreads,
+        timer_event_chan: IpcSender<TimerEvent>,
+        origin: MutableOrigin,
+        microtask_queue: Rc<MicrotaskQueue>,
+    ) -> Self {
+        Self {
             eventtarget: EventTarget::new_inherited(),
             crypto: Default::default(),
             next_worker_id: Cell::new(WorkerId(0)),
-            pipeline_id: pipeline_id,
+            pipeline_id,
             devtools_wants_updates: Default::default(),
             console_timers: DOMRefCell::new(Default::default()),
-            devtools_chan: devtools_chan,
-            mem_profiler_chan: mem_profiler_chan,
-            time_profiler_chan: time_profiler_chan,
-            script_to_constellation_chan: script_to_constellation_chan,
+            devtools_chan,
+            mem_profiler_chan,
+            time_profiler_chan,
+            script_to_constellation_chan,
             scheduler_chan: scheduler_chan.clone(),
             in_error_reporting_mode: Default::default(),
-            resource_threads: resource_threads,
+            resource_threads,
             timers: OneshotTimers::new(timer_event_chan, scheduler_chan),
-            origin: origin,
+            origin,
+            microtask_queue,
         }
     }
 
     /// Returns the global scope of the realm that the given DOM object's reflector
     /// was created in.
     #[allow(unsafe_code)]
     pub fn from_reflector<T: DomObject>(reflector: &T) -> Root<Self> {
         unsafe { GlobalScope::from_object(*reflector.reflector().get_jsobject()) }
@@ -474,55 +486,42 @@ impl GlobalScope {
         if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
             return worker.get_runnable_wrapper();
         }
         unreachable!();
     }
 
     /// Perform a microtask checkpoint.
     pub fn perform_a_microtask_checkpoint(&self) {
-        if self.is::<Window>() {
-            return ScriptThread::invoke_perform_a_microtask_checkpoint();
-        }
-        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
-            return worker.perform_a_microtask_checkpoint();
-        }
-        if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
-            return worker.perform_a_microtask_checkpoint();
-        }
-        unreachable!();
+        self.microtask_queue.checkpoint(|_| Some(Root::from_ref(self)));
     }
 
     /// Enqueue a microtask for subsequent execution.
     pub fn enqueue_microtask(&self, job: Microtask) {
-        if self.is::<Window>() {
-            return ScriptThread::enqueue_microtask(job);
-        }
-        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
-            return worker.enqueue_microtask(job);
-        }
-        if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
-            return worker.enqueue_microtask(job);
-        }
-        unreachable!();
+        self.microtask_queue.enqueue(job);
     }
 
     /// Create a new sender/receiver pair that can be used to implement an on-demand
     /// event loop. Used for implementing web APIs that require blocking semantics
     /// without resorting to nested event loops.
     pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
         if let Some(window) = self.downcast::<Window>() {
             return window.new_script_pair();
         }
         if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
             return worker.new_script_pair();
         }
         unreachable!();
     }
 
+    /// Returns the microtask queue of this global.
+    pub fn microtask_queue(&self) -> &Rc<MicrotaskQueue> {
+        &self.microtask_queue
+    }
+
     /// Process a single event as if it were the next event
     /// in the thread queue for this global scope.
     pub fn process_event(&self, msg: CommonScriptMsg) {
         if self.is::<Window>() {
             return ScriptThread::process_event(msg);
         }
         if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
             return worker.process_event(msg);
--- a/servo/components/script/dom/serviceworkerglobalscope.rs
+++ b/servo/components/script/dom/serviceworkerglobalscope.rs
@@ -214,17 +214,17 @@ impl ServiceWorkerGlobalScope {
                 // https://html.spec.whatwg.org/multipage/#event-loop-processing-model
                 // Step 1
                 while let Ok(event) = global.receive_event() {
                     // Step 3
                     if !global.handle_event(event) {
                         break;
                     }
                     // Step 6
-                    global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint();
+                    global.upcast::<GlobalScope>().perform_a_microtask_checkpoint();
                 }
             }, reporter_name, scope.script_chan(), CommonScriptMsg::CollectReports);
         }).expect("Thread spawning failed");
     }
 
     fn handle_event(&self, event: MixedMessage) -> bool {
         match event {
             MixedMessage::FromDevtools(msg) => {
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -54,16 +54,17 @@ use euclid::{Point2D, Vector2D, Rect, Si
 use fetch;
 use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use js::jsapi::{HandleObject, HandleValue, JSAutoCompartment, JSContext};
 use js::jsapi::{JS_GC, JS_GetRuntime};
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
 use layout_image::fetch_image_for_layout;
+use microtask::MicrotaskQueue;
 use msg::constellation_msg::{FrameType, PipelineId};
 use net_traits::{ResourceThreads, ReferrerPolicy};
 use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse};
 use net_traits::image_cache::{PendingImageId, PendingImageResponse};
 use net_traits::storage_thread::StorageType;
 use num_traits::ToPrimitive;
 use open;
 use profile_traits::mem::ProfilerChan as MemProfilerChan;
@@ -1784,115 +1785,117 @@ impl Window {
             .script_to_constellation_chan()
             .send(msg)
             .unwrap();
     }
 }
 
 impl Window {
     #[allow(unsafe_code)]
-    pub fn new(runtime: Rc<Runtime>,
-               script_chan: MainThreadScriptChan,
-               dom_task_source: DOMManipulationTaskSource,
-               user_task_source: UserInteractionTaskSource,
-               network_task_source: NetworkingTaskSource,
-               history_task_source: HistoryTraversalTaskSource,
-               file_task_source: FileReadingTaskSource,
-               performance_timeline_task_source: PerformanceTimelineTaskSource,
-               image_cache_chan: Sender<ImageCacheMsg>,
-               image_cache: Arc<ImageCache>,
-               resource_threads: ResourceThreads,
-               bluetooth_thread: IpcSender<BluetoothRequest>,
-               mem_profiler_chan: MemProfilerChan,
-               time_profiler_chan: TimeProfilerChan,
-               devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
-               constellation_chan: ScriptToConstellationChan,
-               control_chan: IpcSender<ConstellationControlMsg>,
-               scheduler_chan: IpcSender<TimerSchedulerMsg>,
-               timer_event_chan: IpcSender<TimerEvent>,
-               layout_chan: Sender<Msg>,
-               id: PipelineId,
-               parent_info: Option<(PipelineId, FrameType)>,
-               window_size: Option<WindowSizeData>,
-               origin: MutableOrigin,
-               navigation_start: u64,
-               navigation_start_precise: f64,
-               webgl_chan: WebGLChan,
-               webvr_chan: Option<IpcSender<WebVRMsg>>)
-               -> Root<Window> {
+    pub fn new(
+        runtime: Rc<Runtime>,
+        script_chan: MainThreadScriptChan,
+        dom_manipulation_task_source: DOMManipulationTaskSource,
+        user_interaction_task_source: UserInteractionTaskSource,
+        networking_task_source: NetworkingTaskSource,
+        history_traversal_task_source: HistoryTraversalTaskSource,
+        file_reading_task_source: FileReadingTaskSource,
+        performance_timeline_task_source: PerformanceTimelineTaskSource,
+        image_cache_chan: Sender<ImageCacheMsg>,
+        image_cache: Arc<ImageCache>,
+        resource_threads: ResourceThreads,
+        bluetooth_thread: IpcSender<BluetoothRequest>,
+        mem_profiler_chan: MemProfilerChan,
+        time_profiler_chan: TimeProfilerChan,
+        devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
+        constellation_chan: ScriptToConstellationChan,
+        control_chan: IpcSender<ConstellationControlMsg>,
+        scheduler_chan: IpcSender<TimerSchedulerMsg>,
+        timer_event_chan: IpcSender<TimerEvent>,
+        layout_chan: Sender<Msg>,
+        pipelineid: PipelineId,
+        parent_info: Option<(PipelineId, FrameType)>,
+        window_size: Option<WindowSizeData>,
+        origin: MutableOrigin,
+        navigation_start: u64,
+        navigation_start_precise: f64,
+        webgl_chan: WebGLChan,
+        webvr_chan: Option<IpcSender<WebVRMsg>>,
+        microtask_queue: Rc<MicrotaskQueue>,
+    ) -> Root<Self> {
         let layout_rpc: Box<LayoutRPC + Send> = {
             let (rpc_send, rpc_recv) = channel();
             layout_chan.send(Msg::GetRPC(rpc_send)).unwrap();
             rpc_recv.recv().unwrap()
         };
         let error_reporter = CSSErrorReporter {
-            pipelineid: id,
+            pipelineid,
             script_chan: Arc::new(Mutex::new(control_chan)),
         };
-        let win = box Window {
-            globalscope:
-                GlobalScope::new_inherited(
-                    id,
-                    devtools_chan,
-                    mem_profiler_chan,
-                    time_profiler_chan,
-                    constellation_chan,
-                    scheduler_chan,
-                    resource_threads,
-                    timer_event_chan,
-                    origin),
-            script_chan: script_chan,
-            dom_manipulation_task_source: dom_task_source,
-            user_interaction_task_source: user_task_source,
-            networking_task_source: network_task_source,
-            history_traversal_task_source: history_task_source,
-            file_reading_task_source: file_task_source,
+        let win = box Self {
+            globalscope: GlobalScope::new_inherited(
+                pipelineid,
+                devtools_chan,
+                mem_profiler_chan,
+                time_profiler_chan,
+                constellation_chan,
+                scheduler_chan,
+                resource_threads,
+                timer_event_chan,
+                origin,
+                microtask_queue,
+            ),
+            script_chan,
+            dom_manipulation_task_source,
+            user_interaction_task_source,
+            networking_task_source,
+            history_traversal_task_source,
+            file_reading_task_source,
             performance_timeline_task_source,
-            image_cache_chan: image_cache_chan,
-            image_cache: image_cache.clone(),
+            image_cache_chan,
+            image_cache,
             navigator: Default::default(),
             history: Default::default(),
             custom_element_registry: Default::default(),
             window_proxy: Default::default(),
             document: Default::default(),
             performance: Default::default(),
             navigation_start: Cell::new(navigation_start),
             navigation_start_precise: Cell::new(navigation_start_precise),
             screen: Default::default(),
             session_storage: Default::default(),
             local_storage: Default::default(),
             status: DOMRefCell::new(DOMString::new()),
-            parent_info: parent_info,
+            parent_info,
             dom_static: GlobalStaticData::new(),
             js_runtime: DOMRefCell::new(Some(runtime.clone())),
-            bluetooth_thread: bluetooth_thread,
+            bluetooth_thread,
             bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(),
             page_clip_rect: Cell::new(max_rect()),
-            resize_event: Cell::new(None),
-            layout_chan: layout_chan,
-            layout_rpc: layout_rpc,
+            resize_event: Default::default(),
+            layout_chan,
+            layout_rpc,
             window_size: Cell::new(window_size),
             current_viewport: Cell::new(Rect::zero()),
             suppress_reflow: Cell::new(true),
-            pending_reflow_count: Cell::new(0),
+            pending_reflow_count: Default::default(),
             current_state: Cell::new(WindowState::Alive),
-
-            devtools_marker_sender: DOMRefCell::new(None),
-            devtools_markers: DOMRefCell::new(HashSet::new()),
-            webdriver_script_chan: DOMRefCell::new(None),
+            devtools_marker_sender: Default::default(),
+            devtools_markers: Default::default(),
+            webdriver_script_chan: Default::default(),
             ignore_further_async_events: Default::default(),
-            error_reporter: error_reporter,
-            scroll_offsets: DOMRefCell::new(HashMap::new()),
+            error_reporter,
+            scroll_offsets: Default::default(),
             media_query_lists: WeakMediaQueryListVec::new(),
             test_runner: Default::default(),
-            webgl_chan: webgl_chan,
-            webvr_chan: webvr_chan,
-            permission_state_invocation_results: DOMRefCell::new(HashMap::new()),
-            pending_layout_images: DOMRefCell::new(HashMap::new()),
-            unminified_js_dir: DOMRefCell::new(None),
+            webgl_chan,
+            webvr_chan,
+            permission_state_invocation_results: Default::default(),
+            pending_layout_images: Default::default(),
+            unminified_js_dir: Default::default(),
             test_worklet: Default::default(),
             paint_worklet: Default::default(),
         };
 
         unsafe {
             WindowBinding::Wrap(runtime.cx(), win)
         }
     }
--- a/servo/components/script/dom/workerglobalscope.rs
+++ b/servo/components/script/dom/workerglobalscope.rs
@@ -25,17 +25,16 @@ use dom::workerlocation::WorkerLocation;
 use dom::workernavigator::WorkerNavigator;
 use dom_struct::dom_struct;
 use fetch;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, JSRuntime};
 use js::jsval::UndefinedValue;
 use js::panic::maybe_resume_unwind;
 use js::rust::Runtime;
-use microtask::{MicrotaskQueue, Microtask};
 use net_traits::{IpcSend, load_whole_resource};
 use net_traits::request::{CredentialsMode, Destination, RequestInit as NetRequestInit, Type as RequestType};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort};
 use script_thread::RunnableWrapper;
 use script_traits::{TimerEvent, TimerEventId};
 use script_traits::WorkerGlobalScopeInit;
 use servo_url::{MutableOrigin, ServoUrl};
 use std::default::Default;
@@ -86,51 +85,50 @@ pub struct WorkerGlobalScope {
     /// to the server from within the worker
     from_devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>,
 
     #[ignore_heap_size_of = "Defined in std"]
     /// This `Receiver` will be ignored later if the corresponding
     /// `IpcSender` doesn't exist
     from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
 
-    microtask_queue: MicrotaskQueue,
-
     navigation_start_precise: f64,
     performance: MutNullableJS<Performance>,
 }
 
 impl WorkerGlobalScope {
-    pub fn new_inherited(init: WorkerGlobalScopeInit,
-                         worker_url: ServoUrl,
-                         runtime: Runtime,
-                         from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
-                         timer_event_chan: IpcSender<TimerEvent>,
-                         closing: Option<Arc<AtomicBool>>)
-                         -> WorkerGlobalScope {
-        WorkerGlobalScope {
-            globalscope:
-                GlobalScope::new_inherited(
-                    init.pipeline_id,
-                    init.to_devtools_sender,
-                    init.mem_profiler_chan,
-                    init.time_profiler_chan,
-                    init.script_to_constellation_chan,
-                    init.scheduler_chan,
-                    init.resource_threads,
-                    timer_event_chan,
-                    MutableOrigin::new(init.origin)),
+    pub fn new_inherited(
+        init: WorkerGlobalScopeInit,
+        worker_url: ServoUrl,
+        runtime: Runtime,
+        from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
+        timer_event_chan: IpcSender<TimerEvent>,
+        closing: Option<Arc<AtomicBool>>,
+    ) -> Self {
+        Self {
+            globalscope: GlobalScope::new_inherited(
+                init.pipeline_id,
+                init.to_devtools_sender,
+                init.mem_profiler_chan,
+                init.time_profiler_chan,
+                init.script_to_constellation_chan,
+                init.scheduler_chan,
+                init.resource_threads,
+                timer_event_chan,
+                MutableOrigin::new(init.origin),
+                Default::default(),
+            ),
             worker_id: init.worker_id,
-            worker_url: worker_url,
-            closing: closing,
-            runtime: runtime,
+            worker_url,
+            closing,
+            runtime,
             location: Default::default(),
             navigator: Default::default(),
             from_devtools_sender: init.from_devtools_sender,
-            from_devtools_receiver: from_devtools_receiver,
-            microtask_queue: MicrotaskQueue::default(),
+            from_devtools_receiver,
             navigation_start_precise: precise_time_ns() as f64,
             performance: Default::default(),
         }
     }
 
     pub fn from_devtools_sender(&self) -> Option<IpcSender<DevtoolScriptControlMsg>> {
         self.from_devtools_sender.clone()
     }
@@ -163,28 +161,16 @@ impl WorkerGlobalScope {
         self.worker_id.clone()
     }
 
     pub fn get_runnable_wrapper(&self) -> RunnableWrapper {
         RunnableWrapper {
             cancelled: self.closing.clone(),
         }
     }
-
-    pub fn enqueue_microtask(&self, job: Microtask) {
-        self.microtask_queue.enqueue(job);
-    }
-
-    pub fn perform_a_microtask_checkpoint(&self) {
-        self.microtask_queue.checkpoint(|id| {
-            let global = self.upcast::<GlobalScope>();
-            assert_eq!(global.pipeline_id(), id);
-            Some(Root::from_ref(global))
-        });
-    }
 }
 
 impl WorkerGlobalScopeMethods for WorkerGlobalScope {
     // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-self
     fn Self_(&self) -> Root<WorkerGlobalScope> {
         Root::from_ref(self)
     }
 
--- a/servo/components/script/dom/workletglobalscope.rs
+++ b/servo/components/script/dom/workletglobalscope.rs
@@ -12,18 +12,16 @@ use dom::testworkletglobalscope::TestWor
 use dom::testworkletglobalscope::TestWorkletTask;
 use dom::worklet::WorkletExecutor;
 use dom_struct::dom_struct;
 use ipc_channel::ipc;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::JSContext;
 use js::jsval::UndefinedValue;
 use js::rust::Runtime;
-use microtask::Microtask;
-use microtask::MicrotaskQueue;
 use msg::constellation_msg::PipelineId;
 use net_traits::ResourceThreads;
 use net_traits::image_cache::ImageCache;
 use profile_traits::mem;
 use profile_traits::time;
 use script_layout_interface::message::Msg;
 use script_runtime::CommonScriptMsg;
 use script_runtime::ScriptThreadEventCategory;
@@ -41,52 +39,53 @@ use std::sync::mpsc::Sender;
 
 #[dom_struct]
 /// https://drafts.css-houdini.org/worklets/#workletglobalscope
 pub struct WorkletGlobalScope {
     /// The global for this worklet.
     globalscope: GlobalScope,
     /// The base URL for this worklet.
     base_url: ServoUrl,
-    /// The microtask queue for this worklet
-    microtask_queue: MicrotaskQueue,
     /// Sender back to the script thread
     #[ignore_heap_size_of = "channels are hard"]
     to_script_thread_sender: Sender<MainThreadScriptMsg>,
     /// Worklet task executor
     executor: WorkletExecutor,
 }
 
 impl WorkletGlobalScope {
     /// Create a new stack-allocated `WorkletGlobalScope`.
-    pub fn new_inherited(pipeline_id: PipelineId,
-                         base_url: ServoUrl,
-                         executor: WorkletExecutor,
-                         init: &WorkletGlobalScopeInit)
-                         -> WorkletGlobalScope {
+    pub fn new_inherited(
+        pipeline_id: PipelineId,
+        base_url: ServoUrl,
+        executor: WorkletExecutor,
+        init: &WorkletGlobalScopeInit,
+    ) -> Self {
         // Any timer events fired on this global are ignored.
         let (timer_event_chan, _) = ipc::channel().unwrap();
         let script_to_constellation_chan = ScriptToConstellationChan {
             sender: init.to_constellation_sender.clone(),
-            pipeline_id: pipeline_id,
+            pipeline_id,
         };
-        WorkletGlobalScope {
-            globalscope: GlobalScope::new_inherited(pipeline_id,
-                                                    init.devtools_chan.clone(),
-                                                    init.mem_profiler_chan.clone(),
-                                                    init.time_profiler_chan.clone(),
-                                                    script_to_constellation_chan,
-                                                    init.scheduler_chan.clone(),
-                                                    init.resource_threads.clone(),
-                                                    timer_event_chan,
-                                                    MutableOrigin::new(ImmutableOrigin::new_opaque())),
-            base_url: base_url,
-            microtask_queue: MicrotaskQueue::default(),
+        Self {
+            globalscope: GlobalScope::new_inherited(
+                pipeline_id,
+                init.devtools_chan.clone(),
+                init.mem_profiler_chan.clone(),
+                init.time_profiler_chan.clone(),
+                script_to_constellation_chan,
+                init.scheduler_chan.clone(),
+                init.resource_threads.clone(),
+                timer_event_chan,
+                MutableOrigin::new(ImmutableOrigin::new_opaque()),
+                Default::default(),
+            ),
+            base_url,
             to_script_thread_sender: init.to_script_thread_sender.clone(),
-            executor: executor,
+            executor,
         }
     }
 
     /// Get the JS context.
     pub fn get_cx(&self) -> *mut JSContext {
         self.globalscope.get_cx()
     }
 
@@ -123,30 +122,16 @@ impl WorkletGlobalScope {
         self.base_url.clone()
     }
 
     /// The worklet executor.
     pub fn executor(&self) -> WorkletExecutor {
         self.executor.clone()
     }
 
-    /// Queue up a microtask to be executed in this global.
-    pub fn enqueue_microtask(&self, job: Microtask) {
-        self.microtask_queue.enqueue(job);
-    }
-
-    /// Perform any queued microtasks.
-    pub fn perform_a_microtask_checkpoint(&self) {
-        self.microtask_queue.checkpoint(|id| {
-            let global = self.upcast::<GlobalScope>();
-            assert_eq!(global.pipeline_id(), id);
-            Some(Root::from_ref(global))
-        });
-    }
-
     /// Perform a worklet task
     pub fn perform_a_worklet_task(&self, task: WorkletTask) {
         match task {
             WorkletTask::Test(task) => match self.downcast::<TestWorkletGlobalScope>() {
                 Some(global) => global.perform_a_worklet_task(task),
                 None => warn!("This is not a test worklet."),
             },
             WorkletTask::Paint(task) => match self.downcast::<PaintWorkletGlobalScope>() {
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -510,17 +510,18 @@ pub struct ScriptThread {
     closed_pipelines: DOMRefCell<HashSet<PipelineId>>,
 
     scheduler_chan: IpcSender<TimerSchedulerMsg>,
     timer_event_chan: Sender<TimerEvent>,
     timer_event_port: Receiver<TimerEvent>,
 
     content_process_shutdown_chan: IpcSender<()>,
 
-    microtask_queue: MicrotaskQueue,
+    /// https://html.spec.whatwg.org/multipage/#microtask-queue
+    microtask_queue: Rc<MicrotaskQueue>,
 
     /// Microtask Queue for adding support for mutation observer microtasks
     mutation_observer_compound_microtask_queued: Cell<bool>,
 
     /// The unit of related similar-origin browsing contexts' list of MutationObserver objects
     mutation_observers: DOMRefCell<Vec<JS<MutationObserver>>>,
 
     /// A handle to the webgl thread
@@ -838,17 +839,17 @@ impl ScriptThread {
         let (ipc_devtools_sender, ipc_devtools_receiver) = ipc::channel().unwrap();
         let devtools_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_devtools_receiver);
 
         let (timer_event_chan, timer_event_port) = channel();
 
         // Ask the router to proxy IPC messages from the control port to us.
         let control_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(state.control_port);
 
-        let boxed_script_sender = MainThreadScriptChan(chan.clone()).clone();
+        let boxed_script_sender = box MainThreadScriptChan(chan.clone());
 
         let (image_cache_channel, image_cache_port) = channel();
 
         ScriptThread {
             documents: DOMRefCell::new(Documents::new()),
             window_proxies: DOMRefCell::new(HashMap::new()),
             incomplete_loads: DOMRefCell::new(vec!()),
             incomplete_parser_contexts: DOMRefCell::new(vec!()),
@@ -887,17 +888,17 @@ impl ScriptThread {
             closed_pipelines: DOMRefCell::new(HashSet::new()),
 
             scheduler_chan: state.scheduler_chan,
             timer_event_chan: timer_event_chan,
             timer_event_port: timer_event_port,
 
             content_process_shutdown_chan: state.content_process_shutdown_chan,
 
-            microtask_queue: MicrotaskQueue::default(),
+            microtask_queue: Default::default(),
 
             mutation_observer_compound_microtask_queued: Default::default(),
 
             mutation_observers: Default::default(),
 
             layout_to_constellation_chan: state.layout_to_constellation_chan,
 
             webgl_chan: state.webgl_chan,
@@ -2033,44 +2034,47 @@ impl ScriptThread {
         };
 
         let script_to_constellation_chan = ScriptToConstellationChan {
             sender: self.script_sender.clone(),
             pipeline_id: incomplete.pipeline_id,
         };
 
         // Create the window and document objects.
-        let window = Window::new(self.js_runtime.clone(),
-                                 MainThreadScriptChan(sender.clone()),
-                                 DOMManipulationTaskSource(dom_sender.clone()),
-                                 UserInteractionTaskSource(user_sender.clone()),
-                                 self.networking_task_source.clone(),
-                                 HistoryTraversalTaskSource(history_sender.clone()),
-                                 self.file_reading_task_source.clone(),
-                                 self.performance_timeline_task_source.clone(),
-                                 self.image_cache_channel.clone(),
-                                 self.image_cache.clone(),
-                                 self.resource_threads.clone(),
-                                 self.bluetooth_thread.clone(),
-                                 self.mem_profiler_chan.clone(),
-                                 self.time_profiler_chan.clone(),
-                                 self.devtools_chan.clone(),
-                                 script_to_constellation_chan,
-                                 self.control_chan.clone(),
-                                 self.scheduler_chan.clone(),
-                                 ipc_timer_event_chan,
-                                 incomplete.layout_chan,
-                                 incomplete.pipeline_id,
-                                 incomplete.parent_info,
-                                 incomplete.window_size,
-                                 origin,
-                                 incomplete.navigation_start,
-                                 incomplete.navigation_start_precise,
-                                 self.webgl_chan.channel(),
-                                 self.webvr_chan.clone());
+        let window = Window::new(
+            self.js_runtime.clone(),
+            MainThreadScriptChan(sender.clone()),
+            DOMManipulationTaskSource(dom_sender.clone()),
+            UserInteractionTaskSource(user_sender.clone()),
+            self.networking_task_source.clone(),
+            HistoryTraversalTaskSource(history_sender.clone()),
+            self.file_reading_task_source.clone(),
+            self.performance_timeline_task_source.clone(),
+            self.image_cache_channel.clone(),
+            self.image_cache.clone(),
+            self.resource_threads.clone(),
+            self.bluetooth_thread.clone(),
+            self.mem_profiler_chan.clone(),
+            self.time_profiler_chan.clone(),
+            self.devtools_chan.clone(),
+            script_to_constellation_chan,
+            self.control_chan.clone(),
+            self.scheduler_chan.clone(),
+            ipc_timer_event_chan,
+            incomplete.layout_chan,
+            incomplete.pipeline_id,
+            incomplete.parent_info,
+            incomplete.window_size,
+            origin,
+            incomplete.navigation_start,
+            incomplete.navigation_start_precise,
+            self.webgl_chan.channel(),
+            self.webvr_chan.clone(),
+            self.microtask_queue.clone(),
+        );
 
         // Initialize the browsing context for the window.
         let window_proxy = self.local_window_proxy(&window,
                                                    incomplete.browsing_context_id,
                                                    incomplete.top_level_browsing_context_id,
                                                    incomplete.parent_info);
         window.init_window_proxy(&window_proxy);
 
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -652,36 +652,28 @@ impl ToCss for PropertyDeclarationBlock 
                         shorthand == ShorthandId::Font &&
                         self.declarations.iter().any(|l| {
                             !already_serialized.contains(l.id()) &&
                             l.get_system().is_some()
                         });
 
                     if is_system_font {
                         for (longhand, importance) in self.declaration_importance_iter() {
-                            if already_serialized.contains(longhand.id()) {
-                                continue;
-                            }
-
                             if longhand.get_system().is_some() || longhand.is_default_line_height() {
                                 current_longhands.push(longhand);
                                 if found_system.is_none() {
                                    found_system = longhand.get_system();
                                 }
                                 if importance.important() {
                                     important_count += 1;
                                 }
                             }
                         }
                     } else {
                         for (longhand, importance) in self.declaration_importance_iter() {
-                            if already_serialized.contains(longhand.id()) {
-                                continue;
-                            }
-
                             if longhand.id().is_longhand_of(shorthand) {
                                 current_longhands.push(longhand);
                                 if importance.important() {
                                     important_count += 1;
                                 }
                             }
                         }
                         // Substep 1:
@@ -766,16 +758,23 @@ impl ToCss for PropertyDeclarationBlock 
                              importance,
                              &mut is_first_serialization)?;
                     }
 
                     for current_longhand in &current_longhands {
                         // Substep 9
                         already_serialized.insert(current_longhand.id());
                     }
+
+                    // FIXME(https://github.com/w3c/csswg-drafts/issues/1774)
+                    // The specification does not include an instruction to abort
+                    // the shorthand loop at this point, but doing so both matches
+                    // Gecko and makes sense since shorthands are checked in
+                    // preferred order.
+                    break;
                 }
             }
 
             // Step 3.3.4
             if already_serialized.contains(property) {
                 continue;
             }
 
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -471,25 +471,38 @@ impl LonghandId {
     fn shorthands(&self) -> &'static [ShorthandId] {
         // first generate longhand to shorthands lookup map
         //
         // NOTE(emilio): This currently doesn't exclude the "all" shorthand. It
         // could potentially do so, which would speed up serialization
         // algorithms and what not, I guess.
         <%
             longhand_to_shorthand_map = {}
+            num_sub_properties = {}
             for shorthand in data.shorthands:
+                num_sub_properties[shorthand.camel_case] = len(shorthand.sub_properties)
                 for sub_property in shorthand.sub_properties:
                     if sub_property.ident not in longhand_to_shorthand_map:
                         longhand_to_shorthand_map[sub_property.ident] = []
 
                     longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case)
 
+            def preferred_order(x, y):
+                # Since we want properties in order from most subproperties to least,
+                # reverse the arguments to cmp from the expected order.
+                result = cmp(num_sub_properties.get(y, 0), num_sub_properties.get(x, 0))
+                if result:
+                    return result
+                # Fall back to lexicographic comparison.
+                return cmp(x, y)
+
+            # Sort the lists of shorthand properties according to preferred order:
+            # https://drafts.csswg.org/cssom/#concept-shorthands-preferred-order
             for shorthand_list in longhand_to_shorthand_map.itervalues():
-                shorthand_list.sort()
+                shorthand_list.sort(cmp=preferred_order)
         %>
 
         // based on lookup results for each longhand, create result arrays
         % for property in data.longhands:
             static ${property.ident.upper()}: &'static [ShorthandId] = &[
                 % for shorthand in longhand_to_shorthand_map.get(property.ident, []):
                     ShorthandId::${shorthand},
                 % endfor
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -336,16 +336,63 @@ mod shorthand_serialization {
             assert_eq!(serialization, "border-radius: 1% 2% 3% 4% / 5% 6% 7% 8%;");
         }
     }
 
 
     mod border_shorthands {
         use super::*;
 
+        #[test]
+        fn border_top_and_color() {
+            let mut properties = Vec::new();
+            properties.push(PropertyDeclaration::BorderTopWidth(BorderSideWidth::Length(Length::from_px(1.))));
+            properties.push(PropertyDeclaration::BorderTopStyle(BorderStyle::solid));
+            let c = Color::Numeric {
+                parsed: RGBA::new(255, 0, 0, 255),
+                authored: Some("green".to_string().into_boxed_str())
+            };
+            properties.push(PropertyDeclaration::BorderTopColor(c));
+            let c = Color::Numeric {
+                parsed: RGBA::new(0, 255, 0, 255),
+                authored: Some("red".to_string().into_boxed_str())
+            };
+            properties.push(PropertyDeclaration::BorderTopColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderBottomColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderLeftColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderRightColor(c.clone()));
+
+            let serialization = shorthand_properties_to_string(properties);
+            assert_eq!(serialization, "border-top: 1px solid red; border-color: red;");
+        }
+
+        #[test]
+        fn border_color_and_top() {
+            let mut properties = Vec::new();
+                let c = Color::Numeric {
+                parsed: RGBA::new(0, 255, 0, 255),
+                authored: Some("red".to_string().into_boxed_str())
+            };
+            properties.push(PropertyDeclaration::BorderTopColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderBottomColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderLeftColor(c.clone()));
+            properties.push(PropertyDeclaration::BorderRightColor(c.clone()));
+
+            properties.push(PropertyDeclaration::BorderTopWidth(BorderSideWidth::Length(Length::from_px(1.))));
+            properties.push(PropertyDeclaration::BorderTopStyle(BorderStyle::solid));
+            let c = Color::Numeric {
+                parsed: RGBA::new(255, 0, 0, 255),
+                authored: Some("green".to_string().into_boxed_str())
+            };
+            properties.push(PropertyDeclaration::BorderTopColor(c));
+
+            let serialization = shorthand_properties_to_string(properties);
+            assert_eq!(serialization, "border-color: green red red; border-top: 1px solid green;");
+        }
+
         // we can use border-top as a base to test out the different combinations
         // but afterwards, we only need to to one test per "directional border shorthand"
 
         #[test]
         fn directional_border_should_show_all_properties_when_values_are_set() {
             let mut properties = Vec::new();
 
             let width = BorderSideWidth::Length(Length::from_px(4f32));
--- a/taskcluster/ci/source-test/python.yml
+++ b/taskcluster/ci/source-test/python.yml
@@ -70,16 +70,17 @@ mochitest-harness:
             start_xvfb '1600x1200x24' 0 &&
             cd /builds/worker/checkouts/gecko &&
             ./mach python-test --subsuite mochitest
     when:
         files-changed:
             - 'config/mozunit.py'
             - 'python/mach_commands.py'
             - 'testing/mochitest/**'
+            - 'testing/mozbase/moztest/moztest/selftest/**'
             - 'testing/mozharness/mozharness/base/log.py'
             - 'testing/mozharness/mozharness/mozilla/structuredlog.py'
             - 'testing/mozharness/mozharness/mozilla/testing/errors.py'
             - 'testing/profiles/prefs_general.js'
 
 mozbase:
     description: testing/mozbase unit tests
     platform:
@@ -143,8 +144,42 @@ mozlint:
                 max-run-time: 3600
     run:
         using: mach
         mach: python-test --subsuite mozlint
     when:
         files-changed:
             - 'python/mozlint/**'
             - 'python/mach_commands.py'
+
+reftest-harness:
+    description: layout/tools/reftest unittests
+    platform:
+        - linux64/opt
+    require-build: true
+    treeherder:
+        symbol: py(ref)
+        kind: test
+        tier: 2
+    worker-type:
+        by-platform:
+            linux64.*: aws-provisioner-v1/gecko-t-linux-xlarge
+    worker:
+        by-platform:
+            linux64.*:
+                docker-image: {in-tree: "desktop1604-test"}
+                max-run-time: 3600
+    run:
+        using: run-task
+        command: >
+            source /builds/worker/scripts/xvfb.sh &&
+            start_xvfb '1600x1200x24' 0 &&
+            cd /builds/worker/checkouts/gecko &&
+            ./mach python-test --subsuite reftest
+    when:
+        files-changed:
+            - 'config/mozunit.py'
+            - 'layout/tools/reftest/**'
+            - 'python/mach_commands.py'
+            - 'testing/mozbase/moztest/moztest/selftest/**'
+            - 'testing/mozharness/mozharness/base/log.py'
+            - 'testing/mozharness/mozharness/mozilla/structuredlog.py'
+            - 'testing/mozharness/mozharness/mozilla/testing/errors.py'
--- a/testing/mochitest/tests/python/conftest.py
+++ b/testing/mochitest/tests/python/conftest.py
@@ -1,144 +1,45 @@
 # 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/.
 
 from __future__ import print_function, unicode_literals
-
 import json
 import os
-import shutil
-import sys
 from argparse import Namespace
 from cStringIO import StringIO
 
 import pytest
-import requests
 
-import mozfile
 import mozinfo
-import mozinstall
 from manifestparser import TestManifest, expression
-from mozbuild.base import MozbuildObject
+from moztest.selftest.fixtures import binary, setup_test_harness  # noqa
 
 here = os.path.abspath(os.path.dirname(__file__))
-build = MozbuildObject.from_environment(cwd=here)
-
-HARNESS_ROOT_NOT_FOUND = """
-Could not find test harness root. Either a build or the 'GECKO_INSTALLER_URL'
-environment variable is required.
-""".lstrip()
-
-
-def filter_action(actions, lines):
-    if isinstance(actions, basestring):
-        actions = (actions,)
-    return filter(lambda x: x['action'] in actions, lines)
-
-
-def _get_harness_root():
-    # Check if there is a local build
-    harness_root = os.path.join(build.topobjdir, '_tests', 'testing', 'mochitest')
-    if os.path.isdir(harness_root):
-        return harness_root
-
-    # Check if it was previously set up by another test
-    harness_root = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests', 'mochitest')
-    if os.path.isdir(harness_root):
-        return harness_root
-
-    # Check if there is a test package to download
-    if 'GECKO_INSTALLER_URL' in os.environ:
-        base_url = os.environ['GECKO_INSTALLER_URL'].rsplit('/', 1)[0]
-        test_packages = requests.get(base_url + '/target.test_packages.json').json()
-
-        dest = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests')
-        for name in test_packages['mochitest']:
-            url = base_url + '/' + name
-            bundle = os.path.join(os.environ['PYTHON_TEST_TMP'], name)
-
-            r = requests.get(url, stream=True)
-            with open(bundle, 'w+b') as fh:
-                for chunk in r.iter_content(chunk_size=1024):
-                    fh.write(chunk)
-
-            mozfile.extract(bundle, dest)
-
-        return os.path.join(dest, 'mochitest')
-
-    # Couldn't find a harness root, let caller do error handling.
-    return None
-
-
-@pytest.fixture(scope='session')
-def setup_harness_root():
-    harness_root = _get_harness_root()
-    if harness_root:
-        sys.path.insert(0, harness_root)
-
-        # Link the test files to the test package so updates are automatically
-        # picked up. Fallback to copy on Windows.
-        test_root = os.path.join(harness_root, 'tests', 'selftests')
-        if not os.path.exists(test_root):
-            files = os.path.join(here, 'files')
-            if hasattr(os, 'symlink'):
-                os.symlink(files, test_root)
-            else:
-                shutil.copytree(files, test_root)
-
-    elif 'GECKO_INSTALLER_URL' in os.environ:
-        # The mochitest tests will run regardless of whether a build exists or not.
-        # In a local environment, they should simply be skipped if setup fails. But
-        # in automation, we'll need to make sure an error is propagated up.
-        pytest.fail(HARNESS_ROOT_NOT_FOUND)
-    else:
-        # Tests will be marked skipped by the calls to pytest.importorskip() below.
-        # We are purposefully not failing here because running |mach python-test|
-        # without a build is a perfectly valid use case.
-        pass
-
-
-@pytest.fixture(scope='session')
-def binary():
-    try:
-        return build.get_binary_path()
-    except:
-        pass
-
-    app = 'firefox'
-    bindir = os.path.join(os.environ['PYTHON_TEST_TMP'], app)
-    if os.path.isdir(bindir):
-        try:
-            return mozinstall.get_binary(bindir, app_name=app)
-        except:
-            pass
-
-    if 'GECKO_INSTALLER_URL' in os.environ:
-        bindir = mozinstall.install(
-            os.environ['GECKO_INSTALLER_URL'], os.environ['PYTHON_TEST_TMP'])
-        return mozinstall.get_binary(bindir, app_name='firefox')
+setup_args = [os.path.join(here, 'files'), 'mochitest', 'testing/mochitest']
 
 
 @pytest.fixture(scope='function')
 def parser(request):
     parser = pytest.importorskip('mochitest_options')
 
     app = getattr(request.module, 'APP', 'generic')
     return parser.MochitestArgumentParser(app=app)
 
 
-@pytest.fixture(scope='function')
-def runtests(setup_harness_root, binary, parser, request):
+@pytest.fixture(scope='function')  # noqa: F811
+def runtests(setup_test_harness, binary, parser, request):
     """Creates an easy to use entry point into the mochitest harness.
 
     :returns: A function with the signature `*tests, **opts`. Each test is a file name
               (relative to the `files` dir). At least one is required. The opts are
               used to override the default mochitest options, they are optional.
     """
+    setup_test_harness(*setup_args)
     runtests = pytest.importorskip('runtests')
 
     mochitest_root = runtests.SCRIPT_DIR
     test_root = os.path.join(mochitest_root, 'tests', 'selftests')
 
     buf = StringIO()
     options = vars(parser.parse_args([]))
     options.update({
@@ -176,32 +77,34 @@ def runtests(setup_harness_root, binary,
 
         result = runtests.run_test_harness(parser, Namespace(**options))
         out = json.loads('[' + ','.join(buf.getvalue().splitlines()) + ']')
         buf.close()
         return result, out
     return inner
 
 
-@pytest.fixture
-def build_obj(setup_harness_root):
+@pytest.fixture  # noqa: F811
+def build_obj(setup_test_harness):
+    setup_test_harness(*setup_args)
     mochitest_options = pytest.importorskip('mochitest_options')
     return mochitest_options.build_obj
 
 
-@pytest.fixture(autouse=True)
-def skip_using_mozinfo(request, setup_harness_root):
+@pytest.fixture(autouse=True)  # noqa: F811
+def skip_using_mozinfo(request, setup_test_harness):
     """Gives tests the ability to skip based on values from mozinfo.
 
     Example:
         @pytest.mark.skip_mozinfo("!e10s || os == 'linux'")
         def test_foo():
             pass
     """
 
+    setup_test_harness(*setup_args)
     runtests = pytest.importorskip('runtests')
     runtests.update_mozinfo()
 
     skip_mozinfo = request.node.get_marker('skip_mozinfo')
     if skip_mozinfo:
         value = skip_mozinfo.args[0]
         if expression.parse(value, **mozinfo.info):
             pytest.skip("skipped due to mozinfo match: \n{}".format(value))
--- a/testing/mochitest/tests/python/test_basic_mochitest_plain.py
+++ b/testing/mochitest/tests/python/test_basic_mochitest_plain.py
@@ -1,49 +1,25 @@
 # 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 json
 import os
-import sys
+from functools import partial
 
 import mozunit
 import pytest
+from moztest.selftest.output import get_mozharness_status, filter_action
 
-from conftest import build, filter_action
-
-sys.path.insert(0, os.path.join(build.topsrcdir, 'testing', 'mozharness'))
 from mozharness.base.log import INFO, WARNING, ERROR
-from mozharness.base.errors import BaseErrorList
 from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WARNING, TBPL_FAILURE
-from mozharness.mozilla.structuredlog import StructuredOutputParser
-from mozharness.mozilla.testing.errors import HarnessErrorList
-
-here = os.path.abspath(os.path.dirname(__file__))
 
 
-def get_mozharness_status(lines, status):
-    parser = StructuredOutputParser(
-        config={'log_level': INFO},
-        error_list=BaseErrorList+HarnessErrorList,
-        strict=False,
-        suite_category='mochitest',
-    )
-
-    # Processing the log with mozharness will re-print all the output to stdout
-    # Since this exact same output has already been printed by the actual test
-    # run, temporarily redirect stdout to devnull.
-    with open(os.devnull, 'w') as fh:
-        orig = sys.stdout
-        sys.stdout = fh
-        for line in lines:
-            parser.parse_single_line(json.dumps(line))
-        sys.stdout = orig
-    return parser.evaluate_parser(status)
+here = os.path.abspath(os.path.dirname(__file__))
+get_mozharness_status = partial(get_mozharness_status, 'mochitest')
 
 
 def test_output_pass(runtests):
     status, lines = runtests('test_pass.html')
     assert status == 0
 
     tbpl_status, log_level = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_SUCCESS
--- a/testing/mochitest/tests/python/test_get_active_tests.py
+++ b/testing/mochitest/tests/python/test_get_active_tests.py
@@ -6,20 +6,22 @@ from __future__ import print_function, u
 
 import os
 from argparse import Namespace
 
 from manifestparser import TestManifest
 
 import mozunit
 import pytest
+from conftest import setup_args
 
 
 @pytest.fixture
-def get_active_tests(setup_harness_root, parser):
+def get_active_tests(setup_test_harness, parser):
+    setup_test_harness(*setup_args)
     runtests = pytest.importorskip('runtests')
     md = runtests.MochitestDesktop('plain', {'log_tbpl': '-'})
 
     options = vars(parser.parse_args([]))
 
     def inner(**kwargs):
         opts = options.copy()
         opts.update(kwargs)
--- a/testing/mozbase/mozlog/mozlog/formatters/tbplformatter.py
+++ b/testing/mozbase/mozlog/mozlog/formatters/tbplformatter.py
@@ -215,17 +215,17 @@ class TbplFormatter(BaseFormatter):
             if "reftest_screenshots" in extra:
                 screenshots = extra["reftest_screenshots"]
                 if len(screenshots) == 3:
                     message += ("\nREFTEST   IMAGE 1 (TEST): data:image/png;base64,%s\n"
                                 "REFTEST   IMAGE 2 (REFERENCE): data:image/png;base64,%s") % (
                                     screenshots[0]["screenshot"],
                                     screenshots[2]["screenshot"])
                 elif len(screenshots) == 1:
-                    message += "\nREFTEST   IMAGE: data:image/png;base64,%(image1)s" \
+                    message += "\nREFTEST   IMAGE: data:image/png;base64,%s" \
                                % screenshots[0]["screenshot"]
 
             failure_line = "TEST-UNEXPECTED-%s | %s | %s\n" % (
                 data["status"], test_id, message)
 
             if data["expected"] not in ("PASS", "OK"):
                 expected_msg = "expected %s | " % data["expected"]
             else:
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/moztest/moztest/selftest/fixtures.py
@@ -0,0 +1,113 @@
+# 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/.
+"""Pytest fixtures to help set up Firefox and a tests.zip
+in test harness selftests.
+"""
+
+import os
+import shutil
+import sys
+
+import mozfile
+import mozinstall
+import pytest
+import requests
+from mozbuild.base import MozbuildObject
+
+here = os.path.abspath(os.path.dirname(__file__))
+build = MozbuildObject.from_environment(cwd=here)
+
+
+HARNESS_ROOT_NOT_FOUND = """
+Could not find test harness root. Either a build or the 'GECKO_INSTALLER_URL'
+environment variable is required.
+""".lstrip()
+
+
+def _get_test_harness(suite, install_dir):
+    # Check if there is a local build
+    harness_root = os.path.join(build.topobjdir, '_tests', install_dir)
+    if os.path.isdir(harness_root):
+        return harness_root
+
+    # Check if it was previously set up by another test
+    harness_root = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests', suite)
+    if os.path.isdir(harness_root):
+        return harness_root
+
+    # Check if there is a test package to download
+    if 'GECKO_INSTALLER_URL' in os.environ:
+        base_url = os.environ['GECKO_INSTALLER_URL'].rsplit('/', 1)[0]
+        test_packages = requests.get(base_url + '/target.test_packages.json').json()
+
+        dest = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests')
+        for name in test_packages[suite]:
+            url = base_url + '/' + name
+            bundle = os.path.join(os.environ['PYTHON_TEST_TMP'], name)
+
+            r = requests.get(url, stream=True)
+            with open(bundle, 'w+b') as fh:
+                for chunk in r.iter_content(chunk_size=1024):
+                    fh.write(chunk)
+
+            mozfile.extract(bundle, dest)
+
+        return os.path.join(dest, suite)
+
+    # Couldn't find a harness root, let caller do error handling.
+    return None
+
+
+@pytest.fixture(scope='session')
+def setup_test_harness(request):
+    """Fixture for setting up a mozharness-based test harness like
+    mochitest or reftest"""
+    def inner(files_dir, *args, **kwargs):
+        harness_root = _get_test_harness(*args, **kwargs)
+        if harness_root:
+            sys.path.insert(0, harness_root)
+
+            # Link the test files to the test package so updates are automatically
+            # picked up. Fallback to copy on Windows.
+            if files_dir:
+                test_root = os.path.join(harness_root, 'tests', 'selftests')
+                if not os.path.exists(test_root):
+                    if hasattr(os, 'symlink'):
+                        os.symlink(files_dir, test_root)
+                    else:
+                        shutil.copytree(files_dir, test_root)
+
+        elif 'GECKO_INSTALLER_URL' in os.environ:
+            # The mochitest tests will run regardless of whether a build exists or not.
+            # In a local environment, they should simply be skipped if setup fails. But
+            # in automation, we'll need to make sure an error is propagated up.
+            pytest.fail(HARNESS_ROOT_NOT_FOUND)
+        else:
+            # Tests will be marked skipped by the calls to pytest.importorskip() below.
+            # We are purposefully not failing here because running |mach python-test|
+            # without a build is a perfectly valid use case.
+            pass
+    return inner
+
+
+@pytest.fixture(scope='session')
+def binary():
+    """Return a Firefox binary"""
+    try:
+        return build.get_binary_path()
+    except:
+        pass
+
+    app = 'firefox'
+    bindir = os.path.join(os.environ['PYTHON_TEST_TMP'], app)
+    if os.path.isdir(bindir):
+        try:
+            return mozinstall.get_binary(bindir, app_name=app)
+        except:
+            pass
+
+    if 'GECKO_INSTALLER_URL' in os.environ:
+        bindir = mozinstall.install(
+            os.environ['GECKO_INSTALLER_URL'], os.environ['PYTHON_TEST_TMP'])
+        return mozinstall.get_binary(bindir, app_name='firefox')
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/moztest/moztest/selftest/output.py
@@ -0,0 +1,47 @@
+# 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/.
+
+"""Methods for testing interactions with mozharness."""
+
+import json
+import os
+import sys
+
+from mozbuild.base import MozbuildObject
+
+here = os.path.abspath(os.path.dirname(__file__))
+build = MozbuildObject.from_environment(cwd=here)
+
+sys.path.insert(0, os.path.join(build.topsrcdir, 'testing', 'mozharness'))
+from mozharness.base.log import INFO
+from mozharness.base.errors import BaseErrorList
+from mozharness.mozilla.structuredlog import StructuredOutputParser
+from mozharness.mozilla.testing.errors import HarnessErrorList
+
+
+def get_mozharness_status(suite, lines, status):
+    """Given list of log lines, determine what the mozharness status would be."""
+    parser = StructuredOutputParser(
+        config={'log_level': INFO},
+        error_list=BaseErrorList+HarnessErrorList,
+        strict=False,
+        suite_category=suite,
+    )
+
+    # Processing the log with mozharness will re-print all the output to stdout
+    # Since this exact same output has already been printed by the actual test
+    # run, temporarily redirect stdout to devnull.
+    with open(os.devnull, 'w') as fh:
+        orig = sys.stdout
+        sys.stdout = fh
+        for line in lines:
+            parser.parse_single_line(json.dumps(line))
+        sys.stdout = orig
+    return parser.evaluate_parser(status)
+
+
+def filter_action(actions, lines):
+    if isinstance(actions, basestring):
+        actions = (actions,)
+    return filter(lambda x: x['action'] in actions, lines)
--- a/testing/web-platform/moz.build
+++ b/testing/web-platform/moz.build
@@ -147,20 +147,20 @@ with Files("tests/cookies/**"):
 
 with Files("tests/cors/**"):
     BUG_COMPONENT = ("Core", "DOM: Security")
 
 with Files("tests/credential-management/**"):
     BUG_COMPONENT = ("Core", "DOM: Security")
 
 with Files("tests/css/**"):
-    BUG_COMPONENT = ("Core", "CSS Parsing and Comp")
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
 
 with Files("tests/css-cascade/**"):
-    BUG_COMPONENT = ("Core", "CSS Parsing and Comp")
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
 
 with Files("tests/css-font-display/**"):
     BUG_COMPONENT = ("Core", "Layout: Text")
 
 with Files("tests/css-font-loading/**"):
     BUG_COMPONENT = ("Core", "Layout: Text")
 
 with Files("tests/css-fonts/**"):
--- a/testing/xpcshell/mach_test_package_commands.py
+++ b/testing/xpcshell/mach_test_package_commands.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 import os
 import sys
 from argparse import Namespace
 from functools import partial
 
 
 import mozlog
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -1,14 +1,16 @@
 #!/usr/bin/env 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/.
 
+from __future__ import absolute_import, print_function
+
 import logging
 import posixpath
 import sys, os
 import subprocess
 import runxpcshelltests as xpcshell
 import tempfile
 import time
 from argparse import Namespace
@@ -163,17 +165,17 @@ class RemoteXPCShellTestThread(xpcshell.
     def checkForCrashes(self,
                         dump_directory,
                         symbols_path,
                         test_name=None):
         if not self.device.dirExists(self.remoteMinidumpDir):
             # The minidumps directory is automatically created when Fennec
             # (first) starts, so its lack of presence is a hint that
             # something went wrong.
-            print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir
+            print("Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir)
             # Whilst no crash was found, the run should still display as a failure
             return True
         with mozfile.TemporaryDirectory() as dumpDir:
             self.device.getDirectory(self.remoteMinidumpDir, dumpDir)
             crashed = xpcshell.XPCShellTestThread.checkForCrashes(self, dumpDir, symbols_path, test_name)
             self.clearRemoteDir(self.remoteMinidumpDir)
         return crashed
 
@@ -270,17 +272,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
 
         self.env = {}
 
         if options['objdir']:
             self.xpcDir = os.path.join(options['objdir'], "_tests/xpcshell")
         elif os.path.isdir(os.path.join(here, 'tests')):
             self.xpcDir = os.path.join(here, 'tests')
         else:
-            print >> sys.stderr, "Couldn't find local xpcshell test directory"
+            print("Couldn't find local xpcshell test directory", file=sys.stderr)
             sys.exit(1)
 
         if options['localAPK']:
             self.localAPKContents = ZipFile(options['localAPK'])
         if options['setup']:
             self.setupTestDir()
             self.setupUtilities()
             self.setupModules()
@@ -370,17 +372,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
         self.appRoot = None
         packageName = None
         if self.options['localAPK']:
             try:
                 packageName = self.localAPKContents.read("package-name.txt")
                 if packageName:
                     self.appRoot = self.device.getAppRoot(packageName.strip())
             except Exception as detail:
-                print "unable to determine app root: " + str(detail)
+                print("unable to determine app root: " + str(detail))
                 pass
         return None
 
     def setupUtilities(self):
         if (not self.device.dirExists(self.remoteBinDir)):
             # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
             try:
                 self.device.shellCheckOutput(["mkdir", self.remoteBinDir]);
@@ -412,21 +414,21 @@ class XPCShellRemote(xpcshell.XPCShellTe
                     "certutil",
                     "pk12util",
                     "BadCertServer",
                     "OCSPStaplingServer",
                     "GenerateOCSPResponse"]
         for fname in binaries:
             local = os.path.join(self.localBin, fname)
             if os.path.isfile(local):
-                print >> sys.stderr, "Pushing %s.." % fname
+                print("Pushing %s.." % fname, file=sys.stderr)
                 remoteFile = remoteJoin(self.remoteBinDir, fname)
                 self.device.pushFile(local, remoteFile)
             else:
-                print >> sys.stderr, "*** Expected binary %s not found in %s!" % (fname, self.localBin)
+                print("*** Expected binary %s not found in %s!" % (fname, self.localBin), file=sys.stderr)
 
         local = os.path.join(self.localBin, "components/httpd.js")
         remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js")
         self.device.pushFile(local, remoteFile)
 
         local = os.path.join(self.localBin, "components/httpd.manifest")
         remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
         self.device.pushFile(local, remoteFile)
@@ -443,17 +445,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
 
     def pushLibs(self):
         pushed_libs_count = 0
         if self.options['localAPK']:
             try:
                 dir = tempfile.mkdtemp()
                 for info in self.localAPKContents.infolist():
                     if info.filename.endswith(".so"):
-                        print >> sys.stderr, "Pushing %s.." % info.filename
+                        print("Pushing %s.." % info.filename, file=sys.stderr)
                         remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
                         self.localAPKContents.extract(info, dir)
                         localFile = os.path.join(dir, info.filename)
                         with open(localFile) as f:
                             # Decompress xz-compressed file.
                             if f.read(5)[1:] == '7zXZ':
                                 cmd = ['xz', '-df', '--suffix', '.so', localFile]
                                 subprocess.check_output(cmd)
@@ -462,44 +464,44 @@ class XPCShellRemote(xpcshell.XPCShellTe
                         self.device.pushFile(localFile, remoteFile)
                         pushed_libs_count += 1
             finally:
                 shutil.rmtree(dir)
             return pushed_libs_count
 
         for file in os.listdir(self.localLib):
             if (file.endswith(".so")):
-                print >> sys.stderr, "Pushing %s.." % file
+                print("Pushing %s.." % file, file=sys.stderr)
                 if 'libxul' in file:
-                    print >> sys.stderr, "This is a big file, it could take a while."
+                    print("This is a big file, it could take a while.", file=sys.stderr)
                 localFile = os.path.join(self.localLib, file)
                 remoteFile = remoteJoin(self.remoteBinDir, file)
                 self.device.pushFile(localFile, remoteFile)
                 pushed_libs_count += 1
 
         # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
         localArmLib = os.path.join(self.localLib, "lib")
         if os.path.exists(localArmLib):
             for root, dirs, files in os.walk(localArmLib):
                 for file in files:
                     if (file.endswith(".so")):
-                        print >> sys.stderr, "Pushing %s.." % file
+                        print("Pushing %s.." % file, file=sys.stderr)
                         localFile = os.path.join(root, file)
                         remoteFile = remoteJoin(self.remoteBinDir, file)
                         self.device.pushFile(localFile, remoteFile)
                         pushed_libs_count += 1
 
         return pushed_libs_count
 
     def setupModules(self):
         if self.testingModulesDir:
             self.device.pushDir(self.testingModulesDir, self.remoteModulesDir)
 
     def setupTestDir(self):
-        print 'pushing %s' % self.xpcDir
+        print('pushing %s' % self.xpcDir)
         try:
             # The tests directory can be quite large: 5000 files and growing!
             # Sometimes - like on a low-end aws instance running an emulator - the push
             # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
             self.device.pushDir(self.xpcDir, self.remoteScriptsDir, timeout=600, retryLimit=10)
         except TypeError:
             # Foopies have an older mozdevice ver without retryLimit
             self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
@@ -557,47 +559,47 @@ def verifyRemoteOptions(parser, options)
 class PathMapping:
 
     def __init__(self, localDir, remoteDir):
         self.local = localDir
         self.remote = remoteDir
 
 def main():
     if sys.version_info < (2,7):
-        print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
+        print("Error: You must use python version 2.7 or newer but less than 3.0", file=sys.stderr)
         sys.exit(1)
 
     parser = parser_remote()
     options = parser.parse_args()
     if not options.localAPK:
         for file in os.listdir(os.path.join(options.objdir, "dist")):
             if (file.endswith(".apk") and file.startswith("fennec")):
                 options.localAPK = os.path.join(options.objdir, "dist")
                 options.localAPK = os.path.join(options.localAPK, file)
-                print >>sys.stderr, "using APK: " + options.localAPK
+                print("using APK: " + options.localAPK, file=sys.stderr)
                 break
         else:
-            print >>sys.stderr, "Error: please specify an APK"
+            print("Error: please specify an APK", file=sys.stderr)
             sys.exit(1)
 
     options = verifyRemoteOptions(parser, options)
     log = commandline.setup_logging("Remote XPCShell",
                                     options,
                                     {"tbpl": sys.stdout})
 
     dm_args = {'deviceRoot': options['remoteTestRoot']}
     if options['deviceIP']:
         dm_args['host'] = options['deviceIP']
         dm_args['port'] = options['devicePort']
     if options['log_tbpl_level'] == 'debug' or options['log_mach_level'] == 'debug':
         dm_args['logLevel'] = logging.DEBUG
     dm = mozdevice.DroidADB(**dm_args)
 
     if options['interactive'] and not options['testPath']:
-        print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
+        print("Error: You must specify a test filename in interactive mode!", file=sys.stderr)
         sys.exit(1)
 
     if options['xpcshell'] is None:
         options['xpcshell'] = "xpcshell"
 
     xpcsh = XPCShellRemote(dm, options, log)
 
     # we don't run concurrent tests on mobile
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -1,14 +1,16 @@
 #!/usr/bin/env 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/.
 
+from __future__ import absolute_import, print_function
+
 import copy
 import importlib
 import json
 import math
 import mozdebug
 import mozinfo
 import os
 import random
@@ -1063,17 +1065,17 @@ class XPCShellTests(object):
 
                 # Check to make sure the server starts properly by waiting for it to
                 # tell us it's started
                 msg = process.stdout.readline()
                 if 'server listening' in msg:
                     searchObj = re.search( r'HTTP2 server listening on port (.*)', msg, 0)
                     if searchObj:
                       self.env["MOZHTTP2_PORT"] = searchObj.group(1)
-            except OSError, e:
+            except OSError as e:
                 # This occurs if the subprocess couldn't be started
                 self.log.error('Could not run %s server: %s' % (name, str(e)))
                 raise
 
         myDir = os.path.split(os.path.abspath(__file__))[0]
         startServer('moz-http2', os.path.join(myDir, 'moz-http2', 'moz-http2.js'))
 
     def shutdownNode(self):
--- a/testing/xpcshell/selftest.py
+++ b/testing/xpcshell/selftest.py
@@ -1,14 +1,16 @@
 #!/usr/bin/env python
 #
 # Any copyright is dedicated to the Public Domain.
 # http://creativecommons.org/publicdomain/zero/1.0/
 #
 
+from __future__ import absolute_import
+
 import mozinfo
 import mozunit
 import os
 import pprint
 import re
 import shutil
 import sys
 import tempfile
@@ -1079,17 +1081,17 @@ add_test({
         self.writeFile("test_basic.js", SIMPLE_PASSING_TEST)
         self.writeManifest([("test_basic.js", "head = missing.js")])
 
         raised = False
 
         try:
             # The actual return value is never checked because we raise.
             self.assertTestResult(True)
-        except Exception, ex:
+        except Exception as ex:
             raised = True
             self.assertEquals(ex.message[0:9], "head file")
 
         self.assertTrue(raised)
 
     def testRandomExecution(self):
         """
         Check that random execution doesn't break.
--- a/testing/xpcshell/xpcshellcommandline.py
+++ b/testing/xpcshell/xpcshellcommandline.py
@@ -1,8 +1,10 @@
+from __future__ import absolute_import
+
 import argparse
 
 from mozlog import commandline
 
 
 def add_common_arguments(parser):
     parser.add_argument("--app-path",
                         type=unicode, dest="appPath", default=None,
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -768,23 +768,33 @@ add_task(async function test_checkSubses
 
   const stableKeyedHistograms = new Set([
     "TELEMETRY_TEST_KEYED_FLAG",
     "TELEMETRY_TEST_KEYED_COUNT",
     "TELEMETRY_TEST_KEYED_RELEASE_OPTIN",
     "TELEMETRY_TEST_KEYED_RELEASE_OPTOUT",
   ]);
 
+  // List of prefixes of histograms that could anomalously be present in
+  // the subsession snapshot but not the session snapshot.
+  // If you add something to this list, please reference a bug#
+  const possibleAnomalyPrefixes = [
+    "CYCLE_COLLECTOR_WORKER", // non-MT CC can happen between payload gathering - bug 1398431
+  ];
+
   // Compare the two sets of histograms.
   // The "subsession" histograms should match the registered
   // "classic" histograms. However, histograms can change
   // between us collecting the different payloads, so we only
   // check for deep equality on known stable histograms.
   let checkHistograms = (classic, subsession, message) => {
     for (let id of Object.keys(subsession)) {
+      if (possibleAnomalyPrefixes.some(prefix => id.startsWith(prefix))) {
+        continue;
+      }
       Assert.ok(id in classic, message + ` (${id})`);
       if (stableHistograms.has(id)) {
         Assert.deepEqual(classic[id],
                          subsession[id], message);
       } else {
         Assert.equal(classic[id].histogram_type,
                      subsession[id].histogram_type, message);
       }
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -1423,23 +1423,24 @@ var Search = {
       this.updateNoResults(text, noSearchResults);
     }
     return noSearchResults;
   },
 
   updateNoResults(text, noSearchResults) {
     document.getElementById("no-search-results").classList.toggle("hidden", !noSearchResults);
     if (noSearchResults) {
+      let searchStatus;
       let section = document.querySelector(".category.selected > span");
-      let selectedTitle = section.textContent.trim();
       if (section.parentElement.id === "category-home") {
-        selectedTitle = bundle.GetStringFromName("allSections");
+        searchStatus = bundle.formatStringFromName("noSearchResultsAll", [text], 1);
+      } else {
+        let format = [section.textContent.trim(), text];
+        searchStatus = bundle.formatStringFromName("noSearchResults", format, 2);
       }
-      let format = [selectedTitle, text];
-      let searchStatus = bundle.formatStringFromName("noSearchResults", format, 2);
       document.getElementById("no-search-results-text").textContent = searchStatus;
     }
   },
 
   resetHome() {
     document.getElementById("main").classList.remove("search");
     document.getElementById("no-search-results").classList.add("hidden");
     adjustHeaderState();
@@ -1885,21 +1886,24 @@ function adjustSection() {
     PingPicker._showStructuredPingData();
   }
 }
 
 function adjustHeaderState(title = null) {
   let selected = document.querySelector(".category.selected .category-name");
   let selectedTitle = selected.textContent.trim();
   document.getElementById("sectionTitle").textContent = title ? title : selectedTitle;
+
+  let placeholder;
   if (selected.parentElement.id === "category-home") {
-    selectedTitle = bundle.GetStringFromName("allSections");
+    placeholder = bundle.GetStringFromName("filterAllPlaceholder");
+  } else {
+    placeholder = bundle.formatStringFromName("filterPlaceholder", [ selectedTitle ], 1);
   }
   let search = document.getElementById("search");
-  let placeholder = bundle.formatStringFromName("filterPlaceholder", [ selectedTitle ], 1);
   search.setAttribute("placeholder", placeholder);
 }
 
 /**
  * Change the url according to the current section displayed
  * e.g about:telemetry#general-data
  */
 function changeUrlPath(selectedSection, subSection) {
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -3585,16 +3585,27 @@ OOPDeinit()
   pidToMinidump = nullptr;
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
   free(childCrashNotifyPipe);
   childCrashNotifyPipe = nullptr;
 #endif
 }
 
+void
+GetChildProcessTmpDir(nsIFile** aOutTmpDir)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+#if (defined(XP_MACOSX) || defined(XP_WIN))
+  if (childProcessTmpDir) {
+    CreateFileFromPath(*childProcessTmpDir, aOutTmpDir);
+  }
+#endif
+}
+
 #if defined(XP_WIN) || defined(XP_MACOSX)
 // Parent-side API for children
 const char*
 GetChildNotificationPipe()
 {
   if (!GetEnabled())
     return kNullNotifyPipe;
 
@@ -3739,19 +3750,24 @@ GetLastRunCrashID(nsAString& id)
   }
 
   id = *lastRunCrashID;
   return true;
 }
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
 void
-InitChildProcessTmpDir()
+InitChildProcessTmpDir(nsIFile* aDirOverride)
 {
   MOZ_ASSERT(!XRE_IsParentProcess());
+  if (aDirOverride) {
+    childProcessTmpDir = CreatePathFromFile(aDirOverride);
+    return;
+  }
+
   // When retrieved by the child process, this will always resolve to the
   // correct directory regardless of sandbox level.
   nsCOMPtr<nsIFile> tmpDir;
   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir));
   if (NS_SUCCEEDED(rv)) {
     childProcessTmpDir = CreatePathFromFile(tmpDir);
   }
 }
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -212,16 +212,20 @@ CreateMinidumpsAndPair(ProcessHandle aTa
 // a minidump (|parentMinidump|).
 // The resulting dump will get the id of the parent and use the |name| as
 // an extension.
 bool CreateAdditionalChildMinidump(ProcessHandle childPid,
                                    ThreadId childBlamedThread,
                                    nsIFile* parentMinidump,
                                    const nsACString& name);
 
+// Parent-side API, returns the tmp dir for child processes to use, accounting
+// for sandbox considerations.
+void GetChildProcessTmpDir(nsIFile** aOutTmpDir);
+
 #  if defined(XP_WIN32) || defined(XP_MACOSX)
 // Parent-side API for children
 const char* GetChildNotificationPipe();
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
 // Inject a crash report client into an arbitrary process, and inform the
 // callback object when it crashes. Parent process only.
 
@@ -242,17 +246,17 @@ public:
 
 // This method implies OOPInit
 void InjectCrashReporterIntoProcess(DWORD processID, InjectorCrashCallback* cb);
 void UnregisterInjectorCallback(DWORD processID);
 #endif
 
 // Child-side API
 bool SetRemoteExceptionHandler(const nsACString& crashPipe);
-void InitChildProcessTmpDir();
+void InitChildProcessTmpDir(nsIFile* aDirOverride = nullptr);
 
 #  elif defined(XP_LINUX)
 // Parent-side API for children
 
 // Set the outparams for crash reporter server's fd (|childCrashFd|)
 // and the magic fd number it should be remapped to
 // (|childCrashRemapFd|) before exec() in the child process.
 // |SetRemoteExceptionHandler()| in the child process expects to find
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
@@ -46,32 +46,34 @@ currentPingSidebar = current ping
 # - %1$S will be replaced by the current text in the search input
 resultsForSearch = Results for “%1$S”
 
 # Note to translators:
 # - %1$S will be replaced by the section name from the structure of the ping. More info about it can be found here : https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/main-ping.html
 # - %2$S will be replaced by the current text in the search input
 noSearchResults = Sorry! There are no results in %1$S for “%2$S”
 
+# Note to translators:
+# - %S is replaced by the searched terms
+noSearchResultsAll = Sorry! There are no results in any sections for “%S”
+
 telemetryPingTypeAll = all
 
 telemetryLogTitle = Telemetry Log
 
 telemetryLogHeadingId = Id
 
 telemetryLogHeadingTimestamp = Timestamp
 
 telemetryLogHeadingData = Data
 
 # Note to translators:
 # - %1$S will be replaced by the section name from the structure of the ping. More info about it can be found here : https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/main-ping.html
-
 filterPlaceholder = Find in %1$S
-
-allSections = all sections
+filterAllPlaceholder = Find in all sections
 
 slowSqlMain = Slow SQL Statements on Main Thread
 
 slowSqlOther = Slow SQL Statements on Helper Threads
 
 slowSqlHits = Hits
 
 slowSqlAverage = Avg. Time (ms)
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -558,16 +558,30 @@ XRE_InitChildProcess(int aArgc,
   const char* const parentPIDString = aArgv[aArgc-1];
   MOZ_ASSERT(parentPIDString, "NULL parent PID");
   --aArgc;
 
   char* end = 0;
   base::ProcessId parentPID = strtol(parentPIDString, &end, 10);
   MOZ_ASSERT(!*end, "invalid parent PID");
 
+  nsCOMPtr<nsIFile> crashReportTmpDir;
+  if (XRE_GetProcessType() == GeckoProcessType_GPU) {
+    aArgc--;
+    if (strlen(aArgv[aArgc])) { // if it's empty, ignore it
+      nsresult rv = XRE_GetFileFromPath(aArgv[aArgc], getter_AddRefs(crashReportTmpDir));
+      if (NS_FAILED(rv)) {
+        // If we don't have a valid tmp dir we can probably still run ok, but
+        // crash report .extra files might not get picked up by the parent
+        // process. Debug-assert because this shouldn't happen in practice.
+        MOZ_ASSERT(false, "GPU process started without valid tmp dir!");
+      }
+    }
+  }
+
 #ifdef XP_MACOSX
   mozilla::ipc::SharedMemoryBasic::SetupMachMemory(parentPID, ports_in_receiver, ports_in_sender,
                                                    ports_out_sender, ports_out_receiver, true);
 #endif
 
 #if defined(XP_WIN)
   // On Win7+, register the application user model id passed in by
   // parent. This insures windows created by the container properly
@@ -657,17 +671,17 @@ XRE_InitChildProcess(int aArgc,
       }
 
       if (!process->Init(aArgc, aArgv)) {
         return NS_ERROR_FAILURE;
       }
 
 #ifdef MOZ_CRASHREPORTER
 #if defined(XP_WIN) || defined(XP_MACOSX)
-      CrashReporter::InitChildProcessTmpDir();
+      CrashReporter::InitChildProcessTmpDir(crashReportTmpDir);
 #endif
 #endif
 
 #if defined(XP_WIN)
       // Set child processes up such that they will get killed after the
       // chrome process is killed in cases where the user shuts the system
       // down or logs off.
       ::SetProcessShutdownParameters(0x280 - 1, SHUTDOWN_NORETRY);
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -180,17 +180,16 @@
   "record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
   "recursive_importA.jsm": ["foo", "bar"],
   "recursive_importB.jsm": ["baz", "qux"],
   "reflect.jsm": ["Reflect"],
   "RemoteFinder.jsm": ["RemoteFinder", "RemoteFinderListener"],
   "RemotePageManager.jsm": ["RemotePages", "RemotePageManager", "PageListener"],
   "RemoteWebProgress.jsm": ["RemoteWebProgressManager"],
   "resource.js": ["AsyncResource", "Resource"],
-  "responsivedesign.jsm": ["ResponsiveUIManager"],
   "rest.js": ["RESTRequest", "RESTResponse", "TokenAuthenticatedRESTRequest", "SyncStorageRequest"],
   "rotaryengine.js": ["RotaryEngine", "RotaryRecord", "RotaryStore", "RotaryTracker"],
   "require.js": ["require"],
   "RTCStatsReport.jsm": ["convertToRTCStatsReport"],
   "scratchpad-manager.jsm": ["ScratchpadManager"],
   "server.js": ["server"],
   "service.js": ["Service"],
   "SharedPromptUtils.jsm": ["PromptUtils", "EnableDelayHelper"],
--- a/tools/lint/py2.yml
+++ b/tools/lint/py2.yml
@@ -45,17 +45,16 @@ py2:
         - testing/mozbase
         - testing/mozharness
         - testing/remotecppunittests.py
         - testing/runcppunittests.py
         - testing/runtimes
         - testing/tools
         - testing/tps
         - testing/web-platform
-        - testing/xpcshell
         - third_party
         - toolkit
         - tools/docs
         - tools/git/eslintvalidate.py
         - tools/jprof/split-profile.py
         - tools/lint
         - tools/mach_commands.py
         - tools/mercurial/eslintvalidate.py
--- a/widget/windows/nsWidgetFactory.cpp
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -186,17 +186,17 @@ NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SP
 static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
   { &kNS_WINDOW_CID, false, nullptr, WindowConstructor },
   { &kNS_CHILD_CID, false, nullptr, ChildWindowConstructor },
   { &kNS_FILEPICKER_CID, false, nullptr, FilePickerConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_COLORPICKER_CID, false, nullptr, ColorPickerConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS },
   { &kNS_SCREENMANAGER_CID, false, nullptr, ScreenManagerConstructor,
     Module::MAIN_PROCESS_ONLY },
-  { &kNS_GFXINFO_CID, false, nullptr, GfxInfoConstructor },
+  { &kNS_GFXINFO_CID, false, nullptr, GfxInfoConstructor, Module::ALLOW_IN_GPU_PROCESS },
   { &kNS_THEMERENDERER_CID, false, nullptr, NS_NewNativeTheme },
   { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceWinConstructor },
   { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
   { &kNS_SOUND_CID, false, nullptr, nsISoundConstructor, Module::MAIN_PROCESS_ONLY },
   { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
   { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
   { &kNS_WIN_TASKBAR_CID, false, nullptr, WinTaskbarConstructor },
--- a/xpcom/idl-parser/xpidl/xpidl.py
+++ b/xpcom/idl-parser/xpidl/xpidl.py
@@ -524,17 +524,17 @@ class Interface(object):
 
         parent.setName(self)
         if self.base is not None:
             realbase = parent.getName(self.base, self.location)
             if realbase.kind != 'interface':
                 raise IDLError("interface '%s' inherits from non-interface type '%s'" % (self.name, self.base), self.location)
 
             if self.attributes.scriptable and not realbase.attributes.scriptable:
-                print >>sys.stderr, IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True)
+                raise IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True)
 
             if self.attributes.scriptable and realbase.attributes.builtinclass and not self.attributes.builtinclass:
                 raise IDLError("interface '%s' is not builtinclass but derives from builtinclass '%s'" % (self.name, self.base), self.location)
 
         for member in self.members:
             member.resolve(self)
 
         # The number 250 is NOT arbitrary; this number is the maximum number of