merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 06 May 2015 11:59:15 +0200
changeset 273886 60349cbc3d4eb0423b20303a7c4384d9f715a133
parent 273821 daeb7cfc60a842512641109aed57cfda89c46ea2 (current diff)
parent 273885 bbe25ecbebdaa2a5e955f0b6789a9fcc43faddd5 (diff)
child 273887 b90ebba4306d208d852dce3e621c2672f864236e
child 273902 0e78345db8b6364b3f62f3652051ec7f8d9c9d4b
child 273927 eecce2240c024761b5309453d8dcf72ee9334066
child 273966 845b8366a7e4f65613c0a7620e337fd89da8de45
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
60349cbc3d4e / 40.0a1 / 20150506030206 / files
nightly linux64
60349cbc3d4e / 40.0a1 / 20150506030206 / files
nightly mac
60349cbc3d4e / 40.0a1 / 20150506030206 / files
nightly win32
60349cbc3d4e / 40.0a1 / 20150506030206 / files
nightly win64
60349cbc3d4e / 40.0a1 / 20150506030206 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
CLOBBER
dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul
dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul
dom/tests/mochitest/general/file_MozEnteredDomFullscreen.html
testing/taskcluster/tasks/tests/b2g_build_test.yml
testing/web-platform/meta/web-animations/animation-timeline/animation-timeline.html.ini
testing/web-platform/meta/workers/Worker_terminate_event_queue.htm.ini
testing/web-platform/tests/IndexedDB/interfaces.htm
testing/web-platform/tests/web-animations/animation-timeline/animation-timeline.html
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1075157 needed a clobber for unknown reasons due to Android debug mochitest crashes.
+Bug 1155494 seems to need a clobber to pick up a change the ipdl parser.
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -5,23 +5,25 @@
 
 var FullScreen = {
   _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
 
   init: function() {
     // called when we go into full screen, even if initiated by a web page script
     window.addEventListener("fullscreen", this, true);
     window.messageManager.addMessageListener("MozEnteredDomFullscreen", this);
+    window.messageManager.addMessageListener("MozExitedDomFullscreen", this);
 
     if (window.fullScreen)
       this.toggle();
   },
 
   uninit: function() {
     window.messageManager.removeMessageListener("MozEnteredDomFullscreen", this);
+    window.messageManager.removeMessageListener("MozExitedDomFullscreen", this);
     this.cleanup();
   },
 
   toggle: function (event) {
     var enterFS = window.fullScreen;
 
     // We get the fullscreen event _before_ the window transitions into or out of FS mode.
     if (event && event.type == "fullscreen")
@@ -65,33 +67,26 @@ var FullScreen = {
 
     // show/hide menubars, toolbars (except the full screen toolbar)
     this.showXULChrome("toolbar", !enterFS);
 
     if (enterFS) {
       document.addEventListener("keypress", this._keyToggleCallback, false);
       document.addEventListener("popupshown", this._setPopupOpen, false);
       document.addEventListener("popuphidden", this._setPopupOpen, false);
+      this._shouldAnimate = true;
       // We don't animate the toolbar collapse if in DOM full-screen mode,
       // as the size of the content area would still be changing after the
       // mozfullscreenchange event fired, which could confuse content script.
-      this._shouldAnimate = !document.mozFullScreen;
-      this.mouseoverToggle(false);
+      this.hideNavToolbox(document.mozFullScreen);
     }
     else {
-      // The user may quit fullscreen during an animation
-      this._cancelAnimation();
-      gNavToolbox.style.marginTop = "";
-      if (this._isChromeCollapsed)
-        this.mouseoverToggle(true);
+      this.showNavToolbox(false);
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
-
-      document.documentElement.removeAttribute("inDOMFullscreen");
-
       this.cleanup();
     }
   },
 
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
@@ -122,16 +117,26 @@ var FullScreen = {
       let data = aMessage.data;
       let browser = aMessage.target;
       if (gMultiProcessBrowser && browser.getAttribute("remote") == "true") {
         let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDOMWindowUtils);
         windowUtils.remoteFrameFullscreenChanged(browser, data.origin);
       }
       this.enterDomFullscreen(browser, data.origin);
+    } else if (aMessage.name == "MozExitedDomFullscreen") {
+      document.documentElement.removeAttribute("inDOMFullscreen");
+      this.cleanupDomFullscreen();
+      this.showNavToolbox();
+      // If we are still in fullscreen mode, re-hide
+      // the toolbox with animation.
+      if (window.fullScreen) {
+        this._shouldAnimate = true;
+        this.hideNavToolbox();
+      }
     }
   },
 
   enterDomFullscreen : function(aBrowser, aOrigin) {
     if (!document.mozFullScreen)
       return;
 
     // If we've received a fullscreen notification, we have to ensure that the
@@ -167,64 +172,67 @@ var FullScreen = {
     // If a fullscreen window loses focus, we show a warning when the
     // fullscreen window is refocused.
     if (!this.useLionFullScreen) {
       window.addEventListener("activate", this);
     }
 
     // Cancel any "hide the toolbar" animation which is in progress, and make
     // the toolbar hide immediately.
-    this._cancelAnimation();
-    this.mouseoverToggle(false);
+    this.hideNavToolbox(true);
+    this._fullScrToggler.hidden = true;
   },
 
   cleanup: function () {
     if (window.fullScreen) {
       MousePosTracker.removeListener(this);
       document.removeEventListener("keypress", this._keyToggleCallback, false);
       document.removeEventListener("popupshown", this._setPopupOpen, false);
       document.removeEventListener("popuphidden", this._setPopupOpen, false);
 
-      this.cancelWarning();
-      gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
-      gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
-      gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
-      if (!this.useLionFullScreen)
-        window.removeEventListener("activate", this);
+      this.cleanupDomFullscreen();
+    }
+  },
 
-      window.messageManager
-            .broadcastAsyncMessage("DOMFullscreen:Cleanup");
-    }
+  cleanupDomFullscreen: function () {
+    this.cancelWarning();
+    gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
+    gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
+    gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
+    if (!this.useLionFullScreen)
+      window.removeEventListener("activate", this);
+
+    window.messageManager
+          .broadcastAsyncMessage("DOMFullscreen:Cleanup");
   },
 
   getMouseTargetRect: function()
   {
     return this._mouseTargetRect;
   },
 
   // Event callbacks
   _expandCallback: function()
   {
-    FullScreen.mouseoverToggle(true);
+    FullScreen.showNavToolbox();
   },
   onMouseEnter: function()
   {
-    FullScreen.mouseoverToggle(false);
+    FullScreen.hideNavToolbox();
   },
   _keyToggleCallback: function(aEvent)
   {
     // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
     // should provide a way to collapse them too.
     if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
-      FullScreen._shouldAnimate = false;
-      FullScreen.mouseoverToggle(false, true);
+      FullScreen.hideNavToolbox(true);
     }
     // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
     else if (aEvent.keyCode == aEvent.DOM_VK_F6)
-      FullScreen.mouseoverToggle(true);
+      FullScreen.showNavToolbox();
   },
 
   // Checks whether we are allowed to collapse the chrome
   _isPopupOpen: false,
   _isChromeCollapsed: false,
   _safeToCollapse: function(forceHide)
   {
     if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
@@ -268,57 +276,16 @@ var FullScreen = {
   },
   setAutohide: function()
   {
     gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
   },
 
   // Animate the toolbars disappearing
   _shouldAnimate: true,
-  _isAnimating: false,
-  _animationTimeout: 0,
-  _animationHandle: 0,
-  _animateUp: function() {
-    // check again, the user may have done something before the animation was due to start
-    if (!window.fullScreen || !this._safeToCollapse(false)) {
-      this._isAnimating = false;
-      this._shouldAnimate = true;
-      return;
-    }
-
-    this._animateStartTime = window.mozAnimationStartTime;
-    if (!this._animationHandle)
-      this._animationHandle = window.mozRequestAnimationFrame(this);
-  },
-
-  sample: function (timeStamp) {
-    const duration = 1500;
-    const timePassed = timeStamp - this._animateStartTime;
-    const pos = timePassed >= duration ? 1 :
-                1 - Math.pow(1 - timePassed / duration, 4);
-
-    if (pos >= 1) {
-      // We've animated enough
-      this._cancelAnimation();
-      gNavToolbox.style.marginTop = "";
-      this.mouseoverToggle(false);
-      return;
-    }
-
-    gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px";
-    this._animationHandle = window.mozRequestAnimationFrame(this);
-  },
-
-  _cancelAnimation: function() {
-    window.mozCancelAnimationFrame(this._animationHandle);
-    this._animationHandle = 0;
-    clearTimeout(this._animationTimeout);
-    this._isAnimating = false;
-    this._shouldAnimate = false;
-  },
 
   cancelWarning: function(event) {
     if (!this.warningBox)
       return;
     this.warningBox.removeEventListener("transitionend", this);
     if (this.warningFadeOutTimeout) {
       clearTimeout(this.warningFadeOutTimeout);
       this.warningFadeOutTimeout = null;
@@ -465,63 +432,79 @@ var FullScreen = {
         setTimeout(
           function() {
             if (this.warningBox)
               this.warningBox.setAttribute("fade-warning-out", "true");
           }.bind(this),
           3000);
   },
 
-  mouseoverToggle: function(aShow, forceHide)
-  {
-    // Don't do anything if:
-    // a) we're already in the state we want,
-    // b) we're animating and will become collapsed soon, or
-    // c) we can't collapse because it would be undesirable right now
-    if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
-        (!aShow && !this._safeToCollapse(forceHide)))
-      return;
+  showNavToolbox: function(trackMouse = true) {
+    this._fullScrToggler.hidden = true;
+    gNavToolbox.removeAttribute("fullscreenShouldAnimate");
+    gNavToolbox.style.marginTop = "";
 
-    // browser.fullscreen.animateUp
-    // 0 - never animate up
-    // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
-    // 2 - animate every time it collapses
-    if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0)
-      this._shouldAnimate = false;
-
-    if (!aShow && this._shouldAnimate) {
-      this._isAnimating = true;
-      this._shouldAnimate = false;
-      this._animationTimeout = setTimeout(this._animateUp.bind(this), 800);
+    if (!this._isChromeCollapsed) {
       return;
     }
 
-    // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
-    // so we just move it off-screen instead. See bug 430687.
-    gNavToolbox.style.marginTop =
-      aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
-
-    this._fullScrToggler.hidden = aShow || document.mozFullScreen;
-
-    if (aShow) {
+    // Track whether mouse is near the toolbox
+    this._isChromeCollapsed = false;
+    if (trackMouse) {
       let rect = gBrowser.mPanelContainer.getBoundingClientRect();
       this._mouseTargetRect = {
         top: rect.top + 50,
         bottom: rect.bottom,
         left: rect.left,
         right: rect.right
       };
       MousePosTracker.addListener(this);
-    } else {
-      MousePosTracker.removeListener(this);
+    }
+  },
+
+  hideNavToolbox: function(forceHide = false) {
+    this._fullScrToggler.hidden = document.mozFullScreen;
+    if (this._isChromeCollapsed) {
+      if (forceHide) {
+        gNavToolbox.removeAttribute("fullscreenShouldAnimate");
+      }
+      return;
+    }
+    if (!this._safeToCollapse(forceHide)) {
+      this._fullScrToggler.hidden = true;
+      return;
     }
 
-    this._isChromeCollapsed = !aShow;
-    if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
+    // browser.fullscreen.animateUp
+    // 0 - never animate up
+    // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
+    // 2 - animate every time it collapses
+    let animateUp = gPrefService.getIntPref("browser.fullscreen.animateUp");
+    if (animateUp == 0) {
+      this._shouldAnimate = false;
+    } else if (animateUp == 2) {
       this._shouldAnimate = true;
+    }
+    if (this._shouldAnimate && !forceHide) {
+      gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
+      this._shouldAnimate = false;
+      // Hide the fullscreen toggler until the transition ends.
+      let listener = () => {
+        gNavToolbox.removeEventListener("transitionend", listener, true);
+        if (this._isChromeCollapsed)
+          this._fullScrToggler.hidden = false;
+      };
+      gNavToolbox.addEventListener("transitionend", listener, true);
+      this._fullScrToggler.hidden = true;
+    }
+
+    gNavToolbox.style.marginTop =
+      -gNavToolbox.getBoundingClientRect().height + "px";
+    this._isChromeCollapsed = true;
+    MousePosTracker.removeListener(this);
   },
 
   showXULChrome: function(aTag, aShow)
   {
     var els = document.getElementsByTagNameNS(this._XULNS, aTag);
 
     for (let el of els) {
       // XXX don't interfere with previously collapsed toolbars
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -295,16 +295,20 @@ toolbar[customizing] > .overflow-button 
   visibility: collapse;
 }
 
 #main-window[inFullscreen] #global-notificationbox,
 #main-window[inFullscreen] #high-priority-global-notificationbox {
   visibility: collapse;
 }
 
+#navigator-toolbox[fullscreenShouldAnimate] {
+  transition: 1.5s margin-top ease-out;
+}
+
 /* Rules to help integrate SDK widgets */
 toolbaritem[sdkstylewidget="true"] > toolbarbutton,
 toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > iframe,
 toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-text {
   display: none;
 }
 
 toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1950,17 +1950,17 @@ function loadOneOrMoreURIs(aURIString)
   }
   catch (e) {
   }
 }
 
 function focusAndSelectUrlBar() {
   if (gURLBar) {
     if (window.fullScreen)
-      FullScreen.mouseoverToggle(true);
+      FullScreen.showNavToolbox();
 
     gURLBar.select();
     if (document.activeElement == gURLBar.inputField)
       return true;
   }
   return false;
 }
 
@@ -3413,17 +3413,17 @@ const BrowserSearch = {
       let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
       navBar.overflowable.show().then(() => {
         focusSearchBar();
       });
       return;
     }
     if (searchBar) {
       if (window.fullScreen)
-        FullScreen.mouseoverToggle(true);
+        FullScreen.showNavToolbox();
       searchBar.select();
     }
     openSearchPageIfFieldIsNotActive(searchBar);
   },
 
   /**
    * Loads a search results page, given a set of search terms. Uses the current
    * engine if the search bar is visible, or the default engine otherwise.
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -585,16 +585,17 @@ if (Services.appinfo.processType == Serv
 
 let DOMFullscreenHandler = {
   _fullscreenDoc: null,
 
   init: function() {
     addMessageListener("DOMFullscreen:Approved", this);
     addMessageListener("DOMFullscreen:CleanUp", this);
     addEventListener("MozEnteredDomFullscreen", this);
+    addEventListener("MozExitedDomFullscreen", this);
   },
 
   receiveMessage: function(aMessage) {
     switch(aMessage.name) {
       case "DOMFullscreen:Approved": {
         if (this._fullscreenDoc) {
           Services.obs.notifyObservers(this._fullscreenDoc,
                                        "fullscreen-approved",
@@ -610,12 +611,14 @@ let DOMFullscreenHandler = {
   },
 
   handleEvent: function(aEvent) {
     if (aEvent.type == "MozEnteredDomFullscreen") {
       this._fullscreenDoc = aEvent.target;
       sendAsyncMessage("MozEnteredDomFullscreen", {
         origin: this._fullscreenDoc.nodePrincipal.origin,
       });
+    } else if (aEvent.type == "MozExitedDomFullscreen") {
+      sendAsyncMessage("MozExitedDomFullscreen");
     }
   }
 };
 DOMFullscreenHandler.init();
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -472,8 +472,9 @@ skip-if = e10s # bug 1100687 - test dire
 [browser_contextmenu_childprocess.js]
 [browser_bug963945.js]
 [browser_readerMode.js]
 support-files =
   readerModeArticle.html
 [browser_bug1124271_readerModePinnedTab.js]
 support-files =
   readerModeArticle.html
+[browser_domFullscreen_fullscreenMode.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
@@ -0,0 +1,208 @@
+"use strict";
+
+let gMessageManager;
+
+function frameScript() {
+  addMessageListener("Test:RequestFullscreen", () => {
+    content.document.body.mozRequestFullScreen();
+  });
+  addMessageListener("Test:ExitFullscreen", () => {
+    content.document.mozCancelFullScreen();
+  });
+  addMessageListener("Test:QueryFullscreenState", () => {
+    sendAsyncMessage("Test:FullscreenState", {
+      inDOMFullscreen: content.document.mozFullScreen,
+      inFullscreen: content.fullScreen
+    });
+  });
+  content.document.addEventListener("mozfullscreenchange", () => {
+    sendAsyncMessage("Test:FullscreenChanged", {
+      inDOMFullscreen: content.document.mozFullScreen,
+      inFullscreen: content.fullScreen
+    });
+  });
+  function waitUntilActive() {
+    let doc = content.document;
+    if (doc.docShell.isActive && doc.hasFocus()) {
+      sendAsyncMessage("Test:Activated");
+    } else {
+      setTimeout(waitUntilActive, 10);
+    }
+  }
+  waitUntilActive();
+}
+
+function listenOneMessage(aMsg, aListener) {
+  function listener({ data }) {
+    gMessageManager.removeMessageListener(aMsg, listener);
+    aListener(data);
+  }
+  gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function listenOneEvent(aEvent, aListener) {
+  function listener(evt) {
+    removeEventListener(aEvent, listener);
+    aListener(evt);
+  }
+  addEventListener(aEvent, listener);
+}
+
+function queryFullscreenState() {
+  return new Promise(resolve => {
+    listenOneMessage("Test:FullscreenState", resolve);
+    gMessageManager.sendAsyncMessage("Test:QueryFullscreenState");
+  });
+}
+
+function captureUnexpectedFullscreenChange() {
+  ok(false, "catched an unexpected fullscreen change");
+}
+
+const FS_CHANGE_DOM = 1 << 0;
+const FS_CHANGE_SIZE = 1 << 1;
+const FS_CHANGE_BOTH = FS_CHANGE_DOM | FS_CHANGE_SIZE;
+
+function waitForFullscreenChanges(aFlags) {
+  return new Promise(resolve => {
+    let fullscreenData = null;
+    let sizemodeChanged = false;
+    function tryResolve() {
+      if ((!(aFlags & FS_CHANGE_DOM) || fullscreenData) &&
+          (!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged)) {
+        if (!fullscreenData) {
+          queryFullscreenState().then(resolve);
+        } else {
+          resolve(fullscreenData);
+        }
+      }
+    }
+    if (aFlags & FS_CHANGE_SIZE) {
+      listenOneEvent("sizemodechange", () => {
+        sizemodeChanged = true;
+        tryResolve();
+      });
+    }
+    if (aFlags & FS_CHANGE_DOM) {
+      gMessageManager.removeMessageListener(
+        "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+      listenOneMessage("Test:FullscreenChanged", data => {
+        gMessageManager.addMessageListener(
+          "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+        fullscreenData = data;
+        tryResolve();
+      });
+    }
+  });
+}
+
+let gTests = [
+  {
+    desc: "document method",
+    affectsFullscreenMode: false,
+    exitFunc: () => {
+      gMessageManager.sendAsyncMessage("Test:ExitFullscreen");
+    }
+  },
+  {
+    desc: "escape key",
+    affectsFullscreenMode: false,
+    exitFunc: () => {
+      executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+    }
+  },
+  {
+    desc: "F11 key",
+    affectsFullscreenMode: true,
+    exitFunc: function () {
+      executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+    }
+  }
+];
+
+add_task(function* () {
+  let tab = gBrowser.addTab("about:robots");
+  let browser = tab.linkedBrowser;
+  gBrowser.selectedTab = tab;
+  yield waitForDocLoadComplete();
+
+  registerCleanupFunction(() => {
+    if (browser.contentWindow.fullScreen) {
+      BrowserFullScreen();
+    }
+    gBrowser.removeTab(tab);
+  });
+
+  gMessageManager = browser.messageManager;
+  gMessageManager.loadFrameScript(
+    "data:,(" + frameScript.toString() + ")();", false);
+  gMessageManager.addMessageListener(
+    "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+
+  // Wait for the document being activated, so that
+  // fullscreen request won't be denied.
+  yield new Promise(resolve => listenOneMessage("Test:Activated", resolve));
+
+  for (let test of gTests) {
+    info("Testing exit DOM fullscreen via " + test.desc);
+
+    var { inDOMFullscreen, inFullscreen } = yield queryFullscreenState();
+    ok(!inDOMFullscreen, "Shouldn't have been in DOM fullscreen");
+    ok(!inFullscreen, "Shouldn't have been in fullscreen");
+
+    /* DOM fullscreen without fullscreen mode */
+
+    // Enter DOM fullscreen
+    gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+    var { inDOMFullscreen, inFullscreen } =
+      yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+    ok(inDOMFullscreen, "Should now be in DOM fullscreen");
+    ok(inFullscreen, "Should now be in fullscreen");
+
+    // Exit DOM fullscreen
+    test.exitFunc();
+    var { inDOMFullscreen, inFullscreen } =
+      yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+    ok(!inDOMFullscreen, "Should no longer be in DOM fullscreen");
+    ok(!inFullscreen, "Should no longer be in fullscreen");
+
+    /* DOM fullscreen with fullscreen mode */
+
+    // Enter fullscreen mode
+    // Need to be asynchronous because sizemodechange event could be
+    // dispatched synchronously, which would cause the event listener
+    // miss that event and wait infinitely.
+    executeSoon(() => BrowserFullScreen());
+    var { inDOMFullscreen, inFullscreen } =
+      yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+    ok(!inDOMFullscreen, "Shouldn't have been in DOM fullscreen");
+    ok(inFullscreen, "Should now be in fullscreen mode");
+
+    // Enter DOM fullscreen
+    gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+    var { inDOMFullscreen, inFullscreen } =
+      yield waitForFullscreenChanges(FS_CHANGE_DOM);
+    ok(inDOMFullscreen, "Should now be in DOM fullscreen");
+    ok(inFullscreen, "Should still be in fullscreen");
+
+    // Exit DOM fullscreen
+    test.exitFunc();
+    var { inDOMFullscreen, inFullscreen } =
+      yield waitForFullscreenChanges(test.affectsFullscreenMode ?
+                                     FS_CHANGE_BOTH : FS_CHANGE_DOM);
+    ok(!inDOMFullscreen, "Should no longer be in DOM fullscreen");
+    if (test.affectsFullscreenMode) {
+      ok(!inFullscreen, "Should no longer be in fullscreen mode");
+    } else {
+      ok(inFullscreen, "Should still be in fullscreen mode");
+    }
+
+    /* Cleanup */
+
+    // Exit fullscreen mode if we are still in
+    if (browser.contentWindow.fullScreen) {
+      executeSoon(() => BrowserFullScreen());
+      yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+    }
+  }
+});
--- a/browser/base/content/test/general/browser_windowopen_reflows.js
+++ b/browser/base/content/test/general/browser_windowopen_reflows.js
@@ -18,16 +18,19 @@ const EXPECTED_REFLOWS = [
     "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
 
   // Focusing the content area causes a reflow.
   "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
 
   // Sometimes sessionstore collects data during this test, which causes a sync reflow
   // (https://bugzilla.mozilla.org/show_bug.cgi?id=892154 will fix this)
   "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm",
+
+  // We may get a resize event, see bug 1149555.
+  "PreviewController.prototype.wasResizedSinceLastPreview@resource:///modules/WindowsPreviewPerTab.jsm"
 ];
 
 if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
   // TabsInTitlebar._update causes a reflow on OS X and Windows trying to do calculations
   // since layout info is already dirty. This doesn't seem to happen before
   // MozAfterPaint on Linux.
   EXPECTED_REFLOWS.push("rect@chrome://browser/content/browser.js|" +
                           "TabsInTitlebar._update@chrome://browser/content/browser.js|" +
--- a/browser/base/content/test/social/browser_social_chatwindow_resize.js
+++ b/browser/base/content/test/social/browser_social_chatwindow_resize.js
@@ -67,18 +67,18 @@ var tests = {
         [chatWidth-2, 1, "to < 1 chat width - only last should be visible."],
         [chatWidth+2, 1, "2 pixels more then one fully exposed (not counting popup) - still only 1."],
         [chatWidth+popupWidth+2, 1, "2 pixels more than one fully exposed (including popup) - still only 1."],
         [chatWidth*2-2, 1, "second not showing by 2 pixels (not counting popup) - only 1 exposed."],
         [chatWidth*2+popupWidth-2, 1, "second not showing by 2 pixelx (including popup) - only 1 exposed."],
         [chatWidth*2+popupWidth+2, 2, "big enough to fit 2 - nub remains visible as first is still hidden"],
         [chatWidth*3+popupWidth-2, 2, "one smaller than the size necessary to display all three - first still hidden"],
         [chatWidth*3+popupWidth+2, 3, "big enough to fit all - all exposed (which removes the nub)"],
-        [chatWidth*3+2, 3, "now the nub is hidden we can resize back down to chatWidth*3 before overflow."],
-        [chatWidth*3-2, 2, "2 pixels less and the first is again collapsed (and the nub re-appears)"],
+        [chatWidth*3+4, 3, "now the nub is hidden we can resize back down to chatWidth*3 before overflow."],
+        [chatWidth*3-4, 2, "4 pixels less and the first is again collapsed (and the nub re-appears)"],
         [chatWidth*2+popupWidth+2, 2, "back down to just big enough to fit 2"],
         [chatWidth*2+popupWidth-2, 1, "back down to just not enough to fit 2"],
         [chatWidth*3+popupWidth+2, 3, "now a large jump to make all 3 visible (ie, affects 2)"],
         [chatWidth*1.5, 1, "and a large jump back down to 1 visible (ie, affects 2)"],
       ], function() {
         closeAllChats();
         next();
       });
--- a/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
+++ b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
@@ -1,39 +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/. */
 
 "use strict";
 
+function isFullscreenSizeMode() {
+  let sizemode = document.documentElement.getAttribute("sizemode");
+  return sizemode == "fullscreen";
+}
+
 // Observers should be disabled when in customization mode.
 add_task(function() {
   // Open and close the panel to make sure that the
   // area is generated before getting a child of the area.
   let shownPanelPromise = promisePanelShown(window);
   PanelUI.toggle({type: "command"});
   yield shownPanelPromise;
   let hiddenPanelPromise = promisePanelHidden(window);
   PanelUI.toggle({type: "command"});
   yield hiddenPanelPromise;
 
   let fullscreenButton = document.getElementById("fullscreen-button");
   ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
+  ok(!isFullscreenSizeMode(), "Should not be in fullscreen sizemode before we enter fullscreen.");
 
   BrowserFullScreen();
-  yield waitForCondition(function() fullscreenButton.checked);
+  yield waitForCondition(() => isFullscreenSizeMode());
   ok(fullscreenButton.checked, "Fullscreen button should be checked when in fullscreen.")
 
   yield startCustomizing();
 
   let fullscreenButtonWrapper = document.getElementById("wrapper-fullscreen-button");
   ok(fullscreenButtonWrapper.hasAttribute("itemobserves"), "Observer should be moved to wrapper");
   fullscreenButton = document.getElementById("fullscreen-button");
   ok(!fullscreenButton.hasAttribute("observes"), "Observer should be removed from button");
   ok(!fullscreenButton.checked, "Fullscreen button should no longer be checked during customization mode");
 
   yield endCustomizing();
 
   BrowserFullScreen();
   fullscreenButton = document.getElementById("fullscreen-button");
-  yield waitForCondition(function() !fullscreenButton.checked);
+  yield waitForCondition(() => !isFullscreenSizeMode());
   ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
 });
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -633,24 +633,33 @@ IsOnFullDomainWhitelist(nsIURI* aURI)
   NS_ENSURE_SUCCESS(rv, false);
 
   // NOTE: This static whitelist is expected to be short. If that changes,
   // we should consider a different representation; e.g. hash-set, prefix tree.
   static const nsLiteralCString sFullDomainsOnWhitelist[] = {
     // 0th entry only active when testing:
     NS_LITERAL_CSTRING("test1.example.org"),
     NS_LITERAL_CSTRING("map.baidu.com"),
-    NS_LITERAL_CSTRING("music.baidu.com"),
     NS_LITERAL_CSTRING("3g.163.com"),
     NS_LITERAL_CSTRING("3glogo.gtimg.com"), // for 3g.163.com
     NS_LITERAL_CSTRING("info.3g.qq.com"), // for 3g.qq.com
     NS_LITERAL_CSTRING("3gimg.qq.com"), // for 3g.qq.com
     NS_LITERAL_CSTRING("img.m.baidu.com"), // for [shucheng|ks].baidu.com
     NS_LITERAL_CSTRING("m.mogujie.com"),
     NS_LITERAL_CSTRING("touch.qunar.com"),
+    NS_LITERAL_CSTRING("mjs.sinaimg.cn"), // for sina.cn
+    NS_LITERAL_CSTRING("static.qiyi.com"), // for m.iqiyi.com
+    NS_LITERAL_CSTRING("www.kuaidi100.com"), // for m.kuaidi100.com
+    NS_LITERAL_CSTRING("m.pc6.com"),
+    NS_LITERAL_CSTRING("m.haosou.com"),
+    NS_LITERAL_CSTRING("m.mi.com"),
+    NS_LITERAL_CSTRING("wappass.baidu.com"),
+    NS_LITERAL_CSTRING("m.video.baidu.com"),
+    NS_LITERAL_CSTRING("m.video.baidu.com"),
+    NS_LITERAL_CSTRING("imgcache.gtimg.cn"), // for m.v.qq.com
   };
   static const size_t sNumFullDomainsOnWhitelist =
     MOZ_ARRAY_LENGTH(sFullDomainsOnWhitelist);
 
   // Skip 0th (dummy) entry in whitelist, unless a pref is enabled.
   const size_t firstWhitelistIdx = IsWhitelistingTestDomains() ? 0 : 1;
 
   for (size_t i = firstWhitelistIdx; i < sNumFullDomainsOnWhitelist; ++i) {
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/dom/URLBinding.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIIOService.h"
 #include "nsEscape.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIURL.h"
+#include "nsContentUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(URL)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(URL)
   if (tmp->mSearchParams) {
@@ -37,81 +38,80 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(URL)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(URL)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-URL::URL(nsIURI* aURI)
+URL::URL(already_AddRefed<nsIURI> aURI)
   : mURI(aURI)
 {
 }
 
 bool
 URL::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector)
 {
   return URLBinding::Wrap(aCx, this, aGivenProto, aReflector);
 }
 
 /* static */ already_AddRefed<URL>
 URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
                  URL& aBase, ErrorResult& aRv)
 {
-  nsresult rv;
-  nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-    return nullptr;
+  return Constructor(aUrl, aBase.GetURI(), aRv);
+}
+
+/* static */ already_AddRefed<URL>
+URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
+                 const Optional<nsAString>& aBase, ErrorResult& aRv)
+{
+  if (aBase.WasPassed()) {
+    return Constructor(aUrl, aBase.Value(), aRv);
   }
 
-  nsCOMPtr<nsIURI> uri;
-  rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, aBase.GetURI(),
-                         getter_AddRefs(uri));
-  if (NS_FAILED(rv)) {
-    nsAutoString label(aUrl);
-    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
-    return nullptr;
-  }
-
-  nsRefPtr<URL> url = new URL(uri);
-  return url.forget();
+  return Constructor(aUrl, nullptr, aRv);
 }
 
 /* static */ already_AddRefed<URL>
 URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
-                  const nsAString& aBase, ErrorResult& aRv)
+                 const nsAString& aBase, ErrorResult& aRv)
 {
-  nsresult rv;
-  nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
+  return Constructor(aUrl, aBase, aRv);
+}
+
+/* static */ already_AddRefed<URL>
+URL::Constructor(const nsAString& aUrl, const nsAString& aBase,
+                 ErrorResult& aRv)
+{
+  nsCOMPtr<nsIURI> baseUri;
+  nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr,
+                          nsContentUtils::GetIOService());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.ThrowTypeError(MSG_INVALID_URL, &aBase);
     return nullptr;
   }
 
-  nsCOMPtr<nsIURI> baseUri;
-  rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aBase), nullptr, nullptr,
-                         getter_AddRefs(baseUri));
-  if (NS_FAILED(rv)) {
-    nsAutoString label(aBase);
-    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
+  return Constructor(aUrl, baseUri, aRv);
+}
+
+/* static */
+already_AddRefed<URL>
+URL::Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBase,
+                          nsContentUtils::GetIOService());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.ThrowTypeError(MSG_INVALID_URL, &aUrl);
     return nullptr;
   }
 
-  nsCOMPtr<nsIURI> uri;
-  rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, baseUri,
-                         getter_AddRefs(uri));
-  if (NS_FAILED(rv)) {
-    nsAutoString label(aUrl);
-    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
-    return nullptr;
-  }
-
-  nsRefPtr<URL> url = new URL(uri);
+  nsRefPtr<URL> url = new URL(uri.forget());
   return url.forget();
 }
 
 void
 URL::CreateObjectURL(const GlobalObject& aGlobal,
                      File& aBlob,
                      const objectURLOptions& aOptions,
                      nsAString& aResult,
--- a/dom/base/URL.h
+++ b/dom/base/URL.h
@@ -34,28 +34,36 @@ class URLProxy;
 class URL final : public URLSearchParamsObserver
 {
   ~URL() {}
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(URL)
 
-  explicit URL(nsIURI* aURI);
+  explicit URL(already_AddRefed<nsIURI> aURI);
 
   // WebIDL methods
   bool
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
 
   static already_AddRefed<URL>
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               URL& aBase, ErrorResult& aRv);
   static already_AddRefed<URL>
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
+              const Optional<nsAString>& aBase, ErrorResult& aRv);
+  // Versions of Constructor that we can share with workers and other code.
+  static already_AddRefed<URL>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               const nsAString& aBase, ErrorResult& aRv);
+  static already_AddRefed<URL>
+  Constructor(const nsAString& aUrl, const nsAString& aBase, ErrorResult& aRv);
+  static already_AddRefed<URL>
+  Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv);
 
   static void CreateObjectURL(const GlobalObject& aGlobal,
                               File& aBlob,
                               const objectURLOptions& aOptions,
                               nsAString& aResult,
                               ErrorResult& aError);
   static void CreateObjectURL(const GlobalObject& aGlobal,
                               DOMMediaStream& aStream,
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -11261,16 +11261,22 @@ ExitFullscreenInDocTree(nsIDocument* aMa
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
 
   NS_ASSERTION(!root->IsFullScreenDoc(),
     "Fullscreen root should no longer be a fullscreen doc...");
 
+  // Dispatch MozExitedDomFullscreen to the last document in
+  // the list since we want this event to follow the same path
+  // MozEnteredDomFullscreen dispatched.
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
+    changed.LastElement(), NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true);
+  asyncDispatcher->PostDOMEvent();
   // Move the top-level window out of fullscreen mode.
   SetWindowFullScreen(root, false);
 }
 
 /* static */
 void
 nsDocument::ExitFullscreen(nsIDocument* aDoc)
 {
@@ -11417,16 +11423,19 @@ nsDocument::RestorePreviousFullScreenSta
     }
   }
 
   if (doc == nullptr) {
     // We moved all documents in this doctree out of fullscreen mode,
     // move the top-level window out of fullscreen mode.
     NS_ASSERTION(!GetFullscreenRootDocument(this)->IsFullScreenDoc(),
       "Should have cleared all docs' stacks");
+    nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
+      this, NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true);
+    asyncDispatcher->PostDOMEvent();
     SetWindowFullScreen(this, false);
   }
 }
 
 bool
 nsDocument::IsFullScreenDoc()
 {
   return GetFullScreenElement() != nullptr;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1079,16 +1079,17 @@ NewOuterWindowProxy(JSContext *cx, JS::H
 nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
   : nsPIDOMWindow(aOuterWindow),
     mIdleFuzzFactor(0),
     mIdleCallbackIndex(-1),
     mCurrentlyIdle(false),
     mAddActiveEventFuzzTime(true),
     mIsFrozen(false),
     mFullScreen(false),
+    mFullscreenMode(false),
     mIsClosed(false),
     mInClose(false),
     mHavePendingClose(false),
     mHadOriginalOpener(false),
     mIsPopupSpam(false),
     mBlockScriptedClosingFlag(false),
     mWasOffline(false),
     mNotifyIdleObserversIdleOnThaw(false),
@@ -6077,16 +6078,32 @@ nsGlobalWindow::SetFullScreenInternal(bo
   // which might happen in embedding world
   if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
     return NS_ERROR_FAILURE;
 
   // If we are already in full screen mode, just return.
   if (mFullScreen == aFullScreen)
     return NS_OK;
 
+  // If a fullscreen is originated from chrome, we are switching to
+  // the fullscreen mode, otherwise, we are entering DOM fullscreen.
+  // Note that although entering DOM fullscreen could also cause
+  // consequential calls to this method, those calls will be skipped
+  // at the condition above.
+  if (aRequireTrust) {
+    mFullscreenMode = aFullScreen;
+  } else {
+    // If we are exiting from DOM fullscreen while we
+    // initially make the window fullscreen because of
+    // fullscreen mode, don't restore the window.
+    if (!aFullScreen && mFullscreenMode) {
+      return NS_OK;
+    }
+  }
+
   // dispatch a "fullscreen" DOM event so that XUL apps can
   // respond visually if we are kicked into full screen mode
   if (!DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"))) {
     return NS_OK;
   }
 
   // Prevent chrome documents which are still loading from resizing
   // the window after we set fullscreen mode.
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1533,16 +1533,17 @@ protected:
   // change. On outer windows it means that the window is in a state
   // where we don't want to force creation of a new inner window since
   // we're in the middle of doing just that.
   bool                          mIsFrozen : 1;
 
   // These members are only used on outer window objects. Make sure
   // you never set any of these on an inner object!
   bool                          mFullScreen : 1;
+  bool                          mFullscreenMode : 1;
   bool                          mIsClosed : 1;
   bool                          mInClose : 1;
   // mHavePendingClose means we've got a termination function set to
   // close us when the JS stops executing or that we have a close
   // event posted.  If this is set, just ignore window.close() calls.
   bool                          mHavePendingClose : 1;
   bool                          mHadOriginalOpener : 1;
   bool                          mIsPopupSpam : 1;
--- a/dom/base/test/test_reentrant_flush.html
+++ b/dom/base/test/test_reentrant_flush.html
@@ -43,19 +43,18 @@ addLoadEvent(function() {
   win.addEventListener("resize", handleResize, false);
   SpecialPowers.setFullZoom(win, 2);
 
   is(resizeHandlerRan, false,
      "Resize handler should not have run yet for this test to be valid");
 
   // Now flush out layout on the subdocument, to trigger the resize handler
   is(bod.getBoundingClientRect().width, 50, "Width of body should still be 50px");
-
-  is(resizeHandlerRan, true, "Resize handler should have run");
-
-  win.removeEventListener("resize", handleResize, false);
-
-  SimpleTest.finish();
+  window.requestAnimationFrame(function() {
+    is(resizeHandlerRan, true, "Resize handler should have run");
+    win.removeEventListener("resize", handleResize, false);
+    SimpleTest.finish();
+  });
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -141,17 +141,17 @@ ErrorResult::ThrowErrorWithMessage(va_li
   }
   mResult = errorType;
   Message* message = new Message();
   message->mErrorNumber = errorNumber;
   uint16_t argCount = dom::GetErrorMessage(nullptr, errorNumber)->argCount;
   MOZ_ASSERT(argCount <= 10);
   argCount = std::min<uint16_t>(argCount, 10);
   while (argCount--) {
-    message->mArgs.AppendElement(*va_arg(ap, nsString*));
+    message->mArgs.AppendElement(*va_arg(ap, const nsAString*));
   }
   mMessage = message;
 #ifdef DEBUG
   mHasMessage = true;
 #endif
 }
 
 void
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1044,17 +1044,17 @@ WrapNewBindingNonWrapperCachedObject(JSC
     if (!value->WrapObject(cx, JS::NullPtr(), &obj)) {
       return false;
     }
   }
 
   // We can end up here in all sorts of compartments, per above.  Make
   // sure to JS_WrapValue!
   rval.set(JS::ObjectValue(*obj));
-  return JS_WrapValue(cx, rval);
+  return MaybeWrapObjectValue(cx, rval);
 }
 
 // Create a JSObject wrapping "value", for cases when "value" is a
 // non-wrapper-cached owned object using WebIDL bindings.  "value" must implement a
 // WrapObject() method taking a JSContext, a scope, and a boolean outparam that
 // is true if the JSObject took ownership
 template <class T>
 inline bool
@@ -1092,17 +1092,17 @@ WrapNewBindingNonWrapperCachedObject(JSC
     }
 
     value.forget();
   }
 
   // We can end up here in all sorts of compartments, per above.  Make
   // sure to JS_WrapValue!
   rval.set(JS::ObjectValue(*obj));
-  return JS_WrapValue(cx, rval);
+  return MaybeWrapObjectValue(cx, rval);
 }
 
 // Helper for smart pointers (nsRefPtr/nsCOMPtr).
 template <template <typename> class SmartPtr, typename T,
           typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type>
 inline bool
 WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope,
                                      const SmartPtr<T>& value,
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -309,16 +309,40 @@ CacheStorage::Keys(ErrorResult& aRv)
 
 // static
 bool
 CacheStorage::PrefEnabled(JSContext* aCx, JSObject* aObj)
 {
   return Cache::PrefEnabled(aCx, aObj);
 }
 
+// static
+already_AddRefed<CacheStorage>
+CacheStorage::Constructor(const GlobalObject& aGlobal,
+                          CacheStorageNamespace aNamespace,
+                          nsIPrincipal* aPrincipal, ErrorResult& aRv)
+{
+  if (NS_WARN_IF(!NS_IsMainThread())) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  // TODO: remove Namespace in favor of CacheStorageNamespace
+  static_assert(DEFAULT_NAMESPACE == (uint32_t)CacheStorageNamespace::Content,
+                "Default namespace should match webidl Content enum");
+  static_assert(CHROME_ONLY_NAMESPACE == (uint32_t)CacheStorageNamespace::Chrome,
+                "Chrome namespace should match webidl Chrome enum");
+  static_assert(NUMBER_OF_NAMESPACES == (uint32_t)CacheStorageNamespace::EndGuard_,
+                "Number of namespace should match webidl endguard enum");
+
+  Namespace ns = static_cast<Namespace>(aNamespace);
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  return CreateOnMainThread(ns, global, aPrincipal, aRv);
+}
+
 nsISupports*
 CacheStorage::GetParentObject() const
 {
   return mGlobal;
 }
 
 JSObject*
 CacheStorage::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
--- a/dom/cache/CacheStorage.h
+++ b/dom/cache/CacheStorage.h
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_dom_cache_CacheStorage_h
 #define mozilla_dom_cache_CacheStorage_h
 
-#include "mozilla/dom/CacheBinding.h"
 #include "mozilla/dom/cache/Types.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
@@ -24,16 +23,17 @@ namespace mozilla {
 class ErrorResult;
 
 namespace ipc {
   class PrincipalInfo;
 }
 
 namespace dom {
 
+enum class CacheStorageNamespace : uint32_t;
 class Promise;
 
 namespace workers {
   class WorkerPrivate;
 }
 
 namespace cache {
 
@@ -59,16 +59,21 @@ public:
   already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
                                   const CacheQueryOptions& aOptions,
                                   ErrorResult& aRv);
   already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);
   already_AddRefed<Promise> Keys(ErrorResult& aRv);
 
+  // chrome-only webidl interface methods
+  static already_AddRefed<CacheStorage>
+  Constructor(const GlobalObject& aGlobal, CacheStorageNamespace aNamespace,
+              nsIPrincipal* aPrincipal, ErrorResult& aRv);
+
   // binding methods
   static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
 
   nsISupports* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto) override;
 
   // nsIIPCbackgroundChildCreateCallback methods
   virtual void ActorCreated(PBackgroundChild* aActor) override;
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -89,8 +89,12 @@ LOCAL_INCLUDES += [
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest/mochitest.ini',
 ]
+
+MOCHITEST_CHROME_MANIFESTS += [
+    'test/mochitest/chrome.ini',
+]
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/chrome.ini
@@ -0,0 +1,1 @@
+[test_chrome_constructor.html]
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_chrome_constructor.html
@@ -0,0 +1,43 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Validate Interfaces Exposed to Workers</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+  var Cu = Components.utils;
+  Cu.import('resource://gre/modules/Services.jsm');
+  SimpleTest.waitForExplicitFinish();
+
+  // attach to a different origin's CacheStorage
+  var url = 'http://example.com/';
+  var uri = Services.io.newURI(url, null, null);
+  var principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+  var storage = new CacheStorage('content', principal);
+
+  // verify we can use the other origin's CacheStorage as normal
+  var req = new Request('http://example.com/index.html');
+  var res = new Response('hello world');
+  var cache;
+  storage.open('foo').then(function(c) {
+    cache = c;
+    ok(cache, 'storage should create cache');
+    return cache.put(req, res.clone());
+  }).then(function() {
+    return cache.match(req);
+  }).then(function(foundResponse) {
+    return Promise.all([res.text(), foundResponse.text()]);
+  }).then(function(results) {
+    is(results[0], results[1], 'cache should contain response');
+    return storage.delete('foo');
+  }).then(function(deleted) {
+    ok(deleted, 'storage should delete cache');
+    SimpleTest.finish();
+  });
+</script>
+</body>
+</html>
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -16,16 +16,17 @@ fail-if = (os == 'b2g')
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
 [webgl-mochitest/test_implicit_color_buffer_float.html]
 [webgl-mochitest/test_highp_fs.html]
 [webgl-mochitest/test_no_arr_points.html]
 skip-if = android_version == '10' || android_version == '18' #Android 2.3 and 4.3 aws only; bug 1030942
 [webgl-mochitest/test_noprog_draw.html]
 [webgl-mochitest/test_privileged_exts.html]
 [webgl-mochitest/test_texsubimage_float.html]
+[webgl-mochitest/test_uninit_data.html]
 [webgl-mochitest/test_webgl_available.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 #[webgl-mochitest/test_webgl_color_buffer_float.html]
 # We haven't cleaned up the Try results yet, but let's get this on the books first.
 [webgl-mochitest/test_webgl_conformance.html]
 skip-if = buildapp == 'mulet' || toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl_request_context.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_uninit_data.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv='content-type' content='text/html; charset=utf-8'/>
+
+  <title>Test contents of uninitialized buffers</title>
+
+  <script src='/tests/SimpleTest/SimpleTest.js'></script>
+  <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+  <script src='webgl-util.js'></script>
+</head>
+
+<body>
+<script>
+'use strict';
+
+function TestFB(gl) {
+  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+  ok(status == gl.FRAMEBUFFER_COMPLETE, 'FB should be complete.');
+
+  var pixel = new Uint8Array(4);
+  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
+
+  ok(!pixel[0], 'R channel should be 0, was ' + pixel[0] + '.');
+  ok(!pixel[1], 'G channel should be 0, was ' + pixel[1] + '.');
+  ok(!pixel[2], 'B channel should be 0, was ' + pixel[2] + '.');
+  ok(!pixel[3], 'A channel should be 0, was ' + pixel[3] + '.');
+}
+
+function Test(contextAttribs) {
+  ok(true, '===============================');
+  ok(true, 'Testing ' + JSON.stringify(contextAttribs));
+
+  var c = document.createElement('canvas');
+  var gl = c.getContext('webgl', contextAttribs);
+  if (!gl) {
+    todo(false, 'WebGL is unavailable.');
+    return;
+  }
+
+  var rb = gl.createRenderbuffer();
+  gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+
+  var err = gl.getError();
+  ok(!err, 'Error should be 0x0, was 0x' + err.toString(16));
+  if (err)
+    return;
+
+  var fb = gl.createFramebuffer();
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+  ok(true, 'Backed with RB');
+  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+  gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 1, 1);
+  TestFB(gl);
+
+  ok(true, 'Backed with texture');
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+  TestFB(gl);
+
+  err = gl.getError();
+  ok(!err, 'Error should be 0x0, was 0x' + err.toString(16));
+}
+
+// Give ourselves a scope to return early from:
+(function() {
+  // We test multiple configurations because we've had bugs regarding faking RGBX on
+  // ANGLE: With alpha:false, uninitialized buffers were being filled with (0,0,0,1)
+  // instead of (0,0,0,0).
+  Test({alpha: false, antialias: false});
+  Test({alpha: true, antialias: false});
+  Test({alpha: false, antialias: true});
+  Test({alpha: true, antialias: true});
+
+  ok(true, 'Test complete.');
+})();
+
+</script>
+</body>
+</html>
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1523,16 +1523,19 @@ EventStateManager::BeginTrackingDragGest
   // synthesized mouse move event.
   mGestureDownPoint = inDownEvent->refPoint + inDownEvent->widget->WidgetToScreenOffset();
 
   if (inDownFrame) {
     inDownFrame->GetContentForEvent(inDownEvent,
                                     getter_AddRefs(mGestureDownContent));
 
     mGestureDownFrameOwner = inDownFrame->GetContent();
+    if (!mGestureDownFrameOwner) {
+      mGestureDownFrameOwner = mGestureDownContent;
+    }
   }
   mGestureModifiers = inDownEvent->modifiers;
   mGestureDownButtons = inDownEvent->buttons;
 
   if (Prefs::ClickHoldContextMenu()) {
     // fire off a timer to track click-hold
     CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
   }
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1561,23 +1561,16 @@ nsHTMLDocument::Open(JSContext* cx,
                      nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
                      nsIContentPolicy::TYPE_OTHER,
                      group);
 
   if (rv.Failed()) {
     return nullptr;
   }
 
-  // We can't depend on channels implementing property bags, so do our
-  // base URI manually after reset.
-
-  if (rv.Failed()) {
-    return nullptr;
-  }
-
   if (callerChannel) {
     nsLoadFlags callerLoadFlags;
     rv = callerChannel->GetLoadFlags(&callerLoadFlags);
     if (rv.Failed()) {
       return nullptr;
     }
 
     nsLoadFlags loadFlags;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -46,17 +46,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(9bb4a34f-de8f-43d3-a58c-10757cac95a0)]
+[scriptable, uuid(1a75c351-d115-4d51-94df-731dd1723a1f)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -609,16 +609,21 @@ interface nsIDOMWindowUtils : nsISupport
   /**
    * If MOUSESCROLL_PREFER_WIDGET_AT_POINT is set, widget will dispatch
    * the event to a widget which is under the cursor.  Otherwise, dispatch to
    * a default target on the platform.  E.g., on Windows, it's focused window.
    */
   const unsigned long MOUSESCROLL_PREFER_WIDGET_AT_POINT = 0x00000001;
 
   /**
+   * Interpret the scroll delta values as lines rather than pixels.
+   */
+  const unsigned long MOUSESCROLL_SCROLL_LINES = 0x00000002;
+
+  /**
    * The platform specific values of aAdditionalFlags.  Must be over 0x00010000.
    */
 
   /**
    * If MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL is set and aNativeMessage is
    * WM_VSCROLL or WM_HSCROLL, widget will set the window handle to the lParam
    * instead of NULL.
    */
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -540,17 +540,17 @@ child:
          nullable PRenderFrame renderFrame,
          bool parentIsActive);
 
     LoadURL(nsCString uri, BrowserConfiguration config);
 
     CacheFileDescriptor(nsString path, FileDescriptor fd);
 
     UpdateDimensions(IntRect rect, ScreenIntSize size, ScreenOrientation orientation,
-                     LayoutDeviceIntPoint chromeDisp) compress;
+                     LayoutDeviceIntPoint chromeDisp) compressall;
 
     UpdateFrame(FrameMetrics frame);
 
     // The following methods correspond to functions on the GeckoContentController
     // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
     // in that file for these functions.
     RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
     AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -929,21 +929,30 @@ MediaDecoderStateMachine::OnNotDecoded(M
     return;
   }
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
+    nsRefPtr<MediaDecoderStateMachine> self = this;
     WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                                &MediaDecoderReader::WaitForData, aType)
-      ->RefableThen(TaskQueue(), __func__, this,
-                    &MediaDecoderStateMachine::OnWaitForDataResolved,
-                    &MediaDecoderStateMachine::OnWaitForDataRejected));
+      ->RefableThen(TaskQueue(), __func__,
+                    [self] (MediaData::Type aType) -> void {
+                      ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+                      self->WaitRequestRef(aType).Complete();
+                      self->DispatchDecodeTasksIfNeeded();
+                    },
+                    [self] (WaitForDataRejectValue aRejection) -> void {
+                      ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+                      self->WaitRequestRef(aRejection.mType).Complete();
+                    }));
+
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
   }
 
@@ -1896,22 +1905,34 @@ MediaDecoderStateMachine::InitiateSeek()
         &MediaDecoder::SeekingStarted,
         mCurrentSeek.mTarget.mEventVisibility);
   AbstractThread::MainThread()->Dispatch(startEvent.forget());
 
   // Reset our state machine and decoding pipeline before seeking.
   Reset();
 
   // Do the seek.
+  nsRefPtr<MediaDecoderStateMachine> self = this;
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
                                     GetEndTime())
-    ->RefableThen(TaskQueue(), __func__, this,
-                  &MediaDecoderStateMachine::OnSeekCompleted,
-                  &MediaDecoderStateMachine::OnSeekFailed));
+    ->RefableThen(TaskQueue(), __func__,
+                  [self] (int64_t) -> void {
+                    ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+                    self->mSeekRequest.Complete();
+                    // We must decode the first samples of active streams, so we can determine
+                    // the new stream time. So dispatch tasks to do that.
+                    self->mDecodeToSeekTarget = true;
+                    self->DispatchDecodeTasksIfNeeded();
+                  }, [self] (nsresult aResult) -> void {
+                    ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+                    self->mSeekRequest.Complete();
+                    MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
+                    self->DecodeError();
+                  }));
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
@@ -2382,40 +2403,16 @@ MediaDecoderStateMachine::FinishDecodeFi
     SetState(DECODER_STATE_SEEKING);
     ScheduleStateMachine();
   }
 
   return NS_OK;
 }
 
 void
-MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime)
-{
-  MOZ_ASSERT(OnTaskQueue());
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mSeekRequest.Complete();
-
-  // We must decode the first samples of active streams, so we can determine
-  // the new stream time. So dispatch tasks to do that.
-  mDecodeToSeekTarget = true;
-
-  DispatchDecodeTasksIfNeeded();
-}
-
-void
-MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
-{
-  MOZ_ASSERT(OnTaskQueue());
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mSeekRequest.Complete();
-  MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
-  DecodeError();
-}
-
-void
 MediaDecoderStateMachine::SeekCompleted()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(mState == DECODER_STATE_SEEKING);
 
   int64_t seekTime = mCurrentSeek.mTarget.mTime;
   int64_t newCurrentTime = seekTime;
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -422,32 +422,16 @@ public:
   {
     OnNotDecoded(MediaData::AUDIO_DATA, aReason);
   }
   void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     OnNotDecoded(MediaData::VIDEO_DATA, aReason);
   }
 
-  void OnSeekCompleted(int64_t aTime);
-  void OnSeekFailed(nsresult aResult);
-
-  void OnWaitForDataResolved(MediaData::Type aType)
-  {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    WaitRequestRef(aType).Complete();
-    DispatchDecodeTasksIfNeeded();
-  }
-
-  void OnWaitForDataRejected(WaitForDataRejectValue aRejection)
-  {
-    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    WaitRequestRef(aRejection.mType).Complete();
-  }
-
   // Resets all state related to decoding and playback, emptying all buffers
   // and aborting all pending operations on the decode task queue.
   void Reset();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -44,16 +44,41 @@ extern PRLogModuleInfo* gMediaPromiseLog
  */
 template<typename T> class MediaPromiseHolder;
 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
 class MediaPromise
 {
 public:
   typedef ResolveValueT ResolveValueType;
   typedef RejectValueT RejectValueType;
+  class ResolveOrRejectValue
+  {
+  public:
+    void SetResolve(ResolveValueType& aResolveValue)
+    {
+      MOZ_ASSERT(IsNothing());
+      mResolveValue.emplace(aResolveValue);
+    }
+
+    void SetReject(RejectValueType& aRejectValue)
+    {
+      MOZ_ASSERT(IsNothing());
+      mRejectValue.emplace(aRejectValue);
+    }
+
+    bool IsResolve() const { return mResolveValue.isSome(); }
+    bool IsReject() const { return mRejectValue.isSome(); }
+    bool IsNothing() const { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
+    ResolveValueType& ResolveValue() { return mResolveValue.ref(); }
+    RejectValueType& RejectValue() { return mRejectValue.ref(); }
+
+  private:
+    Maybe<ResolveValueType> mResolveValue;
+    Maybe<RejectValueType> mRejectValue;
+  };
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPromise)
 
 protected:
   // MediaPromise is the public type, and never constructed directly. Construct
   // a MediaPromise::Private, defined below.
   explicit MediaPromise(const char* aCreationSite)
     : mCreationSite(aCreationSite)
@@ -114,74 +139,84 @@ protected:
    * A ThenValue tracks a single consumer waiting on the promise. When a consumer
    * invokes promise->Then(...), a ThenValue is created. Once the Promise is
    * resolved or rejected, a {Resolve,Reject}Runnable is dispatched, which
    * invokes the resolve/reject method and then deletes the ThenValue.
    */
   class ThenValueBase : public Consumer
   {
   public:
-    class ResolveRunnable : public nsRunnable
+    class ResolveOrRejectRunnable : public nsRunnable
     {
     public:
-      ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue)
+      ResolveOrRejectRunnable(ThenValueBase* aThenValue, ResolveOrRejectValue& aValue)
         : mThenValue(aThenValue)
-        , mResolveValue(aResolveValue) {}
+        , mValue(aValue) {}
 
-      ~ResolveRunnable()
+      ~ResolveOrRejectRunnable()
       {
         MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
       }
 
       NS_IMETHODIMP Run()
       {
-        PROMISE_LOG("ResolveRunnable::Run() [this=%p]", this);
-        mThenValue->DoResolve(mResolveValue);
+        PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this);
+        mThenValue->DoResolveOrReject(mValue);
         mThenValue = nullptr;
         return NS_OK;
       }
 
     private:
       nsRefPtr<ThenValueBase> mThenValue;
-      ResolveValueType mResolveValue;
+      ResolveOrRejectValue mValue;
     };
 
-    class RejectRunnable : public nsRunnable
+    explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite)
+      : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {}
+
+    void Dispatch(MediaPromise *aPromise)
     {
-    public:
-      RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue)
-        : mThenValue(aThenValue)
-        , mRejectValue(aRejectValue) {}
+      aPromise->mMutex.AssertCurrentThreadOwns();
+      MOZ_ASSERT(!aPromise->IsPending());
+
+      nsRefPtr<nsRunnable> runnable =
+        static_cast<nsRunnable*>(new (typename ThenValueBase::ResolveOrRejectRunnable)(this, aPromise->mValue));
+      PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
+                  aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
+                  runnable.get(), aPromise, this);
+
+      // Promise consumers are allowed to disconnect the Consumer object and
+      // then shut down the thread or task queue that the promise result would
+      // be dispatched on. So we unfortunately can't assert that promise
+      // dispatch succeeds. :-(
+      mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
+    }
 
-      ~RejectRunnable()
-      {
-        MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected());
+    virtual void Disconnect() override
+    {
+      MOZ_ASSERT(ThenValueBase::mResponseTarget->IsCurrentThreadIn());
+      MOZ_DIAGNOSTIC_ASSERT(!Consumer::mComplete);
+      Consumer::mDisconnected = true;
+    }
+
+  protected:
+    virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0;
+
+    void DoResolveOrReject(ResolveOrRejectValue& aValue)
+    {
+      Consumer::mComplete = true;
+      if (Consumer::mDisconnected) {
+        PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this);
+        return;
       }
 
-      NS_IMETHODIMP Run()
-      {
-        PROMISE_LOG("RejectRunnable::Run() [this=%p]", this);
-        mThenValue->DoReject(mRejectValue);
-        mThenValue = nullptr;
-        return NS_OK;
-      }
+      DoResolveOrRejectInternal(aValue);
+    }
 
-    private:
-      nsRefPtr<ThenValueBase> mThenValue;
-      RejectValueType mRejectValue;
-    };
-
-    explicit ThenValueBase(const char* aCallSite) : mCallSite(aCallSite) {}
-
-    virtual void Dispatch(MediaPromise *aPromise) = 0;
-
-  protected:
-    virtual void DoResolve(ResolveValueType aResolveValue) = 0;
-    virtual void DoReject(RejectValueType aRejectValue) = 0;
-
+    nsRefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
     const char* mCallSite;
   };
 
   /*
    * We create two overloads for invoking Resolve/Reject Methods so as to
    * make the resolve/reject value argument "optional".
    */
 
@@ -202,158 +237,182 @@ protected:
 
   template<typename ThisType, typename ValueType>
   static void InvokeCallbackMethod(ThisType* aThisVal, void(ThisType::*aMethod)(), ValueType aValue)
   {
       ((*aThisVal).*aMethod)();
   }
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
-  class ThenValue : public ThenValueBase
+  class MethodThenValue : public ThenValueBase
   {
   public:
-    ThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
-              ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
-              const char* aCallSite)
-      : ThenValueBase(aCallSite)
-      , mResponseTarget(aResponseTarget)
+    MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
+                    ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
+                    const char* aCallSite)
+      : ThenValueBase(aResponseTarget, aCallSite)
       , mThisVal(aThisVal)
       , mResolveMethod(aResolveMethod)
       , mRejectMethod(aRejectMethod) {}
 
-    void Dispatch(MediaPromise *aPromise) override
-    {
-      aPromise->mMutex.AssertCurrentThreadOwns();
-      MOZ_ASSERT(!aPromise->IsPending());
-      bool resolved = aPromise->mResolveValue.isSome();
-      nsRefPtr<nsRunnable> runnable =
-        resolved ? static_cast<nsRunnable*>(new (typename ThenValueBase::ResolveRunnable)(this, aPromise->mResolveValue.ref()))
-                 : static_cast<nsRunnable*>(new (typename ThenValueBase::RejectRunnable)(this, aPromise->mRejectValue.ref()));
-      PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
-                  resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
-                  runnable.get(), aPromise, this);
-
-      // Promise consumers are allowed to disconnect the Consumer object and
-      // then shut down the thread or task queue that the promise result would
-      // be dispatched on. So we unfortunately can't assert that promise
-      // dispatch succeeds. :-(
-      mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
-    }
-
-#ifdef DEBUG
-  void AssertOnDispatchThread()
-  {
-    MOZ_ASSERT(mResponseTarget->IsCurrentThreadIn());
-  }
-#else
-  void AssertOnDispatchThread() {}
-#endif
-
   virtual void Disconnect() override
   {
-    AssertOnDispatchThread();
-    MOZ_DIAGNOSTIC_ASSERT(!Consumer::mComplete);
-    Consumer::mDisconnected = true;
+    ThenValueBase::Disconnect();
 
     // If a Consumer has been disconnected, we don't guarantee that the
     // resolve/reject runnable will be dispatched. Null out our refcounted
     // this-value now so that it's released predictably on the dispatch thread.
     mThisVal = nullptr;
   }
 
   protected:
-    virtual void DoResolve(ResolveValueType aResolveValue) override
+    virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
     {
-      Consumer::mComplete = true;
-      if (Consumer::mDisconnected) {
-        MOZ_ASSERT(!mThisVal);
-        PROMISE_LOG("ThenValue::DoResolve disconnected - bailing out [this=%p]", this);
-        return;
+      if (aValue.IsResolve()) {
+        InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aValue.ResolveValue());
+      } else {
+        InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aValue.RejectValue());
       }
-      InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aResolveValue);
-
-      // Null out mThisVal after invoking the callback so that any references are
-      // released predictably on the dispatch thread. Otherwise, it would be
-      // released on whatever thread last drops its reference to the ThenValue,
-      // which may or may not be ok.
-      mThisVal = nullptr;
-    }
-
-    virtual void DoReject(RejectValueType aRejectValue) override
-    {
-      Consumer::mComplete = true;
-      if (Consumer::mDisconnected) {
-        MOZ_ASSERT(!mThisVal);
-        PROMISE_LOG("ThenValue::DoReject disconnected - bailing out [this=%p]", this);
-        return;
-      }
-      InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aRejectValue);
 
       // Null out mThisVal after invoking the callback so that any references are
       // released predictably on the dispatch thread. Otherwise, it would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mThisVal = nullptr;
     }
 
   private:
-    nsRefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
     nsRefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
     ResolveMethodType mResolveMethod;
     RejectMethodType mRejectMethod;
   };
+
+  // NB: We could use std::function here instead of a template if it were supported. :-(
+  template<typename ResolveFunction, typename RejectFunction>
+  class FunctionThenValue : public ThenValueBase
+  {
+  public:
+    FunctionThenValue(AbstractThread* aResponseTarget,
+                      ResolveFunction&& aResolveFunction,
+                      RejectFunction&& aRejectFunction,
+                      const char* aCallSite)
+      : ThenValueBase(aResponseTarget, aCallSite)
+    {
+      mResolveFunction.emplace(Move(aResolveFunction));
+      mRejectFunction.emplace(Move(aRejectFunction));
+    }
+
+  virtual void Disconnect() override
+  {
+    ThenValueBase::Disconnect();
+
+    // If a Consumer has been disconnected, we don't guarantee that the
+    // resolve/reject runnable will be dispatched. Destroy our callbacks
+    // now so that any references in closures are released predictable on
+    // the dispatch thread.
+    mResolveFunction.reset();
+    mRejectFunction.reset();
+  }
+
+  protected:
+    virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    {
+      if (aValue.IsResolve()) {
+        mResolveFunction.ref()(aValue.ResolveValue());
+      } else {
+        mRejectFunction.ref()(aValue.RejectValue());
+      }
+
+      // Destroy callbacks after invocation so that any references in closures are
+      // released predictably on the dispatch thread. Otherwise, they would be
+      // released on whatever thread last drops its reference to the ThenValue,
+      // which may or may not be ok.
+      mResolveFunction.reset();
+      mRejectFunction.reset();
+    }
+
+  private:
+    Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
+    Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
+  };
+
+public:
+  void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
+                    const char* aCallSite)
+  {
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(aResponseThread->IsDispatchReliable());
+    MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveConsumer);
+    mHaveConsumer = true;
+    PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
+                aCallSite, this, aThenValue, (int) IsPending());
+    if (!IsPending()) {
+      aThenValue->Dispatch(this);
+    } else {
+      mThenValues.AppendElement(aThenValue);
+    }
+  }
+
 public:
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   already_AddRefed<Consumer> RefableThen(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
                                          ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
   {
-    MutexAutoLock lock(mMutex);
-    MOZ_ASSERT(aResponseThread->IsDispatchReliable());
-    MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveConsumer);
-    mHaveConsumer = true;
-    nsRefPtr<ThenValueBase> thenValue = new ThenValue<ThisType, ResolveMethodType, RejectMethodType>(
+    nsRefPtr<ThenValueBase> thenValue = new MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>(
                                               aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite);
-    PROMISE_LOG("%s invoking Then() [this=%p, thenValue=%p, aThisVal=%p, isPending=%d]",
-                aCallSite, this, thenValue.get(), aThisVal, (int) IsPending());
-    if (!IsPending()) {
-      thenValue->Dispatch(this);
-    } else {
-      mThenValues.AppendElement(thenValue);
-    }
+    ThenInternal(aResponseThread, thenValue, aCallSite);
+    return thenValue.forget();
+  }
 
+  template<typename ResolveFunction, typename RejectFunction>
+  already_AddRefed<Consumer> RefableThen(AbstractThread* aResponseThread, const char* aCallSite,
+                                         ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+  {
+    nsRefPtr<ThenValueBase> thenValue = new FunctionThenValue<ResolveFunction, RejectFunction>(aResponseThread,
+                                              Move(aResolveFunction), Move(aRejectFunction), aCallSite);
+    ThenInternal(aResponseThread, thenValue, aCallSite);
     return thenValue.forget();
   }
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   void Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
             ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
   {
     nsRefPtr<Consumer> c =
       RefableThen(aResponseThread, aCallSite, aThisVal, aResolveMethod, aRejectMethod);
     return;
   }
 
+  template<typename ThisType, typename ResolveFunction, typename RejectFunction>
+  void Then(AbstractThread* aResponseThread, const char* aCallSite,
+            ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+  {
+    nsRefPtr<Consumer> c =
+      RefableThen(aResponseThread, aCallSite, Move(aResolveFunction), Move(aRejectFunction));
+    return;
+  }
+
   void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
   {
     MutexAutoLock lock(mMutex);
     MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveConsumer);
     mHaveConsumer = true;
     nsRefPtr<Private> chainedPromise = aChainedPromise;
     PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
                 aCallSite, this, chainedPromise.get(), (int) IsPending());
     if (!IsPending()) {
       ForwardTo(chainedPromise);
     } else {
       mChainedPromises.AppendElement(chainedPromise);
     }
   }
 
 protected:
-  bool IsPending() { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
+  bool IsPending() { return mValue.IsNothing(); }
   void DispatchAll()
   {
     mMutex.AssertCurrentThreadOwns();
     for (size_t i = 0; i < mThenValues.Length(); ++i) {
       mThenValues[i]->Dispatch(this);
     }
     mThenValues.Clear();
 
@@ -361,35 +420,34 @@ protected:
       ForwardTo(mChainedPromises[i]);
     }
     mChainedPromises.Clear();
   }
 
   void ForwardTo(Private* aOther)
   {
     MOZ_ASSERT(!IsPending());
-    if (mResolveValue.isSome()) {
-      aOther->Resolve(mResolveValue.ref(), "<chained promise>");
+    if (mValue.IsResolve()) {
+      aOther->Resolve(mValue.ResolveValue(), "<chained promise>");
     } else {
-      aOther->Reject(mRejectValue.ref(), "<chained promise>");
+      aOther->Reject(mValue.RejectValue(), "<chained promise>");
     }
   }
 
   ~MediaPromise()
   {
     PROMISE_LOG("MediaPromise::~MediaPromise [this=%p]", this);
     MOZ_ASSERT(!IsPending());
     MOZ_ASSERT(mThenValues.IsEmpty());
     MOZ_ASSERT(mChainedPromises.IsEmpty());
   };
 
   const char* mCreationSite; // For logging
   Mutex mMutex;
-  Maybe<ResolveValueType> mResolveValue;
-  Maybe<RejectValueType> mRejectValue;
+  ResolveOrRejectValue mValue;
   nsTArray<nsRefPtr<ThenValueBase>> mThenValues;
   nsTArray<nsRefPtr<Private>> mChainedPromises;
   bool mHaveConsumer;
 };
 
 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
 class MediaPromise<ResolveValueT, RejectValueT, IsExclusive>::Private
   : public MediaPromise<ResolveValueT, RejectValueT, IsExclusive>
@@ -397,26 +455,26 @@ class MediaPromise<ResolveValueT, Reject
 public:
   explicit Private(const char* aCreationSite) : MediaPromise(aCreationSite) {}
 
   void Resolve(ResolveValueT aResolveValue, const char* aResolveSite)
   {
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s resolving MediaPromise (%p created at %s)", aResolveSite, this, mCreationSite);
-    mResolveValue.emplace(aResolveValue);
+    mValue.SetResolve(aResolveValue);
     DispatchAll();
   }
 
   void Reject(RejectValueT aRejectValue, const char* aRejectSite)
   {
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s rejecting MediaPromise (%p created at %s)", aRejectSite, this, mCreationSite);
-    mRejectValue.emplace(aRejectValue);
+    mValue.SetReject(aRejectValue);
     DispatchAll();
   }
 };
 
 /*
  * Class to encapsulate a promise for a particular role. Use this as the member
  * variable for a class whose method returns a promise.
  */
--- a/dom/media/fmp4/SharedDecoderManager.cpp
+++ b/dom/media/fmp4/SharedDecoderManager.cpp
@@ -93,19 +93,22 @@ SharedDecoderManager::CreateVideoDecoder
                           mTaskQueue,
                           mCallback,
                           mLayersBackend,
                           mImageContainer);
     if (!mDecoder) {
       mPDM = nullptr;
       return nullptr;
     }
+    nsresult rv = mDecoder->Init();
+    if (NS_FAILED(rv)) {
+      mDecoder = nullptr;
+      return nullptr;
+    }
     mPDM = aPDM;
-    nsresult rv = mDecoder->Init();
-    NS_ENSURE_SUCCESS(rv, nullptr);
   }
 
   nsRefPtr<SharedDecoderProxy> proxy(new SharedDecoderProxy(this, aCallback));
   return proxy.forget();
 }
 
 void
 SharedDecoderManager::DisableHardwareAcceleration()
@@ -142,37 +145,47 @@ SharedDecoderManager::Select(SharedDecod
   mActiveProxy = aProxy;
   mActiveCallback = aProxy->mCallback;
 }
 
 void
 SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy)
 {
   if (aProxy && mActiveProxy == aProxy) {
+    MonitorAutoLock mon(mMonitor);
     mWaitForInternalDrain = true;
-    mActiveProxy->Drain();
-    MonitorAutoLock mon(mMonitor);
-    while (mWaitForInternalDrain) {
-      mon.Wait();
+    nsresult rv;
+    {
+      // We don't want to hold the lock while calling Drain() has some
+      // platform implementations call DrainComplete() immediately.
+      MonitorAutoUnlock mon(mMonitor);
+      rv = mActiveProxy->Drain();
+    }
+    if (NS_SUCCEEDED(rv)) {
+      while (mWaitForInternalDrain) {
+        mon.Wait();
+      }
     }
     mActiveProxy->Flush();
     mActiveProxy = nullptr;
   }
 }
 
 void
 SharedDecoderManager::DrainComplete()
 {
-  if (mWaitForInternalDrain) {
+  {
     MonitorAutoLock mon(mMonitor);
-    mWaitForInternalDrain = false;
-    mon.NotifyAll();
-  } else {
-    mActiveCallback->DrainComplete();
+    if (mWaitForInternalDrain) {
+      mWaitForInternalDrain = false;
+      mon.NotifyAll();
+      return;
+    }
   }
+  mActiveCallback->DrainComplete();
 }
 
 void
 SharedDecoderManager::Shutdown()
 {
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nullptr;
--- a/dom/media/fmp4/apple/AppleVDADecoder.cpp
+++ b/dom/media/fmp4/apple/AppleVDADecoder.cpp
@@ -359,16 +359,17 @@ AppleVDADecoder::SubmitFrame(MediaRawDat
 
   OSStatus rv = VDADecoderDecode(mDecoder,
                                  0,
                                  block,
                                  frameInfo);
 
   if (rv != noErr) {
     NS_WARNING("AppleVDADecoder: Couldn't pass frame to decoder");
+    mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
   if (mIs106) {
     // TN2267:
     // frameInfo: A CFDictionaryRef containing information to be returned in
     // the output callback for this frame.
     // This dictionary can contain client provided information associated with
--- a/dom/media/fmp4/apple/AppleVTDecoder.cpp
+++ b/dom/media/fmp4/apple/AppleVTDecoder.cpp
@@ -163,18 +163,17 @@ PlatformCallback(void* decompressionOutp
     static_cast<AppleVTDecoder::AppleFrameRef*>(sourceFrameRefCon));
 
   // Validate our arguments.
   if (status != noErr || !image) {
     NS_WARNING("VideoToolbox decoder returned no data");
     return;
   }
   if (flags & kVTDecodeInfo_FrameDropped) {
-    NS_WARNING("  ...frame dropped...");
-    return;
+    NS_WARNING("  ...frame tagged as dropped...");
   }
   MOZ_ASSERT(CFGetTypeID(image) == CVPixelBufferGetTypeID(),
     "VideoToolbox returned an unexpected image type");
 
   // Forward the data back to an object method which can access
   // the correct MP4Reader callback.
   decoder->OutputFrame(image, frameRef);
 }
@@ -241,17 +240,19 @@ AppleVTDecoder::SubmitFrame(MediaRawData
   VTDecodeFrameFlags decodeFlags =
     kVTDecodeFrame_EnableAsynchronousDecompression;
   rv = VTDecompressionSessionDecodeFrame(mSession,
                                          sample,
                                          decodeFlags,
                                          CreateAppleFrameRef(aSample),
                                          &infoFlags);
   if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) {
+    LOG("AppleVTDecoder: Error %d VTDecompressionSessionDecodeFrame", rv);
     NS_WARNING("Couldn't pass frame to decoder");
+    mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
   // Ask for more data.
   if (mTaskQueue->IsEmpty()) {
     LOG("AppleVTDecoder task queue empty; requesting more data");
     mCallback->InputExhausted();
   }
--- a/dom/tests/mochitest/bugs/test_sizetocontent_clamp.html
+++ b/dom/tests/mochitest/bugs/test_sizetocontent_clamp.html
@@ -32,33 +32,31 @@ var epsilon =  navigator.platform.indexO
 // outer window chrome.
 var isWin8 = (navigator.userAgent.indexOf("Windows NT 6.2") != -1);
 
 var innerWidthMin = (isWin8 ? 120 : 100);
 var innerWidthMax = (isWin8 ? 125 : 100);
 
 function test() {
   var w = window.open('data:text/html,null', null, 'width=300,height=300');
-  var nbResize = 0;
 
   SimpleTest.waitForFocus(function() {
     w.onresize = function() {
-      nbResize++;
-
-      if (nbResize == 1) {
+      if (!(w.innerWidth + epsilon >= innerWidthMin &&
+            w.innerWidth - epsilon <= innerWidthMax)) {
+        // We need still another resize event.
         return;
       }
-
-      ok(w.innerWidth + epsilon >= innerWidthMin && w.innerWidth - epsilon <= innerWidthMax,
-         "innerWidth should be between " + innerWidthMin + " and " + innerWidthMax);
-      ok(w.innerHeight + epsilon >= 100 && w.innerHeight - epsilon <= 100,
-         "innerHeight should be around 100");
-
-      // It's not clear why 2 events are coming...
-      is(nbResize, 2, "We should get 2 events.");
+      if (!(w.innerHeight + epsilon >= 100 &&
+        w.innerHeight - epsilon <= 100)) {
+        // ditto
+        return;
+      }
+      ok(true, "innerWidth should be between " + innerWidthMin + " and " + innerWidthMax);
+      ok(true, "innerHeight should be around 100");
 
       w.close();
 
       SimpleTest.waitForFocus(function() {
         SimpleTest.finish();
       });
     };
     w.sizeToContent();
rename from dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul
rename to dom/tests/mochitest/chrome/MozDomFullscreen_chrome.xul
--- a/dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul
+++ b/dom/tests/mochitest/chrome/MozDomFullscreen_chrome.xul
@@ -55,29 +55,41 @@ function secondEntry(event) {
   window.addEventListener("MozEnteredDomFullscreen", thirdEntry, false);
   gInnerDoc.mozCancelFullScreen();
 }
 
 function thirdEntry(event) {
   is(event.target, gOuterDoc, "Third MozEnteredDomFullscreen should be targeted at outer doc");
   ok(gOuterDoc.mozFullScreenElement != null, "Outer doc return to fullscreen after cancel fullscreen in inner doc");
   window.removeEventListener("MozEnteredDomFullscreen", thirdEntry, false);
+  window.removeEventListener("MozExitedDomFullscreen", earlyExit, false);
+  window.addEventListener("MozExitedDomFullscreen", lastExit, false);
   gOuterDoc.mozCancelFullScreen();
+}
+
+function earlyExit(event) {
+  ok(false, "MozExitedDomFullscreen should only be triggered after cancel all fullscreen");
+}
+
+function lastExit(event) {
+  is(event.target, gOuterDoc, "MozExitedDomFullscreen should be targeted at the last exited doc");
+  ok(gOuterDoc.mozFullScreenElement == null, "Fullscreen should have been fully exited");
   window.opener.wrappedJSObject.done();
 }
 
 function start() {
   SimpleTest.waitForFocus(
     function() {
       gBrowser = document.getElementById("browser");
       gOuterDoc = gBrowser.contentDocument;
       gBrowser.contentWindow.focus();
       window.addEventListener("MozEnteredDomFullscreen", firstEntry, false);
+      window.addEventListener("MozExitedDomFullscreen", earlyExit, false);
       gOuterDoc.body.mozRequestFullScreen();
     });
 }
 
 ]]>
 </script>
 <!-- chrome://mochitests/content/chrome/dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul -->
-<browser type="content" id="browser" width="400" height="400" src="http://mochi.test:8888/tests/dom/tests/mochitest/general/file_MozEnteredDomFullscreen.html"/>
+<browser type="content" id="browser" width="400" height="400" src="http://mochi.test:8888/tests/dom/tests/mochitest/general/file_MozDomFullscreen.html"/>
 
 </window>
--- a/dom/tests/mochitest/chrome/chrome.ini
+++ b/dom/tests/mochitest/chrome/chrome.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
   489127.html
   DOMWindowCreated_chrome.xul
   DOMWindowCreated_content.html
-  MozEnteredDomFullscreen_chrome.xul
+  MozDomFullscreen_chrome.xul
   child_focus_frame.html
   file_DOM_element_instanceof.xul
   file_bug799299.xul
   file_bug800817.xul
   file_bug830858.xul
   file_subscript_bindings.js
   focus_frameset.html
   focus_window2.xul
@@ -44,17 +44,17 @@ skip-if = buildapp == 'mulet'
 [test_fullscreen.xul]
 # disabled on linux for timeouts--bug-867745
 skip-if = os == 'linux'
 [test_fullscreen_preventdefault.xul]
 [test_geolocation.xul]
 [test_indexedSetter.html]
 [test_moving_nodeList.xul]
 [test_moving_xhr.xul]
-[test_MozEnteredDomFullscreen_event.xul]
+[test_MozDomFullscreen_event.xul]
 # disabled on OS X for intermittent failures--bug-798848
 skip-if = toolkit == 'cocoa'
 [test_nodesFromRect.html]
 [test_popup_blocker_chrome.xul]
 [test_queryCaretRect.html]
 [test_resize_move_windows.xul]
 # disabled on linux for timeouts--bug-834716
 skip-if = os == 'linux'
rename from dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul
rename to dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
--- a/dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul
+++ b/dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
@@ -31,17 +31,17 @@ var principal = Components.classes["@moz
                   .getService(Ci.nsIScriptSecurityManager)
                   .getNoAppCodebasePrincipal(uri);
 pm.removeFromPrincipal(principal, "fullscreen");
 
 SpecialPowers.pushPrefEnv({"set": [['full-screen-api.enabled', true],
                                    ['full-screen-api.allow-trusted-requests-only', false]]}, setup);
 
 function setup() {
-   newwindow = window.open("MozEnteredDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");
+   newwindow = window.open("MozDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");
 }
 
 function done()
 {
   newwindow.close();
   SimpleTest.finish();
 }
 
rename from dom/tests/mochitest/general/file_MozEnteredDomFullscreen.html
rename to dom/tests/mochitest/general/file_MozDomFullscreen.html
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -1,12 +1,12 @@
 [DEFAULT]
 support-files =
   497633.html
-  file_MozEnteredDomFullscreen.html
+  file_MozDomFullscreen.html
   file_bug628069.html
   file_clonewrapper.html
   file_domWindowUtils_scrollbarSize.html
   file_frameElementWrapping.html
   file_interfaces.xml
   file_moving_nodeList.html
   file_moving_xhr.html
   file_showModalDialog.html
--- a/dom/webidl/Cache.webidl
+++ b/dom/webidl/Cache.webidl
@@ -8,37 +8,37 @@
  *
  */
 
 // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cache
 
 [Exposed=(Window,Worker),
  Func="mozilla::dom::cache::Cache::PrefEnabled"]
 interface Cache {
-[Throws]
-Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
-[Throws]
-Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
-[Throws]
-Promise<void> add(RequestInfo request);
-[Throws]
-Promise<void> addAll(sequence<RequestInfo> requests);
-[Throws]
-Promise<void> put(RequestInfo request, Response response);
-[Throws]
-Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
-[Throws]
-Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
+  [NewObject]
+  Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject]
+  Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
+  [NewObject]
+  Promise<void> add(RequestInfo request);
+  [NewObject]
+  Promise<void> addAll(sequence<RequestInfo> requests);
+  [NewObject]
+  Promise<void> put(RequestInfo request, Response response);
+  [NewObject]
+  Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject]
+  Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
 };
 
 dictionary CacheQueryOptions {
-boolean ignoreSearch = false;
-boolean ignoreMethod = false;
-boolean ignoreVary = false;
-DOMString cacheName;
+  boolean ignoreSearch = false;
+  boolean ignoreMethod = false;
+  boolean ignoreVary = false;
+  DOMString cacheName;
 };
 
 dictionary CacheBatchOperation {
-DOMString type;
-Request request;
-Response response;
-CacheQueryOptions options;
+  DOMString type;
+  Request request;
+  Response response;
+  CacheQueryOptions options;
 };
--- a/dom/webidl/CacheStorage.webidl
+++ b/dom/webidl/CacheStorage.webidl
@@ -5,22 +5,30 @@
  *
  * The origin of this IDL file is
  * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
  *
  */
 
 // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cache-storage
 
+interface Principal;
+
 [Exposed=(Window,Worker),
+ ChromeConstructor(CacheStorageNamespace namespace, Principal principal),
  Func="mozilla::dom::cache::CacheStorage::PrefEnabled"]
 interface CacheStorage {
-[Throws]
-Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
-[Throws]
-Promise<boolean> has(DOMString cacheName);
-[Throws]
-Promise<Cache> open(DOMString cacheName);
-[Throws]
-Promise<boolean> delete(DOMString cacheName);
-[Throws]
-Promise<sequence<DOMString>> keys();
+  [NewObject]
+  Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject]
+  Promise<boolean> has(DOMString cacheName);
+  [NewObject]
+  Promise<Cache> open(DOMString cacheName);
+  [NewObject]
+  Promise<boolean> delete(DOMString cacheName);
+  [NewObject]
+  Promise<sequence<DOMString>> keys();
 };
+
+// chrome-only, gecko specific extension
+enum CacheStorageNamespace {
+  "content", "chrome"
+};
--- a/dom/webidl/URL.webidl
+++ b/dom/webidl/URL.webidl
@@ -9,17 +9,17 @@
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#url
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 // [Constructor(DOMString url, optional (URL or DOMString) base = "about:blank")]
 [Constructor(DOMString url, URL base),
- Constructor(DOMString url, optional DOMString base = "about:blank"),
+ Constructor(DOMString url, optional DOMString base),
  Exposed=(Window,Worker)]
 interface URL {
 };
 URL implements URLUtils;
 URL implements URLUtilsSearchParams;
 
 partial interface URL {
   [Throws]
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -11,16 +11,17 @@
 #include "nsIDocument.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamLoader.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIMutableArray.h"
+#include "nsIUploadChannel2.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptLoader.h"
 #include "nsDebug.h"
 
 #include "jsapi.h"
 
 #include "mozilla/ErrorNames.h"
 #include "mozilla/LoadContext.h"
@@ -2352,16 +2353,17 @@ class FetchEventRunnable : public Worker
   nsTArray<nsCString> mHeaderValues;
   nsAutoPtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
   bool mIsReload;
   RequestMode mRequestMode;
   RequestCredentials mRequestCredentials;
   nsContentPolicyType mContentPolicyType;
+  nsCOMPtr<nsIInputStream> mUploadStream;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
                      nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo,
                      bool aIsReload)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mInterceptedChannel(aChannel)
@@ -2446,16 +2448,23 @@ public:
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsILoadInfo> loadInfo;
     rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
     NS_ENSURE_SUCCESS(rv, rv);
 
     mContentPolicyType = loadInfo->GetContentPolicyType();
 
+    nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+    if (uploadChannel) {
+      MOZ_ASSERT(!mUploadStream);
+      rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     return NS_OK;
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     return DispatchFetchEvent(aCx, aWorkerPrivate);
@@ -2505,31 +2514,31 @@ private:
         return false;
       }
     }
 
     nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders);
     reqInit.mHeaders.Construct();
     reqInit.mHeaders.Value().SetAsHeaders() = headers;
 
-    //TODO(jdm): set request body
-
     reqInit.mMode.Construct(mRequestMode);
     reqInit.mCredentials.Construct(mRequestCredentials);
 
     ErrorResult result;
     nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, result);
     if (NS_WARN_IF(result.Failed())) {
       return false;
     }
     // For Telemetry, note that this Request object was created by a Fetch event.
     nsRefPtr<InternalRequest> internalReq = request->GetInternalRequest();
     MOZ_ASSERT(internalReq);
     internalReq->SetCreatedByFetchEvent();
 
+    internalReq->SetBody(mUploadStream);
+
     request->SetContentPolicyType(mContentPolicyType);
 
     RootedDictionary<FetchEventInit> init(aCx);
     init.mRequest.Construct();
     init.mRequest.Value() = request;
     init.mBubbles = false;
     init.mCancelable = true;
     init.mIsReload.Construct(mIsReload);
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -29,17 +29,17 @@
 BEGIN_WORKERS_NAMESPACE
 using mozilla::dom::GlobalObject;
 
 class URLProxy final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
 
-  explicit URLProxy(mozilla::dom::URL* aURL)
+  explicit URLProxy(already_AddRefed<mozilla::dom::URL> aURL)
     : mURL(aURL)
   {
     AssertIsOnMainThread();
   }
 
   mozilla::dom::URL* URL()
   {
     return mURL;
@@ -223,79 +223,69 @@ public:
 };
 
 // This class creates a URL object on the main thread.
 class ConstructorRunnable : public WorkerMainThreadRunnable
 {
 private:
   const nsString mURL;
 
-  const nsString mBase;
+  nsString mBase; // IsVoid() if we have no base URI string.
   nsRefPtr<URLProxy> mBaseProxy;
   mozilla::ErrorResult& mRv;
 
   nsRefPtr<URLProxy> mRetval;
 
 public:
   ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
-                      const nsAString& aURL, const nsAString& aBase,
+                      const nsAString& aURL, const Optional<nsAString>& aBase,
                       mozilla::ErrorResult& aRv)
   : WorkerMainThreadRunnable(aWorkerPrivate)
   , mURL(aURL)
-  , mBase(aBase)
   , mRv(aRv)
   {
+    if (aBase.WasPassed()) {
+      mBase = aBase.Value();
+    } else {
+      mBase.SetIsVoid(true);
+    }
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
                       const nsAString& aURL, URLProxy* aBaseProxy,
                       mozilla::ErrorResult& aRv)
   : WorkerMainThreadRunnable(aWorkerPrivate)
   , mURL(aURL)
   , mBaseProxy(aBaseProxy)
   , mRv(aRv)
   {
+    mBase.SetIsVoid(true);
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   bool
   MainThreadRun()
   {
     AssertIsOnMainThread();
 
-    nsresult rv;
-    nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
-    if (NS_FAILED(rv)) {
-      mRv.Throw(rv);
+    nsRefPtr<mozilla::dom::URL> url;
+    if (mBaseProxy) {
+      url = mozilla::dom::URL::Constructor(mURL, mBaseProxy->URI(), mRv);
+    } else if (!mBase.IsVoid()) {
+      url = mozilla::dom::URL::Constructor(mURL, mBase, mRv);
+    } else {
+      url = mozilla::dom::URL::Constructor(mURL, nullptr, mRv);
+    }
+
+    if (mRv.Failed()) {
       return true;
     }
 
-    nsCOMPtr<nsIURI> baseURL;
-
-    if (!mBaseProxy) {
-      rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mBase), nullptr, nullptr,
-                             getter_AddRefs(baseURL));
-      if (NS_FAILED(rv)) {
-        mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return true;
-      }
-    } else {
-      baseURL = mBaseProxy->URI();
-    }
-
-    nsCOMPtr<nsIURI> url;
-    rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mURL), nullptr, baseURL,
-                           getter_AddRefs(url));
-    if (NS_FAILED(rv)) {
-      mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return true;
-    }
-
-    mRetval = new URLProxy(new mozilla::dom::URL(url));
+    mRetval = new URLProxy(url.forget());
     return true;
   }
 
   URLProxy*
   GetURLProxy()
   {
     return mRetval;
   }
@@ -517,52 +507,69 @@ URL::Constructor(const GlobalObject& aGl
                  URL& aBase, ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
   nsRefPtr<ConstructorRunnable> runnable =
     new ConstructorRunnable(workerPrivate, aUrl, aBase.GetURLProxy(), aRv);
 
-  if (!runnable->Dispatch(cx)) {
-    JS_ReportPendingException(cx);
-  }
+  return FinishConstructor(cx, workerPrivate, runnable, aRv);
+}
 
-  nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
-  if (!proxy) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return nullptr;
-  }
+// static
+already_AddRefed<URL>
+URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
+                 const Optional<nsAString>& aBase, ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.Context();
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
-  nsRefPtr<URL> url = new URL(workerPrivate, proxy);
-  return url.forget();
+  nsRefPtr<ConstructorRunnable> runnable =
+    new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv);
+
+  return FinishConstructor(cx, workerPrivate, runnable, aRv);
 }
 
 // static
 already_AddRefed<URL>
 URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
                  const nsAString& aBase, ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
+  Optional<nsAString> base;
+  base = &aBase;
   nsRefPtr<ConstructorRunnable> runnable =
-    new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv);
+    new ConstructorRunnable(workerPrivate, aUrl, base, aRv);
+
+  return FinishConstructor(cx, workerPrivate, runnable, aRv);
+}
 
-  if (!runnable->Dispatch(cx)) {
-    JS_ReportPendingException(cx);
+// static
+already_AddRefed<URL>
+URL::FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
+                       ConstructorRunnable* aRunnable, ErrorResult& aRv)
+{
+  if (!aRunnable->Dispatch(aCx)) {
+    JS_ReportPendingException(aCx);
   }
 
-  nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<URLProxy> proxy = aRunnable->GetURLProxy();
   if (!proxy) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
-  nsRefPtr<URL> url = new URL(workerPrivate, proxy);
+  nsRefPtr<URL> url = new URL(aPrivate, proxy);
   return url.forget();
 }
 
 URL::URL(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy)
   : mWorkerPrivate(aWorkerPrivate)
   , mURLProxy(aURLProxy)
 {
   MOZ_COUNT_CTOR(workers::URL);
--- a/dom/workers/URL.h
+++ b/dom/workers/URL.h
@@ -18,16 +18,17 @@ namespace dom {
 class File;
 struct objectURLOptions;
 }
 }
 
 BEGIN_WORKERS_NAMESPACE
 
 class URLProxy;
+class ConstructorRunnable;
 
 class URL final : public mozilla::dom::URLSearchParamsObserver
 {
   typedef mozilla::dom::URLSearchParams URLSearchParams;
 
   ~URL();
 
 public:
@@ -48,16 +49,19 @@ public:
 
   // Methods for WebIDL
 
   static already_AddRefed<URL>
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               URL& aBase, ErrorResult& aRv);
   static already_AddRefed<URL>
   Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
+              const Optional<nsAString>& aBase, ErrorResult& aRv);
+  static already_AddRefed<URL>
+  Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
               const nsAString& aBase, ErrorResult& aRv);
 
   static void
   CreateObjectURL(const GlobalObject& aGlobal,
                   File& aArg, const objectURLOptions& aOptions,
                   nsAString& aResult, ErrorResult& aRv);
 
   static void
@@ -118,16 +122,20 @@ public:
   void URLSearchParamsUpdated(URLSearchParams* aSearchParams) override;
 
 private:
   URLProxy* GetURLProxy() const
   {
     return mURLProxy;
   }
 
+  static already_AddRefed<URL>
+  FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
+                    ConstructorRunnable* aRunnable, ErrorResult& aRv);
+
   void CreateSearchParamsIfNeeded();
 
   void SetSearchInternal(const nsAString& aSearch);
 
   void UpdateURLSearchParams();
 
   WorkerPrivate* mWorkerPrivate;
   nsRefPtr<URLProxy> mURLProxy;
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -175,8 +175,78 @@ expectAsyncResult();
 fetch('http://example.com/cors-for-no-cors', { mode: "no-cors" })
 .then(function(res) {
   my_ok(res.type == "opaque", "intercepted non-opaque response for no-cors request should resolve to opaque response.");
   finish();
 }, function(e) {
   my_ok(false, "intercepted non-opaque response for no-cors request should resolve to opaque response. It should not fail.");
   finish();
 });
+
+function arrayBufferFromString(str) {
+  var arr = new Uint8Array(str.length);
+  for (var i = 0; i < str.length; ++i) {
+    arr[i] = str.charCodeAt(i);
+  }
+  return arr;
+}
+
+expectAsyncResult();
+fetch(new Request('body-simple', {method: 'POST', body: 'my body'}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body == 'my bodymy body', "the body of the intercepted fetch should be visible in the SW");
+  finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-arraybufferview', {method: 'POST', body: arrayBufferFromString('my body')}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body == 'my bodymy body', "the ArrayBufferView body of the intercepted fetch should be visible in the SW");
+  finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-arraybuffer', {method: 'POST', body: arrayBufferFromString('my body').buffer}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body == 'my bodymy body', "the ArrayBuffer body of the intercepted fetch should be visible in the SW");
+  finish();
+});
+
+expectAsyncResult();
+var usp = new URLSearchParams();
+usp.set("foo", "bar");
+usp.set("baz", "qux");
+fetch(new Request('body-urlsearchparams', {method: 'POST', body: usp}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body == 'foo=bar&baz=quxfoo=bar&baz=qux', "the URLSearchParams body of the intercepted fetch should be visible in the SW");
+  finish();
+});
+
+expectAsyncResult();
+var fd = new FormData();
+fd.set("foo", "bar");
+fd.set("baz", "qux");
+fetch(new Request('body-formdata', {method: 'POST', body: fd}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body.indexOf("Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar") <
+        body.indexOf("Content-Disposition: form-data; name=\"baz\"\r\n\r\nqux"),
+        "the FormData body of the intercepted fetch should be visible in the SW");
+  finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-blob', {method: 'POST', body: new Blob(new String('my body'))}))
+.then(function(res) {
+  return res.text();
+}).then(function(body) {
+  my_ok(body == 'my bodymy body', "the Blob body of the intercepted fetch should be visible in the SW");
+  finish();
+});
--- a/dom/workers/test/serviceworkers/fetch_event_worker.js
+++ b/dom/workers/test/serviceworkers/fetch_event_worker.js
@@ -167,9 +167,15 @@ onfetch = function(ev) {
                      "opener.postMessage({status: 'done'}, '*');" +
                    "</script>";
         ev.respondWith(new Response(body, {headers: {'Content-Type': 'text/html'}}));
     } else {
       seenIndex = true;
       ev.respondWith(fetch(ev.request.url));
     }
   }
+
+  else if (ev.request.url.includes("body-")) {
+    ev.respondWith(ev.request.text().then(function (body) {
+      return new Response(body + body);
+    }));
+  }
 }
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -89,18 +89,34 @@
 
   function runTest() {
     navigator.serviceWorker.ready.then(setupSW);
     navigator.serviceWorker.register("serviceworker_wrapper.js", {scope: "."});
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
-    SpecialPowers.pushPrefEnv({"set": [
+    // The handling of "dom.caches.enabled" here is a bit complicated.  What we
+    // want to happen is that Cache is always enabled in service workers.  So
+    // if service workers are disabled by default we want to force on both
+    // service workers and "dom.caches.enabled".  But if service workers are
+    // enabled by default, we do not want to mess with the "dom.caches.enabled"
+    // value, since that would defeat the purpose of the test.  Use a subframe
+    // to decide whether service workers are enabled by default, so we don't
+    // force creation of our own Navigator object before our prefs are set.
+    var prefs = [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
-      ["dom.caches.enabled", true]
-    ]}, runTest);
+    ];
+
+    var subframe = document.createElement("iframe");
+    document.body.appendChild(subframe);
+    if (!("serviceWorker" in subframe.contentWindow.navigator)) {
+	prefs.push(["dom.caches.enabled", true]);
+    }
+    subframe.remove();
+
+    SpecialPowers.pushPrefEnv({"set": prefs}, runTest);
   };
 </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -86,19 +86,19 @@ var ecmaGlobals =
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BroadcastChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "Cache", release: false },
+    "Cache",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "CacheStorage", release: false },
+    "CacheStorage",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Client",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Clients",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStore", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStoreCursor", b2g: true },
--- a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
+++ b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
@@ -12,34 +12,28 @@
 <body>
 <p id="display"></p>
 <div id="content"></div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
   var registration;
   var promise;
 
-  SimpleTest.requestCompleteLog();
-
   function start() {
-    info("start got called");
     return navigator.serviceWorker.register("worker_updatefoundevent.js",
                                             { scope: "./updatefoundevent.html" })
       .then((swr) => registration = swr);
   }
 
   function startWaitForUpdateFound() {
-    info("startWaitForUpdateFound got called");
     registration.onupdatefound = function(e) {
-      info("onupdatefound");
     }
 
     promise = new Promise(function(resolve, reject) {
       window.onmessage = function(e) {
-        info("Got message " + e.data);
 
         if (e.data == "finish") {
           ok(true, "Received updatefound");
           resolve();
         }
       }
     });
 
@@ -47,28 +41,26 @@
     iframe = document.createElement("iframe");
     content.appendChild(iframe);
     iframe.setAttribute("src", "./updatefoundevent.html");
 
     return Promise.resolve();
   }
 
   function registerNext() {
-    info("registerNext got called");
     return navigator.serviceWorker.register("worker_updatefoundevent2.js",
                                             { scope: "./updatefoundevent.html" });
   }
 
   function waitForUpdateFound() {
-    info("waitForUpdateFound got called");
     return promise;
   }
 
   function unregister() {
-    info("unregister got called");
+    window.onmessage = null;
     return registration.unregister().then(function(result) {
       ok(result, "Unregister should return true.");
     });
   }
 
   function runTest() {
      start()
       .then(startWaitForUpdateFound)
@@ -80,15 +72,14 @@
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
-    ["browser.dom.window.dump.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/worker_updatefoundevent.js
+++ b/dom/workers/test/serviceworkers/worker_updatefoundevent.js
@@ -1,20 +1,18 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 onactivate = function(e) {
   e.waitUntil(new Promise(function(resolve, reject) {
     registration.onupdatefound = function(e) {
-      dump("Update found for scope " + registration.scope + "\n");
       clients.matchAll().then(function(clients) {
         if (!clients.length) {
-          dump("No clients found\n");
           reject("No clients found");
         }
 
         if (registration.scope.match(/updatefoundevent\.html$/)) {
           clients[0].postMessage("finish");
           resolve();
         } else {
           dump("Scope did not match");
--- a/dom/workers/test/serviceworkers/worker_updatefoundevent2.js
+++ b/dom/workers/test/serviceworkers/worker_updatefoundevent2.js
@@ -1,2 +1,1 @@
 // Not useful.
-dump("worker_updatefoundevent2.js loaded\n");
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/apz_test_native_event_utils.js
@@ -0,0 +1,72 @@
+// Utilities for synthesizing of native events.
+
+function getPlatform() {
+  if (navigator.platform.indexOf("Win") == 0) {
+    return "windows";
+  }
+  if (navigator.platform.indexOf("Mac") == 0) {
+    return "mac";
+  }
+  if (navigator.platform.indexOf("Linux") == 0) {
+    return "linux";
+  }
+  return "unknown";
+}
+
+function nativeVerticalWheelEventMsg() {
+  switch (getPlatform()) {
+    case "windows": return 0x020A; // WM_MOUSEWHEEL
+    case "mac": return 0; // value is unused, can be anything
+  }
+  throw "Native wheel events not supported on platform " + getPlatform();
+}
+
+function nativeHorizontalWheelEventMsg() {
+  switch (getPlatform()) {
+    case "windows": return 0x020E; // WM_MOUSEHWHEEL
+    case "mac": return 0; // value is unused, can be anything
+  }
+  throw "Native wheel events not supported on platform " + getPlatform();
+}
+
+// Synthesizes a native mousewheel event and returns immediately. This does not
+// guarantee anything; you probably want to use one of the other functions below
+// which actually wait for results.
+// aX and aY are relative to |window|'s top-left. aDeltaX and aDeltaY
+// are pixel deltas, and aObserver can be left undefined if not needed.
+function synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, aObserver) {
+  aX += window.mozInnerScreenX;
+  aY += window.mozInnerScreenY;
+  if (aDeltaX && aDeltaY) {
+    throw "Simultaneous wheeling of horizontal and vertical is not supported on all platforms.";
+  }
+  var msg = aDeltaX ? nativeHorizontalWheelEventMsg() : nativeVerticalWheelEventMsg();
+  _getDOMWindowUtils().sendNativeMouseScrollEvent(aX, aY, msg, aDeltaX, aDeltaY, 0, 0, 0, aElement, aObserver);
+  return true;
+}
+
+// Synthesizes a native mousewheel event and invokes the callback once the
+// request has been successfully made to the OS. This does not necessarily
+// guarantee that the OS generates the event we requested. See
+// synthesizeNativeWheel for details on the parameters.
+function synthesizeNativeWheelAndWaitForObserver(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
+  var observer = {
+    observe: function(aSubject, aTopic, aData) {
+      if (aCallback && aTopic == "mousescrollevent") {
+        setTimeout(aCallback, 0);
+      }
+    }
+  };
+  return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, observer);
+}
+
+// Synthesizes a native mousewheel event and invokes the callback once the
+// wheel event is dispatched to the window. See synthesizeNativeWheel for
+// details on the parameters.
+function synthesizeNativeWheelAndWaitForEvent(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
+  window.addEventListener("wheel", function wheelWaiter(e) {
+    window.removeEventListener("wheel", wheelWaiter);
+    setTimeout(aCallback, 0);
+  });
+  return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY);
+}
--- a/gfx/layers/apz/test/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest.ini
@@ -1,9 +1,13 @@
 [DEFAULT]
 support-files =
   apz_test_utils.js
+  apz_test_native_event_utils.js
   helper_bug982141.html
   helper_bug1151663.html
+tags = apz
 [test_bug982141.html]
 skip-if = toolkit != 'gonk'  # bug 991198
 [test_bug1151663.html]
 skip-if = toolkit != 'gonk'  # bug 991198
+[test_wheel_scroll.html]
+skip-if = toolkit != 'windows' && toolkit != 'cocoa'
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/test_wheel_scroll.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
+-->
+<head>
+  <title>Test for Bug 1013412</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+  #content {
+    height: 800px;
+    overflow: scroll;
+  }
+
+  #scroller {
+    height: 2000px;
+    background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+  }
+
+  #scrollbox {
+    margin-top: 200px;
+    width: 500px;
+    height: 500px;
+    border-radius: 250px;
+    box-shadow: inset 0 0 0 60px #555;
+    background: #777;
+  }
+
+  #circle {
+    position: relative;
+    left: 240px;
+    top: 20px;
+    border: 10px solid white;
+    border-radius: 10px;
+    width: 0px;
+    height: 0px;
+    transform-origin: 10px 230px;
+    will-change: transform;
+  }
+  </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161206">Mozilla Bug 1161206</a>
+<p id="display"></p>
+<div id="content">
+  <p>Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.</p>
+  <div id="scroller">
+   <div id="scrollbox">
+    <div id="circle"></div>
+   </div>
+  </div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+var rotation = 0;
+var rotationAdjusted = false;
+
+var incrementForMode = function (mode) {
+  switch (mode) {
+    case WheelEvent.DOM_DELTA_PIXEL: return 1;
+    case WheelEvent.DOM_DELTA_LINE: return 15;
+    case WheelEvent.DOM_DELTA_PAGE: return 400;
+  }
+  return 0;
+};
+
+document.getElementById("scrollbox").addEventListener("wheel", function (e) {
+  rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2;
+  document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)";
+  rotationAdjusted = true;
+  e.preventDefault();
+});
+
+function* runTest() {
+  var content = document.getElementById('content');
+  for (i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
+    yield synthesizeNativeWheelAndWaitForEvent(content, 100, 150, 0, -5, continueTest);
+  }
+  var scrollbox = document.getElementById('scrollbox');
+  is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
+  is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
+  is(rotationAdjusted, true, "The rotation should have been adjusted");
+}
+
+var gTestContinuation = null;
+function continueTest() {
+  if (!gTestContinuation) {
+    gTestContinuation = runTest();
+  }
+  var ret = gTestContinuation.next();
+  if (ret.done) {
+    SimpleTest.finish();
+  } else {
+    is(ret.value, true, "Wheel event successfully synthesized");
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(continueTest, window);
+
+</script>
+</pre>
+
+</body>
+</html>
--- a/gfx/layers/d3d9/DeviceManagerD3D9.cpp
+++ b/gfx/layers/d3d9/DeviceManagerD3D9.cpp
@@ -201,17 +201,17 @@ DeviceManagerD3D9::Init()
     return false;
   }
 
   if (gfxPrefs::StereoVideoEnabled()) {
     /* Create an Nv3DVUtils instance */
     if (!mNv3DVUtils) {
       mNv3DVUtils = new Nv3DVUtils();
       if (!mNv3DVUtils) {
-        NS_WARNING("Could not create a new instance of Nv3DVUtils.\n");
+        NS_WARNING("Could not create a new instance of Nv3DVUtils.");
       }
     }
 
     /* Initialize the Nv3DVUtils object */
     if (mNv3DVUtils) {
       mNv3DVUtils->Initialize();
     }
   }
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -661,17 +661,17 @@ CompositorParent::CompositorParent(nsIWi
     sIndirectLayerTrees[mRootLayerTreeID].mParent = this;
   }
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     mApzcTreeManager = new APZCTreeManager();
   }
 
   if (UseVsyncComposition()) {
-    NS_WARNING("Enabling vsync compositor\n");
+    NS_WARNING("Enabling vsync compositor");
     mCompositorScheduler = new CompositorVsyncScheduler(this, aWidget);
   } else {
     mCompositorScheduler = new CompositorSoftwareTimerScheduler(this);
   }
 
   gfxPlatform::GetPlatform()->ComputeTileSize();
 }
 
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -1082,17 +1082,17 @@ CompositorOGL::DrawQuad(const Rect& aRec
       EffectYCbCr* effectYCbCr =
         static_cast<EffectYCbCr*>(aEffectChain.mPrimaryEffect.get());
       TextureSource* sourceYCbCr = effectYCbCr->mTexture;
       const int Y = 0, Cb = 1, Cr = 2;
       TextureSourceOGL* sourceY =  sourceYCbCr->GetSubSource(Y)->AsSourceOGL();
       TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL();
       TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL();
 
-      if (!sourceY && !sourceCb && !sourceCr) {
+      if (!sourceY || !sourceCb || !sourceCr) {
         NS_WARNING("Invalid layer texture.");
         return;
       }
 
       sourceY->BindTexture(LOCAL_GL_TEXTURE0, effectYCbCr->mFilter);
       sourceCb->BindTexture(LOCAL_GL_TEXTURE1, effectYCbCr->mFilter);
       sourceCr->BindTexture(LOCAL_GL_TEXTURE2, effectYCbCr->mFilter);
 
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -492,17 +492,17 @@ gfxAndroidPlatform::CreateHardwareVsyncS
     // Android pre-JB doesn't have hardware vsync
     // Once L HwcComposer issues have been resolved, re-enable for L devices
     // L is andriod version 21, Kit-kat is 19, 20 is kit-kat for wearables
 #if defined(MOZ_WIDGET_GONK) && (ANDROID_VERSION == 19)
     nsRefPtr<GonkVsyncSource> vsyncSource = new GonkVsyncSource();
     VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
     display.EnableVsync();
     if (!display.IsVsyncEnabled()) {
-        NS_WARNING("Error enabling gonk vsync. Falling back to software vsync\n");
+        NS_WARNING("Error enabling gonk vsync. Falling back to software vsync");
         return gfxPlatform::CreateHardwareVsyncSource();
     }
     display.DisableVsync();
     return vsyncSource.forget();
 #else
     return gfxPlatform::CreateHardwareVsyncSource();
 #endif
 }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2339,17 +2339,17 @@ gfxPlatform::UsesOffMainThreadCompositin
   }
 
   return result;
 }
 
 already_AddRefed<mozilla::gfx::VsyncSource>
 gfxPlatform::CreateHardwareVsyncSource()
 {
-  NS_WARNING("Hardware Vsync support not yet implemented. Falling back to software timers\n");
+  NS_WARNING("Hardware Vsync support not yet implemented. Falling back to software timers");
   nsRefPtr<mozilla::gfx::VsyncSource> softwareVsync = new SoftwareVsyncSource();
   return softwareVsync.forget();
 }
 
 /* static */ bool
 gfxPlatform::IsInLayoutAsapMode()
 {
   // There are 2 modes of ASAP mode.
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -477,17 +477,17 @@ public:
         return;
       }
 
       // Create a display link capable of being used with all active displays
       // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor
       // situations. According to the docs, it is compatible with all displays running on the computer
       // But if we have different monitors at different display rates, we may hit issues.
       if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
-        NS_WARNING("Could not create a display link with all active displays. Retrying\n");
+        NS_WARNING("Could not create a display link with all active displays. Retrying");
         CVDisplayLinkRelease(mDisplayLink);
         mDisplayLink = nullptr;
 
         // bug 1142708 - When coming back from sleep,
         // or when changing displays, active displays may not be ready yet,
         // even if listening for the kIOMessageSystemHasPoweredOn event
         // from OS X sleep notifications.
         // Active displays are those that are drawable.
@@ -581,17 +581,17 @@ static CVReturn VsyncCallback(CVDisplayL
 
 already_AddRefed<mozilla::gfx::VsyncSource>
 gfxPlatformMac::CreateHardwareVsyncSource()
 {
   nsRefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
   VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay();
   primaryDisplay.EnableVsync();
   if (!primaryDisplay.IsVsyncEnabled()) {
-    NS_WARNING("OS X Vsync source not enabled. Falling back to software vsync.\n");
+    NS_WARNING("OS X Vsync source not enabled. Falling back to software vsync.");
     return gfxPlatform::CreateHardwareVsyncSource();
   }
 
   primaryDisplay.DisableVsync();
   return osxVsyncSource.forget();
 }
 
 void
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -1314,17 +1314,17 @@ gfxUtils::WriteAsPNG(SourceSurface* aSur
           rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
           if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
             file = fopen(aFile, "wb");
           }
         }
       }
     }
     if (!file) {
-      NS_WARNING("Failed to open file to create PNG!\n");
+      NS_WARNING("Failed to open file to create PNG!");
       return;
     }
   }
 
   EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
                       EmptyString(), eBinaryEncode, file);
   fclose(file);
 }
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -2130,17 +2130,17 @@ public:
         MOZ_ASSERT(NS_IsMainThread());
         MonitorAutoLock lock(mVsyncEnabledLock);
         return mVsyncEnabled;
       }
 
       void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp)
       {
         MOZ_ASSERT(IsInVsyncThread());
-        NS_WARNING("DwmComposition dynamically disabled, falling back to software timers\n");
+        NS_WARNING("DwmComposition dynamically disabled, falling back to software timers");
 
         TimeStamp nextVsync = aVsyncTimestamp + mSoftwareVsyncRate;
         TimeDuration delay = nextVsync - TimeStamp::Now();
         if (delay.ToMilliseconds() < 0) {
           delay = mozilla::TimeDuration::FromMilliseconds(0);
         }
 
         mVsyncThread->message_loop()->PostDelayedTask(FROM_HERE,
@@ -2241,24 +2241,24 @@ private:
   nsRefPtr<D3DVsyncDisplay> mPrimaryDisplay;
 }; // end D3DVsyncSource
 
 already_AddRefed<mozilla::gfx::VsyncSource>
 gfxWindowsPlatform::CreateHardwareVsyncSource()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (!WinUtils::dwmIsCompositionEnabledPtr) {
-    NS_WARNING("Dwm composition not available, falling back to software vsync\n");
+    NS_WARNING("Dwm composition not available, falling back to software vsync");
     return gfxPlatform::CreateHardwareVsyncSource();
   }
 
   BOOL dwmEnabled = false;
   WinUtils::dwmIsCompositionEnabledPtr(&dwmEnabled);
   if (!dwmEnabled) {
-    NS_WARNING("DWM not enabled, falling back to software vsync\n");
+    NS_WARNING("DWM not enabled, falling back to software vsync");
     return gfxPlatform::CreateHardwareVsyncSource();
   }
 
   nsRefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource();
   return d3dVsyncSource.forget();
 }
 
 bool
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -714,26 +714,26 @@ nsJPEGDecoder::OutputScanlines(bool* sus
         sampleRow += 3;
       }
 
       if (mDownscaler) {
         mDownscaler->CommitRow();
       }
   }
 
-  if (top != mInfo.output_scanline) {
+  if (mDownscaler && mDownscaler->HasInvalidation()) {
+    DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
+    PostInvalidation(invalidRect.mOriginalSizeRect,
+                     Some(invalidRect.mTargetSizeRect));
+    MOZ_ASSERT(!mDownscaler->HasInvalidation());
+  } else if (!mDownscaler && top != mInfo.output_scanline) {
     PostInvalidation(nsIntRect(0, top,
                                mInfo.output_width,
-                               mInfo.output_scanline - top),
-                     mDownscaler ? Some(mDownscaler->TakeInvalidRect())
-                                 : Nothing());
+                               mInfo.output_scanline - top));
   }
-
-  MOZ_ASSERT(!mDownscaler || !mDownscaler->HasInvalidation(),
-             "Didn't send downscaler's invalidation");
 }
 
 // Override the standard error method in the IJG JPEG decoder code.
 METHODDEF(void)
 my_error_exit (j_common_ptr cinfo)
 {
   decoder_error_mgr* err = (decoder_error_mgr*) cinfo->err;
 
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -468,17 +468,18 @@ Decoder::InternalAddFrame(uint32_t aFram
   }
 
   if (aTargetSize.width <= 0 || aTargetSize.height <= 0 ||
       aFrameRect.width <= 0 || aFrameRect.height <= 0) {
     NS_WARNING("Trying to add frame with zero or negative size");
     return RawAccessFrameRef();
   }
 
-  if (!SurfaceCache::CanHold(aTargetSize)) {
+  const uint32_t bytesPerPixel = aPaletteDepth == 0 ? 4 : 1;
+  if (!SurfaceCache::CanHold(aFrameRect.Size(), bytesPerPixel)) {
     NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
     return RawAccessFrameRef();
   }
 
   nsRefPtr<imgFrame> frame = new imgFrame();
   bool nonPremult =
     aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
   if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
--- a/image/src/Downscaler.cpp
+++ b/image/src/Downscaler.cpp
@@ -64,16 +64,18 @@ Downscaler::BeginFrame(const nsIntSize& 
   MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
              "Created a downscaler, but width is larger");
   MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
              "Created a downscaler, but height is larger");
   MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
              "Invalid original size");
 
   mOriginalSize = aOriginalSize;
+  mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
+                   double(mOriginalSize.height) / mTargetSize.height);
   mOutputBuffer = aOutputBuffer;
   mHasAlpha = aHasAlpha;
 
   ResetForNextProgressivePass();
   ReleaseWindow();
 
   auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
 
@@ -178,27 +180,35 @@ Downscaler::CommitRow()
 }
 
 bool
 Downscaler::HasInvalidation() const
 {
   return mCurrentOutLine > mPrevInvalidatedLine;
 }
 
-nsIntRect
+DownscalerInvalidRect
 Downscaler::TakeInvalidRect()
 {
   if (MOZ_UNLIKELY(!HasInvalidation())) {
-    return nsIntRect();
+    return DownscalerInvalidRect();
   }
 
-  nsIntRect invalidRect(0, mPrevInvalidatedLine,
-                        mTargetSize.width,
-                        mCurrentOutLine - mPrevInvalidatedLine);
+  DownscalerInvalidRect invalidRect;
+
+  // Compute the target size invalid rect.
+  invalidRect.mTargetSizeRect =
+    nsIntRect(0, mPrevInvalidatedLine,
+              mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
   mPrevInvalidatedLine = mCurrentOutLine;
+
+  // Compute the original size invalid rect.
+  invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
+  invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height);
+
   return invalidRect;
 }
 
 void
 Downscaler::DownscaleInputLine()
 {
   typedef skia::ConvolutionFilter1D::Fixed FilterValue;
 
--- a/image/src/Downscaler.h
+++ b/image/src/Downscaler.h
@@ -20,16 +20,26 @@
 namespace skia {
   class ConvolutionFilter1D;
 } // namespace skia
 
 namespace mozilla {
 namespace image {
 
 /**
+ * DownscalerInvalidRect wraps two invalidation rects: one in terms of the
+ * original image size, and one in terms of the target size.
+ */
+struct DownscalerInvalidRect
+{
+  nsIntRect mOriginalSizeRect;
+  nsIntRect mTargetSizeRect;
+};
+
+/**
  * Downscaler is a high-quality, streaming image downscaler based upon Skia's
  * scaling implementation.
  *
  * Decoders can construct a Downscaler once they know their target size, then
  * call BeginFrame() for each frame they decode. They should write a decoded row
  * into the buffer returned by RowBuffer(), and then call CommitRow() to signal
  * that they have finished.
  *
@@ -42,16 +52,17 @@ class Downscaler
 {
 public:
   /// Constructs a new Downscaler which to scale to size @aTargetSize.
   explicit Downscaler(const nsIntSize& aTargetSize);
   ~Downscaler();
 
   const nsIntSize& OriginalSize() const { return mOriginalSize; }
   const nsIntSize& TargetSize() const { return mTargetSize; }
+  const gfxSize& Scale() const { return mScale; }
 
   /**
    * Begins a new frame and reinitializes the Downscaler.
    *
    * @param aOriginalSize The original size of this frame, before scaling.
    * @param aOutputBuffer The buffer to which the Downscaler should write its
    *                      output; this is the same buffer where the Decoder
    *                      would write its output when not downscaling during
@@ -68,31 +79,32 @@ public:
 
   /// Signals that the decoder has finished writing a row into the row buffer.
   void CommitRow();
 
   /// Returns true if there is a non-empty invalid rect available.
   bool HasInvalidation() const;
 
   /// Takes the Downscaler's current invalid rect and resets it.
-  nsIntRect TakeInvalidRect();
+  DownscalerInvalidRect TakeInvalidRect();
 
   /**
    * Resets the Downscaler's position in the image, for a new progressive pass
    * over the same frame. Because the same data structures can be reused, this
    * is more efficient than calling BeginFrame.
    */
   void ResetForNextProgressivePass();
 
 private:
   void DownscaleInputLine();
   void ReleaseWindow();
 
   nsIntSize mOriginalSize;
   nsIntSize mTargetSize;
+  gfxSize mScale;
 
   uint8_t* mOutputBuffer;
 
   UniquePtr<uint8_t[]> mRowBuffer;
   UniquePtr<uint8_t*[]> mWindow;
 
   UniquePtr<skia::ConvolutionFilter1D> mXFilter;
   UniquePtr<skia::ConvolutionFilter1D> mYFilter;
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -27,16 +27,17 @@
 #include "Image.h"
 #include "nsAutoPtr.h"
 #include "nsExpirationTracker.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsSize.h"
 #include "nsTArray.h"
 #include "prsystem.h"
+#include "ShutdownTracker.h"
 #include "SVGImageContext.h"
 
 using std::max;
 using std::min;
 
 namespace mozilla {
 
 using namespace gfx;
@@ -61,19 +62,20 @@ static StaticRefPtr<SurfaceCacheImpl> sI
  * Cost models the cost of storing a surface in the cache. Right now, this is
  * simply an estimate of the size of the surface in bytes, but in the future it
  * may be worth taking into account the cost of rematerializing the surface as
  * well.
  */
 typedef size_t Cost;
 
 static Cost
-ComputeCost(const IntSize& aSize)
+ComputeCost(const IntSize& aSize, uint32_t aBytesPerPixel)
 {
-  return aSize.width * aSize.height * 4;  // width * height * 4 bytes (32bpp)
+  MOZ_ASSERT(aBytesPerPixel == 1 || aBytesPerPixel == 4);
+  return aSize.width * aSize.height * aBytesPerPixel;
 }
 
 /**
  * Since we want to be able to make eviction decisions based on cost, we need to
  * be able to look up the CachedSurface which has a certain cost as well as the
  * cost associated with a certain CachedSurface. To make this possible, in data
  * structures we actually store a CostEntry, which contains a weak pointer to
  * its associated surface.
@@ -521,17 +523,18 @@ public:
       MOZ_ASSERT(!mCosts.Contains(costEntry),
                  "Shouldn't have a cost entry for a locked surface");
     } else {
       if (MOZ_LIKELY(aSurface->GetExpirationState()->IsTracked())) {
         mExpirationTracker.RemoveObject(aSurface);
       } else {
         // Our call to AddObject must have failed in StartTracking; most likely
         // we're in XPCOM shutdown right now.
-        NS_WARNING("Not expiration-tracking an unlocked surface!");
+        NS_WARN_IF_FALSE(ShutdownTracker::ShutdownHasStarted(),
+                         "Not expiration-tracking an unlocked surface!");
       }
 
       DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
       MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
     }
 
     mAvailableCost += costEntry.GetCost();
     MOZ_ASSERT(mAvailableCost <= mMaxCost,
@@ -996,28 +999,28 @@ SurfaceCache::Insert(imgFrame*         a
                      const SurfaceKey& aSurfaceKey,
                      Lifetime          aLifetime)
 {
   if (!sInstance) {
     return InsertOutcome::FAILURE;
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
-  Cost cost = ComputeCost(aSurfaceKey.Size());
+  Cost cost = ComputeCost(aSurface->GetSize(), aSurface->GetBytesPerPixel());
   return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
-SurfaceCache::CanHold(const IntSize& aSize)
+SurfaceCache::CanHold(const IntSize& aSize, uint32_t aBytesPerPixel /* = 4 */)
 {
   if (!sInstance) {
     return false;
   }
 
-  Cost cost = ComputeCost(aSize);
+  Cost cost = ComputeCost(aSize, aBytesPerPixel);
   return sInstance->CanHold(cost);
 }
 
 /* static */ bool
 SurfaceCache::CanHold(size_t aSize)
 {
   if (!sInstance) {
     return false;
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -283,20 +283,23 @@ struct SurfaceCache
    * surface, but the inverse is not true: Insert() may take more information
    * into account than just image size when deciding whether to cache the
    * surface, so Insert() may still fail even if CanHold() returns true.
    *
    * Use CanHold() to avoid the need to create a temporary surface when we know
    * for sure the cache can't hold it.
    *
    * @param aSize  The dimensions of a surface in pixels.
+   * @param aBytesPerPixel  How many bytes each pixel of the surface requires.
+   *                        Defaults to 4, which is appropriate for RGBA or RGBX
+   *                        images.
    *
    * @return false if the surface cache can't hold a surface of that size.
    */
-  static bool CanHold(const IntSize& aSize);
+  static bool CanHold(const IntSize& aSize, uint32_t aBytesPerPixel = 4);
   static bool CanHold(size_t aSize);
 
   /**
    * Locks an image. Any of the image's persistent surfaces which are either
    * inserted or accessed while the image is locked will not expire.
    *
    * Locking an image does not automatically lock that image's existing
    * surfaces. A call to LockImage() guarantees that persistent surfaces which
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -231,16 +231,24 @@ public:
    * aborted.
    *
    * Note that calling this on the main thread _blocks the main thread_. Be very
    * careful in your use of this method to avoid excessive main thread jank or
    * deadlock.
    */
   void WaitUntilComplete() const;
 
+  /**
+   * Returns the number of bytes per pixel this imgFrame requires.  This is a
+   * worst-case value that does not take into account the effects of format
+   * changes caused by Optimize(), since an imgFrame is not optimized throughout
+   * its lifetime.
+   */
+  uint32_t GetBytesPerPixel() const { return GetIsPaletted() ? 1 : 4; }
+
   IntSize GetImageSize() const { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
   void GetImageData(uint8_t** aData, uint32_t* length) const;
   uint8_t* GetImageData() const;
 
   bool GetIsPaletted() const;
--- a/intl/uconv/ucvlatin/nsMacGujaratiToUnicode.cpp
+++ b/intl/uconv/ucvlatin/nsMacGujaratiToUnicode.cpp
@@ -12,11 +12,12 @@
 nsresult
 nsMacGujaratiToUnicodeConstructor(nsISupports *aOuter, REFNSIID aIID,
                                   void **aResult) 
 {
    static const uint16_t g_utMappingTable[] = {
 #include "macgujarati.ut"
    };
 
+   Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_MACGUJARATI, true);
    return CreateOneByteDecoder((uMappingTable*) &g_utMappingTable,
                                aOuter, aIID, aResult);
 }
--- a/ipc/chromium/src/chrome/common/ipc_message.cc
+++ b/ipc/chromium/src/chrome/common/ipc_message.cc
@@ -44,16 +44,18 @@ Message::Message()
 Message::Message(int32_t routing_id, msgid_t type, PriorityValue priority,
                  MessageCompression compression, const char* const name)
     : Pickle(sizeof(Header)) {
   header()->routing = routing_id;
   header()->type = type;
   header()->flags = priority;
   if (compression == COMPRESSION_ENABLED)
     header()->flags |= COMPRESS_BIT;
+  else if (compression == COMPRESSION_ALL)
+    header()->flags |= COMPRESSALL_BIT;
 #if defined(OS_POSIX)
   header()->num_fds = 0;
 #endif
   header()->interrupt_remote_stack_depth_guess = static_cast<uint32_t>(-1);
   header()->interrupt_local_stack_depth = static_cast<uint32_t>(-1);
   header()->seqno = 0;
 #if defined(OS_MACOSX)
   header()->cookie = 0;
--- a/ipc/chromium/src/chrome/common/ipc_message.h
+++ b/ipc/chromium/src/chrome/common/ipc_message.h
@@ -51,17 +51,18 @@ class Message : public Pickle {
   enum PriorityValue {
     PRIORITY_NORMAL = 1,
     PRIORITY_HIGH = 2,
     PRIORITY_URGENT = 3
   };
 
   enum MessageCompression {
     COMPRESSION_NONE,
-    COMPRESSION_ENABLED
+    COMPRESSION_ENABLED,
+    COMPRESSION_ALL
   };
 
   virtual ~Message();
 
   Message();
 
   // Initialize a message with a user-defined type, priority value, and
   // destination WebView ID.
@@ -94,18 +95,22 @@ class Message : public Pickle {
   }
 
   // True if this is a synchronous message.
   bool is_interrupt() const {
     return (header()->flags & INTERRUPT_BIT) != 0;
   }
 
   // True if compression is enabled for this message.
-  bool compress() const {
-    return (header()->flags & COMPRESS_BIT) != 0;
+  MessageCompression compress_type() const {
+    return (header()->flags & COMPRESS_BIT) ?
+               COMPRESSION_ENABLED :
+               (header()->flags & COMPRESSALL_BIT) ?
+                   COMPRESSION_ALL :
+                   COMPRESSION_NONE;
   }
 
   // Set this on a reply to a synchronous message.
   void set_reply() {
     header()->flags |= REPLY_BIT;
   }
 
   bool is_reply() const {
@@ -280,16 +285,17 @@ class Message : public Pickle {
     SYNC_BIT        = 0x0004,
     REPLY_BIT       = 0x0008,
     REPLY_ERROR_BIT = 0x0010,
     UNBLOCK_BIT     = 0x0020,
     PUMPING_MSGS_BIT= 0x0040,
     HAS_SENT_TIME_BIT = 0x0080,
     INTERRUPT_BIT   = 0x0100,
     COMPRESS_BIT    = 0x0200,
+    COMPRESSALL_BIT = 0x0400,
   };
 
   struct Header : Pickle::Header {
     int32_t routing;  // ID of the view that this message is destined for
     msgid_t type;   // specifies the user-defined message type
     uint32_t flags;   // specifies control flags for the message
 #if defined(OS_POSIX)
     uint32_t num_fds; // the number of descriptors included with this message
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -607,16 +607,29 @@ MessageChannel::ShouldDeferMessage(const
     //
     // Deferring in the parent only sort of breaks message ordering. When the
     // child's message comes in, we can pretend the child hasn't quite
     // finished sending it yet. Since the message is sync, we know that the
     // child hasn't moved on yet.
     return mSide == ParentSide && aMsg.transaction_id() != mCurrentTransaction;
 }
 
+// Predicate that is true for messages that should be consolidated if 'compress' is set.
+class MatchingKinds {
+    typedef IPC::Message Message;
+    Message::msgid_t mType;
+    int32_t mRoutingId;
+public:
+    MatchingKinds(Message::msgid_t aType, int32_t aRoutingId) :
+        mType(aType), mRoutingId(aRoutingId) {}
+    bool operator()(const Message &msg) {
+        return msg.type() == mType && msg.routing_id() == mRoutingId;
+    }
+};
+
 void
 MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
 {
     AssertLinkThread();
     mMonitor->AssertCurrentThreadOwns();
 
     if (MaybeInterceptSpecialIOMessage(aMsg))
         return;
@@ -648,27 +661,45 @@ MessageChannel::OnMessageReceivedFromLin
         }
 
         mRecvd = new Message(aMsg);
         NotifyWorkerThread();
         return;
     }
 
     // Prioritized messages cannot be compressed.
-    MOZ_ASSERT(!aMsg.compress() || aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
+    MOZ_ASSERT_IF(aMsg.compress_type() != IPC::Message::COMPRESSION_NONE,
+                  aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
 
-    bool compress = (aMsg.compress() && !mPending.empty() &&
-                     mPending.back().type() == aMsg.type() &&
-                     mPending.back().routing_id() == aMsg.routing_id());
-    if (compress) {
-        // This message type has compression enabled, and the back of the
-        // queue was the same message type and routed to the same destination.
-        // Replace it with the newer message.
-        MOZ_ASSERT(mPending.back().compress());
-        mPending.pop_back();
+    bool compress = false;
+    if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
+        compress = (!mPending.empty() &&
+                    mPending.back().type() == aMsg.type() &&
+                    mPending.back().routing_id() == aMsg.routing_id());
+        if (compress) {
+            // This message type has compression enabled, and the back of the
+            // queue was the same message type and routed to the same destination.
+            // Replace it with the newer message.
+            MOZ_ASSERT(mPending.back().compress_type() ==
+                       IPC::Message::COMPRESSION_ENABLED);
+            mPending.pop_back();
+        }
+    } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL) {
+        // Check the message queue for another message with this type/destination.
+        auto it = std::find_if(mPending.rbegin(), mPending.rend(),
+                               MatchingKinds(aMsg.type(), aMsg.routing_id()));
+        if (it != mPending.rend()) {
+            // This message type has compression enabled, and the queue holds
+            // a message with the same message type and routed to the same destination.
+            // Erase it.  Note that, since we always compress these redundancies, There Can
+            // Be Only One.
+            compress = true;
+            MOZ_ASSERT((*it).compress_type() == IPC::Message::COMPRESSION_ALL);
+            mPending.erase((++it).base());
+        }
     }
 
     bool shouldWakeUp = AwaitingInterruptReply() ||
                         (AwaitingSyncReply() && !ShouldDeferMessage(aMsg)) ||
                         AwaitingIncomingMessage();
 
     // There are three cases we're concerned about, relating to the state of the
     // main thread:
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -1855,18 +1855,21 @@ def _generateMessageClass(clsname, msgid
     cls = Class(name=clsname, inherits=[ Inherit(Type('IPC::Message')) ])
     cls.addstmt(Label.PUBLIC)
 
     idenum = TypeEnum()
     idenum.addId('ID', msgid)
     cls.addstmt(StmtDecl(Decl(idenum, '')))
 
     # make the message constructor
-    if compress:
+    if compress == 'compress':
         compression = ExprVar('COMPRESSION_ENABLED')
+    elif compress:
+        assert compress == 'compressall'
+        compression = ExprVar('COMPRESSION_ALL')
     else:
         compression = ExprVar('COMPRESSION_NONE')
     if priority == ipdl.ast.NORMAL_PRIORITY:
         priorityEnum = 'IPC::Message::PRIORITY_NORMAL'
     elif priority == ipdl.ast.HIGH_PRIORITY:
         priorityEnum = 'IPC::Message::PRIORITY_HIGH'
     else:
         assert priority == ipdl.ast.URGENT_PRIORITY
--- a/ipc/ipdl/ipdl/parser.py
+++ b/ipc/ipdl/ipdl/parser.py
@@ -118,16 +118,17 @@ reserved = set((
         'as',
         'async',
         'both',
         'bridges',
         'call',
         'child',
         'class',
         'compress',
+        'compressall',
         '__delete__',
         'delete',                       # reserve 'delete' to prevent its use
         'from',
         'goto',
         'high',
         'include',
         'intr',
         'manager',
@@ -541,22 +542,27 @@ def p_MessageOutParams(p):
     """MessageOutParams : RETURNS '(' ParamList ')'
                         | """
     if 1 == len(p):
         p[0] = [ ]
     else:
         p[0] = p[3]
 
 def p_OptionalMessageCompress(p):
-    """OptionalMessageCompress : COMPRESS
+    """OptionalMessageCompress : MessageCompress
                                | """
     if 1 == len(p):
         p[0] = ''
     else:
-        p[0] = 'compress'
+        p[0] = p[1]
+
+def p_MessageCompress(p):
+    """MessageCompress : COMPRESS
+                       | COMPRESSALL"""
+    p[0] = p[1]
 
 ##--------------------
 ## State machine
 
 def p_TransitionStmtsOpt(p):
     """TransitionStmtsOpt : TransitionStmt TransitionStmtsOpt
                           |"""
     if 1 == len(p):
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -1096,17 +1096,17 @@ class GatherDecls(TcheckVisitor):
             cdtype = self.currentProtocolDecl.type
 
 
         # enter message scope
         self.symtab.enterScope(md)
 
         msgtype = MessageType(md.priority, md.sendSemantics, md.direction,
                               ctor=isctor, dtor=isdtor, cdtype=cdtype,
-                              compress=(md.compress == 'compress'))
+                              compress=md.compress)
 
         # replace inparam Param nodes with proper Decls
         def paramToDecl(param):
             ptname = param.typespec.basename()
             ploc = param.typespec.loc
 
             ptdecl = self.symtab.lookup(ptname)
             if ptdecl is None:
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -199,29 +199,33 @@ JS_FRIEND_API(bool) isGCEnabled();
  *   foo(JS::NullPtr());
  * which avoids creating a Rooted<JSObject*> just to pass nullptr.
  */
 struct JS_PUBLIC_API(NullPtr)
 {
     static void * const constNullValue;
 };
 
-JS_FRIEND_API(void) HeapCellPostBarrier(js::gc::Cell** cellp);
-JS_FRIEND_API(void) HeapCellRelocate(js::gc::Cell** cellp);
+JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp);
+JS_FRIEND_API(void) HeapObjectRelocate(JSObject** objp);
 
 #ifdef JS_DEBUG
 /*
  * For generational GC, assert that an object is in the tenured generation as
  * opposed to being in the nursery.
  */
 extern JS_FRIEND_API(void)
 AssertGCThingMustBeTenured(JSObject* obj);
+extern JS_FRIEND_API(void)
+AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell);
 #else
 inline void
 AssertGCThingMustBeTenured(JSObject* obj) {}
+inline void
+AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {}
 #endif
 
 /*
  * The Heap<T> class is a heap-stored reference to a JS GC thing. All members of
  * heap classes that refer to GC things should use Heap<T> (or possibly
  * TenuredHeap<T>, described below).
  *
  * Heap<T> is an abstraction that hides some of the complexity required to
@@ -634,17 +638,20 @@ struct RootKind<T*>
     static ThingRootKind rootKind() { return T::rootKind(); }
 };
 
 template <typename T>
 struct GCMethods<T*>
 {
     static T* initial() { return nullptr; }
     static bool needsPostBarrier(T* v) { return false; }
-    static void postBarrier(T** vp) {}
+    static void postBarrier(T** vp) {
+        if (vp)
+            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(vp));
+    }
     static void relocate(T** vp) {}
 };
 
 template <>
 struct GCMethods<JSObject*>
 {
     static JSObject* initial() { return nullptr; }
     static gc::Cell* asGCThingOrNull(JSObject* v) {
@@ -652,35 +659,35 @@ struct GCMethods<JSObject*>
             return nullptr;
         MOZ_ASSERT(uintptr_t(v) > 32);
         return reinterpret_cast<gc::Cell*>(v);
     }
     static bool needsPostBarrier(JSObject* v) {
         return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
     }
     static void postBarrier(JSObject** vp) {
-        JS::HeapCellPostBarrier(reinterpret_cast<js::gc::Cell**>(vp));
+        JS::HeapObjectPostBarrier(vp);
     }
     static void relocate(JSObject** vp) {
-        JS::HeapCellRelocate(reinterpret_cast<js::gc::Cell**>(vp));
+        JS::HeapObjectRelocate(vp);
     }
 };
 
 template <>
 struct GCMethods<JSFunction*>
 {
     static JSFunction* initial() { return nullptr; }
     static bool needsPostBarrier(JSFunction* v) {
         return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
     }
     static void postBarrier(JSFunction** vp) {
-        JS::HeapCellPostBarrier(reinterpret_cast<js::gc::Cell**>(vp));
+        JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp));
     }
     static void relocate(JSFunction** vp) {
-        JS::HeapCellRelocate(reinterpret_cast<js::gc::Cell**>(vp));
+        JS::HeapObjectRelocate(reinterpret_cast<JSObject**>(vp));
     }
 };
 
 } /* namespace js */
 
 namespace JS {
 
 /*
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -70,8 +70,38 @@ js::PreBarrierFunctor<S>::operator()(T* 
 {
     InternalGCMethods<T*>::preBarrier(t);
 }
 template void js::PreBarrierFunctor<JS::Value>::operator()<JS::Symbol>(JS::Symbol*);
 template void js::PreBarrierFunctor<JS::Value>::operator()<JSObject>(JSObject*);
 template void js::PreBarrierFunctor<JS::Value>::operator()<JSString>(JSString*);
 template void js::PreBarrierFunctor<jsid>::operator()<JS::Symbol>(JS::Symbol*);
 template void js::PreBarrierFunctor<jsid>::operator()<JSString>(JSString*);
+
+JS_PUBLIC_API(void)
+JS::HeapObjectPostBarrier(JSObject** objp)
+{
+    MOZ_ASSERT(objp);
+    MOZ_ASSERT(*objp);
+    js::InternalGCMethods<JSObject*>::postBarrierRelocate(objp);
+}
+
+JS_PUBLIC_API(void)
+JS::HeapObjectRelocate(JSObject** objp)
+{
+    MOZ_ASSERT(objp);
+    MOZ_ASSERT(*objp);
+    js::InternalGCMethods<JSObject*>::postBarrierRemove(objp);
+}
+
+JS_PUBLIC_API(void)
+JS::HeapValuePostBarrier(JS::Value* valuep)
+{
+    MOZ_ASSERT(valuep);
+    js::InternalGCMethods<JS::Value>::postBarrierRelocate(valuep);
+}
+
+JS_PUBLIC_API(void)
+JS::HeapValueRelocate(JS::Value* valuep)
+{
+    MOZ_ASSERT(valuep);
+    js::InternalGCMethods<JS::Value>::postBarrierRemove(valuep);
+}
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -197,56 +197,12 @@ StoreBuffer::addSizeOfExcludingThis(mozi
     sizes->storeBufferCells      += bufferCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferSlots      += bufferSlot.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferWholeCells += bufferWholeCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferRelocVals  += bufferRelocVal.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferRelocCells += bufferRelocCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferGenerics   += bufferGeneric.sizeOfExcludingThis(mallocSizeOf);
 }
 
-JS_PUBLIC_API(void)
-JS::HeapCellPostBarrier(js::gc::Cell** cellp)
-{
-    MOZ_ASSERT(cellp);
-    MOZ_ASSERT(*cellp);
-    StoreBuffer* storeBuffer = (*cellp)->storeBuffer();
-    if (storeBuffer)
-        storeBuffer->putRelocatableCellFromAnyThread(cellp);
-}
-
-JS_PUBLIC_API(void)
-JS::HeapCellRelocate(js::gc::Cell** cellp)
-{
-    /* Called with old contents of *cellp before overwriting. */
-    MOZ_ASSERT(cellp);
-    MOZ_ASSERT(*cellp);
-    JSRuntime* runtime = (*cellp)->runtimeFromMainThread();
-    runtime->gc.storeBuffer.removeRelocatableCellFromAnyThread(cellp);
-}
-
-JS_PUBLIC_API(void)
-JS::HeapValuePostBarrier(JS::Value* valuep)
-{
-    MOZ_ASSERT(valuep);
-    MOZ_ASSERT(valuep->isMarkable());
-    if (valuep->isObject()) {
-        StoreBuffer* storeBuffer = valuep->toObject().storeBuffer();
-        if (storeBuffer)
-            storeBuffer->putRelocatableValueFromAnyThread(valuep);
-    }
-}
-
-JS_PUBLIC_API(void)
-JS::HeapValueRelocate(JS::Value* valuep)
-{
-    /* Called with old contents of *valuep before overwriting. */
-    MOZ_ASSERT(valuep);
-    MOZ_ASSERT(valuep->isMarkable());
-    if (valuep->isString() && valuep->toString()->isPermanentAtom())
-        return;
-    JSRuntime* runtime = static_cast<js::gc::Cell*>(valuep->toGCThing())->runtimeFromMainThread();
-    runtime->gc.storeBuffer.removeRelocatableValueFromAnyThread(valuep);
-}
-
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::ValueEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::CellPtrEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::SlotsEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::WholeCellEdges>;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6812,16 +6812,23 @@ AutoDisableProxyCheck::~AutoDisableProxy
 JS_FRIEND_API(void)
 JS::AssertGCThingMustBeTenured(JSObject* obj)
 {
     MOZ_ASSERT(obj->isTenured() &&
                (!IsNurseryAllocable(obj->asTenured().getAllocKind()) || obj->getClass()->finalize));
 }
 
 JS_FRIEND_API(void)
+JS::AssertGCThingIsNotAnObjectSubclass(Cell* cell)
+{
+    MOZ_ASSERT(cell);
+    MOZ_ASSERT(cell->getTraceKind() != JSTRACE_OBJECT);
+}
+
+JS_FRIEND_API(void)
 js::gc::AssertGCThingHasType(js::gc::Cell* cell, JSGCTraceKind kind)
 {
     if (!cell)
         MOZ_ASSERT(kind == JSTRACE_NULL);
     else if (IsInsideNursery(cell))
         MOZ_ASSERT(kind == JSTRACE_OBJECT);
     else
         MOZ_ASSERT(MapAllocToTraceKind(cell->asTenured().getAllocKind()) == kind);
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -9574,17 +9574,17 @@ nsCSSFrameConstructor::CreateNeededAnonF
       !::IsFlexOrGridContainer(aParentFrame)) {
     return;
   }
 
   nsIAtom* containerType = aParentFrame->GetType();
   FCItemIterator iter(aItems);
   do {
     // Advance iter past children that don't want to be wrapped
-    if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState)) {
+    if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState, containerType)) {
       // Hit the end of the items without finding any remaining children that
       // need to be wrapped. We're finished!
       return;
     }
 
     // If our next potentially-wrappable child is whitespace, then see if
     // there's anything wrappable immediately after it. If not, we just drop
     // the whitespace and move on. (We're not supposed to create any anonymous
@@ -9596,41 +9596,42 @@ nsCSSFrameConstructor::CreateNeededAnonF
     // entirely whitespace*, then we technically should still skip over it, per
     // the CSS grid & flexbox specs. I'm not bothering with that at this point,
     // since it's a pretty extreme edge case.
     if (!aParentFrame->IsGeneratedContentFrame() &&
         iter.item().IsWhitespace(aState)) {
       FCItemIterator afterWhitespaceIter(iter);
       bool hitEnd = afterWhitespaceIter.SkipWhitespace(aState);
       bool nextChildNeedsAnonItem =
-        !hitEnd && afterWhitespaceIter.item().NeedsAnonFlexOrGridItem(aState);
+        !hitEnd && afterWhitespaceIter.item().NeedsAnonFlexOrGridItem(aState,
+                                                containerType);
 
       if (!nextChildNeedsAnonItem) {
         // There's nothing after the whitespace that we need to wrap, so we
         // just drop this run of whitespace.
         iter.DeleteItemsTo(afterWhitespaceIter);
         if (hitEnd) {
           // Nothing left to do -- we're finished!
           return;
         }
         // else, we have a next child and it does not want to be wrapped.  So,
         // we jump back to the beginning of the loop to skip over that child
         // (and anything else non-wrappable after it)
         MOZ_ASSERT(!iter.IsDone() &&
-                   !iter.item().NeedsAnonFlexOrGridItem(aState),
+                   !iter.item().NeedsAnonFlexOrGridItem(aState, containerType),
                    "hitEnd and/or nextChildNeedsAnonItem lied");
         continue;
       }
     }
 
     // Now |iter| points to the first child that needs to be wrapped in an
     // anonymous flex/grid item. Now we see how many children after it also want
     // to be wrapped in an anonymous flex/grid item.
     FCItemIterator endIter(iter); // iterator to find the end of the group
-    endIter.SkipItemsThatNeedAnonFlexOrGridItem(aState);
+    endIter.SkipItemsThatNeedAnonFlexOrGridItem(aState, containerType);
 
     NS_ASSERTION(iter != endIter,
                  "Should've had at least one wrappable child to seek past");
 
     // Now, we create the anonymous flex or grid item to contain the children
     // between |iter| and |endIter|.
     nsIAtom* pseudoType = containerType == nsGkAtoms::flexContainerFrame ?
       nsCSSAnonBoxes::anonymousFlexItem : nsCSSAnonBoxes::anonymousGridItem;
@@ -11826,30 +11827,31 @@ nsCSSFrameConstructor::WipeContainingBlo
   // Situation #2 is a flex or grid container frame into which we're inserting
   // new inline non-replaced children, adjacent to an existing anonymous
   // flex or grid item.
   if (::IsFlexOrGridContainer(aFrame)) {
     FCItemIterator iter(aItems);
 
     // Check if we're adding to-be-wrapped content right *after* an existing
     // anonymous flex or grid item (which would need to absorb this content).
+    nsIAtom* containerType = aFrame->GetType();
     if (aPrevSibling && IsAnonymousFlexOrGridItem(aPrevSibling) &&
-        iter.item().NeedsAnonFlexOrGridItem(aState)) {
+        iter.item().NeedsAnonFlexOrGridItem(aState, containerType)) {
       RecreateFramesForContent(aFrame->GetContent(), true,
                                REMOVE_FOR_RECONSTRUCTION, nullptr);
       return true;
     }
 
     // Check if we're adding to-be-wrapped content right *before* an existing
     // anonymous flex or grid item (which would need to absorb this content).
     if (nextSibling && IsAnonymousFlexOrGridItem(nextSibling)) {
       // Jump to the last entry in the list
       iter.SetToEnd();
       iter.Prev();
-      if (iter.item().NeedsAnonFlexOrGridItem(aState)) {
+      if (iter.item().NeedsAnonFlexOrGridItem(aState, containerType)) {
         RecreateFramesForContent(aFrame->GetContent(), true,
                                  REMOVE_FOR_RECONSTRUCTION, nullptr);
         return true;
       }
     }
   }
 
   // Situation #3 is an anonymous flex or grid item that's getting new children
@@ -11864,20 +11866,21 @@ nsCSSFrameConstructor::WipeContainingBlo
     // We're not honoring floats on this content because it has the
     // _flex/grid container_ as its parent in the content tree.
     nsFrameConstructorSaveState floatSaveState;
     aState.PushFloatContainingBlock(nullptr, floatSaveState);
 
     FCItemIterator iter(aItems);
     // Skip over things that _do_ need an anonymous flex item, because
     // they're perfectly happy to go here -- they won't cause a reframe.
-    if (!iter.SkipItemsThatNeedAnonFlexOrGridItem(aState)) {
+    nsIFrame* containerFrame = aFrame->GetParent();
+    if (!iter.SkipItemsThatNeedAnonFlexOrGridItem(aState,
+                                                  containerFrame->GetType())) {
       // We hit something that _doesn't_ need an anonymous flex item!
       // Rebuild the flex container to bust it out.
-      nsIFrame* containerFrame = aFrame->GetParent();
       RecreateFramesForContent(containerFrame->GetContent(), true,
                                REMOVE_FOR_RECONSTRUCTION, nullptr);
       return true;
     }
 
     // If we get here, then everything in |aItems| needs to be wrapped in
     // an anonymous flex or grid item.  That's where it's already going - good!
   }
@@ -12319,57 +12322,62 @@ Iterator::SkipItemsNotWantingParentType(
       return true;
     }
   }
   return false;
 }
 
 bool
 nsCSSFrameConstructor::FrameConstructionItem::
-  NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState)
+  NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+                          nsIAtom* aContainerType)
 {
   if (mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) {
     // This will be an inline non-replaced box.
     return true;
   }
 
-  if (!(mFCData->mBits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
+  // Bug 874718: Flex containers still wrap placeholders; Grid containers don't.
+  if (aContainerType == nsGkAtoms::flexContainerFrame &&
+      !(mFCData->mBits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
       aState.GetGeometricParent(mStyleContext->StyleDisplay(), nullptr)) {
     // We're abspos or fixedpos, which means we'll spawn a placeholder which
     // we'll need to wrap in an anonymous flex item.  So, we just treat
     // _this_ frame as if _it_ needs to be wrapped in an anonymous flex item,
     // and then when we spawn the placeholder, it'll end up in the right spot.
     return true;
   }
 
   return false;
 }
 
 inline bool
 nsCSSFrameConstructor::FrameConstructionItemList::
 Iterator::SkipItemsThatNeedAnonFlexOrGridItem(
-  const nsFrameConstructorState& aState)
+  const nsFrameConstructorState& aState,
+  nsIAtom* aContainerType)
 {
   NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
-  while (item().NeedsAnonFlexOrGridItem(aState)) {
+  while (item().NeedsAnonFlexOrGridItem(aState, aContainerType)) {
     Next();
     if (IsDone()) {
       return true;
     }
   }
   return false;
 }
 
 inline bool
 nsCSSFrameConstructor::FrameConstructionItemList::
 Iterator::SkipItemsThatDontNeedAnonFlexOrGridItem(
-  const nsFrameConstructorState& aState)
+  const nsFrameConstructorState& aState,
+  nsIAtom* aContainerType)
 {
   NS_PRECONDITION(!IsDone(), "Shouldn't be done yet");
-  while (!(item().NeedsAnonFlexOrGridItem(aState))) {
+  while (!(item().NeedsAnonFlexOrGridItem(aState, aContainerType))) {
     Next();
     if (IsDone()) {
       return true;
     }
   }
   return false;
 }
 
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -926,23 +926,23 @@ private:
       // one.  Return whether the iterator is done after doing that.  The
       // iterator must not be done when this is called.
       inline bool SkipItemsNotWantingParentType(ParentType aParentType);
 
       // Skip over non-replaced inline frames and positioned frames.
       // Return whether the iterator is done after doing that.
       // The iterator must not be done when this is called.
       inline bool SkipItemsThatNeedAnonFlexOrGridItem(
-        const nsFrameConstructorState& aState);
+        const nsFrameConstructorState& aState, nsIAtom* aContainerType);
 
       // Skip to the first frame that is a non-replaced inline or is
       // positioned.  Return whether the iterator is done after doing that.
       // The iterator must not be done when this is called.
       inline bool SkipItemsThatDontNeedAnonFlexOrGridItem(
-        const nsFrameConstructorState& aState);
+        const nsFrameConstructorState& aState, nsIAtom* aContainerType);
 
       // Skip over all items that do not want a ruby parent.  Return whether
       // the iterator is done after doing that.  The iterator must not be done
       // when this is called.
       inline bool SkipItemsNotWantingRubyParent();
 
       // Skip over whitespace.  Return whether the iterator is done after doing
       // that.  The iterator must not be done, and must be pointing to a
@@ -1069,17 +1069,18 @@ private:
     }
 
     ParentType DesiredParentType() {
       return FCDATA_DESIRED_PARENT_TYPE(mFCData->mBits);
     }
 
     // Indicates whether (when in a flex or grid container) this item needs
     // to be wrapped in an anonymous block.
-    bool NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState);
+    bool NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+                                 nsIAtom* aContainerType);
 
     // Don't call this unless the frametree really depends on the answer!
     // Especially so for generated content, where we don't want to reframe
     // things.
     bool IsWhitespace(nsFrameConstructorState& aState) const;
 
     bool IsLineBoundary() const {
       return mIsBlock || (mFCData->mBits & FCDATA_IS_LINE_BREAK);
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -133,20 +133,20 @@ typedef struct CapturingContentInfo {
   // capture should only be allowed during a mousedown event
   bool mAllowed;
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   mozilla::StaticRefPtr<nsIContent> mContent;
 } CapturingContentInfo;
 
-// d910f009-d209-74c1-6b04-30c83c051c78
+// b7b89561-4f03-44b3-9afa-b47e7f313ffb
 #define NS_IPRESSHELL_IID \
-  { 0x025264c6, 0x0b12, 0x4804, \
-    { 0xa3, 0x3e, 0xb7, 0x73, 0xf2, 0x19, 0x48, 0x90 } }
+  { 0xb7b89561, 0x4f03, 0x44b3, \
+    { 0x9a, 0xfa, 0xb4, 0x7e, 0x7f, 0x31, 0x3f, 0xfb } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -1545,16 +1545,18 @@ public:
   virtual void EnsureImageInVisibleList(nsIImageLoadingContent* aImage) = 0;
 
   // Removes the image from the list of visible images if it is present there.
   virtual void RemoveImageFromVisibleList(nsIImageLoadingContent* aImage) = 0;
 
   // Whether we should assume all images are visible.
   virtual bool AssumeAllImagesVisible() = 0;
 
+  virtual void FireResizeEvent() = 0;
+
   /**
    * Refresh observer management.
    */
 protected:
   virtual bool AddRefreshObserverExternal(nsARefreshObserver* aObserver,
                                           mozFlushType aFlushType);
   bool AddRefreshObserverInternal(nsARefreshObserver* aObserver,
                                   mozFlushType aFlushType);
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -1211,31 +1211,28 @@ PresShell::Destroy()
     if (mDocument->HasAnimationController()) {
       mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
     }
   }
 
   // Revoke any pending events.  We need to do this and cancel pending reflows
   // before we destroy the frame manager, since apparently frame destruction
   // sometimes spins the event queue when plug-ins are involved(!).
+  if (mResizeEventPending) {
+    rd->RemoveResizeEventFlushObserver(this);
+  }
   rd->RemoveLayoutFlushObserver(this);
   if (mHiddenInvalidationObserverRefreshDriver) {
     mHiddenInvalidationObserverRefreshDriver->RemovePresShellToInvalidateIfHidden(this);
   }
 
   if (rd->PresContext() == GetPresContext()) {
     rd->RevokeViewManagerFlush();
   }
 
-  mResizeEvent.Revoke();
-  if (mAsyncResizeTimerIsActive) {
-    mAsyncResizeEventTimer->Cancel();
-    mAsyncResizeTimerIsActive = false;
-  }
-
   CancelAllPendingReflows();
   CancelPostedReflowCallbacks();
 
   // Destroy the frame manager. This will destroy the frame hierarchy
   mFrameConstructor->WillDestroyFrameTree();
 
   // Destroy all frame properties (whose destruction was suppressed
   // while destroying the frame tree, but which might contain more
@@ -1993,22 +1990,16 @@ PresShell::Initialize(nscoord aWidth, ns
 void
 PresShell::sPaintSuppressionCallback(nsITimer *aTimer, void* aPresShell)
 {
   nsRefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
   if (self)
     self->UnsuppressPainting();
 }
 
-void
-PresShell::AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell)
-{
-  static_cast<PresShell*>(aPresShell)->FireResizeEvent();
-}
-
 nsresult
 PresShell::ResizeReflowOverride(nscoord aWidth, nscoord aHeight)
 {
   mViewportOverridden = true;
   return ResizeReflowIgnoreOverride(aWidth, aHeight);
 }
 
 nsresult
@@ -2083,63 +2074,40 @@ PresShell::ResizeReflowIgnoreOverride(ns
   }
 
   rootFrame = mFrameConstructor->GetRootFrame();
   if (aHeight == NS_UNCONSTRAINEDSIZE && rootFrame) {
     mPresContext->SetVisibleArea(
       nsRect(0, 0, aWidth, rootFrame->GetRect().height));
   }
 
-  if (!mIsDestroying && !mResizeEvent.IsPending() &&
-      !mAsyncResizeTimerIsActive) {
-    if (mInResize) {
-      if (!mAsyncResizeEventTimer) {
-        mAsyncResizeEventTimer = do_CreateInstance("@mozilla.org/timer;1");
-      }
-      if (mAsyncResizeEventTimer) {
-        mAsyncResizeTimerIsActive = true;
-        mAsyncResizeEventTimer->InitWithFuncCallback(AsyncResizeEventCallback,
-                                                     this, 15,
-                                                     nsITimer::TYPE_ONE_SHOT);
-      }
-    } else {
-      nsRefPtr<nsRunnableMethod<PresShell> > resizeEvent =
-        NS_NewRunnableMethod(this, &PresShell::FireResizeEvent);
-      if (NS_SUCCEEDED(NS_DispatchToCurrentThread(resizeEvent))) {
-        mResizeEvent = resizeEvent;
-        mDocument->SetNeedStyleFlush();
-      }
-    }
+  if (!mIsDestroying && !mResizeEventPending) {
+    mResizeEventPending = true;
+    GetPresContext()->RefreshDriver()->AddResizeEventFlushObserver(this);
   }
 
   return NS_OK; //XXX this needs to be real. MMP
 }
 
 void
 PresShell::FireResizeEvent()
 {
-  if (mAsyncResizeTimerIsActive) {
-    mAsyncResizeTimerIsActive = false;
-    mAsyncResizeEventTimer->Cancel();
-  }
-  mResizeEvent.Revoke();
-
-  if (mIsDocumentGone)
+  if (mIsDocumentGone) {
     return;
+  }
+
+  mResizeEventPending = false;
 
   //Send resize event from here.
   WidgetEvent event(true, NS_RESIZE_EVENT);
   nsEventStatus status = nsEventStatus_eIgnore;
 
-  nsPIDOMWindow *window = mDocument->GetWindow();
+  nsPIDOMWindow* window = mDocument->GetWindow();
   if (window) {
-    nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
-    mInResize = true;
     EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
-    mInResize = false;
   }
 }
 
 void
 PresShell::SetIgnoreFrameDestruction(bool aIgnore)
 {
   if (mDocument) {
     // We need to tell the ImageLoader to drop all its references to frames
@@ -4210,23 +4178,16 @@ PresShell::FlushPendingNotifications(moz
   nsRefPtr<nsViewManager> viewManagerDeathGrip = mViewManager;
   bool didStyleFlush = false;
   bool didLayoutFlush = false;
   if (isSafeToFlush && mViewManager) {
     // Processing pending notifications can kill us, and some callers only
     // hold weak refs when calling FlushPendingNotifications().  :(
     nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
 
-    if (mResizeEvent.IsPending()) {
-      FireResizeEvent();
-      if (mIsDestroying) {
-        return;
-      }
-    }
-
     // We need to make sure external resource documents are flushed too (for
     // example, svg filters that reference a filter in an external document
     // need the frames in the external document to be constructed for the
     // filter to work). We only need external resources to be flushed when the
     // main document is flushing >= Flush_Frames, so we flush external
     // resources here instead of nsDocument::FlushPendingNotifications.
     mDocument->FlushExternalResources(flushType);
 
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -385,16 +385,18 @@ public:
   virtual void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) override;
 
   virtual void DispatchAfterKeyboardEvent(nsINode* aTarget,
                                           const mozilla::WidgetKeyboardEvent& aEvent,
                                           bool aEmbeddedCancelled) override;
 
   void SetNextPaintCompressed() { mNextPaintCompressed = true; }
 
+  virtual void FireResizeEvent() override;
+
 protected:
   virtual ~PresShell();
 
   void HandlePostedReflowCallbacks(bool aInterruptible);
   void CancelPostedReflowCallbacks();
 
   void UnsuppressAndInvalidate();
 
@@ -695,19 +697,16 @@ protected:
 
   // Get the selected item and coordinates in device pixels relative to root
   // document's root view for element, first ensuring the element is onscreen
   void GetCurrentItemAndPositionForElement(nsIDOMElement *aCurrentEl,
                                            nsIContent **aTargetToUse,
                                            mozilla::LayoutDeviceIntPoint& aTargetPt,
                                            nsIWidget *aRootWidget);
 
-  void FireResizeEvent();
-  static void AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell);
-
   virtual void SynthesizeMouseMove(bool aFromScroll) override;
 
   PresShell* GetRootPresShell();
 
   nscolor GetDefaultBackgroundColorToDraw();
 
   DOMHighResTimeStamp GetPerformanceNow();
 
@@ -787,18 +786,16 @@ protected:
   // Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
   // we finish reflowing mCurrentReflowRoot.
   nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesToDirty;
 
   // Reflow roots that need to be reflowed.
   nsTArray<nsIFrame*>       mDirtyRoots;
 
   nsTArray<nsAutoPtr<DelayedEvent> > mDelayedEvents;
-  nsRevocableEventPtr<nsRunnableMethod<PresShell> > mResizeEvent;
-  nsCOMPtr<nsITimer>        mAsyncResizeEventTimer;
 private:
   nsIFrame*                 mCurrentEventFrame;
   nsCOMPtr<nsIContent>      mCurrentEventContent;
   nsTArray<nsIFrame*>       mCurrentEventFrameStack;
   nsCOMArray<nsIContent>    mCurrentEventContentStack;
 protected:
   nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
   nsCOMPtr<nsIContent>      mLastAnchorScrolledTo;
@@ -856,18 +853,17 @@ protected:
   // We've been disconnected from the document.  We will refuse to paint the
   // document until either our timer fires or all frames are constructed.
   bool                      mIsDocumentGone : 1;
 
   // Indicates that it is safe to unlock painting once all pending reflows
   // have been processed.
   bool                      mShouldUnsuppressPainting : 1;
 
-  bool                      mAsyncResizeTimerIsActive : 1;
-  bool                      mInResize : 1;
+  bool                      mResizeEventPending : 1;
 
   bool                      mImageVisibilityVisited : 1;
 
   bool                      mNextPaintCompressed : 1;
 
   bool                      mHasCSSBackgroundColor : 1;
 
   // Whether content should be scaled by the resolution amount. If this is
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -854,17 +854,17 @@ CreateVsyncRefreshTimer()
   gfxPrefs::GetSingleton();
 
   if (!gfxPrefs::VsyncAlignedRefreshDriver()
         || !gfxPrefs::HardwareVsyncEnabled()
         || gfxPlatform::IsInLayoutAsapMode()) {
     return;
   }
 
-  NS_WARNING("Enabling vsync refresh driver\n");
+  NS_WARNING("Enabling vsync refresh driver");
 
   if (XRE_IsParentProcess()) {
     // Make sure all vsync systems are ready.
     gfxPlatform::GetPlatform();
     // In parent process, we don't need to use ipc. We can create the
     // VsyncRefreshDriverTimer directly.
     sRegularRateTimer = new VsyncRefreshDriverTimer();
     return;
@@ -1325,16 +1325,17 @@ nsRefreshDriver::ObserverCount() const
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
     sum += mObservers[i].Length();
   }
 
   // Even while throttled, we need to process layout and style changes.  Style
   // changes can trigger transitions which fire events when they complete, and
   // layout changes can affect media queries on child documents, triggering
   // style changes, etc.
+  sum += mResizeEventFlushObservers.Length();
   sum += mStyleFlushObservers.Length();
   sum += mLayoutFlushObservers.Length();
   sum += mFrameRequestCallbackDocs.Length();
   sum += mThrottledFrameRequestCallbackDocs.Length();
   sum += mViewManagerFlushIsPending;
   return sum;
 }
 
@@ -1622,16 +1623,34 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
   }
 
   AutoRestore<bool> restoreInRefresh(mInRefresh);
   mInRefresh = true;
 
   AutoRestore<TimeStamp> restoreTickStart(mTickStart);
   mTickStart = TimeStamp::Now();
 
+  // Resize events should be fired before layout flushes or
+  // calling animation frame callbacks.
+  nsAutoTArray<nsIPresShell*, 16> observers;
+  observers.AppendElements(mResizeEventFlushObservers);
+  for (uint32_t i = observers.Length(); i; --i) {
+    if (!mPresContext || !mPresContext->GetPresShell()) {
+      break;
+    }
+    // Make sure to not process observers which might have been removed
+    // during previous iterations.
+    nsIPresShell* shell = observers[i - 1];
+    if (!mResizeEventFlushObservers.Contains(shell)) {
+      continue;
+    }
+    mResizeEventFlushObservers.RemoveElement(shell);
+    shell->FireResizeEvent();
+  }
+
   /*
    * The timer holds a reference to |this| while calling |Notify|.
    * However, implementations of |WillRefresh| are permitted to destroy
    * the pres context, which will cause our |mPresContext| to become
    * null.  If this happens, we must stop notifying observers.
    */
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
     ObserverArray::EndLimitedIterator etor(mObservers[i]);
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -144,16 +144,32 @@ public:
    * imgIRequests to the nsRefreshDriver for notification of paint events.
    *
    * @returns whether the operation succeeded, or void in the case of removal.
    */
   bool AddImageRequest(imgIRequest* aRequest);
   void RemoveImageRequest(imgIRequest* aRequest);
 
   /**
+   * Add / remove presshells which have pending resize event.
+   */
+  void AddResizeEventFlushObserver(nsIPresShell* aShell)
+  {
+    NS_ASSERTION(!mResizeEventFlushObservers.Contains(aShell),
+      "Double-adding resize event flush observer");
+    mResizeEventFlushObservers.AppendElement(aShell);
+    EnsureTimerStarted();
+  }
+
+  void RemoveResizeEventFlushObserver(nsIPresShell* aShell)
+  {
+    mResizeEventFlushObservers.RemoveElement(aShell);
+  }
+
+  /**
    * Add / remove presshells that we should flush style and layout on
    */
   bool AddStyleFlushObserver(nsIPresShell* aShell) {
     NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
 		 "Double-adding style flush observer");
     // We only get the cause for the first observer each frame because capturing
     // a stack is expensive. This is still useful if (1) you're trying to remove
     // all flushes for a particial frame or (2) the costly flush is triggered
@@ -387,16 +403,17 @@ private:
   mozilla::TimeStamp mTickStart;
   mozilla::TimeStamp mNextThrottledFrameRequestTick;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[3];
   RequestTable mRequests;
   ImageStartTable mStartTable;
 
+  nsAutoTArray<nsIPresShell*, 16> mResizeEventFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
   nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
   nsTArray<nsAPostRefreshObserver*> mPostRefreshObservers;
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4736,18 +4736,30 @@ AdjustOverlappingScrollbars(nsRect& aVRe
 void
 ScrollFrameHelper::LayoutScrollbars(nsBoxLayoutState& aState,
                                         const nsRect& aContentArea,
                                         const nsRect& aOldScrollArea)
 {
   NS_ASSERTION(!mSupppressScrollbarUpdate,
                "This should have been suppressed");
 
+  nsIPresShell* presShell = mOuter->PresContext()->PresShell();
+
   bool hasResizer = HasResizer();
   bool scrollbarOnLeft = !IsScrollbarOnRight();
+  bool overlayScrollBarsWithZoom =
+    mIsRoot && LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) &&
+    presShell->IsScrollPositionClampingScrollPortSizeSet();
+
+  nsSize scrollPortClampingSize = mScrollPort.Size();
+  double res = 1.0;
+  if (overlayScrollBarsWithZoom) {
+    scrollPortClampingSize = presShell->GetScrollPositionClampingScrollPortSize();
+    res = presShell->GetCumulativeResolution();
+  }
 
   // place the scrollcorner
   if (mScrollCornerBox || mResizerBox) {
     NS_PRECONDITION(!mScrollCornerBox || mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
 
     nsRect r(0, 0, 0, 0);
     if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
       // scrollbar (if any) on left
@@ -4800,32 +4812,38 @@ ScrollFrameHelper::LayoutScrollbars(nsBo
     }
   }
 
   nsPresContext* presContext = mScrolledFrame->PresContext();
   nsRect vRect;
   if (mVScrollbarBox) {
     NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
     vRect = mScrollPort;
+    if (overlayScrollBarsWithZoom) {
+      vRect.height = NSToCoordRound(res * scrollPortClampingSize.height);
+    }
     vRect.width = aContentArea.width - mScrollPort.width;
-    vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
+    vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.x + NSToCoordRound(res * scrollPortClampingSize.width);
     if (mHasVerticalScrollbar) {
       nsMargin margin;
       mVScrollbarBox->GetMargin(margin);
       vRect.Deflate(margin);
     }
     AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, true);
   }
 
   nsRect hRect;
   if (mHScrollbarBox) {
     NS_PRECONDITION(mHScrollbarBox->IsBoxFrame(), "Must be a box frame!");
     hRect = mScrollPort;
+    if (overlayScrollBarsWithZoom) {
+      hRect.width = NSToCoordRound(res * scrollPortClampingSize.width);
+    }
     hRect.height = aContentArea.height - mScrollPort.height;
-    hRect.y = true ? mScrollPort.YMost() : aContentArea.y;
+    hRect.y = mScrollPort.y + NSToCoordRound(res * scrollPortClampingSize.height);
     if (mHasHorizontalScrollbar) {
       nsMargin margin;
       mHScrollbarBox->GetMargin(margin);
       hRect.Deflate(margin);
     }
     AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, false);
   }
 
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -21,29 +21,32 @@
 #include "nsIFrameInlines.h"
 #include "nsPresContext.h"
 #include "nsReadableUtils.h"
 #include "nsRuleNode.h"
 #include "nsStyleContext.h"
 
 using namespace mozilla;
 typedef nsGridContainerFrame::TrackSize TrackSize;
-const uint32_t nsGridContainerFrame::kAutoLine = 12345U;
 const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
   uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine - 1);
+const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;
 
 class nsGridContainerFrame::GridItemCSSOrderIterator
 {
 public:
   enum OrderState { eUnknownOrder, eKnownOrdered, eKnownUnordered };
+  enum ChildFilter { eSkipPlaceholders, eIncludeAll };
   GridItemCSSOrderIterator(nsIFrame* aGridContainer,
                            nsIFrame::ChildListID aListID,
+                           ChildFilter aFilter = eSkipPlaceholders,
                            OrderState aState = eUnknownOrder)
     : mChildren(aGridContainer->GetChildList(aListID))
     , mArrayIndex(0)
+    , mSkipPlaceholders(aFilter == eSkipPlaceholders)
 #ifdef DEBUG
     , mGridContainer(aGridContainer)
     , mListID(aListID)
 #endif
   {
     size_t count = 0;
     bool isOrdered = aState != eKnownUnordered;
     if (aState == eUnknownOrder) {
@@ -64,27 +67,53 @@ public:
       count *= 2; // XXX somewhat arbitrary estimate for now...
       mArray.emplace(count);
       for (nsFrameList::Enumerator e(mChildren); !e.AtEnd(); e.Next()) {
         mArray->AppendElement(e.get());
       }
       // XXX replace this with nsTArray::StableSort when bug 1147091 is fixed.
       std::stable_sort(mArray->begin(), mArray->end(), IsCSSOrderLessThan);
     }
+
+    if (mSkipPlaceholders) {
+      SkipPlaceholders();
+    }
   }
 
   nsIFrame* operator*() const
   {
     MOZ_ASSERT(!AtEnd());
     if (mEnumerator) {
       return mEnumerator->get();
     }
     return (*mArray)[mArrayIndex];
   }
 
+  /**
+   * Skip over placeholder children.
+   */
+  void SkipPlaceholders()
+  {
+    if (mEnumerator) {
+      for (; !mEnumerator->AtEnd(); mEnumerator->Next()) {
+        nsIFrame* child = mEnumerator->get();
+        if (child->GetType() != nsGkAtoms::placeholderFrame) {
+          return;
+        }
+      }
+    } else {
+      for (; mArrayIndex < mArray->Length(); ++mArrayIndex) {
+        nsIFrame* child = (*mArray)[mArrayIndex];
+        if (child->GetType() != nsGkAtoms::placeholderFrame) {
+          return;
+        }
+      }
+    }
+  }
+
   bool AtEnd() const
   {
     MOZ_ASSERT(mEnumerator || mArrayIndex <= mArray->Length());
     return mEnumerator ? mEnumerator->AtEnd() : mArrayIndex >= mArray->Length();
   }
 
   void Next()
   {
@@ -95,40 +124,49 @@ public:
                "the list of child frames must not change while iterating!");
 #endif
     if (mEnumerator) {
       mEnumerator->Next();
     } else {
       MOZ_ASSERT(mArrayIndex < mArray->Length(), "iterating past end");
       ++mArrayIndex;
     }
+    if (mSkipPlaceholders) {
+      SkipPlaceholders();
+    }
   }
 
-  void Reset()
+  void Reset(ChildFilter aFilter = eSkipPlaceholders)
   {
     if (mEnumerator) {
       mEnumerator.reset();
       mEnumerator.emplace(mChildren);
     } else {
       mArrayIndex = 0;
     }
+    mSkipPlaceholders = aFilter == eSkipPlaceholders;
+    if (mSkipPlaceholders) {
+      SkipPlaceholders();
+    }
   }
 
   bool ItemsAreAlreadyInOrder() const { return mEnumerator.isSome(); }
 
 private:
   static bool IsCSSOrderLessThan(nsIFrame* const& a, nsIFrame* const& b)
     { return a->StylePosition()->mOrder < b->StylePosition()->mOrder; }
 
   nsFrameList mChildren;
   // Used if child list is already in ascending 'order'.
   Maybe<nsFrameList::Enumerator> mEnumerator;
   // Used if child list is *not* in ascending 'order'.
   Maybe<nsTArray<nsIFrame*>> mArray;
   size_t mArrayIndex;
+  // Skip placeholder children in the iteration?
+  bool mSkipPlaceholders;
 #ifdef DEBUG
   nsIFrame* mGridContainer;
   nsIFrame::ChildListID mListID;
 #endif
 };
 
 /**
  * Search for the aNth occurrence of aName in aNameList (forward), starting at
@@ -1208,37 +1246,44 @@ nsGridContainerFrame::ReflowChildren(Gri
 {
   WritingMode wm = aReflowState.GetWritingMode();
   const LogicalPoint gridOrigin(aContentArea.Origin(wm));
   const nscoord containerWidth = aContentArea.Width(wm) +
     aReflowState.ComputedPhysicalBorderPadding().LeftRight();
   nsPresContext* pc = PresContext();
   for (; !aIter.AtEnd(); aIter.Next()) {
     nsIFrame* child = *aIter;
-    GridArea* area = GetGridAreaForChild(child);
-    MOZ_ASSERT(area && area->IsDefinite());
-    LogicalRect cb = ContainingBlockFor(wm, *area, aColSizes, aRowSizes);
-    cb += gridOrigin;
+    const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame;
+    LogicalRect cb(wm);
+    if (MOZ_LIKELY(isGridItem)) {
+      GridArea* area = GetGridAreaForChild(child);
+      MOZ_ASSERT(area && area->IsDefinite());
+      cb = ContainingBlockFor(wm, *area, aColSizes, aRowSizes);
+      cb += gridOrigin;
+    } else {
+      cb = aContentArea;
+    }
     nsHTMLReflowState childRS(pc, aReflowState, child, cb.Size(wm));
     const LogicalMargin margin = childRS.ComputedLogicalMargin();
-    if (childRS.ComputedBSize() == NS_AUTOHEIGHT) {
+    if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) {
       // XXX the start of an align-self:stretch impl.  Needs min-/max-bsize
       // clamping though, and check the prop value is actually 'stretch'!
       LogicalMargin bp = childRS.ComputedLogicalBorderPadding();
       bp.ApplySkipSides(child->GetLogicalSkipSides());
       nscoord bSize = cb.BSize(wm) - bp.BStartEnd(wm) - margin.BStartEnd(wm);
       childRS.SetComputedBSize(std::max(bSize, 0));
     }
     LogicalPoint childPos = cb.Origin(wm);
     childPos.I(wm) += margin.IStart(wm);
     childPos.B(wm) += margin.BStart(wm);
     nsHTMLReflowMetrics childSize(childRS);
     nsReflowStatus childStatus;
     ReflowChild(child, pc, childSize, childRS, wm, childPos,
                 containerWidth, 0, childStatus);
+    childRS.ApplyRelativePositioning(&childPos, containerWidth);
     FinishReflowChild(child, pc, childSize, &childRS, wm, childPos,
                       containerWidth, 0);
     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
     // XXX deal with 'childStatus' not being COMPLETE
   }
 
   if (IsAbsoluteContainer()) {
     nsFrameList children(GetChildList(GetAbsoluteListID()));
@@ -1325,17 +1370,17 @@ nsGridContainerFrame::Reflow(nsPresConte
   bSize = std::max(bSize - GetConsumedBSize(), 0);
   LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
                           bSize + bp.BStartEnd(wm));
   aDesiredSize.SetSize(wm, desiredSize);
   aDesiredSize.SetOverflowAreasToDesiredBounds();
 
   LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm),
                           computedISize, bSize);
-  normalFlowIter.Reset();
+  normalFlowIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
   ReflowChildren(normalFlowIter, contentArea, colSizes, rowSizes, aDesiredSize,
                  aReflowState, aStatus);
 
   FinishAndStoreOverflow(&aDesiredSize);
   aStatus = NS_FRAME_COMPLETE;
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
 }
 
@@ -1362,17 +1407,18 @@ nsGridContainerFrame::BuildDisplayList(n
                               aLists.BlockBorderBackgrounds(),
                               aLists.Floats(),
                               aLists.Content(),
                               &positionedDescendants,
                               aLists.Outlines());
   typedef GridItemCSSOrderIterator::OrderState OrderState;
   OrderState order = mIsNormalFlowInCSSOrder ? OrderState::eKnownOrdered
                                              : OrderState::eKnownUnordered;
-  GridItemCSSOrderIterator iter(this, kPrincipalList, order);
+  GridItemCSSOrderIterator iter(this, kPrincipalList,
+                                GridItemCSSOrderIterator::eIncludeAll, order);
   for (; !iter.AtEnd(); iter.Next()) {
     nsIFrame* child = *iter;
     BuildDisplayListForChild(aBuilder, child, aDirtyRect, childLists,
                              ::GetDisplayFlagsForGridItem(child));
   }
   positionedDescendants.SortByCSSOrder(aBuilder);
   aLists.PositionedDescendants()->AppendToTop(&positionedDescendants);
 }
@@ -1432,18 +1478,17 @@ nsGridContainerFrame::CellMap::Dump() co
   }
 }
 
 static bool
 FrameWantsToBeInAnonymousGridItem(nsIFrame* aFrame)
 {
   // Note: This needs to match the logic in
   // nsCSSFrameConstructor::FrameConstructionItem::NeedsAnonFlexOrGridItem()
-  return (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
-          nsGkAtoms::placeholderFrame == aFrame->GetType());
+  return aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
 }
 
 // Debugging method, to let us assert that our anonymous grid items are
 // set up correctly -- in particular, we assert:
 //  (1) we don't have any inline non-replaced children
 //  (2) we don't have any consecutive anonymous grid items
 //  (3) we don't have any empty anonymous grid items
 //  (4) all children are on the expected child lists
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=325">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=125">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-3.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=525">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-4.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-5.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=925">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-6.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=1325">
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-h-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-h.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-rtl.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-v-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-v.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="height: 20000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-vh-rtl.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+<style> html { direction: rtl; } </style>
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref-vh.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+<div style="height: 20000px; width: 9000px;"></div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1133905-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=725">
+</head>
+<body>
+</body>
+</html>
+
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1867,12 +1867,60 @@ fuzzy-if(d2d,36,304) HTTP(..) == 1116480
 == 1121748-2.html 1121748-2-ref.html
 == 1127107-1a-nowrap.html 1127107-1-ref.html
 == 1127107-1b-pre.html 1127107-1-ref.html
 == 1127107-2-capitalize.html 1127107-2-capitalize-ref.html
 == 1127679-1a-inline-flex-relpos.html 1127679-1b-inline-flex-relpos.html
 == 1128354-1.html 1128354-1-ref.html
 == 1130231-1-button-padding-rtl.html 1130231-1-button-padding-rtl-ref.html
 == 1130231-2-button-padding-rtl.html 1130231-2-button-padding-rtl-ref.html
+== 1133905-1.html 1133905-ref.html
+== 1133905-2.html 1133905-ref.html
+== 1133905-3.html 1133905-ref.html
+== 1133905-4.html 1133905-ref.html
+== 1133905-5.html 1133905-ref.html
+== 1133905-6.html 1133905-ref.html
+== 1133905-1-v.html 1133905-ref-v.html
+== 1133905-2-v.html 1133905-ref-v.html
+== 1133905-3-v.html 1133905-ref-v.html
+== 1133905-4-v.html 1133905-ref-v.html
+fuzzy-if(B2G,61,336) == 1133905-5-v.html 1133905-ref-v.html
+fuzzy-if(B2G,61,480) == 1133905-6-v.html 1133905-ref-v.html
+== 1133905-1-h.html 1133905-ref-h.html
+== 1133905-2-h.html 1133905-ref-h.html
+== 1133905-3-h.html 1133905-ref-h.html
+== 1133905-4-h.html 1133905-ref-h.html
+== 1133905-5-h.html 1133905-ref-h.html
+== 1133905-6-h.html 1133905-ref-h.html
+== 1133905-1-vh.html 1133905-ref-vh.html
+== 1133905-2-vh.html 1133905-ref-vh.html
+== 1133905-3-vh.html 1133905-ref-vh.html
+== 1133905-4-vh.html 1133905-ref-vh.html
+fuzzy-if(B2G,102,720) == 1133905-5-vh.html 1133905-ref-vh.html
+fuzzy-if(B2G,101,1138) == 1133905-6-vh.html 1133905-ref-vh.html
+== 1133905-1-rtl.html 1133905-ref-rtl.html
+== 1133905-2-rtl.html 1133905-ref-rtl.html
+== 1133905-3-rtl.html 1133905-ref-rtl.html
+== 1133905-4-rtl.html 1133905-ref-rtl.html
+== 1133905-5-rtl.html 1133905-ref-rtl.html
+== 1133905-6-rtl.html 1133905-ref-rtl.html
+== 1133905-1-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-2-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-3-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-4-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-5-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-6-v-rtl.html 1133905-ref-v-rtl.html
+== 1133905-1-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-2-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-3-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-4-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-5-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-6-h-rtl.html 1133905-ref-h-rtl.html
+== 1133905-1-vh-rtl.html 1133905-ref-vh-rtl.html
+== 1133905-2-vh-rtl.html 1133905-ref-vh-rtl.html
+== 1133905-3-vh-rtl.html 1133905-ref-vh-rtl.html
+== 1133905-4-vh-rtl.html 1133905-ref-vh-rtl.html
+fuzzy-if(B2G,102,396) == 1133905-5-vh-rtl.html 1133905-ref-vh-rtl.html
+fuzzy-if(B2G,101,672) == 1133905-6-vh-rtl.html 1133905-ref-vh-rtl.html
 skip-if(B2G||Mulet) == 1150021-1.xul 1150021-1-ref.xul
 == 1151145-1.html 1151145-1-ref.html
 == 1151306-1.html 1151306-1-ref.html
 == 1156129-1.html 1156129-1-ref.html
--- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html
+++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html
@@ -20,17 +20,17 @@ body,html { color:black; background:whit
  grid-auto-flow: row;
  grid-auto-columns: 23px;
  grid-auto-rows: 17px;
  padding: 17px 7px 11px 13px;
  width: 100px;
  height: 100px;
 }
 .zero-size { width:0; height:0; }
-.auto-size { width:auto; height:17px; }
+.auto-size { width:auto; height:0px; }
 
 .a {
   position: absolute;
   left: 13px; top: 51px;
   height: 32px; width: 44px;
 }
 
 .abs {
@@ -83,38 +83,38 @@ span {
 <span class="e abs">e</span>
 <span class="f abs">f</span>
 <span class="g abs">g</span>
 </div>
 
 <div class="grid">
 <span class="a">a</span>
 <span class="b abs">b</span>
-<span class="c abs" style="height:20px">c</span>
+<span class="c abs">c</span>
 <span class="d abs">d</span>
 <span class="e abs">e</span>
 <span class="f abs">f</span>
 <span class="g abs">g</span>
 </div>
 
 <div class="grid zero-size">
-<span class="b abs">b</span>
+<span class="b abs" style="width:5px">b</span>
 </div>
 
 <div class="grid auto-size">
 <span class="h abs">h</span>
 </div>
 
 <div class="grid" style="width:43px; height:53px">
 <span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span>
 <span class="abs" style="right:5px; top:3px; height:11px; width:42px;">b</span>
 <span class="abs" style="left:1px; bottom:1px; height:58px; width:5px;">c</span>
 <span class="abs" style="right:5px; bottom:1px; height:58px; width:42px;">d</span>
 </div>
 
 <div class="grid" style="width:43px; height:53px; border-width:0;">
-<span class="abs" style="right:24px; top:3px; height:11px; width:0px;">b</span>
-<span class="abs" style="left:1px; bottom:42px; height:0px; width:5px;">c</span>
-<span class="abs" style="right:24px; bottom:42px; height:0px; width:0px;">d</span>
+<span class="abs" style="right:47px; top:3px; height:11px; width:0px;">b</span>
+<span class="abs" style="left:1px; bottom:54px; height:5px; width:5px;">c</span>
+<span class="abs" style="right:47px; bottom:54px; height:5px; width:0px;">d</span>
 </div>
 
 </body>
 </html>
--- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html
+++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html
@@ -21,17 +21,17 @@ body,html { color:black; background:whit
  grid-auto-flow: row;
  grid-auto-columns: 23px;
  grid-auto-rows: 17px;
  padding: 17px 7px 11px 13px;
  width: 100px;
  height: 100px;
 }
 .zero-size { width:0; height:0; }
-.auto-size { width:auto; height:17px; }
+.auto-size { width:auto; height:0px; }
 
 .a {
   position: absolute;
   left: 13px; top: 51px;
   height: 32px; width: 44px;
 }
 
 .abs {
@@ -84,25 +84,25 @@ span {
 <span class="e abs">e</span>
 <span class="f abs">f</span>
 <span class="g abs">g</span>
 </div>
 
 <div class="grid">
 <span class="a">a</span>
 <span class="b abs">b</span>
-<span class="c abs" style="height:20px">c</span>
+<span class="c abs">c</span>
 <span class="d abs">d</span>
 <span class="e abs">e</span>
 <span class="f abs">f</span>
 <span class="g abs">g</span>
 </div>
 
 <div class="grid zero-size">
-<span class="b abs">b</span>
+<span class="b abs" style="width:5px">b</span>
 </div>
 
 <div class="grid auto-size">
 <span class="h abs">h</span>
 </div>
 
 <div class="grid" style="width:43px; height:53px">
 <span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-relpos-items-001-ref.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <title>CSS Test: Testing layout of rel.pos. grid items</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151316">
+  <meta charset="utf-8">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:16px; padding:0; margin:0; }
+
+.grid {
+ height: 20px;
+ border: 3px solid;
+}
+
+.c1 { background: grey; }
+.r2 { -moz-transform: translate(-201px, -90px); }
+.r3 { -moz-transform: translate(-10px, -40px); }
+.r4 { -moz-transform: translate(70px, 40px); }
+
+span {
+  border: 1px solid;
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  vertical-align: top;
+}
+.abs {
+  background: lime;
+  position: absolute;
+  top: 116px;
+  left: 204px;
+  padding-left: 3px;
+  padding-top: 5px;
+  width: 15px;
+  height: 13px;
+}
+.abs1 {
+  position: absolute;
+  top: -5px;
+  left: -11px;
+  width: 27px;
+  height: 21px;
+}
+  </style>
+</head>
+<body>
+
+<div class="grid">
+<span class="c1">1</span>
+<span class="abs r1"><iframe width="20" height="20" src="data:text/html,<body>F"></iframe></span>
+<span class="abs r2"><iframe width="20" height="20" src="data:text/html,<body>2"></iframe></span>
+<span class="abs r3"><span class="abs1">r3</span></span>
+<span class="abs r4"><span class="abs1">r4</span></span>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-relpos-items-001.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <title>CSS Test: Testing layout of rel.pos. grid items</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151316">
+  <link rel="help" href="http://dev.w3.org/csswg/css-grid/">
+  <link rel="match" href="grid-relpos-items-001-ref.html">
+  <meta charset="utf-8">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:16px; padding:0; margin:0; }
+
+.grid {
+ display: grid;
+ grid-template-columns: 20px 20px (A) 20px;
+ grid-template-rows: 20px;
+ grid-auto-columns: 20px;
+ grid-auto-rows: 20px;
+ border: solid;
+}
+
+.c1 { background: grey; }
+.r1 { }
+.r2 { -moz-transform: translate(-201px, -90px); }
+.r3 { grid-column-start: auto; -moz-transform: translate(-30px, -40px); }
+.r4 { grid-area: auto; -moz-transform: translate(30px, 40px); }
+
+span {
+  border: 1px solid;
+  grid-area: 1 / 1;
+}
+.rel {
+  background: lime;
+  position: relative;
+  top: 113px;
+  left: 201px;
+  padding-left: 3px;
+  padding-top: 5px;
+}
+.abs1 {
+  position: absolute;
+  top: -5px;
+  left: -11px;
+  right: 0px;
+  bottom: 0px;
+}
+  </style>
+</head>
+<body>
+
+<div class="grid">
+<span class="c1">1</span>
+<span class="rel r1"><iframe width="20" height="20" src="data:text/html,<body>F"></iframe></span>
+<span class="rel r2"><iframe width="20" height="20" src="data:text/html,<body>2"></iframe></span>
+<span class="rel r3"><span class="abs1">r3</span></span>
+<span class="rel r4"><span class="abs1">r4</span></span>
+</div>
+
+</body>
+</html>
--- a/layout/reftests/css-grid/reftest.list
+++ b/layout/reftests/css-grid/reftest.list
@@ -18,8 +18,9 @@ fails == grid-whitespace-handling-1b.xht
 skip-if(Android) == grid-placement-definite-implicit-001.html grid-placement-definite-implicit-001-ref.html
 == grid-placement-definite-implicit-002.html grid-placement-definite-implicit-002-ref.html
 skip-if(Android) == grid-placement-auto-implicit-001.html grid-placement-auto-implicit-001-ref.html
 == grid-placement-abspos-implicit-001.html grid-placement-abspos-implicit-001-ref.html
 pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-definite-001.html rtl-grid-placement-definite-001-ref.html
 pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-auto-row-sparse-001.html rtl-grid-placement-auto-row-sparse-001-ref.html
 pref(layout.css.vertical-text.enabled,true) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001-ref.html
 pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001-ref.html
+== grid-relpos-items-001.html grid-relpos-items-001-ref.html
--- a/layout/reftests/font-face/ex-unit-1-dynamic.html
+++ b/layout/reftests/font-face/ex-unit-1-dynamic.html
@@ -10,11 +10,11 @@ body { font-family: Ahhhem; font-size: 5
 <script type="application/ecmascript">
 function run() {
   document.getElementsByTagName("iframe")[0].contentWindow.arm();
   document.getElementsByTagName("style")[0].sheet.insertRule(
     '@font-face { font-family: "Ahhhem"; src: url(../fonts/Ahem.ttf); }',
     0);
 }
 </script>
-<body onload="setTimeout(run, 0)">
+<body onload="setTimeout(run, 20)">
 <iframe style="visibility:hidden;position:absolute;height:100%;width:100%" src="resize-detector-iframe.html"></iframe>
 </body>
copy from layout/style/test/test_animations_async_tests.html
copy to layout/style/test/file_animations_async_tests.html
--- a/layout/style/test/test_animations_async_tests.html
+++ b/layout/style/test/file_animations_async_tests.html
@@ -1,29 +1,35 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1086937</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script>
+    var is = opener.is.bind(opener);
+    var ok = opener.ok.bind(opener);
+    var todo = opener.todo.bind(opener);
+    function finish() {
+      var o = opener;
+      self.close();
+      o.SimpleTest.finish();
+    }
+  </script>
   <script type="application/javascript" src="animation_utils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
   /* must use implicit value at one end */
   @keyframes slide-left { from { margin-left: -1000px } }
   </style>
   <script type="application/javascript">
 
-  SimpleTest.waitForExplicitFinish();
-
   var gDisplay;
 
   function run() {
     gDisplay = document.getElementById("display");
-    setTimeout(test1, 0);
+    opener.SimpleTest.executeSoon(test1);
   }
 
   /*
    * Bug 1086937 - Animations continue correctly across load of
    * downloadable font.
    */
   function test1() {
     var animdiv = document.createElement("div");
@@ -51,17 +57,17 @@
       is(cs.marginLeft, "-980px",
          "animation should still be advancing after font load");
 
       SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
       document.fonts.delete(font);
       animdiv.remove();
       fontdiv.remove();
 
-      SimpleTest.finish();
+      finish();
     });
   }
 
   </script>
 </head>
 <body onload="run()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
 <div id="display"></div>
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -35,16 +35,17 @@ generated-files = css_properties.js
 [test_acid3_test46.html]
 [test_all_shorthand.html]
 [test_animations.html]
 skip-if = toolkit == 'android'
 [test_animations_omta.html]
 [test_animations_omta_start.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1041017
 [test_animations_pausing.html]
+support-files = file_animations_pausing.html
 [test_any_dynamic.html]
 [test_at_rule_parse_serialize.html]
 [test_bug73586.html]
 [test_bug74880.html]
 [test_bug98997.html]
 [test_bug160403.html]
 [test_bug200089.html]
 [test_bug221428.html]
@@ -262,12 +263,12 @@ skip-if = buildapp == 'b2g' || toolkit =
 support-files = bug732209-css.sjs
 [test_bug795520.html]
 [test_background_blend_mode.html]
 [test_property_database.html]
 [test_counter_style.html]
 [test_counter_descriptor_storage.html]
 [test_position_float_display.html]
 [test_animations_async_tests.html]
-support-files = ../../reftests/fonts/Ahem.ttf file_animations_pausing.html
+support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html
 [test_setPropertyWithNull.html]
 [test_attribute_selector_eof_behavior.html]
 [test_css_loader_crossorigin_data_url.html]
--- a/layout/style/test/test_animations_async_tests.html
+++ b/layout/style/test/test_animations_async_tests.html
@@ -1,70 +1,25 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1086937</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="animation_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <style>
-  /* must use implicit value at one end */
-  @keyframes slide-left { from { margin-left: -1000px } }
-  </style>
   <script type="application/javascript">
 
   SimpleTest.waitForExplicitFinish();
 
-  var gDisplay;
-
   function run() {
-    gDisplay = document.getElementById("display");
-    setTimeout(test1, 0);
+    SpecialPowers.pushPrefEnv(
+      {"set": [['layout.css.font-loading-api.enabled', true]]},
+      function() { window.open("file_animations_async_tests.html"); });
   }
-
-  /*
-   * Bug 1086937 - Animations continue correctly across load of
-   * downloadable font.
-   */
-  function test1() {
-    var animdiv = document.createElement("div");
-    // Take control of the refresh driver right from the start
-    advance_clock(0);
-    animdiv.style.animation = "slide-left 100s linear"; // 10px per second
-    gDisplay.appendChild(animdiv);
-    var cs = getComputedStyle(animdiv, "");
-    is(cs.marginLeft, "-1000px", "initial value of animation (force flush)");
-    advance_clock(1000);
-    is(cs.marginLeft, "-990px", "value of animation before font load");
-
-    var font = new FontFace("DownloadedAhem", "url(Ahem.ttf)");
-    document.fonts.add(font);
-
-    var fontdiv = document.createElement("div");
-    fontdiv.appendChild(document.createTextNode("A"));
-    fontdiv.style.fontFamily = "DownloadedAhem";
-    gDisplay.appendChild(fontdiv);
-
-    font.load().then(function(loadedFace) {
-      is(cs.marginLeft, "-990px", "value of animation after font load " +
-                                  "(clock only advances when we say so)");
-      advance_clock(1000);
-      is(cs.marginLeft, "-980px",
-         "animation should still be advancing after font load");
-
-      SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
-      document.fonts.delete(font);
-      animdiv.remove();
-      fontdiv.remove();
-
-      SimpleTest.finish();
-    });
-  }
-
   </script>
 </head>
 <body onload="run()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
 <div id="display"></div>
 <pre id="test">
 </pre>
 </body>
--- a/media/gmp-clearkey/0.1/AudioDecoder.cpp
+++ b/media/gmp-clearkey/0.1/AudioDecoder.cpp
@@ -27,16 +27,18 @@ using namespace wmf;
 AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
   : mHostAPI(aHostAPI)
   , mCallback(nullptr)
   , mWorkerThread(nullptr)
   , mMutex(nullptr)
   , mNumInputTasks(0)
   , mHasShutdown(false)
 {
+  // We drop the ref in DecodingComplete().
+  AddRef();
 }
 
 AudioDecoder::~AudioDecoder()
 {
   if (mMutex) {
     mMutex->Destroy();
   }
 }
@@ -79,19 +81,19 @@ AudioDecoder::EnsureWorker()
 void
 AudioDecoder::Decode(GMPAudioSamples* aInput)
 {
   EnsureWorker();
   {
     AutoLock lock(mMutex);
     mNumInputTasks++;
   }
-  mWorkerThread->Post(WrapTask(this,
-                               &AudioDecoder::DecodeTask,
-                               aInput));
+  mWorkerThread->Post(WrapTaskRefCounted(this,
+                                         &AudioDecoder::DecodeTask,
+                                         aInput));
 }
 
 void
 AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
 {
   HRESULT hr;
 
   {
@@ -253,46 +255,41 @@ AudioDecoder::DrainTask()
 
 void
 AudioDecoder::Drain()
 {
   if (!mDecoder) {
     return;
   }
   EnsureWorker();
-  mWorkerThread->Post(WrapTask(this,
-                               &AudioDecoder::DrainTask));
+  mWorkerThread->Post(WrapTaskRefCounted(this,
+                                         &AudioDecoder::DrainTask));
 }
 
 void
 AudioDecoder::DecodingComplete()
 {
   if (mWorkerThread) {
     mWorkerThread->Join();
   }
   mHasShutdown = true;
 
-  // Worker thread might have dispatched more tasks to the main thread that need this object.
-  // Append another task to delete |this|.
-  GetPlatform()->runonmainthread(WrapTask(this, &AudioDecoder::Destroy));
+  // Release the reference we added in the constructor. There may be
+  // WrapRefCounted tasks that also hold references to us, and keep
+  // us alive a little longer.
+  Release();
 }
 
 void
-AudioDecoder::Destroy()
-{
-  delete this;
-}
-
-void
-AudioDecoder::MaybeRunOnMainThread(gmp_task_args_base* aTask)
+AudioDecoder::MaybeRunOnMainThread(GMPTask* aTask)
 {
   class MaybeRunTask : public GMPTask
   {
   public:
-    MaybeRunTask(AudioDecoder* aDecoder, gmp_task_args_base* aTask)
+    MaybeRunTask(AudioDecoder* aDecoder, GMPTask* aTask)
       : mDecoder(aDecoder), mTask(aTask)
     { }
 
     virtual void Run(void) {
       if (mDecoder->HasShutdown()) {
         CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
         return;
       }
@@ -302,14 +299,14 @@ AudioDecoder::MaybeRunOnMainThread(gmp_t
 
     virtual void Destroy()
     {
       mTask->Destroy();
       delete this;
     }
 
   private:
-    AudioDecoder* mDecoder;
-    gmp_task_args_base* mTask;
+    RefPtr<AudioDecoder> mDecoder;
+    GMPTask* mTask;
   };
 
   GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
 }
--- a/media/gmp-clearkey/0.1/AudioDecoder.h
+++ b/media/gmp-clearkey/0.1/AudioDecoder.h
@@ -16,53 +16,53 @@
 
 #ifndef __AudioDecoder_h__
 #define __AudioDecoder_h__
 
 #include "gmp-audio-decode.h"
 #include "gmp-audio-host.h"
 #include "gmp-task-utils.h"
 #include "WMFAACDecoder.h"
+#include "RefCounted.h"
 
 #include "mfobjects.h"
 
 class AudioDecoder : public GMPAudioDecoder
+                   , public RefCounted
 {
 public:
   AudioDecoder(GMPAudioHost *aHostAPI);
 
-  virtual ~AudioDecoder();
-
   virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
                           GMPAudioDecoderCallback* aCallback) override;
 
   virtual void Decode(GMPAudioSamples* aEncodedSamples);
 
   virtual void Reset() override;
 
   virtual void Drain() override;
 
   virtual void DecodingComplete() override;
 
   bool HasShutdown() { return mHasShutdown; }
 
 private:
+  virtual ~AudioDecoder();
 
   void EnsureWorker();
 
   void DecodeTask(GMPAudioSamples* aEncodedSamples);
   void DrainTask();
 
   void ReturnOutput(IMFSample* aSample);
 
   HRESULT MFToGMPSample(IMFSample* aSample,
                         GMPAudioSamples* aAudioFrame);
 
-  void MaybeRunOnMainThread(gmp_task_args_base* aTask);
-  void Destroy();
+  void MaybeRunOnMainThread(GMPTask* aTask);
 
   GMPAudioHost *mHostAPI; // host-owned, invalid at DecodingComplete
   GMPAudioDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
   GMPThread* mWorkerThread;
   GMPMutex* mMutex;
   wmf::AutoPtr<wmf::WMFAACDecoder> mDecoder;
 
   int32_t mNumInputTasks;
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -18,16 +18,17 @@
 #include "ClearKeySession.h"
 #include "ClearKeyUtils.h"
 #include "ClearKeyStorage.h"
 #include "gmp-task-utils.h"
 
 #include "gmp-api/gmp-decryption.h"
 #include "Endian.h"
 #include <assert.h>
+#include <string.h>
 
 using namespace mozilla;
 
 ClearKeySession::ClearKeySession(const std::string& aSessionId,
                                  GMPDecryptorCallback* aCallback,
                                  GMPSessionType aSessionType)
   : mSessionId(aSessionId)
   , mCallback(aCallback)
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -43,17 +43,16 @@ ClearKeySessionManager::ClearKeySessionM
     CK_LOGD("failed to create thread in clearkey cdm");
     mThread = nullptr;
   }
 }
 
 ClearKeySessionManager::~ClearKeySessionManager()
 {
   CK_LOGD("ClearKeySessionManager dtor %p", this);
-   assert(!mRefCount);
 }
 
 static bool
 ShouldBeAbleToDecode()
 {
 #if !defined(ENABLE_WMF)
   return false;
 #else
@@ -382,19 +381,19 @@ ClearKeySessionManager::Decrypt(GMPBuffe
   CK_LOGD("ClearKeySessionManager::Decrypt");
 
   if (!mThread) {
     CK_LOGW("No decrypt thread");
     mCallback->Decrypted(aBuffer, GMPGenericErr);
     return;
   }
 
-  mThread->Post(WrapTask(this,
-                         &ClearKeySessionManager::DoDecrypt,
-                         aBuffer, aMetadata));
+  mThread->Post(WrapTaskRefCounted(this,
+                                   &ClearKeySessionManager::DoDecrypt,
+                                   aBuffer, aMetadata));
 }
 
 void
 ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
                                   GMPEncryptedBufferMetadata* aMetadata)
 {
   CK_LOGD("ClearKeySessionManager::DoDecrypt");
 
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -19,16 +19,17 @@
 #include <stdarg.h>
 #include <stdint.h>
 #include <vector>
 
 #include "ClearKeyUtils.h"
 #include "ClearKeyBase64.h"
 #include "ArrayUtils.h"
 #include <assert.h>
+#include <memory.h>
 #include "Endian.h"
 #include "openaes/oaes_lib.h"
 
 using namespace std;
 
 #define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
 
 // System ID identifying the cenc v2 pssh box format; specified at:
@@ -534,8 +535,15 @@ ClearKeyUtils::IsValidSessionId(const ch
   }
   for (uint32_t i = 0; i < aLength; i++) {
     if (!isdigit(aBuff[i])) {
       return false;
     }
   }
   return true;
 }
+
+GMPMutex* GMPCreateMutex() {
+  GMPMutex* mutex;
+  auto err = GetPlatform()->createmutex(&mutex);
+  assert(mutex);
+  return GMP_FAILED(err) ? nullptr : mutex;
+}
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.h
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -15,16 +15,17 @@
  */
 
 #ifndef __ClearKeyUtils_h__
 #define __ClearKeyUtils_h__
 
 #include <stdint.h>
 #include <string>
 #include <vector>
+#include <assert.h>
 #include "gmp-decryption.h"
 
 #define CLEARKEY_KEY_LEN ((size_t)16)
 
 #if 0
 void CK_Log(const char* aFmt, ...);
 #define CK_LOGE(...) CK_Log(__VA_ARGS__)
 #define CK_LOGD(...) CK_Log(__VA_ARGS__)
@@ -70,9 +71,30 @@ public:
 
 template<class Container, class Element>
 inline bool
 Contains(const Container& aContainer, const Element& aElement)
 {
   return aContainer.find(aElement) != aContainer.end();
 }
 
+class AutoLock {
+public:
+  explicit AutoLock(GMPMutex* aMutex)
+    : mMutex(aMutex)
+  {
+    assert(aMutex);
+    if (mMutex) {
+      mMutex->Acquire();
+    }
+  }
+  ~AutoLock() {
+    if (mMutex) {
+      mMutex->Release();
+    }
+  }
+private:
+  GMPMutex* mMutex;
+};
+
+GMPMutex* GMPCreateMutex();
+
 #endif // __ClearKeyUtils_h__
--- a/media/gmp-clearkey/0.1/RefCounted.h
+++ b/media/gmp-clearkey/0.1/RefCounted.h
@@ -13,18 +13,56 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef __RefCount_h__
 #define __RefCount_h__
 
 #include <stdint.h>
+#include <assert.h>
+#include "ClearKeyUtils.h"
 
-// Note: Not thread safe!
+#if defined(_MSC_VER)
+#include <atomic>
+typedef std::atomic<uint32_t> AtomicRefCount;
+#else
+class AtomicRefCount {
+public:
+  explicit AtomicRefCount(uint32_t aValue)
+    : mCount(aValue)
+    , mMutex(GMPCreateMutex())
+  {
+    assert(mMutex);
+  }
+  ~AtomicRefCount()
+  {
+    if (mMutex) {
+      mMutex->Destroy();
+    }
+  }
+  uint32_t operator--() {
+    AutoLock lock(mMutex);
+    return --mCount;
+  }
+  uint32_t operator++() {
+    AutoLock lock(mMutex);
+    return ++mCount;
+  }
+  operator uint32_t() {
+    AutoLock lock(mMutex);
+    return mCount;
+  }
+private:
+  uint32_t mCount;
+  GMPMutex* mMutex;
+};
+#endif
+
+// Note: Thread safe.
 class RefCounted {
 public:
   void AddRef() {
     ++mRefCount;
   }
 
   uint32_t Release() {
     uint32_t newCount = --mRefCount;
@@ -36,18 +74,19 @@ public:
 
 protected:
   RefCounted()
     : mRefCount(0)
   {
   }
   virtual ~RefCounted()
   {
+    assert(!mRefCount);
   }
-  uint32_t mRefCount;
+  AtomicRefCount mRefCount;
 };
 
 template<class T>
 class RefPtr {
 public:
   explicit RefPtr(T* aPtr) : mPtr(nullptr) {
     Assign(aPtr);
   }
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -30,16 +30,18 @@ VideoDecoder::VideoDecoder(GMPVideoHost 
   : mHostAPI(aHostAPI)
   , mCallback(nullptr)
   , mWorkerThread(nullptr)
   , mMutex(nullptr)
   , mNumInputTasks(0)
   , mSentExtraData(false)
   , mHasShutdown(false)
 {
+  // We drop the ref in DecodingComplete().
+  AddRef();
 }
 
 VideoDecoder::~VideoDecoder()
 {
   if (mMutex) {
     mMutex->Destroy();
   }
 }
@@ -107,19 +109,19 @@ VideoDecoder::Decode(GMPVideoEncodedFram
   {
     AutoLock lock(mMutex);
     mNumInputTasks++;
   }
 
   // Note: we don't need the codec specific info on a per-frame basis.
   // It's mostly useful for WebRTC use cases.
 
-  mWorkerThread->Post(WrapTask(this,
-                               &VideoDecoder::DecodeTask,
-                               aInputFrame));
+  mWorkerThread->Post(WrapTaskRefCounted(this,
+                                         &VideoDecoder::DecodeTask,
+                                         aInputFrame));
 }
 
 class AutoReleaseVideoFrame {
 public:
   AutoReleaseVideoFrame(GMPVideoEncodedFrame* aFrame)
     : mFrame(aFrame)
   {
   }
@@ -192,22 +194,22 @@ VideoDecoder::DecodeTask(GMPVideoEncoded
   }
 
   while (hr == S_OK) {
     CComPtr<IMFSample> output;
     hr = mDecoder->Output(&output);
     CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
     if (hr == S_OK) {
       MaybeRunOnMainThread(
-        WrapTask(this,
-                 &VideoDecoder::ReturnOutput,
-                 CComPtr<IMFSample>(output),
-                 mDecoder->GetFrameWidth(),
-                 mDecoder->GetFrameHeight(),
-                 mDecoder->GetStride()));
+        WrapTaskRefCounted(this,
+                           &VideoDecoder::ReturnOutput,
+                           CComPtr<IMFSample>(output),
+                           mDecoder->GetFrameWidth(),
+                           mDecoder->GetFrameHeight(),
+                           mDecoder->GetStride()));
     }
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       AutoLock lock(mMutex);
       if (mNumInputTasks == 0) {
         // We have run all input tasks. We *must* notify Gecko so that it will
         // send us more data.
         MaybeRunOnMainThread(
           WrapTask(mCallback,
@@ -227,21 +229,30 @@ VideoDecoder::ReturnOutput(IMFSample* aS
                            int32_t aStride)
 {
   CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
   assert(aSample);
 
   HRESULT hr;
 
   GMPVideoFrame* f = nullptr;
-  mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
-  if (!f) {
+  auto err = mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
+  if (GMP_FAILED(err) || !f) {
     CK_LOGE("Failed to create i420 frame!\n");
     return;
   }
+  if (HasShutdown()) {
+    // Note: GMPVideoHost::CreateFrame() can process messages before returning,
+    // including a message that calls VideoDecoder::DecodingComplete(), i.e.
+    // we can shutdown during the call!
+    CK_LOGD("Shutdown while waiting on GMPVideoHost::CreateFrame()!\n");
+    f->Destroy();
+    return;
+  }
+
   auto vf = static_cast<GMPVideoi420Frame*>(f);
 
   hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
   ENSURE(SUCCEEDED(hr), /*void*/);
 
   mCallback->Decoded(vf);
 }
 
@@ -286,19 +297,20 @@ VideoDecoder::SampleToVideoFrame(IMFSamp
   if (aHeight % 16 != 0) {
     padding = 16 - (aHeight % 16);
   }
   int32_t y_size = stride * (aHeight + padding);
   int32_t v_size = stride * (aHeight + padding) / 4;
   int32_t halfStride = (stride + 1) / 2;
   int32_t halfHeight = (aHeight + 1) / 2;
 
-  aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
+  auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
+  ENSURE(GMP_SUCCEEDED(err), E_FAIL);
 
-  auto err = aVideoFrame->SetWidth(aWidth);
+  err = aVideoFrame->SetWidth(aWidth);
   ENSURE(GMP_SUCCEEDED(err), E_FAIL);
   err = aVideoFrame->SetHeight(aHeight);
   ENSURE(GMP_SUCCEEDED(err), E_FAIL);
 
   uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
   ENSURE(outBuffer != nullptr, E_FAIL);
   assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
   memcpy(outBuffer, data, stride*aHeight);
@@ -350,67 +362,62 @@ VideoDecoder::DrainTask()
   // Return any pending output.
   HRESULT hr = S_OK;
   while (hr == S_OK) {
     CComPtr<IMFSample> output;
     hr = mDecoder->Output(&output);
     CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
     if (hr == S_OK) {
       MaybeRunOnMainThread(
-        WrapTask(this,
-                 &VideoDecoder::ReturnOutput,
-                 CComPtr<IMFSample>(output),
-                 mDecoder->GetFrameWidth(),
-                 mDecoder->GetFrameHeight(),
-                 mDecoder->GetStride()));
+        WrapTaskRefCounted(this,
+                           &VideoDecoder::ReturnOutput,
+                           CComPtr<IMFSample>(output),
+                           mDecoder->GetFrameWidth(),
+                           mDecoder->GetFrameHeight(),
+                           mDecoder->GetStride()));
     }
   }
   MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
 }
 
 void
 VideoDecoder::Drain()
 {
   if (!mDecoder) {
     if (mCallback) {
       mCallback->DrainComplete();
     }
     return;
   }
   EnsureWorker();
-  mWorkerThread->Post(WrapTask(this,
-                               &VideoDecoder::DrainTask));
+  mWorkerThread->Post(WrapTaskRefCounted(this,
+                                         &VideoDecoder::DrainTask));
 }
 
 void
 VideoDecoder::DecodingComplete()
 {
   if (mWorkerThread) {
     mWorkerThread->Join();
   }
   mHasShutdown = true;
 
-  // Worker thread might have dispatched more tasks to the main thread that need this object.
-  // Append another task to delete |this|.
-  GetPlatform()->runonmainthread(WrapTask(this, &VideoDecoder::Destroy));
+  // Release the reference we added in the constructor. There may be
+  // WrapRefCounted tasks that also hold references to us, and keep
+  // us alive a little longer.
+  Release();
 }
 
 void
-VideoDecoder::Destroy()
-{
-  delete this;
-}
-
-void
-VideoDecoder::MaybeRunOnMainThread(gmp_task_args_base* aTask)
+VideoDecoder::MaybeRunOnMainThread(GMPTask* aTask)
 {
   class MaybeRunTask : public GMPTask
   {
   public:
-    MaybeRunTask(VideoDecoder* aDecoder, gmp_task_args_base* aTask)
+    MaybeRunTask(VideoDecoder* aDecoder, GMPTask* aTask)
       : mDecoder(aDecoder), mTask(aTask)
     { }
 
     virtual void Run(void) {
       if (mDecoder->HasShutdown()) {
         CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
         return;
       }
@@ -420,14 +427,14 @@ VideoDecoder::MaybeRunOnMainThread(gmp_t
 
     virtual void Destroy()
     {
       mTask->Destroy();
       delete this;
     }
 
   private:
-    VideoDecoder* mDecoder;
-    gmp_task_args_base* mTask;
+    RefPtr<VideoDecoder> mDecoder;
+    GMPTask* mTask;
   };
 
   GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
 }
--- a/media/gmp-clearkey/0.1/VideoDecoder.h
+++ b/media/gmp-clearkey/0.1/VideoDecoder.h
@@ -20,22 +20,21 @@
 #include "gmp-task-utils.h"
 #include "gmp-video-decode.h"
 #include "gmp-video-host.h"
 #include "WMFH264Decoder.h"
 
 #include "mfobjects.h"
 
 class VideoDecoder : public GMPVideoDecoder
+                   , public RefCounted
 {
 public:
   VideoDecoder(GMPVideoHost *aHostAPI);
 
-  virtual ~VideoDecoder();
-
   virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
                           const uint8_t* aCodecSpecific,
                           uint32_t aCodecSpecificLength,
                           GMPVideoDecoderCallback* aCallback,
                           int32_t aCoreCount) override;
 
   virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
                       bool aMissingFrames,
@@ -48,16 +47,18 @@ public:
   virtual void Drain() override;
 
   virtual void DecodingComplete() override;
 
   bool HasShutdown() { return mHasShutdown; }
 
 private:
 
+  virtual ~VideoDecoder();
+
   void EnsureWorker();
 
   void DrainTask();
 
   void DecodeTask(GMPVideoEncodedFrame* aInputFrame);
 
   void ReturnOutput(IMFSample* aSample,
                     int32_t aWidth,
@@ -65,18 +66,17 @@ private:
                     int32_t aStride);
 
   HRESULT SampleToVideoFrame(IMFSample* aSample,
                              int32_t aWidth,
                              int32_t aHeight,
                              int32_t aStride,
                              GMPVideoi420Frame* aVideoFrame);
 
-  void MaybeRunOnMainThread(gmp_task_args_base* aTask);
-  void Destroy();
+  void MaybeRunOnMainThread(GMPTask* aTask);
 
   GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
   GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
   GMPThread* mWorkerThread;
   GMPMutex* mMutex;
   wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
 
   std::vector<uint8_t> mExtraData;
--- a/media/gmp-clearkey/0.1/WMFUtils.h
+++ b/media/gmp-clearkey/0.1/WMFUtils.h
@@ -238,31 +238,16 @@ private:
 inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) {
   return (rtptime * 1000000) / 90000;
 }
 
 inline uint32_t MicrosecondsToRTPTime(Microseconds us) {
   return uint32_t(0xffffffff & (us * 90000) / 1000000);
 }
 
-class AutoLock {
-public:
-  AutoLock(GMPMutex* aMutex)
-    : mMutex(aMutex)
-  {
-    assert(aMutex);
-    mMutex->Acquire();
-  }
-  ~AutoLock() {
-    mMutex->Release();
-  }
-private:
-  GMPMutex* mMutex;
-};
-
 void dump(const uint8_t* data, uint32_t len, const char* filename);
 
 HRESULT
 CreateMFT(const CLSID& clsid,
           const char* aDllName,
           CComPtr<IMFTransform>& aOutMFT);
 
 enum CodecType {
--- a/media/gmp-clearkey/0.1/gmp-task-utils-generated.h
+++ b/media/gmp-clearkey/0.1/gmp-task-utils-generated.h
@@ -9,16 +9,17 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
+#include "RefCounted.h"
 
 // 0 arguments --
 template<typename M> class gmp_task_args_nm_0 : public gmp_task_args_base {
  public:
   explicit gmp_task_args_nm_0(M m) :
     m_(m)  {}
 
   void Run() {
@@ -1903,8 +1904,35 @@ gmp_task_args_m_14<C, M, A0, A1, A2, A3,
 
 // 14 arguments --
 template<typename C, typename M, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9, typename A10, typename A11, typename A12, typename A13, typename R>
 gmp_task_args_m_14_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>* WrapTaskRet(C o, M m, A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 a12, A13 a13, R* r) {
   return new gmp_task_args_m_14_ret<C, M, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R>
     (o, m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, r);
 }
 
+class RefCountTaskWrapper : public gmp_task_args_base {
+public:
+  RefCountTaskWrapper(GMPTask* aTask, RefCounted* aRefCounted)
+    : mTask(aTask)
+    , mRefCounted(aRefCounted)
+  {}
+  virtual void Run() override {
+    mTask->Run();
+  }
+  virtual void Destroy() override {
+    mTask->Destroy();
+    gmp_task_args_base::Destroy();
+  }
+private:
+  ~RefCountTaskWrapper() {}
+
+  GMPTask* mTask;
+  RefPtr<RefCounted> mRefCounted;
+};
+
+template<typename Type, typename Method, typename... Args>
+GMPTask*
+WrapTaskRefCounted(Type* aType, Method aMethod, Args... args)
+{
+  GMPTask* t = WrapTask(aType, aMethod, args...);
+  return new RefCountTaskWrapper(t, aType);
+}
--- a/netwerk/base/nsIUploadChannel2.idl
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -2,17 +2,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 "nsISupports.idl"
 
 interface nsIInputStream;
 
-[scriptable, uuid(AD9D3F1C-A8DE-4d0b-9714-1B922297AD65)]
+[scriptable, uuid(62e6529a-5cf6-491a-82ef-b3a8273cdd19)]
 interface nsIUploadChannel2 : nsISupports
 {
     /**
      * Sets a stream to be uploaded by this channel with the specified
      * Content-Type and Content-Length header values.
      *
      * Most implementations of this interface require that the stream:
      *   (1) implement threadsafe addRef and release
@@ -40,9 +40,15 @@ interface nsIUploadChannel2 : nsISupport
                                  in boolean aStreamHasHeaders);
 
     /**
      * Value of aStreamHasHeaders from the last successful call to
      * explicitSetUploadStream.  TRUE indicates the attached upload stream
      * contians request headers.
      */
     readonly attribute boolean uploadStreamHasHeaders;
+
+    /**
+     * Clones the upload stream and returns an equivalent stream.
+     */
+    [noscript]
+    nsIInputStream cloneUploadStream();
 };
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -32,16 +32,17 @@
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIObserverService.h"
 #include "nsProxyRelease.h"
 #include "nsPIDOMWindow.h"
 #include "nsPerformance.h"
 #include "nsINetworkInterceptController.h"
 #include "mozIThirdPartyUtil.h"
+#include "nsStreamUtils.h"
 
 #include <algorithm>
 
 namespace mozilla {
 namespace net {
 
 HttpBaseChannel::HttpBaseChannel()
   : mStartPos(UINT64_MAX)
@@ -559,16 +560,61 @@ HttpBaseChannel::SetUploadStream(nsIInpu
   // if stream is null, ExplicitSetUploadStream returns error.
   // So we need special case for GET method.
   mUploadStreamHasHeaders = false;
   mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET")); // revert to GET request
   mUploadStream = stream;
   return NS_OK;
 }
 
+static void
+EnsureStreamBuffered(nsCOMPtr<nsIInputStream>& aStream)
+{
+  if (!NS_InputStreamIsBuffered(aStream)) {
+    nsCOMPtr<nsIInputStream> bufferedStream;
+    nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+                                            aStream,
+                                            4096);
+    NS_ENSURE_SUCCESS_VOID(rv);
+    aStream.swap(bufferedStream);
+  }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(nsIInputStream** aClonedStream)
+{
+  NS_ENSURE_ARG_POINTER(aClonedStream);
+  *aClonedStream = nullptr;
+
+  if (!mUploadStream) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIInputStream> clonedStream;
+  nsCOMPtr<nsIInputStream> replacementStream;
+  nsresult rv = NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream),
+                                    getter_AddRefs(replacementStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (replacementStream) {
+    mUploadStream.swap(replacementStream);
+
+    // Ensure that the replacement stream is buffered.
+    EnsureStreamBuffered(mUploadStream);
+  }
+
+  // Ensure that the cloned stream is buffered.
+  EnsureStreamBuffered(clonedStream);
+
+  clonedStream.forget(aClonedStream);
+
+  return NS_OK;
+}
+
+
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIUploadChannel2
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
                                        const nsACString &aContentType,
                                        int64_t aContentLength,
--- a/testing/config/mozharness/android_arm_4_3_config.py
+++ b/testing/config/mozharness/android_arm_4_3_config.py
@@ -2,51 +2,48 @@
 # 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/.
 
 config = {
     "suite_definitions": {
         "mochitest": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=adb",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=adb", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=16",
             ],
         },
         "mochitest-gl": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=adb",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=adb", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=4",
                 "--subsuite=webgl",
             ],
         },
         "robocop": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=adb",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=adb", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=4",
-                "--robocop-path=../..",
+                "--robocop-apk=../../robocop.apk",
                 "--robocop-ids=fennec_ids.txt",
-                "--robocop=robocop.ini",
+                "--robocop-ini=robocop.ini",
             ],
         },
         "reftest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [ "--app=%(app)s", "--ignore-window-size",
                 "--dm_trans=adb",
                 "--bootstrap",
--- a/testing/config/mozharness/android_arm_config.py
+++ b/testing/config/mozharness/android_arm_config.py
@@ -2,54 +2,51 @@
 # 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/.
 
 config = {
     "suite_definitions": {
         "mochitest": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=sut",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=sut", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--deviceIP=%(device_ip)s", "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=16",
             ],
         },
         "mochitest-gl": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=sut",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=sut", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--deviceIP=%(device_ip)s", "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=4",
                 "--subsuite=webgl",
             ],
         },
         "robocop": {
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=sut",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=sut", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--deviceIP=%(device_ip)s", "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
                 "--total-chunks=4",
-                "--robocop-path=../..",
+                "--robocop-apk=../../robocop.apk",
                 "--robocop-ids=fennec_ids.txt",
-                "--robocop=robocop.ini",
+                "--robocop-ini=robocop.ini",
             ],
         },
         "reftest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [ "--app=%(app)s", "--ignore-window-size",
                 "--bootstrap",
                 "--remote-webserver=%(remote_webserver)s", "--xre-path=%(xre_path)s",
--- a/testing/config/mozharness/android_panda_config.py
+++ b/testing/config/mozharness/android_panda_config.py
@@ -3,17 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 config = {
     "suite_definitions": {
         "cppunittest": {
             "options": [
                 "--symbols-path=%(symbols_path)s",
                 "--xre-path=tests/bin",
-                "--dm_trans=SUT",
+                "--dm_trans=sut",
                 "--deviceIP=%(device_ip)s",
                 "--localBinDir=../tests/bin",
                 "--apk=%(apk_path)s",
                 "--skip-manifest=../tests/cppunittests/android_cppunittest_manifest.txt"
             ],
             "run_filename": "remotecppunittests.py",
             "testsdir": "cppunittests"
         },
@@ -64,22 +64,22 @@ config = {
                 "--ssl-port=%(ssl_port)s",
                 "--symbols-path=%(symbols_path)s"
             ],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "mochitest": {
             "options": [
+                "--dm_trans=sut",
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
                 "--utility-path=../hostutils/bin",
                 "--certificate-path=certs",
                 "--app=%(app_name)s",
-                "--console-level=INFO",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--symbols-path=%(symbols_path)s",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s"
             ],
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest"
@@ -97,26 +97,27 @@ config = {
                 "--symbols-path=%(symbols_path)s",
                 "reftest/tests/layout/reftests/reftest.list"
             ],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "robocop": {
             "options": [
+                "--dm_trans=sut",
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
                 "--utility-path=../hostutils/bin",
                 "--certificate-path=certs",
                 "--app=%(app_name)s",
                 "--console-level=INFO",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--symbols-path=%(symbols_path)s",
-                "--robocop=mochitest/robocop.ini"
+                "--robocop-ini=mochitest/robocop.ini"
             ],
             "run_filename": "runtestsremote.py",
             "testsdir": "mochitest"
         },
         "xpcshell": {
             "options": [
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
@@ -128,9 +129,9 @@ config = {
                 "--no-logfiles",
                 "--symbols-path=%(symbols_path)s",
                 "--log-raw=%(raw_log_file)s"
             ],
             "run_filename": "remotexpcshelltests.py",
             "testsdir": "xpcshell"
         }
     }
-}
\ No newline at end of file
+}
--- a/testing/config/mozharness/android_x86_config.py
+++ b/testing/config/mozharness/android_x86_config.py
@@ -1,18 +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/.
 
 config = {
     "suite_definitions": {
         "mochitest": {
             "run_filename": "runtestsremote.py",
-            "options": ["--autorun", "--close-when-done", "--dm_trans=sut",
-                "--console-level=INFO", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
+            "options": ["--dm_trans=sut", "--app=%(app)s", "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s", "--utility-path=%(utility_path)s",
                 "--deviceIP=%(device_ip)s", "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s", "--ssl-port=%(ssl_port)s",
                 "--certificate-path=%(certs_path)s", "--symbols-path=%(symbols_path)s",
                 "--quiet", "--log-raw=%(raw_log_file)s",
             ],
         },
         "reftest": {
--- a/testing/config/mozharness/b2g_desktop_config.py
+++ b/testing/config/mozharness/b2g_desktop_config.py
@@ -1,17 +1,16 @@
 # 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/.
 
 config = {
     "suite_definitions": {
         "mochitest": {
             "options": [
-                "--console-level=INFO",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
                 "--profile=%(gaia_profile)s",
                 "--app=%(application)s",
                 "--desktop",
                 "--utility-path=%(utility_path)s",
                 "--certificate-path=%(cert_path)s",
                 "--symbols-path=%(symbols_path)s",
--- a/testing/config/mozharness/b2g_emulator_config.py
+++ b/testing/config/mozharness/b2g_emulator_config.py
@@ -56,17 +56,16 @@ config = {
             ],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "mochitest": {
             "options": [
                 "--adbpath=%(adbpath)s",
                 "--b2gpath=%(b2gpath)s",
-                "--console-level=INFO",
                 "--emulator=%(emulator)s",
                 "--logdir=%(logcat_dir)s",
                 "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--busybox=%(busybox)s",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
@@ -77,17 +76,16 @@ config = {
             ],
             "run_filename": "runtestsb2g.py",
             "testsdir": "mochitest"
         },
         "mochitest-chrome": {
             "options": [
                 "--adbpath=%(adbpath)s",
                 "--b2gpath=%(b2gpath)s",
-                "--console-level=INFO",
                 "--emulator=%(emulator)s",
                 "--logdir=%(logcat_dir)s",
                 "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--busybox=%(busybox)s",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
--- a/testing/config/mozharness/linux_config.py
+++ b/testing/config/mozharness/linux_config.py
@@ -25,19 +25,16 @@ config = {
         },
         "mochitest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
-                "--autorun",
-                "--close-when-done",
-                "--console-level=INFO",
                 "--setpref=webgl.force-enabled=true",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--use-test-media-devices"
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
--- a/testing/config/mozharness/mac_config.py
+++ b/testing/config/mozharness/mac_config.py
@@ -25,19 +25,16 @@ config = {
         },
         "mochitest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
-                "--autorun",
-                "--close-when-done",
-                "--console-level=INFO",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s"
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
         "mozbase": {
             "options": [
--- a/testing/config/mozharness/taskcluster_linux_config.py
+++ b/testing/config/mozharness/taskcluster_linux_config.py
@@ -5,18 +5,17 @@
 config = {
     "reftest_options": [
         "--appname=%(binary_path)s", "--utility-path=tests/bin",
         "--extra-profile-file=tests/bin/plugins", "--symbols-path=%(symbols_path)s"
     ],
     "mochitest_options": [
         "--appname=%(binary_path)s", "--utility-path=tests/bin",
         "--extra-profile-file=tests/bin/plugins", "--symbols-path=%(symbols_path)s",
-        "--certificate-path=tests/certs", "--autorun", "--close-when-done",
-        "--console-level=INFO", "--setpref=webgl.force-enabled=true",
+        "--certificate-path=tests/certs", "--setpref=webgl.force-enabled=true",
         "--quiet", "--log-raw=%(raw_log_file)s"
     ],
     "webapprt_options": [
         "--app=%(app_path)s", "--utility-path=tests/bin",
         "--extra-profile-file=tests/bin/plugins", "--symbols-path=%(symbols_path)s",
         "--certificate-path=tests/certs", "--autorun", "--close-when-done",
         "--console-level=INFO", "--testing-modules-dir=tests/modules",
         "--quiet"
--- a/testing/config/mozharness/windows_config.py
+++ b/testing/config/mozharness/windows_config.py
@@ -25,19 +25,16 @@ config = {
         },
         "mochitest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
-                "--autorun",
-                "--close-when-done",
-                "--console-level=INFO",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s"
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
         "mozbase": {
             "options": [
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -1,15 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
-import argparse
+from argparse import Namespace
 import logging
 import mozpack.path as mozpath
 import os
 import sys
 import warnings
 import which
 
 from mozbuild.base import (
@@ -19,18 +19,18 @@ from mozbuild.base import (
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
+here = os.path.abspath(os.path.dirname(__file__))
 
-from mozlog import structured
 
 ADB_NOT_FOUND = '''
 The %s command requires the adb binary to be on your path.
 
 If you have a B2G build, this can be found in
 '%s/out/host/<platform>/bin'.
 '''.lstrip()
 
@@ -112,28 +112,17 @@ class MochitestRunner(MozbuildObject):
 
         self.tests_dir = os.path.join(self.topobjdir, '_tests')
         self.mochitest_dir = os.path.join(
             self.tests_dir,
             'testing',
             'mochitest')
         self.bin_dir = os.path.join(self.topobjdir, 'dist', 'bin')
 
-    def run_b2g_test(
-            self,
-            test_paths=None,
-            b2g_home=None,
-            xre_path=None,
-            total_chunks=None,
-            this_chunk=None,
-            no_window=None,
-            repeat=0,
-            run_until_failure=False,
-            chrome=False,
-            **kwargs):
+    def run_b2g_test(self, test_paths=None, **kwargs):
         """Runs a b2g mochitest.
 
         test_paths is an enumerable of paths to tests. It can be a relative path
         from the top source directory, an absolute filename, or a directory
         containing test files.
         """
         # Need to call relpath before os.chdir() below.
         test_path = ''
@@ -152,158 +141,64 @@ class MochitestRunner(MozbuildObject):
 
             import imp
             path = os.path.join(self.mochitest_dir, 'runtestsb2g.py')
             with open(path, 'r') as fh:
                 imp.load_module('mochitest', fh, path,
                                 ('.py', 'r', imp.PY_SOURCE))
 
             import mochitest
-            from mochitest_options import B2GOptions
 
-        parser = B2GOptions()
-        options = parser.parse_args([])
+        options = Namespace(**kwargs)
 
         if test_path:
-            if chrome:
+            if options.chrome:
                 test_root_file = mozpath.join(
                     self.mochitest_dir,
                     'chrome',
                     test_path)
             else:
                 test_root_file = mozpath.join(
                     self.mochitest_dir,
                     'tests',
                     test_path)
             if not os.path.exists(test_root_file):
                 print(
                     'Specified test path does not exist: %s' %
                     test_root_file)
                 return 1
             options.testPath = test_path
 
-        for k, v in kwargs.iteritems():
-            setattr(options, k, v)
-        options.noWindow = no_window
-        options.totalChunks = total_chunks
-        options.thisChunk = this_chunk
-        options.repeat = repeat
-        options.runUntilFailure = run_until_failure
-
-        options.symbolsPath = os.path.join(
-            self.distdir,
-            'crashreporter-symbols')
-
-        options.consoleLevel = 'INFO'
-        if conditions.is_b2g_desktop(self):
-            options.desktop = True
-            options.app = self.get_binary_path()
-            if not options.app.endswith('-bin'):
-                options.app = '%s-bin' % options.app
-            if not os.path.isfile(options.app):
-                options.app = options.app[:-len('-bin')]
-
-            return mochitest.run_desktop_mochitests(parser, options)
+        if options.desktop:
+            return mochitest.run_desktop_mochitests(options)
 
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
-            print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
+            print(ADB_NOT_FOUND % ('mochitest-remote', options.b2gPath))
             return 1
 
-        options.b2gPath = b2g_home
-        options.logdir = self.mochitest_dir
-        options.httpdPath = self.mochitest_dir
-        options.xrePath = xre_path
-        options.chrome = chrome
-        return mochitest.run_remote_mochitests(parser, options)
+        return mochitest.run_remote_mochitests(options)
 
-    def run_desktop_test(
-            self,
-            context,
-            suite=None,
-            test_paths=None,
-            debugger=None,
-            debugger_args=None,
-            slowscript=False,
-            screenshot_on_fail=False,
-            shuffle=False,
-            closure_behaviour='auto',
-            rerun_failures=False,
-            no_autorun=False,
-            repeat=0,
-            run_until_failure=False,
-            slow=False,
-            chunk_by_dir=0,
-            chunk_by_runtime=False,
-            total_chunks=None,
-            this_chunk=None,
-            extraPrefs=[],
-            jsdebugger=False,
-            debug_on_failure=False,
-            start_at=None,
-            end_at=None,
-            e10s=False,
-            enable_cpow_warnings=False,
-            strict_content_sandbox=False,
-            nested_oop=False,
-            dmd=False,
-            dump_output_directory=None,
-            dump_about_memory_after_test=False,
-            dump_dmd_after_test=False,
-            install_extension=None,
-            quiet=False,
-            environment=[],
-            app_override=None,
-            bisectChunk=None,
-            runByDir=False,
-            useTestMediaDevices=False,
-            timeout=None,
-            max_timeouts=None,
-            **kwargs):
+    def run_desktop_test(self, context, suite=None, test_paths=None, **kwargs):
         """Runs a mochitest.
 
+        suite is the type of mochitest to run. It can be one of ('plain',
+        'chrome', 'browser', 'metro', 'a11y', 'jetpack-package', 'jetpack-addon').
+
         test_paths are path to tests. They can be a relative path from the
         top source directory, an absolute filename, or a directory containing
         test files.
-
-        suite is the type of mochitest to run. It can be one of ('plain',
-        'chrome', 'browser', 'metro', 'a11y', 'jetpack-package', 'jetpack-addon').
-
-        debugger is a program name or path to a binary (presumably a debugger)
-        to run the test in. e.g. 'gdb'
-
-        debugger_args are the arguments passed to the debugger.
-
-        slowscript is true if the user has requested the SIGSEGV mechanism of
-        invoking the slow script dialog.
-
-        shuffle is whether test order should be shuffled (defaults to false).
-
-        closure_behaviour denotes whether to keep the browser open after tests
-        complete.
         """
-        if rerun_failures and test_paths:
-            print('Cannot specify both --rerun-failures and a test path.')
-            return 1
-
         # Make absolute paths relative before calling os.chdir() below.
         if test_paths:
             test_paths = [self._wrap_path_argument(
                 p).relpath() if os.path.isabs(p) else p for p in test_paths]
 
-        failure_file_path = os.path.join(
-            self.statedir,
-            'mochitest_failures.json')
-
-        if rerun_failures and not os.path.exists(failure_file_path):
-            print('No failure file present. Did you run mochitests before?')
-            return 1
-
         # runtests.py is ambiguous, so we load the file/module manually.
         if 'mochitest' not in sys.modules:
             import imp
             path = os.path.join(self.mochitest_dir, 'runtests.py')
             with open(path, 'r') as fh:
                 imp.load_module('mochitest', fh, path,
                                 ('.py', 'r', imp.PY_SOURCE))
 
@@ -316,108 +211,52 @@ class MochitestRunner(MozbuildObject):
 
         # Automation installs its own stream handler to stdout. Since we want
         # all logging to go through us, we just remove their handler.
         remove_handlers = [l for l in logging.getLogger().handlers
                            if isinstance(l, logging.StreamHandler)]
         for handler in remove_handlers:
             logging.getLogger().removeHandler(handler)
 
-        opts = mochitest.MochitestOptions()
-        options = opts.parse_args([])
+        options = Namespace(**kwargs)
 
-        options.subsuite = ''
         flavor = suite
 
-        # Need to set the suite options before verifyOptions below.
         if suite == 'plain':
             # Don't need additional options for plain.
             flavor = 'mochitest'
         elif suite == 'chrome':
             options.chrome = True
         elif suite == 'browser':
             options.browserChrome = True
             flavor = 'browser-chrome'
         elif suite == 'devtools':
             options.browserChrome = True
+            options.subsuite = 'devtools'
         elif suite == 'jetpack-package':
             options.jetpackPackage = True
         elif suite == 'jetpack-addon':
             options.jetpackAddon = True
         elif suite == 'metro':
             options.immersiveMode = True
             options.browserChrome = True
         elif suite == 'a11y':
             options.a11y = True
         elif suite == 'webapprt-content':
             options.webapprtContent = True
-            options.app = self.get_webapp_runtime_path()
+            if not options.app or options.app == self.get_binary_path():
+                options.app = self.get_webapp_runtime_path()
         elif suite == 'webapprt-chrome':
             options.webapprtChrome = True
-            options.app = self.get_webapp_runtime_path()
             options.browserArgs.append("-test-mode")
+            if not options.app or options.app == self.get_binary_path():
+                options.app = self.get_webapp_runtime_path()
         else:
             raise Exception('None or unrecognized mochitest suite type.')
 
-        if dmd:
-            options.dmdPath = self.bin_dir
-
-        options.autorun = not no_autorun
-        options.closeWhenDone = closure_behaviour != 'open'
-        options.slowscript = slowscript
-        options.screenshotOnFail = screenshot_on_fail
-        options.shuffle = shuffle
-        options.consoleLevel = 'INFO'
-        options.repeat = repeat
-        options.runUntilFailure = run_until_failure
-        options.runSlower = slow
-        options.testingModulesDir = os.path.join(self.tests_dir, 'modules')
-        options.extraProfileFiles.append(os.path.join(self.distdir, 'plugins'))
-        options.symbolsPath = os.path.join(
-            self.distdir,
-            'crashreporter-symbols')
-        options.chunkByDir = chunk_by_dir
-        options.chunkByRuntime = chunk_by_runtime
-        options.totalChunks = total_chunks
-        options.thisChunk = this_chunk
-        options.jsdebugger = jsdebugger
-        options.debugOnFailure = debug_on_failure
-        options.startAt = start_at
-        options.endAt = end_at
-        options.e10s = e10s
-        options.enableCPOWWarnings = enable_cpow_warnings
-        options.strictContentSandbox = strict_content_sandbox
-        options.nested_oop = nested_oop
-        options.dumpAboutMemoryAfterTest = dump_about_memory_after_test
-        options.dumpDMDAfterTest = dump_dmd_after_test
-        options.dumpOutputDirectory = dump_output_directory
-        options.quiet = quiet
-        options.environment = environment
-        options.extraPrefs = extraPrefs
-        options.bisectChunk = bisectChunk
-        options.runByDir = runByDir
-        options.useTestMediaDevices = useTestMediaDevices
-        if timeout:
-            options.timeout = int(timeout)
-        if max_timeouts:
-            options.maxTimeouts = int(max_timeouts)
-
-        options.failureFile = failure_file_path
-        if install_extension is not None:
-            options.extensionsToInstall = [
-                os.path.join(
-                    self.topsrcdir,
-                    install_extension)]
-
-        for k, v in kwargs.iteritems():
-            setattr(options, k, v)
-
-        if suite == 'devtools':
-            options.subsuite = 'devtools'
-
         if test_paths:
             resolver = self._spawn(TestResolver)
 
             tests = list(
                 resolver.resolve_tests(
                     paths=test_paths,
                     flavor=flavor))
 
@@ -425,519 +264,230 @@ class MochitestRunner(MozbuildObject):
                 print('No tests could be found in the path specified. Please '
                       'specify a path that is a test file or is a directory '
                       'containing tests.')
                 return 1
 
             manifest = TestManifest()
             manifest.tests.extend(tests)
 
-            if len(
-                    tests) == 1 and closure_behaviour == 'auto' and suite == 'plain':
+            # XXX why is this such a special case?
+            if len(tests) == 1 and options.closeWhenDone and suite == 'plain':
                 options.closeWhenDone = False
 
             options.manifestFile = manifest
 
-        if rerun_failures:
-            options.testManifest = failure_file_path
-
-        if debugger:
-            options.debugger = debugger
-
-        if debugger_args:
-            if options.debugger is None:
-                print("--debugger-args passed, but no debugger specified.")
-                return 1
-            options.debuggerArgs = debugger_args
-
-        if app_override:
-            if app_override == "dist":
-                options.app = self.get_binary_path(where='staged-package')
-            elif app_override:
-                options.app = app_override
-            if options.gmp_path is None:
-                # Need to fix the location of gmp_fake which might not be
-                # shipped in the binary
-                bin_path = self.get_binary_path()
-                gmp_modules = (
-                    ('gmp-fake', '1.0'),
-                    ('gmp-clearkey', '0.1'),
-                    ('gmp-fakeopenh264', '1.0')
-                )
-                options.gmp_path = os.pathsep.join(
-                    os.path.join(os.path.dirname(bin_path), *p)
-                    for p in gmp_modules)
-
-        logger_options = {
-            key: value for key,
-            value in vars(options).iteritems() if key.startswith('log')}
-        runner = mochitest.Mochitest(logger_options)
-        options = opts.verifyOptions(options, runner)
-
-        if options is None:
-            raise Exception('mochitest option validator failed.')
-
         # We need this to enable colorization of output.
         self.log_manager.enable_unstructured()
-
-        result = runner.runTests(options)
-
+        result = mochitest.run_test_harness(options)
         self.log_manager.disable_unstructured()
-        if runner.message_logger.errors:
-            result = 1
-            runner.message_logger.logger.warning("The following tests failed:")
-            for error in runner.message_logger.errors:
-                runner.message_logger.logger.log_raw(error)
-
-        runner.message_logger.finish()
-
         return result
 
-    def run_android_test(self, args):
+    def run_android_test(self, test_path, **kwargs):
         self.tests_dir = os.path.join(self.topobjdir, '_tests')
         self.mochitest_dir = os.path.join(self.tests_dir, 'testing', 'mochitest')
         import imp
         path = os.path.join(self.mochitest_dir, 'runtestsremote.py')
         with open(path, 'r') as fh:
             imp.load_module('runtestsremote', fh, path,
-                ('.py', 'r', imp.PY_SOURCE))
+                            ('.py', 'r', imp.PY_SOURCE))
         import runtestsremote
 
-        sys.exit(runtestsremote.main(args))
-
-def add_mochitest_general_args(parser):
-    parser.add_argument(
-        '--debugger',
-        '-d',
-        metavar='DEBUGGER',
-        help='Debugger binary to run test in. Program name or path.')
-
-    parser.add_argument(
-        '--debugger-args',
-        metavar='DEBUGGER_ARGS',
-        help='Arguments to pass to the debugger.')
-
-    # Bug 933807 introduced JS_DISABLE_SLOW_SCRIPT_SIGNALS to avoid clever
-    # segfaults induced by the slow-script-detecting logic for Ion/Odin JITted
-    # code. If we don't pass this, the user will need to periodically type
-    # "continue" to (safely) resume execution. There are ways to implement
-    # automatic resuming; see the bug.
-    parser.add_argument(
-        '--slowscript',
-        action='store_true',
-        help='Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; when not set, recoverable but misleading SIGSEGV instances may occur in Ion/Odin JIT code')
-
-    parser.add_argument(
-        '--screenshot-on-fail',
-        action='store_true',
-        help='Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory for storing the screenshots.')
-
-    parser.add_argument(
-        '--shuffle', action='store_true',
-        help='Shuffle execution order.')
-
-    parser.add_argument(
-        '--keep-open',
-        action='store_const',
-        dest='closure_behaviour',
-        const='open',
-        default='auto',
-        help='Always keep the browser open after tests complete.')
-
-    parser.add_argument(
-        '--auto-close',
-        action='store_const',
-        dest='closure_behaviour',
-        const='close',
-        default='auto',
-        help='Always close the browser after tests complete.')
-
-    parser.add_argument(
-        '--rerun-failures',
-        action='store_true',
-        help='Run only the tests that failed during the last test run.')
-
-    parser.add_argument(
-        '--no-autorun',
-        action='store_true',
-        help='Do not starting running tests automatically.')
-
-    parser.add_argument(
-        '--repeat', type=int, default=0,
-        help='Repeat the test the given number of times.')
-
-    parser.add_argument(
-        "--run-until-failure",
-        action='store_true',
-        help='Run tests repeatedly and stops on the first time a test fails. '
-        'Default cap is 30 runs, which can be overwritten '
-        'with the --repeat parameter.')
-
-    parser.add_argument(
-        '--slow', action='store_true',
-        help='Delay execution between tests.')
-
-    parser.add_argument(
-        '--end-at',
-        type=str,
-        help='Stop running the test sequence at this test.')
-
-    parser.add_argument(
-        '--start-at',
-        type=str,
-        help='Start running the test sequence at this test.')
-
-    parser.add_argument(
-        '--chunk-by-dir',
-        type=int,
-        help='Group tests together in chunks by this many top directories.')
-
-    parser.add_argument(
-        '--chunk-by-runtime',
-        action='store_true',
-        help="Group tests such that each chunk has roughly the same runtime.")
-
-    parser.add_argument(
-        '--total-chunks',
-        type=int,
-        help='Total number of chunks to split tests into.')
-
-    parser.add_argument(
-        '--this-chunk',
-        type=int,
-        help='If running tests by chunks, the number of the chunk to run.')
-
-    parser.add_argument(
-        '--debug-on-failure',
-        action='store_true',
-        help='Breaks execution and enters the JS debugger on a test failure. '
-        'Should be used together with --jsdebugger.')
-
-    parser.add_argument(
-        '--setpref', default=[], action='append',
-        metavar='PREF=VALUE', dest='extraPrefs',
-        help='defines an extra user preference')
+        options = Namespace(**kwargs)
+        if test_path:
+            options.testPath = test_path
 
-    parser.add_argument(
-        '--jsdebugger',
-        action='store_true',
-        help='Start the browser JS debugger before running the test. Implies --no-autorun.')
-
-    parser.add_argument(
-        '--e10s',
-        action='store_true',
-        help='Run tests with electrolysis preferences and test filtering enabled.')
-
-    parser.add_argument(
-        '--enable-cpow-warnings',
-        action='store_true',
-        help='Run tests with unsafe CPOW usage warnings enabled.')
-
-    parser.add_argument(
-        '--strict-content-sandbox',
-        action='store_true',
-        help='Run tests with a more strict content sandbox (Windows only).')
-
-    parser.add_argument(
-        '--nested_oop',
-        action='store_true',
-        help='Run tests with nested oop preferences and test filtering enabled.')
-
-    parser.add_argument(
-        '--dmd', action='store_true',
-        help='Run tests with DMD active.')
-
-    parser.add_argument(
-        '--dump-about-memory-after-test',
-        action='store_true',
-        help='Dump an about:memory log after every test.')
-
-    parser.add_argument(
-        '--dump-dmd-after-test', action='store_true',
-        help='Dump a DMD log after every test.')
-
-    parser.add_argument(
-        '--dump-output-directory',
-        action='store',
-        help='Specifies the directory in which to place dumped memory reports.')
-
-    parser.add_argument(
-        'test_paths',
-        default=None,
-        nargs='*',
-        metavar='TEST',
-        help='Test to run. Can be specified as a single file, a '
-        'directory, or omitted. If omitted, the entire test suite is '
-        'executed.')
-
-    parser.add_argument(
-        '--install-extension',
-        help='Install given extension before running selected tests. '
-        'Parameter is a path to xpi file.')
-
-    parser.add_argument(
-        '--quiet',
-        default=False,
-        action='store_true',
-        help='Do not print test log lines unless a failure occurs.')
-
-    parser.add_argument(
-        '--setenv',
-        default=[],
-        action='append',
-        metavar='NAME=VALUE',
-        dest='environment',
-        help="Sets the given variable in the application's environment")
-
-    parser.add_argument(
-        '--run-by-dir',
-        default=False,
-        action='store_true',
-        dest='runByDir',
-        help='Run each directory in a single browser instance with a fresh profile.')
-
-    parser.add_argument(
-        '--bisect-chunk',
-        type=str,
-        dest='bisectChunk',
-        help='Specify the failing test name to find the previous tests that may be causing the failure.')
-
-    parser.add_argument(
-        '--use-test-media-devices',
-        default=False,
-        action='store_true',
-        dest='useTestMediaDevices',
-        help='Use test media device drivers for media testing.')
-
-    parser.add_argument(
-        '--app-override',
-        default=None,
-        action='store',
-        help="Override the default binary used to run tests with the path you provide, e.g. "
-        " --app-override /usr/bin/firefox . "
-        "If you have run ./mach package beforehand, you can specify 'dist' to "
-        "run tests against the distribution bundle's binary.")
-
-    parser.add_argument(
-        '--timeout',
-        default=None,
-        help='The per-test timeout time in seconds (default: 60 seconds)')
-
-    parser.add_argument(
-        '--max-timeouts', default=None,
-        help='The maximum number of timeouts permitted before halting testing')
-
-    parser.add_argument(
-        "--tag",
-        dest='test_tags', action='append',
-        help="Filter out tests that don't have the given tag. Can be used "
-             "multiple times in which case the test must contain at least one "
-             "of the given tags.")
-
-    parser.add_argument(
-        "--subsuite",
-        default=None,
-        help="Subsuite of tests to run. Unlike tags, subsuites also remove "
-             "tests from the default set. Only one can be specified at once.")
+        sys.exit(runtestsremote.run_test_harness(options))
 
 
-    return parser
-
-def add_mochitest_b2g_args(parser):
-    parser.add_argument(
-        '--busybox',
-        default=None,
-        help='Path to busybox binary to install on device')
-
-    parser.add_argument(
-        '--logdir', default=None,
-        help='directory to store log files')
-
-    parser.add_argument(
-        '--profile', default=None,
-        help='for desktop testing, the path to the \
-              gaia profile to use')
+# parser
 
-    parser.add_argument(
-        '--gecko-path', default=None,
-        help='the path to a gecko distribution that should \
-              be installed on the emulator prior to test')
-
-    parser.add_argument(
-        '--no-window',
-        action='store_true',
-        default=False,
-        help='Pass --no-window to the emulator')
-
-    parser.add_argument(
-        '--sdcard', default="10MB",
-        help='Define size of sdcard: 1MB, 50MB...etc')
-
-    parser.add_argument(
-        '--marionette',
-        default=None,
-        help='host:port to use when connecting to Marionette')
-
-    return parser
+def TestPathArg(func):
+    test_paths = CommandArgument('test_paths', nargs='*', metavar='TEST', default=None,
+        help='Test to run. Can be a single test file or a directory of tests to '
+             '(run recursively). If omitted, the entire suite is run.')
+    return test_paths(func)
 
 
 def setup_argument_parser():
-    parser = argparse.ArgumentParser()
+    build_obj = MozbuildObject.from_environment(cwd=here)
 
-    general_args = parser.add_argument_group('Mochitest Arguments',
-        'Arguments that apply to all versions of mochitest.')
-    general_args = add_mochitest_general_args(general_args)
+    build_path = os.path.join(build_obj.topobjdir, 'build')
+    if build_path not in sys.path:
+        sys.path.append(build_path)
+
+    mochitest_dir = os.path.join(build_obj.topobjdir, '_tests', 'testing', 'mochitest')
 
-    b2g_args = parser.add_argument_group('B2G Arguments', 'Arguments specific \
-        to running mochitest on B2G devices and emulator')
-    b2g_args = add_mochitest_b2g_args(b2g_args)
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore')
 
-    structured.commandline.add_logging_group(parser)
-    return parser
+        import imp
+        path = os.path.join(build_obj.topobjdir, mochitest_dir, 'runtests.py')
+        with open(path, 'r') as fh:
+            imp.load_module('mochitest', fh, path,
+                            ('.py', 'r', imp.PY_SOURCE))
+
+        from mochitest_options import MochitestArgumentParser
+
+    return MochitestArgumentParser()
 
 
 # condition filters
 
 def is_platform_in(*platforms):
     def is_platform_supported(cls):
         for p in platforms:
             c = getattr(conditions, 'is_{}'.format(p), None)
             if c and c(cls):
                 return True
         return False
 
     is_platform_supported.__doc__ = 'Must have a {} build.'.format(
         ' or '.join(platforms))
     return is_platform_supported
 
+
 def verify_host_bin():
     # validate MOZ_HOST_BIN environment variables for Android tests
     MOZ_HOST_BIN = os.environ.get('MOZ_HOST_BIN')
     if not MOZ_HOST_BIN:
         print('environment variable MOZ_HOST_BIN must be set to a directory containing host xpcshell')
         return 1
     elif not os.path.isdir(MOZ_HOST_BIN):
         print('$MOZ_HOST_BIN does not specify a directory')
         return 1
     elif not os.path.isfile(os.path.join(MOZ_HOST_BIN, 'xpcshell')):
         print('$MOZ_HOST_BIN/xpcshell does not exist')
         return 1
     return 0
 
+
 @CommandProvider
 class MachCommands(MachCommandBase):
 
     def __init__(self, context):
         MachCommandBase.__init__(self, context)
 
-        for attr in ('b2g_home', 'xre_path', 'device_name', 'target_out'):
+        for attr in ('device_name', 'target_out'):
             setattr(self, attr, getattr(context, attr, None))
 
     @Command(
         'mochitest-plain',
         category='testing',
         conditions=[is_platform_in('firefox', 'mulet', 'b2g', 'b2g_desktop', 'android')],
         description='Run a plain mochitest (integration test, plain web page).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_plain(self, test_paths, **kwargs):
         if is_platform_in('firefox', 'mulet')(self):
             return self.run_mochitest(test_paths, 'plain', **kwargs)
         elif conditions.is_emulator(self):
             return self.run_mochitest_remote(test_paths, **kwargs)
         elif conditions.is_b2g_desktop(self):
             return self.run_mochitest_b2g_desktop(test_paths, **kwargs)
         elif conditions.is_android(self):
             return self.run_mochitest_android(test_paths, **kwargs)
 
     @Command(
         'mochitest-chrome',
         category='testing',
         conditions=[is_platform_in('firefox', 'emulator', 'android')],
         description='Run a chrome mochitest (integration test with some XUL).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_chrome(self, test_paths, **kwargs):
+        kwargs['chrome'] = True
         if conditions.is_firefox(self):
             return self.run_mochitest(test_paths, 'chrome', **kwargs)
         elif conditions.is_b2g(self) and conditions.is_emulator(self):
-            return self.run_mochitest_remote(test_paths, chrome=True, **kwargs)
+            return self.run_mochitest_remote(test_paths, **kwargs)
         elif conditions.is_android(self):
-            return self.run_mochitest_android(test_paths, chrome=True, **kwargs)
+            return self.run_mochitest_android(test_paths, **kwargs)
 
     @Command(
         'mochitest-browser',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a mochitest with browser chrome (integration test with a standard browser).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_browser(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'browser', **kwargs)
 
     @Command(
         'mochitest-devtools',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a devtools mochitest with browser chrome (integration test with a standard browser with the devtools frame).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_devtools(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'devtools', **kwargs)
 
     @Command('jetpack-package', category='testing',
              conditions=[conditions.is_firefox],
              description='Run a jetpack package test.',
              parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_jetpack_package(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'jetpack-package', **kwargs)
 
     @Command('jetpack-addon', category='testing',
              conditions=[conditions.is_firefox],
              description='Run a jetpack addon test.',
              parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_jetpack_addon(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'jetpack-addon', **kwargs)
 
     @Command(
         'mochitest-metro',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a mochitest with metro browser chrome (tests for Windows touch interface).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_metro(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'metro', **kwargs)
 
     @Command('mochitest-a11y', category='testing',
              conditions=[conditions.is_firefox],
              description='Run an a11y mochitest (accessibility tests).',
              parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_a11y(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'a11y', **kwargs)
 
     @Command(
         'webapprt-test-chrome',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a webapprt chrome mochitest (Web App Runtime with the browser chrome).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_webapprt_chrome(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'webapprt-chrome', **kwargs)
 
     @Command(
         'webapprt-test-content',
         category='testing',
         conditions=[conditions.is_firefox],
         description='Run a webapprt content mochitest (Content rendering of the Web App Runtime).',
         parser=setup_argument_parser)
+    @TestPathArg
     def run_mochitest_webapprt_content(self, test_paths, **kwargs):
         return self.run_mochitest(test_paths, 'webapprt-content', **kwargs)
 
     @Command('mochitest', category='testing',
              conditions=[conditions.is_firefox],
              description='Run any flavor of mochitest (integration test).',
              parser=setup_argument_parser)
     @CommandArgument('-f', '--flavor', choices=FLAVORS.keys(),
                      help='Only run tests of this flavor.')
+    @TestPathArg
     def run_mochitest_general(self, test_paths, flavor=None, test_objects=None,
                               **kwargs):
         self._preruntest()
 
         from mozbuild.testing import TestResolver
 
         if test_objects:
             tests = test_objects
@@ -1014,31 +564,23 @@ class MachCommands(MachCommandBase):
                     'test-container.gaiamobile.org')):
                 print(
                     ENG_BUILD_REQUIRED %
                     ('mochitest-remote', host_webapps_dir))
                 return 1
 
         from mozbuild.controller.building import BuildDriver
 
-        if self.device_name.startswith('emulator'):
-            emulator = 'arm'
-            if 'x86' in self.device_name:
-                emulator = 'x86'
-            kwargs['emulator'] = emulator
-
         self._ensure_state_subdir_exists('.')
 
         driver = self._spawn(BuildDriver)
         driver.install_tests(remove=False)
 
         mochitest = self._spawn(MochitestRunner)
         return mochitest.run_b2g_test(
-            b2g_home=self.b2g_home,
-            xre_path=self.xre_path,
             test_paths=test_paths,
             **kwargs)
 
     def run_mochitest_b2g_desktop(self, test_paths, **kwargs):
         kwargs['profile'] = kwargs.get(
             'profile') or os.environ.get('GAIA_PROFILE')
         if not kwargs['profile'] or not os.path.isdir(kwargs['profile']):
             print(GAIA_PROFILE_NOT_FOUND % 'mochitest-b2g-desktop')
@@ -1055,82 +597,51 @@ class MachCommands(MachCommandBase):
         self._ensure_state_subdir_exists('.')
 
         driver = self._spawn(BuildDriver)
         driver.install_tests(remove=False)
 
         mochitest = self._spawn(MochitestRunner)
         return mochitest.run_b2g_test(test_paths=test_paths, **kwargs)
 
-    def run_mochitest_android(self, test_paths, chrome=False, **kwargs):
+    def run_mochitest_android(self, test_paths, **kwargs):
         host_ret = verify_host_bin()
         if host_ret != 0:
             return host_ret
 
-        args = [
-            '--xre-path=' + os.environ.get('MOZ_HOST_BIN'),
-            '--dm_trans=adb',
-            '--deviceIP=',
-            '--console-level=INFO',
-            '--app=' + self.substs['ANDROID_PACKAGE_NAME'],
-            '--log-mach=-',
-            '--autorun',
-            '--close-when-done',
-            '--testing-modules-dir=' + os.path.join(self.topobjdir, '_tests', 'modules'),
-        ]
+        test_path = None
         if test_paths:
             if len(test_paths) > 1:
                 print('Warning: Only the first test path will be used.')
             test_path = self._wrap_path_argument(test_paths[0]).relpath()
-            args.append('--test-path=%s' % test_path)
-        if chrome:
-            args.append('--chrome')
 
         mochitest = self._spawn(MochitestRunner)
-        return mochitest.run_android_test(args)
+        return mochitest.run_android_test(test_path, **kwargs)
+
 
 @CommandProvider
 class AndroidCommands(MachCommandBase):
 
     @Command('robocop', category='testing',
              conditions=[conditions.is_android],
-             description='Run a Robocop test.')
+             description='Run a Robocop test.',
+             parser=setup_argument_parser)
     @CommandArgument(
         'test_path',
         default=None,
         nargs='?',
         metavar='TEST',
         help='Test to run. Can be specified as a Robocop test name (like "testLoad"), '
         'or omitted. If omitted, the entire test suite is executed.')
-    def run_robocop(self, test_path):
+    def run_robocop(self, test_path, **kwargs):
         host_ret = verify_host_bin()
         if host_ret != 0:
             return host_ret
 
-        args = [
-            '--xre-path=' + os.environ.get('MOZ_HOST_BIN'),
-            '--dm_trans=adb',
-            '--deviceIP=',
-            '--console-level=INFO',
-            '--app=' +
-            self.substs['ANDROID_PACKAGE_NAME'],
-            '--robocop-apk=' +
-            os.path.join(
-                self.topobjdir,
-                'build',
-                'mobile',
-                'robocop',
-                'robocop-debug.apk'),
-            '--robocop-ini=' +
-            os.path.join(
-                self.topobjdir,
-                '_tests',
-                'testing',
-                'mochitest',
-                'robocop.ini'),
-            '--log-mach=-',
-        ]
+        if not kwargs.get('robocopIni'):
+            kwargs['robocopIni'] = os.path.join(self.topobjdir, '_tests', 'testing',
+                                                'mochitest', 'robocop.ini')
 
-        if test_path:
-            args.append('--test-path=%s' % test_path)
-
+        if not kwargs.get('robocopApk'):
+            kwargs['robocopApk'] = os.path.join(self.topobjdir, 'build', 'mobile',
+                                                'robocop', 'robocop-debug.apk')
         mochitest = self._spawn(MochitestRunner)
-        return mochitest.run_android_test(args)
+        return mochitest.run_android_test(test_path, **kwargs)
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -1,845 +1,945 @@
 # 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 argparse import ArgumentParser
+from abc import ABCMeta, abstractmethod, abstractproperty
+from argparse import ArgumentParser, SUPPRESS
 from urlparse import urlparse
-import logging
 import os
 import tempfile
 
+from droid import DroidADB, DroidSUT
+from mozlog import structured
 from mozprofile import DEFAULT_PORTS
 import mozinfo
 import moznetwork
 
-from automation import Automation
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 try:
-    from mozbuild.base import MozbuildObject
+    from mozbuild.base import (
+        MozbuildObject,
+        MachCommandConditions as conditions,
+    )
     build_obj = MozbuildObject.from_environment(cwd=here)
 except ImportError:
     build_obj = None
+    conditions = None
 
-__all__ = ["MochitestOptions", "B2GOptions"]
 
 VMWARE_RECORDING_HELPER_BASENAME = "vmwarerecordinghelper"
 
 
-class MochitestOptions(ArgumentParser):
-    """Usage instructions for runtests.py.
-    All arguments are optional.
-    If --chrome is specified, chrome tests will be run instead of web content tests.
-    If --browser-chrome is specified, browser-chrome tests will be run instead of web content tests.
-    See <http://mochikit.com/doc/html/MochiKit/Logging.html> for details on the logging levels.
-    """
+class ArgumentContainer():
+    __metaclass__ = ABCMeta
+
+    @abstractproperty
+    def args(self):
+        pass
+
+    @abstractproperty
+    def defaults(self):
+        pass
+
+    @abstractmethod
+    def validate(self, parser, args, context):
+        pass
+
+    def get_full_path(self, path, cwd):
+        """Get an absolute path relative to cwd."""
+        return os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+
+
+class MochitestArguments(ArgumentContainer):
+    """General mochitest arguments."""
 
     LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "FATAL")
     LEVEL_STRING = ", ".join(LOG_LEVELS)
-    mochitest_options = [
-        [["--close-when-done"],
-         {"action": "store_true",
+
+    args = [
+        [["--keep-open"],
+         {"action": "store_false",
           "dest": "closeWhenDone",
-          "default": False,
-          "help": "close the application when tests are done running",
+          "default": True,
+          "help": "Always keep the browser open after tests complete.",
           }],
         [["--appname"],
          {"dest": "app",
           "default": None,
-          "help": "absolute path to application, overriding default",
+          "help": "Override the default binary used to run tests with the path provided, e.g "
+                  "/usr/bin/firefox. If you have run ./mach package beforehand, you can "
+                  "specify 'dist' to run tests against the distribution bundle's binary.",
+          "suppress": True,
           }],
         [["--utility-path"],
          {"dest": "utilityPath",
           "default": build_obj.bindir if build_obj is not None else None,
-          "help": "absolute path to directory containing utility programs (xpcshell, ssltunnel, certutil)",
+          "help": "absolute path to directory containing utility programs "
+                  "(xpcshell, ssltunnel, certutil)",
+          "suppress": True,
           }],
         [["--certificate-path"],
          {"dest": "certPath",
+          "default": None,
           "help": "absolute path to directory containing certificate store to use testing profile",
-          "default": os.path.join(build_obj.topsrcdir, 'build', 'pgo', 'certs') if build_obj is not None else None,
+          "suppress": True,
           }],
-        [["--autorun"],
-         {"action": "store_true",
+        [["--no-autorun"],
+         {"action": "store_false",
           "dest": "autorun",
-          "help": "start running tests when the application starts",
-          "default": False,
+          "default": True,
+          "help": "Do not start running tests automatically.",
           }],
         [["--timeout"],
          {"type": int,
-          "dest": "timeout",
-          "help": "per-test timeout in seconds",
           "default": None,
+          "help": "The per-test timeout in seconds (default: 60 seconds).",
+          }],
+        [["--max-timeouts"],
+         {"type": int,
+          "dest": "maxTimeouts",
+          "default": None,
+          "help": "The maximum number of timeouts permitted before halting testing.",
           }],
         [["--total-chunks"],
          {"type": int,
           "dest": "totalChunks",
-          "help": "how many chunks to split the tests up into",
+          "help": "Total number of chunks to split tests into.",
           "default": None,
           }],
         [["--this-chunk"],
          {"type": int,
           "dest": "thisChunk",
-          "help": "which chunk to run",
+          "help": "If running tests by chunks, the chunk number to run.",
           "default": None,
           }],
         [["--chunk-by-runtime"],
          {"action": "store_true",
           "dest": "chunkByRuntime",
-          "help": "group tests such that each chunk has roughly the same runtime",
+          "help": "Group tests such that each chunk has roughly the same runtime.",
           "default": False,
           }],
         [["--chunk-by-dir"],
          {"type": int,
           "dest": "chunkByDir",
-          "help": "group tests together in the same chunk that are in the same top chunkByDir directories",
+          "help": "Group tests together in the same chunk that are in the same top "
+                  "chunkByDir directories.",
           "default": 0,
           }],
         [["--run-by-dir"],
          {"action": "store_true",
           "dest": "runByDir",
-          "help": "Run each directory in a single browser instance with a fresh profile",
+          "help": "Run each directory in a single browser instance with a fresh profile.",
           "default": False,
           }],
         [["--shuffle"],
-         {"dest": "shuffle",
-          "action": "store_true",
-          "help": "randomize test order",
+         {"action": "store_true",
+          "help": "Shuffle execution order of tests.",
           "default": False,
           }],
         [["--console-level"],
          {"dest": "consoleLevel",
           "choices": LOG_LEVELS,
-          "metavar": "LEVEL",
-          "help": "one of %s to determine the level of console "
-                  "logging" % LEVEL_STRING,
-          "default": None,
+          "default": "INFO",
+          "help": "One of %s to determine the level of console logging." % LEVEL_STRING,
+          "suppress": True,
           }],
         [["--chrome"],
          {"action": "store_true",
-          "dest": "chrome",
-          "help": "run chrome Mochitests",
           "default": False,
+          "help": "Run chrome mochitests.",
+          "suppress": True,
           }],
         [["--ipcplugins"],
          {"action": "store_true",
           "dest": "ipcplugins",
-          "help": "run ipcplugins Mochitests",
+          "help": "Run ipcplugins mochitests.",
           "default": False,
+          "suppress": True,
           }],
         [["--test-path"],
          {"dest": "testPath",
-          "help": "start in the given directory's tests",
           "default": "",
+          "help": "Run the given test or recursively run the given directory of tests.",
+          # if running from mach, a test_paths arg is exposed instead
+          "suppress": build_obj is not None,
           }],
         [["--bisect-chunk"],
          {"dest": "bisectChunk",
-          "help": "Specify the failing test name to find the previous tests that may be causing the failure.",
           "default": None,
+          "help": "Specify the failing test name to find the previous tests that may be "
+                  "causing the failure.",
           }],
         [["--start-at"],
          {"dest": "startAt",
-          "help": "skip over tests until reaching the given test",
           "default": "",
+          "help": "Start running the test sequence at this test.",
           }],
         [["--end-at"],
          {"dest": "endAt",
-          "help": "don't run any tests after the given one",
           "default": "",
+          "help": "Stop running the test sequence at this test.",
           }],
         [["--browser-chrome"],
          {"action": "store_true",
           "dest": "browserChrome",
+          "default": False,
           "help": "run browser chrome Mochitests",
-          "default": False,
+          "suppress": True,
           }],
         [["--subsuite"],
-         {"dest": "subsuite",
-          "help": "subsuite of tests to run",
-          "default": None,
+         {"default": None,
+          "help": "Subsuite of tests to run. Unlike tags, subsuites also remove tests from "
+                  "the default set. Only one can be specified at once.",
           }],
         [["--jetpack-package"],
          {"action": "store_true",
           "dest": "jetpackPackage",
-          "help": "run jetpack package tests",
+          "help": "Run jetpack package tests.",
           "default": False,
+          "suppress": True,
           }],
         [["--jetpack-addon"],
          {"action": "store_true",
           "dest": "jetpackAddon",
-          "help": "run jetpack addon tests",
+          "help": "Run jetpack addon tests.",
           "default": False,
+          "suppress": True,
           }],
         [["--webapprt-content"],
          {"action": "store_true",
           "dest": "webapprtContent",
-          "help": "run WebappRT content tests",
+          "help": "Run WebappRT content tests.",
           "default": False,
+          "suppress": True,
           }],
         [["--webapprt-chrome"],
          {"action": "store_true",
           "dest": "webapprtChrome",
-          "help": "run WebappRT chrome tests",
+          "help": "Run WebappRT chrome tests.",
           "default": False,
+          "suppress": True,
           }],
         [["--a11y"],
          {"action": "store_true",
-          "dest": "a11y",
-          "help": "run accessibility Mochitests",
+          "help": "Run accessibility Mochitests.",
           "default": False,
+          "suppress": True,
           }],
         [["--setenv"],
          {"action": "append",
           "dest": "environment",
           "metavar": "NAME=VALUE",
-          "help": "sets the given variable in the application's "
-          "environment",
           "default": [],
+          "help": "Sets the given variable in the application's environment.",
           }],
         [["--exclude-extension"],
          {"action": "append",
           "dest": "extensionsToExclude",
-          "help": "excludes the given extension from being installed "
-          "in the test profile",
           "default": [],
+          "help": "Excludes the given extension from being installed in the test profile.",
+          "suppress": True,
           }],
         [["--browser-arg"],
          {"action": "append",
           "dest": "browserArgs",
-          "metavar": "ARG",
-          "help": "provides an argument to the test application",
           "default": [],
+          "help": "Provides an argument to the test application (e.g Firefox).",
+          "suppress": True,
           }],
         [["--leak-threshold"],
          {"type": int,
           "dest": "defaultLeakThreshold",
-          "metavar": "THRESHOLD",
-          "help": "fail if the number of bytes leaked in default "
-          "processes through refcounted objects (or bytes "
-          "in classes with MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) "
-          "is greater than the given number",
           "default": 0,
+          "help": "Fail if the number of bytes leaked in default processes through "
+                  "refcounted objects (or bytes in classes with MOZ_COUNT_CTOR and "
+                  "MOZ_COUNT_DTOR) is greater than the given number.",
+          "suppress": True,
           }],
         [["--fatal-assertions"],
          {"action": "store_true",
           "dest": "fatalAssertions",
-          "help": "abort testing whenever an assertion is hit "
-          "(requires a debug build to be effective)",
           "default": False,
+          "help": "Abort testing whenever an assertion is hit (requires a debug build to "
+                  "be effective).",
+          "suppress": True,
           }],
         [["--extra-profile-file"],
          {"action": "append",
           "dest": "extraProfileFiles",
-          "help": "copy specified files/dirs to testing profile",
           "default": [],
+          "help": "Copy specified files/dirs to testing profile. Can be specified more "
+                  "than once.",
+          "suppress": True,
           }],
         [["--install-extension"],
          {"action": "append",
           "dest": "extensionsToInstall",
-          "help": "install the specified extension in the testing profile."
-          "The extension file's name should be <id>.xpi where <id> is"
-          "the extension's id as indicated in its install.rdf."
-          "An optional path can be specified too.",
           "default": [],
+          "help": "Install the specified extension in the testing profile. Can be a path "
+                  "to a .xpi file.",
           }],
         [["--profile-path"],
          {"dest": "profilePath",
-          "help": "Directory where the profile will be stored."
-          "This directory will be deleted after the tests are finished",
           "default": None,
+          "help": "Directory where the profile will be stored. This directory will be "
+                  "deleted after the tests are finished.",
+          "suppress": True,
           }],
         [["--testing-modules-dir"],
          {"dest": "testingModulesDir",
+          "default": None,
           "help": "Directory where testing-only JS modules are located.",
-          "default": None,
+          "suppress": True,
           }],
         [["--use-vmware-recording"],
          {"action": "store_true",
           "dest": "vmwareRecording",
-          "help": "enables recording while the application is running "
-          "inside a VMware Workstation 7.0 or later VM",
           "default": False,
+          "help": "Enables recording while the application is running inside a VMware "
+                  "Workstation 7.0 or later VM.",
+          "suppress": True,
           }],
         [["--repeat"],
          {"type": int,
-          "dest": "repeat",
-          "metavar": "REPEAT",
-          "help": "repeats the test or set of tests the given number of times, ie: repeat: 1 will run the test twice.",
           "default": 0,
+          "help": "Repeat the tests the given number of times.",
           }],
         [["--run-until-failure"],
          {"action": "store_true",
           "dest": "runUntilFailure",
-          "help": "Run tests repeatedly and stops on the first time a test fails. "
-          "Default cap is 30 runs, which can be overwritten with the --repeat parameter.",
           "default": False,
+          "help": "Run tests repeatedly but stop the first time a test fails. Default cap "
+                  "is 30 runs, which can be overridden with the --repeat parameter.",
           }],
         [["--manifest"],
          {"dest": "manifestFile",
-          "help": ".ini format of tests to run.",
           "default": None,
+          "help": "Path to a manifestparser (.ini formatted) manifest of tests to run.",
+          "suppress": True,
           }],
         [["--testrun-manifest-file"],
          {"dest": "testRunManifestFile",
-          "help": "Overrides the default filename of the tests.json manifest file that is created from the manifest and used by the test runners to run the tests. Only useful when running multiple test runs simulatenously on the same machine.",
           "default": 'tests.json',
+          "help": "Overrides the default filename of the tests.json manifest file that is "
+                  "generated by the harness and used by SimpleTest. Only useful when running "
+                  "multiple test runs simulatenously on the same machine.",
+          "suppress": True,
           }],
         [["--failure-file"],
          {"dest": "failureFile",
-          "help": "Filename of the output file where we can store a .json list of failures to be run in the future with --run-only-tests.",
           "default": None,
+          "help": "Filename of the output file where we can store a .json list of failures "
+                  "to be run in the future with --run-only-tests.",
+          "suppress": True,
           }],
         [["--run-slower"],
          {"action": "store_true",
           "dest": "runSlower",
-          "help": "Delay execution between test files.",
           "default": False,
+          "help": "Delay execution between tests.",
           }],
         [["--metro-immersive"],
          {"action": "store_true",
           "dest": "immersiveMode",
-          "help": "launches tests in immersive browser",
           "default": False,
+          "help": "Launches tests in an immersive browser.",
+          "suppress": True,
           }],
         [["--httpd-path"],
          {"dest": "httpdPath",
           "default": None,
-          "help": "path to the httpd.js file",
+          "help": "Path to the httpd.js file.",
+          "suppress": True,
           }],
         [["--setpref"],
          {"action": "append",
+          "metavar": "PREF=VALUE",
           "default": [],
           "dest": "extraPrefs",
-          "metavar": "PREF=VALUE",
-          "help": "defines an extra user preference",
+          "help": "Defines an extra user preference.",
           }],
         [["--jsdebugger"],
          {"action": "store_true",
           "default": False,
-          "dest": "jsdebugger",
-          "help": "open the browser debugger",
+          "help": "Start the browser JS debugger before running the test. Implies --no-autorun.",
           }],
         [["--debug-on-failure"],
          {"action": "store_true",
           "default": False,
           "dest": "debugOnFailure",
-          "help": "breaks execution and enters the JS debugger on a test failure. Should be used together with --jsdebugger."
+          "help": "Breaks execution and enters the JS debugger on a test failure. Should "
+                  "be used together with --jsdebugger."
           }],
         [["--e10s"],
          {"action": "store_true",
           "default": False,
-          "dest": "e10s",
           "help": "Run tests with electrolysis preferences and test filtering enabled.",
           }],
         [["--strict-content-sandbox"],
          {"action": "store_true",
           "default": False,
           "dest": "strictContentSandbox",
           "help": "Run tests with a more strict content sandbox (Windows only).",
+          "suppress": not mozinfo.isWin,
           }],
         [["--nested_oop"],
          {"action": "store_true",
           "default": False,
-          "dest": "nested_oop",
           "help": "Run tests with nested_oop preferences and test filtering enabled.",
           }],
+        [["--dmd"],
+         {"action": "store_true",
+          "default": False,
+          "help": "Run tests with DMD active.",
+          }],
         [["--dmd-path"],
          {"default": None,
           "dest": "dmdPath",
           "help": "Specifies the path to the directory containing the shared library for DMD.",
+          "suppress": True,
           }],
         [["--dump-output-directory"],
          {"default": None,
           "dest": "dumpOutputDirectory",
           "help": "Specifies the directory in which to place dumped memory reports.",
           }],
         [["--dump-about-memory-after-test"],
          {"action": "store_true",
           "default": False,
           "dest": "dumpAboutMemoryAfterTest",
-          "help": "Produce an about:memory dump after each test in the directory specified "
-          "by --dump-output-directory."
+          "help": "Dump an about:memory log after each test in the directory specified "
+                  "by --dump-output-directory.",
           }],
         [["--dump-dmd-after-test"],
          {"action": "store_true",
           "default": False,
           "dest": "dumpDMDAfterTest",
-          "help": "Produce a DMD dump after each test in the directory specified "
-          "by --dump-output-directory."
+          "help": "Dump a DMD log after each test in the directory specified "
+                  "by --dump-output-directory.",
           }],
         [["--slowscript"],
          {"action": "store_true",
           "default": False,
-          "dest": "slowscript",
           "help": "Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; "
-          "when not set, recoverable but misleading SIGSEGV instances "
-          "may occur in Ion/Odin JIT code."
+                  "when not set, recoverable but misleading SIGSEGV instances "
+                  "may occur in Ion/Odin JIT code.",
           }],
         [["--screenshot-on-fail"],
          {"action": "store_true",
           "default": False,
           "dest": "screenshotOnFail",
-          "help": "Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory for storing the screenshots."
+          "help": "Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory "
+                  "for storing the screenshots."
           }],
         [["--quiet"],
          {"action": "store_true",
+          "dest": "quiet",
           "default": False,
-          "dest": "quiet",
-          "help": "Do not print test log lines unless a failure occurs."
+          "help": "Do not print test log lines unless a failure occurs.",
           }],
         [["--pidfile"],
          {"dest": "pidFile",
-          "help": "name of the pidfile to generate",
           "default": "",
+          "help": "Name of the pidfile to generate.",
+          "suppress": True,
           }],
         [["--use-test-media-devices"],
          {"action": "store_true",
           "default": False,
           "dest": "useTestMediaDevices",
           "help": "Use test media device drivers for media testing.",
           }],
         [["--gmp-path"],
          {"default": None,
-          "dest": "gmp_path",
           "help": "Path to fake GMP plugin. Will be deduced from the binary if not passed.",
+          "suppress": True,
           }],
         [["--xre-path"],
          {"dest": "xrePath",
           "default": None,    # individual scripts will set a sane default
-          "help": "absolute path to directory containing XRE (probably xulrunner)",
+          "help": "Absolute path to directory containing XRE (probably xulrunner).",
+          "suppress": True,
           }],
         [["--symbols-path"],
          {"dest": "symbolsPath",
           "default": None,
-          "help": "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols",
+          "help": "Absolute path to directory containing breakpad symbols, or the URL of a "
+                  "zip file containing symbols",
+          "suppress": True,
           }],
         [["--debugger"],
-         {"dest": "debugger",
-          "help": "use the given debugger to launch the application",
+         {"default": None,
+          "help": "Debugger binary to run tests in. Program name or path.",
           }],
         [["--debugger-args"],
          {"dest": "debuggerArgs",
-          "help": "pass the given args to the debugger _before_ the application on the command line",
+          "default": None,
+          "help": "Arguments to pass to the debugger.",
           }],
         [["--debugger-interactive"],
          {"action": "store_true",
           "dest": "debuggerInteractive",
-          "help": "prevents the test harness from redirecting stdout and stderr for interactive debuggers",
-          }],
-        [["--max-timeouts"],
-         {"type": int,
-          "dest": "maxTimeouts",
-          "help": "maximum number of timeouts permitted before halting testing",
           "default": None,
+          "help": "Prevents the test harness from redirecting stdout and stderr for "
+                  "interactive debuggers.",
+          "suppress": True,
           }],
         [["--tag"],
          {"action": "append",
           "dest": "test_tags",
           "default": None,
-          "help": "filter out tests that don't have the given tag. Can be "
-                  "used multiple times in which case the test must contain "
-                  "at least one of the given tags.",
+          "help": "Filter out tests that don't have the given tag. Can be used multiple "
+                  "times in which case the test must contain at least one of the given tags.",
           }],
         [["--enable-cpow-warnings"],
          {"action": "store_true",
           "dest": "enableCPOWWarnings",
-          "help": "enable logging of unsafe CPOW usage, which is disabled by default for tests",
+          "help": "Enable logging of unsafe CPOW usage, which is disabled by default for tests",
+          "suppress": True,
           }],
     ]
 
-    def __init__(self, **kwargs):
-        ArgumentParser.__init__(self, usage=self.__doc__, **kwargs)
-        for option, value in self.mochitest_options:
-            # Allocate new lists so references to original don't get mutated.
-            # allowing multiple uses within a single process.
-            if "default" in value and isinstance(value["default"], list):
-                value["default"] = []
-            self.add_argument(*option, **value)
+    defaults = {
+        # Bug 1065098 - The geckomediaplugin process fails to produce a leak
+        # log for some reason.
+        'ignoreMissingLeaks': ["geckomediaplugin"],
 
-    def verifyOptions(self, options, mochitest):
-        """ verify correct options and cleanup paths """
+        # Set server information on the args object
+        'webServer': '127.0.0.1',
+        'httpPort': DEFAULT_PORTS['http'],
+        'sslPort': DEFAULT_PORTS['https'],
+        'webSocketPort': '9988',
+        # The default websocket port is incorrect in mozprofile; it is
+        # set to the SSL proxy setting. See:
+        # see https://bugzilla.mozilla.org/show_bug.cgi?id=916517
+        # args.webSocketPort = DEFAULT_PORTS['ws']
+    }
+
+    def validate(self, parser, options, context):
+        """Validate generic options."""
 
         # for test manifest parsing.
         mozinfo.update({"strictContentSandbox": options.strictContentSandbox})
         # for test manifest parsing.
         mozinfo.update({"nested_oop": options.nested_oop})
 
-        if options.app is None:
-            if build_obj is not None:
-                options.app = build_obj.get_binary_path()
-            else:
-                self.error(
-                    "could not find the application path, --appname must be specified")
+        # b2g and android don't use 'app' the same way, so skip validation
+        if parser.app not in ('b2g', 'android'):
+            if options.app is None:
+                if build_obj:
+                    options.app = build_obj.get_binary_path()
+                else:
+                    parser.error(
+                        "could not find the application path, --appname must be specified")
+            elif options.app == "dist" and build_obj:
+                options.app = build_obj.get_binary_path(where='staged-package')
+
+            options.app = self.get_full_path(options.app, parser.oldcwd)
+            if not os.path.exists(options.app):
+                parser.error("Error: Path {} doesn't exist. Are you executing "
+                             "$objdir/_tests/testing/mochitest/runtests.py?".format(
+                                 options.app))
+
+        if options.gmp_path is None and options.app and build_obj:
+            # Need to fix the location of gmp_fake which might not be shipped in the binary
+            gmp_modules = (
+                ('gmp-fake', '1.0'),
+                ('gmp-clearkey', '0.1'),
+                ('gmp-fakeopenh264', '1.0')
+            )
+            options.gmp_path = os.pathsep.join(
+                os.path.join(build_obj.bindir, *p) for p in gmp_modules)
 
         if options.totalChunks is not None and options.thisChunk is None:
-            self.error(
+            parser.error(
                 "thisChunk must be specified when totalChunks is specified")
 
         if options.totalChunks:
             if not 1 <= options.thisChunk <= options.totalChunks:
-                self.error("thisChunk must be between 1 and totalChunks")
+                parser.error("thisChunk must be between 1 and totalChunks")
 
         if options.chunkByDir and options.chunkByRuntime:
-            self.error(
+            parser.error(
                 "can only use one of --chunk-by-dir or --chunk-by-runtime")
 
         if options.xrePath is None:
             # default xrePath to the app path if not provided
             # but only if an app path was explicitly provided
-            if options.app != self.get_default('app'):
+            if options.app != parser.get_default('app'):
                 options.xrePath = os.path.dirname(options.app)
                 if mozinfo.isMac:
                     options.xrePath = os.path.join(
                         os.path.dirname(
                             options.xrePath),
                         "Resources")
             elif build_obj is not None:
                 # otherwise default to dist/bin
                 options.xrePath = build_obj.bindir
             else:
-                self.error(
+                parser.error(
                     "could not find xre directory, --xre-path must be specified")
 
         # allow relative paths
-        options.xrePath = mochitest.getFullPath(options.xrePath)
+        options.xrePath = self.get_full_path(options.xrePath, parser.oldcwd)
         if options.profilePath:
-            options.profilePath = mochitest.getFullPath(options.profilePath)
-        options.app = mochitest.getFullPath(options.app)
-        if options.dmdPath is not None:
-            options.dmdPath = mochitest.getFullPath(options.dmdPath)
+            options.profilePath = self.get_full_path(options.profilePath, parser.oldcwd)
+
+        if options.dmdPath:
+            options.dmdPath = self.get_full_path(options.dmdPath, parser.oldcwd)
 
-        if not os.path.exists(options.app):
-            msg = """\
-            Error: Path %(app)s doesn't exist.
-            Are you executing $objdir/_tests/testing/mochitest/runtests.py?"""
-            self.error(msg % {"app": options.app})
-            return None
+        if options.dmd and not options.dmdPath:
+            if build_obj:
+                options.dmdPath = build_obj.bin_dir
+            else:
+                parser.error(
+                    "could not find dmd libraries, specify them with --dmd-path")
 
         if options.utilityPath:
-            options.utilityPath = mochitest.getFullPath(options.utilityPath)
+            options.utilityPath = self.get_full_path(options.utilityPath, parser.oldcwd)
 
         if options.certPath:
-            options.certPath = mochitest.getFullPath(options.certPath)
-
-        if options.symbolsPath and len(
-            urlparse(
-                options.symbolsPath).scheme) < 2:
-            options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
+            options.certPath = self.get_full_path(options.certPath, parser.oldcwd)
+        elif build_obj:
+            options.certPath = os.path.join(build_obj.topsrcdir, 'build', 'pgo', 'certs')
 
-        # Set server information on the options object
-        options.webServer = '127.0.0.1'
-        options.httpPort = DEFAULT_PORTS['http']
-        options.sslPort = DEFAULT_PORTS['https']
-        #        options.webSocketPort = DEFAULT_PORTS['ws']
-        # <- http://hg.mozilla.org/mozilla-central/file/b871dfb2186f/build/automation.py.in#l30
-        options.webSocketPort = str(9988)
-        # The default websocket port is incorrect in mozprofile; it is
-        # set to the SSL proxy setting. See:
-        # see https://bugzilla.mozilla.org/show_bug.cgi?id=916517
+        if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2:
+            options.symbolsPath = self.get_full_path(options.symbolsPath, parser.oldcwd)
+        elif not options.symbolsPath and build_obj:
+            options.symbolsPath = os.path.join(build_obj.distdir, 'crashreporter-symbols')
 
         if options.vmwareRecording:
             if not mozinfo.isWin:
-                self.error(
+                parser.error(
                     "use-vmware-recording is only supported on Windows.")
-            mochitest.vmwareHelperPath = os.path.join(
+            options.vmwareHelperPath = os.path.join(
                 options.utilityPath, VMWARE_RECORDING_HELPER_BASENAME + ".dll")
-            if not os.path.exists(mochitest.vmwareHelperPath):
-                self.error("%s not found, cannot automate VMware recording." %
-                           mochitest.vmwareHelperPath)
+            if not os.path.exists(options.vmwareHelperPath):
+                parser.error("%s not found, cannot automate VMware recording." %
+                             options.vmwareHelperPath)
 
         if options.webapprtContent and options.webapprtChrome:
-            self.error(
+            parser.error(
                 "Only one of --webapprt-content and --webapprt-chrome may be given.")
 
         if options.jsdebugger:
             options.extraPrefs += [
                 "devtools.debugger.remote-enabled=true",
                 "devtools.chrome.enabled=true",
                 "devtools.debugger.prompt-connection=false"
             ]
             options.autorun = False
 
         if options.debugOnFailure and not options.jsdebugger:
-            self.error(
-                "--debug-on-failure should be used together with --jsdebugger.")
+            parser.error(
+                "--debug-on-failure requires --jsdebugger.")
+
+        if options.debuggerArgs and not options.debugger:
+            parser.error(
+                "--debugger-args requires --debugger.")
 
-        # Try to guess the testing modules directory.
-        # This somewhat grotesque hack allows the buildbot machines to find the
-        # modules directory without having to configure the buildbot hosts. This
-        # code should never be executed in local runs because the build system
-        # should always set the flag that populates this variable. If buildbot ever
-        # passes this argument, this code can be deleted.
         if options.testingModulesDir is None:
-            possible = os.path.join(here, os.path.pardir, 'modules')
+            if build_obj:
+                options.testingModulesDir = os.path.join(
+                    build_obj.topobjdir, '_tests', 'modules')
+            else:
+                # Try to guess the testing modules directory.
+                # This somewhat grotesque hack allows the buildbot machines to find the
+                # modules directory without having to configure the buildbot hosts. This
+                # code should never be executed in local runs because the build system
+                # should always set the flag that populates this variable. If buildbot ever
+                # passes this argument, this code can be deleted.
+                possible = os.path.join(here, os.path.pardir, 'modules')
 
-            if os.path.isdir(possible):
-                options.testingModulesDir = possible
+                if os.path.isdir(possible):
+                    options.testingModulesDir = possible
+
+        if build_obj:
+            options.extraProfileFiles.append(os.path.join(build_obj.distdir, 'plugins'))
 
         # Even if buildbot is updated, we still want this, as the path we pass in
         # to the app must be absolute and have proper slashes.
         if options.testingModulesDir is not None:
             options.testingModulesDir = os.path.normpath(
                 options.testingModulesDir)
 
             if not os.path.isabs(options.testingModulesDir):
                 options.testingModulesDir = os.path.abspath(
                     options.testingModulesDir)
 
             if not os.path.isdir(options.testingModulesDir):
-                self.error('--testing-modules-dir not a directory: %s' %
-                           options.testingModulesDir)
+                parser.error('--testing-modules-dir not a directory: %s' %
+                             options.testingModulesDir)
 
             options.testingModulesDir = options.testingModulesDir.replace(
                 '\\',
                 '/')
             if options.testingModulesDir[-1] != '/':
                 options.testingModulesDir += '/'
 
         if options.immersiveMode:
             if not mozinfo.isWin:
-                self.error("immersive is only supported on Windows 8 and up.")
-            mochitest.immersiveHelperPath = os.path.join(
+                parser.error("immersive is only supported on Windows 8 and up.")
+            options.immersiveHelperPath = os.path.join(
                 options.utilityPath, "metrotestharness.exe")
-            if not os.path.exists(mochitest.immersiveHelperPath):
-                self.error("%s not found, cannot launch immersive tests." %
-                           mochitest.immersiveHelperPath)
+            if not os.path.exists(options.immersiveHelperPath):
+                parser.error("%s not found, cannot launch immersive tests." %
+                             options.immersiveHelperPath)
 
         if options.runUntilFailure:
             if not options.repeat:
                 options.repeat = 29
 
         if options.dumpOutputDirectory is None:
             options.dumpOutputDirectory = tempfile.gettempdir()
 
         if options.dumpAboutMemoryAfterTest or options.dumpDMDAfterTest:
             if not os.path.isdir(options.dumpOutputDirectory):
-                self.error('--dump-output-directory not a directory: %s' %
-                           options.dumpOutputDirectory)
+                parser.error('--dump-output-directory not a directory: %s' %
+                             options.dumpOutputDirectory)
 
         if options.useTestMediaDevices:
             if not mozinfo.isLinux:
-                self.error(
+                parser.error(
                     '--use-test-media-devices is only supported on Linux currently')
             for f in ['/usr/bin/gst-launch-0.10', '/usr/bin/pactl']:
                 if not os.path.isfile(f):
-                    self.error(
+                    parser.error(
                         'Missing binary %s required for '
                         '--use-test-media-devices' % f)
 
         if options.nested_oop:
             if not options.e10s:
                 options.e10s = True
         mozinfo.update({"e10s": options.e10s})  # for test manifest parsing.
 
         options.leakThresholds = {
             "default": options.defaultLeakThreshold,
             "tab": 25000,  # See dependencies of bug 1051230.
             # GMP rarely gets a log, but when it does, it leaks a little.
             "geckomediaplugin": 20000,
         }
 
-        # Bug 1065098 - The geckomediaplugin process fails to produce a leak
-        # log for some reason.
-        options.ignoreMissingLeaks = ["geckomediaplugin"]
-
         # Bug 1091917 - We exit early in tab processes on Windows, so we don't
         # get leak logs yet.
         if mozinfo.isWin:
             options.ignoreMissingLeaks.append("tab")
 
         # Bug 1121539 - OSX-only intermittent tab process leak in test_ipc.html
         if mozinfo.isMac:
             options.leakThresholds["tab"] = 100000
 
         return options
 
 
-class B2GOptions(MochitestOptions):
-    b2g_options = [
+class B2GArguments(ArgumentContainer):
+    """B2G specific arguments."""
+
+    args = [
         [["--b2gpath"],
          {"dest": "b2gPath",
-          "help": "path to B2G repo or qemu dir",
           "default": None,
+          "help": "Path to B2G repo or QEMU directory.",
+          "suppress": True,
           }],
         [["--desktop"],
          {"action": "store_true",
-          "dest": "desktop",
-          "help": "Run the tests on a B2G desktop build",
           "default": False,
+          "help": "Run the tests on a B2G desktop build.",
+          "suppress": True,
           }],
         [["--marionette"],
-         {"dest": "marionette",
+         {"default": None,
           "help": "host:port to use when connecting to Marionette",
-          "default": None,
           }],
         [["--emulator"],
-         {"dest": "emulator",
-          "help": "Architecture of emulator to use: x86 or arm",
-          "default": None,
+         {"default": None,
+          "help": "Architecture of emulator to use, x86 or arm",
+          "suppress": True,
           }],
         [["--wifi"],
-         {"dest": "wifi",
+         {"default": False,
           "help": "Devine wifi configuration for on device mochitest",
-          "default": False,
+          "suppress": True,
           }],
         [["--sdcard"],
-         {"dest": "sdcard",
+         {"default": "10MB",
           "help": "Define size of sdcard: 1MB, 50MB...etc",
-          "default": "10MB",
           }],
         [["--no-window"],
          {"action": "store_true",
           "dest": "noWindow",
+          "default": False,
           "help": "Pass --no-window to the emulator",
-          "default": False,
           }],
         [["--adbpath"],
          {"dest": "adbPath",
-          "help": "path to adb",
           "default": "adb",
+          "help": "Path to adb binary.",
+          "suppress": True,
           }],
         [["--deviceIP"],
          {"dest": "deviceIP",
-          "help": "ip address of remote device to test",
           "default": None,
+          "help": "IP address of remote device to test.",
+          "suppress": True,
           }],
         [["--devicePort"],
-         {"dest": "devicePort",
+         {"default": 20701,
           "help": "port of remote device to test",
-          "default": 20701,
+          "suppress": True,
           }],
         [["--remote-logfile"],
          {"dest": "remoteLogFile",
-          "help": "Name of log file on the device relative to the device root. \
-                  PLEASE ONLY USE A FILENAME.",
           "default": None,
+          "help": "Name of log file on the device relative to the device root. "
+                  "PLEASE ONLY USE A FILENAME.",
+          "suppress": True,
           }],
         [["--remote-webserver"],
          {"dest": "remoteWebServer",
-          "help": "ip address where the remote web server is hosted at",
           "default": None,
+          "help": "IP address where the remote web server is hosted.",
+          "suppress": True,
           }],
         [["--http-port"],
          {"dest": "httpPort",
-          "help": "ip address where the remote web server is hosted at",
           "default": DEFAULT_PORTS['http'],
+          "help": "Port used for http on the remote web server.",
+          "suppress": True,
           }],
         [["--ssl-port"],
          {"dest": "sslPort",
-          "help": "ip address where the remote web server is hosted at",
           "default": DEFAULT_PORTS['https'],
+          "help": "Port used for https on the remote web server.",
+          "suppress": True,
           }],
         [["--gecko-path"],
          {"dest": "geckoPath",
-          "help": "the path to a gecko distribution that should \
-                   be installed on the emulator prior to test",
           "default": None,
+          "help": "The path to a gecko distribution that should be installed on the emulator "
+                  "prior to test.",
+          "suppress": True,
           }],
         [["--profile"],
          {"dest": "profile",
-          "help": "for desktop testing, the path to the \
-                   gaia profile to use",
           "default": None,
+          "help": "For desktop testing, the path to the gaia profile to use.",
           }],
         [["--logdir"],
          {"dest": "logdir",
-          "help": "directory to store log files",
           "default": None,
+          "help": "Directory to store log files.",
           }],
         [['--busybox'],
          {"dest": 'busybox',
-          "help": "Path to busybox binary to install on device",
           "default": None,
+          "help": "Path to busybox binary to install on device.",
           }],
         [['--profile-data-dir'],
          {"dest": 'profile_data_dir',
-          "help": "Path to a directory containing preference and other \
-                   data to be installed into the profile",
           "default": os.path.join(here, 'profile_data'),
+          "help": "Path to a directory containing preference and other data to be installed "
+                  "into the profile.",
+          "suppress": True,
           }],
     ]
 
-    def __init__(self):
-        MochitestOptions.__init__(self)
+    defaults = {
+        'logFile': 'mochitest.log',
+        'extensionsToExclude': ['specialpowers'],
+        # See dependencies of bug 1038943.
+        'defaultLeakThreshold': 5536,
+    }
 
-        for option in self.b2g_options:
-            self.add_argument(*option[0], **option[1])
+    def validate(self, parser, options, context):
+        """Validate b2g options."""
 
-        defaults = {}
-        defaults["logFile"] = "mochitest.log"
-        defaults["autorun"] = True
-        defaults["closeWhenDone"] = True
-        defaults["extensionsToExclude"] = ["specialpowers"]
-        # See dependencies of bug 1038943.
-        defaults["defaultLeakThreshold"] = 5536
-        self.set_defaults(**defaults)
+        if options.desktop and not options.app:
+            if not (build_obj and conditions.is_b2g_desktop(build_obj)):
+                parser.error(
+                    "--desktop specified, but no b2g desktop build detected! Either "
+                    "build for b2g desktop, or point --appname to a b2g desktop binary.")
+        elif build_obj and conditions.is_b2g_desktop(build_obj):
+            options.desktop = True
+            if not options.app:
+                options.app = build_obj.get_binary_path()
+                if not options.app.endswith('-bin'):
+                    options.app = '%s-bin' % options.app
+                if not os.path.isfile(options.app):
+                    options.app = options.app[:-len('-bin')]
 
-    def verifyRemoteOptions(self, options):
         if options.remoteWebServer is None:
             if os.name != "nt":
                 options.remoteWebServer = moznetwork.get_ip()
             else:
-                self.error(
+                parser.error(
                     "You must specify a --remote-webserver=<ip address>")
         options.webServer = options.remoteWebServer
 
+        if not options.b2gPath and hasattr(context, 'b2g_home'):
+            options.b2gPath = context.b2g_home
+
+        if hasattr(context, 'device_name') and not options.emulator:
+            if context.device_name.startswith('emulator'):
+                options.emulator = 'x86' if 'x86' in context.device_name else 'arm'
+
         if options.geckoPath and not options.emulator:
-            self.error(
+            parser.error(
                 "You must specify --emulator if you specify --gecko-path")
 
         if options.logdir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logdir")
+            parser.error("You must specify --emulator if you specify --logdir")
+        elif not options.logdir and options.emulator and build_obj:
+            options.logdir = os.path.join(
+                build_obj.topobjdir, '_tests', 'testing', 'mochitest')
+
+        if hasattr(context, 'xre_path'):
+            options.xrePath = context.xre_path
 
         if not os.path.isdir(options.xrePath):
-            self.error("--xre-path '%s' is not a directory" % options.xrePath)
+            parser.error("--xre-path '%s' is not a directory" % options.xrePath)
+
         xpcshell = os.path.join(options.xrePath, 'xpcshell')
         if not os.access(xpcshell, os.F_OK):
-            self.error('xpcshell not found at %s' % xpcshell)
+            parser.error('xpcshell not found at %s' % xpcshell)
+
         if self.elf_arm(xpcshell):
-            self.error('--xre-path points to an ARM version of xpcshell; it '
-                       'should instead point to a version that can run on '
-                       'your desktop')
+            parser.error('--xre-path points to an ARM version of xpcshell; it '
+                         'should instead point to a version that can run on '
+                         'your desktop')
+
+        if not options.httpdPath and build_obj:
+            options.httpdPath = os.path.join(
+                build_obj.topobjdir, '_tests', 'testing', 'mochitest')
+
+        # Bug 1071866 - B2G Mochitests do not always produce a leak log.
+        options.ignoreMissingLeaks.append("default")
+        # Bug 1070068 - Leak logging does not work for tab processes on B2G.
+        options.ignoreMissingLeaks.append("tab")
 
         if options.pidFile != "":
             f = open(options.pidFile, 'w')
             f.write("%s" % os.getpid())
             f.close()
 
         return options
 
-    def verifyOptions(self, options, mochitest):
-        # since we are reusing verifyOptions, it will exit if App is not found
-        temp = options.app
-        options.app = __file__
-        tempPort = options.httpPort
-        tempSSL = options.sslPort
-        tempIP = options.webServer
-        options = MochitestOptions.verifyOptions(self, options, mochitest)
-        options.webServer = tempIP
-        options.app = temp
-        options.sslPort = tempSSL
-        options.httpPort = tempPort
-
-        # Bug 1071866 - B2G Mochitests do not always produce a leak log.
-        options.ignoreMissingLeaks.append("default")
-
-        # Bug 1070068 - Leak logging does not work for tab processes on B2G.
-        options.ignoreMissingLeaks.append("tab")
-
-        return options
-
     def elf_arm(self, filename):
         data = open(filename, 'rb').read(20)
         return data[:4] == "\x7fELF" and ord(data[18]) == 40  # EM_ARM
 
 
-class RemoteOptions(MochitestOptions):
-    remote_options = [
+class AndroidArguments(ArgumentContainer):
+    """Android specific arguments."""
+
+    args = [
         [["--remote-app-path"],
          {"dest": "remoteAppPath",
           "help": "Path to remote executable relative to device root using \
                    only forward slashes. Either this or app must be specified \
                    but not both.",
           "default": None,
           }],
         [["--deviceIP"],
@@ -848,212 +948,266 @@ class RemoteOptions(MochitestOptions):
           "default": None,
           }],
         [["--deviceSerial"],
          {"dest": "deviceSerial",
           "help": "ip address of remote device to test",
           "default": None,
           }],
         [["--dm_trans"],
-         {"dest": "dm_trans",
-          "default": "sut",
-          "help": "the transport to use to communicate with device: \
-                   [adb|sut]; default=sut",
+         {"choices": ["adb", "sut"],
+          "default": "adb",
+          "help": "The transport to use for communication with the device [default: adb].",
+          "suppress": True,
           }],
         [["--devicePort"],
          {"dest": "devicePort",
           "type": int,
           "default": 20701,
           "help": "port of remote device to test",
           }],
         [["--remote-product-name"],
          {"dest": "remoteProductName",
           "default": "fennec",
           "help": "The executable's name of remote product to test - either \
                    fennec or firefox, defaults to fennec",
+          "suppress": True,
           }],
         [["--remote-logfile"],
          {"dest": "remoteLogFile",
           "default": None,
           "help": "Name of log file on the device relative to the device \
                    root. PLEASE ONLY USE A FILENAME.",
           }],
         [["--remote-webserver"],
          {"dest": "remoteWebServer",
           "default": None,
           "help": "ip address where the remote web server is hosted at",
           }],
         [["--http-port"],
          {"dest": "httpPort",
           "default": DEFAULT_PORTS['http'],
           "help": "http port of the remote web server",
+          "suppress": True,
           }],
         [["--ssl-port"],
          {"dest": "sslPort",
           "default": DEFAULT_PORTS['https'],
           "help": "ssl port of the remote web server",
+          "suppress": True,
           }],
         [["--robocop-ini"],