Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 27 Oct 2014 16:07:50 +0100
changeset 212462 f7163ced3bae10cbf324acf92f48c4b0d3ef6650
parent 212410 fa5da83e4aee66594282d81d91dac2ad29fd0035 (current diff)
parent 212461 20408ad61ce5a530d7e563da564efb9bb6b8658f (diff)
child 212463 2bb46b19c290f7022afd23cd2ef05b59362e210f
push id11670
push usercbook@mozilla.com
push dateMon, 27 Oct 2014 15:08:08 +0000
treeherderb2g-inbound@f7163ced3bae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.0a1
Merge mozilla-central to b2g-inbound
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
memory/replace/dmd/test/script-show-all-block-sizes-expected.txt
memory/replace/dmd/test/script-show-all-block-sizes.json
xpcom/reflect/xptcall/md/unix/xptcinvoke_amd64_openbsd.cpp
xpcom/reflect/xptcall/md/unix/xptcstubs_amd64_openbsd.cpp
--- a/b2g/components/OopCommandLine.js
+++ b/b2g/components/OopCommandLine.js
@@ -34,14 +34,14 @@ oopCommandlineHandler.prototype = {
             } catch (e) { }
 
         }
         if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
             cmdLine.preventDefault = true;
         }
     },
 
-    helpInfo: "  -oop         Use out-of-process model in B2G\n",
+    helpInfo: "  --oop        Use out-of-process model in B2G\n",
     classID: Components.ID("{e30b0e13-2d12-4cb0-bc4c-4e617a1bf76e}"),
     QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([oopCommandlineHandler]);
--- a/b2g/gaia/run-b2g.c
+++ b/b2g/gaia/run-b2g.c
@@ -30,18 +30,20 @@ int main(int argc, char* argv[], char* e
     full_profile_path = (char*) malloc(strlen(cwd) + strlen(GAIA_PATH) + 2);
     if (!full_profile_path) {
         error(NOMEM);
         return -2;
     }
     sprintf(full_path, "%s/%s", cwd, B2G_NAME);
     sprintf(full_profile_path, "%s/%s", cwd, GAIA_PATH);
     free(cwd);
-    printf("Running: %s -profile %s\n", full_path, full_profile_path);
+    printf("Running: %s --profile %s\n", full_path, full_profile_path);
     fflush(stdout);
     fflush(stderr);
+    // XXX: yes, the printf above says --profile and this execle uses -profile.
+    // Bug 1088430 will change the execle to use --profile.
     execle(full_path, full_path, "-profile", full_profile_path, NULL, envp);
     error("unable to start");
     perror(argv[0]);
     free(full_path);
     free(full_profile_path);
     return -1;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -848,16 +848,17 @@ var gBrowserInit = {
     DOMLinkHandler.init();
     gPageStyleMenu.init();
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     DevEdition.init();
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/content.js", true);
+    mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
 
     // initialize observers and listeners
     // and give C++ access to gBrowser
     XULBrowserWindow.init();
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(nsIWebNavigation)
           .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
           .QueryInterface(Ci.nsIInterfaceRequestor)
@@ -4254,17 +4255,17 @@ nsBrowserAccess.prototype = {
     return browser;
   },
 
   openURI: function (aURI, aOpener, aWhere, aContext) {
     var newWindow = null;
     var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
 
     if (isExternal && aURI && aURI.schemeIs("chrome")) {
-      dump("use -chrome command-line option to load external chrome urls\n");
+      dump("use --chrome command-line option to load external chrome urls\n");
       return null;
     }
 
     if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
       if (isExternal &&
           gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
         aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
       else
new file mode 100644
--- /dev/null
+++ b/browser/base/content/content-UITour.js
@@ -0,0 +1,86 @@
+let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
+const UITOUR_PERMISSION   = "uitour";
+
+let UITourListener = {
+  handleEvent: function (event) {
+    if (!Services.prefs.getBoolPref("browser.uitour.enabled")) {
+      return;
+    }
+    if (!this.ensureTrustedOrigin()) {
+      return;
+    }
+    addMessageListener("UITour:SendPageCallback", this);
+    sendAsyncMessage("UITour:onPageEvent", {detail: event.detail, type: event.type});
+  },
+
+  isTestingOrigin: function(aURI) {
+    if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) {
+      return false;
+    }
+
+    // Add any testing origins (comma-seperated) to the whitelist for the session.
+    for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) {
+      try {
+        let testingURI = Services.io.newURI(origin, null, null);
+        if (aURI.prePath == testingURI.prePath) {
+          return true;
+        }
+      } catch (ex) {
+        Cu.reportError(ex);
+      }
+    }
+    return false;
+  },
+
+  // This function is copied from UITour.jsm.
+  isSafeScheme: function(aURI) {
+    let allowedSchemes = new Set(["https", "about"]);
+    if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
+      allowedSchemes.add("http");
+
+    if (!allowedSchemes.has(aURI.scheme))
+      return false;
+
+    return true;
+  },
+
+  ensureTrustedOrigin: function() {
+    if (content.top != content)
+      return false;
+
+    let uri = content.document.documentURIObject;
+
+    if (uri.schemeIs("chrome"))
+      return true;
+
+    if (!this.isSafeScheme(uri))
+      return false;
+
+    let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
+    if (permission == Services.perms.ALLOW_ACTION)
+      return true;
+
+    return this.isTestingOrigin(uri);
+  },
+
+  receiveMessage: function(aMessage) {
+    switch (aMessage.name) {
+      case "UITour:SendPageCallback":
+        this.sendPageCallback(aMessage.data);
+        break;
+    }
+  },
+
+  sendPageCallback: function (detail) {
+    let doc = content.document;
+    let event = new doc.defaultView.CustomEvent("mozUITourResponse", {
+      bubbles: true,
+      detail: Cu.cloneInto(detail, doc.defaultView)
+    });
+    doc.dispatchEvent(event);
+  }
+};
+
+addEventListener("mozUITour", UITourListener, false, true);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -20,18 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
   "resource:///modules/PluginContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UITour",
-  "resource:///modules/UITour.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
   "resource:///modules/FormSubmitObserver.jsm");
 
 // TabChildGlobal
 var global = this;
 
 // Load the form validation popup handler
 var formSubmitObserver = new FormSubmitObserver(content, this);
@@ -117,25 +115,16 @@ if (Services.appinfo.processType == Serv
       sendSyncMessage("contextmenu", { editFlags, spellInfo }, { event });
     }
   }
 
   Cc["@mozilla.org/eventlistenerservice;1"]
     .getService(Ci.nsIEventListenerService)
     .addSystemEventListener(global, "contextmenu", handleContentContextMenu, true);
 
-} else {
-  addEventListener("mozUITour", function(event) {
-    if (!Services.prefs.getBoolPref("browser.uitour.enabled"))
-      return;
-
-    let handled = UITour.onPageEvent(event);
-    if (handled)
-      addEventListener("pagehide", UITour);
-  }, false, true);
 }
 
 let AboutHomeListener = {
   init: function(chromeGlobal) {
     chromeGlobal.addEventListener('AboutHomeLoad', this, false, true);
   },
 
   get isAboutHome() {
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -68,21 +68,35 @@ Sanitizer.prototype = {
 
     // Ensure open windows get cleared first, if they're in our list, so that they don't stick
     // around in the recently closed windows list, and so we can cancel the whole thing
     // if the user selects to keep a window open from a beforeunload prompt.
     let openWindowsIndex = itemsToClear.indexOf("openWindows");
     if (openWindowsIndex != -1) {
       itemsToClear.splice(openWindowsIndex, 1);
       let item = this.items.openWindows;
-      if (!item.clear()) {
-        // When cancelled, reject the deferred and return the promise:
-        deferred.reject();
-        return deferred.promise;
+
+      function onWindowsCleaned() {
+        try {
+          let clearedPromise = this.sanitize(itemsToClear);
+          clearedPromise.then(deferred.resolve, deferred.reject);
+        } catch(e) {
+          let error = "Sanitizer threw after closing windows: " + e;
+          Cu.reportError(error);
+          deferred.reject(error);
+        }
       }
+
+      let ok = item.clear(onWindowsCleaned.bind(this));
+      // When cancelled, reject immediately
+      if (!ok) {
+        deferred.reject("Sanitizer canceled closing windows");
+      }
+
+      return deferred.promise;
     }
 
     // Cache the range of times to clear
     if (this.ignoreTimespan)
       var range = null;  // If we ignore timespan, clear everything
     else
       range = this.range || Sanitizer.getClearRange();
 
@@ -460,21 +474,25 @@ Sanitizer.prototype = {
         }
         return true;
       },
       _resetAllWindowClosures: function(aWindowList) {
         for (let win of aWindowList) {
           win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
         }
       },
-      clear: function()
+      clear: function(aCallback)
       {
         // NB: this closes all *browser* windows, not other windows like the library, about window,
         // browser console, etc.
 
+        if (!aCallback) {
+          throw "Sanitizer's openWindows clear() requires a callback.";
+        }
+
         // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
         // dialogs
         let existingWindow = Services.appShell.hiddenDOMWindow;
         let startDate = existingWindow.performance.now();
 
         // First check if all these windows are OK with being closed:
         let windowEnumerator = Services.wm.getEnumerator("navigator:browser");
         let windowList = [];
@@ -502,17 +520,49 @@ Sanitizer.prototype = {
         // First create a new window. We do this first so that on non-mac, we don't
         // accidentally close the app by closing all the windows.
         let handler = Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler);
         let defaultArgs = handler.defaultArgs;
         let features = "chrome,all,dialog=no," + this.privateStateForNewWindow;
         let newWindow = existingWindow.openDialog("chrome://browser/content/", "_blank",
                                                   features, defaultArgs);
 
-        // Then close all those windows we checked:
+        // Window creation and destruction is asynchronous. We need to wait
+        // until all existing windows are fully closed, and the new window is
+        // fully open, before continuing. Otherwise the rest of the sanitizer
+        // could run too early (and miss new cookies being set when a page
+        // closes) and/or run too late (and not have a fully-formed window yet
+        // in existence). See bug 1088137.
+        let newWindowOpened = false;
+        function onWindowOpened(subject, topic, data) {
+          if (subject != newWindow)
+            return;
+
+          Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
+          newWindowOpened = true;
+          // If we're the last thing to happen, invoke callback.
+          if (numWindowsClosing == 0)
+            aCallback();
+        }
+
+        let numWindowsClosing = windowList.length;
+        function onWindowClosed() {
+          numWindowsClosing--;
+          if (numWindowsClosing == 0) {
+            Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
+            // If we're the last thing to happen, invoke callback.
+            if (newWindowOpened)
+              aCallback();
+          }
+        }
+
+        Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
+        Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
+
+        // Start the process of closing windows
         while (windowList.length) {
           windowList.pop().close();
         }
         newWindow.focus();
         return true;
       },
 
       get canClear()
--- a/browser/base/content/test/general/browser_bug520538.js
+++ b/browser/base/content/test/general/browser_bug520538.js
@@ -1,15 +1,15 @@
 function test() {
   var tabCount = gBrowser.tabs.length;
   gBrowser.selectedBrowser.focus();
   browserDOMWindow.openURI(makeURI("about:blank"),
                            null,
                            Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
                            Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
   is(gBrowser.tabs.length, tabCount + 1,
-     "'-new-tab about:blank' opens a new tab");
+     "'--new-tab about:blank' opens a new tab");
   is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
-     "'-new-tab about:blank' selects the new tab");
+     "'--new-tab about:blank' selects the new tab");
   is(document.activeElement, gURLBar.inputField,
-     "'-new-tab about:blank' focuses the location bar");
+     "'--new-tab about:blank' focuses the location bar");
   gBrowser.removeCurrentTab();
 }
--- a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -87,17 +87,17 @@ function test() {
       // Select "Save Image As" option from context menu
       var saveVideoCommand = aWindow.document.getElementById("context-saveimage");
       saveVideoCommand.doCommand();
 
       event.target.hidePopup();
     }
 
     aWindow.gBrowser.addEventListener("pageshow", function pageShown(event) {
-      // If data: -url PAC file isn't loaded soon enough, we may get about:privatebrowsing loaded
+      // If data: --url PAC file isn't loaded soon enough, we may get about:privatebrowsing loaded
       if (event.target.location == "about:blank" ||
           event.target.location == "about:privatebrowsing") {
         aWindow.gBrowser.selectedBrowser.loadURI(testURI);
         return;
       }
       aWindow.gBrowser.removeEventListener("pageshow", pageShown);
 
       waitForFocus(function () {
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -72,16 +72,17 @@ browser.jar:
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/chatWindow.xul                (content/chatWindow.xul)
         content/browser/content.js                    (content/content.js)
+        content/browser/content-UITour.js             (content/content-UITour.js)
         content/browser/defaultthemes/1.footer.jpg    (content/defaultthemes/1.footer.jpg)
         content/browser/defaultthemes/1.header.jpg    (content/defaultthemes/1.header.jpg)
         content/browser/defaultthemes/1.icon.jpg      (content/defaultthemes/1.icon.jpg)
         content/browser/defaultthemes/1.preview.jpg   (content/defaultthemes/1.preview.jpg)
         content/browser/defaultthemes/2.footer.jpg    (content/defaultthemes/2.footer.jpg)
         content/browser/defaultthemes/2.header.jpg    (content/defaultthemes/2.header.jpg)
         content/browser/defaultthemes/2.icon.jpg      (content/defaultthemes/2.icon.jpg)
         content/browser/defaultthemes/2.preview.jpg   (content/defaultthemes/2.preview.jpg)
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -43,17 +43,17 @@ const NS_ERROR_ABORT = Components.result
 const URI_INHERITS_SECURITY_CONTEXT = Components.interfaces.nsIHttpProtocolHandler
                                         .URI_INHERITS_SECURITY_CONTEXT;
 
 function shouldLoadURI(aURI) {
   if (aURI && !aURI.schemeIs("chrome"))
     return true;
 
   dump("*** Preventing external load of chrome: URI into browser window\n");
-  dump("    Use -chrome <uri> instead\n");
+  dump("    Use --chrome <uri> instead\n");
   return false;
 }
 
 function resolveURIInternal(aCmdLine, aArgument) {
   var uri = aCmdLine.resolveURI(aArgument);
   var urifixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
                            .getService(nsIURIFixup);
 
@@ -451,26 +451,26 @@ nsBrowserContentHandler.prototype = {
 
         searchParam = param.substr(2);
         doSearch(searchParam, cmdLine);
       }
     }
 #endif
   },
 
-  helpInfo : "  -browser           Open a browser window.\n" +
-             "  -new-window  <url> Open <url> in a new window.\n" +
-             "  -new-tab     <url> Open <url> in a new tab.\n" +
-             "  -private-window <url> Open <url> in a new private window.\n" +
+  helpInfo : "  --browser          Open a browser window.\n" +
+             "  --new-window <url> Open <url> in a new window.\n" +
+             "  --new-tab <url>    Open <url> in a new tab.\n" +
+             "  --private-window <url> Open <url> in a new private window.\n" +
 #ifdef XP_WIN
-             "  -preferences       Open Options dialog.\n" +
+             "  --preferences      Open Options dialog.\n" +
 #else
-             "  -preferences       Open Preferences dialog.\n" +
+             "  --preferences      Open Preferences dialog.\n" +
 #endif
-             "  -search     <term> Search <term> with your default search engine.\n",
+             "  --search <term>    Search <term> with your default search engine.\n",
 
   /* nsIBrowserHandler */
 
   get defaultArgs() {
     var prefb = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(nsIPrefBranch);
 
     if (!gFirstWindow) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -12,16 +12,19 @@ const XULNS = "http://www.mozilla.org/ke
 const POLARIS_ENABLED = "browser.polaris.enabled";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "UITour",
+                                  "resource:///modules/UITour.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
                                   "resource:///modules/ContentClick.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
                                   "resource:///modules/DirectoryLinksProvider.jsm");
@@ -2548,8 +2551,17 @@ let E10SUINotification = {
 
     win.PopupNotifications.show(browser, "a11y_enabled_with_e10s", promptMessage, null, mainAction, secondaryActions, options);
   },
 };
 #endif
 
 var components = [BrowserGlue, ContentPermissionPrompt];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
+
+
+// Listen for UITour messages.
+// Do it here instead of the UITour module itself so that the UITour module is lazy loaded
+// when the first message is received.
+let globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+globalMM.addMessageListener("UITour:onPageEvent", function(aMessage) {
+  UITour.onPageEvent(aMessage, aMessage.data);
+});
--- a/browser/components/shell/nsSetDefaultBrowser.js
+++ b/browser/components/shell/nsSetDefaultBrowser.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* 
- * -setDefaultBrowser commandline handler
+ * --setDefaultBrowser commandline handler
  * Makes the current executable the "default browser".
  */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function nsSetDefaultBrowser() {}
@@ -17,15 +17,15 @@ nsSetDefaultBrowser.prototype = {
   handle: function nsSetDefault_handle(aCmdline) {
     if (aCmdline.handleFlag("setDefaultBrowser", false)) {
       var shell = Cc["@mozilla.org/browser/shell-service;1"].
                   getService(Ci.nsIShellService);
       shell.setDefaultBrowser(true, true);
     }
   },
 
-  helpInfo: "  -setDefaultBrowser Set this app as the default browser.\n",
+  helpInfo: "  --setDefaultBrowser Set this app as the default browser.\n",
 
   classID: Components.ID("{F57899D0-4E2C-4ac6-9E29-50C736103B0C}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSetDefaultBrowser]);
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -71,16 +71,17 @@ support-files =
   doc_minified_bogus_map.html
   doc_native-event-handler.html
   doc_no-page-sources.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
   doc_pretty-print-3.html
   doc_pretty-print-on-paused.html
+  doc_promise.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_same-line-functions.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_scope-variable-4.html
   doc_script-switching-01.html
@@ -273,16 +274,17 @@ skip-if = os == "linux" || e10s # Bug 88
 [browser_dbg_tracing-06.js]
 [browser_dbg_tracing-07.js]
 [browser_dbg_tracing-08.js]
 [browser_dbg_variables-view-01.js]
 [browser_dbg_variables-view-02.js]
 [browser_dbg_variables-view-03.js]
 [browser_dbg_variables-view-04.js]
 [browser_dbg_variables-view-05.js]
+[browser_dbg_variables-view-06.js]
 [browser_dbg_variables-view-accessibility.js]
 [browser_dbg_variables-view-data.js]
 [browser_dbg_variables-view-edit-cancel.js]
 [browser_dbg_variables-view-edit-click.js]
 skip-if = (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
 [browser_dbg_variables-view-edit-getset-01.js]
 [browser_dbg_variables-view-edit-getset-02.js]
 [browser_dbg_variables-view-edit-value.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-06.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Promises get their internal state added as psuedo properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_promise.html";
+
+const test = Task.async(function* () {
+  const [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+  yield ensureSourceIs(panel, "doc_promise.html", true);
+
+  const scopes = waitForCaretAndScopes(panel, 21);
+  executeSoon(debuggee.doPause);
+  yield scopes;
+
+  const variables = panel.panelWin.DebuggerView.Variables;
+  ok(variables, "Should get the variables view.");
+
+  const scope = [...variables][0];
+  ok(scope, "Should get the current function's scope.");
+
+  const promiseVariables = [...scope].filter(([name]) =>
+    ["p", "f", "r"].indexOf(name) !== -1);
+
+  is(promiseVariables.length, 3,
+     "Should have our 3 promise variables: p, f, r");
+
+  for (let [name, item] of promiseVariables) {
+    info("Expanding variable '" + name + "'");
+    let expanded = once(variables, "fetched");
+    item.expand();
+    yield expanded;
+
+    let foundState = false;
+    switch (name) {
+      case "p":
+        for (let [property, { value }] of item) {
+          if (property !== "<state>") {
+            isnot(property, "<value>",
+                  "A pending promise shouldn't have a value");
+            isnot(property, "<reason>",
+                  "A pending promise shouldn't have a reason");
+            continue;
+          }
+
+          foundState = true;
+          is(value, "pending", "The state should be pending.");
+        }
+        ok(foundState, "We should have found the <state> property.");
+        break;
+
+      case "f":
+        let foundValue = false;
+        for (let [property, value] of item) {
+          if (property === "<state>") {
+            foundState = true;
+            is(value.value, "fulfilled", "The state should be fulfilled.");
+          } else if (property === "<value>") {
+            foundValue = true;
+
+            let expanded = once(variables, "fetched");
+            value.expand();
+            yield expanded;
+
+            let expectedProps = new Map([["a", 1], ["b", 2], ["c", 3]]);
+            for (let [prop, val] of value) {
+              if (prop === "__proto__") {
+                continue;
+              }
+              ok(expectedProps.has(prop), "The property should be expected.");
+              is(val.value, expectedProps.get(prop), "The property value should be correct.");
+              expectedProps.delete(prop);
+            }
+            is(Object.keys(expectedProps).length, 0,
+               "Should have found all of the expected properties.");
+          } else {
+            isnot(property, "<reason>",
+                  "A fulfilled promise shouldn't have a reason");
+          }
+        }
+        ok(foundState, "We should have found the <state> property.");
+        ok(foundValue, "We should have found the <value> property.");
+        break;
+
+      case "r":
+        let foundReason = false;
+        for (let [property, value] of item) {
+          if (property === "<state>") {
+            foundState = true;
+            is(value.value, "rejected", "The state should be rejected.");
+          } else if (property === "<reason>") {
+            foundReason = true;
+
+            let expanded = once(variables, "fetched");
+            value.expand();
+            yield expanded;
+
+            let foundMessage = false;
+            for (let [prop, val] of value) {
+              if (prop !== "message") {
+                continue;
+              }
+              foundMessage = true;
+              is(val.value, "uh oh", "Should have the correct error message.");
+            }
+            ok(foundMessage, "Should have found the error's message");
+          } else {
+            isnot(property, "<value>",
+                  "A rejected promise shouldn't have a value");
+          }
+        }
+        ok(foundState, "We should have found the <state> property.");
+        break;
+    }
+  }
+
+  debugger;
+
+  resumeDebuggerThenCloseAndFinish(panel);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_promise.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger + Promise test page</title>
+  </head>
+
+  <body>
+    <script>
+    window.pending   = new Promise(function () {});
+    window.fulfilled = Promise.resolve({ a: 1, b: 2, c: 3 });
+    window.rejected  = Promise.reject(new Error("uh oh"));
+
+    window.doPause = function () {
+      var p = window.pending;
+      var f = window.fulfilled;
+      var r = window.rejected;
+      debugger;
+    };
+
+    // Attach an error handler so that the logs don't have a warning about an
+    // unhandled, rejected promise.
+    window.rejected.then(null, function () {});
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/devtools-clhandler.js
+++ b/browser/devtools/devtools-clhandler.js
@@ -62,16 +62,16 @@ devtoolsCommandlineHandler.prototype = {
       dump(errorMsg + "\n");
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
-  helpInfo : "  -jsconsole         Open the Browser Console.\n" +
-             "  -jsdebugger        Open the Browser Toolbox.\n",
+  helpInfo : "  --jsconsole        Open the Browser Console.\n" +
+             "  --jsdebugger       Open the Browser Toolbox.\n",
 
   classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([devtoolsCommandlineHandler]);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/unit/test_VariablesView_getString_promise.js
@@ -0,0 +1,75 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Cu = Components.utils;
+const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const { VariablesView } = Cu.import("resource:///modules/devtools/VariablesView.jsm", {});
+
+const PENDING = {
+  "type": "object",
+  "class": "Promise",
+  "actor": "conn0.pausedobj35",
+  "extensible": true,
+  "frozen": false,
+  "sealed": false,
+  "promiseState": {
+    "state": "pending"
+  },
+  "preview": {
+    "kind": "Object",
+    "ownProperties": {},
+    "ownPropertiesLength": 0,
+    "safeGetterValues": {}
+  }
+};
+
+const FULFILLED = {
+  "type": "object",
+  "class": "Promise",
+  "actor": "conn0.pausedobj35",
+  "extensible": true,
+  "frozen": false,
+  "sealed": false,
+  "promiseState": {
+    "state": "fulfilled",
+    "value": 10
+  },
+  "preview": {
+    "kind": "Object",
+    "ownProperties": {},
+    "ownPropertiesLength": 0,
+    "safeGetterValues": {}
+  }
+};
+
+const REJECTED = {
+  "type": "object",
+  "class": "Promise",
+  "actor": "conn0.pausedobj35",
+  "extensible": true,
+  "frozen": false,
+  "sealed": false,
+  "promiseState": {
+    "state": "rejected",
+    "reason": 10
+  },
+  "preview": {
+    "kind": "Object",
+    "ownProperties": {},
+    "ownPropertiesLength": 0,
+    "safeGetterValues": {}
+  }
+};
+
+function run_test() {
+  equal(VariablesView.getString(PENDING, { concise: true }), "Promise");
+  equal(VariablesView.getString(PENDING),                    'Promise {<state>: "pending"}');
+
+  equal(VariablesView.getString(FULFILLED, { concise: true }), "Promise");
+  equal(VariablesView.getString(FULFILLED),                    'Promise {<state>: "fulfilled", <value>: 10}');
+
+  equal(VariablesView.getString(REJECTED, { concise: true }), "Promise");
+  equal(VariablesView.getString(REJECTED),                    'Promise {<state>: "rejected", <reason>: 10}');
+}
--- a/browser/devtools/shared/test/unit/xpcshell.ini
+++ b/browser/devtools/shared/test/unit/xpcshell.ini
@@ -2,8 +2,9 @@
 head =
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_bezierCanvas.js]
 [test_cubicBezier.js]
 [test_undoStack.js]
+[test_VariablesView_getString_promise.js]
\ No newline at end of file
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -3570,16 +3570,27 @@ VariablesView.stringifiers.byObjectKind 
   // Stringifier for any kind of object.
   Object: function(aGrip, {concise}) {
     if (concise) {
       return aGrip.class;
     }
 
     let {preview} = aGrip;
     let props = [];
+
+    if (aGrip.class == "Promise" && aGrip.promiseState) {
+      let { state, value, reason } = aGrip.promiseState;
+      props.push("<state>: " + VariablesView.getString(state));
+      if (state == "fulfilled") {
+        props.push("<value>: " + VariablesView.getString(value, { concise: true }));
+      } else if (state == "rejected") {
+        props.push("<reason>: " + VariablesView.getString(reason, { concise: true }));
+      }
+    }
+
     for (let key of Object.keys(preview.ownProperties || {})) {
       let value = preview.ownProperties[key];
       let valueString = "";
       if (value.get) {
         valueString = "Getter";
       } else if (value.set) {
         valueString = "Setter";
       } else {
--- a/browser/devtools/shared/widgets/VariablesViewController.jsm
+++ b/browser/devtools/shared/widgets/VariablesViewController.jsm
@@ -165,16 +165,26 @@ VariablesViewController.prototype = {
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The grip to use to populate the target.
    */
   _populateFromObject: function(aTarget, aGrip) {
     let deferred = promise.defer();
 
+    if (aGrip.class === "Promise" && aGrip.promiseState) {
+      const { state, value, reason } = aGrip.promiseState;
+      aTarget.addItem("<state>", { value: state });
+      if (state === "fulfilled") {
+        this.addExpander(aTarget.addItem("<value>", { value }), value);
+      } else if (state === "rejected") {
+        this.addExpander(aTarget.addItem("<reason>", { value: reason }), reason);
+      }
+    }
+
     let objectClient = this._getObjectClient(aGrip);
     objectClient.getPrototypeAndProperties(aResponse => {
       let { ownProperties, prototype } = aResponse;
       // 'safeGetterValues' is new and isn't necessary defined on old actors.
       let safeGetterValues = aResponse.safeGetterValues || {};
       let sortable = VariablesView.isSortable(aGrip.class);
 
       // Merge the safe getter values into one object such that we can use it
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -1167,28 +1167,26 @@ Messages.Extended.prototype = Heritage.e
    * @param object options
    *        Options to use for displaying the ObjectActor.
    * @see this._renderValueGrip for the available options.
    * @return DOMElement
    *         The DOM element that displays the object actor.
    */
   _renderObjectActor: function(objectActor, options = {})
   {
-    let widget = null;
-    let {preview} = objectActor;
-
-    if (preview && preview.kind) {
+    let widget = Widgets.ObjectRenderers.byClass[objectActor.class];
+
+    let { preview } = objectActor;
+    if ((!widget || (widget.canRender && !widget.canRender(objectActor)))
+        && preview
+        && preview.kind) {
       widget = Widgets.ObjectRenderers.byKind[preview.kind];
     }
 
     if (!widget || (widget.canRender && !widget.canRender(objectActor))) {
-      widget = Widgets.ObjectRenderers.byClass[objectActor.class];
-    }
-
-    if (!widget || (widget.canRender && !widget.canRender(objectActor))) {
       widget = Widgets.JSObject;
     }
 
     let instance = new widget(this, objectActor, options).render();
     return instance.element;
   },
 }); // Messages.Extended.prototype
 
@@ -2295,16 +2293,128 @@ Widgets.JSObject.prototype = Heritage.ex
     if (!className && this.objectActor.class == "Object") {
       className = "cm-variable";
     }
 
     this.element = this._anchor(str, { className: className });
   },
 
   /**
+   * Render a concise representation of an object.
+   */
+  _renderConciseObject: function()
+  {
+    this.element = this._anchor(this.objectActor.class,
+                                { className: "cm-variable" });
+  },
+
+  /**
+   * Render the `<class> { ` prefix of an object.
+   */
+  _renderObjectPrefix: function()
+  {
+    let { kind } = this.objectActor.preview;
+    this.element = this.el("span.kind-" + kind);
+    this._anchor(this.objectActor.class, { className: "cm-variable" });
+    this._text(" { ");
+  },
+
+  /**
+   * Render the ` }` suffix of an object.
+   */
+  _renderObjectSuffix: function()
+  {
+    this._text(" }");
+  },
+
+  /**
+   * Render an object property.
+   *
+   * @param String key
+   *        The property name.
+   * @param Object value
+   *        The property value, as an RDP grip.
+   * @param nsIDOMNode container
+   *        The container node to render to.
+   * @param Boolean needsComma
+   *        True if there was another property before this one and we need to
+   *        separate them with a comma.
+   * @param Boolean valueIsText
+   *        Add the value as is, don't treat it as a grip and pass it to
+   *        `_renderValueGrip`.
+   */
+  _renderObjectProperty: function(key, value, container, needsComma, valueIsText = false)
+  {
+    if (needsComma) {
+      this._text(", ");
+    }
+
+    container.appendChild(this.el("span.cm-property", key));
+    this._text(": ");
+
+    if (valueIsText) {
+      this._text(value);
+    } else {
+      let valueElem = this.message._renderValueGrip(value, { concise: true });
+      container.appendChild(valueElem);
+    }
+  },
+
+  /**
+   * Render this object's properties.
+   *
+   * @param nsIDOMNode container
+   *        The container node to render to.
+   * @param Boolean needsComma
+   *        True if there was another property before this one and we need to
+   *        separate them with a comma.
+   */
+  _renderObjectProperties: function(container, needsComma)
+  {
+    let { preview } = this.objectActor;
+    let { ownProperties, safeGetterValues } = preview;
+
+    let shown = 0;
+
+    let getValue = desc => {
+      if (desc.get) {
+        return "Getter";
+      } else if (desc.set) {
+        return "Setter";
+      } else {
+        return desc.value;
+      }
+    };
+
+    for (let key of Object.keys(ownProperties || {})) {
+      this._renderObjectProperty(key, getValue(ownProperties[key]), container,
+                                 shown > 0 || needsComma,
+                                 ownProperties[key].get || ownProperties[key].set);
+      shown++;
+    }
+
+    let ownPropertiesShown = shown;
+
+    for (let key of Object.keys(safeGetterValues || {})) {
+      this._renderObjectProperty(key, safeGetterValues[key].getterValue,
+                                 container, shown > 0 || needsComma);
+      shown++;
+    }
+
+    if (typeof preview.ownPropertiesLength == "number" &&
+        ownPropertiesShown < preview.ownPropertiesLength) {
+      this._text(", ");
+
+      let n = preview.ownPropertiesLength - ownPropertiesShown;
+      let str = VariablesView.stringifiers._getNMoreString(n);
+      this._anchor(str);
+    }
+  },
+
+  /**
    * Render an anchor with a given text content and link.
    *
    * @private
    * @param string text
    *        Text to show in the anchor.
    * @param object [options]
    *        Available options:
    *        - onClick (function): "click" event handler.By default a click on
@@ -3081,90 +3191,68 @@ Widgets.ObjectRenderers.add({
       this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, true);
       this.toolbox = null;
       this._nodeFront = null;
     }
   },
 }); // Widgets.ObjectRenderers.byKind.DOMNode
 
 /**
+ * The widget user for displaying Promise objects.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "Promise",
+
+  render: function()
+  {
+    let { ownProperties, safeGetterValues } = this.objectActor.preview;
+    if ((!ownProperties && !safeGetterValues) || this.options.concise) {
+      this._renderConciseObject();
+      return;
+    }
+
+    this._renderObjectPrefix();
+    let container = this.element;
+    let addedPromiseInternalProps = false;
+
+    if (this.objectActor.promiseState) {
+      const { state, value, reason } = this.objectActor.promiseState;
+
+      this._renderObjectProperty("<state>", state, container, false);
+      addedPromiseInternalProps = true;
+
+      if (state == "fulfilled") {
+        this._renderObjectProperty("<value>", value, container, true);
+      } else if (state == "rejected") {
+        this._renderObjectProperty("<reason>", reason, container, true);
+      }
+    }
+
+    this._renderObjectProperties(container, addedPromiseInternalProps);
+    this._renderObjectSuffix();
+  }
+}); // Widgets.ObjectRenderers.byClass.Promise
+
+/**
  * The widget used for displaying generic JS object previews.
  */
 Widgets.ObjectRenderers.add({
   byKind: "Object",
 
   render: function()
   {
-    let {preview} = this.objectActor;
-    let {ownProperties, safeGetterValues} = preview;
-
+    let { ownProperties, safeGetterValues } = this.objectActor.preview;
     if ((!ownProperties && !safeGetterValues) || this.options.concise) {
-      this.element = this._anchor(this.objectActor.class,
-                                  { className: "cm-variable" });
+      this._renderConciseObject();
       return;
     }
 
-    let container = this.element = this.el("span.kind-" + preview.kind);
-    this._anchor(this.objectActor.class, { className: "cm-variable" });
-    this._text(" { ");
-
-    let addProperty = (str) => {
-      container.appendChild(this.el("span.cm-property", str));
-    };
-
-    let shown = 0;
-    for (let key of Object.keys(ownProperties || {})) {
-      if (shown > 0) {
-        this._text(", ");
-      }
-
-      let value = ownProperties[key];
-
-      addProperty(key);
-      this._text(": ");
-
-      if (value.get) {
-        addProperty("Getter");
-      } else if (value.set) {
-        addProperty("Setter");
-      } else {
-        let valueElem = this.message._renderValueGrip(value.value, { concise: true });
-        container.appendChild(valueElem);
-      }
-
-      shown++;
-    }
-
-    let ownPropertiesShown = shown;
-
-    for (let key of Object.keys(safeGetterValues || {})) {
-      if (shown > 0) {
-        this._text(", ");
-      }
-
-      addProperty(key);
-      this._text(": ");
-
-      let value = safeGetterValues[key].getterValue;
-      let valueElem = this.message._renderValueGrip(value, { concise: true });
-      container.appendChild(valueElem);
-
-      shown++;
-    }
-
-    if (typeof preview.ownPropertiesLength == "number" &&
-        ownPropertiesShown < preview.ownPropertiesLength) {
-      this._text(", ");
-
-      let n = preview.ownPropertiesLength - ownPropertiesShown;
-      let str = VariablesView.stringifiers._getNMoreString(n);
-      this._anchor(str);
-    }
-
-    this._text(" }");
+    this._renderObjectPrefix();
+    this._renderObjectProperties(this.element, false);
+    this._renderObjectSuffix();
   },
 }); // Widgets.ObjectRenderers.byKind.Object
 
 /**
  * The long string widget.
  *
  * @constructor
  * @param object message
--- a/browser/devtools/webconsole/test/browser_webconsole_output_05.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_05.js
@@ -79,16 +79,36 @@ let inputTests = [
   // 9
   {
     input: "new String('hello')",
     output: 'String [ "h", "e", "l", "l", "o" ]',
     printOutput: "hello",
     inspectable: true,
     variablesViewLabel: "String[5]"
   },
+
+  // 9
+  {
+    // XXX: Can't test fulfilled and rejected promises, because promises get
+    // settled on the next tick of the event loop.
+    input: "new Promise(function () {})",
+    output: 'Promise { <state>: "pending" }',
+    printOutput: "[object Promise]",
+    inspectable: true,
+    variablesViewLabel: "Promise"
+  },
+
+  // 10
+  {
+    input: "(function () { var p = new Promise(function () {}); p.foo = 1; return p; }())",
+    output: 'Promise { <state>: "pending", foo: 1 }',
+    printOutput: "[object Promise]",
+    inspectable: true,
+    variablesViewLabel: "Promise"
+  }
 ];
 
 function test() {
   Task.spawn(function*() {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
     return checkOutputForInputs(hud, inputTests);
   }).then(finishUp);
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -941,16 +941,18 @@ function openDebugger(aOptions = {})
  *         if any of the following properties are present in the rule:
  *         |consoleTrace| or |objects|.
  *         - longStrings: a list of long string ellipsis elements you can click
  *         in the message element, to expand a long string. This is available
  *         only if |longString| is present in the matching rule.
  */
 function waitForMessages(aOptions)
 {
+  info("Waiting for messages...");
+
   gPendingOutputTest++;
   let webconsole = aOptions.webconsole;
   let rules = WebConsoleUtils.cloneObject(aOptions.messages, true);
   let rulesMatched = 0;
   let listenerAdded = false;
   let deferred = promise.defer();
   aOptions.matchCondition = aOptions.matchCondition || "all";
 
@@ -1468,16 +1470,17 @@ function checkOutputForInputs(hud, input
   {
     yield checkConsoleLog(entry);
     yield checkPrintOutput(entry);
     yield checkJSEval(entry);
   }
 
   function* checkConsoleLog(entry)
   {
+    info("Logging: " + entry.input);
     hud.jsterm.clearOutput();
     hud.jsterm.execute("console.log(" + entry.input + ")");
 
     let [result] = yield waitForMessages({
       webconsole: hud,
       messages: [{
         name: "console.log() output: " + entry.output,
         text: entry.output,
@@ -1489,16 +1492,17 @@ function checkOutputForInputs(hud, input
     if (typeof entry.inspectorIcon == "boolean") {
       let msg = [...result.matched][0];
       yield checkLinkToInspector(entry, msg);
     }
   }
 
   function checkPrintOutput(entry)
   {
+    info("Printing: " + entry.input);
     hud.jsterm.clearOutput();
     hud.jsterm.execute("print(" + entry.input + ")");
 
     let printOutput = entry.printOutput || entry.output;
 
     return waitForMessages({
       webconsole: hud,
       messages: [{
@@ -1506,16 +1510,17 @@ function checkOutputForInputs(hud, input
         text: printOutput,
         category: CATEGORY_OUTPUT,
       }],
     });
   }
 
   function* checkJSEval(entry)
   {
+    info("Evaluating: " + entry.input);
     hud.jsterm.clearOutput();
     hud.jsterm.execute(entry.input);
 
     let [result] = yield waitForMessages({
       webconsole: hud,
       messages: [{
         name: "JS eval output: " + entry.output,
         text: entry.output,
@@ -1529,16 +1534,17 @@ function checkOutputForInputs(hud, input
     }
     if (typeof entry.inspectorIcon == "boolean") {
       yield checkLinkToInspector(entry, msg);
     }
   }
 
   function* checkObjectClick(entry, msg)
   {
+    info("Clicking: " + entry.input);
     let body = msg.querySelector(".message-body a") ||
                msg.querySelector(".message-body");
     ok(body, "the message body");
 
     let deferredVariablesView = promise.defer();
     entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferredVariablesView);
     hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
 
@@ -1564,16 +1570,17 @@ function checkOutputForInputs(hud, input
       entry._onTabOpen = null;
     }
 
     yield promise.resolve(null);
   }
 
   function checkLinkToInspector(entry, msg)
   {
+    info("Checking Inspector Link: " + entry.input);
     let elementNodeWidget = [...msg._messageObject.widgets][0];
     if (!elementNodeWidget) {
       ok(!entry.inspectorIcon, "The message has no ElementNode widget");
       return;
     }
 
     return elementNodeWidget.linkToInspector().then(() => {
       // linkToInspector resolved, check for the .open-inspector element
@@ -1587,16 +1594,17 @@ function checkOutputForInputs(hud, input
     }, () => {
       // linkToInspector promise rejected, node not linked to inspector
       ok(!entry.inspectorIcon, "The ElementNode widget isn't linked to the inspector");
     });
   }
 
   function onVariablesViewOpen(entry, {resolve, reject}, event, view, options)
   {
+    info("Variables view opened: " + entry.input);
     let label = entry.variablesViewLabel || entry.output;
     if (typeof label == "string" && options.label != label) {
       return;
     }
     if (label instanceof RegExp && !label.test(options.label)) {
       return;
     }
 
--- a/browser/devtools/webide/components/webideCli.js
+++ b/browser/devtools/webide/components/webideCli.js
@@ -5,33 +5,33 @@
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
 /**
- * Handles -webide command line option.
+ * Handles --webide command line option.
  */
 
 function webideCli() { }
 
 webideCli.prototype = {
   handle: function(cmdLine) {
     let param;
 
     if (!cmdLine.handleFlag("webide", false)) {
       return;
     }
 
-    // If -webide is used remotely, we don't want to open
+    // If --webide is used remotely, we don't want to open
     // a new tab.
     //
-    // If -webide is used for a new Firefox instance, we
+    // If --webide is used for a new Firefox instance, we
     // want to open webide only.
     cmdLine.preventDefault = true;
 
     let win = Services.wm.getMostRecentWindow("devtools:webide");
     if (win) {
       win.focus();
     } else {
       win = Services.ww.openWindow(null,
--- a/browser/metro/components/BrowserCLH.js
+++ b/browser/metro/components/BrowserCLH.js
@@ -132,20 +132,20 @@ BrowserCLH.prototype = {
   handle: function fs_handle(aCmdLine) {
 #ifdef DEBUG
     for (var idx = 0; idx <  aCmdLine.length; idx++) {
       dump(aCmdLine.getArgument(idx) + "\n");
     }
 #endif
     // Instantiate the search service so the search engine cache is created now
     // instead when the application is running. The install process will register
-    // this component by using the -silent command line flag, thereby creating
+    // this component by using the --silent command line flag, thereby creating
     // the cache during install, not runtime.
     // NOTE: This code assumes this CLH is run before the nsDefaultCLH, which
-    // consumes the "-silent" flag.
+    // consumes the "--silent" flag.
     if (aCmdLine.findFlag("silent", false) > -1) {
       let searchService = Services.search;
       let autoComplete = Cc["@mozilla.org/autocomplete/search;1?name=history"].
                          getService(Ci.nsIAutoCompleteSearch);
       return;
     }
 
     // Handle chrome windows loaded via commandline
--- a/browser/metro/components/SessionStore.js
+++ b/browser/metro/components/SessionStore.js
@@ -621,17 +621,17 @@ SessionStore.prototype = {
     return data;
   },
 
   _collectTabData: function ss__collectTabData(aBrowser, aHistory) {
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
-    let aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
+    aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
 
     let tabData = {};
     tabData.entries = aHistory.entries;
     tabData.index = aHistory.index;
     tabData.attributes = { image: aBrowser.mIconURL };
 
     aBrowser.__SS_data = tabData;
   },
--- a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
+++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
@@ -611,17 +611,17 @@ void LaunchDesktopBrowserWithParams(CStr
     // Fallback to the module path if it failed to get the default browser.
     GetDefaultBrowserPath(aBrowserPath);
     params += "-url ";
     params += "\"";
     params += aTarget;
     params += "\"";
   }
 
-  // Tack on any extra parameters we received (for example -profilemanager)
+  // Tack on any extra parameters we received (for example --profilemanager)
   if (!aParameters.IsEmpty()) {
     params += " ";
     params += aParameters;
   }
 
   Log(L"Desktop Launch: verb:'%s' exe:'%s' params:'%s'", aVerb, aBrowserPath, params);
 
   // Relaunch in Desktop mode uses a special URL to trick Windows into
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -20,18 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 
 
-const UITOUR_PERMISSION   = "uitour";
-const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
 const MAX_BUTTONS         = 4;
 
 const BUCKET_NAME         = "UITour";
 const BUCKET_TIMESTEPS    = [
   1 * 60 * 1000, // Until 1 minute after tab is closed/inactive.
   3 * 60 * 1000, // Until 3 minutes after tab is closed/inactive.
   10 * 60 * 1000, // Until 10 minutes after tab is closed/inactive.
@@ -217,54 +215,51 @@ this.UITour = {
       Services.prefs.clearUserPref(PREF_SEENPAGEIDS);
       return;
     }
 
     Services.prefs.setCharPref(PREF_SEENPAGEIDS,
                                JSON.stringify([...this.seenPageIDs]));
   },
 
-  onPageEvent: function(aEvent) {
+  onPageEvent: function(aMessage, aEvent) {
     let contentDocument = null;
-    if (aEvent.target instanceof Ci.nsIDOMHTMLDocument)
-      contentDocument = aEvent.target;
-    else if (aEvent.target instanceof Ci.nsIDOMHTMLElement)
-      contentDocument = aEvent.target.ownerDocument;
-    else
-      return false;
 
-    // Ignore events if they're not from a trusted origin.
-    if (!this.ensureTrustedOrigin(contentDocument))
-      return false;
+    let browser = aMessage.target;
+    let window = browser.ownerDocument.defaultView;
+    let tab = window.gBrowser.getTabForBrowser(browser);
+    let messageManager = browser.messageManager;
 
     if (typeof aEvent.detail != "object")
       return false;
 
     let action = aEvent.detail.action;
     if (typeof action != "string" || !action)
       return false;
 
     let data = aEvent.detail.data;
     if (typeof data != "object")
       return false;
 
-    let window = this.getChromeWindow(contentDocument);
     // Do this before bailing if there's no tab, so later we can pick up the pieces:
     window.gBrowser.tabContainer.addEventListener("TabSelect", this);
-    let tab = window.gBrowser._getTabForContentWindow(contentDocument.defaultView);
-    if (!tab) {
-      // This should only happen while detaching a tab:
-      if (this._detachingTab) {
-        this._queuedEvents.push(aEvent);
-        this._pendingDoc = Cu.getWeakReference(contentDocument);
+
+    if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
+      contentDocument = browser.contentWindow.document;
+      if (!tab) {
+        // This should only happen while detaching a tab:
+        if (this._detachingTab) {
+          this._queuedEvents.push(aEvent);
+          this._pendingDoc = Cu.getWeakReference(contentDocument);
+          return;
+        }
+        Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
+                       "This shouldn't happen!");
         return;
       }
-      Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
-                     "This shouldn't happen!");
-      return;
     }
 
     switch (action) {
       case "registerPageID": {
         // This is only relevant if Telemtry is enabled.
         if (!UITelemetry.enabled)
           break;
 
@@ -310,31 +305,31 @@ this.UITour = {
         targetPromise.then(target => {
           if (!target.node) {
             Cu.reportError("UITour: Target could not be resolved: " + data.target);
             return;
           }
 
           let iconURL = null;
           if (typeof data.icon == "string")
-            iconURL = this.resolveURL(contentDocument, data.icon);
+            iconURL = this.resolveURL(browser, data.icon);
 
           let buttons = [];
           if (Array.isArray(data.buttons) && data.buttons.length > 0) {
             for (let buttonData of data.buttons) {
               if (typeof buttonData == "object" &&
                   typeof buttonData.label == "string" &&
                   typeof buttonData.callbackID == "string") {
                 let button = {
                   label: buttonData.label,
                   callbackID: buttonData.callbackID,
                 };
 
                 if (typeof buttonData.icon == "string")
-                  button.iconURL = this.resolveURL(contentDocument, buttonData.icon);
+                  button.iconURL = this.resolveURL(browser, buttonData.icon);
 
                 if (typeof buttonData.style == "string")
                   button.style = buttonData.style;
 
                 buttons.push(button);
 
                 if (buttons.length == MAX_BUTTONS)
                   break;
@@ -344,17 +339,17 @@ this.UITour = {
 
           let infoOptions = {};
 
           if (typeof data.closeButtonCallbackID == "string")
             infoOptions.closeButtonCallbackID = data.closeButtonCallbackID;
           if (typeof data.targetCallbackID == "string")
             infoOptions.targetCallbackID = data.targetCallbackID;
 
-          this.showInfo(contentDocument, target, data.title, data.text, iconURL, buttons, infoOptions);
+          this.showInfo(messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
         }).then(null, Cu.reportError);
         break;
       }
 
       case "hideInfo": {
         this.hideInfo(window);
         break;
       }
@@ -377,17 +372,17 @@ this.UITour = {
       case "removePinnedTab": {
         this.removePinnedTab(window);
         break;
       }
 
       case "showMenu": {
         this.showMenu(window, data.name, () => {
           if (typeof data.showCallbackID == "string")
-            this.sendPageCallback(contentDocument, data.showCallbackID);
+            this.sendPageCallback(messageManager, data.showCallbackID);
         });
         break;
       }
 
       case "hideMenu": {
         this.hideMenu(window, data.name);
         break;
       }
@@ -423,17 +418,17 @@ this.UITour = {
         break;
       }
 
       case "getConfiguration": {
         if (typeof data.configuration != "string") {
           return false;
         }
 
-        this.getConfiguration(contentDocument, data.configuration, data.callbackID);
+        this.getConfiguration(messageManager, window, data.configuration, data.callbackID);
         break;
       }
 
       case "showFirefoxAccounts": {
         // 'signup' is the only action that makes sense currently, so we don't
         // accept arbitrary actions just to be safe...
         // We want to replace the current tab.
         contentDocument.location.href = "about:accounts?action=signup&entrypoint=uitour";
@@ -445,29 +440,32 @@ this.UITour = {
         ResetProfile.openConfirmationDialog(window);
         break;
       }
 
       case "addNavBarWidget": {
         // Add a widget to the toolbar
         let targetPromise = this.getTarget(window, data.name);
         targetPromise.then(target => {
-          this.addNavBarWidget(target, contentDocument, data.callbackID);
+          this.addNavBarWidget(target, messageManager, data.callbackID);
         }).then(null, Cu.reportError);
         break;
       }
     }
 
-    if (!this.originTabs.has(window))
-      this.originTabs.set(window, new Set());
+    if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
+      if (!this.originTabs.has(window)) {
+        this.originTabs.set(window, new Set());
+      }
 
-    this.originTabs.get(window).add(tab);
-    tab.addEventListener("TabClose", this);
-    tab.addEventListener("TabBecomingWindow", this);
-    window.addEventListener("SSWindowClosing", this);
+      this.originTabs.get(window).add(tab);
+      tab.addEventListener("TabClose", this);
+      tab.addEventListener("TabBecomingWindow", this);
+      window.addEventListener("SSWindowClosing", this);
+    }
 
     return true;
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "pagehide": {
         let window = this.getChromeWindow(aEvent.target);
@@ -616,88 +614,44 @@ this.UITour = {
                            .getInterface(Ci.nsIWebNavigation)
                            .QueryInterface(Ci.nsIDocShellTreeItem)
                            .rootTreeItem
                            .QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindow)
                            .wrappedJSObject;
   },
 
-  isTestingOrigin: function(aURI) {
-    if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) {
-      return false;
-    }
-
-    // Add any testing origins (comma-seperated) to the whitelist for the session.
-    for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) {
-      try {
-        let testingURI = Services.io.newURI(origin, null, null);
-        if (aURI.prePath == testingURI.prePath) {
-          return true;
-        }
-      } catch (ex) {
-        Cu.reportError(ex);
-      }
-    }
-    return false;
-  },
-
-  ensureTrustedOrigin: function(aDocument) {
-    if (aDocument.defaultView.top != aDocument.defaultView)
-      return false;
-
-    let uri = aDocument.documentURIObject;
-
-    if (uri.schemeIs("chrome"))
-      return true;
-
-    if (!this.isSafeScheme(uri))
-      return false;
-
-    let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
-    if (permission == Services.perms.ALLOW_ACTION)
-      return true;
-
-    return this.isTestingOrigin(uri);
-  },
-
+  // This function is copied to UITourListener.
   isSafeScheme: function(aURI) {
     let allowedSchemes = new Set(["https", "about"]);
     if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
       allowedSchemes.add("http");
 
     if (!allowedSchemes.has(aURI.scheme))
       return false;
 
     return true;
   },
 
-  resolveURL: function(aDocument, aURL) {
+  resolveURL: function(aBrowser, aURL) {
     try {
-      let uri = Services.io.newURI(aURL, null, aDocument.documentURIObject);
+      let uri = Services.io.newURI(aURL, null, aBrowser.currentURI);
 
       if (!this.isSafeScheme(uri))
         return null;
 
       return uri.spec;
     } catch (e) {}
 
     return null;
   },
 
-  sendPageCallback: function(aDocument, aCallbackID, aData = {}) {
-
+  sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
     let detail = {data: aData, callbackID: aCallbackID};
-    detail = Cu.cloneInto(detail, aDocument.defaultView);
-    let event = new aDocument.defaultView.CustomEvent("mozUITourResponse", {
-      bubbles: true,
-      detail: detail
-    });
-
-    aDocument.dispatchEvent(event);
+    aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
   },
 
   isElementVisible: function(aElement) {
     let targetStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
     return (targetStyle.display != "none" && targetStyle.visibility == "visible");
   },
 
   getTarget: function(aWindow, aTargetName, aSticky = false) {
@@ -961,26 +915,26 @@ this.UITour = {
       for (let menuItem of searchPopup.children)
         menuItem.removeAttribute("_moz-menuactive");
     }
   },
 
   /**
    * Show an info panel.
    *
-   * @param {Document} aContentDocument
+   * @param {nsIMessageSender} aMessageManager
    * @param {Node}     aAnchor
    * @param {String}   [aTitle=""]
    * @param {String}   [aDescription=""]
    * @param {String}   [aIconURL=""]
    * @param {Object[]} [aButtons=[]]
    * @param {Object}   [aOptions={}]
    * @param {String}   [aOptions.closeButtonCallbackID]
    */
-  showInfo: function(aContentDocument, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
+  showInfo: function(aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
                      aButtons = [], aOptions = {}) {
     function showInfoPanel(aAnchorEl) {
       aAnchorEl.focus();
 
       let document = aAnchorEl.ownerDocument;
       let tooltip = document.getElementById("UITourTooltip");
       let tooltipTitle = document.getElementById("UITourTooltipTitle");
       let tooltipDesc = document.getElementById("UITourTooltipDescription");
@@ -1009,38 +963,38 @@ this.UITour = {
           el.setAttribute("class", "button-link");
 
         if (button.style == "primary")
           el.setAttribute("class", "button-primary");
 
         let callbackID = button.callbackID;
         el.addEventListener("command", event => {
           tooltip.hidePopup();
-          this.sendPageCallback(aContentDocument, callbackID);
+          this.sendPageCallback(aMessageManager, callbackID);
         });
 
         tooltipButtons.appendChild(el);
       }
 
       tooltipButtons.hidden = !aButtons.length;
 
       let tooltipClose = document.getElementById("UITourTooltipClose");
       let closeButtonCallback = (event) => {
         this.hideInfo(document.defaultView);
         if (aOptions && aOptions.closeButtonCallbackID)
-          this.sendPageCallback(aContentDocument, aOptions.closeButtonCallbackID);
+          this.sendPageCallback(aMessageManager, aOptions.closeButtonCallbackID);
       };
       tooltipClose.addEventListener("command", closeButtonCallback);
 
       let targetCallback = (event) => {
         let details = {
           target: aAnchor.targetName,
           type: event.type,
         };
-        this.sendPageCallback(aContentDocument, aOptions.targetCallbackID, details);
+        this.sendPageCallback(aMessageManager, aOptions.targetCallbackID, details);
       };
       if (aOptions.targetCallbackID && aAnchor.addTargetListener) {
         aAnchor.addTargetListener(document, targetCallback);
       }
 
       tooltip.addEventListener("popuphiding", function tooltipHiding(event) {
         tooltip.removeEventListener("popuphiding", tooltipHiding);
         tooltipClose.removeEventListener("command", closeButtonCallback);
@@ -1209,44 +1163,44 @@ this.UITour = {
 
     let tab = aWindow.gBrowser.addTab(url, {
       owner: aWindow.gBrowser.selectedTab,
       relatedToCurrent: true
     });
     aWindow.gBrowser.selectedTab = tab;
   },
 
-  getConfiguration: function(aContentDocument, aConfiguration, aCallbackID) {
+  getConfiguration: function(aMessageManager, aWindow, aConfiguration, aCallbackID) {
     switch (aConfiguration) {
       case "availableTargets":
-        this.getAvailableTargets(aContentDocument, aCallbackID);
+        this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
         break;
       case "sync":
-        this.sendPageCallback(aContentDocument, aCallbackID, {
+        this.sendPageCallback(aMessageManager, aCallbackID, {
           setup: Services.prefs.prefHasUserValue("services.sync.username"),
         });
         break;
       case "appinfo":
         let props = ["defaultUpdateChannel", "version"];
         let appinfo = {};
         props.forEach(property => appinfo[property] = Services.appinfo[property]);
-        this.sendPageCallback(aContentDocument, aCallbackID, appinfo);
+        this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
         break;
       default:
         Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
   },
 
-  getAvailableTargets: function(aContentDocument, aCallbackID) {
+  getAvailableTargets: function(aMessageManager, aChromeWindow, aCallbackID) {
     Task.spawn(function*() {
-      let window = this.getChromeWindow(aContentDocument);
+      let window = aChromeWindow;
       let data = this.availableTargetsCache.get(window);
       if (data) {
-        this.sendPageCallback(aContentDocument, aCallbackID, data);
+        this.sendPageCallback(aMessageManager, aCallbackID, data);
         return;
       }
 
       let promises = [];
       for (let targetName of this.targets.keys()) {
         promises.push(this.getTarget(window, targetName));
       }
       let targetObjects = yield Promise.all(promises);
@@ -1263,41 +1217,41 @@ this.UITour = {
       targetNames = targetNames.concat(
         yield this.getAvailableSearchEngineTargets(window)
       );
 
       data = {
         targets: targetNames,
       };
       this.availableTargetsCache.set(window, data);
-      this.sendPageCallback(aContentDocument, aCallbackID, data);
+      this.sendPageCallback(aMessageManager, aCallbackID, data);
     }.bind(this)).catch(err => {
       Cu.reportError(err);
-      this.sendPageCallback(aContentDocument, aCallbackID, {
+      this.sendPageCallback(aMessageManager, aCallbackID, {
         targets: [],
       });
     });
   },
 
-  addNavBarWidget: function (aTarget, aContentDocument, aCallbackID) {
+  addNavBarWidget: function (aTarget, aMessageManager, aCallbackID) {
     if (aTarget.node) {
       Cu.reportError("UITour: can't add a widget already present: " + data.target);
       return;
     }
     if (!aTarget.allowAdd) {
       Cu.reportError("UITour: not allowed to add this widget: " + data.target);
       return;
     }
     if (!aTarget.widgetName) {
       Cu.reportError("UITour: can't add a widget without a widgetName property: " + data.target);
       return;
     }
 
     CustomizableUI.addWidgetToArea(aTarget.widgetName, CustomizableUI.AREA_NAVBAR);
-    this.sendPageCallback(aContentDocument, aCallbackID);
+    this.sendPageCallback(aMessageManager, aCallbackID);
   },
 
   _addAnnotationPanelMutationObserver: function(aPanelEl) {
 #ifdef XP_LINUX
     let observer = this._annotationPanelMutationObservers.get(aPanelEl);
     if (observer) {
       return;
     }
--- a/browser/modules/test/browser_UITour.js
+++ b/browser/modules/test/browser_UITour.js
@@ -76,22 +76,26 @@ let tests = [
 
     Services.prefs.setBoolPref("browser.uitour.enabled", true);
     done();
   },
   function test_highlight(done) {
     function test_highlight_2() {
       let highlight = document.getElementById("UITourHighlight");
       gContentAPI.hideHighlight();
+
+      waitForElementToBeHidden(highlight, test_highlight_3, "Highlight should be hidden after hideHighlight()");
+    }
+    function test_highlight_3() {
       is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
 
       gContentAPI.showHighlight("urlbar");
-      waitForElementToBeVisible(highlight, test_highlight_3, "Highlight should be shown after showHighlight()");
+      waitForElementToBeVisible(highlight, test_highlight_4, "Highlight should be shown after showHighlight()");
     }
-    function test_highlight_3() {
+    function test_highlight_4() {
       let highlight = document.getElementById("UITourHighlight");
       gContentAPI.showHighlight("backForward");
       waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
     }
 
     let highlight = document.getElementById("UITourHighlight");
     is_element_hidden(highlight, "Highlight should initially be hidden");
 
@@ -297,43 +301,37 @@ let tests = [
         gContentAPI.showInfo("urlbar", "test title", "test text");
 
       });
       gContentAPI.hideInfo();
     });
 
     gContentAPI.showInfo("urlbar", "test title", "test text");
   },
-  function test_info_2(done) {
+  taskify(function* test_info_2() {
     let popup = document.getElementById("UITourTooltip");
     let title = document.getElementById("UITourTooltipTitle");
     let desc = document.getElementById("UITourTooltipDescription");
     let icon = document.getElementById("UITourTooltipIcon");
     let buttons = document.getElementById("UITourTooltipButtons");
 
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
-      is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
-      is(title.textContent, "urlbar title", "Popup should have correct title");
-      is(desc.textContent, "urlbar text", "Popup should have correct description text");
-      is(icon.src, "", "Popup should have no icon");
-      is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+    yield showInfoPromise("urlbar", "urlbar title", "urlbar text");
 
-      gContentAPI.showInfo("search", "search title", "search text");
-      executeSoon(function() {
-        is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
-        is(title.textContent, "search title", "Popup should have correct title");
-        is(desc.textContent, "search text", "Popup should have correct description text");
+    is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
+    is(title.textContent, "urlbar title", "Popup should have correct title");
+    is(desc.textContent, "urlbar text", "Popup should have correct description text");
+    is(icon.src, "", "Popup should have no icon");
+    is(buttons.hasChildNodes(), false, "Popup should have no buttons");
 
-        done();
-      });
-    });
+    yield showInfoPromise("search", "search title", "search text");
 
-    gContentAPI.showInfo("urlbar", "urlbar title", "urlbar text");
-  },
+    is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
+    is(title.textContent, "search title", "Popup should have correct title");
+    is(desc.textContent, "search text", "Popup should have correct description text");
+  }),
   function test_getConfigurationVersion(done) {
     function callback(result) {
       let props = ["defaultUpdateChannel", "version"];
       for (let property of props) {
         ok(typeof(result[property]) !== undefined, "Check " + property + " isn't undefined.");
         is(result[property], Services.appinfo[property], "Should have the same " + property + " property.");
       }
       done();
@@ -363,13 +361,14 @@ let tests = [
           CustomizableUI.removeWidgetFromArea("panic-button");
           done();
         });
       });
     });
   },
 
   // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
-  function cleanupMenus(done) {
+  taskify(function* cleanupMenus() {
+    let shownPromise = promisePanelShown(window);
     gContentAPI.showMenu("appMenu");
-    done();
-  },
+    yield shownPromise;
+  }),
 ];
--- a/browser/modules/test/browser_UITour2.js
+++ b/browser/modules/test/browser_UITour2.js
@@ -51,53 +51,57 @@ let tests = [
           ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should still be set");
 
           // Move the info outside which shouldn't close the app menu since it was manually opened.
           gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
           UITour.getTarget(window, "appMenu").then((target) => {
             waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
               isnot(PanelUI.panel.state, "closed",
                     "Menu should remain open since UITour didn't open it in the first place");
+              waitForElementToBeHidden(window.PanelUI.panel, () => {
+                ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
+                done();
+              });
               gContentAPI.hideMenu("appMenu");
-              ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
-              done();
             }, "Info should move to the appMenu button");
           });
         }, "Info should be shown after showInfo() for fixed menu panel items");
       });
     }).then(null, Components.utils.reportError);
   },
-  function test_pinnedTab(done) {
+  taskify(function* test_pinnedTab() {
     is(UITour.pinnedTabs.get(window), null, "Should not already have a pinned tab");
 
-    gContentAPI.addPinnedTab();
+    yield addPinnedTabPromise();
+
     let tabInfo = UITour.pinnedTabs.get(window);
     isnot(tabInfo, null, "Should have recorded data about a pinned tab after addPinnedTab()");
     isnot(tabInfo.tab, null, "Should have added a pinned tab after addPinnedTab()");
     is(tabInfo.tab.pinned, true, "Tab should be marked as pinned");
 
     let tab = tabInfo.tab;
 
-    gContentAPI.removePinnedTab();
+    yield removePinnedTabPromise();
     isnot(gBrowser.tabs[0], tab, "First tab should not be the pinned tab");
     tabInfo = UITour.pinnedTabs.get(window);
     is(tabInfo, null, "Should not have any data about the removed pinned tab after removePinnedTab()");
 
-    gContentAPI.addPinnedTab();
-    gContentAPI.addPinnedTab();
-    gContentAPI.addPinnedTab();
+    yield addPinnedTabPromise();
+    yield addPinnedTabPromise();
+    yield addPinnedTabPromise();
     is(gBrowser.tabs[1].pinned, false, "After multiple calls of addPinnedTab, should still only have one pinned tab");
+  }),
+  taskify(function* test_menu() {
+    let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
 
-    done();
-  },
-  function test_menu(done) {
-    let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
     ise(bookmarksMenuButton.open, false, "Menu should initially be closed");
+    gContentAPI.showMenu("bookmarks");
 
-    gContentAPI.showMenu("bookmarks");
-    ise(bookmarksMenuButton.open, true, "Menu should be shown after showMenu()");
+    yield waitForConditionPromise(() => {
+      return bookmarksMenuButton.open;
+    }, "Menu should be visible after showMenu()");
 
     gContentAPI.hideMenu("bookmarks");
-    ise(bookmarksMenuButton.open, false, "Menu should be closed after hideMenu()");
-
-    done();
-  },
+    yield waitForConditionPromise(() => {
+        return !bookmarksMenuButton.open;
+    }, "Menu should be hidden after hideMenu()");
+  }),
 ];
--- a/browser/modules/test/browser_UITour3.js
+++ b/browser/modules/test/browser_UITour3.js
@@ -11,155 +11,138 @@ Components.utils.import("resource:///mod
 
 requestLongerTimeout(2);
 
 function test() {
   UITourTest();
 }
 
 let tests = [
-  function test_info_icon(done) {
+  taskify(function* test_info_icon() {
     let popup = document.getElementById("UITourTooltip");
     let title = document.getElementById("UITourTooltipTitle");
     let desc = document.getElementById("UITourTooltipDescription");
     let icon = document.getElementById("UITourTooltipIcon");
     let buttons = document.getElementById("UITourTooltipButtons");
 
     // Disable the animation to prevent the mouse clicks from hitting the main
     // window during the transition instead of the buttons in the popup.
     popup.setAttribute("animate", "false");
 
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
+    yield showInfoPromise("urlbar", "a title", "some text", "image.png");
 
-      is(title.textContent, "a title", "Popup should have correct title");
-      is(desc.textContent, "some text", "Popup should have correct description text");
+    is(title.textContent, "a title", "Popup should have correct title");
+    is(desc.textContent, "some text", "Popup should have correct description text");
 
-      let imageURL = getRootDirectory(gTestPath) + "image.png";
-      imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
-      is(icon.src, imageURL,  "Popup should have correct icon shown");
-
-      is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+    let imageURL = getRootDirectory(gTestPath) + "image.png";
+    imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
+    is(icon.src, imageURL,  "Popup should have correct icon shown");
 
-      done();
-    });
+    is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+  }),
 
-    gContentAPI.showInfo("urlbar", "a title", "some text", "image.png");
-  },
-  function test_info_buttons_1(done) {
+  taskify(function* test_info_buttons_1() {
     let popup = document.getElementById("UITourTooltip");
     let title = document.getElementById("UITourTooltipTitle");
     let desc = document.getElementById("UITourTooltipDescription");
     let icon = document.getElementById("UITourTooltipIcon");
 
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
+    let buttons = gContentWindow.makeButtons();
 
-      is(title.textContent, "another title", "Popup should have correct title");
-      is(desc.textContent, "moar text", "Popup should have correct description text");
+    yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
+
+    is(title.textContent, "another title", "Popup should have correct title");
+    is(desc.textContent, "moar text", "Popup should have correct description text");
 
-      let imageURL = getRootDirectory(gTestPath) + "image.png";
-      imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
-      is(icon.src, imageURL,  "Popup should have correct icon shown");
+    let imageURL = getRootDirectory(gTestPath) + "image.png";
+    imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
+    is(icon.src, imageURL,  "Popup should have correct icon shown");
 
-      let buttons = document.getElementById("UITourTooltipButtons");
-      is(buttons.childElementCount, 2, "Popup should have two buttons");
-
-      is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
-      is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
-
-      is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
-      is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
+    buttons = document.getElementById("UITourTooltipButtons");
+    is(buttons.childElementCount, 2, "Popup should have two buttons");
 
-      popup.addEventListener("popuphidden", function onPopupHidden() {
-        popup.removeEventListener("popuphidden", onPopupHidden);
-        ok(true, "Popup should close automatically");
+    is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
+    is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
 
-        executeSoon(function() {
-          is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
+    is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
+    is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
 
-          done();
-        });
-      });
+    let promiseHidden = promisePanelElementHidden(window, popup);
+    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
+    yield promiseHidden;
+
+    ok(true, "Popup should close automatically");
 
-      EventUtils.synthesizeMouseAtCenter(buttons.childNodes[0], {}, window);
-    });
+    yield waitForCallbackResultPromise();
 
-    let buttons = gContentWindow.makeButtons();
-    gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
-  },
-  function test_info_buttons_2(done) {
+    is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
+  }),
+  taskify(function* test_info_buttons_2() {
     let popup = document.getElementById("UITourTooltip");
     let title = document.getElementById("UITourTooltipTitle");
     let desc = document.getElementById("UITourTooltipDescription");
     let icon = document.getElementById("UITourTooltipIcon");
 
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
+    let buttons = gContentWindow.makeButtons();
 
-      is(title.textContent, "another title", "Popup should have correct title");
-      is(desc.textContent, "moar text", "Popup should have correct description text");
+    yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
+
+    is(title.textContent, "another title", "Popup should have correct title");
+    is(desc.textContent, "moar text", "Popup should have correct description text");
 
-      let imageURL = getRootDirectory(gTestPath) + "image.png";
-      imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
-      is(icon.src, imageURL,  "Popup should have correct icon shown");
+    let imageURL = getRootDirectory(gTestPath) + "image.png";
+    imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.com/");
+    is(icon.src, imageURL,  "Popup should have correct icon shown");
 
-      let buttons = document.getElementById("UITourTooltipButtons");
-      is(buttons.childElementCount, 2, "Popup should have two buttons");
-
-      is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
-      is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
-
-      is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
-      is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
+    buttons = document.getElementById("UITourTooltipButtons");
+    is(buttons.childElementCount, 2, "Popup should have two buttons");
 
-      popup.addEventListener("popuphidden", function onPopupHidden() {
-        popup.removeEventListener("popuphidden", onPopupHidden);
-        ok(true, "Popup should close automatically");
+    is(buttons.childNodes[0].getAttribute("label"), "Button 1", "First button should have correct label");
+    is(buttons.childNodes[0].getAttribute("image"), "", "First button should have no image");
 
-        executeSoon(function() {
-          is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
+    is(buttons.childNodes[1].getAttribute("label"), "Button 2", "Second button should have correct label");
+    is(buttons.childNodes[1].getAttribute("image"), imageURL, "Second button should have correct image");
 
-          done();
-        });
-      });
+    let promiseHidden = promisePanelElementHidden(window, popup);
+    EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
+    yield promiseHidden;
+
+    ok(true, "Popup should close automatically");
 
-      EventUtils.synthesizeMouseAtCenter(buttons.childNodes[1], {}, window);
-    });
+    yield waitForCallbackResultPromise();
 
-    let buttons = gContentWindow.makeButtons();
-    gContentAPI.showInfo("urlbar", "another title", "moar text", "./image.png", buttons);
-  },
+    is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
+  }),
 
-  function test_info_close_button(done) {
+  taskify(function* test_info_close_button() {
     let popup = document.getElementById("UITourTooltip");
     let closeButton = document.getElementById("UITourTooltipClose");
+    let infoOptions = gContentWindow.makeInfoOptions();
 
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
-      EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
-      executeSoon(function() {
-        is(gContentWindow.callbackResult, "closeButton", "Close button callback called");
-        done();
-      });
-    });
+    yield showInfoPromise("urlbar", "Close me", "X marks the spot", null, null, infoOptions);
+
+    EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
 
-    let infoOptions = gContentWindow.makeInfoOptions();
-    gContentAPI.showInfo("urlbar", "Close me", "X marks the spot", null, null, infoOptions);
-  },
+    yield waitForCallbackResultPromise();
 
-  function test_info_target_callback(done) {
+    is(gContentWindow.callbackResult, "closeButton", "Close button callback called");
+  }),
+
+  taskify(function* test_info_target_callback() {
     let popup = document.getElementById("UITourTooltip");
-    popup.addEventListener("popupshown", function onPopupShown() {
-      popup.removeEventListener("popupshown", onPopupShown);
-      PanelUI.show().then(() => {
-        is(gContentWindow.callbackResult, "target", "target callback called");
-        is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
-        is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
-        popup.removeAttribute("animate");
-        done();
-      });
-    });
+    let infoOptions = gContentWindow.makeInfoOptions();
+
+    yield showInfoPromise("appMenu", "I want to know when the target is clicked", "*click*", null, null, infoOptions);
+
+    yield PanelUI.show();
+
+    yield waitForCallbackResultPromise();
 
-    let infoOptions = gContentWindow.makeInfoOptions();
-    gContentAPI.showInfo("appMenu", "I want to know when the target is clicked", "*click*", null, null, infoOptions);
-  },
+    is(gContentWindow.callbackResult, "target", "target callback called");
+    is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
+    is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
+
+    // Cleanup.
+    yield hideInfoPromise();
+
+    popup.removeAttribute("animate");
+  }),
 ];
--- a/browser/modules/test/browser_UITour_detach_tab.js
+++ b/browser/modules/test/browser_UITour_detach_tab.js
@@ -6,78 +6,84 @@
  */
 
 "use strict";
 
 let gTestTab;
 let gContentAPI;
 let gContentWindow;
 let gContentDoc;
-let highlight = document.getElementById("UITourHighlight");
-let tooltip = document.getElementById("UITourTooltip");
 
 Components.utils.import("resource:///modules/UITour.jsm");
 
 function test() {
   registerCleanupFunction(function() {
     gContentDoc = null;
   });
   UITourTest();
 }
 
+/**
+ * When tab is changed we're tearing the tour down. So the UITour client has to always be aware of this
+ * fact and therefore listens to visibilitychange events.
+ * In particular this scenario happens for detaching the tab (ie. moving it to a new window).
+ */
 let tests = [
-  function test_move_tab_to_new_window(done) {
-    let gOpenedWindow;
+  taskify(function* test_move_tab_to_new_window(done) {
     let onVisibilityChange = (aEvent) => {
       if (!document.hidden && window != UITour.getChromeWindow(aEvent.target)) {
         gContentAPI.showHighlight("appMenu");
       }
     };
-    let onDOMWindowDestroyed = (aWindow, aTopic, aData) => {
-      if (gOpenedWindow && aWindow == gOpenedWindow) {
+
+    let highlight = document.getElementById("UITourHighlight");
+    let windowDestroyedDeferred = Promise.defer();
+    let onDOMWindowDestroyed = (aWindow) => {
+      if (gContentWindow && aWindow == gContentWindow) {
         Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
-        done();
-      }
-    };
-    let onBrowserDelayedStartup = (aWindow, aTopic, aData) => {
-      gOpenedWindow = aWindow;
-      Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
-      try {
-        let newWindowHighlight = gOpenedWindow.document.getElementById("UITourHighlight");
-        let selectedTab = aWindow.gBrowser.selectedTab;
-        is(selectedTab.linkedBrowser && selectedTab.linkedBrowser.contentDocument, gContentDoc, "Document should be selected in new window");
-        ok(UITour.originTabs && UITour.originTabs.has(aWindow), "Window should be known");
-        ok(UITour.originTabs.get(aWindow).has(selectedTab), "Tab should be known");
-        waitForElementToBeVisible(newWindowHighlight, function checkHighlightIsThere() {
-          let shownPromise = promisePanelShown(aWindow);
-          gContentAPI.showMenu("appMenu");
-          shownPromise.then(() => {
-            isnot(aWindow.PanelUI.panel.state, "closed", "Panel should be open");
-            ok(aWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
-            gContentAPI.hideHighlight();
-            gContentAPI.hideMenu("appMenu");
-            gTestTab = null;
-            aWindow.close();
-          }).then(null, Components.utils.reportError);
-        }, "Highlight should be shown in new window.");
-      } catch (ex) {
-        Cu.reportError(ex);
-        ok(false, "An error occurred running UITour tab detach test.");
-      } finally {
-        gContentDoc.removeEventListener("visibilitychange", onVisibilityChange, false);
-        Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
+        windowDestroyedDeferred.resolve();
       }
     };
 
-    Services.obs.addObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished", false);
+    let browserStartupDeferred = Promise.defer();
+    Services.obs.addObserver(function onBrowserDelayedStartup(aWindow) {
+      Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
+      browserStartupDeferred.resolve(aWindow);
+    }, "browser-delayed-startup-finished", false);
+
     // NB: we're using this rather than gContentWindow.document because the latter wouldn't
     // have an XRayWrapper, and we need to compare this to the doc we get using this method
     // later on...
     gContentDoc = gBrowser.selectedTab.linkedBrowser.contentDocument;
     gContentDoc.addEventListener("visibilitychange", onVisibilityChange, false);
     gContentAPI.showHighlight("appMenu");
-    waitForElementToBeVisible(highlight, function checkForInitialHighlight() {
-      gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-    });
+
+    yield elementVisiblePromise(highlight);
+
+    gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+    gContentWindow = yield browserStartupDeferred.promise;
+
+    // This highlight should be shown thanks to the visibilitychange listener.
+    let newWindowHighlight = gContentWindow.document.getElementById("UITourHighlight");
+    yield elementVisiblePromise(newWindowHighlight);
+
+    let selectedTab = gContentWindow.gBrowser.selectedTab;
+    is(selectedTab.linkedBrowser && selectedTab.linkedBrowser.contentDocument, gContentDoc, "Document should be selected in new window");
+    ok(UITour.originTabs && UITour.originTabs.has(gContentWindow), "Window should be known");
+    ok(UITour.originTabs.get(gContentWindow).has(selectedTab), "Tab should be known");
 
-  },
+    let shownPromise = promisePanelShown(gContentWindow);
+    gContentAPI.showMenu("appMenu");
+    yield shownPromise;
+
+    isnot(gContentWindow.PanelUI.panel.state, "closed", "Panel should be open");
+    ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
+    gContentAPI.hideHighlight();
+    gContentAPI.hideMenu("appMenu");
+    gTestTab = null;
+
+    Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
+    gContentWindow.close();
+
+    yield windowDestroyedDeferred.promise;
+  }),
 ];
-
--- a/browser/modules/test/browser_UITour_registerPageID.js
+++ b/browser/modules/test/browser_UITour_registerPageID.js
@@ -61,50 +61,52 @@ let tests = [
     Services.prefs.setCharPref("browser.uitour.seenPageIDs",
                                data);
 
     resetSeenPageIDsLazyGetter();
     checkExpectedSeenPageIDs(["savedID1", "savedID2"]);
 
     done();
   },
-  function test_seenPageIDs_set_1(done) {
+  taskify(function* test_seenPageIDs_set_1() {
     gContentAPI.registerPageID("testpage1");
 
+    yield waitForConditionPromise(() => UITour.seenPageIDs.size == 3, "Waiting for page to be registered.");
+
     checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
 
     const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
     const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
 
     let bucket = PREFIX + "UITour" + SEP + "testpage1";
     is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
 
     gBrowser.selectedTab = gBrowser.addTab("about:blank");
     bucket = PREFIX + "UITour" + SEP + "testpage1" + SEP + "inactive" + SEP + "1m";
     is(BrowserUITelemetry.currentBucket, bucket,
        "After switching tabs, bucket should be expiring");
 
     gBrowser.removeTab(gBrowser.selectedTab);
     gBrowser.selectedTab = gTestTab;
     BrowserUITelemetry.setBucket(null);
-    done();
-  },
-  function test_seenPageIDs_set_2(done) {
+  }),
+  taskify(function* test_seenPageIDs_set_2() {
     gContentAPI.registerPageID("testpage2");
 
+    yield waitForConditionPromise(() => UITour.seenPageIDs.size == 4, "Waiting for page to be registered.");
+
     checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
 
     const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
     const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
 
     let bucket = PREFIX + "UITour" + SEP + "testpage2";
     is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
 
     gBrowser.removeTab(gTestTab);
     gTestTab = null;
     bucket = PREFIX + "UITour" + SEP + "testpage2" + SEP + "closed" + SEP + "1m";
     is(BrowserUITelemetry.currentBucket, bucket,
        "After closing tab, bucket should be expiring");
 
     BrowserUITelemetry.setBucket(null);
-    done();
-  },
+  }),
 ];
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -1,33 +1,57 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource:///modules/UITour.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 
-function waitForCondition(condition, nextTest, errorMsg) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 30) {
-      ok(false, errorMsg);
-      moveOn();
+const SINGLE_TRY_TIMEOUT = 100;
+const NUMBER_OF_TRIES = 30;
+
+function waitForConditionPromise(condition, timeoutMsg) {
+  let defer = Promise.defer();
+  let tries = 0;
+  function checkCondition() {
+    if (tries >= NUMBER_OF_TRIES) {
+      defer.reject(timeoutMsg);
     }
     var conditionPassed;
     try {
       conditionPassed = condition();
     } catch (e) {
-      ok(false, e + "\n" + e.stack);
-      conditionPassed = false;
+      return defer.reject(e);
     }
     if (conditionPassed) {
-      moveOn();
+      return defer.resolve();
     }
     tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
+    setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+  }
+  setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+  return defer.promise;
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+  waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
+    ok(false, reason + (reason.stack ? "\n" + e.stack : ""));
+  });
+}
+
+/**
+ * Wrapper to partially transition tests to Task.
+ */
+function taskify(fun) {
+  return (done) => {
+    return Task.spawn(fun).then(done, (reason) => {
+      ok(false, reason);
+      done();
+    });
+  }
 }
 
 function is_hidden(element) {
   var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
@@ -75,44 +99,97 @@ function waitForElementToBeHidden(elemen
   waitForCondition(() => is_hidden(element),
                    () => {
                      ok(true, msg);
                      nextTest();
                    },
                    "Timeout waiting for invisibility: " + msg);
 }
 
+function elementVisiblePromise(element, msg) {
+  return waitForConditionPromise(() => is_visible(element), "Timeout waiting for visibility: " + msg);
+}
+
+function elementHiddenPromise(element, msg) {
+  return waitForConditionPromise(() => is_hidden(element), "Timeout waiting for invisibility: " + msg);
+}
+
 function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
   waitForCondition(() => is_visible(popup) && popup.popupBoxObject.anchorNode == anchorNode,
                    () => {
                      ok(true, msg);
                      is_element_visible(popup, "Popup should be visible");
                      nextTest();
                    },
                    "Timeout waiting for popup at anchor: " + msg);
 }
 
+function hideInfoPromise(...args) {
+  let popup = document.getElementById("UITourTooltip");
+  gContentAPI.hideInfo.apply(gContentAPI, args);
+  return promisePanelElementHidden(window, popup);
+}
+
+function showInfoPromise(...args) {
+  let popup = document.getElementById("UITourTooltip");
+  gContentAPI.showInfo.apply(gContentAPI, args);
+  return promisePanelElementShown(window, popup);
+}
+
+function waitForCallbackResultPromise() {
+  return waitForConditionPromise(() => {
+    return gContentWindow.callbackResult;
+  }, "callback should be called");
+}
+
+function addPinnedTabPromise() {
+  gContentAPI.addPinnedTab();
+  return waitForConditionPromise(() => {
+    let tabInfo = UITour.pinnedTabs.get(window);
+    if (!tabInfo) {
+      return false;
+    }
+    return tabInfo.tab.pinned;
+  });
+}
+
+function removePinnedTabPromise() {
+  gContentAPI.removePinnedTab();
+  return waitForConditionPromise(() => {
+    let tabInfo = UITour.pinnedTabs.get(window);
+    return tabInfo == null;
+  });
+}
+
 function promisePanelShown(win) {
   let panelEl = win.PanelUI.panel;
   return promisePanelElementShown(win, panelEl);
 }
 
-function promisePanelElementShown(win, aPanel) {
+function promisePanelElementEvent(win, aPanel, aEvent) {
   let deferred = Promise.defer();
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Panel did not show within 5 seconds.");
   }, 5000);
-  aPanel.addEventListener("popupshown", function onPanelOpen(e) {
-    aPanel.removeEventListener("popupshown", onPanelOpen);
+  aPanel.addEventListener(aEvent, function onPanelEvent(e) {
+    aPanel.removeEventListener(aEvent, onPanelEvent);
     win.clearTimeout(timeoutId);
     deferred.resolve();
   });
   return deferred.promise;
 }
 
+function promisePanelElementShown(win, aPanel) {
+  return promisePanelElementEvent(win, aPanel, "popupshown");
+}
+
+function promisePanelElementHidden(win, aPanel) {
+  return promisePanelElementEvent(win, aPanel, "popuphidden");
+}
+
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
   ok(is_hidden(element), msg);
 }
 
 function loadUITourTestPage(callback, host = "https://example.com/") {
   if (gTestTab)
     gBrowser.removeTab(gTestTab);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4699,17 +4699,17 @@ window > chatbox {
   }
   #main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
     background-image: url("chrome://browser/skin/privatebrowsing-mask-short@2x.png");
   }
 }
 
 @media (-moz-mac-lion-theme) {
   #TabsToolbar > .private-browsing-indicator {
-    transform: translateY(calc(0px - var(--negative-space)));
+    transform: translateY(calc(0px - var(--space-above-tabbar)));
     /* We offset by 38px for mask graphic, plus 4px to account for the
      * margin-left, which sums to 42px.
      */
     margin-right: -42px;
   }
 
   #main-window[privatebrowsingmode=temporary] .titlebar-placeholder[type="fullscreen-button"],
   #main-window[privatebrowsingmode=temporary] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
--- a/configure.in
+++ b/configure.in
@@ -4000,17 +4000,17 @@ else
   # "mobile" no longer exists.
   if test "$MOZ_BUILD_APP" = "mobile" ; then
     AC_MSG_RESULT([none])
     AC_MSG_ERROR([--enable-application=mobile is no longer supported.])
   fi
   # Support comm-central.
   if test -n "$EXTERNAL_SOURCE_DIR" ; then
     MOZ_BUILD_APP="$EXTERNAL_SOURCE_DIR/$MOZ_BUILD_APP"
-    MOZ_BUILD_APP=`${PYTHON} -c "import os.path; print(os.path.relpath(\"${MOZ_BUILD_APP}\", \"${srcdir}\"))"`
+    MOZ_BUILD_APP=`${PYTHON} -c "import mozpack.path as mozpath; print(mozpath.relpath(\"${MOZ_BUILD_APP}\", \"${srcdir}\"))"`
   fi
   # We have a valid application only if it has a build.mk file in its top
   # directory.
   if test ! -f "${srcdir}/${MOZ_BUILD_APP}/build.mk" ; then
     AC_MSG_RESULT([none])
     AC_MSG_ERROR([--enable-application value not recognized (${MOZ_BUILD_APP}/build.mk does not exist).])
   else
     AC_MSG_RESULT([$MOZ_BUILD_APP])
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9637,17 +9637,17 @@ nsDocShell::InternalLoad(nsIURI * aURI,
         }
     }
 
     // Before going any further vet loads initiated by external programs.
     if (aLoadType == LOAD_NORMAL_EXTERNAL) {
         // Disallow external chrome: loads targetted at content windows
         bool isChrome = false;
         if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) {
-            NS_WARNING("blocked external chrome: url -- use '-chrome' option");
+            NS_WARNING("blocked external chrome: url -- use '--chrome' option");
             return NS_ERROR_FAILURE;
         }
 
         // clear the decks to prevent context bleed-through (bug 298255)
         rv = CreateAboutBlankContentViewer(nullptr, nullptr);
         if (NS_FAILED(rv))
             return NS_ERROR_FAILURE;
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -4136,21 +4136,16 @@ nsContentUtils::CreateContextualFragment
   nsAutoString uriStr, nameStr;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aContextNode);
   // just in case we have a text node
   if (content && !content->IsElement())
     content = content->GetParent();
 
   while (content && content->IsElement()) {
     nsString& tagName = *tagStack.AppendElement();
-    if (!&tagName) {
-      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-      return nullptr;
-    }
-
     tagName = content->NodeInfo()->QualifiedName();
 
     // see if we need to add xmlns declarations
     uint32_t count = content->GetAttrCount();
     bool setDefaultNamespace = false;
     if (count > 0) {
       uint32_t index;
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9350,31 +9350,40 @@ nsGlobalWindow::UpdateCommands(const nsA
                                                             anAction));
     }
   }
 
   if (gSelectionCaretPrefEnabled && mDoc && anAction.EqualsLiteral("selectionchange")) {
     SelectionChangeEventInit init;
     init.mBubbles = true;
     if (aSel) {
-      nsCOMPtr<nsIDOMRange> range;
-      nsresult rv = aSel->GetRangeAt(0, getter_AddRefs(range));
-      if (NS_SUCCEEDED(rv) && range) {
-        nsRefPtr<nsRange> nsrange = static_cast<nsRange*>(range.get());
-        init.mBoundingClientRect = nsrange->GetBoundingClientRect(true, false);
-        range->ToString(init.mSelectedText);
-
-        for (uint32_t reasonType = 0;
-             reasonType < static_cast<uint32_t>(SelectionChangeReason::EndGuard_);
-             ++reasonType) {
-          SelectionChangeReason strongReasonType =
-            static_cast<SelectionChangeReason>(reasonType);
-          if (CheckReason(aReason, strongReasonType)) {
-            init.mReasons.AppendElement(strongReasonType);
-          }
+      Selection* selection = static_cast<Selection*>(aSel);
+      int32_t rangeCount = selection->GetRangeCount();
+      nsLayoutUtils::RectAccumulator accumulator;
+      for (int32_t idx = 0; idx < rangeCount; ++idx) {
+        nsRange* range = selection->GetRangeAt(idx);
+        nsRange::CollectClientRects(&accumulator, range,
+                                    range->GetStartParent(), range->StartOffset(),
+                                    range->GetEndParent(), range->EndOffset(),
+                                    true, false);
+      }
+      nsRect rect = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
+        accumulator.mResultRect;
+      nsRefPtr<DOMRect> domRect = new DOMRect(ToSupports(this));
+      domRect->SetLayoutRect(rect);
+      init.mBoundingClientRect = domRect;
+
+      selection->Stringify(init.mSelectedText);
+      for (uint32_t reasonType = 0;
+           reasonType < static_cast<uint32_t>(SelectionChangeReason::EndGuard_);
+           ++reasonType) {
+        SelectionChangeReason strongReasonType =
+          static_cast<SelectionChangeReason>(reasonType);
+        if (CheckReason(aReason, strongReasonType)) {
+          init.mReasons.AppendElement(strongReasonType);
         }
       }
 
       nsRefPtr<SelectionChangeEvent> event =
         SelectionChangeEvent::Constructor(mDoc, NS_LITERAL_STRING("mozselectionchange"), init);
 
       event->SetTrusted(true);
       event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
--- a/dom/base/test/test_bug811701.html
+++ b/dom/base/test/test_bug811701.html
@@ -21,28 +21,28 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript">
 
   /** Test for Bug 811701 **/
   var math = document.querySelector("math");
   is(math.innerHTML, "<mtext>test</mtext>", "<math> should have innerHTML");
   is(math.outerHTML, "<math><mtext>test</mtext></math>",
      "<math> should have innerHTML");
   math.innerHTML = "<mo>+</mo>";
-  is(math.firstChild.namespaceURI, "http://www.w3.org/1999/xhtml",
+  is(math.firstChild.namespaceURI, "http://www.w3.org/1998/Math/MathML",
      "Should have the right namespace after setting innerHTML on <math>");
 
   var polygon = document.querySelector("polygon");
   is(polygon.parentNode.innerHTML,
      '<polygon points="0,0 100,100 200,300"></polygon>',
      "<svg> should have innerHTML");
   is(polygon.parentNode.outerHTML,
      '<svg><polygon points="0,0 100,100 200,300"></polygon></svg>',
      "<svg> should have outerHTML");
   is(polygon.outerHTML, '<polygon points="0,0 100,100 200,300"></polygon>',
      "<polygon> should have outerHTML");
 
   var svg = document.querySelector("svg");
   svg.innerHTML = "<rect/>";
-  is(svg.firstChild.namespaceURI, "http://www.w3.org/1999/xhtml",
-     "Should have the right namespace after setting innerHTML on <math>");
+  is(svg.firstChild.namespaceURI, "http://www.w3.org/2000/svg",
+     "Should have the right namespace after setting innerHTML on <svg>");
   </script>
 </body>
 </html>
old mode 100755
new mode 100644
old mode 100755
new mode 100644
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -168,20 +168,16 @@ protected:
   // Boolean indicating whether we've seen a <head> tag that might have had
   // attributes once already.
   bool mHaveSeenHead;
 
   // Boolean indicating whether we've notified insertion of our root content
   // yet.  We want to make sure to only do this once.
   bool mNotifiedRootInsertion;
 
-  uint8_t mScriptEnabled : 1;
-  uint8_t mFramesEnabled : 1;
-  uint8_t unused : 6;  // bits available if someone needs one
-
   mozilla::dom::NodeInfo* mNodeInfoCache[NS_HTML_TAG_MAX + 1];
 
   nsresult FlushTags();
 
   // Routines for tags that require special handling
   nsresult CloseHTML();
   nsresult OpenBody();
   nsresult CloseBody();
@@ -719,35 +715,16 @@ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION
     NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIContentSink)
     NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIHTMLContentSink)
   NS_INTERFACE_TABLE_END
 NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink)
 
 NS_IMPL_ADDREF_INHERITED(HTMLContentSink, nsContentSink)
 NS_IMPL_RELEASE_INHERITED(HTMLContentSink, nsContentSink)
 
-static bool
-IsScriptEnabled(nsIDocument *aDoc, nsIDocShell *aContainer)
-{
-  NS_ENSURE_TRUE(aDoc && aContainer, true);
-
-  nsCOMPtr<nsIScriptGlobalObject> globalObject =
-    do_QueryInterface(aDoc->GetInnerWindow());
-
-  // Getting context is tricky if the document hasn't had its
-  // GlobalObject set yet
-  if (!globalObject) {
-    globalObject = aContainer->GetScriptGlobalObject();
-  }
-
-  NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
-  return nsContentUtils::GetSecurityManager()->
-           ScriptAllowed(globalObject->GetGlobalJSObject());
-}
-
 nsresult
 HTMLContentSink::Init(nsIDocument* aDoc,
                       nsIURI* aURI,
                       nsISupports* aContainer,
                       nsIChannel* aChannel)
 {
   NS_ENSURE_TRUE(aContainer, NS_ERROR_NULL_POINTER);
   
@@ -757,31 +734,16 @@ HTMLContentSink::Init(nsIDocument* aDoc,
   }
 
   aDoc->AddObserver(this);
   mIsDocumentObserver = true;
   mHTMLDocument = do_QueryInterface(aDoc);
 
   NS_ASSERTION(mDocShell, "oops no docshell!");
 
-  // Find out if subframes are enabled
-  if (mDocShell) {
-    bool subFramesEnabled = true;
-    mDocShell->GetAllowSubframes(&subFramesEnabled);
-    if (subFramesEnabled) {
-      mFramesEnabled = true;
-    }
-  }
-
-  // Find out if scripts are enabled, if not, show <noscript> content
-  if (IsScriptEnabled(aDoc, mDocShell)) {
-    mScriptEnabled = true;
-  }
-
-
   // Changed from 8192 to greatly improve page loading performance on
   // large pages.  See bugzilla bug 77540.
   mMaxTextRun = Preferences::GetInt("content.maxtextrun", 8191);
 
   nsRefPtr<mozilla::dom::NodeInfo> nodeInfo;
   nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr,
                                            kNameSpaceID_XHTML,
                                            nsIDOMNode::ELEMENT_NODE);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -208,17 +208,19 @@ MediaDecoderStateMachine::MediaDecoderSt
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mDropAudioUntilNextDiscontinuity(false),
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
   mCurrentTimeBeforeSeek(0),
-  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
+  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
+  mDecodingFrozenAtStateMetadata(false),
+  mDecodingFrozenAtStateDecoding(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   mAmpleVideoFrames =
     std::max<uint32_t>(Preferences::GetUint("media.video-queue.default-size", 10), 3);
 
   mBufferingWait = mScheduler->IsRealTime() ? 0 : BUFFERING_WAIT_S;
@@ -1351,18 +1353,19 @@ void MediaDecoderStateMachine::SetDorman
 
   DECODER_LOG("SetDormant=%d", aDormant);
 
   if (aDormant) {
     ScheduleStateMachine();
     SetState(DECODER_STATE_DORMANT);
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
+    mDecodingFrozenAtStateMetadata = true;
+    mDecodingFrozenAtStateDecoding = true;
     ScheduleStateMachine();
-    mStartTime = 0;
     mCurrentFrameTime = 0;
     SetState(DECODER_STATE_DECODING_NONE);
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
 }
 
 void MediaDecoderStateMachine::Shutdown()
 {
@@ -1449,16 +1452,20 @@ void MediaDecoderStateMachine::Play()
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
     DECODER_LOG("Changed state from BUFFERING to DECODING");
     SetState(DECODER_STATE_DECODING);
     mDecodeStartTime = TimeStamp::Now();
   }
+  if (mDecodingFrozenAtStateDecoding) {
+    mDecodingFrozenAtStateDecoding = false;
+    DispatchDecodeTasksIfNeeded();
+  }
   // Once we start playing, we don't want to minimize our prerolling, as we
   // assume the user is likely to want to keep playing in future.
   mMinimizePreroll = false;
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::ResetPlayback()
 {
@@ -1510,16 +1517,18 @@ void MediaDecoderStateMachine::NotifyDat
   }
 }
 
 void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
+  mDecodingFrozenAtStateDecoding = false;
+
   if (mState == DECODER_STATE_SHUTDOWN) {
     return;
   }
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
   if (!mDecoder->IsMediaSeekable()) {
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
@@ -1617,16 +1626,21 @@ MediaDecoderStateMachine::DispatchDecode
   AssertCurrentThreadInMonitor();
 
   if (mState != DECODER_STATE_DECODING &&
       mState != DECODER_STATE_BUFFERING &&
       mState != DECODER_STATE_SEEKING) {
     return;
   }
 
+  if (mState == DECODER_STATE_DECODING && mDecodingFrozenAtStateDecoding) {
+    DECODER_LOG("DispatchDecodeTasksIfNeeded return due to "
+                "mFreezeDecodingAtStateDecoding");
+    return;
+  }
   // NeedToDecodeAudio() can go from false to true while we hold the
   // monitor, but it can't go from true to false. This can happen because
   // NeedToDecodeAudio() takes into account the amount of decoded audio
   // that's been written to the AudioStream but not played yet. So if we
   // were calling NeedToDecodeAudio() twice and we thread-context switch
   // between the calls, audio can play, which can affect the return value
   // of NeedToDecodeAudio() giving inconsistent results. So we cache the
   // value returned by NeedToDecodeAudio(), and make decisions
@@ -1967,16 +1981,20 @@ nsresult MediaDecoderStateMachine::Decod
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
     VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
 
   if (mScheduler->IsRealTime()) {
     SetStartTime(0);
     res = FinishDecodeMetadata();
     NS_ENSURE_SUCCESS(res, res);
+  } else if (mDecodingFrozenAtStateMetadata) {
+    SetStartTime(mStartTime);
+    res = FinishDecodeMetadata();
+    NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
       mReader->RequestAudioData();
     }
     if (HasVideo()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
       mReader->RequestVideoData(false, 0);
@@ -1992,17 +2010,17 @@ MediaDecoderStateMachine::FinishDecodeMe
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   DECODER_LOG("FinishDecodeMetadata");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
     return NS_ERROR_FAILURE;
   }
 
-  if (!mScheduler->IsRealTime()) {
+  if (!mScheduler->IsRealTime() && !mDecodingFrozenAtStateMetadata) {
 
     const VideoData* v = VideoQueue().PeekFront();
     const AudioData* a = AudioQueue().PeekFront();
 
     int64_t startTime = std::min<int64_t>(a ? a->mTime : INT64_MAX,
                                           v ? v->mTime : INT64_MAX);
     if (startTime == INT64_MAX) {
       startTime = 0;
@@ -2024,16 +2042,18 @@ MediaDecoderStateMachine::FinishDecodeMe
   MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
                GetDuration() != -1,
              "Seekable media should have duration");
   DECODER_LOG("Media goes from %lld to %lld (duration %lld) "
               "transportSeekable=%d, mediaSeekable=%d",
               mStartTime, mEndTime, GetDuration(),
               mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
 
+  mDecodingFrozenAtStateMetadata = false;
+
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
   }
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -916,12 +916,23 @@ protected:
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
 
   mozilla::MediaMetadataManager mMetadataManager;
 
   MediaDecoderOwner::NextFrameStatus mLastFrameStatus;
+
+  // True if we are back from DECODER_STATE_DORMANT state, and we can skip
+  // SetStartTime because the mStartTime already set before. Also we don't need
+  // to decode any audio/video since the MediaDecoder will trigger a seek
+  // operation soon.
+  // mDecodingFrozenAtStateMetadata: turn on/off at
+  //                                 SetDormant/FinishDecodeMetadata.
+  // mDecodingFrozenAtStateDecoding: turn on/off at
+  //                                 SetDormant/Seek,Play.
+  bool mDecodingFrozenAtStateMetadata;
+  bool mDecodingFrozenAtStateDecoding;
 };
 
 } // namespace mozilla;
 #endif
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -96,21 +96,9 @@ RtspMediaCodecReader::ReadMetadata(Media
   nsresult rv = MediaCodecReader::ReadMetadata(aInfo, aTags);
   if (rv == NS_OK && !IsWaitingMediaResources()) {
     EnsureActive();
   }
 
   return rv;
 }
 
-// Called on Binder thread.
-void
-RtspMediaCodecReader::codecReserved(Track& aTrack)
-{
-  // TODO: fix me, we need a SeekTime(0) here because the
-  // MediaDecoderStateMachine will update the mStartTime after ReadMetadata.
-  MediaCodecReader::codecReserved(aTrack);
-  if (aTrack.mCodec != nullptr) {
-    mRtspResource->SeekTime(0);
-  }
-}
-
 } // namespace mozilla
--- a/dom/media/omx/RtspMediaCodecReader.h
+++ b/dom/media/omx/RtspMediaCodecReader.h
@@ -60,18 +60,16 @@ public:
                                 int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAudioDataTask to decode audio data.
   virtual void RequestAudioData() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
-  virtual void codecReserved(Track& aTrack) MOZ_OVERRIDE;
-
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspMediaCodecReader.
   RtspMediaResource* mRtspResource;
 };
 
--- a/dom/svg/SVGRectElement.cpp
+++ b/dom/svg/SVGRectElement.cpp
@@ -2,16 +2,18 @@
 /* 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 "mozilla/dom/SVGRectElement.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/SVGRectElementBinding.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include <algorithm>
 
 NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Rect)
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
@@ -103,16 +105,46 @@ SVGRectElement::GetLengthInfo()
 {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // nsSVGPathGeometryElement methods
 
+bool
+SVGRectElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                  const Matrix& aTransform)
+{
+  Rect r;
+  Float rx, ry;
+  GetAnimatedLengthValues(&r.x, &r.y, &r.width, &r.height, &rx, &ry, nullptr);
+
+  if (r.IsEmpty()) {
+    // Rendering of the element disabled
+    r.SetEmpty(); // make sure width/height are actually zero
+    *aBounds = r;
+    return true;
+  }
+
+  rx = std::max(rx, 0.0f);
+  ry = std::max(ry, 0.0f);
+
+  if (rx != 0 || ry != 0) {
+    return false;
+  }
+
+  if (aStrokeWidth > 0.f) {
+    r.Inflate(aStrokeWidth / 2.f);
+  }
+
+  *aBounds = aTransform.TransformBounds(r);
+  return true;
+}
+
 void
 SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath)
 {
   float x, y, width, height, rx, ry;
   GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
 
   if (width <= 0 || height <= 0) {
     aSimplePath->Reset();
--- a/dom/svg/SVGRectElement.h
+++ b/dom/svg/SVGRectElement.h
@@ -25,16 +25,18 @@ protected:
   friend nsresult (::NS_NewSVGRectElement(nsIContent **aResult,
                                           already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
 public:
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
+  virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                 const Matrix& aTransform) MOZ_OVERRIDE;
   virtual void GetAsSimplePath(SimplePath* aSimplePath) MOZ_OVERRIDE;
   virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> X();
   already_AddRefed<SVGAnimatedLength> Y();
--- a/dom/svg/nsSVGPathGeometryElement.h
+++ b/dom/svg/nsSVGPathGeometryElement.h
@@ -29,16 +29,17 @@ struct nsSVGMark {
 typedef mozilla::dom::SVGGraphicsElement nsSVGPathGeometryElementBase;
 
 class nsSVGPathGeometryElement : public nsSVGPathGeometryElementBase
 {
 protected:
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::FillRule FillRule;
   typedef mozilla::gfx::Float Float;
+  typedef mozilla::gfx::Matrix Matrix;
   typedef mozilla::gfx::Path Path;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::PathBuilder PathBuilder;
   typedef mozilla::gfx::Rect Rect;
 
 public:
   explicit nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
@@ -64,16 +65,21 @@ public:
    * This could be moved up to a more general class so it can be used for non-leaf
    * elements, but that would require care and for now there's no need.
    */
   bool GeometryDependsOnCoordCtx();
 
   virtual bool IsMarkable();
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks);
 
+  virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                 const Matrix& aTransform) {
+    return false;
+  }
+
   /**
    * For use with GetAsSimplePath.
    */
   class SimplePath
   {
   public:
     SimplePath()
       : mType(NONE)
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -169,16 +169,19 @@ const uint32_t kNoIndex = uint32_t(-1);
 const JS::ContextOptions kRequiredContextOptions =
   JS::ContextOptions().setDontReportUncaught(true);
 
 uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
 
 // Does not hold an owning reference.
 RuntimeService* gRuntimeService = nullptr;
 
+// Only true during the call to Init.
+bool gRuntimeServiceDuringInit = false;
+
 #ifdef ENABLE_TESTS
 bool gTestPBackground = false;
 #endif // ENABLE_TESTS
 
 enum {
   ID_Worker = 0,
   ID_ChromeWorker,
   ID_Event,
@@ -459,127 +462,132 @@ LoadJSGCMemoryOptions(const char* aPrefN
   // be no just a "mem." pref here.
   if (!rts) {
     NS_ASSERTION(memPrefName.EqualsLiteral(PREF_MEM_OPTIONS_PREFIX), "Huh?!");
   }
 #endif
 
   // If we're running in Init() then do this for every pref we care about.
   // Otherwise we just want to update the parameter that changed.
-  for (uint32_t index = rts ? JSSettings::kGCSettingsArraySize - 1 : 0;
+  for (uint32_t index = !gRuntimeServiceDuringInit
+                          ? JSSettings::kGCSettingsArraySize - 1 : 0;
        index < JSSettings::kGCSettingsArraySize;
        index++) {
     LiteralRebindingCString matchName;
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "max");
-    if (memPrefName == matchName || (!rts && index == 0)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 0)) {
       int32_t prefValue = GetWorkerPref(matchName, -1);
       uint32_t value = (prefValue <= 0 || prefValue >= 0x1000) ?
                        uint32_t(-1) :
                        uint32_t(prefValue) * 1024 * 1024;
       UpdatOtherJSGCMemoryOption(rts, JSGC_MAX_BYTES, value);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "high_water_mark");
-    if (memPrefName == matchName || (!rts && index == 1)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 1)) {
       int32_t prefValue = GetWorkerPref(matchName, 128);
       UpdatOtherJSGCMemoryOption(rts, JSGC_MAX_MALLOC_BYTES,
                                  uint32_t(prefValue) * 1024 * 1024);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_high_frequency_time_limit_ms");
-    if (memPrefName == matchName || (!rts && index == 2)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 2)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_HIGH_FREQUENCY_TIME_LIMIT);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_low_frequency_heap_growth");
-    if (memPrefName == matchName || (!rts && index == 3)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 3)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_LOW_FREQUENCY_HEAP_GROWTH);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_high_frequency_heap_growth_min");
-    if (memPrefName == matchName || (!rts && index == 4)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 4)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_high_frequency_heap_growth_max");
-    if (memPrefName == matchName || (!rts && index == 5)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 5)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_high_frequency_low_limit_mb");
-    if (memPrefName == matchName || (!rts && index == 6)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 6)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_HIGH_FREQUENCY_LOW_LIMIT);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_high_frequency_high_limit_mb");
-    if (memPrefName == matchName || (!rts && index == 7)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 7)) {
       UpdateCommonJSGCMemoryOption(rts, matchName,
                                    JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
                             "gc_allocation_threshold_mb");
-    if (memPrefName == matchName || (!rts && index == 8)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 8)) {
       UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_ALLOCATION_THRESHOLD);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_incremental_slice_ms");
-    if (memPrefName == matchName || (!rts && index == 9)) {
+    if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 9)) {
       int32_t prefValue = GetWorkerPref(matchName, -1);
       uint32_t value =
         (prefValue <= 0 || prefValue >= 100000) ? 0 : uint32_t(prefValue);
       UpdatOtherJSGCMemoryOption(rts, JSGC_SLICE_TIME_BUDGET, value);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_heap_growth");
-    if (memPrefName == matchName || (!rts && index == 10)) {
+    if (memPrefName == matchName ||
+        (gRuntimeServiceDuringInit && index == 10)) {
       bool prefValue = GetWorkerPref(matchName, false);
       UpdatOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_HEAP_GROWTH,
                                  prefValue ? 0 : 1);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_mark_slice");
-    if (memPrefName == matchName || (!rts && index == 11)) {
+    if (memPrefName == matchName ||
+        (gRuntimeServiceDuringInit && index == 11)) {
       bool prefValue = GetWorkerPref(matchName, false);
       UpdatOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_MARK_SLICE,
                                  prefValue ? 0 : 1);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_min_empty_chunk_count");
-    if (memPrefName == matchName || (!rts && index == 12)) {
+    if (memPrefName == matchName ||
+        (gRuntimeServiceDuringInit && index == 12)) {
       UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MIN_EMPTY_CHUNK_COUNT);
       continue;
     }
 
     matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_max_empty_chunk_count");
-    if (memPrefName == matchName || (!rts && index == 13)) {
+    if (memPrefName == matchName ||
+        (gRuntimeServiceDuringInit && index == 13)) {
       UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MAX_EMPTY_CHUNK_COUNT);
       continue;
     }
 
 #ifdef DEBUG
     nsAutoCString message("Workers don't support the 'mem.");
     message.Append(memPrefName);
     message.AppendLiteral("' preference!");
@@ -1760,16 +1768,19 @@ RuntimeService::Init()
                                  false))) {
     NS_WARNING("Failed to register for memory pressure notifications!");
   }
 
   if (NS_FAILED(obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) {
     NS_WARNING("Failed to register for offline notification event!");
   }
 
+  MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!");
+  gRuntimeServiceDuringInit = true;
+
   if (NS_FAILED(Preferences::RegisterCallback(
                                  LoadJSGCMemoryOptions,
                                  PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
                                  nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                             LoadJSGCMemoryOptions,
                             PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
                             nullptr)) ||
@@ -1817,16 +1828,19 @@ RuntimeService::Init()
                                                  nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                                  JSVersionChanged,
                                                  PREF_WORKERS_LATEST_JS_VERSION,
                                                  nullptr))) {
     NS_WARNING("Failed to register pref callbacks!");
   }
 
+  MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!");
+  gRuntimeServiceDuringInit = false;
+
   // We assume atomic 32bit reads/writes. If this assumption doesn't hold on
   // some wacky platform then the worst that could happen is that the close
   // handler will run for a slightly different amount of time.
   if (NS_FAILED(Preferences::AddIntVarCache(
                                    &sDefaultJSSettings.content.maxScriptRuntime,
                                    PREF_MAX_SCRIPT_RUN_TIME_CONTENT,
                                    MAX_SCRIPT_RUN_TIME_SEC)) ||
       NS_FAILED(Preferences::AddIntVarCache(
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -257,17 +257,17 @@ APZCTreeManager::PrepareAPZCForLayer(con
   // that is supposed to scroll together is split into multiple layers because of
   // e.g. non-scrolling content interleaved in z-index order.
   ScrollableLayerGuid guid(aLayersId, aMetrics);
   auto insertResult = aState.mApzcMap.insert(std::make_pair(guid, static_cast<AsyncPanZoomController*>(nullptr)));
   if (!insertResult.second) {
     apzc = insertResult.first->second;
     PrintAPZCInfo(aLayer, apzc);
   }
-  APZCTM_LOG("Found APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), guid.mLayersId, guid.mScrollId);
+  APZCTM_LOG("Found APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), guid.mLayersId, guid.mScrollId);
 
   // If we haven't encountered a layer already with the same metrics, then we need to
   // do the full reuse-or-make-an-APZC algorithm, which is contained inside the block
   // below.
   if (apzc == nullptr) {
     apzc = aLayer.GetApzc();
 
     // If the content represented by the scrollable layer has changed (which may
@@ -310,17 +310,17 @@ APZCTreeManager::PrepareAPZCForLayer(con
       // so that it doesn't continue pointing to APZCs that should no longer
       // be in the tree. These pointers will get reset properly as we continue
       // building the tree. Also remove it from the set of APZCs that are going
       // to be destroyed, because it's going to remain active.
       aState.mApzcsToDestroy.RemoveElement(apzc);
       apzc->SetPrevSibling(nullptr);
       apzc->SetLastChild(nullptr);
     }
-    APZCTM_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
+    APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
 
     apzc->NotifyLayersUpdated(aMetrics,
         aState.mIsFirstPaint && (aLayersId == aState.mOriginatingLayersId));
 
     nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
     apzc->SetLayerHitTestData(unobscured, aAncestorTransform);
     APZCTM_LOG("Setting region %s as visible region for APZC %p\n",
         Stringify(unobscured).c_str(), apzc);
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -100,17 +100,17 @@ InputQueue::ReceiveInputEvent(const nsRe
   block->AddEvent(aEvent.AsMultiTouchInput());
   return result;
 }
 
 uint64_t
 InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget)
 {
   TouchBlockState* block = StartNewTouchBlock(aTarget, true);
-  INPQ_LOG("%p injecting new touch block with id %llu and target %p\n",
+  INPQ_LOG("%p injecting new touch block with id %" PRIu64 " and target %p\n",
     this, block->GetBlockId(), aTarget);
   ScheduleContentResponseTimeout(aTarget, block->GetBlockId());
   return block->GetBlockId();
 }
 
 TouchBlockState*
 InputQueue::StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget, bool aCopyAllowedTouchBehaviorFromCurrent)
 {
@@ -157,51 +157,51 @@ InputQueue::ScheduleContentResponseTimeo
     NewRunnableMethod(this, &InputQueue::ContentResponseTimeout, aInputBlockId),
     gfxPrefs::APZContentResponseTimeout());
 }
 
 void
 InputQueue::ContentResponseTimeout(const uint64_t& aInputBlockId) {
   AsyncPanZoomController::AssertOnControllerThread();
 
-  INPQ_LOG("got a content response timeout; block=%llu\n", aInputBlockId);
+  INPQ_LOG("got a content response timeout; block=%" PRIu64 "\n", aInputBlockId);
   bool success = false;
   for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
     if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
       success = mTouchBlockQueue[i]->TimeoutContentResponse();
       break;
     }
   }
   if (success) {
     ProcessPendingInputBlocks();
   }
 }
 
 void
 InputQueue::ContentReceivedTouch(uint64_t aInputBlockId, bool aPreventDefault) {
   AsyncPanZoomController::AssertOnControllerThread();
 
-  INPQ_LOG("got a content response; block=%llu\n", aInputBlockId);
+  INPQ_LOG("got a content response; block=%" PRIu64 "\n", aInputBlockId);
   bool success = false;
   for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
     if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
       success = mTouchBlockQueue[i]->SetContentResponse(aPreventDefault);
       break;
     }
   }
   if (success) {
     ProcessPendingInputBlocks();
   }
 }
 
 void
 InputQueue::SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
   AsyncPanZoomController::AssertOnControllerThread();
 
-  INPQ_LOG("got allowed touch behaviours; block=%llu\n", aInputBlockId);
+  INPQ_LOG("got allowed touch behaviours; block=%" PRIu64 "\n", aInputBlockId);
   bool success = false;
   for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
     if (mTouchBlockQueue[i]->GetBlockId() == aInputBlockId) {
       success = mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors);
       break;
     }
   }
   if (success) {
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -1516,17 +1516,16 @@ TEST_F(APZCGestureDetectorTester, Double
   MakeApzcZoomable();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
   EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
 
   int time = 0;
   uint64_t blockIds[2];
   ApzcDoubleTapAndCheckStatus(apzc, 10, 10, time, &blockIds);
-printf_stderr("blockids %llu %llu\n", blockIds[0], blockIds[1]);
 
   // responses to the two touchstarts
   apzc->ContentReceivedTouch(blockIds[0], true);
   apzc->ContentReceivedTouch(blockIds[1], true);
 
   while (mcc->RunThroughDelayedTasks());
 
   apzc->AssertStateIsReset();
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -320,32 +320,16 @@ gfxContext::CurveTo(const gfxPoint& pt1,
 void
 gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
 {
   EnsurePathBuilder();
   mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2));
 }
 
 void
-gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
-                gfxFloat angle1, gfxFloat angle2)
-{
-  EnsurePathBuilder();
-  mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2));
-}
-
-void
-gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
-                        gfxFloat angle1, gfxFloat angle2)
-{
-  EnsurePathBuilder();
-  mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1));
-}
-
-void
 gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
 {
   EnsurePathBuilder();
   mPathBuilder->MoveTo(ToPoint(start));
   mPathBuilder->LineTo(ToPoint(end));
 }
 
 // XXX snapToPixels is only valid when snapping for filled
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -130,17 +130,17 @@ public:
     void ClosePath();
 
     /**
      * Returns the current path.
      */
     mozilla::TemporaryRef<Path> GetPath();
 
     /**
-     * Appends the given path to the current path.
+     * Sets the given path as the current path.
      */
     void SetPath(Path* path);
 
     /**
      * Moves the pen to a new point without drawing a line.
      */
     void MoveTo(const gfxPoint& pt);
 
@@ -167,37 +167,16 @@ public:
      */
     void CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3);
 
     /**
      * Draws a quadratic Bézier curve with control points pt1, pt2 and pt3.
      */
     void QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2);
 
-    /**
-     * Draws a clockwise arc (i.e. a circle segment).
-     * @param center The center of the circle
-     * @param radius The radius of the circle
-     * @param angle1 Starting angle for the segment
-     * @param angle2 Ending angle
-     */
-    void Arc(const gfxPoint& center, gfxFloat radius,
-             gfxFloat angle1, gfxFloat angle2);
-
-    /**
-     * Draws a counter-clockwise arc (i.e. a circle segment).
-     * @param center The center of the circle
-     * @param radius The radius of the circle
-     * @param angle1 Starting angle for the segment
-     * @param angle2 Ending angle
-     */
-
-    void NegativeArc(const gfxPoint& center, gfxFloat radius,
-                     gfxFloat angle1, gfxFloat angle2);
-
     // path helpers
     /**
      * Draws a line from start to end.
      */
     void Line(const gfxPoint& start, const gfxPoint& end); // XXX snapToPixels option?
 
     /**
      * Draws the rectangle given by rect.
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1567,16 +1567,26 @@ bool DoesD3D11DeviceSupportResourceShari
   return true;
 }
 
 void
 gfxWindowsPlatform::InitD3D11Devices()
 {
   mD3D11DeviceInitialized = true;
 
+  nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+  if (gfxInfo) {
+    int32_t status;
+    if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) {
+      if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
+        return;
+      }
+    }
+  }
+
   nsModuleHandle d3d11Module(LoadLibrarySystem32(L"d3d11.dll"));
   decltype(D3D11CreateDevice)* d3d11CreateDevice = (decltype(D3D11CreateDevice)*)
     GetProcAddress(d3d11Module, "D3D11CreateDevice");
 
   if (!d3d11CreateDevice) {
     return;
   }
 
--- a/intl/locale/PluralForm.jsm
+++ b/intl/locale/PluralForm.jsm
@@ -130,17 +130,17 @@ this.PluralForm = {
       let words = aWords ? aWords.split(/;/) : [""];
 
       // Explicitly check bounds to avoid strict warnings
       let ret = index < words.length ? words[index] : undefined;
 
       // Check for array out of bounds or empty strings
       if ((ret == undefined) || (ret == "")) {
         // Report the caller to help figure out who is causing badness
-        let caller = PluralForm.get.caller ? PluralForm.get.caller.name : "top";
+        let caller = Components.stack.caller ? Components.stack.caller.name : "top";
 
         // Display a message in the error console
         log(["Index #", index, " of '", aWords, "' for value ", aNum,
             " is invalid -- plural rule #", aRuleNum, "; called by ", caller]);
 
         // Default to the first entry (which might be empty, but not undefined)
         ret = words[0];
       }
new file mode 100644
--- /dev/null
+++ b/intl/locale/tests/unit/test_bug1086527.js
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This unit test makes sure that PluralForm.get can be called from strict mode
+ */
+
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+delete PluralForm.numForms;
+delete PluralForm.get;
+[PluralForm.get, PluralForm.numForms] = PluralForm.makeGetter(9);
+
+function run_test() {
+  "use strict";
+
+  do_check_eq(3, PluralForm.numForms());
+  do_check_eq("one", PluralForm.get(5, 'one;many'));
+}
--- a/intl/locale/tests/unit/xpcshell.ini
+++ b/intl/locale/tests/unit/xpcshell.ini
@@ -8,11 +8,12 @@ run-if = toolkit == "windows" || toolkit
 
 [test_bug371611.js]
 [test_bug374040.js]
 skip-if = toolkit == "windows" || toolkit == "cocoa"
 
 [test_collation_mac_icu.js]
 run-if = toolkit == "cocoa"
 
+[test_bug1086527.js]
 [test_pluralForm.js]
 [test_pluralForm_english.js]
 [test_pluralForm_makeGetter.js]
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -461,17 +461,17 @@ js::CreateSimd(JSContext *cx, typename V
 
 template JSObject *js::CreateSimd<Float32x4>(JSContext *cx, Float32x4::Elem *data);
 template JSObject *js::CreateSimd<Int32x4>(JSContext *cx, Int32x4::Elem *data);
 
 namespace js {
 // Unary SIMD operators
 template<typename T>
 struct Abs {
-    static inline T apply(T x) { return x < 0 ? -1 * x : x; }
+    static inline T apply(T x) { return mozilla::Abs(x); }
 };
 template<typename T>
 struct Neg {
     static inline T apply(T x) { return -1 * x; }
 };
 template<typename T>
 struct Not {
     static inline T apply(T x) { return ~x; }
@@ -707,24 +707,24 @@ static bool
 Swizzle(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename V::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != (V::lanes + 1) || !IsVectorObject<V>(args[0]))
         return ErrorBadArgs(cx);
 
-    int32_t lanes[V::lanes];
+    uint32_t lanes[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++) {
         int32_t lane = -1;
         if (!ToInt32(cx, args[i + 1], &lane))
             return false;
-        if (lane < 0 || lane >= V::lanes)
+        if (lane < 0 || uint32_t(lane) >= V::lanes)
             return ErrorBadArgs(cx);
-        lanes[i] = lane;
+        lanes[i] = uint32_t(lane);
     }
 
     Elem *val = TypedObjectMemory<Elem *>(args[0]);
 
     Elem result[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++)
         result[i] = val[lanes[i]];
 
@@ -736,24 +736,24 @@ static bool
 Shuffle(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename V::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != (V::lanes + 2) || !IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]))
         return ErrorBadArgs(cx);
 
-    int32_t lanes[V::lanes];
+    uint32_t lanes[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++) {
         int32_t lane = -1;
         if (!ToInt32(cx, args[i + 2], &lane))
             return false;
-        if (lane < 0 || lane >= (2 * V::lanes))
+        if (lane < 0 || uint32_t(lane) >= (2 * V::lanes))
             return ErrorBadArgs(cx);
-        lanes[i] = lane;
+        lanes[i] = uint32_t(lane);
     }
 
     Elem *lhs = TypedObjectMemory<Elem *>(args[0]);
     Elem *rhs = TypedObjectMemory<Elem *>(args[1]);
 
     Elem result[V::lanes];
     for (unsigned i = 0; i < V::lanes; i++) {
         Elem *selectedInput = lanes[i] < V::lanes ? lhs : rhs;
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4abs.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4abs.js
@@ -3,22 +3,34 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 abs';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(-1, 2, -3, 4);
   var c = SIMD.float32x4.abs(a);
   assertEq(c.x, 1);
   assertEq(c.y, 2);
   assertEq(c.z, 3);
   assertEq(c.w, 4);
 
+  var d = float32x4(-1.63, 2.46, -3.17, 4.94);
+  var f = SIMD.float32x4.abs(d);
+  assertEq(f.x, Math.fround(1.63));
+  assertEq(f.y, Math.fround(2.46));
+  assertEq(f.z, Math.fround(3.17));
+  assertEq(f.w, Math.fround(4.94));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.abs(g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, 0);
+  assertEq(i.z, Infinity);
+  assertEq(i.w, Infinity);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4add.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4add.js
@@ -1,25 +1,43 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 add';
 
+function addf(a, b) {
+  return Math.fround(Math.fround(a) + Math.fround(b));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
   var c = SIMD.float32x4.add(a, b);
   assertEq(c.x, 11);
   assertEq(c.y, 22);
   assertEq(c.z, 33);
   assertEq(c.w, 44);
 
+  var d = float32x4(1.57, 2.27, 3.57, 4.19);
+  var e = float32x4(10.31, 20.49, 30.41, 40.72);
+  var f = SIMD.float32x4.add(d, e);
+  assertEq(f.x, addf(1.57, 10.31));
+  assertEq(f.y, addf(2.27, 20.49));
+  assertEq(f.z, addf(3.57, 30.41));
+  assertEq(f.w, addf(4.19, 40.72));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var h = float32x4(0, -0, -Infinity, -Infinity);
+  var i = SIMD.float32x4.add(g, h);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -0);
+  assertEq(i.z, NaN);
+  assertEq(i.w, -Infinity);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4and.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4and.js
@@ -14,24 +14,38 @@ var andf = (function() {
         i[2] = i[0] & i[1];
         return f[2];
     }
 })();
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
   var c = SIMD.float32x4.and(a, b);
   assertEq(c.x, andf(1, 10));
   assertEq(c.y, andf(2, 20));
   assertEq(c.z, andf(3, 30));
   assertEq(c.w, andf(4, 40));
 
+  var d = float32x4(1.51, 2.98, 3.65, 4.34);
+  var e = float32x4(10.29, 20.12, 30.79, 40.41);
+  var f = SIMD.float32x4.and(d, e);
+  assertEq(f.x, andf(1.51, 10.29));
+  assertEq(f.y, andf(2.98, 20.12));
+  assertEq(f.z, andf(3.65, 30.79));
+  assertEq(f.w, andf(4.34, 40.41));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var h = float32x4(NaN, -0, -Infinity, Infinity);
+  var i = SIMD.float32x4.and(g, h);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -0);
+  assertEq(i.z, Infinity);
+  assertEq(i.w, Infinity);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4clamp.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4clamp.js
@@ -3,23 +3,43 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 clamp';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  var a = float32x4(-13.37, 10.46, 31.79, 0.54);
-  var lower = float32x4(2.1, 1.1, 50.13, 0.0);
-  var upper = float32x4(2.56, 5.55, 55.93, 1.1);
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
+
+  var a = float32x4(-20, 10, 30, 0.5);
+  var lower = float32x4(2, 1, 50, 0);
+  var upper = float32x4(2.5, 5, 55, 1);
   var c = SIMD.float32x4.clamp(a, lower, upper);
-  assertEq(c.x, Math.fround(2.1));
-  assertEq(c.y, Math.fround(5.55));
-  assertEq(c.z, Math.fround(50.13));
-  assertEq(c.w, Math.fround(0.54));
+  assertEq(c.x, 2);
+  assertEq(c.y, 5);
+  assertEq(c.z, 50);
+  assertEq(c.w, 0.5);
+
+  var d = float32x4(-13.37, 10.46, 31.79, 0.54);
+  var lower1 = float32x4(2.1, 1.1, 50.13, 0.0);
+  var upper1 = float32x4(2.56, 5.55, 55.93, 1.1);
+  var f = SIMD.float32x4.clamp(d, lower1, upper1);
+  assertEq(f.x, Math.fround(2.1));
+  assertEq(f.y, Math.fround(5.55));
+  assertEq(f.z, Math.fround(50.13));
+  assertEq(f.w, Math.fround(0.54));
+
+  var g = float32x4(4, -Infinity, 10, -10);
+  var lower2 = float32x4(5, -Infinity, -Infinity, -Infinity);
+  var upper2 = float32x4(Infinity, 5, Infinity, Infinity);
+  var i = SIMD.float32x4.clamp(g, lower2, upper2);
+  assertEq(i.x, 5);
+  assertEq(i.y, -Infinity);
+  assertEq(i.z, 10);
+  assertEq(i.w, -10);
 
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4div.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4div.js
@@ -1,26 +1,43 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 div';
 
+function divf(a, b) {
+    return Math.fround(Math.fround(a) / Math.fround(b));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
-  var c = SIMD.float32x4.div(b,a);
+  var c = SIMD.float32x4.div(b, a);
   assertEq(c.x, 10);
   assertEq(c.y, 10);
   assertEq(c.z, 10);
   assertEq(c.w, 10);
 
+  var d = float32x4(1.26, 2.03, 3.17, 4.59);
+  var e = float32x4(11.025, 17.3768, 29.1957, 46.4049);
+  var f = SIMD.float32x4.div(e, d);
+  assertEq(f.x, divf(11.025, 1.26));
+  assertEq(f.y, divf(17.3768, 2.03));
+  assertEq(f.z, divf(29.1957, 3.17));
+  assertEq(f.w, divf(46.4049, 4.59));
+
+  var g = float32x4(0, -0, Infinity, -Infinity);
+  var h = float32x4(1, 1, -Infinity, Infinity);
+  var i = SIMD.float32x4.div(h, g);
+  assertEq(i.x, Infinity);
+  assertEq(i.y, -Infinity);
+  assertEq(i.z, NaN);
+  assertEq(i.w, NaN);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
-
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4equal.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4equal.js
@@ -3,24 +3,32 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 equal';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of NaN/-0/Infinity/-Infinity border cases.
 
   var a = float32x4(1, 20, 30, 40);
   var b = float32x4(10, 20, 30, 4);
   var c = SIMD.float32x4.equal(a, b);
   assertEq(c.x, 0);
   assertEq(c.y, -1);
   assertEq(c.z, -1);
   assertEq(c.w, 0);
 
+  var d = float32x4(1.89, 20.51, 30.46, 40.12);
+  var e = float32x4(10.89, 20.51, Math.fround(30.46), 4.12);
+  var f = SIMD.float32x4.equal(d, e);
+  assertEq(c.x, 0);
+  assertEq(c.y, -1);
+  assertEq(c.z, -1);
+  assertEq(c.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4fromint32x4.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4fromint32x4.js
@@ -3,23 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 fromInt32x4';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  var INT32_MAX = Math.pow(2, 31) - 1;
+  var INT32_MIN = -Math.pow(2, 31);
 
   var a = int32x4(1, 2, 3, 4);
   var c = SIMD.float32x4.fromInt32x4(a);
   assertEq(c.x, 1);
   assertEq(c.y, 2);
   assertEq(c.z, 3);
   assertEq(c.w, 4);
 
+  var value1 = Math.pow(2, 30) - 1;
+  var value2 = -Math.pow(2, 30);
+  var d = int32x4(INT32_MIN, INT32_MAX, value1, value2);
+  var f = float32x4.fromInt32x4(d);
+  assertEq(f.x, Math.fround(INT32_MIN));
+  assertEq(f.y, Math.fround(INT32_MAX));
+  assertEq(f.z, Math.fround(value1));
+  assertEq(f.w, Math.fround(value2));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4fromint32x4bits.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4fromint32x4bits.js
@@ -3,23 +3,31 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 fromInt32x4Bits';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  var INT32_MAX = Math.pow(2, 31) - 1;
+  var INT32_MIN = -Math.pow(2, 31);
 
   var a = int32x4(100, 200, 300, 400);
   var c = SIMD.float32x4.fromInt32x4Bits(a);
   assertEq(c.x, 1.401298464324817e-43);
   assertEq(c.y, 2.802596928649634e-43);
   assertEq(c.z, 4.203895392974451e-43);
   assertEq(c.w, 5.605193857299268e-43);
 
+  var d = int32x4(INT32_MIN, INT32_MAX, 0, 0);
+  var f = float32x4.fromInt32x4Bits(d);
+  assertEq(f.x, -0);
+  assertEq(f.y, NaN);
+  assertEq(f.z, 0);
+  assertEq(f.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthan.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthan.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 greaterThan';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 3, 40);
   var b = float32x4(10, 2, 30, 4);
-  var c = SIMD.float32x4.greaterThan(b,a);
+  var c = SIMD.float32x4.greaterThan(b, a);
   assertEq(c.x, -1);
   assertEq(c.y, 0);
   assertEq(c.z, -1);
   assertEq(c.w, 0);
 
+  var d = float32x4(10.8399, 20.37, 3.07, 4.6802);
+  var e = float32x4(10.8401, 20.367, 3.1, 4.6801);
+  var f = float32x4.greaterThan(e, d);
+  assertEq(f.x, -1);
+  assertEq(f.y, 0);
+  assertEq(f.z, -1);
+  assertEq(f.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthanorequal.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4greaterthanorequal.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 greaterThanOrEqual';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 30, 40);
   var b = float32x4(10, 20, 30, 4);
-  var c = SIMD.float32x4.greaterThanOrEqual(b,a);
+  var c = SIMD.float32x4.greaterThanOrEqual(b, a);
   assertEq(c.x, -1);
   assertEq(c.y, -1);
   assertEq(c.z, -1);
   assertEq(c.w, 0);
 
+  var d = float32x4(10.029, 20.87, 30.56, 4.7);
+  var e = float32x4(10.03, 20.87, 30.56, 4.698);
+  var f = float32x4.greaterThanOrEqual(e, d);
+  assertEq(f.x, -1);
+  assertEq(f.y, -1);
+  assertEq(f.z, -1);
+  assertEq(f.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthan.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthan.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 lessThan';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 3, 40);
   var b = float32x4(10, 2, 30, 4);
   var c = SIMD.float32x4.lessThan(a, b);
   assertEq(c.x, -1);
   assertEq(c.y, 0);
   assertEq(c.z, -1);
   assertEq(c.w, 0);
 
+  var d = float32x4(1.5399, 20.001, 30.045, 4.74);
+  var e = float32x4(1.54, 19.999, 30.05, 4.72);
+  var f = float32x4.lessThan(a, b);
+  assertEq(f.x, -1);
+  assertEq(f.y, 0);
+  assertEq(f.z, -1);
+  assertEq(f.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthanorequal.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4lessthanorequal.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 lessThanOrEqual';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 30, 40);
   var b = float32x4(10, 20, 30, 4);
   var c = SIMD.float32x4.lessThanOrEqual(a, b);
   assertEq(c.x, -1);
   assertEq(c.y, -1);
   assertEq(c.z, -1);
   assertEq(c.w, 0);
 
+  var d = float32x4(9.999, 20.78, 30.14, 40.1235);
+  var e = float32x4(10, 20.78, 30.14, 40.123);
+  var f = float32x4.lessThanOrEqual(d, e);
+  assertEq(f.x, -1);
+  assertEq(f.y, -1);
+  assertEq(f.z, -1);
+  assertEq(f.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4max.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4max.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 max';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 30, 4);
   var b = float32x4(10, 2, 3, 40);
   var c = SIMD.float32x4.max(a, b);
   assertEq(c.x, 10);
   assertEq(c.y, 20);
   assertEq(c.z, 30);
   assertEq(c.w, 40);
 
+  var d = float32x4(9.999, 2.1234, 30.4443, 4);
+  var e = float32x4(10, 2.1233, 30.4444, 4.0001);
+  var f = float32x4.max(d, e);
+  assertEq(f.x, 10);
+  assertEq(f.y, Math.fround(2.1234));
+  assertEq(f.z, Math.fround(30.4444));
+  assertEq(f.w, Math.fround(4.0001));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4min.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4min.js
@@ -3,24 +3,33 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 min';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
+  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
 
   var a = float32x4(1, 20, 3, 40);
   var b = float32x4(10, 2, 30, 4);
   var c = SIMD.float32x4.min(a, b);
   assertEq(c.x, 1);
   assertEq(c.y, 2);
   assertEq(c.z, 3);
   assertEq(c.w, 4);
 
+  var d = float32x4(1.4321, 20.5567, 30.8999, 4.0002);
+  var e = float32x4(1.432, 20.5568, 30.8998, 4.0001);
+  var f = float32x4.min(d, e);
+  assertEq(f.x, Math.fround(1.432));
+  assertEq(f.y, Math.fround(20.5567));
+  assertEq(f.z, Math.fround(30.8998));
+  assertEq(f.w, Math.fround(4.0001));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4mul.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4mul.js
@@ -1,26 +1,43 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
-var summary = 'float32x4 add';
+var summary = 'float32x4 mul';
+
+function mulf(a, b) {
+    return Math.fround(Math.fround(a) * Math.fround(b));
+}
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
   var c = SIMD.float32x4.mul(a, b);
   assertEq(c.x, 10);
   assertEq(c.y, 40);
   assertEq(c.z, 90);
   assertEq(c.w, 160);
 
+  var d = float32x4(1.66, 2.57, 3.73, 4.12);
+  var e = float32x4(10.67, 20.68, 30.02, 40.58);
+  var f = SIMD.float32x4.mul(d, e);
+  assertEq(f.x, mulf(1.66, 10.67));
+  assertEq(f.y, mulf(2.57, 20.68));
+  assertEq(f.z, mulf(3.73, 30.02));
+  assertEq(f.w, mulf(4.12, 40.58));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var h = float32x4(NaN, -0, -Infinity, 0);
+  var i = SIMD.float32x4.mul(g, h);
+  assertEq(i.x, NaN);
+  assertEq(i.y, 0);
+  assertEq(i.z, -Infinity);
+  assertEq(i.w, NaN);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
-
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4neg.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4neg.js
@@ -3,23 +3,35 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 neg';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var c = SIMD.float32x4.neg(a);
   assertEq(c.x, -1);
   assertEq(c.y, -2);
   assertEq(c.z, -3);
   assertEq(c.w, -4);
 
+  var d = float32x4(0.999, -0.001, 3.78, 4.05);
+  var f = SIMD.float32x4.neg(d);
+  assertEq(f.x, Math.fround(-0.999));
+  assertEq(f.y, Math.fround(0.001));
+  assertEq(f.z, Math.fround(-3.78));
+  assertEq(f.w, Math.fround(-4.05));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.neg(g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, 0);
+  assertEq(i.z, -Infinity);
+  assertEq(i.w, Infinity);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4not.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4not.js
@@ -13,23 +13,35 @@ var notf = (function() {
         i[0] = ~i[0];
         return f[0];
     }
 })();
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(2, 13, -37, 4.2);
   var c = SIMD.float32x4.not(a);
   assertEq(c.x, notf(2));
   assertEq(c.y, notf(13));
   assertEq(c.z, notf(-37));
   assertEq(c.w, notf(4.2));
 
+  var d = float32x4(2.897, 13.245, -37.781, 5.28);
+  var f = SIMD.float32x4.not(d);
+  assertEq(f.x, notf(2.897));
+  assertEq(f.y, notf(13.245));
+  assertEq(f.z, notf(-37.781));
+  assertEq(f.w, notf(5.28));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.not(g);
+  assertEq(i.x, notf(NaN));
+  assertEq(i.y, notf(-0));
+  assertEq(i.z, notf(Infinity));
+  assertEq(i.w, notf(-Infinity));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4notequal.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4notequal.js
@@ -3,24 +3,32 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 notEqual';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+  // FIXME -- Bug 1081697: Amend to check for correctness of NaN/-0/Infinity/-Infinity border cases.
 
   var a = float32x4(1, 20, 30, 40);
   var b = float32x4(10, 20, 30, 4);
   var c = SIMD.float32x4.notEqual(a, b);
   assertEq(c.x, -1);
   assertEq(c.y, 0);
   assertEq(c.z, 0);
   assertEq(c.w, -1);
 
+  var d = float32x4(9.98, 20.65, 30.14, 4.235);
+  var e = float32x4(9.99, 20.65, Math.fround(30.14), 4.23);
+  var f = SIMD.float32x4.notEqual(d, e);
+  assertEq(f.x, -1);
+  assertEq(f.y, 0);
+  assertEq(f.z, 0);
+  assertEq(f.w, -1);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4or.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4or.js
@@ -14,24 +14,38 @@ var orf = (function() {
         i[2] = i[0] | i[1];
         return f[2];
     }
 })();
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
   var c = SIMD.float32x4.or(a, b);
   assertEq(c.x, orf(1, 10));
   assertEq(c.y, orf(2, 20));
   assertEq(c.z, orf(3, 30));
   assertEq(c.w, orf(4, 40));
 
+  var d = float32x4(1.12, 2.39, 3.83, 4.57);
+  var e = float32x4(10.76, 20.41, 30.96, 40.23);
+  var f = SIMD.float32x4.or(d, e);
+  assertEq(f.x, orf(1.12, 10.76));
+  assertEq(f.y, orf(2.39, 20.41));
+  assertEq(f.z, orf(3.83, 30.96));
+  assertEq(f.w, orf(4.57, 40.23));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var h = float32x4(5, 5, -Infinity, Infinity);
+  var i = SIMD.float32x4.or(g, h);
+  assertEq(i.x, orf(NaN, 5));
+  assertEq(i.y, orf(-0, 5));
+  assertEq(i.z, orf(Infinity, -Infinity));
+  assertEq(i.w, orf(-Infinity, Infinity));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4reciprocal.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4reciprocal.js
@@ -1,25 +1,41 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 reciprocal';
 
+function reciprocalf(a) {
+  return Math.fround(1 / Math.fround(a));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 0.5, 0.25, 0.125);
   var c = SIMD.float32x4.reciprocal(a);
   assertEq(c.x, 1);
   assertEq(c.y, 2);
   assertEq(c.z, 4);
   assertEq(c.w, 8);
 
+  var d = float32x4(1.6, 0.8, 0.4, 0.2);
+  var f = SIMD.float32x4.reciprocal(d);
+  assertEq(f.x, reciprocalf(1.6));
+  assertEq(f.y, reciprocalf(0.8));
+  assertEq(f.z, reciprocalf(0.4));
+  assertEq(f.w, reciprocalf(0.2));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.reciprocal(g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -Infinity);
+  assertEq(i.z, 0);
+  assertEq(i.w, -0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4reciprocalsqrt.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4reciprocalsqrt.js
@@ -1,25 +1,41 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 reciprocalSqrt';
 
+function reciprocalsqrtf(a) {
+    return Math.fround(Math.sqrt(1 / Math.fround(a)));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 1, 0.25, 0.25);
   var c = SIMD.float32x4.reciprocalSqrt(a);
   assertEq(c.x, 1);
   assertEq(c.y, 1);
   assertEq(c.z, 2);
   assertEq(c.w, 2);
 
+  var d = float32x4(25, 16, 6.25, 1.5625);
+  var f = SIMD.float32x4.reciprocalSqrt(d);
+  assertEq(f.x, reciprocalsqrtf(25));
+  assertEq(f.y, reciprocalsqrtf(16));
+  assertEq(f.z, reciprocalsqrtf(6.25));
+  assertEq(f.w, reciprocalsqrtf(1.5625));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.reciprocalSqrt(g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -Infinity);
+  assertEq(i.z, 0);
+  assertEq(i.w, NaN);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4scale.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4scale.js
@@ -1,25 +1,41 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 scale';
 
+function mulf(a, b) {
+  return Math.fround(Math.fround(a) * Math.fround(b));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var c = SIMD.float32x4.scale(a, 2);
   assertEq(c.x, 2);
   assertEq(c.y, 4);
   assertEq(c.z, 6);
   assertEq(c.w, 8);
 
+  var d = float32x4(1.34, 2.76, 3.21, 4.09);
+  var f = float32x4.scale(d, 2.54);
+  assertEq(f.x, mulf(1.34, 2.54));
+  assertEq(f.y, mulf(2.76, 2.54));
+  assertEq(f.z, mulf(3.21, 2.54));
+  assertEq(f.w, mulf(4.09, 2.54));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = float32x4.scale(g, 2.54);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -0);
+  assertEq(i.z, Infinity);
+  assertEq(i.w, -Infinity);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4sqrt.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4sqrt.js
@@ -1,25 +1,41 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 sqrt';
 
+function sqrtf(a) {
+  return Math.fround(Math.sqrt(Math.fround(a)));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 4, 9, 16);
   var c = SIMD.float32x4.sqrt(a);
   assertEq(c.x, 1);
   assertEq(c.y, 2);
   assertEq(c.z, 3);
   assertEq(c.w, 4);
 
+  var d = float32x4(2.7225, 7.3441, 9.4249, -1);
+  var f = SIMD.float32x4.sqrt(d);
+  assertEq(f.x, sqrtf(2.7225));
+  assertEq(f.y, sqrtf(7.3441));
+  assertEq(f.z, sqrtf(9.4249));
+  assertEq(f.w, NaN);
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.sqrt(g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, -0);
+  assertEq(i.z, Infinity);
+  assertEq(i.w, NaN);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4sub.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4sub.js
@@ -1,26 +1,44 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 sub';
 
+function subf(a, b) {
+    return Math.fround(Math.fround(a) - Math.fround(b));
+}
+
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
-  var c = SIMD.float32x4.sub(b,a);
+  var c = SIMD.float32x4.sub(b, a);
   assertEq(c.x, 9);
   assertEq(c.y, 18);
   assertEq(c.z, 27);
   assertEq(c.w, 36);
 
+  var d = float32x4(1.34, 2.95, 3.17, 4.29);
+  var e = float32x4(10.18, 20.43, 30.63, 40.38);
+  var f = SIMD.float32x4.sub(e, d);
+  assertEq(f.x, subf(10.18, 1.34));
+  assertEq(f.y, subf(20.43, 2.95));
+  assertEq(f.z, subf(30.63, 3.17));
+  assertEq(f.w, subf(40.38, 4.29));
+
+  var g = float32x4(NaN, -0, -Infinity, -Infinity);
+  var h = float32x4(NaN, -0, Infinity, -Infinity);
+  var i = SIMD.float32x4.sub(h, g);
+  assertEq(i.x, NaN);
+  assertEq(i.y, 0);
+  assertEq(i.z, Infinity);
+  assertEq(i.w, NaN);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4with.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4with.js
@@ -3,26 +3,89 @@ var BUGNUMBER = 946042;
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'float32x4 with';
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var x = SIMD.float32x4.withX(a, 5);
   var y = SIMD.float32x4.withY(a, 5);
   var z = SIMD.float32x4.withZ(a, 5);
   var w = SIMD.float32x4.withW(a, 5);
   assertEq(x.x, 5);
+  assertEq(x.y, 2);
+  assertEq(x.z, 3);
+  assertEq(x.w, 4);
+
+  assertEq(y.x, 1);
   assertEq(y.y, 5);
+  assertEq(y.z, 3);
+  assertEq(y.w, 4);
+
+  assertEq(z.x, 1);
+  assertEq(z.y, 2);
   assertEq(z.z, 5);
+  assertEq(z.w, 4);
+
+  assertEq(w.x, 1);
+  assertEq(w.y, 2);
+  assertEq(w.z, 3);
   assertEq(w.w, 5);
 
+  var b = float32x4(1.87, 2.08, 3.84, 4.17);
+  var x1 = SIMD.float32x4.withX(b, 5.38);
+  var y1 = SIMD.float32x4.withY(b, 5.19);
+  var z1 = SIMD.float32x4.withZ(b, 5.11);
+  var w1 = SIMD.float32x4.withW(b, 5.07);
+  assertEq(x1.x, Math.fround(5.38));
+  assertEq(x1.y, Math.fround(2.08));
+  assertEq(x1.z, Math.fround(3.84));
+  assertEq(x1.w, Math.fround(4.17));
+
+  assertEq(y1.x, Math.fround(1.87));
+  assertEq(y1.y, Math.fround(5.19));
+  assertEq(y1.z, Math.fround(3.84));
+  assertEq(y1.w, Math.fround(4.17));
+
+  assertEq(z1.x, Math.fround(1.87));
+  assertEq(z1.y, Math.fround(2.08));
+  assertEq(z1.z, Math.fround(5.11));
+  assertEq(z1.w, Math.fround(4.17));
+
+  assertEq(w1.x, Math.fround(1.87));
+  assertEq(w1.y, Math.fround(2.08));
+  assertEq(w1.z, Math.fround(3.84));
+  assertEq(w1.w, Math.fround(5.07));
+
+  var c = float32x4(NaN, -0, Infinity, -Infinity);
+  var x2 = SIMD.float32x4.withX(c, 0);
+  var y2 = SIMD.float32x4.withY(c, 0);
+  var z2 = SIMD.float32x4.withZ(c, 0);
+  var w2 = SIMD.float32x4.withW(c, 0);
+  assertEq(x2.x, 0);
+  assertEq(x2.y, -0);
+  assertEq(x2.z, Infinity);
+  assertEq(x2.w, -Infinity);
+
+  assertEq(y2.x, NaN);
+  assertEq(y2.y, 0);
+  assertEq(y2.z, Infinity);
+  assertEq(y2.w, -Infinity);
+
+  assertEq(z2.x, NaN);
+  assertEq(z2.y, -0);
+  assertEq(z2.z, 0);
+  assertEq(z2.w, -Infinity);
+
+  assertEq(w2.x, NaN);
+  assertEq(w2.y, -0);
+  assertEq(w2.z, Infinity);
+  assertEq(w2.w, 0);
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4xor.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4xor.js
@@ -14,24 +14,38 @@ var xorf = (function() {
         i[2] = i[0] ^ i[1];
         return f[2];
     }
 })();
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
-
   var a = float32x4(1, 2, 3, 4);
   var b = float32x4(10, 20, 30, 40);
   var c = SIMD.float32x4.xor(a, b);
   assertEq(c.x, xorf(1, 10));
   assertEq(c.y, xorf(2, 20));
   assertEq(c.z, xorf(3, 30));
   assertEq(c.w, xorf(4, 40));
 
+  var d = float32x4(1.07, 2.62, 3.79, 4.15);
+  var e = float32x4(10.38, 20.47, 30.44, 40.16);
+  var f = SIMD.float32x4.xor(d, e);
+  assertEq(f.x, xorf(1.07, 10.38));
+  assertEq(f.y, xorf(2.62, 20.47));
+  assertEq(f.z, xorf(3.79, 30.44));
+  assertEq(f.w, xorf(4.15, 40.16));
+
+  var g = float32x4(NaN, -0, Infinity, -Infinity);
+  var h = float32x4(-0, Infinity, -Infinity, NaN);
+  var i = SIMD.float32x4.xor(g, h);
+  assertEq(i.x, xorf(NaN, -0));
+  assertEq(i.y, xorf(-0, Infinity));
+  assertEq(i.z, xorf(Infinity, -Infinity));
+  assertEq(i.w, xorf(-Infinity, NaN));
+
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -3208,25 +3208,21 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
     // were not taken at the time of this writing, so we hazard a guess that
     // ASAN builds have roughly thrice the stack overhead as normal builds.
     // On normal builds, the largest stack frame size we might encounter is
     // 8.2k, so let's use a buffer of 8.2 * 3 * 10 = 246k.
     const size_t kStackQuota =  2 * kDefaultStackQuota;
     const size_t kTrustedScriptBuffer = 246 * 1024;
 #elif defined(XP_WIN)
-    // 1MB is the default stack size on Windows, so use 900k. And since 32-bit
-    // Windows stack frames are 3.4k each, let's use a buffer of 48k and double
-    // that for 64-bit.
-    //
-    // Note - Frames on Win32 PGO builds seem to have grown recently, and 48k
-    // stacks seem about 20-30% too small - so we bump it to 64k.
+    // 1MB is the default stack size on Windows, so use 900k.
+    // Windows PGO stack frames have unfortunately gotten pretty large lately. :-(
     const size_t kStackQuota = 900 * 1024;
-    const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 96 * 1024
-                                                              : 64 * 1024;
+    const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 120 * 1024
+                                                              : 80 * 1024;
     // The following two configurations are linux-only. Given the numbers above,
     // we use 50k and 100k trusted buffers on 32-bit and 64-bit respectively.
 #elif defined(DEBUG)
     // Bug 803182: account for the 4x difference in the size of js::Interpret
     // between optimized and debug builds.
     // XXXbholley - Then why do we only account for 2x of difference?
     const size_t kStackQuota = 2 * kDefaultStackQuota;
     const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
--- a/layout/base/SelectionCarets.cpp
+++ b/layout/base/SelectionCarets.cpp
@@ -379,49 +379,52 @@ SelectionCarets::UpdateSelectionCarets()
   }
 
   nsRefPtr<dom::Selection> selection = GetSelection();
   if (!selection) {
     SetVisibility(false);
     return;
   }
 
-  if (selection->GetRangeCount() <= 0) {
+  if (selection->IsCollapsed()) {
     SetVisibility(false);
     return;
   }
 
-  nsRefPtr<nsRange> range = selection->GetRangeAt(0);
-  if (range->Collapsed()) {
-    SetVisibility(false);
-    return;
-  }
+  int32_t rangeCount = selection->GetRangeCount();
+  nsRefPtr<nsRange> firstRange = selection->GetRangeAt(0);
+  nsRefPtr<nsRange> lastRange = selection->GetRangeAt(rangeCount - 1);
 
-  nsLayoutUtils::FirstAndLastRectCollector collector;
-  nsRange::CollectClientRects(&collector, range,
-                              range->GetStartParent(), range->StartOffset(),
-                              range->GetEndParent(), range->EndOffset(), true, true);
+  nsLayoutUtils::FirstAndLastRectCollector collectorStart;
+  nsRange::CollectClientRects(&collectorStart, firstRange,
+                              firstRange->GetStartParent(), firstRange->StartOffset(),
+                              firstRange->GetEndParent(), firstRange->EndOffset(), true, true);
+
+  nsLayoutUtils::FirstAndLastRectCollector collectorEnd;
+  nsRange::CollectClientRects(&collectorEnd, lastRange,
+                              lastRange->GetStartParent(), lastRange->StartOffset(),
+                              lastRange->GetEndParent(), lastRange->EndOffset(), true, true);
 
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
 
   if (!canvasFrame || !rootFrame) {
     SetVisibility(false);
     return;
   }
 
   // Check start and end frame is rtl or ltr text
   nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
   int32_t startOffset;
   nsIFrame* startFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
-                                                range, fs, false, startOffset);
+                                                firstRange, fs, false, startOffset);
 
   int32_t endOffset;
   nsIFrame* endFrame = FindFirstNodeWithFrame(mPresShell->GetDocument(),
-                                              range, fs, true, endOffset);
+                                              lastRange, fs, true, endOffset);
 
   if (!startFrame || !endFrame) {
     SetVisibility(false);
     return;
   }
 
   // If frame isn't editable and we don't support non-editable fields, bail
   // out.
@@ -437,46 +440,46 @@ SelectionCarets::UpdateSelectionCarets()
     return;
   }
 
   bool startFrameIsRTL = IsRightToLeft(startFrame);
   bool endFrameIsRTL = IsRightToLeft(endFrame);
 
   // If start frame is LTR, then place start caret in first rect's leftmost
   // otherwise put it to first rect's rightmost.
-  ReduceRectToVerticalEdge(collector.mFirstRect, startFrameIsRTL);
+  ReduceRectToVerticalEdge(collectorStart.mFirstRect, startFrameIsRTL);
 
   // Contrary to start frame, if end frame is LTR, put end caret to last
   // rect's rightmost position, otherwise, put it to last rect's leftmost.
-  ReduceRectToVerticalEdge(collector.mLastRect, !endFrameIsRTL);
+  ReduceRectToVerticalEdge(collectorEnd.mLastRect, !endFrameIsRTL);
 
   nsAutoTArray<nsIFrame*, 16> hitFramesInFirstRect;
   nsLayoutUtils::GetFramesForArea(rootFrame,
-    collector.mFirstRect,
+    collectorStart.mFirstRect,
     hitFramesInFirstRect,
     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
       nsLayoutUtils::IGNORE_CROSS_DOC |
       nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
 
   nsAutoTArray<nsIFrame*, 16> hitFramesInLastRect;
   nsLayoutUtils::GetFramesForArea(rootFrame,
-    collector.mLastRect,
+    collectorEnd.mLastRect,
     hitFramesInLastRect,
     nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
       nsLayoutUtils::IGNORE_CROSS_DOC |
       nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
 
   SetStartFrameVisibility(hitFramesInFirstRect.Contains(startFrame));
   SetEndFrameVisibility(hitFramesInLastRect.Contains(endFrame));
 
-  nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mFirstRect);
-  nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collector.mLastRect);
+  nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collectorStart.mFirstRect);
+  nsLayoutUtils::TransformRect(rootFrame, canvasFrame, collectorEnd.mLastRect);
 
-  SetStartFramePos(collector.mFirstRect.BottomLeft());
-  SetEndFramePos(collector.mLastRect.BottomRight());
+  SetStartFramePos(collectorStart.mFirstRect.BottomLeft());
+  SetEndFramePos(collectorEnd.mLastRect.BottomRight());
   SetVisibility(true);
 
   // If range select only one character, append tilt class name to it.
   bool isTilt = false;
   if (startFrame && endFrame) {
     // In this case <textarea>abc</textarea> and we select 'c' character,
     // EndContent would be HTMLDivElement and mResultContent which get by
     // calling startFrame->PeekOffset() with selecting next cluster would be
@@ -684,21 +687,23 @@ SelectionCarets::DragSelection(const nsP
 
   nsFrame::ContentOffsets offsets =
     newFrame->GetContentOffsetsFromPoint(newPoint);
   if (!offsets.content) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   nsRefPtr<dom::Selection> selection = GetSelection();
-  if (selection->GetRangeCount() <= 0) {
+  int32_t rangeCount = selection->GetRangeCount();
+  if (rangeCount <= 0) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
-  nsRefPtr<nsRange> range = selection->GetRangeAt(0);
+  nsRefPtr<nsRange> range = mDragMode == START_FRAME ?
+    selection->GetRangeAt(0) : selection->GetRangeAt(rangeCount - 1);
   if (!CompareRangeWithContentOffset(range, fs, offsets, mDragMode)) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   nsIFrame* anchorFrame;
   selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
   if (!anchorFrame) {
     return nsEventStatus_eConsumeNoDefault;
@@ -732,30 +737,32 @@ SelectionCarets::GetCaretYCenterPosition
 {
   nsIFrame* canvasFrame = mPresShell->GetCanvasFrame();
 
   if (!canvasFrame) {
     return 0;
   }
 
   nsRefPtr<dom::Selection> selection = GetSelection();
-  if (selection->GetRangeCount() <= 0) {
+  int32_t rangeCount = selection->GetRangeCount();
+  if (rangeCount <= 0) {
     return 0;
   }
 
-  nsRefPtr<nsRange> range = selection->GetRangeAt(0);
   nsRefPtr<nsFrameSelection> fs = GetFrameSelection();
 
   MOZ_ASSERT(mDragMode != NONE);
   nsCOMPtr<nsIContent> node;
   uint32_t nodeOffset;
   if (mDragMode == START_FRAME) {
+    nsRefPtr<nsRange> range = selection->GetRangeAt(0);
     node = do_QueryInterface(range->GetStartParent());
     nodeOffset = range->StartOffset();
   } else {
+    nsRefPtr<nsRange> range = selection->GetRangeAt(rangeCount - 1);
     node = do_QueryInterface(range->GetEndParent());
     nodeOffset = range->EndOffset();
   }
 
   int32_t offset;
   CaretAssociationHint hint =
     mDragMode == START_FRAME ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE;
   nsIFrame* theFrame =
@@ -880,22 +887,16 @@ SelectionCarets::GetFrameSelection()
   }
 }
 
 nsresult
 SelectionCarets::NotifySelectionChanged(nsIDOMDocument* aDoc,
                                        nsISelection* aSel,
                                        int16_t aReason)
 {
-  bool isCollapsed;
-  aSel->GetIsCollapsed(&isCollapsed);
-  if (isCollapsed) {
-    SetVisibility(false);
-    return NS_OK;
-  }
   if (!aReason || (aReason & (nsISelectionListener::DRAG_REASON |
                                nsISelectionListener::KEYPRESS_REASON |
                                nsISelectionListener::MOUSEDOWN_REASON))) {
     SetVisibility(false);
   } else {
     UpdateSelectionCarets();
   }
   return NS_OK;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4676,41 +4676,52 @@ static int32_t FindSafeLength(const char
 }
 
 static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics)
 {
   return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
 }
 
 nscoord
-nsLayoutUtils::AppUnitWidthOfString(const nsString& aString,
-                                    nsFontMetrics& aFontMetrics,
-                                    nsRenderingContext& aContext)
-{
-  return AppUnitWidthOfString(aString.get(), aString.Length(),
-                              aFontMetrics, aContext);
-}
-
-nscoord
 nsLayoutUtils::AppUnitWidthOfString(const char16_t *aString,
                                     uint32_t aLength,
                                     nsFontMetrics& aFontMetrics,
                                     nsRenderingContext& aContext)
 {
   uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
   nscoord width = 0;
   while (aLength > 0) {
     int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
     width += aFontMetrics.GetWidth(aString, len, &aContext);
     aLength -= len;
     aString += len;
   }
   return width;
 }
 
+nscoord
+nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
+                                        uint32_t aLength,
+                                        const nsIFrame* aFrame,
+                                        nsFontMetrics& aFontMetrics,
+                                        nsRenderingContext& aContext)
+{
+  nsPresContext* presContext = aFrame->PresContext();
+  if (presContext->BidiEnabled()) {
+    nsBidiLevel level =
+      nsBidiPresUtils::BidiLevelFromStyle(aFrame->StyleContext());
+    return nsBidiPresUtils::MeasureTextWidth(aString, aLength, level,
+                                             presContext, aContext,
+                                             aFontMetrics);
+  }
+  aFontMetrics.SetTextRunRTL(false);
+  return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
+                                             aContext);
+}
+
 nsBoundingMetrics
 nsLayoutUtils::AppUnitBoundsOfString(const char16_t* aString,
                                      uint32_t aLength,
                                      nsFontMetrics& aFontMetrics,
                                      nsRenderingContext& aContext)
 {
   uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
   int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
@@ -4794,36 +4805,16 @@ nsLayoutUtils::DrawUniDirString(const ch
     if (!isRTL) {
       x += width;
     }
     aLength -= len;
     aString += len;
   }
 }
 
-nscoord
-nsLayoutUtils::GetStringWidth(const nsIFrame*      aFrame,
-                              nsRenderingContext* aContext,
-                              nsFontMetrics&      aFontMetrics,
-                              const char16_t*     aString,
-                              int32_t              aLength)
-{
-  nsPresContext* presContext = aFrame->PresContext();
-  if (presContext->BidiEnabled()) {
-    nsBidiLevel level =
-      nsBidiPresUtils::BidiLevelFromStyle(aFrame->StyleContext());
-    return nsBidiPresUtils::MeasureTextWidth(aString, aLength,
-                                             level, presContext, *aContext,
-                                             aFontMetrics);
-  }
-  aFontMetrics.SetTextRunRTL(false);
-  return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
-                                             *aContext);
-}
-
 /* static */ void
 nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame,
                                nsRenderingContext* aContext,
                                const nsRect& aTextRect,
                                const nsRect& aDirtyRect,
                                const nscolor& aForegroundColor,
                                TextShadowCallback aCallback,
                                void* aCallbackData)
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1321,21 +1321,38 @@ public:
 
   static nscoord AppUnitWidthOfString(char16_t aC,
                                       nsFontMetrics& aFontMetrics,
                                       nsRenderingContext& aContext) {
     return AppUnitWidthOfString(&aC, 1, aFontMetrics, aContext);
   }
   static nscoord AppUnitWidthOfString(const nsString& aString,
                                       nsFontMetrics& aFontMetrics,
-                                      nsRenderingContext& aContext);
+                                      nsRenderingContext& aContext) {
+    return nsLayoutUtils::AppUnitWidthOfString(aString.get(), aString.Length(),
+                                               aFontMetrics, aContext);
+  }
   static nscoord AppUnitWidthOfString(const char16_t *aString,
                                       uint32_t aLength,
                                       nsFontMetrics& aFontMetrics,
                                       nsRenderingContext& aContext);
+  static nscoord AppUnitWidthOfStringBidi(const nsString& aString,
+                                          const nsIFrame* aFrame,
+                                          nsFontMetrics& aFontMetrics,
+                                          nsRenderingContext& aContext) {
+    return nsLayoutUtils::AppUnitWidthOfStringBidi(aString.get(),
+                                                   aString.Length(), aFrame,
+                                                   aFontMetrics, aContext);
+  }
+  static nscoord AppUnitWidthOfStringBidi(const char16_t* aString,
+                                          uint32_t aLength,
+                                          const nsIFrame* aFrame,
+                                          nsFontMetrics& aFontMetrics,
+                                          nsRenderingContext& aContext);
+
   static nsBoundingMetrics AppUnitBoundsOfString(const char16_t* aString,
                                                  uint32_t aLength,
                                                  nsFontMetrics& aFontMetrics,
                                                  nsRenderingContext& aContext);
 
   static void DrawString(const nsIFrame*       aFrame,
                          nsFontMetrics&        aFontMetrics,
                          nsRenderingContext*   aContext,
@@ -1348,22 +1365,16 @@ public:
    * Supports only LTR or RTL. Bidi (mixed direction) is not supported.
    */
   static void DrawUniDirString(const char16_t* aString,
                                uint32_t aLength,
                                nsPoint aPoint,
                                nsFontMetrics& aFontMetrics,
                                nsRenderingContext& aContext);
 
-  static nscoord GetStringWidth(const nsIFrame*      aFrame,
-                                nsRenderingContext* aContext,
-                                nsFontMetrics&      aFontMetrics,
-                                const char16_t*     aString,
-                                int32_t              aLength);
-
   /**
    * Helper function for drawing text-shadow. The callback's job
    * is to draw whatever needs to be blurred onto the given context.
    */
   typedef void (* TextShadowCallback)(nsRenderingContext* aCtx,
                                       nsPoint aShadowOffset,
                                       const nscolor& aShadowColor,
                                       void* aData);
--- a/layout/base/tests/marionette/manifest.ini
+++ b/layout/base/tests/marionette/manifest.ini
@@ -8,8 +8,9 @@ browser = true
 ; true if the test is compatible with b2g, otherwise false
 b2g = true
 
 ; true if the test should be skipped
 skip = false
 
 [test_touchcaret.py]
 [test_selectioncarets.py]
+[test_selectioncarets_multiplerange.py]
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/marionette/test_selectioncarets_multiplerange.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+# 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 by import By
+from marionette import Actions
+from marionette_test import MarionetteTestCase
+from selection import SelectionManager
+
+
+class SelectionCaretsMultipleRangeTest(MarionetteTestCase):
+    _long_press_time = 1        # 1 second
+
+    def setUp(self):
+        # Code to execute before a tests are run.
+        MarionetteTestCase.setUp(self)
+        self.actions = Actions(self.marionette)
+
+    def openTestHtml(self, enabled=True):
+        # Open html for testing and enable selectioncaret and
+        # non-editable support
+        self.marionette.execute_script(
+            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' %
+            ('true' if enabled else 'false'))
+        self.marionette.execute_script(
+            'SpecialPowers.setBoolPref("selectioncaret.noneditable", %s);' %
+            ('true' if enabled else 'false'))
+
+        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
+        self.marionette.navigate(test_html)
+
+        self._body = self.marionette.find_element(By.ID, 'bd')
+        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
+        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
+        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
+        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')
+
+    def _long_press_without_contextmenu(self, el, x, y):
+        return self.actions.press(el, x, y).move_by_offset(0, 0).\
+            wait(self._long_press_time).release()
+
+    def _long_press_to_select_word(self, el, wordOrdinal):
+        sel = SelectionManager(el)
+        original_content = sel.content
+        words = original_content.split()
+        self.assertTrue(wordOrdinal < len(words),
+            'Expect at least %d words in the content.' % wordOrdinal)
+
+        # Calc offset
+        offset = 0
+        for i in range(wordOrdinal):
+            offset += (len(words[i]) + 1)
+
+        # Move caret inside the word.
+        el.tap()
+        sel.move_caret_to_front()
+        sel.move_caret_by_offset(offset)
+        x, y = sel.caret_location()
+
+        # Long press the caret position. Selection carets should appear, and the
+        # word will be selected. On Windows, those spaces after the word
+        # will also be selected.
+        self._long_press_without_contextmenu(el, x, y).perform()
+
+    def _to_unix_line_ending(self, s):
+        """Changes all Windows/Mac line endings in s to UNIX line endings."""
+
+        return s.replace('\r\n', '\n').replace('\r', '\n')
+
+    def test_long_press_to_select_non_selectable_word(self):
+        '''Testing long press on non selectable field.
+        We should not select anything when long press on non selectable fields.'''
+
+        self.openTestHtml(enabled=True)
+        halfY = self._nonsel1.size['height'] / 2
+        self._long_press_without_contextmenu(self._nonsel1, 0, halfY).perform()
+        sel = SelectionManager(self._nonsel1)
+        range_count = sel.range_count()
+        self.assertEqual(range_count, 0)
+
+    def test_drag_caret_over_non_selectable_field(self):
+        '''Testing drag caret over non selectable field.
+        So that the selected content should exclude non selectable field and
+        end selection caret should appear in last range's position.'''
+        self.openTestHtml(enabled=True)
+
+        # Select target element and get target caret location
+        self._long_press_to_select_word(self._sel4, 3)
+        sel = SelectionManager(self._body)
+        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
+
+        self._long_press_to_select_word(self._sel6, 0)
+        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()
+
+        # Select start element
+        self._long_press_to_select_word(self._sel3, 3)
+
+        # Drag end caret to target location
+        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
+        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()
+        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
+            'this 3\nuser can select this')
+
+        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
+        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y).perform()
+        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
+            'this 3\nuser can select this 4\nuser can select this 5\nuser')
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -758,18 +758,17 @@ TextOverflow::Marker::SetupString(nsIFra
       mWidth = 0;
     }
   } else {
     nsRefPtr<nsRenderingContext> rc =
       aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
     nsRefPtr<nsFontMetrics> fm;
     nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
       nsLayoutUtils::FontSizeInflationFor(aFrame));
-    mWidth = nsLayoutUtils::GetStringWidth(aFrame, rc, *fm,
-                                           mStyle->mString.get(),
-                                           mStyle->mString.Length());
+    mWidth = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
+                                                     *fm, *rc);
   }
   mIntrinsicISize = mWidth;
   mInitialized = true;
 }
 
 }  // namespace css
 }  // namespace mozilla
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -597,18 +597,18 @@ nsBulletFrame::GetDesiredSize(nsPresCont
       }
       AppendSpacingToPadding(fm);
       break;
 
     default:
       GetListItemText(text);
       finalSize.BSize(wm) = fm->MaxHeight();
       finalSize.ISize(wm) =
-        nsLayoutUtils::GetStringWidth(this, aRenderingContext, *fm,
-                                      text.get(), text.Length());
+        nsLayoutUtils::AppUnitWidthOfStringBidi(text, this, *fm,
+                                                *aRenderingContext);
       aMetrics.SetBlockStartAscent(fm->MaxAscent());
       break;
   }
   aMetrics.SetSize(wm, finalSize);
 }
 
 void
 nsBulletFrame::Reflow(nsPresContext* aPresContext,
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1030,17 +1030,18 @@ nsImageFrame::MeasureString(const char16
         len = i;  // don't include the space when measuring
         trailingSpace = true;
         break;
       }
     }
   
     // Measure this chunk of text, and see if it fits
     nscoord width =
-      nsLayoutUtils::GetStringWidth(this, &aContext, aFontMetrics, aString, len);
+      nsLayoutUtils::AppUnitWidthOfStringBidi(aString, len, this, aFontMetrics,
+                                              aContext);
     bool    fits = (totalWidth + width) <= aMaxWidth;
 
     // If it fits on the line, or it's the first word we've processed then
     // include it
     if (fits || (0 == totalWidth)) {
       // New piece fits
       totalWidth += width;
 
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -238,20 +238,19 @@ nsPageFrame::ProcessSpecialCodes(const n
 
 //------------------------------------------------------------------------------
 nscoord nsPageFrame::GetXPosition(nsRenderingContext& aRenderingContext,
                                   nsFontMetrics&       aFontMetrics,
                                   const nsRect&        aRect, 
                                   int32_t              aJust,
                                   const nsString&      aStr)
 {
-  nscoord width = nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
-                                                aFontMetrics,
-                                                aStr.get(), aStr.Length());
-
+  nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(aStr, this,
+                                                          aFontMetrics,
+                                                          aRenderingContext);
   nscoord x = aRect.x;
   switch (aJust) {
     case nsIPrintSettings::kJustLeft:
       x += mPD->mEdgePaperMargin.left;
       break;
 
     case nsIPrintSettings::kJustCenter:
       x += (aRect.width - width) / 2;
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -95,20 +95,18 @@ static void
 EndSwapDocShellsForViews(nsView* aView);
 
 void
 nsSubDocumentFrame::Init(nsIContent*       aContent,
                          nsContainerFrame* aParent,
                          nsIFrame*         aPrevInFlow)
 {
   // determine if we are a <frame> or <iframe>
-  if (aContent) {
-    nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = do_QueryInterface(aContent);
-    mIsInline = frameElem ? false : true;
-  }
+  nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = do_QueryInterface(aContent);
+  mIsInline = frameElem ? false : true;
 
   nsLeafFrame::Init(aContent, aParent, aPrevInFlow);
 
   // We are going to create an inner view.  If we need a view for the
   // OuterFrame but we wait for the normal view creation path in
   // nsCSSFrameConstructor, then we will lose because the inner view's
   // parent will already have been set to some outer view (e.g., the
   // canvas) when it really needs to have this frame's view as its
new file mode 100644
--- /dev/null
+++ b/layout/reftests/mathml/mfenced-12-ref.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html class="reftest-wait">
+
+<math style="font-size:25px; position: absolute; top: 10px; left:10px;">
+  <mrow id="outer">
+    <mo>(</mo>
+    <mrow>
+      <mn id="a" style="visibility:hidden;">a</mn>
+      <mo>&amp;</mo>
+      <mn id="c" style="visibility:hidden;">c</mn>
+      </mrow>
+    <mo>)</mo>
+  </mrow>
+</math>
+
+<!-- Implementation kludge.  <mfenced> renders the position of the ampersand in
+     a slightly different position compared to <mo>+<mrow>.
+     In this test we are only concerned about the size of the fences "(" and
+     ")", so the ampersand gets redacted. -->
+<div id="div" style="position: absolute; background:black; top: 0px;
+                     height: 120px;"></div>
+
+<script>
+  function doTest()
+  {
+    a = document.getElementById("a");
+    c = document.getElementById("c");
+    div = document.getElementById("div");
+    outer = document.getElementById("outer");
+
+    left = a.getBoundingClientRect().left;  // div's left
+    div.style.left = left + 'px';
+    div.style.width = (c.getBoundingClientRect().right - left ) + 'px';
+
+    document.documentElement.removeAttribute("class");
+   }
+   window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/mathml/mfenced-12.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html class="reftest-wait">
+
+<math style="font-size:25px; position: absolute; top: 10px; left:10px;">
+  <mfenced id="outer" separators="&amp;">
+      <mn id="a" style="visibility:hidden;">a</mn>
+      <mn id="c" style="visibility:hidden;">c</mn>
+  </mfenced>
+</math>
+
+<!-- Implementation kludge.  <mfenced> renders the position of the ampersand in
+     a slightly different position compared to <mo>+<mrow>.
+     In this test we are only concerned about the size of the fences "(" and
+     ")", so the ampersand gets redacted. -->
+<div id="div" style="position: absolute; background:black; top: 0px;
+                     height: 120px;"></div>
+
+<script>
+  function doTest()
+  {
+    a = document.getElementById("a");
+    c = document.getElementById("c");
+    div = document.getElementById("div");
+    outer = document.getElementById("outer");
+
+    left = a.getBoundingClientRect().left;  // div's left
+    div.style.left = left + 'px';
+    div.style.width = (c.getBoundingClientRect().right - left ) + 'px';
+
+    document.documentElement.removeAttribute("class");
+   }
+   window.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+
+</html>
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -33,16 +33,17 @@ skip-if(B2G&&browserIsRemote) random-if(
 == mfenced-5c.xhtml mfenced-5-ref.xhtml
 == mfenced-5d.xhtml mfenced-5-ref.xhtml
 == mfenced-6.html mfenced-6-ref.html
 == mfenced-7.html mfenced-7-ref.html
 != mfenced-8.html mfenced-8-ref.html
 == mfenced-9.html mfenced-9-ref.html
 fails-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) == mfenced-10.html mfenced-10-ref.html # Windows versions without Cambria Math, see bug 670592
 fails-if(winWidget&&d2d) == mfenced-11.html mfenced-11-ref.html
+== mfenced-12.html mfenced-12-ref.html
 == mi-mathvariant-1.xhtml mi-mathvariant-1-ref.xhtml
 == mi-mathvariant-2.xhtml mi-mathvariant-2-ref.xhtml
 != mi-mathvariant-3.html mi-mathvariant-3-ref.html
 != non-spacing-accent-1.xhtml non-spacing-accent-1-ref.xhtml
 == overbar-width-1.xhtml overbar-width-1-ref.xhtml
 skip-if(B2G) == quotes-1.xhtml quotes-1-ref.xhtml
 != stretchy-underbar-1.xhtml stretchy-underbar-1-ref.xhtml
 != stretchy-munderover-1a.html stretchy-munderover-1-ref.html
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3035,16 +3035,20 @@ struct nsStyleSVGReset {
     return NS_CombineHint(nsChangeHint_NeedReflow,
                           nsChangeHint_ClearAncestorIntrinsics);
   }
 
   bool HasFilters() const {
     return mFilters.Length() > 0;
   }
 
+  bool HasNonScalingStroke() const {
+    return mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE;
+  }
+
   nsStyleClipPath mClipPath;          // [reset]
   nsTArray<nsStyleFilter> mFilters;   // [reset]
   nsCOMPtr<nsIURI> mMask;             // [reset]
   nscolor          mStopColor;        // [reset]
   nscolor          mFloodColor;       // [reset]
   nscolor          mLightingColor;    // [reset]
 
   float            mStopOpacity;      // [reset]
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -458,123 +458,142 @@ nsSVGPathGeometryFrame::GetBBoxContribut
   if (aToBBoxUserspace.IsSingular()) {
     // XXX ReportToConsole
     return bbox;
   }
 
   nsSVGPathGeometryElement* element =
     static_cast<nsSVGPathGeometryElement*>(mContent);
 
-  RefPtr<DrawTarget> tmpDT;
-#ifdef XP_WIN
-  // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
-  // when whole number results are expected, even in the case of trivial
-  // calculations. To avoid that and meet the expectations of web content we
-  // have to use a CAIRO DrawTarget. The most efficient way to do that is to
-  // wrap the cached cairo_surface_t from ScreenReferenceSurface():
-  nsRefPtr<gfxASurface> refSurf =
-    gfxPlatform::GetPlatform()->ScreenReferenceSurface();
-  tmpDT = gfxPlatform::GetPlatform()->
-    CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
-#else
-  tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-#endif
+  bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+                 ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
+                  StyleSVG()->mFill.mType != eStyleSVGPaintType_None);
+
+  bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+                   ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+                    nsSVGUtils::HasStroke(this));
 
-  FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
-  RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
-  if (!pathInUserSpace) {
-    return bbox;
-  }
-  RefPtr<Path> pathInBBoxSpace;
-  if (aToBBoxUserspace.IsIdentity()) {
-    pathInBBoxSpace = pathInUserSpace;
-  } else {
-    RefPtr<PathBuilder> builder =
-      pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
-    pathInBBoxSpace = builder->Finish();
-    if (!pathInBBoxSpace) {
-      return bbox;
+  bool gotSimpleBounds = false;
+  if (!StyleSVGReset()->HasNonScalingStroke()) {
+    Float strokeWidth = getStroke ? nsSVGUtils::GetStrokeWidth(this) : 0.f;
+    Rect simpleBounds;
+    gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeWidth,
+                                                 aToBBoxUserspace);
+    if (gotSimpleBounds) {
+      bbox = simpleBounds;
     }
   }
 
-  // Be careful when replacing the following logic to get the fill and stroke
-  // extents independently (instead of computing the stroke extents from the
-  // path extents). You may think that you can just use the stroke extents if
-  // there is both a fill and a stroke. In reality it's necessary to calculate
-  // both the fill and stroke extents, and take the union of the two. There are
-  // two reasons for this:
-  //
-  // # Due to stroke dashing, in certain cases the fill extents could actually
-  //   extend outside the stroke extents.
-  // # If the stroke is very thin, cairo won't paint any stroke, and so the
-  //   stroke bounds that it will return will be empty.
+  if (!gotSimpleBounds) {
+    // Get the bounds using a Moz2D Path object (more expensive):
+    RefPtr<DrawTarget> tmpDT;
+#ifdef XP_WIN
+    // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
+    // when whole number results are expected, even in the case of trivial
+    // calculations. To avoid that and meet the expectations of web content we
+    // have to use a CAIRO DrawTarget. The most efficient way to do that is to
+    // wrap the cached cairo_surface_t from ScreenReferenceSurface():
+    nsRefPtr<gfxASurface> refSurf =
+      gfxPlatform::GetPlatform()->ScreenReferenceSurface();
+    tmpDT = gfxPlatform::GetPlatform()->
+      CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
+#else
+    tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+#endif
 
-  Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
-  if (!pathBBoxExtents.IsFinite()) {
-    // This can happen in the case that we only have a move-to command in the
-    // path commands, in which case we know nothing gets rendered.
-    return bbox;
-  }
+    FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+    RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
+    if (!pathInUserSpace) {
+      return bbox;
+    }
+    RefPtr<Path> pathInBBoxSpace;
+    if (aToBBoxUserspace.IsIdentity()) {
+      pathInBBoxSpace = pathInUserSpace;
+    } else {
+      RefPtr<PathBuilder> builder =
+        pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+      pathInBBoxSpace = builder->Finish();
+      if (!pathInBBoxSpace) {
+        return bbox;
+      }
+    }
 
-  // Account for fill:
-  if ((aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
-      ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
-       StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
-    bbox = pathBBoxExtents;
-  }
+    // Be careful when replacing the following logic to get the fill and stroke
+    // extents independently (instead of computing the stroke extents from the
+    // path extents). You may think that you can just use the stroke extents if
+    // there is both a fill and a stroke. In reality it's necessary to
+    // calculate both the fill and stroke extents, and take the union of the
+    // two. There are two reasons for this:
+    //
+    // # Due to stroke dashing, in certain cases the fill extents could
+    //   actually extend outside the stroke extents.
+    // # If the stroke is very thin, cairo won't paint any stroke, and so the
+    //   stroke bounds that it will return will be empty.
 
-  // Account for stroke:
-  if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
-      ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
-       nsSVGUtils::HasStroke(this))) {
+    Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+    if (!pathBBoxExtents.IsFinite()) {
+      // This can happen in the case that we only have a move-to command in the
+      // path commands, in which case we know nothing gets rendered.
+      return bbox;
+    }
+
+    // Account for fill:
+    if (getFill) {
+      bbox = pathBBoxExtents;
+    }
+
+    // Account for stroke:
+    if (getStroke) {
 #if 0
-    // This disabled code is how we would calculate the stroke bounds using
-    // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing it
-    // there are two problems that prevent us from using it.
-    //
-    // First, it seems that some of the Moz2D backends are really dumb. Not
-    // only do some GetStrokeOptions() implementations sometimes significantly
-    // overestimate the stroke bounds, but if an argument is passed for the
-    // aTransform parameter then they just return bounds-of-transformed-bounds.
-    // These two things combined can lead the bounds to be unacceptably
-    // oversized, leading to massive over-invalidation.
-    //
-    // Second, the way we account for non-scaling-stroke by transforming the
-    // path using the transform to the outer-<svg> element is not compatible
-    // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale into
-    // aToBBoxUserspace and then scales the bounds that we return.
-    SVGContentUtils::AutoStrokeOptions strokeOptions;
-    SVGContentUtils::GetStrokeOptions(&strokeOptions, element, StyleContext(),
-                                      nullptr, SVGContentUtils::eIgnoreStrokeDashing);
-    Rect strokeBBoxExtents;
-    gfxMatrix userToOuterSVG;
-    if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
-      Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
-      outerSVGToUser.Invert();
-      Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
-      RefPtr<PathBuilder> builder =
-        pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
-      RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
-      strokeBBoxExtents =
-        pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
-    } else {
-      strokeBBoxExtents =
-        pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
-    }
-    MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
-    bbox.UnionEdges(strokeBBoxExtents);
+      // This disabled code is how we would calculate the stroke bounds using
+      // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
+      // it there are two problems that prevent us from using it.
+      //
+      // First, it seems that some of the Moz2D backends are really dumb. Not
+      // only do some GetStrokeOptions() implementations sometimes
+      // significantly overestimate the stroke bounds, but if an argument is
+      // passed for the aTransform parameter then they just return bounds-of-
+      // transformed-bounds.  These two things combined can lead the bounds to
+      // be unacceptably oversized, leading to massive over-invalidation.
+      //
+      // Second, the way we account for non-scaling-stroke by transforming the
+      // path using the transform to the outer-<svg> element is not compatible
+      // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale
+      // into aToBBoxUserspace and then scales the bounds that we return.
+      SVGContentUtils::AutoStrokeOptions strokeOptions;
+      SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+                                        StyleContext(), nullptr,
+                                        SVGContentUtils::eIgnoreStrokeDashing);
+      Rect strokeBBoxExtents;
+      gfxMatrix userToOuterSVG;
+      if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+        Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+        outerSVGToUser.Invert();
+        Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+        RefPtr<PathBuilder> builder =
+          pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+        RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+        strokeBBoxExtents =
+          pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+      } else {
+        strokeBBoxExtents =
+          pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
+      }
+      MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+      bbox.UnionEdges(strokeBBoxExtents);
 #else
     // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
-    gfxRect strokeBBoxExtents =
-      nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
-                                                this,
-                                                ThebesMatrix(aToBBoxUserspace));
-    MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
-    bbox.UnionEdges(strokeBBoxExtents);
+      gfxRect strokeBBoxExtents =
+        nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
+                                                  this,
+                                                  ThebesMatrix(aToBBoxUserspace));
+      MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
+      bbox.UnionEdges(strokeBBoxExtents);
 #endif
+    }
   }
 
   // Account for markers:
   if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
       static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
 
     float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
     MarkerProperties properties = GetMarkerProperties(this);
--- a/layout/tools/recording/recording-cmdline.js
+++ b/layout/tools/recording/recording-cmdline.js
@@ -62,13 +62,13 @@ RecordingCmdLineHandler.prototype =
 
         var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                                .getService(nsIWindowWatcher);
         wwatch.openWindow(null, "chrome://recording/content/recording.xul", "_blank",
                           "chrome,dialog=no,all", args);
         cmdLine.preventDefault = true;
     },
 
-    helpInfo : "  -recording <file>  Record drawing for a given URL.\n" +
-               "  -recording-output <file> Specify destination file for a drawing recording.\n"
+    helpInfo : "  --recording <file> Record drawing for a given URL.\n" +
+               "  --recording-output <file> Specify destination file for a drawing recording.\n"
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RecordingCmdLineHandler]);
--- a/layout/tools/reftest/reftest-cmdline.js
+++ b/layout/tools/reftest/reftest-cmdline.js
@@ -100,12 +100,12 @@ RefTestCmdLineHandler.prototype =
         dummy.focus();
         loadReftests();
       }
     }
 
     cmdLine.preventDefault = true;
   },
 
-  helpInfo : "  -reftest <file>    Run layout acceptance tests on given manifest.\n"
+  helpInfo : "  --reftest <file>   Run layout acceptance tests on given manifest.\n"
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RefTestCmdLineHandler]);
--- a/layout/xul/nsListBoxBodyFrame.cpp
+++ b/layout/xul/nsListBoxBodyFrame.cpp
@@ -722,18 +722,18 @@ nsListBoxBodyFrame::ComputeIntrinsicISiz
             }
           }
 
           nsRefPtr<nsFontMetrics> fm;
           nsLayoutUtils::GetFontMetricsForStyleContext(styleContext,
                                                        getter_AddRefs(fm));
 
           nscoord textWidth =
-            nsLayoutUtils::GetStringWidth(this, rendContext, *fm,
-                                          value.get(), value.Length());
+            nsLayoutUtils::AppUnitWidthOfStringBidi(value, this, *fm,
+                                                    *rendContext);
           textWidth += width;
 
           if (textWidth > largestWidth) 
             largestWidth = textWidth;
         }
       }
     }
   }
--- a/layout/xul/nsTextBoxFrame.cpp
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -608,20 +608,19 @@ nsTextBoxFrame::CalculateTitleForWidth(n
         mCroppedTitle.Truncate();
         return 0;
     }
 
     nsRefPtr<nsFontMetrics> fm;
     nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
 
     // see if the text will completely fit in the width given
-    nscoord titleWidth = nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
-                                                       *fm,
-                                                       mTitle.get(), mTitle.Length());
-
+    nscoord titleWidth =
+      nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
+                                              aRenderingContext);
     if (titleWidth <= aWidth) {
         mCroppedTitle = mTitle;
         if (HasRTLChars(mTitle)) {
             mState |= NS_FRAME_IS_BIDI;
         }
         return titleWidth;  // fits, done.
     }
 
@@ -708,18 +707,18 @@ nsTextBoxFrame::CalculateTitleForWidth(n
             mTitle.Right(copy, length-1-i);
             mCroppedTitle += copy;
         }
         break;
 
         case CropCenter:
         {
             nscoord stringWidth =
-                nsLayoutUtils::GetStringWidth(this, &aRenderingContext, *fm,
-                                              mTitle.get(), mTitle.Length());
+                nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm,
+                                                        aRenderingContext);
             if (stringWidth <= aWidth) {
                 // the entire string will fit in the maximum width
                 mCroppedTitle.Insert(mTitle, 0);
                 break;
             }
 
             // determine how much of the string will fit in the max width
             nscoord charWidth = 0;
@@ -766,18 +765,18 @@ nsTextBoxFrame::CalculateTitleForWidth(n
                 rightPos--;
             }
 
             mCroppedTitle = leftString + kEllipsis + rightString;
         }
         break;
     }
 
-    return nsLayoutUtils::GetStringWidth(this, &aRenderingContext, *fm,
-                                         mCroppedTitle.get(), mCroppedTitle.Length());
+    return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm,
+                                                   aRenderingContext);
 }
 
 #define OLD_ELLIPSIS NS_LITERAL_STRING("...")
 
 // the following block is to append the accesskey to mTitle if there is an accesskey
 // but the mTitle doesn't have the character
 void
 nsTextBoxFrame::UpdateAccessTitle()
@@ -995,18 +994,18 @@ nsTextBoxFrame::GetTextSize(nsPresContex
                             nsRenderingContext& aRenderingContext,
                             const nsString& aString,
                             nsSize& aSize, nscoord& aAscent)
 {
     nsRefPtr<nsFontMetrics> fontMet;
     nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
     aSize.height = fontMet->MaxHeight();
     aSize.width =
-      nsLayoutUtils::GetStringWidth(this, &aRenderingContext, *fontMet,
-                                    aString.get(), aString.Length());
+      nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet,
+                                              aRenderingContext);
     aAscent = fontMet->MaxAscent();
 }
 
 void
 nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState)
 {
     if (mNeedsRecalc)
     {
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -1314,19 +1314,19 @@ void
 nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText,
                                    int32_t aRowIndex,  nsTreeColumn* aColumn,
                                    nsRenderingContext& aRenderingContext,
                                    nsFontMetrics& aFontMetrics,
                                    nsRect& aTextRect)
 {
   NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
 
-  nscoord width =
-    nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aFontMetrics,
-                                  aText.get(), aText.Length());
+  nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this,
+                                                          aFontMetrics,
+                                                          aRenderingContext);
   nscoord maxWidth = aTextRect.width;
 
   if (aColumn->Overflow()) {
     DebugOnly<nsresult> rv;
     nsTreeColumn* nextColumn = aColumn->GetNext();
     while (nextColumn && width > maxWidth) {
       while (nextColumn) {
         nscoord width;
@@ -1454,19 +1454,18 @@ nsTreeBodyFrame::AdjustForCellText(nsAut
           aText = leftStr;
           aText.Append(kEllipsis);
           aText += rightStr;
         }
         break;
       }
     }
 
-    width = nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
-                                          aFontMetrics, aText.get(),
-                                          aText.Length());
+    width = nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, aFontMetrics,
+                                                    aRenderingContext);
   }
 
   switch (aColumn->GetTextAlignment()) {
     case NS_STYLE_TEXT_ALIGN_RIGHT: {
       aTextRect.x += aTextRect.width - width;
     }
     break;
     case NS_STYLE_TEXT_ALIGN_CENTER: {
@@ -1743,19 +1742,18 @@ nsTreeBodyFrame::GetCellWidth(int32_t aR
 
   // Get the borders and padding for the text.
   GetBorderPadding(textContext, bp);
 
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForStyleContext(textContext,
                                                getter_AddRefs(fm));
   // Get the width of the text itself
-  nscoord width =
-    nsLayoutUtils::GetStringWidth(this, aRenderingContext, *fm,
-                                  cellText.get(), cellText.Length());
+  nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm,
+                                                          *aRenderingContext);
   nscoord totalTextWidth = width + bp.left + bp.right;
   aDesiredSize += totalTextWidth;
   return NS_OK;
 }
 
 nsresult
 nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval)
 {  
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -222,28 +222,16 @@ StatusMsg(const char* aFmt, ...)
 /* static */ void
 InfallibleAllocPolicy::ExitOnFailure(const void* aP)
 {
   if (!aP) {
     MOZ_CRASH("DMD out of memory; aborting");
   }
 }
 
-class FpWriteFunc : public JSONWriteFunc
-{
-public:
-  explicit FpWriteFunc(FILE* aFp) : mFp(aFp) {}
-  ~FpWriteFunc() { fclose(mFp); }
-
-  void Write(const char* aStr) { fputs(aStr, mFp); }
-
-private:
-  FILE* mFp;
-};
-
 static double
 Percent(size_t part, size_t whole)
 {
   return (whole == 0) ? 0 : 100 * (double)part / whole;
 }
 
 // Commifies the number.
 static char*
@@ -284,46 +272,38 @@ class Options
     const T mDefault;
     const T mMax;
     T       mActual;
     NumOption(T aDefault, T aMax)
       : mDefault(aDefault), mMax(aMax), mActual(aDefault)
     {}
   };
 
-  enum Mode {
-    Normal,   // run normally
-    Test      // do some basic correctness tests
-  };
-
   char* mDMDEnvVar;   // a saved copy, for later printing
 
   NumOption<size_t>   mSampleBelowSize;
   NumOption<uint32_t> mMaxFrames;
   bool mShowDumpStats;
-  Mode mMode;
 
   void BadArg(const char* aArg);
   static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
   static bool GetLong(const char* aArg, const char* aOptionName,
                       long aMin, long aMax, long* aValue);
   static bool GetBool(const char* aArg, const char* aOptionName, bool* aValue);
 
 public:
   explicit Options(const char* aDMDEnvVar);
 
   const char* DMDEnvVar() const { return mDMDEnvVar; }
 
   size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
   size_t MaxFrames()       const { return mMaxFrames.mActual; }
   size_t ShowDumpStats()   const { return mShowDumpStats; }
 
-  void SetSampleBelowSize(size_t aN) { mSampleBelowSize.mActual = aN; }
-
-  bool IsTestMode()   const { return mMode == Test; }
+  void SetSampleBelowSize(size_t aSize) { mSampleBelowSize.mActual = aSize; }
 };
 
 static Options *gOptions;
 
 //---------------------------------------------------------------------------
 // The global lock
 //---------------------------------------------------------------------------
 
@@ -333,63 +313,39 @@ static Options *gOptions;
 
 class MutexBase
 {
   CRITICAL_SECTION mCS;
 
   DISALLOW_COPY_AND_ASSIGN(MutexBase);
 
 public:
-  MutexBase()
-  {
-    InitializeCriticalSection(&mCS);
-  }
-
-  ~MutexBase()
-  {
-    DeleteCriticalSection(&mCS);
-  }
+  MutexBase()  { InitializeCriticalSection(&mCS); }
+  ~MutexBase() { DeleteCriticalSection(&mCS); }
 
-  void Lock()
-  {
-    EnterCriticalSection(&mCS);
-  }
-
-  void Unlock()
-  {
-    LeaveCriticalSection(&mCS);
-  }
+  void Lock()   { EnterCriticalSection(&mCS); }
+  void Unlock() { LeaveCriticalSection(&mCS); }
 };
 
 #else
 
 #include <pthread.h>
 #include <sys/types.h>
 
 class MutexBase
 {
   pthread_mutex_t mMutex;
 
   DISALLOW_COPY_AND_ASSIGN(MutexBase);
 
 public:
-  MutexBase()
-  {
-    pthread_mutex_init(&mMutex, nullptr);
-  }
+  MutexBase() { pthread_mutex_init(&mMutex, nullptr); }
 
-  void Lock()
-  {
-    pthread_mutex_lock(&mMutex);
-  }
-
-  void Unlock()
-  {
-    pthread_mutex_unlock(&mMutex);
-  }
+  void Lock()   { pthread_mutex_lock(&mMutex); }
+  void Unlock() { pthread_mutex_unlock(&mMutex); }
 };
 
 #endif
 
 class Mutex : private MutexBase
 {
   bool mIsLocked;
 
@@ -409,54 +365,39 @@ public:
 
   void Unlock()
   {
     MOZ_ASSERT(mIsLocked);
     mIsLocked = false;
     MutexBase::Unlock();
   }
 
-  bool IsLocked()
-  {
-    return mIsLocked;
-  }
+  bool IsLocked() { return mIsLocked; }
 };
 
 // This lock must be held while manipulating global state, such as
 // gStackTraceTable, gBlockTable, etc.
 static Mutex* gStateLock = nullptr;
 
 class AutoLockState
 {
   DISALLOW_COPY_AND_ASSIGN(AutoLockState);
 
 public:
-  AutoLockState()
-  {
-    gStateLock->Lock();
-  }
-  ~AutoLockState()
-  {
-    gStateLock->Unlock();
-  }
+  AutoLockState()  { gStateLock->Lock(); }
+  ~AutoLockState() { gStateLock->Unlock(); }
 };
 
 class AutoUnlockState
 {
   DISALLOW_COPY_AND_ASSIGN(AutoUnlockState);
 
 public:
-  AutoUnlockState()
-  {
-    gStateLock->Unlock();
-  }
-  ~AutoUnlockState()
-  {
-    gStateLock->Lock();
-  }
+  AutoUnlockState()  { gStateLock->Unlock(); }
+  ~AutoUnlockState() { gStateLock->Lock(); }
 };
 
 //---------------------------------------------------------------------------
 // Thread-local storage and blocking of intercepts
 //---------------------------------------------------------------------------
 
 #ifdef XP_WIN
 
@@ -509,20 +450,17 @@ public:
   }
 
   bool UnblockIntercepts()
   {
     MOZ_ASSERT(mBlockIntercepts);
     return mBlockIntercepts = false;
   }
 
-  bool InterceptsAreBlocked() const
-  {
-    return mBlockIntercepts;
-  }
+  bool InterceptsAreBlocked() const { return mBlockIntercepts; }
 };
 
 /* static */ Thread*
 Thread::Fetch()
 {
   Thread* t = static_cast<Thread*>(DMD_GET_TLS_DATA(gTlsIndex));
 
   if (MOZ_UNLIKELY(!t)) {
@@ -558,20 +496,17 @@ public:
 
 //---------------------------------------------------------------------------
 // Location service
 //---------------------------------------------------------------------------
 
 class StringTable
 {
 public:
-  StringTable()
-  {
-    (void)mSet.init(64);
-  }
+  StringTable() { (void)mSet.init(64); }
 
   const char*
   Intern(const char* aString)
   {
     StringHashSet::AddPtr p = mSet.lookupForAdd(aString);
     if (p) {
       return *p;
     }
@@ -613,23 +548,21 @@ private:
   typedef js::HashSet<const char*, StringHasher, InfallibleAllocPolicy> StringHashSet;
 
   StringHashSet mSet;
 };
 
 class StringAlloc
 {
 public:
-  static char*
-  copy(const char* aString)
+  static char* copy(const char* aString)
   {
     return InfallibleAllocPolicy::strdup_(aString);
   }
-  static void
-  free(char* aString)
+  static void free(char* aString)
   {
     InfallibleAllocPolicy::free_(aString);
   }
 };
 
 struct DescribeCodeAddressLock
 {
   static void Unlock() { gStateLock->Unlock(); }
@@ -1273,18 +1206,17 @@ Options::GetBool(const char* aArg, const
 //   of 4096, for example, then our alloc counter would only take on even
 //   values, because jemalloc always rounds up requests sizes.  In contrast, a
 //   prime size will explore all possible values of the alloc counter.
 //
 Options::Options(const char* aDMDEnvVar)
   : mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)),
     mSampleBelowSize(4093, 100 * 100 * 1000),
     mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames),
-    mShowDumpStats(false),
-    mMode(Normal)
+    mShowDumpStats(false)
 {
   char* e = mDMDEnvVar;
   if (strcmp(e, "1") != 0) {
     bool isEnd = false;
     while (!isEnd) {
       // Consume leading whitespace.
       while (isspace(*e)) {
         e++;
@@ -1309,21 +1241,16 @@ Options::Options(const char* aDMDEnvVar)
         mSampleBelowSize.mActual = myLong;
 
       } else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
         mMaxFrames.mActual = myLong;
 
       } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
         mShowDumpStats = myBool;
 
-      } else if (strcmp(arg, "--mode=normal") == 0) {
-        mMode = Options::Normal;
-      } else if (strcmp(arg, "--mode=test")   == 0) {
-        mMode = Options::Test;
-
       } else if (strcmp(arg, "") == 0) {
         // This can only happen if there is trailing whitespace.  Ignore.
         MOZ_ASSERT(isEnd);
 
       } else {
         BadArg(arg);
       }
 
@@ -1349,48 +1276,32 @@ Options::BadArg(const char* aArg)
   StatusMsg("  --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
             int(mSampleBelowSize.mMax),
             int(mSampleBelowSize.mDefault));
   StatusMsg("                               (prime numbers are recommended)\n");
   StatusMsg("  --max-frames=<1..%d>         Max. depth of stack traces [%d]\n",
             int(mMaxFrames.mMax),
             int(mMaxFrames.mDefault));
   StatusMsg("  --show-dump-stats=<yes|no>   Show stats about dumps? [no]\n");
-  StatusMsg("  --mode=<normal|test>         Mode of operation [normal]\n");
   StatusMsg("\n");
   exit(1);
 }
 
 //---------------------------------------------------------------------------
 // DMD start-up
 //---------------------------------------------------------------------------
 
 #ifdef XP_MACOSX
 static void
 NopStackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
                      void* aClosure)
 {
 }
 #endif
 
-// Note that fopen() can allocate.
-static FILE*
-OpenOutputFile(const char* aFilename)
-{
-  FILE* fp = fopen(aFilename, "w");
-  if (!fp) {
-    StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno));
-    exit(1);
-  }
-  return fp;
-}
-
-static void RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
-                        UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4);
-
 // WARNING: this function runs *very* early -- before all static initializers
 // have run.  For this reason, non-scalar globals such as gStateLock and
 // gStackTraceTable are allocated dynamically (so we can guarantee their
 // construction in this function) rather than statically.
 static void
 Init(const malloc_table_t* aMallocTable)
 {
   MOZ_ASSERT(!gIsDMDRunning);
@@ -1434,41 +1345,17 @@ Init(const malloc_table_t* aMallocTable)
 
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
     gStackTraceTable->init(8192);
 
     gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
     gBlockTable->init(8192);
   }
 
-  if (gOptions->IsTestMode()) {
-    // Do all necessary allocations before setting gIsDMDRunning so those
-    // allocations don't show up in our results.  Once gIsDMDRunning is set we
-    // are intercepting malloc et al. in earnest.
-    //
-    // These files are written to $CWD. It would probably be better to write
-    // them to "TmpD" using the directory service, but that would require
-    // linking DMD with XPCOM.
-    auto f1 = MakeUnique<FpWriteFunc>(OpenOutputFile("full-empty.json"));
-    auto f2 = MakeUnique<FpWriteFunc>(OpenOutputFile("full-unsampled1.json"));
-    auto f3 = MakeUnique<FpWriteFunc>(OpenOutputFile("full-unsampled2.json"));
-    auto f4 = MakeUnique<FpWriteFunc>(OpenOutputFile("full-sampled.json"));
-    gIsDMDRunning = true;
-
-    StatusMsg("running test mode...\n");
-    RunTestMode(Move(f1), Move(f2), Move(f3), Move(f4));
-    StatusMsg("finished test mode; DMD is now disabled again\n");
-
-    // Continue running so that the xpcshell test can complete, but DMD no
-    // longer needs to be running.
-    gIsDMDRunning = false;
-
-  } else {
-    gIsDMDRunning = true;
-  }
+  gIsDMDRunning = true;
 }
 
 //---------------------------------------------------------------------------
 // DMD reporting and unreporting
 //---------------------------------------------------------------------------
 
 static void
 ReportHelper(const void* aPtr, bool aReportedOnAlloc)
@@ -1845,266 +1732,23 @@ AnalyzeReports(JSONWriter& aWriter)
   AnalyzeReportsImpl(aWriter);
   ClearReports();
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
-// This function checks that heap blocks that have the same stack trace but
-// different (or no) reporters get aggregated separately.
-void Foo(int aSeven)
+MOZ_EXPORT void
+SetSampleBelowSize(size_t aSize)
 {
-  char* a[6];
-  for (int i = 0; i < aSeven - 1; i++) {
-    a[i] = (char*) replace_malloc(128 - 16*i);
-  }
-
-  for (int i = 0; i < aSeven - 5; i++) {
-    Report(a[i]);                   // reported
-  }
-  Report(a[2]);                     // reported
-  Report(a[3]);                     // reported
-  // a[4], a[5] unreported
-}
-
-// This stops otherwise-unused variables from being optimized away.
-static void
-UseItOrLoseIt(void* aPtr, int aSeven)
-{
-  char buf[64];
-  int n = sprintf(buf, "%p\n", aPtr);
-  if (n == 20 + aSeven) {
-    fprintf(stderr, "well, that is surprising");
-  }
+  gOptions->SetSampleBelowSize(aSize);
 }
 
-// The output from this function feeds into DMD's xpcshell test.
-static void
-RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
-            UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4)
+MOZ_EXPORT void
+ClearBlocks()
 {
-  // This test relies on the compiler not doing various optimizations, such as
-  // eliding unused replace_malloc() calls or unrolling loops with fixed
-  // iteration counts. So we want a constant value that the compiler can't
-  // determine statically, and we use that in various ways to prevent the above
-  // optimizations from happening.
-  //
-  // This code always sets |seven| to the value 7. It works because we know
-  // that "--mode=test" must be within the DMD environment variable if we reach
-  // here, but the compiler almost certainly does not.
-  //
-  char* env = getenv("DMD");
-  char* p1 = strstr(env, "--mode=t");
-  char* p2 = strstr(p1, "test");
-  int seven = p2 - p1;
-
-  // The first part of this test requires sampling to be disabled.
-  gOptions->SetSampleBelowSize(1);
-
-  //---------
-
-  // AnalyzeReports 1.  Zero for everything.
-  JSONWriter writer1(Move(aF1));
-  AnalyzeReports(writer1);
-
-  //---------
-
-  // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
-  // AnalyzeReports 3: still present and unreported.
-  int i;
-  char* a = nullptr;
-  for (i = 0; i < seven + 3; i++) {
-      a = (char*) replace_malloc(100);
-      UseItOrLoseIt(a, seven);
-  }
-  replace_free(a);
-
-  // Note: 8 bytes is the smallest requested size that gives consistent
-  // behaviour across all platforms with jemalloc.
-  // AnalyzeReports 2: reported.
-  // AnalyzeReports 3: thrice-reported.
-  char* a2 = (char*) replace_malloc(8);
-  Report(a2);
-
-  // AnalyzeReports 2: reported.
-  // AnalyzeReports 3: reportedness carries over, due to ReportOnAlloc.
-  char* b = (char*) replace_malloc(10);
-  ReportOnAlloc(b);
-
-  // ReportOnAlloc, then freed.
-  // AnalyzeReports 2: freed, irrelevant.
-  // AnalyzeReports 3: freed, irrelevant.
-  char* b2 = (char*) replace_malloc(1);
-  ReportOnAlloc(b2);
-  replace_free(b2);
-
-  // AnalyzeReports 2: reported 4 times.
-  // AnalyzeReports 3: freed, irrelevant.
-  char* c = (char*) replace_calloc(10, 3);
-  Report(c);
-  for (int i = 0; i < seven - 4; i++) {
-    Report(c);
-  }
-
-  // AnalyzeReports 2: ignored.
-  // AnalyzeReports 3: irrelevant.
-  Report((void*)(intptr_t)i);
-
-  // jemalloc rounds this up to 8192.
-  // AnalyzeReports 2: reported.
-  // AnalyzeReports 3: freed.
-  char* e = (char*) replace_malloc(4096);
-  e = (char*) replace_realloc(e, 4097);
-  Report(e);
-
-  // First realloc is like malloc;  second realloc is shrinking.
-  // AnalyzeReports 2: reported.
-  // AnalyzeReports 3: re-reported.
-  char* e2 = (char*) replace_realloc(nullptr, 1024);
-  e2 = (char*) replace_realloc(e2, 512);
-  Report(e2);
-
-  // First realloc is like malloc;  second realloc creates a min-sized block.
-  // XXX: on Windows, second realloc frees the block.
-  // AnalyzeReports 2: reported.
-  // AnalyzeReports 3: freed, irrelevant.
-  char* e3 = (char*) replace_realloc(nullptr, 1023);
-//e3 = (char*) replace_realloc(e3, 0);
-  MOZ_ASSERT(e3);
-  Report(e3);
-
-  // AnalyzeReports 2: freed, irrelevant.
-  // AnalyzeReports 3: freed, irrelevant.
-  char* f = (char*) replace_malloc(64);
-  replace_free(f);
-
-  // AnalyzeReports 2: ignored.
-  // AnalyzeReports 3: irrelevant.
-  Report((void*)(intptr_t)0x0);
-
-  // AnalyzeReports 2: mixture of reported and unreported.
-  // AnalyzeReports 3: all unreported.
-  Foo(seven);
-  Foo(seven);
-
-  // AnalyzeReports 2: twice-reported.
-  // AnalyzeReports 3: twice-reported.
-  char* g1 = (char*) replace_malloc(77);
-  ReportOnAlloc(g1);
-  ReportOnAlloc(g1);
-
-  // AnalyzeReports 2: twice-reported.
-  // AnalyzeReports 3: once-reported.
-  char* g2 = (char*) replace_malloc(78);
-  Report(g2);
-  ReportOnAlloc(g2);
-
-  // AnalyzeReports 2: twice-reported.
-  // AnalyzeReports 3: once-reported.
-  char* g3 = (char*) replace_malloc(79);
-  ReportOnAlloc(g3);
-  Report(g3);
-
-  // All the odd-ball ones.
-  // AnalyzeReports 2: all unreported.
-  // AnalyzeReports 3: all freed, irrelevant.
-  // XXX: no memalign on Mac
-//void* x = memalign(64, 65);           // rounds up to 128
-//UseItOrLoseIt(x, seven);
-  // XXX: posix_memalign doesn't work on B2G
-//void* y;
-//posix_memalign(&y, 128, 129);         // rounds up to 256
-//UseItOrLoseIt(y, seven);
-  // XXX: valloc doesn't work on Windows.
-//void* z = valloc(1);                  // rounds up to 4096
-//UseItOrLoseIt(z, seven);
-//aligned_alloc(64, 256);               // XXX: C11 only
-
-  // AnalyzeReports 2.
-  JSONWriter writer2(Move(aF2));
-  AnalyzeReports(writer2);
-
-  //---------
-
-  Report(a2);
-  Report(a2);
-  replace_free(c);
-  replace_free(e);
-  Report(e2);
-  replace_free(e3);
-//replace_free(x);
-//replace_free(y);
-//replace_free(z);
-
-  // AnalyzeReports 3.
-  JSONWriter writer3(Move(aF3));
-  AnalyzeReports(writer3);
-
-  //---------
-
-  // Clear all knowledge of existing blocks to give us a clean slate.
   gBlockTable->clear();
-
-  gOptions->SetSampleBelowSize(128);
-
-  char* s;
-
-  // This equals the sample size, and so is reported exactly.  It should be
-  // listed before records of the same size that are sampled.
-  s = (char*) replace_malloc(128);
-  UseItOrLoseIt(s, seven);
-
-  // This exceeds the sample size, and so is reported exactly.
-  s = (char*) replace_malloc(144);
-  UseItOrLoseIt(s, seven);
-
-  // These together constitute exactly one sample.
-  for (int i = 0; i < seven + 9; i++) {
-    s = (char*) replace_malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 0);
-
-  // These fall 8 bytes short of a full sample.
-  for (int i = 0; i < seven + 8; i++) {
-    s = (char*) replace_malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 120);
-
-  // This exceeds the sample size, and so is recorded exactly.
-  s = (char*) replace_malloc(256);
-  UseItOrLoseIt(s, seven);
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 120);
-
-  // This gets more than to a full sample from the |i < 15| loop above.
-  s = (char*) replace_malloc(96);
-  UseItOrLoseIt(s, seven);
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 88);
-
-  // This gets to another full sample.
-  for (int i = 0; i < seven - 2; i++) {
-    s = (char*) replace_malloc(8);
-    UseItOrLoseIt(s, seven);
-  }
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 0);
-
-  // This allocates 16, 32, ..., 128 bytes, which results in a heap block
-  // record that contains a mix of sample and non-sampled blocks, and so should
-  // be printed with '~' signs.
-  for (int i = 1; i <= seven + 1; i++) {
-    s = (char*) replace_malloc(i * 16);
-    UseItOrLoseIt(s, seven);
-  }
-  MOZ_ASSERT(gSmallBlockActualSizeCounter == 64);
-
-  // At the end we're 64 bytes into the current sample so we report ~1,424
-  // bytes of allocation overall, which is 64 less than the real value 1,488.
-
-  // AnalyzeReports 4.
-  JSONWriter writer4(Move(aF4));
-  AnalyzeReports(writer4);
+  gSmallBlockActualSizeCounter = 0;
 }
 
 }   // namespace dmd
 }   // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -142,12 +142,20 @@ SizeOf(Sizes* aSizes);
 // Prints a status message prefixed with "DMD[<pid>]". Use sparingly.
 MOZ_EXPORT void
 StatusMsg(const char* aFmt, ...);
 
 // Indicates whether or not DMD is running.
 MOZ_EXPORT bool
 IsRunning();
 
+// Sets the sample-below size. Only used for testing purposes.
+MOZ_EXPORT void
+SetSampleBelowSize(size_t aSize);
+
+// Clears all records of live allocations. Only used for testing purposes.
+MOZ_EXPORT void
+ClearBlocks();
+
 } // namespace mozilla
 } // namespace dmd
 
 #endif /* DMD_h___ */
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -1,9 +1,9 @@
-#! /usr/bin/python
+#! /usr/bin/env python
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 '''This script analyzes a JSON file emitted by DMD.'''
 
 from __future__ import print_function, division
@@ -53,43 +53,105 @@ allocatorFns = [
     'pod_realloc',
     # This one necessary to fully filter some sequences of allocation functions
     # that happen in practice. Note that ??? entries that follow non-allocation
     # functions won't be stripped, as explained above.
     '???',
 ]
 
 class Record(object):
+    '''A record is an aggregation of heap blocks that have identical stack
+    traces. It can also be used to represent the difference between two
+    records.'''
+
     def __init__(self):
         self.numBlocks = 0
         self.reqSize = 0
         self.slopSize = 0
         self.usableSize = 0
         self.isSampled = False
+        self.allocatedAtDesc = None
+        self.reportedAtDescs = []
         self.usableSizes = collections.defaultdict(int)
 
+    def isZero(self, args):
+        return self.numBlocks == 0 and \
+               self.reqSize == 0 and \
+               self.slopSize == 0 and \
+               self.usableSize == 0 and \
+               len(self.usableSizes) == 0
+
+    def negate(self):
+        self.numBlocks = -self.numBlocks
+        self.reqSize = -self.reqSize
+        self.slopSize = -self.slopSize
+        self.usableSize = -self.usableSize
+
+        negatedUsableSizes = collections.defaultdict(int)
+        for (usableSize, isSampled), count in self.usableSizes.items():
+            negatedUsableSizes[(-usableSize, isSampled)] = count
+        self.usableSizes = negatedUsableSizes
+
+    def subtract(self, r):
+        # We should only be calling this on records with matching stack traces.
+        # Check this.
+        assert self.allocatedAtDesc == r.allocatedAtDesc
+        assert self.reportedAtDescs == r.reportedAtDescs
+
+        self.numBlocks -= r.numBlocks
+        self.reqSize -= r.reqSize
+        self.slopSize -= r.slopSize
+        self.usableSize -= r.usableSize
+        self.isSampled = self.isSampled or r.isSampled
+
+        usableSizes1 = self.usableSizes
+        usableSizes2 = r.usableSizes
+        usableSizes3 = collections.defaultdict(int)
+        for usableSize, isSampled in usableSizes1:
+            counts1 = usableSizes1[usableSize, isSampled]
+            if (usableSize, isSampled) in usableSizes2:
+                counts2 = usableSizes2[usableSize, isSampled]
+                del usableSizes2[usableSize, isSampled]
+                counts3 = counts1 - counts2
+                if counts3 != 0:
+                    if counts3 < 0:
+                        usableSize = -usableSize
+                        counts3 = -counts3
+                    usableSizes3[usableSize, isSampled] = counts3
+            else:
+                usableSizes3[usableSize, isSampled] = counts1
+
+        for usableSize, isSampled in usableSizes2:
+            usableSizes3[-usableSize, isSampled] = \
+                usableSizes2[usableSize, isSampled]
+
+        self.usableSizes = usableSizes3
+
     @staticmethod
     def cmpByIsSampled(r1, r2):
         # Treat sampled as smaller than non-sampled.
         return cmp(r2.isSampled, r1.isSampled)
 
     @staticmethod
     def cmpByUsableSize(r1, r2):
         # Sort by usable size, then req size, then by isSampled.
-        return cmp(r1.usableSize, r2.usableSize) or Record.cmpByReqSize(r1, r2)
+        return cmp(abs(r1.usableSize), abs(r2.usableSize)) or \
+               Record.cmpByReqSize(r1, r2)
 
     @staticmethod
     def cmpByReqSize(r1, r2):
         # Sort by req size, then by isSampled.
-        return cmp(r1.reqSize, r2.reqSize) or Record.cmpByIsSampled(r1, r2)
+        return cmp(abs(r1.reqSize), abs(r2.reqSize)) or \
+               Record.cmpByIsSampled(r1, r2)
 
     @staticmethod
     def cmpBySlopSize(r1, r2):
         # Sort by slop size, then by isSampled.
-        return cmp(r1.slopSize, r2.slopSize) or Record.cmpByIsSampled(r1, r2)
+        return cmp(abs(r1.slopSize), abs(r2.slopSize)) or \
+               Record.cmpByIsSampled(r1, r2)
 
 
 sortByChoices = {
     'usable': Record.cmpByUsableSize,   # the default
     'req':    Record.cmpByReqSize,
     'slop':   Record.cmpBySlopSize,
 }
 
@@ -100,17 +162,19 @@ def parseCommandLine():
         value = int(string)
         if value < 1 or value > 24:
             msg = '{:s} is not in the range 1..24'.format(string)
             raise argparse.ArgumentTypeError(msg)
         return value
 
     description = '''
 Analyze heap data produced by DMD.
-If no files are specified, read from stdin; input can be gzipped.
+If one file is specified, analyze it; if two files are specified, analyze the
+difference.
+Input files can be gzipped.
 Write to stdout unless -o/--output is specified.
 Stack traces are fixed to show function names, filenames and line numbers
 unless --no-fix-stacks is specified; stack fixing modifies the original file
 and may take some time. If specified, the BREAKPAD_SYMBOLS_PATH environment
 variable is used to find breakpad symbols for stack fixing.
 '''
     p = argparse.ArgumentParser(description=description)
 
@@ -126,26 +190,27 @@ variable is used to find breakpad symbol
 
     p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
                    default=sortByChoices.keys()[0],
                    help='sort the records by a particular metric')
 
     p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
                    help='ignore allocation functions at the start of traces')
 
-    p.add_argument('-b', '--show-all-block-sizes', action='store_true',
-                   help='show individual block sizes for each record')
-
     p.add_argument('--no-fix-stacks', action='store_true',
                    help='do not fix stacks')
 
     p.add_argument('--filter-stacks-for-testing', action='store_true',
                    help='filter stack traces; only useful for testing purposes')
 
-    p.add_argument('input_file')
+    p.add_argument('input_file',
+                   help='a file produced by DMD')
+
+    p.add_argument('input_file2', nargs='?',
+                   help='a file produced by DMD; if present, it is diff\'d with input_file')
 
     return p.parse_args(sys.argv[1:])
 
 
 # Fix stacks if necessary: first write the output to a tempfile, then replace
 # the original file with it.
 def fixStackTraces(inputFilename, isZipped, opener):
     # This append() call is needed to make the import statements work when this
@@ -190,28 +255,26 @@ def fixStackTraces(inputFilename, isZipp
             for line in inputFile:
                 tmpFile.write(fix(line))
 
         tmpFile.close()
 
         shutil.move(tmpFilename, inputFilename)
 
 
-def main():
-    args = parseCommandLine()
-
+def getDigestFromFile(args, inputFile):
     # Handle gzipped input if necessary.
-    isZipped = args.input_file.endswith('.gz')
+    isZipped = inputFile.endswith('.gz')
     opener = gzip.open if isZipped else open
 
     # Fix stack traces unless otherwise instructed.
     if not args.no_fix_stacks:
-        fixStackTraces(args.input_file, isZipped, opener)
+        fixStackTraces(inputFile, isZipped, opener)
 
-    with opener(args.input_file, 'rb') as f:
+    with opener(inputFile, 'rb') as f:
         j = json.load(f)
 
     if j['version'] != outputVersion:
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     # Extract the main parts of the JSON object.
     invocation = j['invocation']
     dmdEnvVar = invocation['dmdEnvVar']
@@ -240,16 +303,41 @@ def main():
             if numSkippedFrames > 0:
                 traceTable[traceKey] = frameKeys[numSkippedFrames:]
 
     # Trim the number of frames.
     for traceKey, frameKeys in traceTable.items():
         if len(frameKeys) > args.max_frames:
             traceTable[traceKey] = frameKeys[:args.max_frames]
 
+    def buildTraceDescription(traceTable, frameTable, traceKey):
+        frameKeys = traceTable[traceKey]
+        fmt = '    #{:02d}{:}'
+
+        if args.filter_stacks_for_testing:
+            # When running SmokeDMD.cpp, every stack trace should contain at
+            # least one frame that contains 'DMD.cpp', from either |DMD.cpp| or
+            # |SmokeDMD.cpp|. (Or 'dmd.cpp' on Windows.) If we see such a
+            # frame, we replace the entire stack trace with a single,
+            # predictable frame. There is too much variation in the stack
+            # traces across different machines and platforms to do more precise
+            # matching, but this level of matching will result in failure if
+            # stack fixing fails completely.
+            for frameKey in frameKeys:
+                frameDesc = frameTable[frameKey]
+                if 'DMD.cpp' in frameDesc or 'dmd.cpp' in frameDesc:
+                    return [fmt.format(1, ': ... DMD.cpp ...')]
+
+        # The frame number is always '#00' (see DMD.h for why), so we have to
+        # replace that with the correct frame number.
+        desc = []
+        for n, frameKey in enumerate(traceTable[traceKey], start=1):
+            desc.append(fmt.format(n, frameTable[frameKey][3:]))
+        return desc
+
     # Aggregate blocks into records. All sufficiently similar blocks go into a
     # single record.
 
     if args.ignore_reports:
         liveRecords = collections.defaultdict(Record)
     else:
         unreportedRecords    = collections.defaultdict(Record)
         onceReportedRecords  = collections.defaultdict(Record)
@@ -259,34 +347,43 @@ def main():
     heapBlocks = 0
 
     for block in blockList:
         # For each block we compute a |recordKey|, and all blocks with the same
         # |recordKey| are aggregated into a single record. The |recordKey| is
         # derived from the block's 'alloc' and 'reps' (if present) stack
         # traces.
         #
-        # Each stack trace has a key in the JSON file. But we don't use that
-        # key to construct |recordKey|; instead we use the frame keys.
-        # This is because the stack trimming done for --max-frames can cause
-        # stack traces with distinct trace keys to end up with the same frame
-        # keys, and these should be considered equivalent. E.g. if we have
-        # distinct traces T1:[A,B,C] and T2:[A,B,D] and we trim the final frame
-        # of each they should be considered equivalent.
-        allocatedAt = block['alloc']
+        # We use frame descriptions (e.g. "#00: foo (X.cpp:99)") when comparing
+        # traces for equality. We can't use trace keys or frame keys because
+        # they're not comparable across different DMD runs (which is relevant
+        # when doing diffs).
+        #
+        # Using frame descriptions also fits in with the stack trimming done
+        # for --max-frames, which requires that stack traces with common
+        # beginnings but different endings to be considered equivalent. E.g. if
+        # we have distinct traces T1:[A:D1,B:D2,C:D3] and T2:[X:D1,Y:D2,Z:D4]
+        # and we trim the final frame of each they should be considered
+        # equivalent because the untrimmed frame descriptions (D1 and D2)
+        # match.
+        def makeRecordKeyPart(traceKey):
+            return str(map(lambda frameKey: frameTable[frameKey],
+                           traceTable[traceKey]))
+
+        allocatedAtTraceKey = block['alloc']
         if args.ignore_reports:
-            recordKey = str(traceTable[allocatedAt])
+            recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             records = liveRecords
         else:
-            recordKey = str(traceTable[allocatedAt])
+            recordKey = makeRecordKeyPart(allocatedAtTraceKey)
             if 'reps' in block:
-                reportedAts = block['reps']
-                for reportedAt in reportedAts:
-                    recordKey += str(traceTable[reportedAt])
-                if len(reportedAts) == 1:
+                reportedAtTraceKeys = block['reps']
+                for reportedAtTraceKey in reportedAtTraceKeys:
+                    recordKey += makeRecordKeyPart(reportedAtTraceKey)
+                if len(reportedAtTraceKeys) == 1:
                     records = onceReportedRecords
                 else:
                     records = twiceReportedRecords
             else:
                 records = unreportedRecords
 
         record = records[recordKey]
 
@@ -307,25 +404,102 @@ def main():
         heapUsableSize += usableSize
         heapBlocks += 1
 
         record.numBlocks  += 1
         record.reqSize    += reqSize
         record.slopSize   += slopSize
         record.usableSize += usableSize
         record.isSampled   = record.isSampled or isSampled
-        record.allocatedAt = block['alloc']
+        if record.allocatedAtDesc == None:
+            record.allocatedAtDesc = \
+                buildTraceDescription(traceTable, frameTable,
+                                      allocatedAtTraceKey)
+
         if args.ignore_reports:
             pass
         else:
-            if 'reps' in block:
-                record.reportedAts = block['reps']
+            if 'reps' in block and record.reportedAtDescs == []:
+                f = lambda k: buildTraceDescription(traceTable, frameTable, k)
+                record.reportedAtDescs = map(f, reportedAtTraceKeys)
         record.usableSizes[(usableSize, isSampled)] += 1
 
-    # Print records.
+    # All the processed data for a single DMD file is called a "digest".
+    digest = {}
+    digest['dmdEnvVar'] = dmdEnvVar
+    digest['sampleBelowSize'] = sampleBelowSize
+    digest['heapUsableSize'] = heapUsableSize
+    digest['heapBlocks'] = heapBlocks
+    digest['heapIsSampled'] = heapIsSampled
+    if args.ignore_reports:
+        digest['liveRecords'] = liveRecords
+    else:
+        digest['unreportedRecords'] = unreportedRecords
+        digest['onceReportedRecords'] = onceReportedRecords
+        digest['twiceReportedRecords'] = twiceReportedRecords
+    return digest
+
+
+def diffRecords(args, records1, records2):
+    records3 = {}
+
+    # Process records1.
+    for k in records1:
+        r1 = records1[k]
+        if k in records2:
+            # This record is present in both records1 and records2.
+            r2 = records2[k]
+            del records2[k]
+            r2.subtract(r1)
+            if not r2.isZero(args):
+                records3[k] = r2
+        else:
+            # This record is present only in records1.
+            r1.negate()
+            records3[k] = r1
+
+    for k in records2:
+        # This record is present only in records2.
+        records3[k] = records2[k]
+
+    return records3
+
+
+def diffDigests(args, d1, d2):
+    d3 = {}
+    d3['dmdEnvVar'] = (d1['dmdEnvVar'], d2['dmdEnvVar'])
+    d3['sampleBelowSize'] = (d1['sampleBelowSize'], d2['sampleBelowSize'])
+    d3['heapUsableSize'] = d2['heapUsableSize'] - d1['heapUsableSize']
+    d3['heapBlocks']     = d2['heapBlocks']     - d1['heapBlocks']
+    d3['heapIsSampled']  = d2['heapIsSampled'] or d1['heapIsSampled']
+    if args.ignore_reports:
+        d3['liveRecords'] = diffRecords(args, d1['liveRecords'],
+                                              d2['liveRecords'])
+    else:
+        d3['unreportedRecords']    = diffRecords(args, d1['unreportedRecords'],
+                                                       d2['unreportedRecords'])
+        d3['onceReportedRecords']  = diffRecords(args, d1['onceReportedRecords'],
+                                                       d2['onceReportedRecords'])
+        d3['twiceReportedRecords'] = diffRecords(args, d1['twiceReportedRecords'],
+                                                       d2['twiceReportedRecords'])
+    return d3
+
+
+def printDigest(args, digest):
+    dmdEnvVar       = digest['dmdEnvVar']
+    sampleBelowSize = digest['sampleBelowSize']
+    heapUsableSize  = digest['heapUsableSize']
+    heapIsSampled   = digest['heapIsSampled']
+    heapBlocks      = digest['heapBlocks']
+    if args.ignore_reports:
+        liveRecords = digest['liveRecords']
+    else:
+        unreportedRecords    = digest['unreportedRecords']
+        onceReportedRecords  = digest['onceReportedRecords']
+        twiceReportedRecords = digest['twiceReportedRecords']
 
     separator = '#' + '-' * 65 + '\n'
 
     def number(n, isSampled):
         '''Format a number, with comma as a separator and a '~' prefix if it's
         sampled.'''
         return '{:}{:,d}'.format('~' if isSampled else '', n)
 
@@ -334,37 +508,19 @@ def main():
 
     def plural(n):
         return '' if n == 1 else 's'
 
     # Prints to stdout, or to file if -o/--output was specified.
     def out(*arguments, **kwargs):
         print(*arguments, file=args.output, **kwargs)
 
-    def printStack(traceTable, frameTable, traceKey):
-        frameKeys = traceTable[traceKey]
-        fmt = '    #{:02d}{:}'
-
-        if args.filter_stacks_for_testing:
-            # If any frame has "DMD.cpp" or "replace_malloc.c" in its
-            # description -- as should be the case for every stack trace when
-            # running DMD in test mode -- we replace the entire trace with a
-            # single, predictable frame. There is too much variation in the
-            # stack traces across different machines and platforms to do more
-            # specific matching.
-            for frameKey in frameKeys:
-                frameDesc = frameTable[frameKey]
-                if 'DMD.cpp' in frameDesc or 'replace_malloc.c' in frameDesc:
-                    out(fmt.format(1, ': ... DMD.cpp ...'))
-                    return
-
-        # The frame number is always '#00' (see DMD.h for why), so we have to
-        # replace that with the correct frame number.
-        for n, frameKey in enumerate(traceTable[traceKey], start=1):
-            out(fmt.format(n, frameTable[frameKey][3:]))
+    def printStack(traceDesc):
+        for frameDesc in traceDesc:
+            out(frameDesc)
 
     def printRecords(recordKind, records, heapUsableSize):
         RecordKind = recordKind.capitalize()
         out(separator)
         numRecords = len(records)
         cmpRecords = sortByChoices[args.sort_by]
         sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
         kindBlocks = 0
@@ -395,78 +551,97 @@ def main():
             out(RecordKind + ' {')
             out('  {:} block{:} in heap block record {:,d} of {:,d}'.
                 format(number(record.numBlocks, isSampled),
                        plural(record.numBlocks), i, numRecords))
             out('  {:} bytes ({:} requested / {:} slop)'.
                 format(number(record.usableSize, isSampled),
                        number(record.reqSize, isSampled),
                        number(record.slopSize, isSampled)))
+
+            abscmp = lambda ((usableSize1, _1a), _1b), \
+                            ((usableSize2, _2a), _2b): \
+                            cmp(abs(usableSize1), abs(usableSize2))
+            usableSizes = sorted(record.usableSizes.items(), cmp=abscmp,
+                                 reverse=True)
+
+            hasSingleBlock = len(usableSizes) == 1 and usableSizes[0][1] == 1
+
+            if not hasSingleBlock:
+                out('  Individual block sizes: ', end='')
+                if len(usableSizes) == 0:
+                    out('(no change)', end='')
+                else:
+                    isFirst = True
+                    for (usableSize, isSampled), count in usableSizes:
+                        if not isFirst:
+                            out('; ', end='')
+                        out('{:}'.format(number(usableSize, isSampled)), end='')
+                        if count > 1:
+                            out(' x {:,d}'.format(count), end='')
+                        isFirst = False
+                out()
+
             out('  {:4.2f}% of the heap ({:4.2f}% cumulative)'.
                 format(perc(record.usableSize, heapUsableSize),
                        perc(kindCumulativeUsableSize, heapUsableSize)))
             if args.ignore_reports:
                 pass
             else:
                 out('  {:4.2f}% of {:} ({:4.2f}% cumulative)'.
                     format(perc(record.usableSize, kindUsableSize),
                            recordKind,
                            perc(kindCumulativeUsableSize, kindUsableSize)))
-
-            if args.show_all_block_sizes:
-                usableSizes = sorted(record.usableSizes.items(), reverse=True)
-
-                out('  Individual block sizes: ', end='')
-                isFirst = True
-                for (usableSize, isSampled), count in usableSizes:
-                    if not isFirst:
-                        out('; ', end='')
-                    out('{:}'.format(number(usableSize, isSampled)), end='')
-                    if count > 1:
-                        out(' x {:,d}'.format(count), end='')
-                    isFirst = False
-                out()
-
             out('  Allocated at {')
-            printStack(traceTable, frameTable, record.allocatedAt)
+            printStack(record.allocatedAtDesc)
             out('  }')
             if args.ignore_reports:
                 pass
             else:
-                if hasattr(record, 'reportedAts'):
-                    for n, reportedAt in enumerate(record.reportedAts):
-                        again = 'again ' if n > 0 else ''
-                        out('  Reported {:}at {{'.format(again))
-                        printStack(traceTable, frameTable, reportedAt)
-                        out('  }')
+                for n, reportedAtDesc in enumerate(record.reportedAtDescs):
+                    again = 'again ' if n > 0 else ''
+                    out('  Reported {:}at {{'.format(again))
+                    printStack(reportedAtDesc)
+                    out('  }')
             out('}\n')
 
         return (kindUsableSize, kindBlocks)
 
 
-    # Print header.
-    out(separator)
-    out('Invocation {')
-    out('  $DMD = \'' + dmdEnvVar + '\'')
-    out('  Sample-below size = ' + str(sampleBelowSize))
-    out('}\n')
+    def printInvocation(n, dmdEnvVar, sampleBelowSize):
+        out('Invocation{:} {{'.format(n))
+        out('  $DMD = \'' + dmdEnvVar + '\'')
+        out('  Sample-below size = ' + str(sampleBelowSize))
+        out('}\n')
+
+    # Print command line. Strip dirs so the output is deterministic, which is
+    # needed for testing.
+    out(separator, end='')
+    out('# ' + ' '.join(map(os.path.basename, sys.argv)) + '\n')
+
+    # Print invocation(s).
+    if type(dmdEnvVar) is not tuple:
+        printInvocation('', dmdEnvVar, sampleBelowSize)
+    else:
+        printInvocation(' 1', dmdEnvVar[0], sampleBelowSize[0])
+        printInvocation(' 2', dmdEnvVar[1], sampleBelowSize[1])
 
     # Print records.
     if args.ignore_reports:
         liveUsableSize, liveBlocks = \
             printRecords('live', liveRecords, heapUsableSize)
     else:
         twiceReportedUsableSize, twiceReportedBlocks = \
             printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
 
         unreportedUsableSize, unreportedBlocks = \
-            printRecords('unreported',     unreportedRecords, heapUsableSize)
+            printRecords('unreported', unreportedRecords, heapUsableSize)
 
         onceReportedUsableSize, onceReportedBlocks = \
-            printRecords('once-reported',  onceReportedRecords, heapUsableSize)
+            printRecords('once-reported', onceReportedRecords, heapUsableSize)
 
     # Print summary.
     out(separator)
     out('Summary {')
     if args.ignore_reports:
         out('  Total: {:} bytes in {:} blocks'.
             format(number(liveUsableSize, heapIsSampled),
                    number(liveBlocks, heapIsSampled)))
@@ -494,10 +669,20 @@ def main():
             format('Twice-reported:',
                    number(twiceReportedUsableSize, heapIsSampled),
                    perc(twiceReportedUsableSize, heapUsableSize),
                    number(twiceReportedBlocks, heapIsSampled),
                    perc(twiceReportedBlocks, heapBlocks)))
     out('}\n')
 
 
+def main():
+    args = parseCommandLine()
+    digest = getDigestFromFile(args, args.input_file)
+    if args.input_file2:
+        digest2 = getDigestFromFile(args, args.input_file2)
+        digest = diffDigests(args, digest, digest2)
+    printDigest(args, digest)
+
+
 if __name__ == '__main__':
     main()
+
--- a/memory/replace/dmd/moz.build
+++ b/memory/replace/dmd/moz.build
@@ -28,12 +28,10 @@ if CONFIG['MOZ_OPTIMIZE']:
 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'dbghelp',
     ]
 
-XPCSHELL_TESTS_MANIFESTS += [
-    'test/xpcshell.ini',
-]
+TEST_DIRS += ['test']
 
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+// This program is used by the DMD xpcshell test. It is run under DMD and
+// produces some output. The xpcshell test then post-processes and checks this
+// output.
+//
+// Note that this file does not have "Test" or "test" in its name, because that
+// will cause the build system to not record breakpad symbols for it, which
+// will stop the post-processing (which includes stack fixing) from working
+// correctly.
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/JSONWriter.h"
+#include "mozilla/UniquePtr.h"
+#include "DMD.h"
+
+using mozilla::JSONWriter;
+using mozilla::MakeUnique;
+using namespace mozilla::dmd;
+
+class FpWriteFunc : public mozilla::JSONWriteFunc
+{
+public:
+  explicit FpWriteFunc(const char* aFilename)
+  {
+    mFp = fopen(aFilename, "w");
+    if (!mFp) {
+      fprintf(stderr, "SmokeDMD: can't create %s file: %s\n",
+              aFilename, strerror(errno));
+      exit(1);
+    }
+  }
+
+  ~FpWriteFunc() { fclose(mFp); }
+
+  void Write(const char* aStr) { fputs(aStr, mFp); }
+
+private:
+  FILE* mFp;
+};
+
+// This stops otherwise-unused variables from being optimized away.
+static void
+UseItOrLoseIt(void* aPtr, int aSeven)
+{
+  char buf[64];
+  int n = sprintf(buf, "%p\n", aPtr);
+  if (n == 20 + aSeven) {
+    fprintf(stderr, "well, that is surprising");
+  }
+}
+
+// This function checks that heap blocks that have the same stack trace but
+// different (or no) reporters get aggregated separately.
+void Foo(int aSeven)
+{
+  char* a[6];
+  for (int i = 0; i < aSeven - 1; i++) {
+    a[i] = (char*) malloc(128 - 16*i);
+  }
+
+  // Oddly, some versions of clang will cause identical stack traces to be
+  // generated for adjacent calls to Report(), which breaks the test. Inserting
+  // the UseItOrLoseIt() calls in between is enough to prevent this.
+
+  Report(a[2]);                     // reported
+
+  UseItOrLoseIt(a[2], aSeven);
+
+  for (int i = 0; i < aSeven - 5; i++) {
+    Report(a[i]);                   // reported
+  }
+
+  UseItOrLoseIt(a[2], aSeven);
+
+  Report(a[3]);                     // reported
+
+  // a[4], a[5] unreported
+}
+
+void
+RunTests()
+{
+  // These files are written to $CWD.
+  auto f1 = MakeUnique<FpWriteFunc>("full-empty.json");
+  auto f2 = MakeUnique<FpWriteFunc>("full-unsampled1.json");
+  auto f3 = MakeUnique<FpWriteFunc>("full-unsampled2.json");
+  auto f4 = MakeUnique<FpWriteFunc>("full-sampled.json");
+
+  // This test relies on the compiler not doing various optimizations, such as
+  // eliding unused malloc() calls or unrolling loops with fixed iteration
+  // counts. So we compile it with -O0 (or equivalent), which probably prevents
+  // that. We also use the following variable for various loop iteration
+  // counts, just in case compilers might unroll very small loops even with
+  // -O0.
+  int seven = 7;
+
+  // Make sure that DMD is actually running; it is initialized on the first
+  // allocation.
+  int *x = (int*)malloc(100);
+  UseItOrLoseIt(x, seven);
+  MOZ_RELEASE_ASSERT(IsRunning());
+
+  // The first part of this test requires sampling to be disabled.
+  SetSampleBelowSize(1);
+
+  // The file manipulations above may have done some heap allocations.
+  // Clear all knowledge of existing blocks to give us a clean slate.
+  ClearBlocks();
+
+  //---------
+
+  // AnalyzeReports 1.  Zero for everything.
+  JSONWriter writer1(Move(f1));
+  AnalyzeReports(writer1);
+
+  //---------
+
+  // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
+  // AnalyzeReports 3: still present and unreported.
+  int i;
+  char* a = nullptr;
+  for (i = 0; i < seven + 3; i++) {
+      a = (char*) malloc(100);
+      UseItOrLoseIt(a, seven);
+  }
+  free(a);
+
+  // Note: 8 bytes is the smallest requested size that gives consistent
+  // behaviour across all platforms with jemalloc.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: thrice-reported.
+  char* a2 = (char*) malloc(8);
+  Report(a2);
+
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: reportedness carries over, due to ReportOnAlloc.
+  char* b = (char*) malloc(10);
+  ReportOnAlloc(b);
+
+  // ReportOnAlloc, then freed.
+  // AnalyzeReports 2: freed, irrelevant.
+  // AnalyzeReports 3: freed, irrelevant.
+  char* b2 = (char*) malloc(1);
+  ReportOnAlloc(b2);
+  free(b2);
+
+  // AnalyzeReports 2: reported 4 times.
+  // AnalyzeReports 3: freed, irrelevant.
+  char* c = (char*) calloc(10, 3);
+  Report(c);
+  for (int i = 0; i < seven - 4; i++) {
+    Report(c);
+  }
+
+  // AnalyzeReports 2: ignored.
+  // AnalyzeReports 3: irrelevant.
+  Report((void*)(intptr_t)i);
+
+  // jemalloc rounds this up to 8192.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: freed.
+  char* e = (char*) malloc(4096);
+  e = (char*) realloc(e, 4097);
+  Report(e);
+
+  // First realloc is like malloc;  second realloc is shrinking.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: re-reported.
+  char* e2 = (char*) realloc(nullptr, 1024);
+  e2 = (char*) realloc(e2, 512);
+  Report(e2);
+
+  // First realloc is like malloc;  second realloc creates a min-sized block.
+  // XXX: on Windows, second realloc frees the block.
+  // AnalyzeReports 2: reported.
+  // AnalyzeReports 3: freed, irrelevant.
+  char* e3 = (char*) realloc(nullptr, 1023);
+//e3 = (char*) realloc(e3, 0);
+  MOZ_ASSERT(e3);
+  Report(e3);
+
+  // AnalyzeReports 2: freed, irrelevant.
+  // AnalyzeReports 3: freed, irrelevant.
+  char* f = (char*) malloc(64);
+  free(f);
+
+  // AnalyzeReports 2: ignored.
+  // AnalyzeReports 3: irrelevant.
+  Report((void*)(intptr_t)0x0);
+
+  // AnalyzeReports 2: mixture of reported and unreported.
+  // AnalyzeReports 3: all unreported.
+  Foo(seven);
+
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: twice-reported.
+  char* g1 = (char*) malloc(77);
+  ReportOnAlloc(g1);
+  ReportOnAlloc(g1);
+
+  // AnalyzeReports 2: mixture of reported and unreported.
+  // AnalyzeReports 3: all unreported.
+  // Nb: this Foo() call is deliberately not adjacent to the previous one. See
+  // the comment about adjacent calls in Foo() for more details.
+  Foo(seven);
+
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: once-reported.
+  char* g2 = (char*) malloc(78);
+  Report(g2);
+  ReportOnAlloc(g2);
+
+  // AnalyzeReports 2: twice-reported.
+  // AnalyzeReports 3: once-reported.
+  char* g3 = (char*) malloc(79);
+  ReportOnAlloc(g3);
+  Report(g3);
+
+  // All the odd-ball ones.
+  // AnalyzeReports 2: all unreported.
+  // AnalyzeReports 3: all freed, irrelevant.
+  // XXX: no memalign on Mac
+//void* w = memalign(64, 65);           // rounds up to 128
+//UseItOrLoseIt(w, seven);
+
+  // XXX: posix_memalign doesn't work on B2G
+//void* x;
+//posix_memalign(&y, 128, 129);         // rounds up to 256
+//UseItOrLoseIt(x, seven);
+
+  // XXX: valloc doesn't work on Windows.
+//void* y = valloc(1);                  // rounds up to 4096
+//UseItOrLoseIt(y, seven);
+
+  // XXX: C11 only
+//void* z = aligned_alloc(64, 256);
+//UseItOrLoseIt(z, seven);
+
+  // AnalyzeReports 2.
+  JSONWriter writer2(Move(f2));
+  AnalyzeReports(writer2);
+
+  //---------
+
+  Report(a2);
+  Report(a2);
+  free(c);
+  free(e);
+  Report(e2);
+  free(e3);
+//free(w);
+//free(x);
+//free(y);
+//free(z);
+
+  // AnalyzeReports 3.
+  JSONWriter writer3(Move(f3));
+  AnalyzeReports(writer3);
+
+  //---------
+
+  // The first part of this test requires sampling to be disabled.
+  SetSampleBelowSize(128);
+
+  // Clear all knowledge of existing blocks to give us a clean slate.
+  ClearBlocks();
+
+  char* s;
+
+  // This equals the sample size, and so is reported exactly.  It should be
+  // listed before records of the same size that are sampled.
+  s = (char*) malloc(128);
+  UseItOrLoseIt(s, seven);
+
+  // This exceeds the sample size, and so is reported exactly.
+  s = (char*) malloc(144);
+  UseItOrLoseIt(s, seven);
+
+  // These together constitute exactly one sample.
+  for (int i = 0; i < seven + 9; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, seven);
+  }
+
+  // These fall 8 bytes short of a full sample.
+  for (int i = 0; i < seven + 8; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, seven);
+  }
+
+  // This exceeds the sample size, and so is recorded exactly.
+  s = (char*) malloc(256);
+  UseItOrLoseIt(s, seven);
+
+  // This gets more than to a full sample from the |i < seven + 8| loop above.
+  s = (char*) malloc(96);
+  UseItOrLoseIt(s, seven);
+
+  // This gets to another full sample.
+  for (int i = 0; i < seven - 2; i++) {
+    s = (char*) malloc(8);
+    UseItOrLoseIt(s, seven);
+  }
+
+  // This allocates 16, 32, ..., 128 bytes, which results in a heap block
+  // record that contains a mix of sample and non-sampled blocks, and so should
+  // be printed with '~' signs.
+  for (int i = 1; i <= seven + 1; i++) {
+    s = (char*) malloc(i * 16);
+    UseItOrLoseIt(s, seven);
+  }
+
+  // At the end we're 64 bytes into the current sample so we report ~1,424
+  // bytes of allocation overall, which is 64 less than the real value 1,488.
+
+  // AnalyzeReports 4.
+  JSONWriter writer4(Move(f4));
+  AnalyzeReports(writer4);
+}
+
+int main()
+{
+  RunTests();
+
+  return 0;
+}
--- a/memory/replace/dmd/test/full-heap-empty-expected.txt
+++ b/memory/replace/dmd/test/full-heap-empty-expected.txt
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-heap-empty-actual.txt --ignore-reports full-empty.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no live heap blocks
 
 #-----------------------------------------------------------------
--- a/memory/replace/dmd/test/full-heap-sampled-expected.txt
+++ b/memory/replace/dmd/test/full-heap-sampled-expected.txt
@@ -1,20 +1,22 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-heap-sampled-actual.txt --ignore-reports full-sampled.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 128
 }
 
 #-----------------------------------------------------------------
 
 Live {
   ~4 blocks in heap block record 1 of 7
   ~512 bytes (~512 requested / ~0 slop)
+  Individual block sizes: ~128 x 3; 128
   35.96% of the heap (35.96% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 2 of 7
--- a/memory/replace/dmd/test/full-heap-unsampled1-expected.txt
+++ b/memory/replace/dmd/test/full-heap-unsampled1-expected.txt
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-heap-unsampled1-actual.txt --ignore-reports full-unsampled1.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   1 block in heap block record 1 of 12
   8,192 bytes (4,097 requested / 4,095 slop)
@@ -23,34 +24,37 @@ Live {
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   9 blocks in heap block record 3 of 12
   1,008 bytes (900 requested / 108 slop)
+  Individual block sizes: 112 x 9
   8.34% of the heap (84.58% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 4 of 12
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   4.37% of the heap (88.95% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 5 of 12
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   4.37% of the heap (93.32% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 6 of 12
--- a/memory/replace/dmd/test/full-heap-unsampled2-expected.txt
+++ b/memory/replace/dmd/test/full-heap-unsampled2-expected.txt
@@ -1,38 +1,42 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-heap-unsampled2-actual.txt --ignore-reports full-unsampled2.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   9 blocks in heap block record 1 of 9
   1,008 bytes (900 requested / 108 slop)
+  Individual block sizes: 112 x 9
   35.49% of the heap (35.49% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 2 of 9
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   18.59% of the heap (54.08% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   6 blocks in heap block record 3 of 9
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   18.59% of the heap (72.68% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Live {
   1 block in heap block record 4 of 9
--- a/memory/replace/dmd/test/full-reports-empty-expected.txt
+++ b/memory/replace/dmd/test/full-reports-empty-expected.txt
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-reports-empty-actual.txt full-empty.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
--- a/memory/replace/dmd/test/full-reports-sampled-expected.txt
+++ b/memory/replace/dmd/test/full-reports-sampled-expected.txt
@@ -1,24 +1,26 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-reports-sampled-actual.txt full-sampled.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 128
 }
 
 #-----------------------------------------------------------------
 
 # no twice-reported heap blocks
 
 #-----------------------------------------------------------------
 
 Unreported {
   ~4 blocks in heap block record 1 of 7
   ~512 bytes (~512 requested / ~0 slop)
+  Individual block sizes: ~128 x 3; 128
   35.96% of the heap (35.96% cumulative)
   35.96% of unreported (35.96% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
--- a/memory/replace/dmd/test/full-reports-unsampled1-expected.txt
+++ b/memory/replace/dmd/test/full-reports-unsampled1-expected.txt
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-reports-unsampled1-actual.txt full-unsampled1.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 4
   80 bytes (79 requested / 1 slop)
@@ -71,36 +72,39 @@ Twice-reported {
   }
 }
 
 #-----------------------------------------------------------------
 
 Unreported {
   9 blocks in heap block record 1 of 3
   1,008 bytes (900 requested / 108 slop)
+  Individual block sizes: 112 x 9
   8.34% of the heap (8.34% cumulative)
   81.82% of unreported (81.82% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   2 blocks in heap block record 2 of 3
   112 bytes (112 requested / 0 slop)
+  Individual block sizes: 64; 48
   0.93% of the heap (9.27% cumulative)
   9.09% of unreported (90.91% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   2 blocks in heap block record 3 of 3
   112 bytes (112 requested / 0 slop)
+  Individual block sizes: 64; 48
   0.93% of the heap (10.19% cumulative)
   9.09% of unreported (100.00% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
@@ -142,29 +146,31 @@ Once-reported {
   Reported at {
     #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   2 blocks in heap block record 4 of 11
   240 bytes (240 requested / 0 slop)
+  Individual block sizes: 128; 112
   1.99% of the heap (82.46% cumulative)
   2.27% of once-reported (94.18% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
   Reported at {
     #01: ... DMD.cpp ...
   }
 }
 
 Once-reported {
   2 blocks in heap block record 5 of 11
   240 bytes (240 requested / 0 slop)
+  Individual block sizes: 128; 112
   1.99% of the heap (84.45% cumulative)
   2.27% of once-reported (96.45% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
   Reported at {
     #01: ... DMD.cpp ...
   }
--- a/memory/replace/dmd/test/full-reports-unsampled2-expected.txt
+++ b/memory/replace/dmd/test/full-reports-unsampled2-expected.txt
@@ -1,12 +1,13 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o full-reports-unsampled2-actual.txt full-unsampled2.json
 
 Invocation {
-  $DMD = '--mode=test'
+  $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Twice-reported {
   1 block in heap block record 1 of 2
   80 bytes (77 requested / 3 slop)
@@ -39,36 +40,39 @@ Twice-reported {
   }
 }
 
 #-----------------------------------------------------------------
 
 Unreported {
   9 blocks in heap block record 1 of 3
   1,008 bytes (900 requested / 108 slop)
+  Individual block sizes: 112 x 9
   35.49% of the heap (35.49% cumulative)
   48.84% of unreported (48.84% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   6 blocks in heap block record 2 of 3
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   18.59% of the heap (54.08% cumulative)
   25.58% of unreported (74.42% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 Unreported {
   6 blocks in heap block record 3 of 3
   528 bytes (528 requested / 0 slop)
+  Individual block sizes: 128; 112; 96; 80; 64; 48
   18.59% of the heap (72.68% cumulative)
   25.58% of unreported (100.00% cumulative)
   Allocated at {
     #01: ... DMD.cpp ...
   }
 }
 
 #-----------------------------------------------------------------
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SimplePrograms([
+    'SmokeDMD',
+])
+
+# See the comment at the top of SmokeDMD.cpp:RunTests().
+if CONFIG['OS_ARCH'] == 'WINNT':
+    CXXFLAGS += ['-Og-']
+else:
+    CXXFLAGS += ['-O0']
+
+DEFINES['MOZ_NO_MOZALLOC'] = True
+
+DISABLE_STL_WRAPPING = True
+
+USE_LIBS += ['dmd']
+
+XPCSHELL_TESTS_MANIFESTS += [
+    'xpcshell.ini',
+]
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff-basic-expected.txt
@@ -0,0 +1,127 @@
+#-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-diff-basic-actual.txt script-diff1.json script-diff2.json
+
+Invocation 1 {
+  $DMD = '--sample-below=127'
+  Sample-below size = 127
+}
+
+Invocation 2 {
+  $DMD = '--sample-below=63'
+  Sample-below size = 63
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+  ~-1 blocks in heap block record 1 of 1
+  ~-1,088 bytes (~-1,064 requested / ~-24 slop)
+  Individual block sizes: -1,024; ~-127; ~63
+  15.46% of the heap (15.46% cumulative)
+  100.00% of twice-reported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+  Reported again at {
+    #01: R2 (R2.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+  4 blocks in heap block record 1 of 5
+  16,384 bytes (16,384 requested / 0 slop)
+  Individual block sizes: 4,096 x 4
+  -232.76% of the heap (-232.76% cumulative)
+  371.01% of unreported (371.01% cumulative)
+  Allocated at {
+    #01: E (E.cpp:99)
+  }
+}
+
+Unreported {
+  ~7 blocks in heap block record 2 of 5
+  ~-11,968 bytes (~-12,016 requested / ~48 slop)
+  Individual block sizes: -15,360; 2,048; 512 x 2; 128; ~-127; 64 x 4; ~63
+  170.02% of the heap (-62.74% cumulative)
+  -271.01% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+}
+
+Unreported {
+  0 blocks in heap block record 3 of 5
+  0 bytes (-384 requested / 384 slop)
+  Individual block sizes: (no change)
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: C (C.cpp:99)
+  }
+}
+
+Unreported {
+  -2 blocks in heap block record 4 of 5
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 8,192 x 2; -4,096 x 4
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: B (B.cpp:99)
+  }
+}
+
+Unreported {
+  0 blocks in heap block record 5 of 5
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 20,480; -16,384; -8,192; 4,096
+  -0.00% of the heap (-62.74% cumulative)
+  0.00% of unreported (100.00% cumulative)
+  Allocated at {
+    #01: G (G.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+  -3 blocks in heap block record 1 of 2
+  -10,240 bytes (-10,192 requested / -48 slop)
+  Individual block sizes: -4,096 x 2; -2,048
+  145.48% of the heap (145.48% cumulative)
+  98.77% of once-reported (98.77% cumulative)
+  Allocated at {
+    #01: D (D.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+}
+
+Once-reported {
+  ~-1 blocks in heap block record 2 of 2
+  ~-127 bytes (~-151 requested / ~24 slop)
+  1.80% of the heap (147.28% cumulative)
+  1.23% of once-reported (100.00% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+  Reported at {
+    #01: R1 (R1.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:               ~-7,039 bytes (100.00%) in      ~4 blocks (100.00%)
+  Unreported:           ~4,416 bytes (-62.74%) in      ~9 blocks (225.00%)
+  Once-reported:      ~-10,367 bytes (147.28%) in     ~-4 blocks (-100.00%)
+  Twice-reported:      ~-1,088 bytes ( 15.46%) in     ~-1 blocks (-25.00%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff-options-expected.txt
@@ -0,0 +1,81 @@
+#-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-diff-options-actual.txt --ignore-reports script-diff1.json script-diff2.json
+
+Invocation 1 {
+  $DMD = '--sample-below=127'
+  Sample-below size = 127
+}
+
+Invocation 2 {
+  $DMD = '--sample-below=63'
+  Sample-below size = 63
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  4 blocks in heap block record 1 of 6
+  16,384 bytes (16,384 requested / 0 slop)
+  Individual block sizes: 4,096 x 4
+  -232.76% of the heap (-232.76% cumulative)
+  Allocated at {
+    #01: E (E.cpp:99)
+  }
+}
+
+Live {
+  ~5 blocks in heap block record 2 of 6
+  ~-13,183 bytes (~-13,231 requested / ~48 slop)
+  Individual block sizes: -15,360; 2,048; -1,024; 512 x 2; 128; ~-127 x 3; 64 x 4; ~63 x 2
+  187.29% of the heap (-45.48% cumulative)
+  Allocated at {
+    #01: F (F.cpp:99)
+  }
+}
+
+Live {
+  -3 blocks in heap block record 3 of 6
+  -10,240 bytes (-10,192 requested / -48 slop)
+  Individual block sizes: -4,096 x 2; -2,048
+  145.48% of the heap (100.00% cumulative)
+  Allocated at {
+    #01: D (D.cpp:99)
+  }
+}
+
+Live {
+  0 blocks in heap block record 4 of 6
+  0 bytes (-384 requested / 384 slop)
+  Individual block sizes: (no change)
+  -0.00% of the heap (100.00% cumulative)
+  Allocated at {
+    #01: C (C.cpp:99)
+  }
+}
+
+Live {
+  0 blocks in heap block record 5 of 6
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 20,480; -16,384; -8,192; 4,096
+  -0.00% of the heap (100.00% cumulative)
+  Allocated at {
+    #01: G (G.cpp:99)
+  }
+}
+
+Live {
+  -2 blocks in heap block record 6 of 6
+  0 bytes (0 requested / 0 slop)
+  Individual block sizes: 8,192 x 2; -4,096 x 4
+  -0.00% of the heap (100.00% cumulative)
+  Allocated at {
+    #01: B (B.cpp:99)
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: ~-7,039 bytes in ~4 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff1.json
@@ -0,0 +1,62 @@
+{
+ "version": 1,
+ "invocation": {
+  "dmdEnvVar": "--sample-below=127",
+  "sampleBelowSize": 127
+ },
+ "blockList": [
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+  {"req": 4096, "alloc": "B"},
+
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+  {"req": 4096, "alloc": "C"},
+
+  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
+  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
+  {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
+
+  {"req": 15360,            "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {                         "alloc": "F"},
+  {"req": 1024,             "alloc": "F", "reps": ["R1"]},
+  {                         "alloc": "F", "reps": ["R1"]},
+  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
+  {                         "alloc": "F", "reps": ["R1", "R2"]},
+
+  {"req": 4096,            "alloc": "G"},
+  {"req": 8192,            "alloc": "G"},
+  {"req": 16384,           "alloc": "G"}
+ ],
+ "traceTable": {
+  "A": ["AA"],
+  "B": ["BB"],
+  "C": ["CC"],
+  "D": ["DD"],
+  "E": ["EE"],
+  "F": ["FF"],
+  "G": ["GG"],
+  "R1": ["RR1"],
+  "R2": ["RR2"]
+ },
+ "frameTable": {
+  "AA": "#00: A (A.cpp:99)",
+  "BB": "#00: B (B.cpp:99)",
+  "CC": "#00: C (C.cpp:99)",
+  "DD": "#00: D (D.cpp:99)",
+  "EE": "#00: E (E.cpp:99)",
+  "FF": "#00: F (F.cpp:99)",
+  "GG": "#00: G (G.cpp:99)",
+  "RR1": "#00: R1 (R1.cpp:99)",
+  "RR2": "#00: R2 (R2.cpp:99)"
+ }
+}
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/script-diff2.json
@@ -0,0 +1,66 @@
+{
+ "version": 1,
+ "invocation": {
+  "dmdEnvVar": "--sample-below=63",
+  "sampleBelowSize": 63
+ },
+ "blockList": [
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+  {"req": 4096, "alloc": "A"},
+
+  {"req": 8192, "alloc": "B"},
+  {"req": 8192, "alloc": "B"},
+
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+  {"req": 4000, "slop": 96, "alloc": "C"},
+
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+  {"req": 4096, "alloc": "E"},
+
+  {"req": 2000, "slop": 48, "alloc": "F"},
+  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1"]},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 512,              "alloc": "F"},
+  {"req": 128,              "alloc": "F"},
+  {                         "alloc": "F", "reps": ["R1", "R2"]},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {"req": 64,               "alloc": "F"},
+  {                         "alloc": "F"},
+
+  {"req": 4096,            "alloc": "G"},
+  {"req": 4096,            "alloc": "G"},
+  {"req": 20480,           "alloc": "G"}
+ ],
+ "traceTable": {
+  "A": ["AA"],
+  "B": ["BB"],
+  "C": ["CC"],
+  "D": ["DD"],
+  "E": ["EE"],
+  "F": ["FF"],
+  "G": ["GG"],
+  "R1": ["RR1"],
+  "R2": ["RR2"]
+ },
+ "frameTable": {
+  "AA": "#00: A (A.cpp:99)",
+  "BB": "#00: B (B.cpp:99)",
+  "CC": "#00: C (C.cpp:99)",
+  "DD": "#00: D (D.cpp:99)",
+  "EE": "#00: E (E.cpp:99)",
+  "FF": "#00: F (F.cpp:99)",
+  "GG": "#00: G (G.cpp:99)",
+  "RR1": "#00: R1 (R1.cpp:99)",
+  "RR2": "#00: R2 (R2.cpp:99)"
+ }
+}
--- a/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
+++ b/memory/replace/dmd/test/script-ignore-alloc-fns-expected.txt
@@ -1,9 +1,10 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-reports --ignore-alloc-fns script-ignore-alloc-fns.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 2500
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-max-frames-1-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-1-expected.txt
@@ -1,20 +1,22 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-max-frames-1-actual.txt --ignore-reports --max-frames=1 script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   4 blocks in heap block record 1 of 1
   4,416 bytes (4,404 requested / 12 slop)
+  Individual block sizes: 4,096; 128; 112; 80
   100.00% of the heap (100.00% cumulative)
   Allocated at {
     #01: E (E.cpp:99)
   }
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-max-frames-3-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-3-expected.txt
@@ -1,20 +1,22 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-max-frames-3-actual.txt --ignore-reports --max-frames=3 --no-fix-stacks script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   2 blocks in heap block record 1 of 3
   4,224 bytes (4,224 requested / 0 slop)
+  Individual block sizes: 4,096; 128
   95.65% of the heap (95.65% cumulative)
   Allocated at {
     #01: E (E.cpp:99)
     #02: F (F.cpp:99)
     #03: G (G.cpp:99)
   }
 }
 
--- a/memory/replace/dmd/test/script-max-frames-8-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-8-expected.txt
@@ -1,9 +1,10 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --ignore-reports --max-frames=8 script-max-frames.json
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
deleted file mode 100644
--- a/memory/replace/dmd/test/script-show-all-block-sizes-expected.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '1'
-  Sample-below size = 4093
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  ~15 blocks in heap block record 1 of 1
-  ~1,343,470 bytes (~1,342,313 requested / ~1,157 slop)
-  100.00% of the heap (100.00% cumulative)
-  Individual block sizes: 1,048,576; 65,536 x 3; 40,960; 8,192 x 4; ~4,093 x 6
-  Allocated at {
-    #01: A (A.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: ~1,343,470 bytes in ~15 blocks
-}
-
deleted file mode 100644
--- a/memory/replace/dmd/test/script-show-all-block-sizes.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "version": 1,
- "invocation": {
-  "dmdEnvVar": "1",
-  "sampleBelowSize": 4093
- },
- "blockList": [
-  {"req": 1048576, "alloc": "A"},
-
-  {"req": 65536,            "alloc": "A"},
-  {"req": 65535, "slop": 1, "alloc": "A"},
-  {"req": 65534, "slop": 2, "alloc": "A"},
-
-  {"req": 40000, "slop": 960, "alloc": "A"},
-
-  {"req": 8192,              "alloc": "A"},
-  {"req": 8192,              "alloc": "A"},
-  {"req": 8190, "slop":   2, "alloc": "A"},
-  {"req": 8000, "slop": 192, "alloc": "A"},
-
-  {"alloc": "A"},
-  {"alloc": "A"},
-  {"alloc": "A"},
-  {"alloc": "A"},
-  {"alloc": "A"},
-  {"alloc": "A"}
- ],
- "traceTable": {
-  "A": ["AA"]
- },
- "frameTable": {
-  "AA": "#00: A (A.cpp:99)"
- }
-}
-
--- a/memory/replace/dmd/test/script-sort-by-req-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-req-expected.txt
@@ -1,38 +1,42 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-sort-by-req-actual.txt --ignore-reports --sort-by=req --no-fix-stacks script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   5 blocks in heap block record 1 of 3
   16,392 bytes (16,392 requested / 0 slop)
+  Individual block sizes: 4,096 x 4; 8
   33.33% of the heap (33.33% cumulative)
   Allocated at {
     #01: A (A.cpp:99)
   }
 }
 
 Live {
   5 blocks in heap block record 2 of 3
   16,400 bytes (12,016 requested / 4,384 slop)
+  Individual block sizes: 4,096 x 4; 16
   33.35% of the heap (66.68% cumulative)
   Allocated at {
     #01: B (B.cpp:99)
   }
 }
 
 Live {
   4 blocks in heap block record 3 of 3
   16,384 bytes (8,196 requested / 8,188 slop)
+  Individual block sizes: 4,096 x 4
   33.32% of the heap (100.00% cumulative)
   Allocated at {
     #01: C (C.cpp:99)
   }
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-sort-by-slop-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-slop-expected.txt
@@ -1,38 +1,42 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-sort-by-slop-actual.txt --ignore-reports --sort-by=slop script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   4 blocks in heap block record 1 of 3
   16,384 bytes (8,196 requested / 8,188 slop)
+  Individual block sizes: 4,096 x 4
   33.32% of the heap (33.32% cumulative)
   Allocated at {
     #01: C (C.cpp:99)
   }
 }
 
 Live {
   5 blocks in heap block record 2 of 3
   16,400 bytes (12,016 requested / 4,384 slop)
+  Individual block sizes: 4,096 x 4; 16
   33.35% of the heap (66.67% cumulative)
   Allocated at {
     #01: B (B.cpp:99)
   }
 }
 
 Live {
   5 blocks in heap block record 3 of 3
   16,392 bytes (16,392 requested / 0 slop)
+  Individual block sizes: 4,096 x 4; 8
   33.33% of the heap (100.00% cumulative)
   Allocated at {
     #01: A (A.cpp:99)
   }
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/script-sort-by-usable-expected.txt
+++ b/memory/replace/dmd/test/script-sort-by-usable-expected.txt
@@ -1,38 +1,42 @@
 #-----------------------------------------------------------------
+# dmd.py --filter-stacks-for-testing -o script-sort-by-usable-actual.txt --ignore-reports --sort-by=usable script-sort-by.json.gz
 
 Invocation {
   $DMD = '1'
   Sample-below size = 1
 }
 
 #-----------------------------------------------------------------
 
 Live {
   5 blocks in heap block record 1 of 3
   16,400 bytes (12,016 requested / 4,384 slop)
+  Individual block sizes: 4,096 x 4; 16
   33.35% of the heap (33.35% cumulative)
   Allocated at {
     #01: B (B.cpp:99)
   }
 }
 
 Live {
   5 blocks in heap block record 2 of 3
   16,392 bytes (16,392 requested / 0 slop)
+  Individual block sizes: 4,096 x 4; 8
   33.33% of the heap (66.68% cumulative)
   Allocated at {
     #01: A (A.cpp:99)
   }
 }
 
 Live {
   4 blocks in heap block record 3 of 3
   16,384 bytes (8,196 requested / 8,188 slop)
+  Individual block sizes: 4,096 x 4
   33.32% of the heap (100.00% cumulative)
   Allocated at {
     #01: C (C.cpp:99)
   }
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -10,113 +10,174 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 
 // The xpcshell test harness sets PYTHON so we can read it here.
 let gEnv = Cc["@mozilla.org/process/environment;1"]
              .getService(Ci.nsIEnvironment);
 let gPythonName = gEnv.get("PYTHON");
 
-// If we're testing locally, the script is in "CurProcD". Otherwise, it is in
-// another location that we have to find.
-let gDmdScriptFile = FileUtils.getFile("CurProcD", ["dmd.py"]);
-if (!gDmdScriptFile.exists()) {
-  gDmdScriptFile = FileUtils.getFile("CurWorkD", []);
-  while (gDmdScriptFile.path.contains("xpcshell")) {
-    gDmdScriptFile = gDmdScriptFile.parent;
+// If we're testing locally, the executable file is in "CurProcD". Otherwise,
+// it is in another location that we have to find.
+function getExecutable(aFilename) {
+  let file = FileUtils.getFile("CurProcD", [aFilename]);
+  if (!file.exists()) {
+    file = FileUtils.getFile("CurWorkD", []);
+    while (file.path.contains("xpcshell")) {
+      file = file.parent;
+    }
+    file.append("bin");
+    file.append(aFilename);
   }
-  gDmdScriptFile.append("bin");
-  gDmdScriptFile.append("dmd.py");
+  return file;
 }
 
-function test(aJsonFile, aPrefix, aOptions) {
+let gIsWindows = Cc["@mozilla.org/xre/app-info;1"]
+                 .getService(Ci.nsIXULRuntime).OS === "WINNT";
+let gDmdTestFile = getExecutable("SmokeDMD" + (gIsWindows ? ".exe" : ""));
+
+let gDmdScriptFile = getExecutable("dmd.py");
+
+function readFile(aFile) {
+  var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+                  .createInstance(Ci.nsIFileInputStream);
+  var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+                  .createInstance(Ci.nsIConverterInputStream);
+  fstream.init(aFile, -1, 0, 0);
+  cstream.init(fstream, "UTF-8", 0, 0);
+
+  var data = "";
+  let (str = {}) {
+    let read = 0;
+    do {
+      // Read as much as we can and put it in str.value.
+      read = cstream.readString(0xffffffff, str);
+      data += str.value;
+    } while (read != 0);
+  }
+  cstream.close();                // this closes fstream
+  return data.replace(/\r/g, ""); // normalize line endings
+}
+
+function runProcess(aExeFile, aArgs) {
+  let process = Cc["@mozilla.org/process/util;1"]
+                  .createInstance(Components.interfaces.nsIProcess);
+  process.init(aExeFile);
+  process.run(/* blocking = */true, aArgs, aArgs.length);
+  return process.exitValue;
+}
+
+function test(aPrefix, aArgs) {
   // DMD writes the JSON files to CurWorkD, so we do likewise here with
   // |actualFile| for consistency. It is removed once we've finished.
   let expectedFile = FileUtils.getFile("CurWorkD", [aPrefix + "-expected.txt"]);
   let actualFile   = FileUtils.getFile("CurWorkD", [aPrefix + "-actual.txt"]);
 
   // Run dmd.py on the JSON file, producing |actualFile|.
 
-  let pythonFile = new FileUtils.File(gPythonName);
-  let pythonProcess = Cc["@mozilla.org/process/util;1"]
-                        .createInstance(Components.interfaces.nsIProcess);
-  pythonProcess.init(pythonFile);
-
   let args = [
     gDmdScriptFile.path,
     "--filter-stacks-for-testing",
     "-o", actualFile.path
-  ];
-  args = args.concat(aOptions);
-  args.push(aJsonFile.path);
+  ].concat(aArgs);
+
+  runProcess(new FileUtils.File(gPythonName), args);
 
-  pythonProcess.run(/* blocking = */true, args, args.length);
+  // Compare |expectedFile| with |actualFile|. We produce nice diffs with
+  // /usr/bin/diff on systems that have it (Mac and Linux). Otherwise (Windows)
+  // we do a string compare of the file contents and then print them both if
+  // they don't match.
 
-  // Compare |expectedFile| with |actualFile|. Difference are printed to
-  // stdout.
+  let success;
+  try {
+    let rv = runProcess(new FileUtils.File("/usr/bin/diff"),
+                        ["-u", expectedFile.path, actualFile.path]);
+    success = rv == 0;
 
-  let diffFile = new FileUtils.File("/usr/bin/diff");
-  let diffProcess = Cc["@mozilla.org/process/util;1"]
-                      .createInstance(Components.interfaces.nsIProcess);
-  // XXX: this doesn't work on Windows (bug 1076446).
-  diffProcess.init(diffFile);
+  } catch (e) {
+    let expectedData = readFile(expectedFile);
+    let actualData   = readFile(actualFile);
+    success = expectedData === actualData;
+    if (!success) {
+      expectedData = expectedData.split("\n");
+      actualData = actualData.split("\n");
+      for (let i = 0; i < expectedData.length; i++) {
+        print("EXPECTED:" + expectedData[i]);
+      }
+      for (let i = 0; i < actualData.length; i++) {
+        print("  ACTUAL:" + actualData[i]);
+      }
+    }
+  }
 
-  args = ["-u", expectedFile.path, actualFile.path];
-  diffProcess.run(/* blocking = */true, args, args.length);
-  let success = diffProcess.exitValue == 0;
   ok(success, aPrefix);
 
   actualFile.remove(true);
 }
 
 function run_test() {
-  let jsonFile;
+  let jsonFile, jsonFile2;
 
   // These tests do full end-to-end testing of DMD, i.e. both the C++ code that
   // generates the JSON output, and the script that post-processes that output.
-  // The test relies on DMD's test mode executing beforehand, in order to
-  // produce the relevant JSON files.
   //
   // Run these synchronously, because test() updates the full*.json files
   // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
   // asynchronously.
+
+  gEnv.set("DMD", "1");
+  gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
+
+  runProcess(gDmdTestFile, []);
+
   let fullTestNames = ["empty", "unsampled1", "unsampled2", "sampled"];
   for (let i = 0; i < fullTestNames.length; i++) {
       let name = fullTestNames[i];
       jsonFile = FileUtils.getFile("CurWorkD", ["full-" + name + ".json"]);
-      test(jsonFile, "full-heap-" + name, ["--ignore-reports"])
-      test(jsonFile, "full-reports-" + name, [])
+      test("full-heap-" + name, ["--ignore-reports", jsonFile.path])
+      test("full-reports-" + name, [jsonFile.path])
       jsonFile.remove(true);
   }
 
   // These tests only test the post-processing script. They use hand-written
   // JSON files as input. Ideally the JSON files would contain comments
   // explaining how they work, but JSON doesn't allow comments, so I've put
   // explanations here.
 
   // This just tests that stack traces of various lengths are truncated
   // appropriately. The number of records in the output is different for each
   // of the tested values.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-max-frames.json"]);
-  test(jsonFile, "script-max-frames-8", ["-r", "--max-frames=8"]);
-  test(jsonFile, "script-max-frames-3", ["-r", "--max-frames=3",
-                                         "--no-fix-stacks"]);
-  test(jsonFile, "script-max-frames-1", ["-r", "--max-frames=1"]);
+  test("script-max-frames-8",
+       ["--ignore-reports", "--max-frames=8", jsonFile.path]);
+  test("script-max-frames-3",
+       ["--ignore-reports", "--max-frames=3", "--no-fix-stacks",
+        jsonFile.path]);
+  test("script-max-frames-1",
+       ["--ignore-reports", "--max-frames=1", jsonFile.path]);
 
-  // This test has three records that are shown in a different order for each
+  // This file has three records that are shown in a different order for each
   // of the different sort values. It also tests the handling of gzipped JSON
   // files.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-sort-by.json.gz"]);
-  test(jsonFile, "script-sort-by-usable", ["-r", "--sort-by=usable"]);
-  test(jsonFile, "script-sort-by-req",    ["-r", "--sort-by=req",
-                                           "--no-fix-stacks"]);
-  test(jsonFile, "script-sort-by-slop",   ["-r", "--sort-by=slop"]);
+  test("script-sort-by-usable",
+       ["--ignore-reports", "--sort-by=usable", jsonFile.path]);
+  test("script-sort-by-req",
+       ["--ignore-reports", "--sort-by=req", "--no-fix-stacks", jsonFile.path]);
+  test("script-sort-by-slop",
+       ["--ignore-reports", "--sort-by=slop", jsonFile.path]);
 
-  // This test has several real stack traces taken from Firefox execution, each
+  // This file has several real stack traces taken from Firefox execution, each
   // of which tests a different allocator function (or functions).
   jsonFile = FileUtils.getFile("CurWorkD", ["script-ignore-alloc-fns.json"]);
-  test(jsonFile, "script-ignore-alloc-fns", ["-r", "--ignore-alloc-fns"]);
+  test("script-ignore-alloc-fns",
+       ["--ignore-reports", "--ignore-alloc-fns", jsonFile.path]);
 
-  // This test has numerous allocations of different sizes, some repeated, some
-  // sampled, that all end up in the same record.
-  jsonFile = FileUtils.getFile("CurWorkD", ["script-show-all-block-sizes.json"]);
-  test(jsonFile, "script-show-all-block-sizes", ["-r", "--show-all-block-sizes"]);
+  // This tests diffs. The first invocation has no options, the second has
+  // several.
+  jsonFile  = FileUtils.getFile("CurWorkD", ["script-diff1.json"]);
+  jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff2.json"]);
+  test("script-diff-basic",
+       [jsonFile.path, jsonFile2.path]);
+  test("script-diff-options",
+       ["--ignore-reports", jsonFile.path, jsonFile2.path]);
 }
+
--- a/memory/replace/dmd/test/xpcshell.ini
+++ b/memory/replace/dmd/test/xpcshell.ini
@@ -13,16 +13,19 @@ support-files =
   script-max-frames-3-expected.txt
   script-max-frames-1-expected.txt
   script-sort-by.json.gz
   script-sort-by-usable-expected.txt
   script-sort-by-req-expected.txt
   script-sort-by-slop-expected.txt
   script-ignore-alloc-fns.json
   script-ignore-alloc-fns-expected.txt
-  script-show-all-block-sizes.json
-  script-show-all-block-sizes-expected.txt
+  script-diff1.json
+  script-diff2.json
+  script-diff-basic-expected.txt
+  script-diff-options-expected.txt
 
 # Bug 1077230 explains why this test is disabled on Mac 10.6.
-# Bug 1076446 is open for getting this test working on on Windows.
+# Bug 1076446 comment 20 explains why this test is only enabled on Windows 5.1
+# (WinXP) and 6.1 (Win7), but not 6.2 (Win8).
 [test_dmd.js]
 dmd = true
-run-if = os == 'linux' || os == 'mac' && os_version != '10.6'
+run-if = os == 'linux' || os == 'mac' && os_version != '10.6' || os == 'win' && (os_version == '5.1' || os_version == '6.1')
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2374,22 +2374,32 @@ public class GeckoAppShell
 
     @WrapElementForJNI(stubName = "GetCurrentNetworkInformationWrapper")
     public static double[] getCurrentNetworkInformation() {
         return GeckoNetworkManager.getInstance().getCurrentInformation();
     }
 
     @WrapElementForJNI
     public static void enableNetworkNotifications() {
-        GeckoNetworkManager.getInstance().enableNotifications();
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                GeckoNetworkManager.getInstance().enableNotifications();
+            }
+        });
     }
 
     @WrapElementForJNI
     public static void disableNetworkNotifications() {
-        GeckoNetworkManager.getInstance().disableNotifications();
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                GeckoNetworkManager.getInstance().disableNotifications();
+            }
+        });
     }
 
     /**
      * Decodes a byte array from Base64 format.
      * No blanks or line breaks are allowed within the Base64 encoded input data.
      * @param s     A string containing the Base64 encoded data.
      * @return      An array containing the decoded data bytes.
      * @throws      IllegalArgumentException If the input is not valid Base64 encoded data.
--- a/mobile/android/base/GeckoNetworkManager.java
+++ b/mobile/android/base/GeckoNetworkManager.java
@@ -32,17 +32,17 @@ import android.util.Log;
  * Current connection is firstly obtained from Android's ConnectivityManager,
  * which is represented by the constant, and then will be mapped into the
  * connection type defined in Network Information API version 3.
  */
 
 public class GeckoNetworkManager extends BroadcastReceiver implements NativeEventListener {
     private static final String LOGTAG = "GeckoNetworkManager";
 
-    static private GeckoNetworkManager sInstance;
+    private static GeckoNetworkManager sInstance;
 
     public static void destroy() {
         if (sInstance != null) {
             sInstance.onDestroy();
             sInstance = null;
         }
     }
 
@@ -70,29 +70,30 @@ public class GeckoNetworkManager extends
     private GeckoNetworkManager() {
         EventDispatcher.getInstance().registerGeckoThreadListener(this, "Wifi:Enable");
     }
 
     private void onDestroy() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Wifi:Enable");
     }
 
-    private ConnectionType mConnectionType = ConnectionType.NONE;
+    private volatile ConnectionType mConnectionType = ConnectionType.NONE;
     private final IntentFilter mNetworkFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
 
     // Whether the manager should be listening to Network Information changes.
     private boolean mShouldBeListening;
 
     // Whether the manager should notify Gecko that a change in Network
     // Information happened.
     private boolean mShouldNotify;
 
     // The application context used for registering receivers, so
     // we can unregister them again later.
     private volatile Context mApplicationContext;
+    private boolean mIsListening;
 
     public static GeckoNetworkManager getInstance() {
         if (sInstance == null) {
             sInstance = new GeckoNetworkManager();
         }
 
         return sInstance;
     }
@@ -113,24 +114,33 @@ public class GeckoNetworkManager extends
         updateConnectionType();
 
         if (mShouldNotify) {
             startListening();
         }
     }
 
     private void startListening() {
+        if (mIsListening) {
+            Log.w(LOGTAG, "Already started!");
+            return;
+        }
+
         final Context appContext = mApplicationContext;
         if (appContext == null) {
             Log.w(LOGTAG, "Not registering receiver: no context!");
             return;
         }
 
-        Log.v(LOGTAG, "Registering receiver.");
-        appContext.registerReceiver(this, mNetworkFilter);
+        // registerReceiver will return null if registering fails.
+        if (appContext.registerReceiver(this, mNetworkFilter) == null) {
+            Log.e(LOGTAG, "Registering receiver failed");
+        } else {
+            mIsListening = true;
+        }
     }
 
     public void stop() {
         mShouldBeListening = false;
 
         if (mShouldNotify) {
             stopListening();
         }
@@ -153,17 +163,23 @@ public class GeckoNetworkManager extends
         }
     }
 
     private void stopListening() {
         if (null == mApplicationContext) {
             return;
         }
 
+        if (!mIsListening) {
+            Log.w(LOGTAG, "Already stopped!");
+            return;
+        }
+
         mApplicationContext.unregisterReceiver(this);
+        mIsListening = false;
     }
 
     private int wifiDhcpGatewayAddress() {
         if (mConnectionType != ConnectionType.WIFI) {
             return 0;
         }
 
         if (null == mApplicationContext) {
@@ -183,33 +199,39 @@ public class GeckoNetworkManager extends
             // getDhcpInfo() is not documented to require any permissions, but on some devices
             // requires android.permission.ACCESS_WIFI_STATE. Just catch the generic exception
             // here and returning 0. Not logging because this could be noisy.
             return 0;
         }
     }
 
     private void updateConnectionType() {
-        ConnectionType previousConnectionType = mConnectionType;
-        mConnectionType = getConnectionType();
+        final ConnectionType previousConnectionType = mConnectionType;
+        final ConnectionType newConnectionType = getConnectionType();
+        if (newConnectionType == previousConnectionType) {
+            return;
+        }
 
-        if (mConnectionType == previousConnectionType || !mShouldNotify) {
+        mConnectionType = newConnectionType;
+
+        if (!mShouldNotify) {
             return;
         }
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkEvent(
-                                       mConnectionType.value,
-                                       mConnectionType == ConnectionType.WIFI,
+                                       newConnectionType.value,
+                                       newConnectionType == ConnectionType.WIFI,
                                        wifiDhcpGatewayAddress()));
     }
 
     public double[] getCurrentInformation() {
-        return new double[] { mConnectionType.value,
-                              (mConnectionType == ConnectionType.WIFI) ? 1.0 : 0.0,
-                              wifiDhcpGatewayAddress()};
+        final ConnectionType connectionType = mConnectionType;
+        return new double[] { connectionType.value,
+                              connectionType == ConnectionType.WIFI ? 1.0 : 0.0,
+                              wifiDhcpGatewayAddress() };
     }
 
     public void enableNotifications() {
         // We set mShouldNotify *after* calling updateConnectionType() to make sure we
         // don't notify an eventual change in mConnectionType.
         mConnectionType = ConnectionType.NONE; // force a notification
         updateConnectionType();
         mShouldNotify = true;
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -98,16 +98,17 @@ skip-if = android_version == "10"
 # Using JavascriptTest
 [testAccounts]
 [testAndroidLog]
 [testBrowserDiscovery]
 [testDebuggerServer]
 [testDeviceSearchEngine]
 [testJNI]
 # [testMozPay] # see bug 945675
+[testNetworkManager]
 [testOrderedBroadcast]
 [testOSLocale]
 [testResourceSubstitutions]
 [testRestrictedProfiles]
 [testSharedPreferences]
 [testSimpleDiscovery]
 [testUITelemetry]
 [testVideoControls]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testNetworkManager.java
@@ -0,0 +1,11 @@
+/* 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/. */
+
+package org.mozilla.gecko.tests;
+
+public class testNetworkManager extends JavascriptTest {
+    public testNetworkManager() {
+        super("testNetworkManager.js");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testNetworkManager.js
@@ -0,0 +1,28 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ok(passed, text) {
+  do_report_result(passed, text, Components.stack.caller, false);
+}
+
+add_test(function check_linktype() {
+  // Let's exercise the interface. Even if the network is not up, we can make sure nothing blows up.
+  let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
+  if (network.isLinkUp) {
+    ok(network.linkType != Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN, "LinkType is not UNKNOWN");
+  } else {
+    ok(network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN, "LinkType is UNKNOWN");
+  }
+
+  run_next_test();
+});
+
+run_next_test();
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -5251,72 +5251,66 @@ web.pk
 gov.pk
 gob.pk
 gok.pk
 gon.pk
 gop.pk
 gos.pk
 info.pk
 
-// pl : http://www.dns.pl/english/
+// pl http://www.dns.pl/english/index.html
+// confirmed on 26.09.2014 from Bogna Tchórzewska <partner@dns.pl>
 pl
-// NASK functional domains (nask.pl / dns.pl) : http://www.dns.pl/english/dns-funk.html
+com.pl
+net.pl
+org.pl
+info.pl
+waw.pl
+gov.pl
+// pl functional domains (http://www.dns.pl/english/index.html)
 aid.pl
 agro.pl
 atm.pl
 auto.pl
 biz.pl
-com.pl
 edu.pl
 gmina.pl
 gsm.pl
-info.pl
 mail.pl
 miasta.pl
 media.pl
 mil.pl
-net.pl
 nieruchomosci.pl
 nom.pl
-org.pl
 pc.pl
 powiat.pl
 priv.pl
 realestate.pl
 rel.pl
 sex.pl
 shop.pl
 sklep.pl
 sos.pl
 szkola.pl
 targi.pl
 tm.pl
 tourism.pl
 travel.pl
 turystyka.pl
-// ICM functional domains (icm.edu.pl)
-6bone.pl
-art.pl
-mbone.pl
 // Government domains (administred by ippt.gov.pl)
-gov.pl
 uw.gov.pl
 um.gov.pl
 ug.gov.pl
 upow.gov.pl
 starostwo.gov.pl
 so.gov.pl
 sr.gov.pl
 po.gov.pl
 pa.gov.pl
-// other functional domains
-ngo.pl
-irc.pl
-usenet.pl
-// NASK geographical domains : http://www.dns.pl/english/dns-regiony.html
+// pl regional domains (http://www.dns.pl/english/index.html)
 augustow.pl
 babia-gora.pl
 bedzin.pl
 beskidy.pl
 bialowieza.pl
 bialystok.pl
 bielawa.pl
 bieszczady.pl
@@ -5392,17 +5386,16 @@ pruszkow.pl
 przeworsk.pl
 pulawy.pl
 radom.pl
 rawa-maz.pl
 rybnik.pl
 rzeszow.pl
 sanok.pl
 sejny.pl
-siedlce.pl
 slask.pl
 slupsk.pl
 sosnowiec.pl
 stalowa-wola.pl
 skoczow.pl
 starachowice.pl
 stargard.pl
 suwalki.pl
@@ -5414,41 +5407,28 @@ szczytno.pl
 tarnobrzeg.pl
 tgory.pl
 turek.pl
 tychy.pl
 ustka.pl
 walbrzych.pl
 warmia.pl
 warszawa.pl
-waw.pl
 wegrow.pl
 wielun.pl
 wlocl.pl
 wloclawek.pl
 wodzislaw.pl
 wolomin.pl
 wroclaw.pl
 zachpomor.pl
 zagan.pl
 zarow.pl
 zgora.pl
 zgorzelec.pl
-// TASK geographical domains (www.task.gda.pl/uslugi/dns)
-gda.pl
-gdansk.pl
-gdynia.pl
-med.pl
-sopot.pl
-// other geographical domains
-gliwice.pl
-krakow.pl
-poznan.pl
-wroc.pl
-zakopane.pl
 
 // pm : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
 pm
 
 // pn : http://www.government.pn/PnRegistry/policies.htm
 pn
 gov.pn
 co.pn
@@ -8698,30 +8678,32 @@ githubusercontent.com
 
 // GlobeHosting, Inc.
 // Submitted by Zoltan Egresi <egresi@globehosting.com> 2013-07-12
 ro.com
 
 // Google, Inc.
 // Submitted by Eduardo Vela <evn@google.com> 2012-10-24
 appspot.com
+blogspot.ae
 blogspot.be
 blogspot.bj
 blogspot.ca
 blogspot.cf
 blogspot.ch
 blogspot.co.at
 blogspot.co.il
 blogspot.co.nz
 blogspot.co.uk
 blogspot.com
 blogspot.com.ar
 blogspot.com.au
 blogspot.com.br
 blogspot.com.es
+blogspot.com.tr
 blogspot.cv
 blogspot.cz
 blogspot.de
 blogspot.dk
 blogspot.fi
 blogspot.fr
 blogspot.gr
 blogspot.hk
@@ -8733,16 +8715,17 @@ blogspot.jp
 blogspot.kr
 blogspot.mr
 blogspot.mx
 blogspot.nl
 blogspot.no
 blogspot.pt
 blogspot.re
 blogspot.ro
+blogspot.ru
 blogspot.se
 blogspot.sg
 blogspot.sk
 blogspot.td
 blogspot.tw
 codespot.com
 googleapis.com
 googlecode.com
@@ -8785,28 +8768,43 @@ nid.io
 // Opera Software, A.S.A.
 // Submitted by Yngve Pettersen <yngve@opera.com> 2009-11-26
 operaunite.com
 
 // OutSystems
 // Submitted by Duarte Santos <domain-admin@outsystemscloud.com> 2014-03-11
 outsystemscloud.com
 
+// .pl domains (grandfathered)
+art.pl
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
 // Red Hat, Inc. OpenShift : https://openshift.redhat.com/
 // Submitted by Tim Kramer <tkramer@rhcloud.com> 2012-10-24
 rhcloud.com
 
 // GDS : https://www.gov.uk/service-manual/operations/operating-servicegovuk-subdomains
 // Submitted by David Illsley <david.illsley@digital.cabinet-office.gov.uk> 2014-08-28
 service.gov.uk
 
 // priv.at : http://www.nic.priv.at/
 // Submitted by registry <lendl@nic.at> 2008-06-09
 priv.at
 
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+
 // Yola : https://www.yola.com/
 // Submitted by Stefano Rivera <stefano@yola.com> 2014-07-09
 yolasite.com
 
 // ZaNiC : http://www.za.net/
 // Submitted by registry <hostmaster@nic.za.net> 2009-10-03
 za.net
 za.org
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -606,42 +606,114 @@ public abstract class TreeBuilder<T> imp
         framesetOk = true;
         if (fragment) {
             T elt;
             if (contextNode != null) {
                 elt = contextNode;
             } else {
                 elt = createHtmlElementSetAsRoot(tokenizer.emptyAttributes());
             }
-            StackNode<T> node = new StackNode<T>(ElementName.HTML, elt
-            // [NOCPP[
-                    , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
-            // ]NOCPP]
-            );
-            currentPtr++;
-            stack[currentPtr] = node;
-            if ("template" == contextName) {
-                pushTemplateMode(IN_TEMPLATE);
-            }
-            resetTheInsertionMode();
-            formPointer = getFormPointerForContext(contextNode);
-            if ("title" == contextName || "textarea" == contextName) {
-                tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, contextName);
-            } else if ("style" == contextName || "xmp" == contextName
-                    || "iframe" == contextName || "noembed" == contextName
-                    || "noframes" == contextName
-                    || (scriptingEnabled && "noscript" == contextName)) {
-                tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, contextName);
-            } else if ("plaintext" == contextName) {
-                tokenizer.setStateAndEndTagExpectation(Tokenizer.PLAINTEXT, contextName);
-            } else if ("script" == contextName) {
-                tokenizer.setStateAndEndTagExpectation(Tokenizer.SCRIPT_DATA,
+            // When the context node is not in the HTML namespace, contrary
+            // to the spec, the first node on the stack is not set to "html"
+            // in the HTML namespace. Instead, it is set to a node that has
+            // the characteristics of the appropriate "adjusted current node".
+            // This way, there is no need to perform "adjusted current node"
+            // checks during tree construction. Instead, it's sufficient to
+            // just look at the current node. However, this also means that it
+            // is not safe to treat "html" in the HTML namespace as a sentinel
+            // that ends stack popping. Instead, stack popping loops that are
+            // meant not to pop the first element on the stack need to check
+            // for currentPos becoming zero.
+            if (contextNamespace == "http://www.w3.org/2000/svg") {
+                ElementName elementName = ElementName.SVG;
+                if ("title" == contextName || "desc" == contextName
+                        || "foreignObject" == contextName) {
+                    // These elements are all alike and we don't care about
+                    // the exact name.
+                    elementName = ElementName.FOREIGNOBJECT;
+                }
+                // This is the SVG variant of the StackNode constructor.
+                StackNode<T> node = new StackNode<T>(elementName,
+                        elementName.camelCaseName, elt
+                        // [NOCPP[
+                        , errorHandler == null ? null
+                                : new TaintableLocatorImpl(tokenizer)
+                // ]NOCPP]
+                );
+                currentPtr++;
+                stack[currentPtr] = node;
+                tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA,
                         contextName);
-            } else {
-                tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, contextName);
+                // The frameset-ok flag is set even though <frameset> never
+                // ends up being allowed as HTML frameset in the fragment case.
+                mode = FRAMESET_OK;
+            } else if (contextNamespace == "http://www.w3.org/1998/Math/MathML") {
+                ElementName elementName = ElementName.MATH;
+                if ("mi" == contextName || "mo" == contextName
+                        || "mn" == contextName || "ms" == contextName
+                        || "mtext" == contextName) {
+                    // These elements are all alike and we don't care about
+                    // the exact name.
+                    elementName = ElementName.MTEXT;
+                } else if ("annotation-xml" == contextName) {
+                    elementName = ElementName.ANNOTATION_XML;
+                    // Blink does not check the encoding attribute of the
+                    // annotation-xml element innerHTML is being set on.
+                    // Let's do the same at least until
+                    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26783
+                    // is resolved.
+                }
+                // This is the MathML variant of the StackNode constructor.
+                StackNode<T> node = new StackNode<T>(elementName, elt,
+                        elementName.name, false
+                        // [NOCPP[
+                        , errorHandler == null ? null
+                                : new TaintableLocatorImpl(tokenizer)
+                // ]NOCPP]
+                );
+                currentPtr++;
+                stack[currentPtr] = node;
+                tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA,
+                        contextName);
+                // The frameset-ok flag is set even though <frameset> never
+                // ends up being allowed as HTML frameset in the fragment case.
+                mode = FRAMESET_OK;
+            } else { // html
+                StackNode<T> node = new StackNode<T>(ElementName.HTML, elt
+                // [NOCPP[
+                        , errorHandler == null ? null
+                                : new TaintableLocatorImpl(tokenizer)
+                // ]NOCPP]
+                );
+                currentPtr++;
+                stack[currentPtr] = node;
+                if ("template" == contextName) {
+                    pushTemplateMode(IN_TEMPLATE);
+                }
+                resetTheInsertionMode();
+                formPointer = getFormPointerForContext(contextNode);
+                if ("title" == contextName || "textarea" == contextName) {
+                    tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA,
+                            contextName);
+                } else if ("style" == contextName || "xmp" == contextName
+                        || "iframe" == contextName || "noembed" == contextName
+                        || "noframes" == contextName
+                        || (scriptingEnabled && "noscript" == contextName)) {
+                    tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT,
+                            contextName);
+                } else if ("plaintext" == contextName) {
+                    tokenizer.setStateAndEndTagExpectation(Tokenizer.PLAINTEXT,
+                            contextName);
+                } else if ("script" == contextName) {
+                    tokenizer.setStateAndEndTagExpectation(
+                            Tokenizer.SCRIPT_DATA, contextName);
+                } else {
+                    tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA,
+                            contextName);
+                }
             }
             contextName = null;
             contextNode = null;
         } else {
             mode = INITIAL;
             // If we are viewing XML source, put a foreign element permanently
             // on the stack so that cdataSectionAllowed() returns true.
             // CPPONLY: if (tokenizer.isViewingXmlSource()) {
@@ -1449,17 +1521,18 @@ public abstract class TreeBuilder<T> imp
                 case IN_SELECT_IN_TABLE:
                 case IN_SELECT:
                 case IN_COLUMN_GROUP:
                 case FRAMESET_OK:
                 case IN_CAPTION:
                 case IN_CELL:
                 case IN_BODY:
                     // [NOCPP[
-                    openelementloop: for (int i = currentPtr; i >= 0; i--) {
+                    // i > 0 to stop in time in the foreign fragment case.
+                    openelementloop: for (int i = currentPtr; i > 0; i--) {
                         int group = stack[i].getGroup();
                         switch (group) {
                             case DD_OR_DT:
                             case LI:
                             case P:
                             case TBODY_OR_THEAD_OR_TFOOT:
                             case TD_OR_TH:
                             case BODY:
@@ -1616,30 +1689,27 @@ public abstract class TreeBuilder<T> imp
                         case HEAD:
                         case HR:
                         case LI:
                         case META:
                         case NOBR:
                         case P:
                         case PRE_OR_LISTING:
                         case TABLE:
-                            errHtmlStartTagInForeignContext(name);
-                            while (!isSpecialParentInForeign(stack[currentPtr])) {
-                                pop();
-                            }
-                            continue starttagloop;
                         case FONT:
-                            if (attributes.contains(AttributeName.COLOR)
-                                    || attributes.contains(AttributeName.FACE)
-                                    || attributes.contains(AttributeName.SIZE)) {
+                            // re-check FONT to deal with the special case
+                            if (!(group == FONT && !(attributes.contains(AttributeName.COLOR)
+                                    || attributes.contains(AttributeName.FACE) || attributes.contains(AttributeName.SIZE)))) {
                                 errHtmlStartTagInForeignContext(name);
-                                while (!isSpecialParentInForeign(stack[currentPtr])) {
-                                    pop();
-                                }
-                                continue starttagloop;
+                                if (!fragment) {
+                                    while (!isSpecialParentInForeign(stack[currentPtr])) {
+                                        pop();
+                                    }
+                                    continue starttagloop;
+                                } // else fall thru
                             }
                             // else fall thru
                         default:
                             if ("http://www.w3.org/2000/svg" == currNs) {
                                 attributes.adjustForSvg();
                                 if (selfClosing) {
                                     appendVoidElementToCurrentMayFosterSVG(
                                             elementName, attributes);
@@ -3303,20 +3373,28 @@ public abstract class TreeBuilder<T> imp
         flushCharacters();
         needToDropLF = false;
         int eltPos;
         int group = elementName.getGroup();
         @Local String name = elementName.name;
         endtagloop: for (;;) {
             if (isInForeign()) {
                 if (stack[currentPtr].name != name) {
-                    errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName);
+                    if (currentPtr == 0) {
+                        errStrayEndTag(name);
+                    } else {
+                        errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName);
+                    }
                 }
                 eltPos = currentPtr;
                 for (;;) {
+                    if (eltPos == 0) {
+                        assert fragment: "We can get this close to the root of the stack in foreign content only in the fragment case.";
+                        break endtagloop;
+                    }
                     if (stack[eltPos].name == name) {
                         while (currentPtr >= eltPos) {
                             pop();
                         }
                         break endtagloop;
                     }
                     if (stack[--eltPos].ns == "http://www.w3.org/1999/xhtml") {
                         break;
@@ -3644,17 +3722,19 @@ public abstract class TreeBuilder<T> imp
                             }
                         case P:
                             eltPos = findLastInButtonScope("p");
                             if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
                                 errNoElementToCloseButEndTagSeen("p");
                                 // XXX Can the 'in foreign' case happen anymore?
                                 if (isInForeign()) {
                                     errHtmlStartTagInForeignContext(name);
-                                    while (stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
+                                    // Check for currentPtr for the fragment
+                                    // case.
+                                    while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
                                         pop();
                                     }
                                 }
                                 appendVoidElementToCurrentMayFoster(
                                         elementName,
                                         HtmlAttributes.EMPTY_ATTRIBUTES);
                                 break endtagloop;
                             }
@@ -3725,18 +3805,21 @@ public abstract class TreeBuilder<T> imp
                                     pop();
                                 }
                                 clearTheListOfActiveFormattingElementsUpToTheLastMarker();
                             }
                             break endtagloop;
                         case BR:
                             errEndTagBr();
                             if (isInForeign()) {
+                                // XXX can this happen anymore?
                                 errHtmlStartTagInForeignContext(name);
-                                while (stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
+                                // Check for currentPtr for the fragment
+                                // case.
+                                while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
                                     pop();
                                 }
                             }
                             reconstructTheActiveFormattingElements();
                             appendVoidElementToCurrentMayFoster(
                                     elementName,
                                     HtmlAttributes.EMPTY_ATTRIBUTES);
                             break endtagloop;
--- a/parser/html/nsHtml5AtomList.h
+++ b/parser/html/nsHtml5AtomList.h
@@ -16,18 +16,26 @@
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
  * DEALINGS IN THE SOFTWARE.
  */
 
 HTML5_ATOM(emptystring, "")
+HTML5_ATOM(title, "title")
+HTML5_ATOM(desc, "desc")
+HTML5_ATOM(foreignObject, "foreignObject")
+HTML5_ATOM(mi, "mi")
+HTML5_ATOM(mo, "mo")
+HTML5_ATOM(mn, "mn")
+HTML5_ATOM(ms, "ms")
+HTML5_ATOM(mtext, "mtext")
+HTML5_ATOM(annotation_xml, "annotation-xml")
 HTML5_ATOM(template_, "template")
-HTML5_ATOM(title, "title")
 HTML5_ATOM(textarea, "textarea")
 HTML5_ATOM(style, "style")
 HTML5_ATOM(xmp, "xmp")
 HTML5_ATOM(iframe, "iframe")
 HTML5_ATOM(noembed, "noembed")
 HTML5_ATOM(noframes, "noframes")
 HTML5_ATOM(noscript, "noscript")
 HTML5_ATOM(plaintext, "plaintext")
@@ -723,20 +731,16 @@ HTML5_ATOM(h3, "h3")
 HTML5_ATOM(h4, "h4")
 HTML5_ATOM(h5, "h5")
 HTML5_ATOM(h6, "h6")
 HTML5_ATOM(gt, "gt")
 HTML5_ATOM(hr, "hr")
 HTML5_ATOM(li, "li")
 HTML5_ATOM(ln, "ln")
 HTML5_ATOM(lt, "lt")
-HTML5_ATOM(mi, "mi")
-HTML5_ATOM(mn, "mn")
-HTML5_ATOM(mo, "mo")
-HTML5_ATOM(ms, "ms")
 HTML5_ATOM(or_, "or")
 HTML5_ATOM(pi, "pi")
 HTML5_ATOM(rb, "rb")
 HTML5_ATOM(rp, "rp")
 HTML5_ATOM(tt, "tt")
 HTML5_ATOM(and_, "and")
 HTML5_ATOM(arg, "arg")
 HTML5_ATOM(abs, "abs")
@@ -780,17 +784,16 @@ HTML5_ATOM(wbr, "wbr")
 HTML5_ATOM(xor_, "xor")
 HTML5_ATOM(area, "area")
 HTML5_ATOM(bvar, "bvar")
 HTML5_ATOM(card, "card")
 HTML5_ATOM(csch, "csch")
 HTML5_ATOM(cosh, "cosh")
 HTML5_ATOM(coth, "coth")
 HTML5_ATOM(curl, "curl")
-HTML5_ATOM(desc, "desc")
 HTML5_ATOM(diff, "diff")
 HTML5_ATOM(defs, "defs")
 HTML5_ATOM(font, "font")
 HTML5_ATOM(grad, "grad")
 HTML5_ATOM(line, "line")
 HTML5_ATOM(meta, "meta")
 HTML5_ATOM(msub, "msub")
 HTML5_ATOM(math, "math")
@@ -831,17 +834,16 @@ HTML5_ATOM(ident, "ident")
 HTML5_ATOM(limit, "limit")
 HTML5_ATOM(mfrac, "mfrac")
 HTML5_ATOM(mpath, "mpath")
 HTML5_ATOM(meter, "meter")
 HTML5_ATOM(mover, "mover")
 HTML5_ATOM(minus, "minus")
 HTML5_ATOM(mroot, "mroot")
 HTML5_ATOM(msqrt, "msqrt")
-HTML5_ATOM(mtext, "mtext")
 HTML5_ATOM(notin, "notin")
 HTML5_ATOM(piece, "piece")
 HTML5_ATOM(param, "param")
 HTML5_ATOM(power, "power")
 HTML5_ATOM(reals, "reals")
 HTML5_ATOM(small_, "small")
 HTML5_ATOM(track, "track")
 HTML5_ATOM(tspan, "tspan")
@@ -1030,24 +1032,22 @@ HTML5_ATOM(feDropShadow, "feDropShadow")
 HTML5_ATOM(femorphology, "femorphology")
 HTML5_ATOM(feMorphology, "feMorphology")
 HTML5_ATOM(outerproduct, "outerproduct")
 HTML5_ATOM(animatemotion, "animatemotion")
 HTML5_ATOM(animateMotion, "animateMotion")
 HTML5_ATOM(font_face_src, "font-face-src")
 HTML5_ATOM(font_face_uri, "font-face-uri")
 HTML5_ATOM(foreignobject, "foreignobject")
-HTML5_ATOM(foreignObject, "foreignObject")
 HTML5_ATOM(fecolormatrix, "fecolormatrix")
 HTML5_ATOM(feColorMatrix, "feColorMatrix")
 HTML5_ATOM(missing_glyph, "missing-glyph")
 HTML5_ATOM(mmultiscripts, "mmultiscripts")
 HTML5_ATOM(scalarproduct, "scalarproduct")
 HTML5_ATOM(vectorproduct, "vectorproduct")
-HTML5_ATOM(annotation_xml, "annotation-xml")
 HTML5_ATOM(definition_src, "definition-src")
 HTML5_ATOM(font_face_name, "font-face-name")
 HTML5_ATOM(fegaussianblur, "fegaussianblur")
 HTML5_ATOM(feGaussianBlur, "feGaussianBlur")
 HTML5_ATOM(fedistantlight, "fedistantlight")
 HTML5_ATOM(feDistantLight, "feDistantLight")
 HTML5_ATOM(lineargradient, "lineargradient")
 HTML5_ATOM(linearGradient, "linearGradient")
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -92,34 +92,58 @@ nsHtml5TreeBuilder::startTokenization(ns
   framesetOk = true;
   if (fragment) {
     nsIContentHandle* elt;
     if (contextNode) {
       elt = contextNode;
     } else {
       elt = createHtmlElementSetAsRoot(tokenizer->emptyAttributes());
     }
-    nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
-    currentPtr++;
-    stack[currentPtr] = node;
-    if (nsHtml5Atoms::template_ == contextName) {
-      pushTemplateMode(NS_HTML5TREE_BUILDER_IN_TEMPLATE);
-    }
-    resetTheInsertionMode();
-    formPointer = getFormPointerForContext(contextNode);
-    if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::textarea == contextName) {
-      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RCDATA, contextName);
-    } else if (nsHtml5Atoms::style == contextName || nsHtml5Atoms::xmp == contextName || nsHtml5Atoms::iframe == contextName || nsHtml5Atoms::noembed == contextName || nsHtml5Atoms::noframes == contextName || (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) {
-      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RAWTEXT, contextName);
-    } else if (nsHtml5Atoms::plaintext == contextName) {
-      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_PLAINTEXT, contextName);
-    } else if (nsHtml5Atoms::script == contextName) {
-      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_SCRIPT_DATA, contextName);
+    if (contextNamespace == kNameSpaceID_SVG) {
+      nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_SVG;
+      if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::desc == contextName || nsHtml5Atoms::foreignObject == contextName) {
+        elementName = nsHtml5ElementName::ELT_FOREIGNOBJECT;
+      }
+      nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elementName->camelCaseName, elt);
+      currentPtr++;
+      stack[currentPtr] = node;
+      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName);
+      mode = NS_HTML5TREE_BUILDER_FRAMESET_OK;
+    } else if (contextNamespace == kNameSpaceID_MathML) {
+      nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_MATH;
+      if (nsHtml5Atoms::mi == contextName || nsHtml5Atoms::mo == contextName || nsHtml5Atoms::mn == contextName || nsHtml5Atoms::ms == contextName || nsHtml5Atoms::mtext == contextName) {
+        elementName = nsHtml5ElementName::ELT_MTEXT;
+      } else if (nsHtml5Atoms::annotation_xml == contextName) {
+        elementName = nsHtml5ElementName::ELT_ANNOTATION_XML;
+      }
+      nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, elementName->name, false);
+      currentPtr++;
+      stack[currentPtr] = node;
+      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName);
+      mode = NS_HTML5TREE_BUILDER_FRAMESET_OK;
     } else {
-      tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName);
+      nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
+      currentPtr++;
+      stack[currentPtr] = node;
+      if (nsHtml5Atoms::template_ == contextName) {
+        pushTemplateMode(NS_HTML5TREE_BUILDER_IN_TEMPLATE);
+      }
+      resetTheInsertionMode();
+      formPointer = getFormPointerForContext(contextNode);
+      if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::textarea == contextName) {
+        tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RCDATA, contextName);
+      } else if (nsHtml5Atoms::style == contextName || nsHtml5Atoms::xmp == contextName || nsHtml5Atoms::iframe == contextName || nsHtml5Atoms::noembed == contextName || nsHtml5Atoms::noframes == contextName || (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) {
+        tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RAWTEXT, contextName);
+      } else if (nsHtml5Atoms::plaintext == contextName) {
+        tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_PLAINTEXT, contextName);
+      } else if (nsHtml5Atoms::script == contextName) {
+        tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_SCRIPT_DATA, contextName);
+      } else {
+        tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_DATA, contextName);
+      }
     }
     contextName = nullptr;
     contextNode = nullptr;
   } else {
     mode = NS_HTML5TREE_BUILDER_INITIAL;
     if (tokenizer->isViewingXmlSource()) {
       nsIContentHandle* elt = createElement(kNameSpaceID_SVG, nsHtml5Atoms::svg, tokenizer->emptyAttributes());
       nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_SVG, nsHtml5Atoms::svg, elt);
@@ -601,30 +625,26 @@ nsHtml5TreeBuilder::startTag(nsHtml5Elem
           case NS_HTML5TREE_BUILDER_H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
           case NS_HTML5TREE_BUILDER_HEAD:
           case NS_HTML5TREE_BUILDER_HR:
           case NS_HTML5TREE_BUILDER_LI:
           case NS_HTML5TREE_BUILDER_META:
           case NS_HTML5TREE_BUILDER_NOBR:
           case NS_HTML5TREE_BUILDER_P:
           case NS_HTML5TREE_BUILDER_PRE_OR_LISTING:
-          case NS_HTML5TREE_BUILDER_TABLE: {
-            errHtmlStartTagInForeignContext(name);
-            while (!isSpecialParentInForeign(stack[currentPtr])) {
-              pop();
-            }
-            NS_HTML5_CONTINUE(starttagloop);
-          }
+          case NS_HTML5TREE_BUILDER_TABLE:
           case NS_HTML5TREE_BUILDER_FONT: {
-            if (attributes->contains(nsHtml5AttributeName::ATTR_COLOR) || attributes->contains(nsHtml5AttributeName::ATTR_FACE) || attributes->contains(nsHtml5AttributeName::ATTR_SIZE)) {
+            if (!(group == NS_HTML5TREE_BUILDER_FONT && !(attributes->contains(nsHtml5AttributeName::ATTR_COLOR) || attributes->contains(nsHtml5AttributeName::ATTR_FACE) || attributes->contains(nsHtml5AttributeName::ATTR_SIZE)))) {
               errHtmlStartTagInForeignContext(name);
-              while (!isSpecialParentInForeign(stack[currentPtr])) {
-                pop();
+              if (!fragment) {
+                while (!isSpecialParentInForeign(stack[currentPtr])) {
+                  pop();
+                }
+                NS_HTML5_CONTINUE(starttagloop);
               }
-              NS_HTML5_CONTINUE(starttagloop);
             }
           }
           default: {
             if (kNameSpaceID_SVG == currNs) {
               attributes->adjustForSvg();
               if (selfClosing) {
                 appendVoidElementToCurrentMayFosterSVG(elementName, attributes);
                 selfClosing = false;
@@ -2197,20 +2217,28 @@ nsHtml5TreeBuilder::endTag(nsHtml5Elemen
   flushCharacters();
   needToDropLF = false;
   int32_t eltPos;
   int32_t group = elementName->getGroup();
   nsIAtom* name = elementName->name;
   for (; ; ) {
     if (isInForeign()) {
       if (stack[currentPtr]->name != name) {
-        errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr]->popName);
+        if (!currentPtr) {
+          errStrayEndTag(name);
+        } else {
+          errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr]->popName);
+        }
       }
       eltPos = currentPtr;
       for (; ; ) {
+        if (!eltPos) {
+          MOZ_ASSERT(fragment, "We can get this close to the root of the stack in foreign content only in the fragment case.");
+          NS_HTML5_BREAK(endtagloop);
+        }
         if (stack[eltPos]->name == name) {
           while (currentPtr >= eltPos) {
             pop();
           }
           NS_HTML5_BREAK(endtagloop);
         }
         if (stack[--eltPos]->ns == kNameSpaceID_XHTML) {
           break;
@@ -2569,17 +2597,17 @@ nsHtml5TreeBuilder::endTag(nsHtml5Elemen
             }
           }
           case NS_HTML5TREE_BUILDER_P: {
             eltPos = findLastInButtonScope(nsHtml5Atoms::p);
             if (eltPos == NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK) {
               errNoElementToCloseButEndTagSeen(nsHtml5Atoms::p);
               if (isInForeign()) {
                 errHtmlStartTagInForeignContext(name);
-                while (stack[currentPtr]->ns != kNameSpaceID_XHTML) {
+                while (currentPtr >= 0 && stack[currentPtr]->ns != kNameSpaceID_XHTML) {
                   pop();
                 }
               }
               appendVoidElementToCurrentMayFoster(elementName, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
               NS_HTML5_BREAK(endtagloop);
             }
             generateImpliedEndTagsExceptFor(nsHtml5Atoms::p);
             MOZ_ASSERT(eltPos != NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK);
@@ -2652,17 +2680,17 @@ nsHtml5TreeBuilder::endTag(nsHtml5Elemen
               clearTheListOfActiveFormattingElementsUpToTheLastMarker();
             }
             NS_HTML5_BREAK(endtagloop);
           }
           case NS_HTML5TREE_BUILDER_BR: {
             errEndTagBr();
             if (isInForeign()) {
               errHtmlStartTagInForeignContext(name);
-              while (stack[currentPtr]->ns != kNameSpaceID_XHTML) {
+              while (currentPtr >= 0 && stack[currentPtr]->ns != kNameSpaceID_XHTML) {
                 pop();
               }
             }
             reconstructTheActiveFormattingElements();
             appendVoidElementToCurrentMayFoster(elementName, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
             NS_HTML5_BREAK(endtagloop);
           }
           case NS_HTML5TREE_BUILDER_TEMPLATE: {
--- a/parser/htmlparser/tests/mochitest/html5lib_tree_construction/README.md
+++ b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/README.md
@@ -16,24 +16,37 @@ Where [TEST] is the following format:
 Each test must begin with a string "\#data" followed by a newline (LF).
 All subsequent lines until a line that says "\#errors" are the test data
 and must be passed to the system being tested unchanged, except with the
 final newline (on the last line) removed.
 
 Then there must be a line that says "\#errors". It must be followed by
 one line per parse error that a conformant checker would return. It
 doesn't matter what those lines are, although they can't be
-"\#document-fragment", "\#document", or empty, the only thing that
-matters is that there be the right number of parse errors.
+"\#document-fragment", "\#document", "\#script-off", "\#script-on", or
+empty, the only thing that matters is that there be the right number
+of parse errors.
 
 Then there \*may\* be a line that says "\#document-fragment", which must
 be followed by a newline (LF), followed by a string of characters that
-indicates the context element, followed by a newline (LF). If this line
-is present the "\#data" must be parsed using the HTML fragment parsing
-algorithm with the context element as context.
+indicates the context element, followed by a newline (LF). If the string 
+of characters starts with "svg ", the context element is in the SVG
+namespace and the substring after "svg " is the local name. If the
+string of characters starts with "math ", the context element is in the
+MathML namespace and the substring after "math " is the local name.
+Otherwise, the context element is in the HTML namespace and the string
+is the local name. If this line is present the "\#data" must be parsed
+using the HTML fragment parsing algorithm with the context element as
+context.
+
+Then there \*may\* be a line that says "\#script-off" or
+"\#script-in". If a line that says "\#script-off" is present, the
+parser must set the scripting flag to disabled. If a line that says
+"\#script-on" is present, it must set it to enabled. Otherwise, the
+test should be run in both modes.
 
 Then there must be a line that says "\#document", which must be followed
 by a dump of the tree of the parsed DOM. Each node must be represented
 by a single line. Each line must start with "| ", followed by two spaces
 per parent node that the node has before the root document node.
 
 -   Element nodes must be represented by a "`<`" then the *tag name
     string* "`>`", and all the attributes must be given, sorted
@@ -48,16 +61,18 @@ per parent node that the node has before
 -   DOCTYPEs must be "`<!DOCTYPE `" then the name then if either of the
     system id or public id is non-empty a space, public id in
     double-quotes, another space an the system id in double-quotes, and
     then in any case "`>`".
 -   Processing instructions must be "`<?`", then the target, then a
     space, then the data and then "`>`". (The HTML parser cannot emit
     processing instructions, but scripts can, and the WebVTT to DOM
     rules can emit them.)
+-   Template contents are represented by the string "content" with the
+    children below it.
 
 The *tag name string* is the local name prefixed by a namespace
 designator. For the HTML namespace, the namespace designator is the
 empty string, i.e. there's no prefix. For the SVG namespace, the
 namespace designator is "svg ". For the MathML namespace, the namespace
 designator is "math ".
 
 The *attribute name string* is the local name prefixed by a namespace
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/foreign-fragment.dat
@@ -0,0 +1,550 @@
+#data
+<nobr>X
+#errors
+6: HTML start tag “nobr” in a foreign namespace context.
+7: End of file seen and there were open elements.
+6: Unclosed element “nobr”.
+#document-fragment
+svg path
+#document
+| <svg nobr>
+|   "X"
+
+#data
+<font color></font>X
+#errors
+12: HTML start tag “font” in a foreign namespace context.
+#document-fragment
+svg path
+#document
+| <svg font>
+|   color=""
+| "X"
+
+#data
+<font></font>X
+#errors
+#document-fragment
+svg path
+#document
+| <svg font>
+| "X"
+
+#data
+<g></path>X
+#errors
+10: End tag “path” did not match the name of the current open element (“g”).
+11: End of file seen and there were open elements.
+3: Unclosed element “g”.
+#document-fragment
+svg path
+#document
+| <svg g>
+|   "X"
+
+#data
+</path>X
+#errors
+5: Stray end tag “path”.
+#document-fragment
+svg path
+#document
+| "X"
+
+#data
+</foreignObject>X
+#errors
+5: Stray end tag “foreignobject”.
+#document-fragment
+svg foreignObject
+#document
+| "X"
+
+#data
+</desc>X
+#errors
+5: Stray end tag “desc”.
+#document-fragment
+svg desc
+#document
+| "X"
+
+#data
+</title>X
+#errors
+5: Stray end tag “title”.
+#document-fragment
+svg title
+#document
+| "X"
+
+#data
+</svg>X
+#errors
+5: Stray end tag “svg”.
+#document-fragment
+svg svg
+#document
+| "X"
+
+#data
+</mfenced>X
+#errors
+5: Stray end tag “mfenced”.
+#document-fragment
+math mfenced
+#document
+| "X"
+
+#data
+</malignmark>X
+#errors
+5: Stray end tag “malignmark”.
+#document-fragment
+math malignmark
+#document
+| "X"
+
+#data
+</math>X
+#errors
+5: Stray end tag “math”.
+#document-fragment
+math math
+#document
+| "X"
+
+#data
+</annotation-xml>X
+#errors
+5: Stray end tag “annotation-xml”.
+#document-fragment
+math annotation-xml
+#document
+| "X"
+
+#data
+</mtext>X
+#errors
+5: Stray end tag “mtext”.
+#document-fragment
+math mtext
+#document
+| "X"
+
+#data
+</mi>X
+#errors
+5: Stray end tag “mi”.
+#document-fragment
+math mi
+#document
+| "X"
+
+#data
+</mo>X
+#errors
+5: Stray end tag “mo”.
+#document-fragment
+math mo
+#document
+| "X"
+
+#data
+</mn>X