merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 01 Jun 2016 15:07:48 +0200
changeset 340891 111970c738234569c8c180319155327316335deb
parent 340689 78e2125dfb6b02cdbab73a30c54bb4dc19aa310a (current diff)
parent 340890 b6fe7fa88db0a4103d175bf5b02cd47c2c6722e6 (diff)
child 340897 22047a4eea784c15026c77911c0bd6ea1b70fa68
child 340961 a0ddb616f2d9448c624ef1878434d3467e671967
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.0a1
first release with
nightly linux32
111970c73823 / 49.0a1 / 20160601061753 / files
nightly linux64
111970c73823 / 49.0a1 / 20160601061753 / files
nightly mac
111970c73823 / 49.0a1 / 20160601061753 / files
nightly win32
111970c73823 / 49.0a1 / 20160601061753 / files
nightly win64
111970c73823 / 49.0a1 / 20160601061753 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
js/src/devtools/rootAnalysis/test/source.cpp
js/src/devtools/rootAnalysis/test/test.py
media/libnestegg/bug1271866.patch
--- a/.hgignore
+++ b/.hgignore
@@ -38,16 +38,17 @@
 ^js/src/.*-obj/
 
 # SpiderMonkey configury
 ^js/src/configure$
 ^js/src/old-configure$
 ^js/src/autom4te.cache$
 # SpiderMonkey test result logs
 ^js/src/tests/results-.*\.(html|txt)$
+^js/src/devtools/rootAnalysis/t/out
 
 # Java HTML5 parser classes
 ^parser/html/java/(html|java)parser/
 
 # SVN directories
 \.svn/
 
 # Ignore the files and directory that Eclipse IDE creates
--- a/accessible/html/HTMLListAccessible.cpp
+++ b/accessible/html/HTMLListAccessible.cpp
@@ -87,16 +87,27 @@ HTMLLIAccessible::Bounds() const
 
   nsIntRect bulletRect = mBullet->Bounds();
 
   rect.width += rect.x - bulletRect.x;
   rect.x = bulletRect.x; // Move x coordinate of list item over to cover bullet as well
   return rect;
 }
 
+bool
+HTMLLIAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+  // Adjust index if there's a bullet.
+  if (mBullet && aIndex == 0 && aChild != mBullet) {
+    return HyperTextAccessible::InsertChildAt(aIndex + 1, aChild);
+  }
+
+  return HyperTextAccessible::InsertChildAt(aIndex, aChild);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLLIAccessible: public
 
 void
 HTMLLIAccessible::UpdateBullet(bool aHasBullet)
 {
   if (aHasBullet == !!mBullet) {
     NS_NOTREACHED("Bullet and accessible are in sync already!");
--- a/accessible/html/HTMLListAccessible.h
+++ b/accessible/html/HTMLListAccessible.h
@@ -48,16 +48,18 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // Accessible
   virtual void Shutdown() override;
   virtual nsIntRect Bounds() const override;
   virtual a11y::role NativeRole() override;
   virtual uint64_t NativeState() override;
 
+  virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+
   // HTMLLIAccessible
   HTMLListBulletAccessible* Bullet() const { return mBullet; }
   void UpdateBullet(bool aHasBullet);
 
 protected:
   virtual ~HTMLLIAccessible() { }
 
 private:
--- a/accessible/html/HTMLTableAccessible.h
+++ b/accessible/html/HTMLTableAccessible.h
@@ -152,17 +152,17 @@ public:
   // Accessible
   virtual TableAccessible* AsTable() override { return this; }
   virtual void Description(nsString& aDescription) override;
   virtual a11y::role NativeRole() override;
   virtual uint64_t NativeState() override;
   virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
   virtual Relation RelationByType(RelationType aRelationType) override;
 
-  bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+  virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
 
 protected:
   virtual ~HTMLTableAccessible() {}
 
   // Accessible
   virtual ENameValueFlag NativeName(nsString& aName) override;
 
   // HTMLTableAccessible
--- a/accessible/tests/mochitest/treeupdate/test_list.html
+++ b/accessible/tests/mochitest/treeupdate/test_list.html
@@ -77,35 +77,60 @@
       this.process = function showProcessor_process()
       {
         this.liNode.style.display = "list-item";
       }
 
       this.onProcessed = function showProcessor_onProcessed()
       {
         testLiAccessibleTree();
+        gSequence.processNext();
+      }
+    };
+
+    function textReplaceProcessor()
+    {
+      this.liNode = getNode("li");
+
+      this.process = function textReplaceProcessor_process()
+      {
+        this.liNode.textContent = "hey";
+      }
+
+      this.onProcessed = function textReplaceProcessor_onProcessed()
+      {
+        var tree = {
+          LISTITEM: [
+            { STATICTEXT: [] },
+            { TEXT_LEAF: [] }
+          ]
+        };
+        testAccessibleTree(this.liNode, tree);
         SimpleTest.finish();
       }
     };
 
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
+    //gA11yEventDumpToConsole = true;
+
     var gSequence = null;
-
     function doTest()
     {
       testLiAccessibleTree();
 
       gSequence = new sequence();
 
       gSequence.append(new hideProcessor(), EVENT_HIDE, getAccessible("li"),
                        "hide HTML li");
       gSequence.append(new showProcessor(), EVENT_SHOW, getNode("li"),
                        "show HTML li");
+      gSequence.append(new textReplaceProcessor(), EVENT_REORDER, getNode("li"),
+                       "change text of HTML li");
 
       gSequence.processNext(); // SimpleTest.finish() will be called in the end
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1082,13 +1082,15 @@ pref("dom.presentation.device.name", "Fi
 
 // Enable notification of performance timing
 pref("dom.performance.enable_notify_performance_timing", true);
 
 // Multi-screen
 pref("b2g.multiscreen.chrome_remote_url", "chrome://b2g/content/shell_remote.html");
 pref("b2g.multiscreen.system_remote_url", "index_remote.html");
 
+// Audio competing between tabs
+pref("dom.audiochannel.audioCompeting", false);
 
 // Because we can't have nice things.
 #ifdef MOZ_GRAPHENE
 #include ../graphene/graphene.js
 #endif
--- a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
@@ -1,47 +1,47 @@
 [
 {
-"version": "gcc 4.9.3",
-"size": 102421980,
-"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
+"size" : 102421980,
+"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"version" : "gcc 4.9.3",
+"unpack" : true,
+"filename" : "gcc.tar.xz",
+"algorithm" : "sha512"
 },
 {
-"hg_id" : "cd93f15a30ce",
+"unpack" : true,
+"algorithm" : "sha512",
+"filename" : "sixgill.tar.xz",
+"hg_id" : "8cb9c3fb039a+ tip",
+"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+"size" : 2631908
+},
+{
 "algorithm" : "sha512",
-"digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
-"filename" : "sixgill.tar.xz",
-"size" : 2626640,
+"filename" : "gtk3.tar.xz",
+"setup" : "setup.sh",
+"unpack" : true,
+"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"size" : 12072532
+},
+{
+"size" : 89319524,
+"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
+"algorithm" : "sha512",
+"filename" : "rustc.tar.xz",
 "unpack" : true
 },
 {
-"size": 12072532,
-"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"setup": "setup.sh",
-"unpack": true
-},
-{
-"size": 89319524,
-"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
-"algorithm": "sha512",
-"filename": "rustc.tar.xz",
-"unpack": true
+"algorithm" : "sha512",
+"filename" : "sccache.tar.bz2",
+"unpack" : true,
+"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"size" : 167175
 },
 {
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-},
-{
-"size": 31078810,
-"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"algorithm": "sha512",
-"filename": "moz-tt.tar.bz2",
-"unpack": true
+"filename" : "moz-tt.tar.bz2",
+"algorithm" : "sha512",
+"unpack" : true,
+"digest" : "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"size" : 31078810
 }
 ]
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4064,21 +4064,21 @@ function updateUserContextUIVisibility()
   if (PrivateBrowsingUtils.isWindowPrivate(window)) {
     menu.setAttribute("disabled", "true");
   }
 }
 
 /**
  * Updates the User Context UI indicators if the browser is in a non-default context
  */
-function updateUserContextUIIndicator(browser)
+function updateUserContextUIIndicator()
 {
   let hbox = document.getElementById("userContext-icons");
 
-  let userContextId = browser.getAttribute("usercontextid");
+  let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
   if (!userContextId) {
     hbox.hidden = true;
     return;
   }
 
   let identity = ContextualIdentityService.getIdentityFromId(userContextId);
   if (!identity) {
     hbox.hidden = true;
@@ -6415,18 +6415,26 @@ function checkEmptyPageOrigin(browser = 
   // If another page opened this page with e.g. window.open, this page might
   // be controlled by its opener - return false.
   if (browser.hasContentOpener) {
     return false;
   }
   let contentPrincipal = browser.contentPrincipal;
   // Not all principals have URIs...
   if (contentPrincipal.URI) {
-    // A manually entered about:blank URI is slightly magical:
-    if (uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) {
+    // There are two specialcases involving about:blank. One is where
+    // the user has manually loaded it and it got created with a null
+    // principal. The other involves the case where we load
+    // some other empty page in a browser and the current page is the
+    // initial about:blank page (which has that as its principal, not
+    // just URI in which case it could be web-based). Especially in
+    // e10s, we need to tackle that case specifically to avoid race
+    // conditions when updating the URL bar.
+    if ((uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
+        contentPrincipal.URI.spec == "about:blank") {
       return true;
     }
     return contentPrincipal.URI.equals(uri);
   }
   // ... so for those that don't have them, enforce that the page has the
   // system principal (this matches e.g. on about:newtab).
   let ssm = Services.scriptSecurityManager;
   return ssm.isSystemPrincipal(contentPrincipal);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -632,25 +632,32 @@
               if (aStateFlags & nsIWebProgressListener.STATE_START &&
                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
                 if (aWebProgress.isTopLevel) {
                   // Need to use originalLocation rather than location because things
                   // like about:home and about:privatebrowsing arrive with nsIRequest
                   // pointing to their resolved jar: or file: URIs.
                   if (!(originalLocation && gInitialPages.includes(originalLocation.spec) &&
                         originalLocation != "about:blank" &&
+                        this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec &&
                         this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
-                    // This will trigger clearing the location bar. Don't do it if
-                    // we loaded off a blank browser and this is an initial page load
-                    // (e.g. about:privatebrowsing, about:newtab, etc.) so we avoid
-                    // clearing the location bar in case the user is typing in it.
-                    // loading about:blank shouldn't trigger this, either, because its
-                    // loads are "special".
+                    // Indicating that we started a load will allow the location
+                    // bar to be cleared when the load finishes.
+                    // In order to not overwrite user-typed content, we avoid it
+                    // (see if condition above) in a very specific case:
+                    // If the load is of an 'initial' page (e.g. about:privatebrowsing,
+                    // about:newtab, etc.), was not explicitly typed in the location
+                    // bar by the user, is not about:blank (because about:blank can be
+                    // loaded by websites under their principal), and the current
+                    // page in the browser is about:blank (indicating it is a newly
+                    // created or re-created browser, e.g. because it just switched
+                    // remoteness or is a new tab/window).
                     this.mBrowser.urlbarChangeTracker.startedLoad();
                   }
+                  delete this.mBrowser.initialPageLoadedFromURLBar;
                   // If the browser is loading it must not be crashed anymore
                   this.mTab.removeAttribute("crashed");
                 }
 
                 if (this._shouldShowProgress(aRequest)) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
 
@@ -1212,17 +1219,17 @@
                 // some element has been focused and respect that.
                 document.activeElement.blur();
               }
 
               if (!gMultiProcessBrowser)
                 this._adjustFocusAfterTabSwitch(this.mCurrentTab);
             }
 
-            updateUserContextUIIndicator(gBrowser.selectedBrowser);
+            updateUserContextUIIndicator();
 
             this.tabContainer._setPositionalAttributes();
 
             if (!gMultiProcessBrowser) {
               let event = new CustomEvent("TabSwitchDone", {
                 bubbles: true,
                 cancelable: true
               });
@@ -4323,17 +4330,22 @@
               break;
             }
             case "Browser:WindowCreated": {
               let tab = this.getTabForBrowser(browser);
               if (tab && data.userContextId) {
                 tab.setUserContextId(data.userContextId);
               }
 
-              updateUserContextUIIndicator(browser);
+	      // We don't want to update the container icon and identifier if
+	      // this is not the selected browser.
+              if (browser == gBrowser.selectedBrowser) {
+                updateUserContextUIIndicator();
+              }
+
               break;
             }
             case "Findbar:Keypress": {
               let tab = this.getTabForBrowser(browser);
               // If the find bar for this tab is not yet alive, only initialize
               // it if there's a possibility FindAsYouType will be used.
               // There's no point in doing it for most random keypresses.
               if (!this.isFindBarInitialized(tab) &&
--- a/browser/base/content/test/general/browser_bug413915.js
+++ b/browser/base/content/test/general/browser_bug413915.js
@@ -1,15 +1,15 @@
 XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
   "resource:///modules/Feeds.jsm");
 
 function test() {
   var exampleUri = makeURI("http://example.com/");
   var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
-  var principal = secman.getSimpleCodebasePrincipal(exampleUri);
+  var principal = secman.createCodebasePrincipal(exampleUri, {});
 
   function testIsFeed(aTitle, aHref, aType, aKnown) {
     var link = { title: aTitle, href: aHref, type: aType };
     return Feeds.isValidFeed(link, principal, aKnown);
   }
 
   var href = "http://example.com/feed/";
   var atomType = "application/atom+xml";
--- a/browser/base/content/test/general/browser_bug882977.js
+++ b/browser/base/content/test/general/browser_bug882977.js
@@ -1,39 +1,36 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
+"use strict";
 
-function test() {
-  waitForExplicitFinish();
-
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("browser.startup.homepage");
-    Services.prefs.clearUserPref("browser.startup.page");
-    win.close();
+/**
+ * Tests that the identity-box shows the chromeUI styling
+ * when viewing about:home in a new window.
+ */
+add_task(function*(){
+  let homepage = "about:home";
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["browser.startup.homepage", homepage],
+      ["browser.startup.page", 1],
+    ]
   });
 
-  let homepage = "about:home";
-  Services.prefs.setCharPref("browser.startup.homepage", homepage);
-  Services.prefs.setIntPref("browser.startup.page", 1);
   let win = OpenBrowserWindow();
-  whenDelayedStartupFinished(win, function() {
-    let browser = win.gBrowser.selectedBrowser;
-    if (browser.contentDocument.readyState == "complete" &&
-        browser.currentURI.spec == homepage) {
-      checkIdentityMode(win);
-      return;
-    }
+  yield BrowserTestUtils.waitForEvent(win, "load");
 
-    browser.addEventListener("load", function onLoad() {
-      if (browser.currentURI.spec != homepage)
-        return;
-      browser.removeEventListener("load", onLoad, true);
-      checkIdentityMode(win);
-    }, true);
-  });
-}
+  let browser = win.gBrowser.selectedBrowser;
+  // If we've finished loading about:home already, we can check
+  // right away - otherwise, we need to wait.
+  if (browser.contentDocument.readyState == "complete" &&
+      browser.currentURI.spec == homepage) {
+    checkIdentityMode(win);
+  } else {
+    yield BrowserTestUtils.browserLoaded(browser, false, homepage);
+    checkIdentityMode(win);
+  }
+
+  yield BrowserTestUtils.closeWindow(win);
+});
 
 function checkIdentityMode(win) {
   let identityMode = win.document.getElementById("identity-box").className;
   is(identityMode, "chromeUI", "Identity state should be chromeUI for about:home in a new window");
-  finish();
 }
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -38,16 +38,17 @@ skip-if = os == "linux" # Linux: Intermi
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
 [browser_tabMatchesInAwesomebar.js]
 support-files =
   moz.png
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = os == 'linux' # Bug 1104755
+[browser_urlbarAboutHomeLoading.js]
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
 subsuite = clipboard
 support-files =
   authenticate.sjs
 [browser_urlbarDecode.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+/**
+ * Test what happens if loading a URL that should clear the
+ * location bar after a parent process URL.
+ */
+add_task(function* clearURLBarAfterParentProcessURL() {
+  let tab = yield new Promise(resolve => {
+    gBrowser.selectedTab = gBrowser.addTab("about:preferences");
+    let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+    newTabBrowser.addEventListener("Initialized", function onInit() {
+      newTabBrowser.removeEventListener("Initialized", onInit, true);
+      resolve(gBrowser.selectedTab);
+    }, true);
+  });
+  document.getElementById("home-button").click();
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  is(gURLBar.value, "", "URL bar should be empty");
+  is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Same as above, but open the tab without passing the URL immediately
+ * which changes behaviour in tabbrowser.xml.
+ */
+add_task(function* clearURLBarAfterParentProcessURLInExistingTab() {
+  let tab = yield new Promise(resolve => {
+    gBrowser.selectedTab = gBrowser.addTab();
+    let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+    newTabBrowser.addEventListener("Initialized", function onInit() {
+      newTabBrowser.removeEventListener("Initialized", onInit, true);
+      resolve(gBrowser.selectedTab);
+    }, true);
+    newTabBrowser.loadURI("about:preferences");
+  });
+  document.getElementById("home-button").click();
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  is(gURLBar.value, "", "URL bar should be empty");
+  is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Load about:home directly from an about:newtab page. Because it is an
+ * 'initial' page, we need to treat this specially if the user actually
+ * loads a page like this from the URL bar.
+ */
+add_task(function* clearURLBarAfterManuallyLoadingAboutHome() {
+  let promiseTabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(gBrowser, () => {});
+  // This opens about:newtab:
+  BrowserOpenTab();
+  let tab = yield promiseTabOpenedAndSwitchedTo;
+  is(gURLBar.value, "", "URL bar should be empty");
+  is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+
+  gURLBar.value = "about:home";
+  gURLBar.select();
+  let aboutHomeLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:home");
+  EventUtils.sendKey("return");
+  yield aboutHomeLoaded;
+
+  is(gURLBar.value, "", "URL bar should be empty");
+  is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Ensure we don't show 'about:home' in the URL bar temporarily in new tabs
+ * while we're switching remoteness (when the URL we're loading and the
+ * default content principal are different).
+ */
+add_task(function* dontTemporarilyShowAboutHome() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.startup.page", 1]]});
+  let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  let win = OpenBrowserWindow();
+  yield windowOpenedPromise;
+  let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {});
+  win.BrowserOpenTab();
+  yield promiseTabSwitch;
+  yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+  yield BrowserTestUtils.closeWindow(win);
+  ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+  windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+  win = SessionStore.undoCloseWindow(0);
+  yield windowOpenedPromise;
+  let wpl = {
+    onLocationChange(wpl, request, location, flags) {
+      is(win.gURLBar.value, "", "URL bar value should stay empty.");
+    },
+  };
+  win.gBrowser.addProgressListener(wpl);
+  let otherTab = win.gBrowser.selectedTab.previousSibling;
+  let tabLoaded = BrowserTestUtils.browserLoaded(otherTab.linkedBrowser, false, "about:home");
+  yield BrowserTestUtils.switchTab(win.gBrowser, otherTab);
+  yield tabLoaded;
+  win.gBrowser.removeProgressListener(wpl);
+  is(win.gURLBar.value, "", "URL bar value should be empty.");
+
+  yield BrowserTestUtils.closeWindow(win);
+});
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -377,16 +377,19 @@ file, You can obtain one at http://mozil
               }
             });
           }
 
           function continueOperation()
           {
             this.value = url;
             gBrowser.userTypedValue = url;
+            if (gInitialPages.includes(url)) {
+              gBrowser.selectedBrowser.initialPageLoadedFromURLBar = url;
+            }
             try {
               addToUrlbarHistory(url);
             } catch (ex) {
               // Things may go wrong when adding url to session history,
               // but don't let that interfere with the loading of the url.
               Cu.reportError(ex);
             }
 
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -919,17 +919,17 @@ FeedWriter.prototype = {
       return;
 
     this._window = window;
     this._document = window.document;
     this._handlersList = this._document.getElementById("handlersMenuList");
 
     let secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
                  getService(Ci.nsIScriptSecurityManager);
-    this._feedPrincipal = secman.getSimpleCodebasePrincipal(this._feedURI);
+    this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {});
 
     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
 
     // Set up the subscription UI
     this._initSubscriptionUI();
     let prefs = Services.prefs;
     prefs.addObserver(PREF_SELECTED_ACTION, this, false);
     prefs.addObserver(PREF_SELECTED_READER, this, false);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -779,17 +779,20 @@ var SessionStoreInternal = {
         let activePageData = tabData.entries[tabData.index - 1] || null;
         let uri = activePageData ? activePageData.url || null : null;
         // NB: we won't set initial URIs (about:home, about:newtab, etc.) here
         // because their load will not normally trigger a location bar clearing
         // when they finish loading (to avoid race conditions where we then
         // clear user input instead), so we shouldn't set them here either.
         // They also don't fall under the issues in bug 439675 where user input
         // needs to be preserved if the load doesn't succeed.
-        if (!browser.userTypedValue && uri && !win.gInitialPages.includes(uri)) {
+        // We also don't do this for remoteness updates, where it should not
+        // be necessary.
+        if (!browser.userTypedValue && uri && !data.isRemotenessUpdate &&
+            !win.gInitialPages.includes(uri)) {
           browser.userTypedValue = uri;
         }
 
         // If the page has a title, set it.
         if (activePageData) {
           if (activePageData.title) {
             tab.label = activePageData.title;
             tab.crop = "end";
@@ -3330,17 +3333,18 @@ var SessionStoreInternal = {
       // Start a new epoch to discard all frame script messages relating to a
       // previous epoch. All async messages that are still on their way to chrome
       // will be ignored and don't override any tab data set when restoring.
       let epoch = this.startNextEpoch(browser);
 
       browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory", {
         tabData: tabData,
         epoch: epoch,
-        loadArguments: aLoadArguments
+        loadArguments: aLoadArguments,
+        isRemotenessUpdate,
       });
 
     }
 
     // If the restored browser wants to show view source content, start up a
     // view source browser that will load the required frame script.
     if (uri && ViewSourceBrowser.isViewSource(uri)) {
       new ViewSourceBrowser(browser);
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -149,17 +149,17 @@ var MessageListener = {
         this.flush(data);
         break;
       default:
         debug("received unknown message '" + name + "'");
         break;
     }
   },
 
-  restoreHistory({epoch, tabData, loadArguments}) {
+  restoreHistory({epoch, tabData, loadArguments, isRemotenessUpdate}) {
     gContentRestore.restoreHistory(tabData, loadArguments, {
       // Note: The callbacks passed here will only be used when a load starts
       // that was not initiated by sessionstore itself. This can happen when
       // some code calls browser.loadURI() or browser.reload() on a pending
       // browser/tab.
 
       onLoadStarted() {
         // Notify the parent that the tab is no longer pending.
@@ -174,17 +174,17 @@ var MessageListener = {
     });
 
     // When restoreHistory finishes, we send a synchronous message to
     // SessionStore.jsm so that it can run SSTabRestoring. Users of
     // SSTabRestoring seem to get confused if chrome and content are out of
     // sync about the state of the restore (particularly regarding
     // docShell.currentURI). Using a synchronous message is the easiest way
     // to temporarily synchronize them.
-    sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch});
+    sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch, isRemotenessUpdate});
   },
 
   restoreTabContent({loadArguments, isRemotenessUpdate}) {
     let epoch = gCurrentEpoch;
 
     // We need to pass the value of didStartLoad back to SessionStore.jsm.
     let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
       // Tell SessionStore.jsm that it may want to restore some more tabs,
--- a/browser/components/sessionstore/test/browser_480893.js
+++ b/browser/components/sessionstore/test/browser_480893.js
@@ -1,51 +1,47 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
 
-function test() {
-  /** Test for Bug 480893 **/
+/**
+ * Tests that we get sent to the right page when the user clicks
+ * the "Close" button in about:sessionrestore
+ */
+add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["browser.startup.page", 0],
+    ]
+  });
 
-  waitForExplicitFinish();
-
-  // Test that starting a new session loads a blank page if Firefox is
-  // configured to display a blank page at startup (browser.startup.page = 0)
-  gPrefService.setIntPref("browser.startup.page", 0);
   let tab = gBrowser.addTab("about:sessionrestore");
   gBrowser.selectedTab = tab;
   let browser = tab.linkedBrowser;
-  promiseBrowserLoaded(browser).then(() => {
-    let doc = browser.contentDocument;
+  yield BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
+
+  let doc = browser.contentDocument;
 
-    // click on the "Start New Session" button after about:sessionrestore is loaded
-    doc.getElementById("errorCancel").click();
-    promiseBrowserLoaded(browser).then(() => {
-      let doc = browser.contentDocument;
+  // Click on the "Close" button after about:sessionrestore is loaded.
+  doc.getElementById("errorCancel").click();
 
-      is(doc.URL, "about:blank", "loaded page is about:blank");
+  yield BrowserTestUtils.browserLoaded(browser, false, "about:blank");
 
-      // Test that starting a new session loads the homepage (set to http://mochi.test:8888)
-      // if Firefox is configured to display a homepage at startup (browser.startup.page = 1)
-      let homepage = "http://mochi.test:8888/";
-      gPrefService.setCharPref("browser.startup.homepage", homepage);
-      gPrefService.setIntPref("browser.startup.page", 1);
-      gBrowser.loadURI("about:sessionrestore");
-      promiseBrowserLoaded(browser).then(() => {
-        let doc = browser.contentDocument;
+  // Test that starting a new session loads the homepage (set to http://mochi.test:8888)
+  // if Firefox is configured to display a homepage at startup (browser.startup.page = 1)
+  let homepage = "http://mochi.test:8888/";
+  yield SpecialPowers.pushPrefEnv({
+    "set": [
+      ["browser.startup.homepage", homepage],
+      ["browser.startup.page", 1],
+    ]
+  });
 
-        // click on the "Start New Session" button after about:sessionrestore is loaded
-        doc.getElementById("errorCancel").click();
-        promiseBrowserLoaded(browser).then(() => {
-          let doc = browser.contentDocument;
-
-          is(doc.URL, homepage, "loaded page is the homepage");
+  browser.loadURI("about:sessionrestore");
+  yield BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
+  doc = browser.contentDocument;
 
-          // close tab, restore default values and finish the test
-          gBrowser.removeTab(tab);
-          gPrefService.clearUserPref("browser.startup.page");
-          gPrefService.clearUserPref("browser.startup.homepage");
-          finish();
-        });
-      });
-    });
-  });
-}
+  // Click on the "Close" button after about:sessionrestore is loaded.
+  doc.getElementById("errorCancel").click();
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  is(browser.currentURI.spec, homepage, "loaded page is the homepage");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/config/tooltool-manifests/linux64/hazard.manifest
+++ b/browser/config/tooltool-manifests/linux64/hazard.manifest
@@ -1,40 +1,40 @@
 [
 {
-"version": "gcc 4.9.3",
-"size": 102421980,
-"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"hg_id" : "cd93f15a30ce",
+"size" : 102421980,
+"version" : "gcc 4.9.3",
+"filename" : "gcc.tar.xz",
 "algorithm" : "sha512",
-"digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
-"filename" : "sixgill.tar.xz",
-"size" : 2626640,
+"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
 "unpack" : true
 },
 {
-"size": 12072532,
-"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"setup": "setup.sh",
-"unpack": true
+"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+"unpack" : true,
+"algorithm" : "sha512",
+"filename" : "sixgill.tar.xz",
+"size" : 2631908,
+"hg_id" : "8cb9c3fb039a+ tip"
 },
 {
-"size": 89319524,
-"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
-"algorithm": "sha512",
-"filename": "rustc.tar.xz",
-"unpack": true
+"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"unpack" : true,
+"setup" : "setup.sh",
+"algorithm" : "sha512",
+"filename" : "gtk3.tar.xz",
+"size" : 12072532
 },
 {
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
+"unpack" : true,
+"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
+"filename" : "rustc.tar.xz",
+"algorithm" : "sha512",
+"size" : 89319524
+},
+{
+"filename" : "sccache.tar.bz2",
+"algorithm" : "sha512",
+"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"unpack" : true,
+"size" : 167175
 }
 ]
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -1,19 +1,19 @@
 [
 {
 "size": 266240,
 "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
 "algorithm": "sha512",
 "filename": "mozmake.exe"
 },
 {
-"version": "rustc 1.8.0 (db2939409 2016-04-11)",
-"size": 78733276,
-"digest": "96ab09cd667ed854efeeca41881a92c9fdc5f3cdeff9b02b12c514183c0b54a21dee8574367abe532429e04660681a1f6c37f6d22cc877d63fbcca7b986d3495",
+"version": "rustc 1.9.0 (e4e8b6668 2016-05-18)",
+"size": 82463178,
+"digest": "a3c54c6792e75d53ec79caf958db25b651fcf968a37b00949fb327c54a54cad6305a4af302f267082d86d70fcf837ed0f273f85b97706c20b957ff3690889b40",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -1,19 +1,19 @@
 [
 {
 "size": 266240,
 "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
 "algorithm": "sha512",
 "filename": "mozmake.exe"
 },
 {
-"version": "rustc 1.8.0 (db2939409 2016-04-11)",
-"size": 85285866,
-"digest": "f3862036781df9f699a18a5449d51a4f8880e7d890500b314d1f16f394da77c2c496fa537a691d9d99f3248ec2067beaf20b4361babe0dd449d4f7b4f539acac",
+"version": "rustc 1.9.0 (e4e8b6668 2016-05-18)",
+"size": 88486080,
+"digest": "a4fb99cd637b236a9c30e111757ca560bc8df1b143324c1d9ab58c32470b9b9a0598e3e0d220278ee157959dcd88421496388e2ed856e6261d9c81f18e6310e9",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
--- a/browser/extensions/loop/chrome/test/mochitest/browser.ini
+++ b/browser/extensions/loop/chrome/test/mochitest/browser.ini
@@ -13,11 +13,12 @@ support-files =
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_chat.js]
 [browser_mozLoop_context.js]
 [browser_mozLoop_infobar.js]
 [browser_mozLoop_socialShare.js]
 [browser_panel_privateBrowsing.js]
 [browser_mozLoop_sharingListeners.js]
 [browser_mozLoop_telemetry.js]
+skip-if = os == win && !debug # Bug 1267562 zombiecheck | child process 1228 still alive after shutdown (on win7-vm specifically)
 [browser_sharingTitleListeners.js]
 [browser_throttler.js]
 [browser_toolbarbutton.js]
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -48,16 +48,18 @@ PluginContent.prototype = {
     global.addEventListener("unload",                this);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
+
+    Services.obs.addObserver(this, "Plugin::HiddenPluginTouched", false);
   },
 
   uninit: function() {
     let global = this.global;
 
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
@@ -70,16 +72,18 @@ PluginContent.prototype = {
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
     delete this.content;
+
+    Services.obs.removeObserver(this, "Plugin::HiddenPluginTouched");
   },
 
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "BrowserPlugins:ActivatePlugins":
         this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
         break;
       case "BrowserPlugins:NotificationShown":
@@ -111,16 +115,25 @@ PluginContent.prototype = {
       case "BrowserPlugins:Test:ClearCrashData":
         // This message should ONLY ever be sent by automated tests.
         if (Services.prefs.getBoolPref("plugins.testmode")) {
           this.pluginCrashData.clear();
         }
     }
   },
 
+  observe: function observe(aSubject, aTopic, aData) {
+    let pluginTag = aSubject;
+    if (aTopic == "Plugin::HiddenPluginTouched") {
+      this._showClickToPlayNotification(pluginTag, false);
+    } else {
+      Cu.reportError("unknown topic observed: " + aTopic);
+    }
+  },
+
   onPageShow: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
 
     // The PluginClickToPlay events are not fired when navigating using the
     // BF cache. |persisted| is true when the page is loaded from the
@@ -189,16 +202,55 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
+  _getPluginInfoForTag: function (pluginTag, tagMimetype, fallbackType) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+    let permissionString = null;
+    let blocklistState = null;
+
+    if (pluginTag) {
+      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+      blocklistState = pluginTag.blocklistState;
+
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
+      // Make state-softblocked == state-notblocked for our purposes,
+      // they have the same UI. STATE_OUTDATED should not exist for plugin
+      // items, but let's alias it anyway, just in case.
+      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+    }
+
+    return { mimetype: tagMimetype,
+             pluginName: pluginName,
+             pluginTag: pluginTag,
+             permissionString: permissionString,
+             fallbackType: fallbackType,
+             blocklistState: blocklistState,
+           };
+  },
+
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -202,22 +202,16 @@ AC_DEFUN([MOZ_ANDROID_AAR],[
 
   define([root], $MOZ_BUILD_ROOT/dist/exploded-aar/$1-$2/)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _LIB), concat(root, $1-$2-classes.jar), REQUIRED)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _RES), concat(root, res), REQUIRED)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _INTERNAL_LIB), concat(root, libs/$1-$2-internal_impl-$2.jar), $5)
   MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _ASSETS), concat(root, assets), $6)
 ])
 
-ANDROID_SUPPORT_LIBRARY_VERSION="23.0.1"
-AC_SUBST(ANDROID_SUPPORT_LIBRARY_VERSION)
-
-ANDROID_GOOGLE_PLAY_SERVICES_VERSION="8.4.0"
-AC_SUBST(ANDROID_GOOGLE_PLAY_SERVICES_VERSION)
-
 AC_DEFUN([MOZ_ANDROID_GOOGLE_PLAY_SERVICES],
 [
 
 if test -n "$MOZ_NATIVE_DEVICES" ; then
     AC_SUBST(MOZ_NATIVE_DEVICES)
 
     MOZ_ANDROID_AAR(play-services-base, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms)
--- a/build/util/count_ctors.py
+++ b/build/util/count_ctors.py
@@ -50,14 +50,15 @@ def count_ctors(filename):
 if __name__ == '__main__':
     for f in sys.argv[1:]:
         perfherder_data = {
             "framework": {"name": "build_metrics"},
             "suites": [{
                 "name": "compiler_metrics",
                 "subtests": [{
                     "name": "num_constructors",
-                    "value": count_ctors(f)
+                    "value": count_ctors(f),
+                    "alertThreshold": 0.25
                 }]}
             ]
         }
         print "PERFHERDER_DATA: %s" % json.dumps(perfherder_data)
 
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -129,23 +129,16 @@ interface nsIScriptSecurityManager : nsI
     ///////////////// Principals ///////////////////////
 
     /**
      * Return the all-powerful system principal.
      */
     nsIPrincipal getSystemPrincipal();
 
     /**
-     * Return a principal that has the same origin as aURI.
-     * This principals should not be used for any data/permission check, it will
-     * have appId = UNKNOWN_APP_ID.
-     */
-    nsIPrincipal getSimpleCodebasePrincipal(in nsIURI aURI);
-
-    /**
      * Returns a principal that has the given information.
      * @param appId is the app id of the principal. It can't be UNKNOWN_APP_ID.
      * @param inMozBrowser is true if the principal has to be considered as
      * inside a mozbrowser frame.
      *
      * @deprecated use createCodebasePrincipal instead.
      */
     [deprecated] nsIPrincipal getAppCodebasePrincipal(in nsIURI uri,
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -1104,26 +1104,16 @@ NS_IMETHODIMP
 nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result)
 {
     NS_ADDREF(*result = mSystemPrincipal);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsScriptSecurityManager::GetSimpleCodebasePrincipal(nsIURI* aURI,
-                                                    nsIPrincipal** aPrincipal)
-{
-  PrincipalOriginAttributes attrs(UNKNOWN_APP_ID, false);
-  nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
-  prin.forget(aPrincipal);
-  return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
 nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI,
                                                    nsIPrincipal** aPrincipal)
 {
   PrincipalOriginAttributes attrs(NO_APP_ID, false);
   nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
   prin.forget(aPrincipal);
   return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
 }
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/dom/TabParent.h"
 
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsThreadUtils.h"
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
+#include "nsGlobalWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsJSUtils.h"
 #include "SpeakerManagerService.h"
 #endif
@@ -36,16 +37,17 @@
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
 namespace {
 
 // If true, any new AudioChannelAgent will be muted when created.
 bool sAudioChannelMutedByDefault = false;
+bool sAudioChannelCompeting = false;
 bool sXPCOMShuttingDown = false;
 
 class NotifyChannelActiveRunnable final : public Runnable
 {
 public:
   NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel,
                               bool aActive)
     : mWindowID(aWindowID)
@@ -205,16 +207,23 @@ AudioChannelService::Shutdown()
 #ifdef MOZ_WIDGET_GONK
     gAudioChannelService->mSpeakerManager.Clear();
 #endif
 
     gAudioChannelService = nullptr;
   }
 }
 
+/* static */ bool
+AudioChannelService::IsEnableAudioCompeting()
+{
+  CreateServiceIfNeeded();
+  return sAudioChannelCompeting;
+}
+
 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(AudioChannelService)
 NS_IMPL_RELEASE(AudioChannelService)
@@ -236,16 +245,18 @@ AudioChannelService::AudioChannelService
       // To monitor the volume settings based on audio channel.
       obs->AddObserver(this, "mozsettings-changed", false);
 #endif
     }
   }
 
   Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault,
                                "dom.audiochannel.mutedByDefault");
+  Preferences::AddBoolVarCache(&sAudioChannelCompeting,
+                               "dom.audiochannel.audioCompeting");
 }
 
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
@@ -331,24 +342,25 @@ AudioChannelService::GetMediaConfig(nsPI
 
   // The volume must be calculated based on the window hierarchy. Here we go up
   // to the top window and we calculate the volume and the muted flag.
   do {
     winData = GetWindowData(window->WindowID());
     if (winData) {
       config.mVolume *= winData->mChannels[aAudioChannel].mVolume;
       config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted;
+      config.mSuspend = winData->mOwningAudioFocus ?
+        config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
     }
 
     config.mVolume *= window->GetAudioVolume();
     config.mMuted = config.mMuted || window->GetAudioMuted();
-
-    // If the mSuspend is already suspended, we don't need to set it again.
-    config.mSuspend = (config.mSuspend == nsISuspendedTypes::NONE_SUSPENDED) ?
-      window->GetMediaSuspend() : config.mSuspend;
+    if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
+      config.mSuspend = window->GetMediaSuspend();
+    }
 
     nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull();
     if (!win) {
       break;
     }
 
     window = do_QueryInterface(win);
 
@@ -989,32 +1001,205 @@ AudioChannelService::ChildStatusReceived
     data = new AudioChannelChildStatus(aChildID);
     mPlayingChildren.AppendElement(data);
   }
 
   data->mActiveTelephonyChannel = aTelephonyChannel;
   data->mActiveContentOrNormalChannel = aContentOrNormalChannel;
 }
 
+void
+AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent,
+                                                    bool aActive)
+{
+  MOZ_ASSERT(aAgent);
+
+  nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
+    iter(mWindows);
+  while (iter.HasMore()) {
+    AudioChannelWindow* winData = iter.GetNext();
+    if (winData->mOwningAudioFocus) {
+      winData->AudioFocusChanged(aAgent, aActive);
+    }
+  }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
+{
+  MOZ_ASSERT(aAgent);
+
+  // We already have the audio focus. No operation is needed.
+  if (mOwningAudioFocus) {
+    return;
+  }
+
+  // Only foreground window can request audio focus, but it would still own the
+  // audio focus even it goes to background. Audio focus would be abandoned
+  // only when other foreground window starts audio competing.
+  mOwningAudioFocus = !(aAgent->Window()->IsBackground());
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("AudioChannelWindow, RequestAudioFocus, this = %p, "
+          "agent = %p, owning audio focus = %d\n",
+          this, aAgent, mOwningAudioFocus));
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent,
+                                                                     bool aActive)
+{
+  // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
+  // agent may be not contained in mAgent. In addition, the agent would still
+  // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
+  MOZ_ASSERT(aAgent);
+
+  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  MOZ_ASSERT(service);
+
+  if (!service->IsEnableAudioCompeting()) {
+    return;
+  }
+
+  if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
+    return;
+  }
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
+          "agent = %p, active = %d\n",
+          this, aAgent, aActive));
+
+  service->RefreshAgentsAudioFocusChanged(aAgent, aActive);
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
+{
+  MOZ_ASSERT(aAgent);
+
+  if(!mOwningAudioFocus) {
+    return false;
+  }
+
+  if (IsAudioCompetingInSameTab()) {
+    return false;
+  }
+
+  // TODO : add MediaSession::ambient kind, because it doens't interact with
+  // other kinds.
+  return true;
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
+{
+  return (mOwningAudioFocus && mAudibleAgents.Length() > 1);
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent,
+                                                           bool aActive)
+{
+  // This agent isn't always known for the current window, because it can comes
+  // from other window.
+  MOZ_ASSERT(aNewPlayingAgent);
+
+  if (mAudibleAgents.IsEmpty()) {
+    // These would happen in two situations,
+    // (1) Audio in page A was ended, and another page B want to play audio.
+    //     Page A should abandon its focus.
+    // (2) Audio was paused by remote-control, page should still own the focus.
+    mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
+  } else {
+    nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(mAudibleAgents);
+    while (iter.HasMore()) {
+      AudioChannelAgent* agent = iter.GetNext();
+      MOZ_ASSERT(agent);
+
+      // Don't need to update the playing state of new playing agent.
+      if (agent == aNewPlayingAgent) {
+        continue;
+      }
+
+      uint32_t type = GetCompetingBehavior(agent,
+                                           aNewPlayingAgent->AudioChannelType(),
+                                           aActive);
+
+      // If window will be suspended, it needs to abandon the audio focus
+      // because only one window can own audio focus at a time. However, we
+      // would support multiple audio focus at the same time in the future.
+      mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
+
+      // TODO : support other behaviors which are definded in MediaSession API.
+      switch (type) {
+        case nsISuspendedTypes::NONE_SUSPENDED:
+        case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
+          agent->WindowSuspendChanged(type);
+          break;
+      }
+    }
+  }
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("AudioChannelWindow, AudioFocusChanged, this = %p, "
+          "OwningAudioFocus = %d\n", this, mOwningAudioFocus));
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
+{
+  return (aAgent->WindowID() == mWindowID);
+}
+
+uint32_t
+AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent,
+                                                              int32_t aIncomingChannelType,
+                                                              bool aIncomingChannelActive) const
+{
+  MOZ_ASSERT(aAgent);
+  MOZ_ASSERT(mAudibleAgents.Contains(aAgent));
+
+  uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED;
+  int32_t presentChannelType = aAgent->AudioChannelType();
+
+  // TODO : add other competing cases for MediaSession API
+  if (presentChannelType == int32_t(AudioChannel::Normal) &&
+      aIncomingChannelType == int32_t(AudioChannel::Normal) &&
+      aIncomingChannelActive) {
+    competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
+  }
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
+          "present type = %d, incoming channel = %d, behavior = %d\n",
+          this, presentChannelType, aIncomingChannelType, competingBehavior));
+
+  return competingBehavior;
+}
+
 /* static */ bool
 AudioChannelService::IsAudioChannelMutedByDefault()
 {
   CreateServiceIfNeeded();
   return sAudioChannelMutedByDefault;
 }
 
 void
 AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent,
                                                      AudibleState aAudible)
 {
   MOZ_ASSERT(aAgent);
 
+  RequestAudioFocus(aAgent);
   AppendAgentAndIncreaseAgentsNum(aAgent);
   AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
-  AudioAudibleChanged(aAgent, aAudible);
+  if (aAudible) {
+    AudioAudibleChanged(aAgent, AudibleState::eAudible);
+  }
 }
 
 void
 AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
 
   RemoveAgentAndReduceAgentsNum(aAgent);
@@ -1077,16 +1262,18 @@ AudioChannelService::AudioChannelWindow:
 {
   MOZ_ASSERT(aAgent);
 
   if (aAudible) {
     AppendAudibleAgentIfNotContained(aAgent);
   } else {
     RemoveAudibleAgentIfContained(aAgent);
   }
+
+  NotifyAudioCompetingChanged(aAgent, aAudible);
 }
 
 void
 AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
   MOZ_ASSERT(mAgents.Contains(aAgent));
 
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -84,16 +84,18 @@ public:
    * Only to be called from main thread.
    */
   static already_AddRefed<AudioChannelService> GetOrCreate();
 
   static bool IsAudioChannelMutedByDefault();
 
   static PRLogModuleInfo* GetAudioChannelLog();
 
+  static bool IsEnableAudioCompeting();
+
   /**
    * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannel.
    */
   void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                  AudibleState aAudible);
 
   /**
@@ -215,52 +217,61 @@ private:
   void MaybeSendStatusUpdate();
 
   bool ContentOrNormalChannelIsActive();
 
   /* Send the default-volume-channel-changed notification */
   void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
                                               bool aVisible, uint64_t aChildID);
 
+  void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent,
+                                      bool aActive);
+
   class AudioChannelConfig final : public AudioPlaybackConfig
   {
   public:
     AudioChannelConfig()
       : AudioPlaybackConfig(1.0, IsAudioChannelMutedByDefault(),
                             nsISuspendedTypes::NONE_SUSPENDED)
       , mNumberOfAgents(0)
     {}
 
     uint32_t mNumberOfAgents;
   };
 
   class AudioChannelWindow final
   {
   public:
     explicit AudioChannelWindow(uint64_t aWindowID)
-      : mWindowID(aWindowID),
-        mIsAudioCaptured(false)
+      : mWindowID(aWindowID)
+      , mIsAudioCaptured(false)
+      , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting())
     {
       // Workaround for bug1183033, system channel type can always playback.
       mChannels[(int16_t)AudioChannel::System].mMuted = false;
     }
 
+    void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, bool aActive);
     void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible);
 
     void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible);
     void RemoveAgent(AudioChannelAgent* aAgent);
 
     uint64_t mWindowID;
     bool mIsAudioCaptured;
     AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
 
     // Raw pointer because the AudioChannelAgent must unregister itself.
     nsTObserverArray<AudioChannelAgent*> mAgents;
     nsTObserverArray<AudioChannelAgent*> mAudibleAgents;
 
+    // Owning audio focus when the window starts playing audible sound, and
+    // lose audio focus when other windows starts playing.
+    bool mOwningAudioFocus;
+
   private:
     void AudioCapturedChanged(AudioChannelAgent* aAgent,
                               AudioCaptureState aCapture);
 
     void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent);
     void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent);
 
     void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent);
@@ -268,16 +279,26 @@ private:
 
     bool IsFirstAudibleAgent() const;
     bool IsLastAudibleAgent() const;
 
     void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
                                    AudibleState aAudible);
     void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel,
                              bool aActive);
+
+    void RequestAudioFocus(AudioChannelAgent* aAgent);
+    void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, bool aActive);
+
+    uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent,
+                                  int32_t aIncomingChannelType,
+                                  bool aIncomingChannelActive) const;
+    bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const;
+    bool IsAudioCompetingInSameTab() const;
+    bool IsContainingPlayingAgent(AudioChannelAgent* aAgent) const;
   };
 
   AudioChannelWindow*
   GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow);
 
   AudioChannelWindow*
   GetWindowData(uint64_t aWindowID) const;
 
--- a/dom/audiochannel/moz.build
+++ b/dom/audiochannel/moz.build
@@ -16,11 +16,15 @@ EXPORTS += [
     'AudioChannelService.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioChannelAgent.cpp',
     'AudioChannelService.cpp',
 ]
 
+LOCAL_INCLUDES += [
+    '/dom/base/',
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -334,22 +334,19 @@ DOMParser::Init(nsIPrincipal* principal,
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   mScriptHandlingObject = do_GetWeakReference(aScriptObject);
   mPrincipal = principal;
   nsresult rv;
   if (!mPrincipal) {
-    nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
-    NS_ENSURE_TRUE(secMan, NS_ERROR_NOT_AVAILABLE);
-    rv =
-      secMan->GetSimpleCodebasePrincipal(mDocumentURI,
-                                         getter_AddRefs(mPrincipal));
-    NS_ENSURE_SUCCESS(rv, rv);
+    PrincipalOriginAttributes attrs;
+    mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs);
+    NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE);
     mOriginalPrincipal = mPrincipal;
   } else {
     mOriginalPrincipal = mPrincipal;
     if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
       // Don't give DOMParsers the system principal.  Use a null
       // principal instead.
       mPrincipal = nsNullPrincipal::Create();
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1176,44 +1176,38 @@ void
 Element::SetAttribute(const nsAString& aName,
                       const nsAString& aValue,
                       ErrorResult& aError)
 {
   aError = nsContentUtils::CheckQName(aName, false);
   if (aError.Failed()) {
     return;
   }
-  const nsAttrName* name = InternalGetExistingAttrNameFromQName(aName);
+
+  nsAutoString nameToUse;
+  const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
   if (!name) {
-    nsCOMPtr<nsIAtom> nameAtom;
-    if (IsHTMLElement() && IsInHTMLDocument()) {
-      nsAutoString lower;
-      nsContentUtils::ASCIIToLower(aName, lower);
-      nameAtom = NS_Atomize(lower);
-    }
-    else {
-      nameAtom = NS_Atomize(aName);
-    }
+    nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(nameToUse);
     if (!nameAtom) {
       aError.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
     aError = SetAttr(kNameSpaceID_None, nameAtom, aValue, true);
     return;
   }
 
   aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
                    aValue, true);
   return;
 }
 
 void
 Element::RemoveAttribute(const nsAString& aName, ErrorResult& aError)
 {
-  const nsAttrName* name = InternalGetExistingAttrNameFromQName(aName);
+  const nsAttrName* name = InternalGetAttrNameFromQName(aName);
 
   if (!name) {
     // If there is no canonical nsAttrName for this attribute name, then the
     // attribute does not exist and we can't get its namespace ID and
     // local name below, so we return early.
     return;
   }
 
@@ -1986,17 +1980,17 @@ Element::FindAttributeDependence(const n
   }
 
   return false;
 }
 
 already_AddRefed<mozilla::dom::NodeInfo>
 Element::GetExistingAttrNameFromQName(const nsAString& aStr) const
 {
-  const nsAttrName* name = InternalGetExistingAttrNameFromQName(aStr);
+  const nsAttrName* name = InternalGetAttrNameFromQName(aStr);
   if (!name) {
     return nullptr;
   }
 
   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
   if (name->IsAtom()) {
     nodeInfo = mNodeInfo->NodeInfoManager()->
       GetNodeInfo(name->Atom(), nullptr, kNameSpaceID_None,
@@ -2162,19 +2156,37 @@ Element::SetEventHandler(nsIAtom* aEvent
                            this);
   return NS_OK;
 }
 
 
 //----------------------------------------------------------------------
 
 const nsAttrName*
-Element::InternalGetExistingAttrNameFromQName(const nsAString& aStr) const
+Element::InternalGetAttrNameFromQName(const nsAString& aStr,
+                                      nsAutoString* aNameToUse) const
 {
-  return mAttrsAndChildren.GetExistingAttrNameFromQName(aStr);
+  MOZ_ASSERT(!aNameToUse || aNameToUse->IsEmpty());
+  const nsAttrName* val = nullptr;
+  if (IsHTMLElement() && IsInHTMLDocument()) {
+    nsAutoString lower;
+    nsAutoString& outStr = aNameToUse ? *aNameToUse : lower;
+    nsContentUtils::ASCIIToLower(aStr, outStr);
+    val = mAttrsAndChildren.GetExistingAttrNameFromQName(outStr);
+    if (val) {
+      outStr.Truncate();
+    }
+  } else {
+    val = mAttrsAndChildren.GetExistingAttrNameFromQName(aStr);
+    if (!val && aNameToUse) {
+      *aNameToUse = aStr;
+    }
+  }
+
+  return val;
 }
 
 bool
 Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
                                nsIAtom* aName,
                                nsIAtom* aPrefix,
                                const nsAttrValueOrString& aValue,
                                bool aNotify,
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -655,17 +655,17 @@ public:
                       ErrorResult& aError);
   void RemoveAttribute(const nsAString& aName,
                        ErrorResult& aError);
   void RemoveAttributeNS(const nsAString& aNamespaceURI,
                          const nsAString& aLocalName,
                          ErrorResult& aError);
   bool HasAttribute(const nsAString& aName) const
   {
-    return InternalGetExistingAttrNameFromQName(aName) != nullptr;
+    return InternalGetAttrNameFromQName(aName) != nullptr;
   }
   bool HasAttributeNS(const nsAString& aNamespaceURI,
                       const nsAString& aLocalName) const;
   bool HasAttributes() const
   {
     return HasAttrs();
   }
   Element* Closest(const nsAString& aSelector,
@@ -1268,19 +1268,23 @@ protected:
   /**
    * Hook to allow subclasses to produce a different EventListenerManager if
    * needed for attachment of attribute-defined handlers
    */
   virtual EventListenerManager*
     GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer);
 
   /**
-   * Internal hook for converting an attribute name-string to an atomized name
+   * Internal hook for converting an attribute name-string to nsAttrName in
+   * case there is such existing attribute. aNameToUse can be passed to get
+   * name which was used for looking for the attribute (lowercase in HTML).
    */
-  virtual const nsAttrName* InternalGetExistingAttrNameFromQName(const nsAString& aStr) const;
+  const nsAttrName*
+  InternalGetAttrNameFromQName(const nsAString& aStr,
+                               nsAutoString* aNameToUse = nullptr) const;
 
   nsIFrame* GetStyledFrame();
 
   virtual Element* GetNameSpaceElement() override
   {
     return this;
   }
 
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -2714,20 +2714,20 @@ WebSocketImpl::SetLoadFlags(nsLoadFlags 
 namespace {
 
 class WorkerRunnableDispatcher final : public WorkerRunnable
 {
   RefPtr<WebSocketImpl> mWebSocketImpl;
 
 public:
   WorkerRunnableDispatcher(WebSocketImpl* aImpl, WorkerPrivate* aWorkerPrivate,
-                           already_AddRefed<nsIRunnable>&& aEvent)
+                           already_AddRefed<nsIRunnable> aEvent)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
     , mWebSocketImpl(aImpl)
-    , mEvent(aEvent)
+    , mEvent(Move(aEvent))
   {
   }
 
   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
     aWorkerPrivate->ModifyBusyCountFromWorker(true);
 
@@ -2774,17 +2774,17 @@ private:
 NS_IMETHODIMP
 WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
   return Dispatch(event.forget(), aFlags);
 }
 
 NS_IMETHODIMP
-WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags)
+WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> event_ref(aEvent);
   // If the target is the main-thread we can just dispatch the runnable.
   if (mIsMainThread) {
     return NS_DispatchToMainThread(event_ref.forget());
   }
 
   MutexAutoLock lock(mMutex);
@@ -2806,17 +2806,17 @@ WebSocketImpl::Dispatch(already_AddRefed
   if (!event->Dispatch()) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t)
+WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::IsOnCurrentThread(bool* aResult)
 {
   *aResult = IsTargetThread();
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -42,8 +42,9 @@ DEPRECATED_OPERATION(ImportXULIntoConten
 DEPRECATED_OPERATION(PannerNodeDoppler)
 DEPRECATED_OPERATION(NavigatorGetUserMedia)
 DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
 DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
 DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedFullscreenAPI)
 DEPRECATED_OPERATION(LenientSetter)
 DEPRECATED_OPERATION(NavigatorBattery)
+DEPRECATED_OPERATION(FileLastModifiedDate)
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -851,16 +851,17 @@ GK_ATOM(onmoznetworkdownload, "onmoznetw
 GK_ATOM(onmapfolderlistingreq, "onmapfolderlistingreq")
 GK_ATOM(onmapmessageslistingreq, "onmapmessageslistingreq")
 GK_ATOM(onmapgetmessagereq, "onmapgetmessagereq")
 GK_ATOM(onmapsetmessagestatusreq, "onmapsetmessagestatusreq")
 GK_ATOM(onmapsendmessagereq, "onmapsendmessagereq")
 GK_ATOM(onmapmessageupdatereq, "onmapmessageupdatereq")
 GK_ATOM(onnewrdsgroup, "onnewrdsgroup")
 GK_ATOM(onnotificationclick, "onnotificationclick")
+GK_ATOM(onnotificationclose, "onnotificationclose")
 GK_ATOM(onnoupdate, "onnoupdate")
 GK_ATOM(onobexpasswordreq, "onobexpasswordreq")
 GK_ATOM(onobsolete, "onobsolete")
 GK_ATOM(ononline, "ononline")
 GK_ATOM(onoffline, "onoffline")
 GK_ATOM(onopen, "onopen")
 GK_ATOM(onorientationchange, "onorientationchange")
 GK_ATOM(onotastatuschange, "onotastatuschange")
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -129,35 +129,16 @@ private:
                                  JS::SourceBufferHolder& aSrcBuf,
                                  JS::Handle<JSObject*> aEvaluationGlobal,
                                  JS::CompileOptions& aCompileOptions,
                                  const EvaluateOptions& aEvaluateOptions,
                                  JS::MutableHandle<JS::Value> aRetValue,
                                  void **aOffThreadToken);
 };
 
-class MOZ_STACK_CLASS AutoDontReportUncaught {
-  JSContext* mContext;
-  bool mWasSet;
-
-public:
-  explicit AutoDontReportUncaught(JSContext* aContext) : mContext(aContext) {
-    MOZ_ASSERT(aContext);
-    mWasSet = JS::ContextOptionsRef(mContext).dontReportUncaught();
-    if (!mWasSet) {
-      JS::ContextOptionsRef(mContext).setDontReportUncaught(true);
-    }
-  }
-  ~AutoDontReportUncaught() {
-    if (!mWasSet) {
-      JS::ContextOptionsRef(mContext).setDontReportUncaught(false);
-    }
-  }
-};
-
 template<typename T>
 inline bool
 AssignJSString(JSContext *cx, T &dest, JSString *s)
 {
   size_t len = js::GetStringLength(s);
   static_assert(js::MaxStringLength < (1 << 28),
                 "Shouldn't overflow here or in SetCapacity");
   if (MOZ_UNLIKELY(!dest.SetLength(len, mozilla::fallible))) {
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -14,16 +14,18 @@
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
@@ -61,17 +63,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins)
+                                      mPlugins,
+                                      mCTPPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -141,16 +144,17 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -216,16 +220,23 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
+  if (!aFound) {
+    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+    if (hiddenPlugin) {
+      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+      obs->NotifyObservers(hiddenPlugin->PluginTag(), "Plugin::HiddenPluginTouched", nsString(aName).get());
+    }
+  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins()) {
     return 0;
@@ -277,34 +288,58 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+    if (!pluginTag) {
+      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    } else if (pluginTag->IsActive()) {
+      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+      if (pluginTag->IsClicktoplay()) {
+        nsCString name;
+        pluginTag->GetName(name);
+        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
+          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+          nsCString permString;
+          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+          if (rv == NS_OK) {
+            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+          }
+        }
+      }
+      if (permission == nsIPermissionManager::ALLOW_ACTION) {
+        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      } else {
+        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      }
+    }
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,16 +55,20 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
+  /* A separate list of click-to-play plugins that we don't tell content
+   * about but keep track of so we can still prompt the user to click to play.
+   */
+  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -13651,17 +13651,19 @@ class CGBindingRoot(CGThing):
     def deps(self):
         return self.root.deps()
 
 
 class CGNativeMember(ClassMethod):
     def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
                  breakAfter=True, passJSBitsAsNeeded=True, visibility="public",
                  typedArraysAreStructs=True, variadicIsSequence=False,
-                 resultNotAddRefed=False):
+                 resultNotAddRefed=False,
+                 virtual=False,
+                 override=False):
         """
         If typedArraysAreStructs is false, typed arrays will be passed as
         JS::Handle<JSObject*>.  If it's true they will be passed as one of the
         dom::TypedArray subclasses.
 
         If passJSBitsAsNeeded is false, we don't automatically pass in a
         JSContext* or a JSObject* based on the return and argument types.  We
         can still pass it based on 'implicitJSContext' annotations.
@@ -13679,17 +13681,19 @@ class CGNativeMember(ClassMethod):
                              self.getArgs(signature[0], signature[1]),
                              static=member.isStatic(),
                              # Mark our getters, which are attrs that
                              # have a non-void return type, as const.
                              const=(not member.isStatic() and member.isAttr() and
                                     not signature[0].isVoid()),
                              breakAfterReturnDecl=" ",
                              breakAfterSelf=breakAfterSelf,
-                             visibility=visibility)
+                             visibility=visibility,
+                             virtual=virtual,
+                             override=override)
 
     def getReturnType(self, type, isMember):
         return self.getRetvalInfo(type, isMember)[0]
 
     def getRetvalInfo(self, type, isMember):
         """
         Returns a tuple:
 
@@ -14443,47 +14447,59 @@ def jsImplName(name):
 
 class CGJSImplMember(CGNativeMember):
     """
     Base class for generating code for the members of the implementation class
     for a JS-implemented WebIDL interface.
     """
     def __init__(self, descriptorProvider, member, name, signature,
                  extendedAttrs, breakAfter=True, passJSBitsAsNeeded=True,
-                 visibility="public", variadicIsSequence=False):
+                 visibility="public", variadicIsSequence=False,
+                 virtual=False, override=False):
         CGNativeMember.__init__(self, descriptorProvider, member, name,
                                 signature, extendedAttrs, breakAfter=breakAfter,
                                 passJSBitsAsNeeded=passJSBitsAsNeeded,
                                 visibility=visibility,
-                                variadicIsSequence=variadicIsSequence)
+                                variadicIsSequence=variadicIsSequence,
+                                virtual=virtual,
+                                override=override)
         self.body = self.getImpl()
 
     def getArgs(self, returnType, argList):
         args = CGNativeMember.getArgs(self, returnType, argList)
         args.append(Argument("JSCompartment*", "aCompartment", "nullptr"))
         return args
 
 
 class CGJSImplMethod(CGJSImplMember):
     """
     Class for generating code for the methods for a JS-implemented WebIDL
     interface.
     """
     def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
+        virtual = False
+        override = False
+        if (method.identifier.name == "eventListenerWasAdded" or
+            method.identifier.name == "eventListenerWasRemoved"):
+            virtual = True
+            override = True
+
         self.signature = signature
         self.descriptor = descriptor
         self.isConstructor = isConstructor
         CGJSImplMember.__init__(self, descriptor, method,
                                 CGSpecializedMethod.makeNativeName(descriptor,
                                                                    method),
                                 signature,
                                 descriptor.getExtendedAttributes(method),
                                 breakAfter=breakAfter,
                                 variadicIsSequence=True,
-                                passJSBitsAsNeeded=False)
+                                passJSBitsAsNeeded=False,
+                                virtual=virtual,
+                                override=override)
 
     def getArgs(self, returnType, argList):
         if self.isConstructor:
             # Skip the JSCompartment bits for constructors; it's handled
             # manually in getImpl.
             return CGNativeMember.getArgs(self, returnType, argList)
         return CGJSImplMember.getArgs(self, returnType, argList)
 
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -213,20 +213,21 @@ TexUnpackBytes::TexOrSubImage(bool isSub
         const auto dstOrigin = (needsYFlip ? gl::OriginPos::TopLeft
                                            : gl::OriginPos::BottomLeft);
 
         const bool srcPremultiplied = false;
         const bool dstPremultiplied = needsAlphaPremult; // Always true here.
 
         // And go!:
         MOZ_ASSERT(srcOrigin != dstOrigin || srcPremultiplied != dstPremultiplied);
+        bool unused_wasTrivial;
         if (!ConvertImage(mWidth, mHeight,
                           mBytes, rowStride, srcOrigin, texelFormat, srcPremultiplied,
                           tempBuffer.get(), rowStride, dstOrigin, texelFormat,
-                          dstPremultiplied))
+                          dstPremultiplied, &unused_wasTrivial))
         {
             MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
             return;
         }
 
         uploadBytes = tempBuffer.get();
     } while (false);
@@ -306,16 +307,20 @@ TexUnpackImage::TexOrSubImage(bool isSub
                                                       dstOrigin))
         {
             break;
         }
 
         return; // Blitting was successful, so we're done!
     } while (false);
 
+    webgl->GenerateWarning("%s: Failed to hit GPU-copy fast-path. Falling back to CPU"
+                           " upload.",
+                           funcName);
+
     RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
     if (!surface) {
         *out_glError = LOCAL_GL_OUT_OF_MEMORY;
         return;
     }
 
     TexUnpackSurface surfBlob(surface, mIsAlphaPremult);
 
@@ -657,17 +662,17 @@ GetFormatForPackingTuple(GLenum packingF
     return false;
 }
 
 /*static*/ bool
 TexUnpackSurface::ConvertSurface(WebGLContext* webgl, const webgl::DriverUnpackInfo* dui,
                                  gfx::DataSourceSurface* surf, bool isSurfAlphaPremult,
                                  UniqueBuffer* const out_convertedBuffer,
                                  uint8_t* const out_convertedAlignment,
-                                 bool* const out_outOfMemory)
+                                 bool* const out_wasTrivial, bool* const out_outOfMemory)
 {
     *out_outOfMemory = false;
 
     const size_t width = surf->GetSize().width;
     const size_t height = surf->GetSize().height;
 
     // Source args:
 
@@ -714,28 +719,31 @@ TexUnpackSurface::ConvertSurface(WebGLCo
     void* const dstBegin = dstBuffer.get();
 
     gl::OriginPos srcOrigin, dstOrigin;
     OriginsForDOM(webgl, &srcOrigin, &dstOrigin);
 
     const bool dstPremultiplied = webgl->mPixelStore_PremultiplyAlpha;
 
     // And go!:
+    bool wasTrivial;
     if (!ConvertImage(width, height,
                       srcBegin, srcStride, srcOrigin, srcFormat, srcPremultiplied,
-                      dstBegin, dstStride, dstOrigin, dstFormat, dstPremultiplied))
+                      dstBegin, dstStride, dstOrigin, dstFormat, dstPremultiplied,
+                      &wasTrivial))
     {
         MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
         NS_ERROR("ConvertImage failed unexpectedly.");
         *out_outOfMemory = true;
         return false;
     }
 
     *out_convertedBuffer = Move(dstBuffer);
     *out_convertedAlignment = dstAlignment;
+    *out_wasTrivial = wasTrivial;
     return true;
 }
 
 
 ////////////////////
 
 TexUnpackSurface::TexUnpackSurface(const RefPtr<gfx::SourceSurface>& surf,
                                    bool isAlphaPremult)
@@ -780,29 +788,36 @@ TexUnpackSurface::TexOrSubImage(bool isS
         *out_glError = LOCAL_GL_OUT_OF_MEMORY;
         return;
     }
 
     // CPU conversion. (++numCopies)
 
     UniqueBuffer convertedBuffer;
     uint8_t convertedAlignment;
+    bool wasTrivial;
     bool outOfMemory;
     if (!ConvertSurface(webgl, dui, dataSurf, mIsAlphaPremult, &convertedBuffer,
-                        &convertedAlignment, &outOfMemory))
+                        &convertedAlignment, &wasTrivial, &outOfMemory))
     {
         if (outOfMemory) {
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
         } else {
             NS_ERROR("Failed to convert surface.");
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
         }
         return;
     }
 
+    if (!wasTrivial) {
+        webgl->GenerateWarning("%s: Chosen format/type incured an expensive reformat:"
+                               " 0x%04x/0x%04x",
+                               funcName, dui->unpackFormat, dui->unpackType);
+    }
+
     MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() );
     ScopedUnpackReset scopedReset(webgl);
     webgl->gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, convertedAlignment);
 
     error = DoTexOrSubImage(isSubImage, webgl->gl, target.get(), level, dui, xOffset,
                             yOffset, zOffset, mWidth, mHeight, mDepth,
                             convertedBuffer.get());
     *out_glError = error;
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -142,17 +142,17 @@ public:
                                GLint yOffset, GLint zOffset,
                                GLenum* const out_glError) override;
 
 protected:
     static bool ConvertSurface(WebGLContext* webgl, const webgl::DriverUnpackInfo* dui,
                                gfx::DataSourceSurface* surf, bool isSurfAlphaPremult,
                                UniqueBuffer* const out_convertedBuffer,
                                uint8_t* const out_convertedAlignment,
-                               bool* const out_outOfMemory);
+                               bool* const out_wasTrivial, bool* const out_outOfMemory);
     static bool UploadDataSurface(bool isSubImage, WebGLContext* webgl,
                                   TexImageTarget target, GLint level,
                                   const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                   GLint yOffset, GLint zOffset, GLsizei width,
                                   GLsizei height, gfx::DataSourceSurface* surf,
                                   bool isSurfAlphaPremult, GLenum* const out_glError);
 };
 
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -38,21 +38,18 @@ WebGL2Context::GetParameter(JSContext* c
     case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE: {
       realGLboolean b = 0;
       gl->fGetBooleanv(pname, &b);
       return JS::BooleanValue(bool(b));
     }
 
     /* GLenum */
     case LOCAL_GL_READ_BUFFER: {
-      if (mBoundReadFramebuffer) {
-        GLint val = LOCAL_GL_NONE;
-        gl->fGetIntegerv(pname, &val);
-        return JS::Int32Value(val);
-      }
+      if (mBoundReadFramebuffer)
+        return JS::Int32Value(mBoundReadFramebuffer->ReadBufferMode());
 
       return JS::Int32Value(LOCAL_GL_BACK);
     }
 
     case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
       /* fall through */
 
     /* GLint */
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -979,54 +979,56 @@ WebGLContext::SetDimensions(int32_t sign
     if (!gl->Caps().depth)
         mOptions.depth = false;
 
     if (!gl->Caps().stencil)
         mOptions.stencil = false;
 
     mOptions.antialias = gl->Caps().antialias;
 
+    //////
+    // Initial setup.
+
     MakeContextCurrent();
 
     gl->fViewport(0, 0, mWidth, mHeight);
     mViewportWidth = mWidth;
     mViewportHeight = mHeight;
 
     gl->fScissor(0, 0, mWidth, mHeight);
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
 
-    // Make sure that we clear this out, otherwise
-    // we'll end up displaying random memory
-    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+    //////
+    // Check everything
 
     AssertCachedBindings();
-    AssertCachedState();
-
-    // Clear immediately, because we need to present the cleared initial
-    // buffer.
-    mBackbufferNeedsClear = true;
-    ClearBackbufferIfNeeded();
-
-    mShouldPresent = true;
+    AssertCachedGlobalState();
 
     MOZ_ASSERT(gl->Caps().color);
 
     MOZ_ASSERT_IF(!mNeedsFakeNoAlpha, gl->Caps().alpha == mOptions.alpha);
     MOZ_ASSERT_IF(mNeedsFakeNoAlpha, !mOptions.alpha && gl->Caps().alpha);
 
     MOZ_ASSERT_IF(!mNeedsFakeNoDepth, gl->Caps().depth == mOptions.depth);
     MOZ_ASSERT_IF(mNeedsFakeNoDepth, !mOptions.depth && gl->Caps().depth);
 
     MOZ_ASSERT_IF(!mNeedsFakeNoStencil, gl->Caps().stencil == mOptions.stencil);
     MOZ_ASSERT_IF(mNeedsFakeNoStencil, !mOptions.stencil && gl->Caps().stencil);
 
     MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias);
     MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
 
-    AssertCachedBindings();
-    AssertCachedState();
+    //////
+    // Clear immediately, because we need to present the cleared initial buffer
+    mBackbufferNeedsClear = true;
+    ClearBackbufferIfNeeded();
+
+    mShouldPresent = true;
+
+    //////
 
     reporter.SetSuccessful();
 
     Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
                           NS_LITERAL_CSTRING("SUCCESS"));
     return NS_OK;
 }
 
@@ -1411,18 +1413,17 @@ WebGLContext::ForceClearFramebufferWithD
     MakeContextCurrent();
 
     const bool initializeColorBuffer = bool(clearBits & LOCAL_GL_COLOR_BUFFER_BIT);
     const bool initializeDepthBuffer = bool(clearBits & LOCAL_GL_DEPTH_BUFFER_BIT);
     const bool initializeStencilBuffer = bool(clearBits & LOCAL_GL_STENCIL_BUFFER_BIT);
 
     // Fun GL fact: No need to worry about the viewport here, glViewport is just
     // setting up a coordinates transformation, it doesn't affect glClear at all.
-    AssertCachedState(); // Can't check cached bindings, as we could
-                         // have a different FB bound temporarily.
+    AssertCachedGlobalState();
 
     // Prepare GL state for clearing.
     gl->fDisable(LOCAL_GL_SCISSOR_TEST);
 
     if (initializeColorBuffer) {
         gl->fColorMask(1, 1, 1, 1);
 
         if (fakeNoAlpha) {
@@ -1876,16 +1877,24 @@ WebGLContext::DidRefresh()
 
 bool
 WebGLContext::ValidateCurFBForRead(const char* funcName,
                                    const webgl::FormatUsageInfo** const out_format,
                                    uint32_t* const out_width, uint32_t* const out_height,
                                    GLenum* const out_mode)
 {
     if (!mBoundReadFramebuffer) {
+        const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
+        if (readBufferMode == LOCAL_GL_NONE) {
+            ErrorInvalidOperation("%s: Can't read from backbuffer when readBuffer mode is"
+                                  " NONE.",
+                                  funcName);
+            return false;
+        }
+
         ClearBackbufferIfNeeded();
 
         // FIXME - here we're assuming that the default framebuffer is backed by
         // UNSIGNED_BYTE that might not always be true, say if we had a 16bpp default
         // framebuffer.
         auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
                                         : webgl::EffectiveFormat::RGB8;
 
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -371,17 +371,17 @@ public:
 
     void RunContextLossTimer();
     void UpdateContextLossStatus();
     void EnqueueUpdateContextLossStatus();
 
     bool TryToRestoreContext();
 
     void AssertCachedBindings();
-    void AssertCachedState();
+    void AssertCachedGlobalState();
 
     dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; }
 
     // WebIDL WebGLRenderingContext API
     void Commit();
     void GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval);
     GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; }
     GLsizei DrawingBufferHeight() const {
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -67,17 +67,18 @@ GLClampFloat(GLfloat val)
 void
 WebGLContext::ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
 {
     if (IsContextLost())
         return;
 
     MakeContextCurrent();
 
-    const bool supportsFloatColorBuffers = (IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float) ||
+    const bool supportsFloatColorBuffers = (IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_float) ||
+                                            IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float) ||
                                             IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float));
     if (!supportsFloatColorBuffers) {
         r = GLClampFloat(r);
         g = GLClampFloat(g);
         b = GLClampFloat(b);
         a = GLClampFloat(a);
     }
 
--- a/dom/canvas/WebGLContextLossHandler.cpp
+++ b/dom/canvas/WebGLContextLossHandler.cpp
@@ -63,27 +63,27 @@ NS_IMPL_ISUPPORTS(ContextLossWorkerEvent
 NS_IMETHODIMP
 ContextLossWorkerEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
     nsCOMPtr<nsIRunnable> event(aEvent);
     return Dispatch(event.forget(), aFlags);
 }
 
 NS_IMETHODIMP
-ContextLossWorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aEvent,
+ContextLossWorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent,
                                        uint32_t aFlags)
 {
     nsCOMPtr<nsIRunnable> eventRef(aEvent);
     RefPtr<ContextLossWorkerRunnable> wrappedEvent =
         new ContextLossWorkerRunnable(eventRef);
     return mEventTarget->Dispatch(wrappedEvent, aFlags);
 }
 
 NS_IMETHODIMP
-ContextLossWorkerEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>&&,
+ContextLossWorkerEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
                                               uint32_t)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 ContextLossWorkerEventTarget::IsOnCurrentThread(bool* aResult)
 {
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -375,47 +375,37 @@ WebGLContext::GetParameter(JSContext* cx
         case LOCAL_GL_BLEND_EQUATION_RGB:
         case LOCAL_GL_BLEND_EQUATION_ALPHA:
         case LOCAL_GL_GENERATE_MIPMAP_HINT: {
             GLint i = 0;
             gl->fGetIntegerv(pname, &i);
             return JS::NumberValue(uint32_t(i));
         }
         case LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE: {
-            if (mBoundReadFramebuffer) {
-                nsCString fbStatusInfoIgnored;
-                const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(&fbStatusInfoIgnored);
-                if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-                    ErrorInvalidOperation("getParameter: Read framebuffer must be"
-                                          " complete before querying"
-                                          " IMPLEMENTATION_COLOR_READ_TYPE.");
-                    return JS::NullValue();
-                }
-            }
+            const webgl::FormatUsageInfo* usage;
+            uint32_t width, height;
+            GLenum mode;
+            if (!ValidateCurFBForRead(funcName, &usage, &width, &height, &mode))
+                return JS::NullValue();
 
             GLint i = 0;
             if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
                 gl->fGetIntegerv(pname, &i);
             } else {
                 i = LOCAL_GL_UNSIGNED_BYTE;
             }
 
             return JS::NumberValue(uint32_t(i));
         }
         case LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT: {
-            if (mBoundReadFramebuffer) {
-                nsCString fbStatusInfoIgnored;
-                const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(&fbStatusInfoIgnored);
-                if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-                    ErrorInvalidOperation("getParameter: Read framebuffer must be"
-                                          " complete before querying"
-                                          " IMPLEMENTATION_COLOR_READ_FORMAT.");
-                    return JS::NullValue();
-                }
-            }
+            const webgl::FormatUsageInfo* usage;
+            uint32_t width, height;
+            GLenum mode;
+            if (!ValidateCurFBForRead(funcName, &usage, &width, &height, &mode))
+                return JS::NullValue();
 
             GLint i = 0;
             if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
                 gl->fGetIntegerv(pname, &i);
             } else {
                 i = LOCAL_GL_RGBA;
             }
 
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -679,31 +679,40 @@ WebGLContext::AssertCachedBindings()
 
     GetAndFlushUnderlyingGLErrors();
 
     if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
         GLuint bound = mBoundVertexArray ? mBoundVertexArray->GLName() : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING, bound);
     }
 
-    // Bound object state
+    // Framebuffers
     if (IsWebGL2()) {
         GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->mGLName
                                              : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, bound);
 
         bound = mBoundReadFramebuffer ? mBoundReadFramebuffer->mGLName : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_READ_FRAMEBUFFER_BINDING, bound);
     } else {
         MOZ_ASSERT(mBoundDrawFramebuffer == mBoundReadFramebuffer);
         GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->mGLName
                                              : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_FRAMEBUFFER_BINDING, bound);
     }
 
+    GLint stencilBits = 0;
+    if (GetStencilBits(&stencilBits)) { // Depends on current draw framebuffer.
+        const GLuint stencilRefMask = (1 << stencilBits) - 1;
+
+        AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_REF,      stencilRefMask, mStencilRefFront);
+        AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_REF, stencilRefMask, mStencilRefBack);
+    }
+
+    // Program
     GLuint bound = mCurrentProgram ? mCurrentProgram->mGLName : 0;
     AssertUintParamCorrect(gl, LOCAL_GL_CURRENT_PROGRAM, bound);
 
     // Textures
     GLenum activeTexture = mActiveTexture + LOCAL_GL_TEXTURE0;
     AssertUintParamCorrect(gl, LOCAL_GL_ACTIVE_TEXTURE, activeTexture);
 
     WebGLTexture* curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_2D);
@@ -725,17 +734,17 @@ WebGLContext::AssertCachedBindings()
 
     MOZ_ASSERT(!GetAndFlushUnderlyingGLErrors());
 #endif
 
     // We do not check the renderbuffer binding, because we never rely on it matching.
 }
 
 void
-WebGLContext::AssertCachedState()
+WebGLContext::AssertCachedGlobalState()
 {
 #ifdef DEBUG
     MakeContextCurrent();
 
     GetAndFlushUnderlyingGLErrors();
 
     ////////////////
 
@@ -766,24 +775,16 @@ WebGLContext::AssertCachedState()
     MOZ_ASSERT(depthWriteMask == mDepthWriteMask);
 
     GLfloat depthClearValue = 0.0f;
     gl->fGetFloatv(LOCAL_GL_DEPTH_CLEAR_VALUE, &depthClearValue);
     MOZ_ASSERT(IsCacheCorrect(mDepthClearValue, depthClearValue));
 
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_CLEAR_VALUE, mStencilClearValue);
 
-    GLint stencilBits = 0;
-    if (GetStencilBits(&stencilBits)) {
-        const GLuint stencilRefMask = (1 << stencilBits) - 1;
-
-        AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_REF,      stencilRefMask, mStencilRefFront);
-        AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_REF, stencilRefMask, mStencilRefBack);
-    }
-
     // GLES 3.0.4, $4.1.4, p177:
     //   [...] the front and back stencil mask are both set to the value `2^s - 1`, where
     //   `s` is greater than or equal to the number of bits in the deepest stencil buffer
     //   supported by the GL implementation.
     const int maxStencilBits = 8;
     const GLuint maxStencilBitsMask = (1 << maxStencilBits) - 1;
     AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_VALUE_MASK,      maxStencilBitsMask, mStencilValueMaskFront);
     AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_VALUE_MASK, maxStencilBitsMask, mStencilValueMaskBack);
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -252,16 +252,18 @@ public:
     const WebGLFBAttachPoint& DepthStencilAttachment() const {
         return mDepthStencilAttachment;
     }
 
     void SetReadBufferMode(GLenum readBufferMode) {
         mReadBufferMode = readBufferMode;
     }
 
+    GLenum ReadBufferMode() const { return mReadBufferMode; }
+
 protected:
     WebGLFBAttachPoint* GetAttachPoint(GLenum attachment); // Fallible
 
 public:
     void DetachTexture(const WebGLTexture* tex);
 
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
--- a/dom/canvas/WebGLTexelConversions.cpp
+++ b/dom/canvas/WebGLTexelConversions.cpp
@@ -344,18 +344,21 @@ public:
 
 } // end anonymous namespace
 
 bool
 ConvertImage(size_t width, size_t height,
              const void* srcBegin, size_t srcStride, gl::OriginPos srcOrigin,
              WebGLTexelFormat srcFormat, bool srcPremultiplied,
              void* dstBegin, size_t dstStride, gl::OriginPos dstOrigin,
-             WebGLTexelFormat dstFormat, bool dstPremultiplied)
+             WebGLTexelFormat dstFormat, bool dstPremultiplied,
+             bool* const out_wasTrivial)
 {
+    *out_wasTrivial = true;
+
     if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion ||
         dstFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion)
     {
         return false;
     }
 
     if (!width || !height)
         return true;
@@ -405,16 +408,18 @@ ConvertImage(size_t width, size_t height
         while (srcItr != srcEnd) {
             memcpy(dstItr, srcItr, bytesPerRow);
             srcItr += srcStride;
             dstItr += dstItrStride;
         }
         return true;
     }
 
+    *out_wasTrivial = false;
+
     WebGLImageConverter converter(width, height, srcItr, dstItr, srcStride, dstItrStride);
     converter.run(srcFormat, dstFormat, premultOp);
 
     if (!converter.Success()) {
         // the dst image may be left uninitialized, so we better not try to
         // continue even in release builds. This should never happen anyway,
         // and would be a bug in our code.
         NS_RUNTIMEABORT("programming mistake in WebGL texture conversions");
--- a/dom/canvas/WebGLTexelConversions.h
+++ b/dom/canvas/WebGLTexelConversions.h
@@ -38,17 +38,18 @@
 #include "mozilla/Casting.h"
 
 namespace mozilla {
 
 bool ConvertImage(size_t width, size_t height,
                   const void* srcBegin, size_t srcStride, gl::OriginPos srcOrigin,
                   WebGLTexelFormat srcFormat, bool srcPremultiplied,
                   void* dstBegin, size_t dstStride, gl::OriginPos dstOrigin,
-                  WebGLTexelFormat dstFormat, bool dstPremultiplied);
+                  WebGLTexelFormat dstFormat, bool dstPremultiplied,
+                  bool* out_wasTrivial);
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 // single precision float
 // seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm
 
 // half precision float
 // seeeeemmmmmmmmmm
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1333,16 +1333,29 @@ EventListenerManager::HandleEventInterna
 
 void
 EventListenerManager::Disconnect()
 {
   mTarget = nullptr;
   RemoveAllListeners();
 }
 
+static EventListenerFlags
+GetEventListenerFlagsFromOptions(const EventListenerOptions& aOptions)
+{
+  EventListenerFlags flags;
+  flags.mCapture = aOptions.mCapture;
+  if (aOptions.mMozSystemGroup) {
+    JSContext* cx = nsContentUtils::GetCurrentJSContext();
+    MOZ_ASSERT(cx, "Not being called from JS?");
+    flags.mInSystemGroup = IsChromeOrXBL(cx, nullptr);
+  }
+  return flags;
+}
+
 void
 EventListenerManager::AddEventListener(
                         const nsAString& aType,
                         const EventListenerHolder& aListenerHolder,
                         bool aUseCapture,
                         bool aWantsUntrusted)
 {
   EventListenerFlags flags;
@@ -1357,18 +1370,19 @@ EventListenerManager::AddEventListener(
                         const EventListenerHolder& aListenerHolder,
                         const dom::AddEventListenerOptionsOrBoolean& aOptions,
                         bool aWantsUntrusted)
 {
   EventListenerFlags flags;
   if (aOptions.IsBoolean()) {
     flags.mCapture = aOptions.GetAsBoolean();
   } else {
-    flags.mCapture = aOptions.GetAsAddEventListenerOptions().mCapture;
-    flags.mPassive = aOptions.GetAsAddEventListenerOptions().mPassive;
+    const auto& options = aOptions.GetAsAddEventListenerOptions();
+    flags = GetEventListenerFlagsFromOptions(options);
+    flags.mPassive = options.mPassive;
   }
   flags.mAllowUntrustedEvents = aWantsUntrusted;
   return AddEventListenerByType(aListenerHolder, aType, flags);
 }
 
 void
 EventListenerManager::RemoveEventListener(
                         const nsAString& aType,
@@ -1382,19 +1396,22 @@ EventListenerManager::RemoveEventListene
 
 void
 EventListenerManager::RemoveEventListener(
                         const nsAString& aType,
                         const EventListenerHolder& aListenerHolder,
                         const dom::EventListenerOptionsOrBoolean& aOptions)
 {
   EventListenerFlags flags;
-  flags.mCapture =
-    aOptions.IsBoolean() ? aOptions.GetAsBoolean()
-                         : aOptions.GetAsEventListenerOptions().mCapture;
+  if (aOptions.IsBoolean()) {
+    flags.mCapture = aOptions.GetAsBoolean();
+  } else {
+    const auto& options = aOptions.GetAsEventListenerOptions();
+    flags = GetEventListenerFlagsFromOptions(options);
+  }
   RemoveEventListenerByType(aListenerHolder, aType, flags);
 }
 
 void
 EventListenerManager::AddListenerForAllEvents(nsIDOMEventListener* aDOMListener,
                                               bool aUseCapture,
                                               bool aWantsUntrusted,
                                               bool aSystemEventGroup)
--- a/dom/filesystem/tests/test_basic.html
+++ b/dom/filesystem/tests/test_basic.html
@@ -72,16 +72,65 @@ function test_duplicateGetFilesAndDirect
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
   script.sendAsyncMessage("dir.open", { path: 'test' });
 }
 
+function test_inputGetFiles() {
+  var url = SimpleTest.getTestFileURL("script_fileList.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    var fileList = document.getElementById('fileList');
+    SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+
+    fileList.getFilesAndDirectories()
+    .then(function(result) {
+       is(result.length, 1, "getFilesAndDirectories should return 1 element");
+       ok(result[0] instanceof Directory, "getFilesAndDirectories should return 1 directory");
+
+      return fileList.getFiles(false);
+    })
+    .then(function(result) {
+      is(result.length, 1, "getFiles should return 1 element");
+      ok(result[0] instanceof File, "getFile should return 1 file");
+      is(result[0].name, 'foo.txt', "getFiles()[0].name should be 'foo.txt'");
+      is(result[0].webkitRelativePath, '/foo.txt', "getFiles()[0].webkitRelativePath should be '/foo.txt'");
+
+      return fileList.getFiles(true);
+    })
+    .then(function(result) {
+      is(result.length, 2, "getFiles should return 2 elements");
+
+      function checkFile(file) {
+        ok(file instanceof File, "getFile[x] should return a file");
+        if (file.name == 'foo.txt') {
+          is(file.webkitRelativePath, '/foo.txt', "getFiles()[x].webkitRelativePath should be '/foo.txt'");
+        } else {
+          is(file.name, 'bar.txt', "getFiles()[x].name should be 'bar.txt'");
+          is(file.webkitRelativePath, '/subdir/bar.txt', "getFiles()[x].webkitRelativePath should be '/subdir/bar.txt'");
+        }
+      }
+
+      checkFile(result[0]);
+      checkFile(result[1]);
+    })
+    .then(function() {
+      script.destroy();
+      next();
+    });
+  }
+
+  script.addMessageListener("dir.opened", onOpened);
+  script.sendAsyncMessage("dir.open", { path: 'test' });
+}
+
 var tests = [
   function() { setup_tests(next); },
 
   function() { create_fileList('ProfD') },
   function() { test_basic(directory, next); },
   function() { test_getFilesAndDirectories(directory, true, next); },
   function() { test_getFiles(directory, false, next); },
   function() { test_getFiles(directory, true, next); },
@@ -90,16 +139,17 @@ var tests = [
   function() { test_getFiles_recursiveComparison(directory, next); },
 
   function() { create_fileList('root'); },
   function() { test_basic(directory, next); },
   function() { test_getFilesAndDirectories(directory, false, next); },
   function() { test_getFiles(directory, false, next); },
 
   test_duplicateGetFilesAndDirectories,
+  test_inputGetFiles,
   test_simpleFilePicker
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -213,16 +213,331 @@ const Decimal HTMLInputElement::kStepAny
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
 
 #define PROGRESS_STR "progress"
 static const uint32_t kProgressEventInterval = 50; // ms
 
+// Retrieving the list of files can be very time/IO consuming. We use this
+// helper class to do it just once.
+class GetFilesHelper final : public Runnable
+{
+public:
+  static already_AddRefed<GetFilesHelper>
+  Create(nsIGlobalObject* aGlobal,
+         const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+         bool aRecursiveFlag, ErrorResult& aRv)
+  {
+    MOZ_ASSERT(aGlobal);
+
+    RefPtr<GetFilesHelper> helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
+
+    nsAutoString directoryPath;
+
+    for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
+      const OwningFileOrDirectory& data = aFilesOrDirectory[i];
+      if (data.IsFile()) {
+        if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
+          aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+          return nullptr;
+        }
+      } else {
+        MOZ_ASSERT(data.IsDirectory());
+
+        // We support the upload of only 1 top-level directory from our
+        // directory picker. This means that we cannot have more than 1
+        // Directory object in aFilesOrDirectory array.
+        MOZ_ASSERT(directoryPath.IsEmpty());
+
+        RefPtr<Directory> directory = data.GetAsDirectory();
+        MOZ_ASSERT(directory);
+
+        aRv = directory->GetFullRealPath(directoryPath);
+        if (NS_WARN_IF(aRv.Failed())) {
+          return nullptr;
+        }
+      }
+    }
+
+    // No directories to explore.
+    if (directoryPath.IsEmpty()) {
+      helper->mListingCompleted = true;
+      return helper.forget();
+    }
+
+    MOZ_ASSERT(helper->mFiles.IsEmpty());
+    helper->SetDirectoryPath(directoryPath);
+
+    nsCOMPtr<nsIEventTarget> target =
+      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    MOZ_ASSERT(target);
+
+    aRv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    return helper.forget();
+  }
+
+  void
+  AddPromise(Promise* aPromise)
+  {
+    MOZ_ASSERT(aPromise);
+
+    // Still working.
+    if (!mListingCompleted) {
+      mPromises.AppendElement(aPromise);
+      return;
+    }
+
+    MOZ_ASSERT(mPromises.IsEmpty());
+    ResolveOrRejectPromise(aPromise);
+  }
+
+  // CC methods
+  void Unlink()
+  {
+    mGlobal = nullptr;
+    mFiles.Clear();
+    mPromises.Clear();
+  }
+
+  void Traverse(nsCycleCollectionTraversalCallback &cb)
+  {
+    GetFilesHelper* tmp = this;
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
+  }
+
+private:
+  GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+    : mGlobal(aGlobal)
+    , mRecursiveFlag(aRecursiveFlag)
+    , mListingCompleted(false)
+    , mErrorResult(NS_OK)
+  {
+    MOZ_ASSERT(aGlobal);
+  }
+
+  void
+  SetDirectoryPath(const nsAString& aDirectoryPath)
+  {
+    mDirectoryPath = aDirectoryPath;
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+    MOZ_ASSERT(!mListingCompleted);
+
+    // First step is to retrieve the list of file paths.
+    // This happens in the I/O thread.
+    if (!NS_IsMainThread()) {
+      RunIO();
+      return NS_DispatchToMainThread(this);
+    }
+
+    RunMainThread();
+
+    // We mark the operation as completed here.
+    mListingCompleted = true;
+
+    // Let's process the pending promises.
+    nsTArray<RefPtr<Promise>> promises;
+    promises.SwapElements(mPromises);
+
+    for (uint32_t i = 0; i < promises.Length(); ++i) {
+      ResolveOrRejectPromise(promises[i]);
+    }
+
+    return NS_OK;
+  }
+
+  void
+  RunIO()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+    MOZ_ASSERT(!mListingCompleted);
+
+    nsCOMPtr<nsIFile> file;
+    mErrorResult = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mDirectoryPath), true,
+                                         getter_AddRefs(file));
+    if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+      return;
+    }
+
+    nsAutoString path;
+    path.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+
+    mErrorResult = ExploreDirectory(path, file);
+  }
+
+  void
+  RunMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+    MOZ_ASSERT(!mListingCompleted);
+
+    // If there is an error, do nothing.
+    if (NS_FAILED(mErrorResult)) {
+      return;
+    }
+
+    // Create the sequence of Files.
+    for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) {
+      nsCOMPtr<nsIFile> file;
+      mErrorResult =
+        NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mTargetPathArray[i].mRealPath),
+                              true, getter_AddRefs(file));
+      if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+        mFiles.Clear();
+        return;
+      }
+
+      RefPtr<File> domFile =
+        File::CreateFromFile(mGlobal, file);
+      MOZ_ASSERT(domFile);
+
+      domFile->SetPath(mTargetPathArray[i].mDomPath);
+
+      if (!mFiles.AppendElement(domFile, fallible)) {
+        mErrorResult = NS_ERROR_OUT_OF_MEMORY;
+        mFiles.Clear();
+        return;
+      }
+    }
+  }
+
+  nsresult
+  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(aFile);
+
+    nsCOMPtr<nsISimpleEnumerator> entries;
+    nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    for (;;) {
+      bool hasMore = false;
+      if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
+        break;
+      }
+
+      nsCOMPtr<nsISupports> supp;
+      if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
+        break;
+      }
+
+      nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
+      MOZ_ASSERT(currFile);
+
+      bool isLink, isSpecial, isFile, isDir;
+      if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
+                     NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+          isLink || isSpecial) {
+        continue;
+      }
+
+      if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+                     NS_FAILED(currFile->IsDirectory(&isDir))) ||
+          !(isFile || isDir)) {
+        continue;
+      }
+
+      // The new domPath
+      nsAutoString domPath;
+      domPath.Assign(aDOMPath);
+      if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
+        domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+      }
+
+      nsAutoString leafName;
+      if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+        continue;
+      }
+      domPath.Append(leafName);
+
+      if (isFile) {
+        FileData* data = mTargetPathArray.AppendElement(fallible);
+        if (!data) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) {
+          continue;
+        }
+
+        data->mDomPath = domPath;
+        continue;
+      }
+
+      MOZ_ASSERT(isDir);
+      if (!mRecursiveFlag) {
+        continue;
+      }
+
+      // Recursive.
+      rv = ExploreDirectory(domPath, currFile);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    return NS_OK;
+  }
+
+  void
+  ResolveOrRejectPromise(Promise* aPromise)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mListingCompleted);
+    MOZ_ASSERT(aPromise);
+
+    // Error propagation.
+    if (NS_FAILED(mErrorResult)) {
+      aPromise->MaybeReject(mErrorResult);
+      return;
+    }
+
+    aPromise->MaybeResolve(mFiles);
+  }
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+
+  bool mRecursiveFlag;
+  bool mListingCompleted;
+  nsString mDirectoryPath;
+
+  // We populate this array in the I/O thread with the paths of the Files that
+  // we want to send as result to the promise objects.
+  struct FileData {
+    nsString mDomPath;
+    nsString mRealPath;
+  };
+  FallibleTArray<FileData> mTargetPathArray;
+
+  // This is the real File sequence that we expose via Promises.
+  Sequence<RefPtr<File>> mFiles;
+
+  // Error code to propagate.
+  nsresult mErrorResult;
+
+  nsTArray<RefPtr<Promise>> mPromises;
+};
+
 class HTMLInputElementState final : public nsISupports
 {
   public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
     NS_DECL_ISUPPORTS
 
     bool IsCheckedSet()
     {
@@ -1047,28 +1362,40 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInput
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
                                                   nsGenericHTMLFormElementWithState)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
   if (tmp->IsSingleLineTextControl(false)) {
     tmp->mInputData.mState->Traverse(cb);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
+
+  if (tmp->mGetFilesRecursiveHelper) {
+    tmp->mGetFilesRecursiveHelper->Traverse(cb);
+  }
+
+  if (tmp->mGetFilesNonRecursiveHelper) {
+    tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
+  }
+
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
                                                 nsGenericHTMLFormElementWithState)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
   if (tmp->IsSingleLineTextControl(false)) {
     tmp->mInputData.mState->Unlink();
   }
+
+  tmp->ClearGetFilesHelpers();
+
   //XXX should unlink more?
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element)
 
 // QueryInterface implementation for HTMLInputElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
@@ -1112,16 +1439,17 @@ HTMLInputElement::Clone(mozilla::dom::No
       }
       break;
     case VALUE_MODE_FILENAME:
       if (it->OwnerDoc()->IsStaticDocument()) {
         // We're going to be used in print preview.  Since the doc is static
         // we can just grab the pretty string and use it as wallpaper
         GetDisplayFileName(it->mStaticDocFileList);
       } else {
+        it->ClearGetFilesHelpers();
         it->mFilesOrDirectories.Clear();
         it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
       }
       break;
     case VALUE_MODE_DEFAULT_ON:
       if (mCheckedChanged) {
         // We no longer have our original checked state.  Set our
         // checked state on the clone.
@@ -2557,28 +2885,31 @@ HTMLInputElement::GetDisplayFileName(nsA
 
   aValue = value;
 }
 
 void
 HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
                                         bool aSetValueChanged)
 {
+  ClearGetFilesHelpers();
+
   mFilesOrDirectories.Clear();
   mFilesOrDirectories.AppendElements(aFilesOrDirectories);
 
   AfterSetFilesOrDirectories(aSetValueChanged);
 }
 
 void
 HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
                            bool aSetValueChanged)
 {
   RefPtr<FileList> files = static_cast<FileList*>(aFiles);
   mFilesOrDirectories.Clear();
+  ClearGetFilesHelpers();
 
   if (aFiles) {
     uint32_t listLength;
     aFiles->GetLength(&listLength);
     for (uint32_t i = 0; i < listLength; i++) {
       OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
       element->SetAsFile() = files->Item(i);
     }
@@ -5076,16 +5407,68 @@ HTMLInputElement::GetFilesAndDirectories
       filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
     }
   }
 
   p->MaybeResolve(filesAndDirsSeq);
   return p.forget();
 }
 
+already_AddRefed<Promise>
+HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
+{
+  if (mType != NS_FORM_INPUT_FILE) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+  MOZ_ASSERT(global);
+  if (!global) {
+    return nullptr;
+  }
+
+  RefPtr<GetFilesHelper> helper;
+  if (aRecursiveFlag) {
+    if (!mGetFilesRecursiveHelper) {
+      mGetFilesRecursiveHelper =
+       GetFilesHelper::Create(global,
+                              GetFilesOrDirectoriesInternal(),
+                              aRecursiveFlag, aRv);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+    }
+
+    helper = mGetFilesRecursiveHelper;
+  } else {
+    if (!mGetFilesNonRecursiveHelper) {
+      mGetFilesNonRecursiveHelper =
+       GetFilesHelper::Create(global,
+                              GetFilesOrDirectoriesInternal(),
+                              aRecursiveFlag, aRv);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+    }
+
+    helper = mGetFilesNonRecursiveHelper;
+  }
+
+  MOZ_ASSERT(helper);
+
+  RefPtr<Promise> p = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  helper->AddPromise(p);
+  return p.forget();
+}
+
 
 // Controllers Methods
 
 nsIControllers*
 HTMLInputElement::GetControllers(ErrorResult& aRv)
 {
   //XXX: what about type "file"?
   if (IsSingleLineTextControl(false))
@@ -7573,12 +7956,26 @@ HTMLInputElement::PickerClosed()
 }
 
 JSObject*
 HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+HTMLInputElement::ClearGetFilesHelpers()
+{
+  if (mGetFilesRecursiveHelper) {
+    mGetFilesRecursiveHelper->Unlink();
+    mGetFilesRecursiveHelper = nullptr;
+  }
+
+  if (mGetFilesNonRecursiveHelper) {
+    mGetFilesNonRecursiveHelper->Unlink();
+    mGetFilesNonRecursiveHelper = nullptr;
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
 
 #undef NS_ORIGINAL_CHECKED_VALUE
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -35,16 +35,17 @@ namespace mozilla {
 class EventChainPostVisitor;
 class EventChainPreVisitor;
 
 namespace dom {
 
 class Date;
 class File;
 class FileList;
+class GetFilesHelper;
 
 /**
  * A class we use to create a singleton object that is used to keep track of
  * the last directory from which the user has picked files (via
  * <input type=file>) on a per-domain basis. The implementation uses
  * nsIContentPrefService2/NS_CONTENT_PREF_SERVICE_CONTRACTID to store the last
  * directory per-domain, and to ensure that whether the directories are
  * persistently saved (saved across sessions) or not honors whether or not the
@@ -698,16 +699,18 @@ public:
   {
     SetHTMLBoolAttr(nsGkAtoms::directory, aValue, aRv);
   }
 
   bool IsFilesAndDirectoriesSupported() const;
 
   already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
 
+  already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
+
   void ChooseDirectory(ErrorResult& aRv);
 
   // XPCOM GetAlign() is OK
   void SetAlign(const nsAString& aValue, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::align, aValue, aRv);
   }
 
@@ -1248,16 +1251,18 @@ protected:
    * Use this function before trying to open a picker.
    * It checks if the page is allowed to open a new pop-up.
    * If it returns true, you should not create the picker.
    *
    * @return true if popup should be blocked, false otherwise
    */
   bool IsPopupBlocked() const;
 
+  void ClearGetFilesHelpers();
+
   nsCOMPtr<nsIControllers> mControllers;
 
   /*
    * In mInputData, the mState field is used if IsSingleLineTextControl returns
    * true and mValue is used otherwise.  We have to be careful when handling it
    * on a type change.
    *
    * Accessing the mState member should be done using the GetEditorState function,
@@ -1281,16 +1286,19 @@ protected:
    * value from a text-input to a file-input. Additionally, the logic for this
    * value is kept as simple as possible to avoid accidental errors where the
    * wrong filename is used.  Therefor the list of filenames is always owned by
    * this member, never by the frame. Whenever the frame wants to change the
    * filename it has to call SetFilesOrDirectories to update this member.
    */
   nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
 
+  RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
+  RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
+
 #ifndef MOZ_CHILD_PERMISSIONS
   /**
    * Hack for bug 1086684: Stash the .value when we're a file picker.
    */
   nsString mFirstFilePath;
 #endif
 
   RefPtr<FileList>  mFileList;
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2797,28 +2797,16 @@ nsGenericHTMLElement::DispatchSimulatedC
                                              nsPresContext* aPresContext)
 {
   WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
                          WidgetMouseEvent::eReal);
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
   return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event);
 }
 
-const nsAttrName*
-nsGenericHTMLElement::InternalGetExistingAttrNameFromQName(const nsAString& aStr) const
-{
-  if (IsInHTMLDocument()) {
-    nsAutoString lower;
-    nsContentUtils::ASCIIToLower(aStr, lower);
-    return mAttrsAndChildren.GetExistingAttrNameFromQName(lower);
-  }
-
-  return mAttrsAndChildren.GetExistingAttrNameFromQName(aStr);
-}
-
 nsresult
 nsGenericHTMLElement::GetEditor(nsIEditor** aEditor)
 {
   *aEditor = nullptr;
 
   // See also HTMLTextFieldAccessible::GetEditor.
   if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
     return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -932,18 +932,16 @@ private:
 protected:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   virtual mozilla::EventListenerManager*
     GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                    bool* aDefer) override;
 
-  virtual const nsAttrName* InternalGetExistingAttrNameFromQName(const nsAString& aStr) const override;
-
   /**
    * Dispatch a simulated mouse click by keyboard to the given element.
    */
   nsresult DispatchSimulatedClick(nsGenericHTMLElement* aElement,
                                   bool aIsTrusted,
                                   nsPresContext* aPresContext);
 
   /**
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -180,16 +180,29 @@ interface nsIServiceWorkerManager : nsIS
                                   in AString aTitle,
                                   in AString aDir,
                                   in AString aLang,
                                   in AString aBody,
                                   in AString aTag,
                                   in AString aIcon,
                                   in AString aData,
                                   in AString aBehavior);
+
+  void sendNotificationCloseEvent(in ACString aOriginSuffix,
+                                  in ACString scope,
+                                  in AString aID,
+                                  in AString aTitle,
+                                  in AString aDir,
+                                  in AString aLang,
+                                  in AString aBody,
+                                  in AString aTag,
+                                  in AString aIcon,
+                                  in AString aData,
+                                  in AString aBehavior);
+
   [optional_argc] void sendPushEvent(in ACString aOriginAttributes,
                                      in ACString aScope,
                                      [optional] in uint32_t aDataLength,
                                      [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
                                        in ACString scope);
 
   void addListener(in nsIServiceWorkerManagerListener aListener);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5534,20 +5534,23 @@ ContentParent::RecvCreateWindow(PBrowser
     }
 
     return true;
   }
 
   nsCOMPtr<mozIDOMWindowProxy> window;
   TabParent::AutoUseNewTab aunt(newTab, aWindowIsNew, aURLToLoad);
 
-  const char* name = aName.IsVoid() ? nullptr : NS_ConvertUTF16toUTF8(aName).get();
   const char* features = aFeatures.IsVoid() ? nullptr : aFeatures.get();
 
-  *aResult = pwwatch->OpenWindow2(parent, nullptr, name, features, aCalledFromJS,
+  *aResult = pwwatch->OpenWindow2(parent, nullptr,
+                                  aName.IsVoid() ?
+                                    nullptr :
+                                    NS_ConvertUTF16toUTF8(aName).get(),
+                                  features, aCalledFromJS,
                                   false, false, thisTabParent, nullptr,
                                   aFullZoom, 1, getter_AddRefs(window));
 
   if (NS_WARN_IF(!window)) {
     return true;
   }
 
   *aResult = NS_ERROR_FAILURE;
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -224,8 +224,9 @@ TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
 RewriteYoutubeEmbed=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
 # LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port
 RewriteYoutubeEmbedInvalidQuery=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Query was invalid and removed from URL. Please update page to use iframe instead of embed/object, if possible.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
 PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
 # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
 PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
 NavigatorBatteryWarning=navigator.battery is deprecated. Use navigator.getBattery() instead.
+FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -310,26 +310,29 @@ struct ToCubebFormat {
   static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
 };
 
 template <>
 struct ToCubebFormat<AUDIO_FORMAT_S16> {
   static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
 };
 
+template <typename Function, typename... Args>
+int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs)
+{
+  MonitorAutoUnlock mon(mMonitor);
+  return aFunction(mCubebStream.get(), Forward<Args>(aArgs)...);
+}
+
 nsresult
 AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
                   const dom::AudioChannel aAudioChannel)
 {
   auto startTime = TimeStamp::Now();
-  mIsFirst = CubebUtils::GetFirstStream();
-
-  if (!CubebUtils::GetCubebContext()) {
-    return NS_ERROR_FAILURE;
-  }
+  auto isFirst = CubebUtils::GetFirstStream();
 
   LOG("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate);
   mInRate = mOutRate = aRate;
   mChannels = aNumChannels;
   mOutChannels = aNumChannels;
 
   mDumpFile = OpenDumpFile(this);
 
@@ -346,59 +349,44 @@ AudioStream::Init(uint32_t aNumChannels,
   if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
     return NS_ERROR_INVALID_ARG;
   }
 #endif
 
   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
   mAudioClock.Init();
 
-  return OpenCubeb(params, startTime);
+  return OpenCubeb(params, startTime, isFirst);
 }
 
-// This code used to live inside AudioStream::Init(), but on Mac (others?)
-// it has been known to take 300-800 (or even 8500) ms to execute(!)
 nsresult
-AudioStream::OpenCubeb(cubeb_stream_params &aParams, TimeStamp aStartTime)
+AudioStream::OpenCubeb(cubeb_stream_params& aParams,
+                       TimeStamp aStartTime, bool aIsFirst)
 {
   cubeb* cubebContext = CubebUtils::GetCubebContext();
   if (!cubebContext) {
     NS_WARNING("Can't get cubeb context!");
-    MonitorAutoLock mon(mMonitor);
-    mState = AudioStream::ERRORED;
     return NS_ERROR_FAILURE;
   }
 
-  // If the latency pref is set, use it. Otherwise, if this stream is intended
-  // for low latency playback, try to get the lowest latency possible.
-  // Otherwise, for normal streams, use 100ms.
-  uint32_t latency = CubebUtils::GetCubebLatency();
-
-  {
-    cubeb_stream* stream;
-    if (cubeb_stream_init(cubebContext, &stream, "AudioStream",
-                          nullptr, nullptr, nullptr, &aParams,
-                          latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
-      MonitorAutoLock mon(mMonitor);
-      MOZ_ASSERT(mState != SHUTDOWN);
-      mCubebStream.reset(stream);
-    } else {
-      MonitorAutoLock mon(mMonitor);
-      mState = ERRORED;
-      NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
-      return NS_ERROR_FAILURE;
-    }
+  cubeb_stream* stream = nullptr;
+  if (cubeb_stream_init(cubebContext, &stream, "AudioStream",
+                        nullptr, nullptr, nullptr, &aParams,
+                        CubebUtils::GetCubebLatency(),
+                        DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
+    mCubebStream.reset(stream);
+  } else {
+    NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
+    return NS_ERROR_FAILURE;
   }
 
-  mState = INITIALIZED;
-
   TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
-  LOG("creation time %sfirst: %u ms", mIsFirst ? "" : "not ",
+  LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
       (uint32_t) timeDelta.ToMilliseconds());
-  Telemetry::Accumulate(mIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS :
+  Telemetry::Accumulate(aIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS :
       Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds());
 
   return NS_OK;
 }
 
 void
 AudioStream::SetVolume(double aVolume)
 {
@@ -408,70 +396,51 @@ AudioStream::SetVolume(double aVolume)
     NS_WARNING("Could not change volume on cubeb stream.");
   }
 }
 
 void
 AudioStream::Start()
 {
   MonitorAutoLock mon(mMonitor);
-  if (mState == INITIALIZED) {
-    mState = STARTED;
-    int r;
-    {
-      MonitorAutoUnlock mon(mMonitor);
-      r = cubeb_stream_start(mCubebStream.get());
-      // DataCallback might be called before we exit this scope
-      // if cubeb_stream_start() succeeds. mState must be set to STARTED
-      // beforehand.
-    }
-    if (r != CUBEB_OK) {
-      mState = ERRORED;
-    }
-    LOG("started, state %s", mState == STARTED ? "STARTED" : "ERRORED");
-  }
+  MOZ_ASSERT(mState == INITIALIZED);
+  auto r = InvokeCubeb(cubeb_stream_start);
+  mState = r == CUBEB_OK ? STARTED : ERRORED;
+  LOG("started, state %s", mState == STARTED ? "STARTED" : "ERRORED");
 }
 
 void
 AudioStream::Pause()
 {
   MonitorAutoLock mon(mMonitor);
 
   if (mState == ERRORED) {
     return;
   }
 
-  if (mState != STARTED && mState != RUNNING) {
+  if (mState != STARTED) {
     mState = STOPPED; // which also tells async OpenCubeb not to start, just init
     return;
   }
 
-  int r;
-  {
-    MonitorAutoUnlock mon(mMonitor);
-    r = cubeb_stream_stop(mCubebStream.get());
-  }
+  int r = InvokeCubeb(cubeb_stream_stop);
   if (mState != ERRORED && r == CUBEB_OK) {
     mState = STOPPED;
   }
 }
 
 void
 AudioStream::Resume()
 {
   MonitorAutoLock mon(mMonitor);
   if (mState != STOPPED) {
     return;
   }
 
-  int r;
-  {
-    MonitorAutoUnlock mon(mMonitor);
-    r = cubeb_stream_start(mCubebStream.get());
-  }
+  int r = InvokeCubeb(cubeb_stream_start);
   if (mState != ERRORED && r == CUBEB_OK) {
     mState = STARTED;
   }
 }
 
 void
 AudioStream::Shutdown()
 {
@@ -509,23 +478,19 @@ AudioStream::GetPositionInFramesUnlocked
 {
   mMonitor.AssertCurrentThreadOwns();
 
   if (mState == ERRORED) {
     return -1;
   }
 
   uint64_t position = 0;
-  {
-    MonitorAutoUnlock mon(mMonitor);
-    if (cubeb_stream_get_position(mCubebStream.get(), &position) != CUBEB_OK) {
-      return -1;
-    }
+  if (InvokeCubeb(cubeb_stream_get_position, &position) != CUBEB_OK) {
+    return -1;
   }
-
   return std::min<uint64_t>(position, INT64_MAX);
 }
 
 bool
 AudioStream::IsPaused()
 {
   MonitorAutoLock mon(mMonitor);
   return mState == STOPPED;
@@ -631,21 +596,16 @@ AudioStream::DataCallback(void* aBuffer,
     NS_WARNING("data callback fires before cubeb_stream_start() is called");
     mAudioClock.UpdateFrameHistory(0, aFrames);
     return writer.WriteZeros(aFrames);
   }
 
   // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
   // Bug 996162
 
-  // callback tells us cubeb succeeded initializing
-  if (mState == STARTED) {
-    mState = RUNNING;
-  }
-
   if (mInRate == mOutRate) {
     GetUnprocessed(writer);
   } else {
     GetTimeStretched(writer);
   }
 
   // Always send audible frames first, and silent frames later.
   // Otherwise it will break the assumption of FrameHistory.
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -308,17 +308,18 @@ protected:
 
   // Return the position, measured in audio frames played since the stream was
   // opened, of the audio hardware, not adjusted for the changes of playback
   // rate or underrun frames.
   // Caller must own the monitor.
   int64_t GetPositionInFramesUnlocked();
 
 private:
-  nsresult OpenCubeb(cubeb_stream_params &aParams, TimeStamp aStartTime);
+  nsresult OpenCubeb(cubeb_stream_params& aParams,
+                     TimeStamp aStartTime, bool aIsFirst);
 
   static long DataCallback_S(cubeb_stream*, void* aThis,
                              const void* /* aInputBuffer */, void* aOutputBuffer,
                              long aFrames)
   {
     return static_cast<AudioStream*>(aThis)->DataCallback(aOutputBuffer, aFrames);
   }
 
@@ -335,16 +336,19 @@ private:
 
   // Return true if audio frames are valid (correct sampling rate and valid
   // channel count) otherwise false.
   bool IsValidAudioFormat(Chunk* aChunk);
 
   void GetUnprocessed(AudioBufferWriter& aWriter);
   void GetTimeStretched(AudioBufferWriter& aWriter);
 
+  template <typename Function, typename... Args>
+  int InvokeCubeb(Function aFunction, Args&&... aArgs);
+
   // The monitor is held to protect all access to member variables.
   Monitor mMonitor;
 
   // Input rate in Hz (characteristic of the media being played)
   uint32_t mInRate;
   // Output rate in Hz (characteristic of the playback rate)
   uint32_t mOutRate;
   uint32_t mChannels;
@@ -355,25 +359,23 @@ private:
   // Output file for dumping audio
   FILE* mDumpFile;
 
   // Owning reference to a cubeb_stream.
   UniquePtr<cubeb_stream, CubebDestroyPolicy> mCubebStream;
 
   enum StreamState {
     INITIALIZED, // Initialized, playback has not begun.
-    STARTED,     // cubeb started, but callbacks haven't started
-    RUNNING,     // DataCallbacks have started after STARTED, or after Resume().
+    STARTED,     // cubeb started.
     STOPPED,     // Stopped by a call to Pause().
     DRAINED,     // StateCallback has indicated that the drain is complete.
     ERRORED,     // Stream disabled due to an internal error.
     SHUTDOWN     // Shutdown has been called
   };
 
   StreamState mState;
-  bool mIsFirst;
 
   DataSource& mDataSource;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -148,64 +148,66 @@ WMFVideoMFTManager::GetMediaSubtypeGUID(
   switch (mStreamType) {
     case H264: return MFVideoFormat_H264;
     case VP8: return MFVideoFormat_VP80;
     case VP9: return MFVideoFormat_VP90;
     default: return GUID_NULL;
   };
 }
 
-struct D3D11BlacklistingCache
+struct D3DDLLBlacklistingCache
 {
-  // D3D11-blacklist pref last seen.
+  // Blacklist pref value last seen.
   nsCString mBlacklistPref;
-  // Non-empty if a D3D11-blacklisted DLL was found.
+  // Non-empty if a blacklisted DLL was found.
   nsCString mBlacklistedDLL;
 };
-StaticAutoPtr<D3D11BlacklistingCache> sD3D11BlacklistingCache;
+StaticAutoPtr<D3DDLLBlacklistingCache> sD3D11BlacklistingCache;
+StaticAutoPtr<D3DDLLBlacklistingCache> sD3D9BlacklistingCache;
 
 // If a blacklisted DLL is found, return its information, otherwise "".
 static const nsACString&
-FindD3D11BlacklistedDLL()
+FindDXVABlacklistedDLL(StaticAutoPtr<D3DDLLBlacklistingCache>& aDLLBlacklistingCache,
+                       const char* aDLLBlacklistPrefName)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
 
-  if (!sD3D11BlacklistingCache) {
+  if (!aDLLBlacklistingCache) {
     // First time here, create persistent data that will be reused in all
     // D3D11-blacklisting checks.
-    sD3D11BlacklistingCache = new D3D11BlacklistingCache();
-    ClearOnShutdown(&sD3D11BlacklistingCache);
+    aDLLBlacklistingCache = new D3DDLLBlacklistingCache();
+    ClearOnShutdown(&aDLLBlacklistingCache);
   }
 
-  nsAdoptingCString blacklist =
-    Preferences::GetCString("media.wmf.disable-d3d11-for-dlls");
+  nsAdoptingCString blacklist = Preferences::GetCString(aDLLBlacklistPrefName);
   if (blacklist.IsEmpty()) {
     // Empty blacklist -> No blacklisting.
-    sD3D11BlacklistingCache->mBlacklistPref.SetLength(0);
-    sD3D11BlacklistingCache->mBlacklistedDLL.SetLength(0);
-    return sD3D11BlacklistingCache->mBlacklistedDLL;
+    aDLLBlacklistingCache->mBlacklistPref.SetLength(0);
+    aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
+    return aDLLBlacklistingCache->mBlacklistedDLL;
   }
 
   // Detect changes in pref.
-  if (sD3D11BlacklistingCache->mBlacklistPref.Equals(blacklist)) {
+  if (aDLLBlacklistingCache->mBlacklistPref.Equals(blacklist)) {
     // Same blacklist -> Return same result (i.e., don't check DLLs again).
-    return sD3D11BlacklistingCache->mBlacklistedDLL;
+    return aDLLBlacklistingCache->mBlacklistedDLL;
   }
   // Adopt new pref now, so we don't work on it again.
-  sD3D11BlacklistingCache->mBlacklistPref = blacklist;
+  aDLLBlacklistingCache->mBlacklistPref = blacklist;
 
-  // media.wmf.disable-d3d11-for-dlls format: (whitespace is trimmed)
+  // media.wmf.disable-d3d*-for-dlls format: (whitespace is trimmed)
   // "dll1.dll: 1.2.3.4[, more versions...][; more dlls...]"
   nsTArray<nsCString> dlls;
   SplitAt(";", blacklist, dlls);
   for (const auto& dll : dlls) {
     nsTArray<nsCString> nameAndVersions;
     SplitAt(":", dll, nameAndVersions);
     if (nameAndVersions.Length() != 2) {
-      NS_WARNING("Skipping incorrect 'media.wmf.disable-d3d11-for-dlls' dll:versions format");
+      NS_WARNING(nsPrintfCString("Skipping incorrect '%s' dll:versions format",
+                                 aDLLBlacklistPrefName).get());
       continue;
     }
 
     nameAndVersions[0].CompressWhitespace();
     NS_ConvertUTF8toUTF16 name(nameAndVersions[0]);
     WCHAR systemPath[MAX_PATH + 1];
     if (!ConstructSystem32Path(name.get(), systemPath, MAX_PATH + 1)) {
       // Cannot build path -> Assume it's not the blacklisted DLL.
@@ -230,17 +232,18 @@ FindD3D11BlacklistedDLL()
     }
 
     nsTArray<nsCString> versions;
     SplitAt(",", nameAndVersions[1], versions);
     for (const auto& version : versions) {
       nsTArray<nsCString> numberStrings;
       SplitAt(".", version, numberStrings);
       if (numberStrings.Length() != 4) {
-        NS_WARNING("Skipping incorrect 'media.wmf.disable-d3d11-for-dlls' a.b.c.d version format");
+        NS_WARNING(nsPrintfCString("Skipping incorrect '%s' a.b.c.d version format",
+                                   aDLLBlacklistPrefName).get());
         continue;
       }
       DWORD numbers[4];
       nsresult errorCode = NS_OK;
       for (int i = 0; i < 4; ++i) {
         numberStrings[i].CompressWhitespace();
         numbers[i] = DWORD(numberStrings[i].ToInteger(&errorCode));
         if (NS_FAILED(errorCode)) {
@@ -248,35 +251,48 @@ FindD3D11BlacklistedDLL()
         }
         if (numbers[i] > UINT16_MAX) {
           errorCode = NS_ERROR_FAILURE;
           break;
         }
       }
 
       if (NS_FAILED(errorCode)) {
-        NS_WARNING("Skipping incorrect 'media.wmf.disable-d3d11-for-dlls' a.b.c.d version format");
+        NS_WARNING(nsPrintfCString("Skipping incorrect '%s' a.b.c.d version format",
+                                   aDLLBlacklistPrefName).get());
         continue;
       }
 
       if (vInfo->dwFileVersionMS == ((numbers[0] << 16) | numbers[1])
           && vInfo->dwFileVersionLS == ((numbers[2] << 16) | numbers[3])) {
         // Blacklisted! Record bad DLL.
-        sD3D11BlacklistingCache->mBlacklistedDLL.SetLength(0);
-        sD3D11BlacklistingCache->mBlacklistedDLL.AppendPrintf(
+        aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
+        aDLLBlacklistingCache->mBlacklistedDLL.AppendPrintf(
           "%s (%lu.%lu.%lu.%lu)",
           nameAndVersions[0].get(), numbers[0], numbers[1], numbers[2], numbers[3]);
-        return sD3D11BlacklistingCache->mBlacklistedDLL;
+        return aDLLBlacklistingCache->mBlacklistedDLL;
       }
     }
   }
 
   // No blacklisted DLL.
-  sD3D11BlacklistingCache->mBlacklistedDLL.SetLength(0);
-  return sD3D11BlacklistingCache->mBlacklistedDLL;
+  aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
+  return aDLLBlacklistingCache->mBlacklistedDLL;
+}
+
+static const nsACString&
+FindD3D11BlacklistedDLL() {
+  return FindDXVABlacklistedDLL(sD3D11BlacklistingCache,
+                                "media.wmf.disable-d3d11-for-dlls");
+}
+
+static const nsACString&
+FindD3D9BlacklistedDLL() {
+  return FindDXVABlacklistedDLL(sD3D9BlacklistingCache,
+                                "media.wmf.disable-d3d9-for-dlls");
 }
 
 class CreateDXVAManagerEvent : public Runnable {
 public:
   CreateDXVAManagerEvent(LayersBackend aBackend, nsCString& aFailureReason)
     : mBackend(aBackend)
     , mFailureReason(aFailureReason)
   {}
@@ -297,19 +313,26 @@ public:
           return NS_OK;
         }
       }
       // Try again with d3d9, but record the failure reason
       // into a new var to avoid overwriting the d3d11 failure.
       failureReason = &secondFailureReason;
       mFailureReason.Append(NS_LITERAL_CSTRING("; "));
     }
-    mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(*failureReason);
-    // Make sure we include the messages from both attempts (if applicable).
-    mFailureReason.Append(secondFailureReason);
+
+    const nsACString& blacklistedDLL = FindD3D9BlacklistedDLL();
+    if (!blacklistedDLL.IsEmpty()) {
+      mFailureReason.AppendPrintf("D3D9 blacklisted with DLL %s",
+                                  blacklistedDLL);
+    } else {
+      mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(*failureReason);
+      // Make sure we include the messages from both attempts (if applicable).
+      mFailureReason.Append(secondFailureReason);
+    }
     return NS_OK;
   }
   nsAutoPtr<DXVA2Manager> mDXVA2Manager;
   LayersBackend mBackend;
   nsACString& mFailureReason;
 };
 
 bool
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -1585,55 +1585,71 @@ WorkerNotificationObserver::Observe(nsIS
 
 NS_IMETHODIMP
 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
                                            const char* aTopic,
                                            const char16_t* aData)
 {
   AssertIsOnMainThread();
 
+  nsAutoCString originSuffix;
+  nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIServiceWorkerManager> swm =
+    mozilla::services::GetServiceWorkerManager();
+  if (NS_WARN_IF(!swm)) {
+    return NS_ERROR_FAILURE;
+  }
+
   if (!strcmp("alertclickcallback", aTopic)) {
-    nsAutoCString originSuffix;
-    nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCOMPtr<nsIServiceWorkerManager> swm =
-      mozilla::services::GetServiceWorkerManager();
-
-    if (swm) {
-      swm->SendNotificationClickEvent(originSuffix,
-                                      NS_ConvertUTF16toUTF8(mScope),
-                                      mID,
-                                      mTitle,
-                                      mDir,
-                                      mLang,
-                                      mBody,
-                                      mTag,
-                                      mIcon,
-                                      mData,
-                                      mBehavior);
-    }
+    rv = swm->SendNotificationClickEvent(originSuffix,
+                                         NS_ConvertUTF16toUTF8(mScope),
+                                         mID,
+                                         mTitle,
+                                         mDir,
+                                         mLang,
+                                         mBody,
+                                         mTag,
+                                         mIcon,
+                                         mData,
+                                         mBehavior);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
     return NS_OK;
   }
 
   if (!strcmp("alertfinished", aTopic)) {
     nsString origin;
     nsresult rv = Notification::GetOrigin(mPrincipal, origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     // Remove closed or dismissed persistent notifications.
     nsCOMPtr<nsINotificationStorage> notificationStorage =
       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
     if (notificationStorage) {
       notificationStorage->Delete(origin, mID);
     }
+
+    rv = swm->SendNotificationCloseEvent(originSuffix,
+                                         NS_ConvertUTF16toUTF8(mScope),
+                                         mID,
+                                         mTitle,
+                                         mDir,
+                                         mLang,
+                                         mBody,
+                                         mTag,
+                                         mIcon,
+                                         mData,
+                                         mBehavior);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+    return NS_OK;
   }
 
   return NS_OK;
 }
 
 bool
 Notification::IsInPrivateBrowsing()
 {
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -95,16 +95,28 @@ interface nsIPluginHost : nsISupports
    *
    * @mimeType The MIME type we're interested in.
    * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
+   * Get the "permission string" for the plugin.  This is a string that can be
+   * passed to the permission manager to see whether the plugin is allowed to
+   * run, for example.  This will typically be based on the plugin's "nice name"
+   * and its blocklist state.
+   *
+   * @tag The tage we're interested in
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
+   */
+  ACString getPermissionStringForTag(in nsIPluginTag tag,
+                                     [optional] in uint32_t excludeFlags);
+
+  /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
    * A fake plugin tag, if one exists and is available, will be returned in
    * preference to NPAPI plugin tags unless excluded by the excludeFlags.
    *
    * @mimeType The MIME type we're interested in.
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1105,33 +1105,41 @@ NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(tag, NS_ERROR_FAILURE);
+  return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag,
+                                        uint32_t aExcludeFlags,
+                                        nsACString &aPermissionString)
+{
+  NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE);
 
   aPermissionString.Truncate();
   uint32_t blocklistState;
-  rv = tag->GetBlocklistState(&blocklistState);
+  nsresult rv = aTag->GetBlocklistState(&blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aPermissionString.AssignLiteral("plugin-vulnerable:");
   }
   else {
     aPermissionString.AssignLiteral("plugin:");
   }
 
   nsCString niceName;
-  rv = tag->GetNiceName(niceName);
+  rv = aTag->GetNiceName(niceName);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
 
   aPermissionString.Append(niceName);
 
   return NS_OK;
 }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -318,17 +318,17 @@ nsPluginTag::nsPluginTag(uint32_t aId,
 {
 }
 
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
-NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag, nsIInternalPluginTag)
+NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag,  nsIInternalPluginTag, nsIPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
                            const char* const* aMimeDescriptions,
                            const char* const* aExtensions,
                            uint32_t aVariantCount)
 {
   if (!aMimeTypes) {
     return;
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -26,16 +26,19 @@ struct FakePluginTagInit;
 } // namespace dom
 } // namespace mozilla
 
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
+#define NS_PLUGINTAG_IID \
+  { 0xcce2e8b9, 0x9702, 0x4d4b, \
+   { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } }
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
 
   nsIInternalPluginTag();
   nsIInternalPluginTag(const char* aName, const char* aDescription,
                        const char* aFileName, const char* aVersion);
@@ -86,16 +89,18 @@ protected:
 };
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
 
 // A linked-list of plugin information that is used for instantiating plugins
 // and reflecting plugin information into JavaScript.
 class nsPluginTag final : public nsIInternalPluginTag
 {
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID)
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
 
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
@@ -188,16 +193,17 @@ private:
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   void InitSandboxLevel();
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 
   static uint32_t sNextId;
 };
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID)
 
 // A class representing "fake" plugin tags; that is plugin tags not
 // corresponding to actual NPAPI plugins.  In practice these are all
 // JS-implemented plugins; maybe we want a better name for this class?
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -140,17 +140,17 @@ MulticastDNSDeviceProvider::Init()
   mDiscoveryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   Preferences::AddStrongObservers(this, kObservedPrefs);
 
   mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY);
-  mDiscveryTimeoutMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS);
+  mDiscoveryTimeoutMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS);
   mDiscoverable = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE);
   mServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
 
 #ifdef MOZ_WIDGET_ANDROID
   // FIXME: Bug 1185806 - Provide a common device name setting.
   if (mServiceName.IsEmpty()) {
     GetAndroidDeviceName(mServiceName);
     Unused << Preferences::SetCString(PREF_PRESENTATION_DEVICE_NAME, mServiceName);
@@ -529,17 +529,17 @@ MulticastDNSDeviceProvider::ForceDiscove
   MOZ_ASSERT(mMulticastDNS);
 
   // if it's already discovering, extend existing discovery timeout.
   nsresult rv;
   if (mIsDiscovering) {
     Unused << mDiscoveryTimer->Cancel();
 
     if (NS_WARN_IF(NS_FAILED( rv = mDiscoveryTimer->Init(this,
-        mDiscveryTimeoutMs,
+        mDiscoveryTimeoutMs,
         nsITimer::TYPE_ONE_SHOT)))) {
         return rv;
     }
     return NS_OK;
   }
 
   StopDiscovery(NS_OK);
 
@@ -560,17 +560,17 @@ MulticastDNSDeviceProvider::OnDiscoveryS
   LOG_I("OnDiscoveryStarted");
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDiscoveryTimer);
 
   MarkAllDevicesUnknown();
 
   nsresult rv;
   if (NS_WARN_IF(NS_FAILED(rv = mDiscoveryTimer->Init(this,
-                                                      mDiscveryTimeoutMs,
+                                                      mDiscoveryTimeoutMs,
                                                       nsITimer::TYPE_ONE_SHOT)))) {
     return rv;
   }
 
   mIsDiscovering = true;
 
   return NS_OK;
 }
@@ -919,17 +919,17 @@ MulticastDNSDeviceProvider::OnDiscoveryC
 }
 
 nsresult
 MulticastDNSDeviceProvider::OnDiscoveryTimeoutChanged(uint32_t aTimeoutMs)
 {
   LOG_I("OnDiscoveryTimeoutChanged = %d\n", aTimeoutMs);
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDiscveryTimeoutMs = aTimeoutMs;
+  mDiscoveryTimeoutMs = aTimeoutMs;
 
   return NS_OK;
 }
 
 nsresult
 MulticastDNSDeviceProvider::OnDiscoverableChanged(bool aEnabled)
 {
   LOG_I("Discoverable = %d\n", aEnabled);
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -181,17 +181,17 @@ private:
 
   nsCOMPtr<nsICancelable> mDiscoveryRequest;
   nsCOMPtr<nsICancelable> mRegisterRequest;
 
   nsTArray<RefPtr<Device>> mDevices;
 
   bool mDiscoveryEnabled = false;
   bool mIsDiscovering = false;
-  uint32_t mDiscveryTimeoutMs;
+  uint32_t mDiscoveryTimeoutMs;
   nsCOMPtr<nsITimer> mDiscoveryTimer;
 
   bool mDiscoverable = false;
 
   nsCString mServiceName;
   nsCString mRegisteredName;
 };
 
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -2618,17 +2618,17 @@ Promise::ResolveInternal(JSContext* aCx,
 {
   NS_ASSERT_OWNINGTHREAD(Promise);
 
   CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
 
   mResolvePending = true;
 
   if (aValue.isObject()) {
-    AutoDontReportUncaught silenceReporting(aCx);
+    MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting());
     JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject());
 
     // Thenables.
     JS::Rooted<JS::Value> then(aCx);
     if (!JS_GetProperty(aCx, valueObj, "then", &then)) {
       HandleException(aCx);
       return;
     }
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -203,35 +203,42 @@ this.PushServiceAndroidGCM = {
 
   disconnect: function() {
     console.debug("disconnect");
   },
 
   register: function(record) {
     console.debug("register:", record);
     let ctime = Date.now();
+    let appServerKey = record.appServerKey ?
+      ChromeUtils.base64URLEncode(record.appServerKey, {
+        // The Push server requires padding.
+        pad: true,
+      }) : null;
     // Caller handles errors.
     return Messaging.sendRequestForResult({
       type: "PushServiceAndroidGCM:SubscribeChannel",
+      appServerKey: appServerKey,
     }).then(data => {
       console.debug("Got data:", data);
       return PushCrypto.generateKeys()
         .then(exportedKeys =>
           new PushRecordAndroidGCM({
             // Straight from autopush.
             channelID: data.channelID,
             pushEndpoint: data.endpoint,
             // Common to all PushRecord implementations.
             scope: record.scope,
             originAttributes: record.originAttributes,
             ctime: ctime,
             // Cryptography!
             p256dhPublicKey: exportedKeys[0],
             p256dhPrivateKey: exportedKeys[1],
             authenticationSecret: PushCrypto.generateAuthenticationSecret(),
+            appServerKey: record.appServerKey,
           })
       );
     });
   },
 
   unregister: function(record) {
     console.debug("unregister: ", record);
     return Messaging.sendRequestForResult({
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -472,18 +472,20 @@ this.PushServiceWebSocket = {
       console.warn("makeWebSocket: connection.enabled is not set to true.",
         "Aborting.");
       return null;
     }
     if (Services.io.offline) {
       console.warn("makeWebSocket: Network is offline.");
       return null;
     }
-    let socket = Cc["@mozilla.org/network/protocol;1?name=wss"]
-                   .createInstance(Ci.nsIWebSocketChannel);
+    let contractId = uri.scheme == "ws" ?
+                     "@mozilla.org/network/protocol;1?name=ws" :
+                     "@mozilla.org/network/protocol;1?name=wss";
+    let socket = Cc[contractId].createInstance(Ci.nsIWebSocketChannel);
 
     socket.initLoadInfo(null, // aLoadingNode
                         Services.scriptSecurityManager.getSystemPrincipal(),
                         null, // aTriggeringPrincipal
                         Ci.nsILoadInfo.SEC_NORMAL,
                         Ci.nsIContentPolicy.TYPE_WEBSOCKET);
 
     return socket;
--- a/dom/security/test/TestCSPParser.cpp
+++ b/dom/security/test/TestCSPParser.cpp
@@ -96,17 +96,19 @@ nsresult runTest(uint32_t aExpectedPolic
   NS_ENSURE_SUCCESS(rv, rv);
 
   // we init the csp with http://www.selfuri.com
   nsCOMPtr<nsIURI> selfURI;
   rv = NS_NewURI(getter_AddRefs(selfURI), "http://www.selfuri.com");
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPrincipal> selfURIPrincipal;
-  rv = secman->GetSimpleCodebasePrincipal(selfURI, getter_AddRefs(selfURIPrincipal));
+  // Can't use BasePrincipal::CreateCodebasePrincipal here
+  // because the symbol is not visible here
+  rv = secman->GetCodebasePrincipal(selfURI, getter_AddRefs(selfURIPrincipal));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create a CSP object
   nsCOMPtr<nsIContentSecurityPolicy> csp =
     do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // for testing the parser we only need to set a principal which is needed
--- a/dom/security/test/csp/test_bug949549.html
+++ b/dom/security/test/csp/test_bug949549.html
@@ -36,17 +36,17 @@
     });
   }
 
   function runTest() {
     try {
       var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
                    .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
       var manifestURI = SpecialPowers.Services.io.newURI(gManifestURL, null, null);
-      principal = secMan.getSimpleCodebasePrincipal(manifestURI);
+      principal = secMan.createCodebasePrincipal(manifestURI, {});
       csp.setRequestContext(null, principal);
       ok(true, "setRequestContext hasn't thown");
     } catch(e) {
       ok(false, "setRequestContext throws");
     }
 
     cleanup()
   }
--- a/dom/security/test/unit/test_csp_reports.js
+++ b/dom/security/test/unit/test_csp_reports.js
@@ -82,17 +82,17 @@ function makeTest(id, expectedJSON, useR
   var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
                                ":" + REPORT_SERVER_PORT +
                                "/foo/self");
 
   dump("Created test " + id + " : " + policy + "\n\n");
 
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
-  principal = ssm.getSimpleCodebasePrincipal(selfuri);
+  principal = ssm.createCodebasePrincipal(selfuri, {});
   csp.setRequestContext(null, principal);
 
   // Load up the policy
   // set as report-only if that's the case
   csp.appendPolicy(policy, useReportOnlyPolicy, false);
 
   // prime the report server
   var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
--- a/dom/webidl/Blob.webidl
+++ b/dom/webidl/Blob.webidl
@@ -27,16 +27,14 @@ interface Blob {
   [Throws]
   Blob slice([Clamp] optional long long start,
              [Clamp] optional long long end,
              optional DOMString contentType = "");
 
   // void close(); TODO bug 1048325
 };
 
-enum EndingTypes{"transparent", "native"};
+enum EndingTypes { "transparent", "native" };
 
 dictionary BlobPropertyBag {
-
   DOMString type = "";
   EndingTypes endings = "transparent";
-
 };
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -8,16 +8,19 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 
 dictionary EventListenerOptions {
   boolean capture = false;
+  /* This is a Mozilla extension only available in Chrome and XBL.
+     Setting to true make the listener be added to the system group. */
+  boolean mozSystemGroup = false;
 };
 
 dictionary AddEventListenerOptions : EventListenerOptions {
   boolean passive = false;
   boolean once = false;
 };
 
 [Exposed=(Window,Worker,WorkerDebugger,System)]
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -1,52 +1,48 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
+ *
+ * The origin of this IDL file is
+ * https://w3c.github.io/FileAPI/#file
  */
 
 interface nsIFile;
 
 [Constructor(sequence<(ArrayBuffer or ArrayBufferView or Blob or DOMString)> fileBits,
              USVString fileName, optional FilePropertyBag options),
 
  // These constructors are just for chrome callers:
  Constructor(Blob fileBits, optional ChromeFilePropertyBag options),
  Constructor(nsIFile fileBits, optional ChromeFilePropertyBag options),
  Constructor(USVString fileBits, optional ChromeFilePropertyBag options),
 
  Exposed=(Window,Worker)]
 interface File : Blob {
-
   readonly attribute DOMString name;
 
   [GetterThrows]
   readonly attribute long long lastModified;
-
 };
 
-
 dictionary FilePropertyBag {
-
-      DOMString type = "";
-      long long lastModified;
-
+  DOMString type = "";
+  long long lastModified;
 };
 
 dictionary ChromeFilePropertyBag : FilePropertyBag {
-
-      DOMString name = "";
-      boolean temporary = false;
+  DOMString name = "";
+  boolean temporary = false;
 };
 
 // Mozilla extensions
 partial interface File {
-
-  [GetterThrows]
+  [GetterThrows, Deprecated="FileLastModifiedDate"]
   readonly attribute Date lastModifiedDate;
 
   [BinaryName="path", Func="mozilla::dom::Directory::WebkitBlinkDirectoryPickerEnabled"]
   readonly attribute DOMString webkitRelativePath;
 
   [GetterThrows, ChromeOnly]
   readonly attribute DOMString mozFullPath;
 };
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -200,16 +200,19 @@ partial interface HTMLInputElement {
 
   [Pref="dom.input.dirpicker"]
   readonly attribute boolean isFilesAndDirectoriesSupported;
 
   [Throws, Pref="dom.input.dirpicker"]
   Promise<sequence<(File or Directory)>> getFilesAndDirectories();
 
   [Throws, Pref="dom.input.dirpicker"]
+  Promise<sequence<File>> getFiles(optional boolean recursiveFlag = false);
+
+  [Throws, Pref="dom.input.dirpicker"]
   void chooseDirectory();
 };
 
 [NoInterfaceObject]
 interface MozPhonetic {
   [Pure, ChromeOnly]
   readonly attribute DOMString phonetic;
 };
--- a/dom/webidl/NotificationEvent.webidl
+++ b/dom/webidl/NotificationEvent.webidl
@@ -18,9 +18,10 @@ interface NotificationEvent : Extendable
 };
 
 dictionary NotificationEventInit : ExtendableEventInit {
   required Notification notification;
 };
 
 partial interface ServiceWorkerGlobalScope {
   attribute EventHandler onnotificationclick;
+  attribute EventHandler onnotificationclose;
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -142,19 +142,16 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 
 #define PREF_WORKERS_OPTIONS_PREFIX PREF_WORKERS_PREFIX "options."
 #define PREF_MEM_OPTIONS_PREFIX "mem."
 #define PREF_GCZEAL "gcZeal"
 
 namespace {
 
 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;
 
@@ -802,18 +799,16 @@ CreateJSContextForWorker(WorkerPrivate* 
     NS_WARNING("Could not create new context!");
     return nullptr;
   }
 
   JS_SetInterruptCallback(aRuntime, InterruptCallback);
 
   js::SetCTypesActivityCallback(aRuntime, CTypesActivityCallback);
 
-  JS::ContextOptionsRef(workerCx) = kRequiredContextOptions;
-
 #ifdef JS_GC_ZEAL
   JS_SetGCZeal(workerCx, settings.gcZeal, settings.gcZealFrequency);
 #endif
 
   return workerCx;
 }
 
 static bool
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -954,44 +954,81 @@ ServiceWorkerManager::SendPushSubscripti
   ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
   if (!info) {
     return NS_ERROR_FAILURE;
   }
   return info->WorkerPrivate()->SendPushSubscriptionChangeEvent();
 #endif
 }
 
+nsresult
+ServiceWorkerManager::SendNotificationEvent(const nsAString& aEventName,
+                                            const nsACString& aOriginSuffix,
+                                            const nsACString& aScope,
+                                            const nsAString& aID,
+                                            const nsAString& aTitle,
+                                            const nsAString& aDir,
+                                            const nsAString& aLang,
+                                            const nsAString& aBody,
+                                            const nsAString& aTag,
+                                            const nsAString& aIcon,
+                                            const nsAString& aData,
+                                            const nsAString& aBehavior)
+{
+  PrincipalOriginAttributes attrs;
+  if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
+  if (!info) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
+  return workerPrivate->SendNotificationEvent(aEventName, aID, aTitle, aDir,
+                                              aLang, aBody, aTag,
+                                              aIcon, aData, aBehavior,
+                                              NS_ConvertUTF8toUTF16(aScope));
+}
+
 NS_IMETHODIMP
 ServiceWorkerManager::SendNotificationClickEvent(const nsACString& aOriginSuffix,
                                                  const nsACString& aScope,
                                                  const nsAString& aID,
                                                  const nsAString& aTitle,
                                                  const nsAString& aDir,
                                                  const nsAString& aLang,
                                                  const nsAString& aBody,
                                                  const nsAString& aTag,
                                                  const nsAString& aIcon,
                                                  const nsAString& aData,
                                                  const nsAString& aBehavior)
 {
-  PrincipalOriginAttributes attrs;
-  if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
-  if (!info) {
-    return NS_ERROR_FAILURE;
-  }
-
-  ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
-  return workerPrivate->SendNotificationClickEvent(aID, aTitle, aDir,
-                                                   aLang, aBody, aTag,
-                                                   aIcon, aData, aBehavior,
-                                                   NS_ConvertUTF8toUTF16(aScope));
+  return SendNotificationEvent(NS_LITERAL_STRING(NOTIFICATION_CLICK_EVENT_NAME),
+                               aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
+                               aBody, aTag, aIcon, aData, aBehavior);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::SendNotificationCloseEvent(const nsACString& aOriginSuffix,
+                                                 const nsACString& aScope,
+                                                 const nsAString& aID,
+                                                 const nsAString& aTitle,
+                                                 const nsAString& aDir,
+                                                 const nsAString& aLang,
+                                                 const nsAString& aBody,
+                                                 const nsAString& aTag,
+                                                 const nsAString& aIcon,
+                                                 const nsAString& aData,
+                                                 const nsAString& aBehavior)
+{
+  return SendNotificationEvent(NS_LITERAL_STRING(NOTIFICATION_CLOSE_EVENT_NAME),
+                               aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
+                               aBody, aTag, aIcon, aData, aBehavior);
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::GetReadyPromise(mozIDOMWindow* aWindow,
                                       nsISupports** aPromise)
 {
   AssertIsOnMainThread();
 
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -426,15 +426,29 @@ private:
   void
   ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope);
 
   void
   UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
 
   void
   MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
+
+  nsresult
+  SendNotificationEvent(const nsAString& aEventName,
+                        const nsACString& aOriginSuffix,
+                        const nsACString& aScope,
+                        const nsAString& aID,
+                        const nsAString& aTitle,
+                        const nsAString& aDir,
+                        const nsAString& aLang,
+                        const nsAString& aBody,
+                        const nsAString& aTag,
+                        const nsAString& aIcon,
+                        const nsAString& aData,
+                        const nsAString& aBehavior);
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkermanager_h
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1074,43 +1074,46 @@ NS_IMPL_ISUPPORTS0(AllowWindowInteractio
 bool
 ClearWindowAllowedRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
   mHandler->ClearWindowAllowed(aWorkerPrivate);
   mHandler = nullptr;
   return true;
 }
 
-class SendNotificationClickEventRunnable final : public ExtendableEventWorkerRunnable
+class SendNotificationEventRunnable final : public ExtendableEventWorkerRunnable
 {
+  const nsString mEventName;
   const nsString mID;
   const nsString mTitle;
   const nsString mDir;
   const nsString mLang;
   const nsString mBody;
   const nsString mTag;
   const nsString mIcon;
   const nsString mData;
   const nsString mBehavior;
   const nsString mScope;
 
 public:
-  SendNotificationClickEventRunnable(WorkerPrivate* aWorkerPrivate,
-                                     KeepAliveToken* aKeepAliveToken,
-                                     const nsAString& aID,
-                                     const nsAString& aTitle,
-                                     const nsAString& aDir,
-                                     const nsAString& aLang,
-                                     const nsAString& aBody,
-                                     const nsAString& aTag,
-                                     const nsAString& aIcon,
-                                     const nsAString& aData,
-                                     const nsAString& aBehavior,
-                                     const nsAString& aScope)
+  SendNotificationEventRunnable(WorkerPrivate* aWorkerPrivate,
+                                KeepAliveToken* aKeepAliveToken,
+                                const nsAString& aEventName,
+                                const nsAString& aID,
+                                const nsAString& aTitle,
+                                const nsAString& aDir,
+                                const nsAString& aLang,
+                                const nsAString& aBody,
+                                const nsAString& aTag,
+                                const nsAString& aIcon,
+                                const nsAString& aData,
+                                const nsAString& aBehavior,
+                                const nsAString& aScope)
       : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+      , mEventName(aEventName)
       , mID(aID)
       , mTitle(aTitle)
       , mDir(aDir)
       , mLang(aLang)
       , mBody(aBody)
       , mTag(aTag)
       , mIcon(aIcon)
       , mData(aData)
@@ -1139,18 +1142,17 @@ public:
     }
 
     NotificationEventInit nei;
     nei.mNotification = notification;
     nei.mBubbles = false;
     nei.mCancelable = false;
 
     RefPtr<NotificationEvent> event =
-      NotificationEvent::Constructor(target,
-                                     NS_LITERAL_STRING("notificationclick"),
+      NotificationEvent::Constructor(target, mEventName,
                                      nei, result);
     if (NS_WARN_IF(result.Failed())) {
       return false;
     }
 
     event->SetTrusted(true);
     RefPtr<Promise> waitUntil;
     aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
@@ -1165,37 +1167,47 @@ public:
 
     return true;
   }
 };
 
 } // namespace anonymous
 
 nsresult
-ServiceWorkerPrivate::SendNotificationClickEvent(const nsAString& aID,
-                                                 const nsAString& aTitle,
-                                                 const nsAString& aDir,
-                                                 const nsAString& aLang,
-                                                 const nsAString& aBody,
-                                                 const nsAString& aTag,
-                                                 const nsAString& aIcon,
-                                                 const nsAString& aData,
-                                                 const nsAString& aBehavior,
-                                                 const nsAString& aScope)
+ServiceWorkerPrivate::SendNotificationEvent(const nsAString& aEventName,
+                                            const nsAString& aID,
+                                            const nsAString& aTitle,
+                                            const nsAString& aDir,
+                                            const nsAString& aLang,
+                                            const nsAString& aBody,
+                                            const nsAString& aTag,
+                                            const nsAString& aIcon,
+                                            const nsAString& aData,
+                                            const nsAString& aBehavior,
+                                            const nsAString& aScope)
 {
-  nsresult rv = SpawnWorkerIfNeeded(NotificationClickEvent, nullptr);
+  WakeUpReason why;
+  if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
+    why = NotificationClickEvent;
+    gDOMDisableOpenClickDelay = Preferences::GetInt("dom.disable_open_click_delay");
+  } else if (aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
+    why = NotificationCloseEvent;
+  } else {
+    MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = SpawnWorkerIfNeeded(why, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  gDOMDisableOpenClickDelay = Preferences::GetInt("dom.disable_open_click_delay");
-
   RefPtr<WorkerRunnable> r =
-    new SendNotificationClickEventRunnable(mWorkerPrivate, mKeepAliveToken,
-                                           aID, aTitle, aDir, aLang,
-                                           aBody, aTag, aIcon, aData,
-                                           aBehavior, aScope);
+    new SendNotificationEventRunnable(mWorkerPrivate, mKeepAliveToken,
+                                      aEventName, aID, aTitle, aDir, aLang,
+                                      aBody, aTag, aIcon, aData, aBehavior,
+                                      aScope);
   if (NS_WARN_IF(!r->Dispatch())) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 namespace {
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -6,16 +6,19 @@
 
 #ifndef mozilla_dom_workers_serviceworkerprivate_h
 #define mozilla_dom_workers_serviceworkerprivate_h
 
 #include "nsCOMPtr.h"
 
 #include "WorkerPrivate.h"
 
+#define NOTIFICATION_CLICK_EVENT_NAME "notificationclick"
+#define NOTIFICATION_CLOSE_EVENT_NAME "notificationclose"
+
 class nsIInterceptedChannel;
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
 class ServiceWorkerInfo;
 class ServiceWorkerRegistrationInfo;
@@ -88,26 +91,27 @@ public:
   SendPushEvent(const nsAString& aMessageId,
                 const Maybe<nsTArray<uint8_t>>& aData,
                 ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
   SendPushSubscriptionChangeEvent();
 
   nsresult
-  SendNotificationClickEvent(const nsAString& aID,
-                             const nsAString& aTitle,
-                             const nsAString& aDir,
-                             const nsAString& aLang,
-                             const nsAString& aBody,
-                             const nsAString& aTag,
-                             const nsAString& aIcon,
-                             const nsAString& aData,
-                             const nsAString& aBehavior,
-                             const nsAString& aScope);
+  SendNotificationEvent(const nsAString& aEventName,
+                        const nsAString& aID,
+                        const nsAString& aTitle,
+                        const nsAString& aDir,
+                        const nsAString& aLang,
+                        const nsAString& aBody,
+                        const nsAString& aTag,
+                        const nsAString& aIcon,
+                        const nsAString& aData,
+                        const nsAString& aBehavior,
+                        const nsAString& aScope);
 
   nsresult
   SendFetchEvent(nsIInterceptedChannel* aChannel,
                  nsILoadGroup* aLoadGroup,
                  const nsAString& aDocumentId,
                  bool aIsReload);
 
   void
@@ -144,16 +148,17 @@ public:
 
 private:
   enum WakeUpReason {
     FetchEvent = 0,
     PushEvent,
     PushSubscriptionChangeEvent,
     MessageEvent,
     NotificationClickEvent,
+    NotificationCloseEvent,
     LifeCycleEvent,
     AttachEvent
   };
 
   // Timer callbacks
   static void
   NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate);
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1823,17 +1823,17 @@ TimerThreadEventTarget::~TimerThreadEven
 NS_IMETHODIMP
 TimerThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
   return Dispatch(runnable.forget(), aFlags);
 }
 
 NS_IMETHODIMP
-TimerThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags)
+TimerThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
 {
   // This should only happen on the timer thread.
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
 
   RefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
 
   // Run the runnable we're given now (should just call DummyCallback()),
@@ -1845,17 +1845,17 @@ TimerThreadEventTarget::Dispatch(already
   // This can fail if we're racing to terminate or cancel, should be handled
   // by the terminate or cancel code.
   mWorkerRunnable->Dispatch();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TimerThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t)
+TimerThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 TimerThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
 {
   MOZ_ASSERT(aIsOnCurrentThread);
@@ -2397,17 +2397,17 @@ WorkerPrivateParent<Derived>::WrapObject
     MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper));
   }
 
   return wrapper;
 }
 
 template <class Derived>
 nsresult
-WorkerPrivateParent<Derived>::DispatchPrivate(already_AddRefed<WorkerRunnable>&& aRunnable,
+WorkerPrivateParent<Derived>::DispatchPrivate(already_AddRefed<WorkerRunnable> aRunnable,
                                               nsIEventTarget* aSyncLoopTarget)
 {
   // May be called on any thread!
   RefPtr<WorkerRunnable> runnable(aRunnable);
 
   WorkerPrivate* self = ParentAsWorkerPrivate();
 
   {
@@ -2475,17 +2475,17 @@ WorkerPrivateParent<Derived>::DisableDeb
   if (NS_FAILED(UnregisterWorkerDebugger(self))) {
     NS_WARNING("Failed to unregister worker debugger!");
   }
 }
 
 template <class Derived>
 nsresult
 WorkerPrivateParent<Derived>::DispatchControlRunnable(
-  already_AddRefed<WorkerControlRunnable>&& aWorkerControlRunnable)
+  already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable)
 {
   // May be called on any thread!
   RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
   MOZ_ASSERT(runnable);
 
   WorkerPrivate* self = ParentAsWorkerPrivate();
 
   {
@@ -2513,17 +2513,17 @@ WorkerPrivateParent<Derived>::DispatchCo
   }
 
   return NS_OK;
 }
 
 template <class Derived>
 nsresult
 WorkerPrivateParent<Derived>::DispatchDebuggerRunnable(
-  already_AddRefed<WorkerRunnable>&& aDebuggerRunnable)
+  already_AddRefed<WorkerRunnable> aDebuggerRunnable)
 {
   // May be called on any thread!
 
   RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
 
   MOZ_ASSERT(runnable);
 
   WorkerPrivate* self = ParentAsWorkerPrivate();
@@ -2543,17 +2543,17 @@ WorkerPrivateParent<Derived>::DispatchDe
     mCondVar.Notify();
   }
 
   return NS_OK;
 }
 
 template <class Derived>
 already_AddRefed<WorkerRunnable>
-WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable>&& aRunnable)
+WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable)
 {
   // May be called on any thread!
 
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
   MOZ_ASSERT(runnable);
 
   RefPtr<WorkerRunnable> workerRunnable =
     WorkerRunnable::FromRunnable(runnable);
@@ -6733,17 +6733,17 @@ EventTarget::DispatchFromScript(nsIRunna
 {
   nsCOMPtr<nsIRunnable> event(aRunnable);
   return Dispatch(event.forget(), aFlags);
 }
 
 template <class Derived>
 NS_IMETHODIMP
 WorkerPrivateParent<Derived>::
-EventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags)
+EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
 {
   // May be called on any thread!
   nsCOMPtr<nsIRunnable> event(aRunnable);
 
   // Workers only support asynchronous dispatch for now.
   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -6769,17 +6769,17 @@ EventTarget::Dispatch(already_AddRefed<n
   }
 
   return NS_OK;
 }
 
 template <class Derived>
 NS_IMETHODIMP
 WorkerPrivateParent<Derived>::
-EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t)
+EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 template <class Derived>
 NS_IMETHODIMP
 WorkerPrivateParent<Derived>::
 EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -233,17 +233,17 @@ private:
   void
   PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const Optional<Sequence<JS::Value>>& aTransferable,
                       UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                       const nsMainThreadPtrHandle<nsISupports>& aKeepAliveToken,
                       ErrorResult& aRv);
 
   nsresult
-  DispatchPrivate(already_AddRefed<WorkerRunnable>&& aRunnable, nsIEventTarget* aSyncLoopTarget);
+  DispatchPrivate(already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget);
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerPrivateParent,
                                                          DOMEventTargetHelper)
@@ -258,29 +258,29 @@ public:
   ClearSelfRef()
   {
     AssertIsOnParentThread();
     MOZ_ASSERT(mSelfRef);
     mSelfRef = nullptr;
   }
 
   nsresult
-  Dispatch(already_AddRefed<WorkerRunnable>&& aRunnable)
+  Dispatch(already_AddRefed<WorkerRunnable> aRunnable)
   {
     return DispatchPrivate(Move(aRunnable), nullptr);
   }
 
   nsresult
-  DispatchControlRunnable(already_AddRefed<WorkerControlRunnable>&& aWorkerControlRunnable);
+  DispatchControlRunnable(already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable);
 
   nsresult
-  DispatchDebuggerRunnable(already_AddRefed<WorkerRunnable>&& aDebuggerRunnable);
+  DispatchDebuggerRunnable(already_AddRefed<WorkerRunnable> aDebuggerRunnable);
 
   already_AddRefed<WorkerRunnable>
-  MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable>&& aRunnable);
+  MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable);
 
   already_AddRefed<nsIEventTarget>
   GetEventTarget();
 
   // May be called on any thread...
   bool
   Start();
 
@@ -1533,20 +1533,20 @@ public:
                          WorkerRunnable* aWorkerRunnable);
 
 protected:
   NS_IMETHOD
   DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
 
 
   NS_IMETHOD
-  Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags) override;
+  Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
 
   NS_IMETHOD
-  DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t) override;
+  DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
 
   NS_IMETHOD
   IsOnCurrentThread(bool* aIsOnCurrentThread) override;
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_workerprivate_h__ */
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -238,16 +238,17 @@ class ServiceWorkerGlobalScope final : p
 
   ~ServiceWorkerGlobalScope();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
                                            WorkerGlobalScope)
   IMPL_EVENT_HANDLER(notificationclick)
+  IMPL_EVENT_HANDLER(notificationclose)
 
   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope);
 
   virtual bool
   WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
   static bool
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -140,17 +140,17 @@ WorkerThread::SetWorker(const WorkerThre
 #endif
       mWorkerPrivate = nullptr;
     }
   }
 }
 
 nsresult
 WorkerThread::DispatchPrimaryRunnable(const WorkerThreadFriendKey& /* aKey */,
-                                      already_AddRefed<nsIRunnable>&& aRunnable)
+                                      already_AddRefed<nsIRunnable> aRunnable)
 {
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 
 #ifdef DEBUG
   MOZ_ASSERT(PR_GetCurrentThread() != mThread);
   MOZ_ASSERT(runnable);
   {
     MutexAutoLock lock(mLock);
@@ -165,17 +165,17 @@ WorkerThread::DispatchPrimaryRunnable(co
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
 WorkerThread::DispatchAnyThread(const WorkerThreadFriendKey& /* aKey */,
-                       already_AddRefed<WorkerRunnable>&& aWorkerRunnable)
+                       already_AddRefed<WorkerRunnable> aWorkerRunnable)
 {
   // May be called on any thread!
 
 #ifdef DEBUG
   {
     const bool onWorkerThread = PR_GetCurrentThread() == mThread;
     {
       MutexAutoLock lock(mLock);
@@ -208,17 +208,17 @@ NS_IMPL_ISUPPORTS_INHERITED0(WorkerThrea
 NS_IMETHODIMP
 WorkerThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
   return Dispatch(runnable.forget(), aFlags);
 }
 
 NS_IMETHODIMP
-WorkerThread::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags)
+WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
 {
   // May be called on any thread!
   nsCOMPtr<nsIRunnable> runnable(aRunnable); // in case we exit early
 
   // Workers only support asynchronous dispatch.
   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -294,17 +294,17 @@ WorkerThread::Dispatch(already_AddRefed<
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t)
+WorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 uint32_t
 WorkerThread::RecursionDepth(const WorkerThreadFriendKey& /* aKey */) const
 {
   MOZ_ASSERT(PR_GetCurrentThread() == mThread);
--- a/dom/workers/WorkerThread.h
+++ b/dom/workers/WorkerThread.h
@@ -63,40 +63,40 @@ public:
   static already_AddRefed<WorkerThread>
   Create(const WorkerThreadFriendKey& aKey);
 
   void
   SetWorker(const WorkerThreadFriendKey& aKey, WorkerPrivate* aWorkerPrivate);
 
   nsresult
   DispatchPrimaryRunnable(const WorkerThreadFriendKey& aKey,
-                          already_AddRefed<nsIRunnable>&& aRunnable);
+                          already_AddRefed<nsIRunnable> aRunnable);
 
   nsresult
   DispatchAnyThread(const WorkerThreadFriendKey& aKey,
-           already_AddRefed<WorkerRunnable>&& aWorkerRunnable);
+           already_AddRefed<WorkerRunnable> aWorkerRunnable);
 
   uint32_t
   RecursionDepth(const WorkerThreadFriendKey& aKey) const;
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
   WorkerThread();
   ~WorkerThread();
 
   // This should only be called by consumers that have an
   // nsIEventTarget/nsIThread pointer.
   NS_IMETHOD
-  Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aFlags) override;
+  Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
 
   NS_IMETHOD
   DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
 
   NS_IMETHOD
-  DelayedDispatch(already_AddRefed<nsIRunnable>&&, uint32_t) override;
+  DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_WorkerThread_h__
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -128,16 +128,18 @@ support-files =
   bug1151916_worker.js
   bug1151916_driver.html
   bug1240436_worker.js
   notificationclick.html
   notificationclick-otherwindow.html
   notificationclick.js
   notificationclick_focus.html
   notificationclick_focus.js
+  notificationclose.html
+  notificationclose.js
   worker_updatefoundevent.js
   worker_updatefoundevent2.js
   updatefoundevent.html
   empty.js
   notification_constructor_error.js
   notification_get_sw.js
   notification/register.html
   notification/unregister.html
@@ -244,16 +246,17 @@ tags = mcb
 [test_match_all_client_properties.html]
 [test_navigator.html]
 [test_not_intercept_plugin.html]
 [test_notification_constructor_error.html]
 [test_notification_get.html]
 [test_notificationclick.html]
 [test_notificationclick_focus.html]
 [test_notificationclick-otherwindow.html]
+[test_notificationclose.html]
 [test_opaque_intercept.html]
 [test_openWindow.html]
 [test_origin_after_redirect.html]
 [test_origin_after_redirect_cached.html]
 [test_origin_after_redirect_to_https.html]
 [test_origin_after_redirect_to_https_cached.html]
 [test_post_message.html]
 [test_post_message_advanced.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclose.html
@@ -0,0 +1,37 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1265841 - controlled page</title>
+<script class="testbody" type="text/javascript">
+  var testWindow = parent;
+  if (opener) {
+    testWindow = opener;
+  }
+
+  navigator.serviceWorker.ready.then(function(swr) {
+    return swr.showNotification(
+      "Hi there. The ServiceWorker should receive a close event for this.",
+      { data: { complex: ["jsval", 5] }}).then(function() {
+        return swr;
+      });
+  }).then(function(swr) {
+    return swr.getNotifications();
+  }).then(function(notifications) {
+    notifications.forEach(function(notification) {
+      notification.close();
+    });
+  });
+
+  navigator.serviceWorker.onmessage = function(msg) {
+    testWindow.callback(msg.data.result);
+  };
+</script>
+
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclose.js
@@ -0,0 +1,19 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+onnotificationclose = function(e) {
+  self.clients.matchAll().then(function(clients) {
+    if (clients.length === 0) {
+      dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n");
+      return;
+    }
+
+    clients.forEach(function(client) {
+      client.postMessage({ result: e.notification.data &&
+                                   e.notification.data['complex'] &&
+                                   e.notification.data['complex'][0] == "jsval" &&
+                                   e.notification.data['complex'][1] == 5 });
+
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclose.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265841
+-->
+<head>
+  <title>Bug 1265841 - Test ServiceWorkerGlobalScope.notificationclose event.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+  <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265841">Bug 1265841</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+  SimpleTest.requestFlakyTimeout("Mock alert service dispatches show, click, and close events.");
+
+  function testFrame(src) {
+    var iframe = document.createElement("iframe");
+    iframe.src = src;
+    window.callback = function(result) {
+      window.callback = null;
+      document.body.removeChild(iframe);
+      iframe = null;
+      ok(result, "Got notificationclose event with correct data.");
+      MockServices.unregister();
+      registration.unregister().then(function() {
+        SimpleTest.finish();
+      });
+    };
+    document.body.appendChild(iframe);
+  }
+
+  var registration;
+
+  function runTest() {
+    MockServices.register();
+    testFrame('notificationclose.html');
+    navigator.serviceWorker.register("notificationclose.js", { scope: "notificationclose.html" }).then(function(reg) {
+      registration = reg;
+    }, function(e) {
+      ok(false, "registration should have passed!");
+    });
+  };
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true],
+    ["dom.webnotifications.workers.enabled", true],
+    ["dom.webnotifications.serviceworker.enabled", true],
+    ["notification.prompt.testing", true],
+  ]}, runTest);
+</script>
+</body>
+</html>
--- a/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp
+++ b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp
@@ -412,21 +412,20 @@ txCompileObserver::loadURI(const nsAStri
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> referrerUri;
     rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIPrincipal> referrerPrincipal;
-    rv = nsContentUtils::GetSecurityManager()->
-      GetSimpleCodebasePrincipal(referrerUri,
-                                 getter_AddRefs(referrerPrincipal));
-    NS_ENSURE_SUCCESS(rv, rv);
+    PrincipalOriginAttributes attrs;
+    nsCOMPtr<nsIPrincipal> referrerPrincipal =
+      BasePrincipal::CreateCodebasePrincipal(referrerUri, attrs);
+    NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE);
 
     return startLoad(uri, aCompiler, referrerPrincipal, aReferrerPolicy);
 }
 
 void
 txCompileObserver::onDoneCompiling(txStylesheetCompiler* aCompiler,
                                    nsresult aResult,
                                    const char16_t *aErrorText,
@@ -617,21 +616,19 @@ txSyncCompileObserver::loadURI(const nsA
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> referrerUri;
     rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIPrincipal> referrerPrincipal;
-    rv = nsContentUtils::GetSecurityManager()->
-      GetSimpleCodebasePrincipal(referrerUri,
-                                 getter_AddRefs(referrerPrincipal));
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIPrincipal> referrerPrincipal =
+      BasePrincipal::CreateCodebasePrincipal(referrerUri, PrincipalOriginAttributes());
+    NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE);
 
     // This is probably called by js, a loadGroup for the channel doesn't
     // make sense.
     nsCOMPtr<nsINode> source;
     if (mProcessor) {
       source =
         do_QueryInterface(mProcessor->GetSourceContentModel());
     }
--- a/gfx/2d/Path.cpp
+++ b/gfx/2d/Path.cpp
@@ -254,17 +254,17 @@ FlattenBezierCurveSegment(const BezierCo
    * equation of a cubic bezier curve is insignificantly small. This can
    * then be approximated by a quadratic equation for which the maximum
    * difference from a linear approximation can be much more easily determined.
    */
   BezierControlPoints currentCP = aControlPoints;
 
   double t = 0;
   while (t < 1.0) {
-    PointD cp21 = currentCP.mCP2 - currentCP.mCP3;
+    PointD cp21 = currentCP.mCP2 - currentCP.mCP1;
     PointD cp31 = currentCP.mCP3 - currentCP.mCP1;
 
     /* To remove divisions and check for divide-by-zero, this is optimized from:
      * Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y);
      * t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3))));
      */
     double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x;
     double h = hypot(cp21.x, cp21.y);
--- a/gfx/angle/src/libANGLE/Framebuffer.cpp
+++ b/gfx/angle/src/libANGLE/Framebuffer.cpp
@@ -64,16 +64,19 @@ Framebuffer::Data::~Data()
 
 const std::string &Framebuffer::Data::getLabel()
 {
     return mLabel;
 }
 
 const FramebufferAttachment *Framebuffer::Data::getReadAttachment() const
 {
+    if (mReadBufferState == GL_NONE)
+        return nullptr;
+
     ASSERT(mReadBufferState == GL_BACK || (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15));
     size_t readIndex = (mReadBufferState == GL_BACK ? 0 : static_cast<size_t>(mReadBufferState - GL_COLOR_ATTACHMENT0));
     ASSERT(readIndex < mColorAttachments.size());
     return mColorAttachments[readIndex].isAttached() ? &mColorAttachments[readIndex] : nullptr;
 }
 
 const FramebufferAttachment *Framebuffer::Data::getFirstColorAttachment() const
 {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -360,16 +360,22 @@ typedef GenericFlingAnimation FlingAnima
  * \li\b apz.x_skate_highmem_adjust
  * \li\b apz.y_skate_highmem_adjust
  * On high memory systems, we adjust the displayport during skating
  * to be larger so we can reduce checkerboarding.
  *
  * \li\b apz.zoom_animation_duration_ms
  * This controls how long the zoom-to-rect animation takes.\n
  * Units: ms
+ *
+ * \li\b apz.scale_repaint_delay_ms
+ * How long to delay between repaint requests during a scale.
+ * A negative number prevents repaint requests during a scale.\n
+ * Units: ms
+ *
  */
 
 /**
  * Computed time function used for sampling frames of a zoom to animation.
  */
 StaticAutoPtr<ComputedTimingFunction> gZoomAnimationFunction;
 
 /**
@@ -698,16 +704,17 @@ AsyncPanZoomController::AsyncPanZoomCont
      mPanDirRestricted(false),
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mLastCheckerboardReport(GetFrameTime()),
      mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
      mState(NOTHING),
      mNotificationBlockers(0),
      mInputQueue(aInputQueue),
+     mPinchPaintTimerSet(false),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false),
      mCheckerboardEventLock("APZCBELock")
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
@@ -1276,16 +1283,17 @@ nsEventStatus AsyncPanZoomController::On
   OnTouchEndOrCancel();
   CancelAnimationAndGestureState();
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
 
+  mPinchPaintTimerSet = false;
   // Note that there may not be a touch block at this point, if we received the
   // PinchGestureEvent directly from widget code without any touch events.
   if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   SetState(PINCHING);
   mX.SetVelocity(0);
@@ -1370,30 +1378,45 @@ nsEventStatus AsyncPanZoomController::On
 
       ScaleWithFocus(spanRatio, cssFocusPoint);
 
       if (neededDisplacement != CSSPoint()) {
         ScrollBy(neededDisplacement);
       }
 
       ScheduleComposite();
-      // We don't want to redraw on every scale, so don't use
-      // RequestContentRepaint()
+
+      // We don't want to redraw on every scale, so throttle it.
+      if (!mPinchPaintTimerSet) {
+        const int delay = gfxPrefs::APZScaleRepaintDelay();
+        if (delay >= 0) {
+          if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+            mPinchPaintTimerSet = true;
+            controller->PostDelayedTask(
+              NewRunnableMethod(this,
+                                &AsyncPanZoomController::DoDelayedRequestContentRepaint),
+              delay);
+          }
+        }
+      }
+
       UpdateSharedCompositorFrameMetrics();
     }
 
     mLastZoomFocus = focusPoint;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-end in state %d\n", this, mState);
 
+  mPinchPaintTimerSet = false;
+
   if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   SetState(NOTHING);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
@@ -1608,16 +1631,25 @@ AsyncPanZoomController::AllowScrollHando
       if (currentBlock->GetScrolledApzc() == this) {
         result = false;
       }
     }
   }
   return result;
 }
 
+void AsyncPanZoomController::DoDelayedRequestContentRepaint()
+{
+  if (!IsDestroyed() && mPinchPaintTimerSet) {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    RequestContentRepaint();
+  }
+  mPinchPaintTimerSet = false;
+}
+
 static ScrollInputMethod
 ScrollInputMethodForWheelDeltaType(ScrollWheelInput::ScrollDeltaType aDeltaType)
 {
   switch (aDeltaType) {
     case ScrollWheelInput::SCROLLDELTA_LINE: {
       return ScrollInputMethod::ApzWheelLine;
     }
     case ScrollWheelInput::SCROLLDELTA_PAGE: {
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -893,16 +893,18 @@ private:
 
   friend class GenericOverscrollEffect;
   friend class WidgetOverscrollEffect;
 
   // The initial velocity of the most recent fling.
   ParentLayerPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
+  // Indicates if the repaint-during-pinch timer is currently set
+  bool mPinchPaintTimerSet;
 
   // Deal with overscroll resulting from a fling animation. This is only ever
   // called on APZC instances that were actually performing a fling.
   // The overscroll is handled by trying to hand the fling off to an APZC
   // later in the handoff chain, or if there are no takers, continuing the
   // fling and entering an overscrolled state.
   void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
                              const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
@@ -916,16 +918,19 @@ private:
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void SmoothScrollTo(const CSSPoint& aDestination);
 
   // Returns whether overscroll is allowed during an event.
   bool AllowScrollHandoffInCurrentBlock() const;
 
+  // Invoked by the pinch repaint timer.
+  void DoDelayedRequestContentRepaint();
+
   /* ===================================================================
    * The functions and members in this section are used to make ancestor chains
    * out of APZC instances. These chains can only be walked or manipulated
    * while holding the lock in the associated APZCTreeManager instance.
    */
 public:
   void SetParent(AsyncPanZoomController* aParent) {
     mParent = aParent;
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -137,19 +137,19 @@ public:
   CompositableForwarder* GetForwarder() { return mForwarder; }
 
   ClientIPCAllocator* GetAllocator() { return mForwarder; }
 
   void ActorDestroy(ActorDestroyReason why) override;
 
   bool IPCOpen() const { return mIPCOpen; }
 
-  void Lock() const { mLock.Enter(); }
+  void Lock() const { if (mForwarder->UsesImageBridge()) { mLock.Enter(); } }
 
-  void Unlock() const { mLock.Leave(); }
+  void Unlock() const { if (mForwarder->UsesImageBridge()) { mLock.Leave(); } }
 
 private:
 
   // AddIPDLReference and ReleaseIPDLReference are only to be called by CreateIPDLActor
   // and DestroyIPDLActor, respectively. We intentionally make them private to prevent misuse.
   // The purpose of these methods is to be aware of when the IPC system around this
   // actor goes down: mIPCOpen is then set to false.
   void AddIPDLReference() {
@@ -158,16 +158,79 @@ private:
     AddRef();
   }
   void ReleaseIPDLReference() {
     MOZ_ASSERT(mIPCOpen == true);
     mIPCOpen = false;
     Release();
   }
 
+  // This lock is used order to prevent several threads to access the
+  // TextureClient's data concurrently. In particular, it prevents shutdown
+  // code to destroy a texture while another thread is reading or writing into
+  // it.
+  // In most places, the lock is held in short and bounded scopes in which we
+  // don't block on any other resource. There are few exceptions to this, which
+  // are discussed below.
+  //
+  // The locking pattern of TextureClient may in some case upset deadlock detection
+  // tools such as TSan.
+  // Typically our tile rendering code will lock all of its tiles, render into them
+  // and unlock them all right after that, which looks something like:
+  //
+  // Lock tile A
+  // Lock tile B
+  // Lock tile C
+  // Apply drawing commands to tiles A, B and C 
+  // Unlock tile A
+  // Unlock tile B
+  // Unlock tile C
+  //
+  // And later, we may end up rendering a tile buffer that has the same tiles,
+  // in a different order, for example:
+  //
+  // Lock tile B
+  // Lock tile A
+  // Lock tile D
+  // Apply drawing commands to tiles A, B and D
+  // Unlock tile B
+  // Unlock tile A
+  // Unlock tile D
+  //
+  // This is because textures being expensive to create, we recycle them as much
+  // as possible and they may reappear in the tile buffer in a different order.
+  //
+  // Unfortunately this is not very friendly to TSan's analysis, which will see
+  // that B was once locked while A was locked, and then A locked while B was
+  // locked. TSan identifies this as a potential dead-lock which would be the
+  // case if this kind of inconsistent and dependent locking order was happening
+  // concurrently.
+  // In the case of TextureClient, dependent locking only ever happens on the
+  // thread that draws into the texture (let's call it the producer thread). Other
+  // threads may call into a method that can lock the texture in a short and
+  // bounded scope inside of which it is not allowed to do anything that could
+  // cause the thread to block. A given texture can only have one producer thread.
+  //
+  // Another example of TSan-unfriendly locking pattern is when copying a texture
+  // into another, which also never happens outside of the producer thread.
+  // Copying A into B looks like this:
+  //
+  // Lock texture B
+  // Lock texture A
+  // Copy A into B
+  // Unlock A
+  // Unlock B
+  //
+  // In a given frame we may need to copy A into B and in another frame copy
+  // B into A. For example A and B can be the Front and Back buffers, alternating
+  // roles and the copy is needed to avoid the cost of re-drawing the valid
+  // region.
+  //
+  // The important rule is that all of the dependent locking must occur only
+  // in the texture's producer thread to avoid deadlocks.
   mutable gfx::CriticalSection mLock;
 
   RefPtr<CompositableForwarder> mForwarder;
   RefPtr<TextureClient> mWaitForRecycle;
 
   TextureClient* mTextureClient;
   TextureData* mTextureData;
   Atomic<bool> mDestroyed;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -930,17 +930,17 @@ DataTextureSourceD3D11::Update(DataSourc
         box.back = 1;
         box.left = rect.x;
         box.top = rect.y;
         box.right = rect.XMost();
         box.bottom = rect.YMost();
 
         void* data = map.mData + map.mStride * rect.y + BytesPerPixel(aSurface->GetFormat()) * rect.x;
 
-        mCompositor->GetDC()->UpdateSubresource(mTexture, 0, &box, data, map.mStride, map.mStride * mSize.height);
+        mCompositor->GetDC()->UpdateSubresource(mTexture, 0, &box, data, map.mStride, map.mStride * rect.height);
       }
     } else {
       mCompositor->GetDC()->UpdateSubresource(mTexture, 0, nullptr, aSurface->GetData(),
                                               aSurface->Stride(), aSurface->Stride() * mSize.height);
     }
 
     aSurface->Unmap();
   } else {
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -182,16 +182,17 @@ private:
   DECL_GFX_PREF(Live, "apz.velocity_relevance_time_ms",        APZVelocityRelevanceTime, uint32_t, 150);
   DECL_GFX_PREF(Live, "apz.x_skate_highmem_adjust",            APZXSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
   DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier",      APZXStationarySizeMultiplier, float, 3.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_highmem_adjust",            APZYSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier",           APZYSkateSizeMultiplier, float, 2.5f);
   DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier",      APZYStationarySizeMultiplier, float, 3.5f);
   DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms",        APZZoomAnimationDuration, int32_t, 250);
+  DECL_GFX_PREF(Live, "apz.scale_repaint_delay_ms",            APZScaleRepaintDelay, int32_t, 500);
 
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -72,22 +72,22 @@ PrincipalInfoToPrincipal(const Principal
         aPrincipalInfo.get_ContentPrincipalInfo();
 
       nsCOMPtr<nsIURI> uri;
       rv = NS_NewURI(getter_AddRefs(uri), info.spec());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return nullptr;
       }
 
-      if (info.attrs().mAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
-        rv = secMan->GetSimpleCodebasePrincipal(uri, getter_AddRefs(principal));
-      } else {
-        principal = BasePrincipal::CreateCodebasePrincipal(uri, info.attrs());
-        rv = principal ? NS_OK : NS_ERROR_FAILURE;
+      PrincipalOriginAttributes attrs;
+      if (info.attrs().mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+        attrs = info.attrs();
       }
+      principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+      rv = principal ? NS_OK : NS_ERROR_FAILURE;
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return nullptr;
       }
 
       return principal.forget();
     }
 
     case PrincipalInfo::TExpandedPrincipalInfo: {
--- a/ipc/unixsocket/SocketBase.h
+++ b/ipc/unixsocket/SocketBase.h
@@ -448,17 +448,17 @@ private:
 //
 // Socket tasks
 //
 
 /* |SocketTask| is a task for sending a message from
  * the I/O thread to the consumer thread.
  */
 template <typename T>
-class SocketTask : public Runnable
+class SocketTask : public CancelableRunnable
 {
 public:
   virtual ~SocketTask()
   { }
 
   T* GetIO() const
   {
     return mIO;
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -405,19 +405,17 @@ WrapperAnswer::RecvCallOrConstruct(const
                 return fail(aes, rs);
             if (!vals.append(v))
                 return fail(aes, rs);
         }
     }
 
     RootedValue rval(cx);
     {
-        AutoSaveContextOptions asco(cx);
-        ContextOptionsRef(cx).setDontReportUncaught(true);
-
+        MOZ_ASSERT(JS::ContextOptionsRef(cx).autoJSAPIOwnsErrorReporting());
         HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
         if (construct) {
             RootedObject obj(cx);
             if (!JS::Construct(cx, vals[0], args, &obj))
                 return fail(aes, rs);
             rval.setObject(*obj);
         } else {
             if(!JS::Call(cx, vals[1], vals[0], args, &rval))
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -546,17 +546,17 @@ class JS_PUBLIC_API(AutoAssertNoAlloc)
  *       that the hazard analysis is correct for that code, rather than relying
  *       on this class.
  */
 class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc
 {
   public:
     AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {}
     explicit AutoSuppressGCAnalysis(JSRuntime* rt) : AutoAssertNoAlloc(rt) {}
-};
+} JS_HAZ_GC_SUPPRESSED;
 
 /**
  * Assert that code is only ever called from a GC callback, disable the static
  * rooting hazard analysis and assert if any allocation that could potentially
  * trigger a GC occurs while this guard object is live.
  *
  * This is useful to make the static analysis ignore code that runs in GC
  * callbacks.
--- a/js/public/GCAnnotations.h
+++ b/js/public/GCAnnotations.h
@@ -18,36 +18,40 @@
 // annotation.)
 # define JS_HAZ_GC_POINTER __attribute__((tag("GC Pointer")))
 
 // Mark a type as a rooted pointer, suitable for use on the stack (eg all
 // Rooted<T> instantiations should have this.)
 # define JS_HAZ_ROOTED __attribute__((tag("Rooted Pointer")))
 
 // Mark a type as something that should not be held live across a GC, but which
-// is itself not a GC pointer.
+// is not itself a GC pointer.
 # define JS_HAZ_GC_INVALIDATED __attribute__((tag("Invalidated by GC")))
 
 // Mark a type that would otherwise be considered a GC Pointer (eg because it
 // contains a JS::Value field) as a non-GC pointer. It is handled almost the
 // same in the analysis as a rooted pointer, except it will not be reported as
 // an unnecessary root if used across a GC call. This should rarely be used,
 // but makes sense for something like ErrorResult, which only contains a GC
 // pointer when it holds an exception (and it does its own rooting,
 // conditionally.)
 # define JS_HAZ_NON_GC_POINTER __attribute__((tag("Suppressed GC Pointer")))
 
 // Mark a function as something that runs a garbage collection, potentially
 // invalidating GC pointers.
 # define JS_HAZ_GC_CALL __attribute__((tag("GC Call")))
 
+// Mark an RAII class as suppressing GC within its scope.
+# define JS_HAZ_GC_SUPPRESSED __attribute__((tag("Suppress GC")))
+
 #else
 
 # define JS_HAZ_GC_THING
 # define JS_HAZ_GC_POINTER
 # define JS_HAZ_ROOTED
 # define JS_HAZ_GC_INVALIDATED
 # define JS_HAZ_NON_GC_POINTER
 # define JS_HAZ_GC_CALL
+# define JS_HAZ_GC_SUPPRESSED
 
 #endif
 
 #endif /* js_GCAnnotations_h */
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2701,32 +2701,37 @@ namespace {
     }
 
 static inline Expr
 SimdToExpr(SimdType type, SimdOperation op)
 {
     switch (type) {
       case SimdType::Uint8x16:
         // Handle the special unsigned opcodes, then fall through to Int8x16.
-        switch(op) {
+        switch (op) {
           case SimdOperation::Fn_addSaturate:        return Expr::I8x16addSaturateU;
           case SimdOperation::Fn_subSaturate:        return Expr::I8x16subSaturateU;
           case SimdOperation::Fn_extractLane:        return Expr::I8x16extractLaneU;
           case SimdOperation::Fn_shiftRightByScalar: return Expr::I8x16shiftRightByScalarU;
           case SimdOperation::Fn_lessThan:           return Expr::I8x16lessThanU;
           case SimdOperation::Fn_lessThanOrEqual:    return Expr::I8x16lessThanOrEqualU;
           case SimdOperation::Fn_greaterThan:        return Expr::I8x16greaterThanU;
           case SimdOperation::Fn_greaterThanOrEqual: return Expr::I8x16greaterThanOrEqualU;
           case SimdOperation::Fn_fromInt8x16Bits:    return Expr::Limit;
           default: break;
         }
         MOZ_FALLTHROUGH;
       case SimdType::Int8x16:
         // Bitcasts Uint8x16 <--> Int8x16 become noops.
-        if (op == SimdOperation::Fn_fromUint8x16Bits) return Expr::Limit;
+        switch (op) {
+          case SimdOperation::Fn_fromUint8x16Bits: return Expr::Limit;
+          case SimdOperation::Fn_fromUint16x8Bits: return Expr::I8x16fromInt16x8Bits;
+          case SimdOperation::Fn_fromUint32x4Bits: return Expr::I8x16fromInt32x4Bits;
+          default: break;
+        }
         ENUMERATE(I8x16, FORALL_INT8X16_ASMJS_OP, I8x16CASE)
         break;
 
       case SimdType::Uint16x8:
         // Handle the special unsigned opcodes, then fall through to Int16x8.
         switch(op) {
           case SimdOperation::Fn_addSaturate:        return Expr::I16x8addSaturateU;
           case SimdOperation::Fn_subSaturate:        return Expr::I16x8subSaturateU;
@@ -2737,17 +2742,22 @@ SimdToExpr(SimdType type, SimdOperation 
           case SimdOperation::Fn_greaterThan:        return Expr::I16x8greaterThanU;
           case SimdOperation::Fn_greaterThanOrEqual: return Expr::I16x8greaterThanOrEqualU;
           case SimdOperation::Fn_fromInt16x8Bits:    return Expr::Limit;
           default: break;
         }
         MOZ_FALLTHROUGH;
       case SimdType::Int16x8:
         // Bitcasts Uint16x8 <--> Int16x8 become noops.
-        if (op == SimdOperation::Fn_fromUint16x8Bits) return Expr::Limit;
+        switch (op) {
+          case SimdOperation::Fn_fromUint8x16Bits: return Expr::I16x8fromInt8x16Bits;
+          case SimdOperation::Fn_fromUint16x8Bits: return Expr::Limit;
+          case SimdOperation::Fn_fromUint32x4Bits: return Expr::I16x8fromInt32x4Bits;
+          default: break;
+        }
         ENUMERATE(I16x8, FORALL_INT16X8_ASMJS_OP, I16x8CASE)
         break;
 
       case SimdType::Uint32x4:
         // Handle the special unsigned opcodes, then fall through to Int32x4.
         switch(op) {
           case SimdOperation::Fn_shiftRightByScalar: return Expr::I32x4shiftRightByScalarU;
           case SimdOperation::Fn_lessThan:           return Expr::I32x4lessThanU;
@@ -2756,21 +2766,32 @@ SimdToExpr(SimdType type, SimdOperation 
           case SimdOperation::Fn_greaterThanOrEqual: return Expr::I32x4greaterThanOrEqualU;
           case SimdOperation::Fn_fromFloat32x4:      return Expr::I32x4fromFloat32x4U;
           case SimdOperation::Fn_fromInt32x4Bits:    return Expr::Limit;
           default: break;
         }
         MOZ_FALLTHROUGH;
       case SimdType::Int32x4:
         // Bitcasts Uint32x4 <--> Int32x4 become noops.
-        if (op == SimdOperation::Fn_fromUint32x4Bits) return Expr::Limit;
+        switch (op) {
+          case SimdOperation::Fn_fromUint8x16Bits: return Expr::I32x4fromInt8x16Bits;
+          case SimdOperation::Fn_fromUint16x8Bits: return Expr::I32x4fromInt16x8Bits;
+          case SimdOperation::Fn_fromUint32x4Bits: return Expr::Limit;
+          default: break;
+        }
         ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32x4CASE)
         break;
 
       case SimdType::Float32x4:
+        switch (op) {
+          case SimdOperation::Fn_fromUint8x16Bits: return Expr::F32x4fromInt8x16Bits;
+          case SimdOperation::Fn_fromUint16x8Bits: return Expr::F32x4fromInt16x8Bits;
+          case SimdOperation::Fn_fromUint32x4Bits: return Expr::F32x4fromInt32x4Bits;
+          default: break;
+        }
         ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32x4CASE)
         break;
 
       case SimdType::Bool8x16:
         ENUMERATE(B8x16, FORALL_BOOL_SIMD_OP, B8x16CASE)
         break;
 
       case SimdType::Bool16x8:
@@ -3379,39 +3400,88 @@ CheckNewArrayView(ModuleValidator& m, Pr
     return m.addArrayView(varName, type, field);
 }
 
 static bool
 IsSimdValidOperationType(SimdType type, SimdOperation op)
 {
 #define CASE(op) case SimdOperation::Fn_##op:
     switch(type) {
+      case SimdType::Int8x16:
+        switch (op) {
+          case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
+          case SimdOperation::Fn_fromUint32x4Bits:
+          FORALL_INT8X16_ASMJS_OP(CASE) return true;
+          default: return false;
+        }
+        break;
+      case SimdType::Int16x8:
+        switch (op) {
+          case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
+          case SimdOperation::Fn_fromUint32x4Bits:
+          FORALL_INT16X8_ASMJS_OP(CASE) return true;
+          default: return false;
+        }
+        break;
       case SimdType::Int32x4:
         switch (op) {
           case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
           case SimdOperation::Fn_fromUint32x4Bits:
           FORALL_INT32X4_ASMJS_OP(CASE) return true;
           default: return false;
         }
         break;
+      case SimdType::Uint8x16:
+        switch (op) {
+          case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromInt8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
+          case SimdOperation::Fn_fromUint32x4Bits:
+          FORALL_INT8X16_ASMJS_OP(CASE) return true;
+          default: return false;
+        }
+        break;
+      case SimdType::Uint16x8:
+        switch (op) {
+          case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromInt16x8Bits:
+          case SimdOperation::Fn_fromUint32x4Bits:
+          FORALL_INT16X8_ASMJS_OP(CASE) return true;
+          default: return false;
+        }
+        break;
       case SimdType::Uint32x4:
         switch (op) {
           case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
           case SimdOperation::Fn_fromInt32x4Bits:
           FORALL_INT32X4_ASMJS_OP(CASE) return true;
           default: return false;
         }
         break;
       case SimdType::Float32x4:
         switch (op) {
           case SimdOperation::Constructor:
+          case SimdOperation::Fn_fromUint8x16Bits:
+          case SimdOperation::Fn_fromUint16x8Bits:
+          case SimdOperation::Fn_fromUint32x4Bits:
           FORALL_FLOAT32X4_ASMJS_OP(CASE) return true;
           default: return false;
         }
         break;
+      case SimdType::Bool8x16:
+      case SimdType::Bool16x8:
       case SimdType::Bool32x4:
         switch (op) {
           case SimdOperation::Constructor:
           FORALL_BOOL_SIMD_OP(CASE) return true;
           default: return false;
         }
         break;
       default:
@@ -5000,32 +5070,30 @@ class CheckSimdScalarArgs
 
         return true;
     }
 };
 
 class CheckSimdSelectArgs
 {
     Type formalType_;
+    Type maskType_;
 
   public:
-    explicit CheckSimdSelectArgs(SimdType t) : formalType_(t) {}
+    explicit CheckSimdSelectArgs(SimdType t) : formalType_(t), maskType_(GetBooleanSimdType(t)) {}
 
     bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const
     {
-        if (argIndex == 0) {
-            // First argument of select is a bool32x4 mask.
-            if (!(actualType <= Type::Bool32x4))
-                return f.failf(arg, "%s is not a subtype of Bool32x4", actualType.toChars());
-            return true;
-        }
-
-        if (!(actualType <= formalType_)) {
+        // The first argument is the boolean selector, the next two are the
+        // values to choose from.
+        Type wantedType = argIndex == 0 ? maskType_ : formalType_;
+
+        if (!(actualType <= wantedType)) {
             return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
-                           formalType_.toChars());
+                           wantedType.toChars());
         }
         return true;
     }
 };
 
 class CheckSimdVectorScalarArgs
 {
     SimdType formalSimdType_;
@@ -5215,85 +5283,90 @@ CheckSimdCast(FunctionValidator& f, Pars
         return false;
     *type = toType;
     return true;
 }
 
 } // namespace
 
 static bool
-CheckSimdShuffleSelectors(FunctionValidator& f, ParseNode* lane, int32_t lanes[4], uint32_t maxLane)
-{
-    for (unsigned i = 0; i < 4; i++, lane = NextNode(lane)) {
+CheckSimdShuffleSelectors(FunctionValidator& f, ParseNode* lane,
+                          mozilla::Array<uint8_t, 16>& lanes, unsigned numLanes, unsigned maxLane)
+{
+    for (unsigned i = 0; i < numLanes; i++, lane = NextNode(lane)) {
         uint32_t u32;
         if (!IsLiteralInt(f.m(), lane, &u32))
             return f.failf(lane, "lane selector should be a constant integer literal");
         if (u32 >= maxLane)
             return f.failf(lane, "lane selector should be less than %u", maxLane);
-        lanes[i] = int32_t(u32);
+        lanes[i] = uint8_t(u32);
     }
     return true;
 }
 
 static bool
 CheckSimdSwizzle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type)
 {
+    const unsigned numLanes = GetSimdLanes(opType);
     unsigned numArgs = CallArgListLength(call);
-    if (numArgs != 5)
-        return f.failf(call, "expected 5 arguments to SIMD swizzle, got %u", numArgs);
+    if (numArgs != 1 + numLanes)
+        return f.failf(call, "expected %u arguments to SIMD swizzle, got %u", 1 + numLanes,
+                       numArgs);
 
     Type retType = opType;
     ParseNode* vec = CallArgList(call);
     Type vecType;
     if (!CheckExpr(f, vec, &vecType))
         return false;
     if (!(vecType <= retType))
         return f.failf(vec, "%s is not a subtype of %s", vecType.toChars(), retType.toChars());
 
     if (!f.writeSimdOp(opType, SimdOperation::Fn_swizzle))
         return false;
 
-    int32_t lanes[4];
-    if (!CheckSimdShuffleSelectors(f, NextNode(vec), lanes, 4))
-        return false;
-
-    for (unsigned i = 0; i < 4; i++) {
-        if (!f.encoder().writeFixedU8(uint8_t(lanes[i])))
+    mozilla::Array<uint8_t, 16> lanes;
+    if (!CheckSimdShuffleSelectors(f, NextNode(vec), lanes, numLanes, numLanes))
+        return false;
+
+    for (unsigned i = 0; i < numLanes; i++) {
+        if (!f.encoder().writeFixedU8(lanes[i]))
             return false;
     }
 
     *type = retType;
     return true;
 }
 
 static bool
 CheckSimdShuffle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type)
 {
+    const unsigned numLanes = GetSimdLanes(opType);
     unsigned numArgs = CallArgListLength(call);
-    if (numArgs != 6)
-        return f.failf(call, "expected 6 arguments to SIMD shuffle, got %u", numArgs);
+    if (numArgs != 2 + numLanes)
+        return f.failf(call, "expected %u arguments to SIMD shuffle, got %u", 2 + numLanes,
+                       numArgs);
 
     Type retType = opType;
     ParseNode* arg = CallArgList(call);
     for (unsigned i = 0; i < 2; i++, arg = NextNode(arg)) {
         Type type;
         if (!CheckExpr(f, arg, &type))
             return false;
         if (!(type <= retType))
             return f.failf(arg, "%s is not a subtype of %s", type.toChars(), retType.toChars());
     }
 
     if (!f.writeSimdOp(opType, SimdOperation::Fn_shuffle))
         return false;
 
-    int32_t lanes[4];
-    if (!CheckSimdShuffleSelectors(f, arg, lanes, 8))
-        return false;
-
-    for (unsigned i = 0; i < 4; i++) {
+    mozilla::Array<uint8_t, 16> lanes;
+    if (!CheckSimdShuffleSelectors(f, arg, lanes, numLanes, 2 * numLanes))
+        return false;
+
+    for (unsigned i = 0; i < numLanes; i++) {
         if (!f.encoder().writeFixedU8(uint8_t(lanes[i])))
             return false;
     }
 
     *type = retType;
     return true;
 }
 
@@ -7534,57 +7607,72 @@ ValidateSimdOperation(JSContext* cx, con
 #define SET_NATIVE_BOOL8X16(op) case SimdOperation::Fn_##op: native = simd_bool8x16_##op; break;
 #define SET_NATIVE_BOOL16X8(op) case SimdOperation::Fn_##op: native = simd_bool16x8_##op; break;
 #define SET_NATIVE_BOOL32X4(op) case SimdOperation::Fn_##op: native = simd_bool32x4_##op; break;
 #define FALLTHROUGH(op) case SimdOperation::Fn_##op:
       case SimdType::Int8x16:
         switch (global.simdOperation()) {
           FORALL_INT8X16_ASMJS_OP(SET_NATIVE_INT8X16)
           SET_NATIVE_INT8X16(fromUint8x16Bits)
+          SET_NATIVE_INT8X16(fromUint16x8Bits)
+          SET_NATIVE_INT8X16(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Int16x8:
         switch (global.simdOperation()) {
           FORALL_INT16X8_ASMJS_OP(SET_NATIVE_INT16X8)
+          SET_NATIVE_INT16X8(fromUint8x16Bits)
           SET_NATIVE_INT16X8(fromUint16x8Bits)
+          SET_NATIVE_INT16X8(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Int32x4:
         switch (global.simdOperation()) {
           FORALL_INT32X4_ASMJS_OP(SET_NATIVE_INT32X4)
+          SET_NATIVE_INT32X4(fromUint8x16Bits)
+          SET_NATIVE_INT32X4(fromUint16x8Bits)
           SET_NATIVE_INT32X4(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Uint8x16:
         switch (global.simdOperation()) {
           FORALL_INT8X16_ASMJS_OP(SET_NATIVE_UINT8X16)
           SET_NATIVE_UINT8X16(fromInt8x16Bits)
+          SET_NATIVE_UINT8X16(fromUint16x8Bits)
+          SET_NATIVE_UINT8X16(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Uint16x8:
         switch (global.simdOperation()) {
           FORALL_INT16X8_ASMJS_OP(SET_NATIVE_UINT16X8)
+          SET_NATIVE_UINT16X8(fromUint8x16Bits)
           SET_NATIVE_UINT16X8(fromInt16x8Bits)
+          SET_NATIVE_UINT16X8(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Uint32x4:
         switch (global.simdOperation()) {
           FORALL_INT32X4_ASMJS_OP(SET_NATIVE_UINT32X4)
+          SET_NATIVE_UINT32X4(fromUint8x16Bits)
+          SET_NATIVE_UINT32X4(fromUint16x8Bits)
           SET_NATIVE_UINT32X4(fromInt32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Float32x4:
         switch (global.simdOperation()) {
           FORALL_FLOAT32X4_ASMJS_OP(SET_NATIVE_FLOAT32X4)
+          SET_NATIVE_FLOAT32X4(fromUint8x16Bits)
+          SET_NATIVE_FLOAT32X4(fromUint16x8Bits)
+          SET_NATIVE_FLOAT32X4(fromUint32x4Bits)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
         break;
       case SimdType::Bool8x16:
         switch (global.simdOperation()) {
           FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL8X16)
           default: MOZ_CRASH("shouldn't have been validated in the first place");
         }
--- a/js/src/asmjs/WasmBinaryIterator.cpp
+++ b/js/src/asmjs/WasmBinaryIterator.cpp
@@ -262,18 +262,27 @@ wasm::Classify(Expr expr)
       case Expr::F64ConvertUI64:
       case Expr::F64ReinterpretI64:
       case Expr::F64PromoteF32:
       case Expr::I32x4fromFloat32x4:
       case Expr::I32x4fromFloat32x4U:
       case Expr::F32x4fromInt32x4:
       case Expr::F32x4fromUint32x4:
       case Expr::I32x4fromFloat32x4Bits:
+      case Expr::I32x4fromInt8x16Bits:
+      case Expr::I32x4fromInt16x8Bits:
+      case Expr::I16x8fromInt8x16Bits:
+      case Expr::I16x8fromInt32x4Bits:
+      case Expr::I16x8fromFloat32x4Bits:
+      case Expr::I8x16fromInt16x8Bits:
+      case Expr::I8x16fromInt32x4Bits:
+      case Expr::I8x16fromFloat32x4Bits:
+      case Expr::F32x4fromInt8x16Bits:
+      case Expr::F32x4fromInt16x8Bits:
       case Expr::F32x4fromInt32x4Bits:
-      case Expr::F32x4fromUint32x4Bits:
         return ExprKind::Conversion;
       case Expr::I32Load8S:
       case Expr::I32Load8U:
       case Expr::I32Load16S:
       case Expr::I32Load16U:
       case Expr::I64Load8S:
       case Expr::I64Load8U:
       case Expr::I64Load16S:
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -317,19 +317,17 @@ class FunctionCompiler
     MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryArith::Operation op,
                             MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
         MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type());
         MOZ_ASSERT(lhs->type() == type);
-        auto* ins = MSimdBinaryArith::New(alloc(), lhs, rhs, op);
-        curBlock_->add(ins);
-        return ins;
+        return MSimdBinaryArith::AddLegalized(alloc(), curBlock_, lhs, rhs, op);
     }
 
     MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryBitwise::Operation op,
                             MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
@@ -344,27 +342,35 @@ class FunctionCompiler
                                 SimdSign sign)
     {
         if (inDeadCode())
             return nullptr;
 
         return MSimdBinaryComp::AddLegalized(alloc(), curBlock_, lhs, rhs, op, sign);
     }
 
-    template<class T>
-    MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, typename T::Operation op)
+    MDefinition* binarySimdSaturating(MDefinition* lhs, MDefinition* rhs,
+                                      MSimdBinarySaturating::Operation op, SimdSign sign)
     {
         if (inDeadCode())
             return nullptr;
 
-        T* ins = T::New(alloc(), lhs, rhs, op);
+        auto* ins = MSimdBinarySaturating::New(alloc(), lhs, rhs, op, sign);
         curBlock_->add(ins);
         return ins;
     }
 
+    MDefinition* binarySimdShift(MDefinition* lhs, MDefinition* rhs, MSimdShift::Operation op)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        return MSimdShift::AddLegalized(alloc(), curBlock_, lhs, rhs, op);
+    }
+
     MDefinition* swizzleSimd(MDefinition* vector, const uint8_t lanes[], MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
         MOZ_ASSERT(vector->type() == type);
         MSimdSwizzle* ins = MSimdSwizzle::New(alloc(), vector, lanes);
         curBlock_->add(ins);
@@ -2394,24 +2400,37 @@ EmitSimdBinaryComp(FunctionCompiler& f, 
     if (!f.iter().readSimdComparison(operandType, &lhs, &rhs))
         return false;
 
     f.iter().setResult(f.binarySimdComp(lhs, rhs, op, sign));
     return true;
 }
 
 static bool
+EmitSimdBinarySaturating(FunctionCompiler& f, ValType type, MSimdBinarySaturating::Operation op,
+                         SimdSign sign)
+{
+    MDefinition* lhs;
+    MDefinition* rhs;
+    if (!f.iter().readBinary(type, &lhs, &rhs))
+        return false;
+
+    f.iter().setResult(f.binarySimdSaturating(lhs, rhs, op, sign));
+    return true;
+}
+
+static bool
 EmitSimdShift(FunctionCompiler& f, ValType operandType, MSimdShift::Operation op)
 {
     MDefinition* lhs;
     MDefinition* rhs;
     if (!f.iter().readSimdShiftByScalar(operandType, &lhs, &rhs))
         return false;
 
-    f.iter().setResult(f.binarySimd<MSimdShift>(lhs, rhs, op));
+    f.iter().setResult(f.binarySimdShift(lhs, rhs, op));
     return true;
 }
 
 static ValType
 SimdToLaneType(ValType type)
 {
     switch (type) {
       case ValType::I8x16:
@@ -2514,16 +2533,18 @@ EmitSimdShuffle(FunctionCompiler& f, Val
     f.iter().setResult(f.shuffleSimd(lhs, rhs, lanes, ToMIRType(simdType)));
     return true;
 }
 
 static inline Scalar::Type
 SimdExprTypeToViewType(ValType type, unsigned* defaultNumElems)
 {
     switch (type) {
+        case ValType::I8x16: *defaultNumElems = 16; return Scalar::Int8x16;
+        case ValType::I16x8: *defaultNumElems = 8; return Scalar::Int16x8;
         case ValType::I32x4: *defaultNumElems = 4; return Scalar::Int32x4;
         case ValType::F32x4: *defaultNumElems = 4; return Scalar::Float32x4;
         default:              break;
     }
     MOZ_CRASH("type not handled in SimdExprTypeToViewType");
 }
 
 static bool
@@ -2788,34 +2809,38 @@ EmitSimdOp(FunctionCompiler& f, ValType 
       case SimdOperation::Fn_xor:
         return EmitSimdBinary(f, type, MSimdBinaryBitwise::xor_);
 #define _CASE(OP) \
       case SimdOperation::Fn_##OP: \
         return EmitSimdBinary(f, type, MSimdBinaryArith::Op_##OP);
       FOREACH_NUMERIC_SIMD_BINOP(_CASE)
       FOREACH_FLOAT_SIMD_BINOP(_CASE)
 #undef _CASE
+      case SimdOperation::Fn_addSaturate:
+        return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::add, sign);
+      case SimdOperation::Fn_subSaturate:
+        return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::sub, sign);
       case SimdOperation::Fn_fromFloat32x4:
         return EmitSimdConvert(f, ValType::F32x4, type, sign);
       case SimdOperation::Fn_fromInt32x4:
         return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Signed);
       case SimdOperation::Fn_fromUint32x4:
         return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Unsigned);
+      case SimdOperation::Fn_fromInt8x16Bits:
+      case SimdOperation::Fn_fromUint8x16Bits:
+        return EmitSimdBitcast(f, ValType::I8x16, type);
+      case SimdOperation::Fn_fromUint16x8Bits:
+      case SimdOperation::Fn_fromInt16x8Bits:
+        return EmitSimdBitcast(f, ValType::I16x8, type);
       case SimdOperation::Fn_fromInt32x4Bits:
       case SimdOperation::Fn_fromUint32x4Bits:
         return EmitSimdBitcast(f, ValType::I32x4, type);
       case SimdOperation::Fn_fromFloat32x4Bits:
-      case SimdOperation::Fn_fromInt8x16Bits:
         return EmitSimdBitcast(f, ValType::F32x4, type);
-      case SimdOperation::Fn_fromInt16x8Bits:
-      case SimdOperation::Fn_fromUint8x16Bits:
-      case SimdOperation::Fn_fromUint16x8Bits:
       case SimdOperation::Fn_fromFloat64x2Bits:
-      case SimdOperation::Fn_addSaturate:
-      case SimdOperation::Fn_subSaturate:
         MOZ_CRASH("NYI");
     }
     MOZ_CRASH("unexpected opcode");
 }
 
 static bool
 EmitExpr(FunctionCompiler& f)
 {
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -272,24 +272,48 @@ static_assert(uint64_t(SimdOperation::La
     /* Remaining fields are not used for inlinable natives. They are zero-initialized. */        \
 };
 
 // This list of inlinable types should match the one in jit/InlinableNatives.h.
 #define TDEFN(Name, Func, Operands) DEFN(Float32x4, Name)
 FLOAT32X4_FUNCTION_LIST(TDEFN)
 #undef TDEFN
 
+#define TDEFN(Name, Func, Operands) DEFN(Int8x16, Name)
+INT8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Uint8x16, Name)
+UINT8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Int16x8, Name)
+INT16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Uint16x8, Name)
+UINT16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
 #define TDEFN(Name, Func, Operands) DEFN(Int32x4, Name)
 INT32X4_FUNCTION_LIST(TDEFN)
 #undef TDEFN
 
 #define TDEFN(Name, Func, Operands) DEFN(Uint32x4, Name)
 UINT32X4_FUNCTION_LIST(TDEFN)
 #undef TDEFN
 
+#define TDEFN(Name, Func, Operands) DEFN(Bool8x16, Name)
+BOOL8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Bool16x8, Name)
+BOOL16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
 #define TDEFN(Name, Func, Operands) DEFN(Bool32x4, Name)
 BOOL32X4_FUNCTION_LIST(TDEFN)
 #undef TDEFN
 
 } // namespace jit
 } // namespace js
 
 const JSFunctionSpec Float32x4Defn::Methods[] = {
@@ -305,73 +329,73 @@ const JSFunctionSpec Float64x2Defn::Meth
     JS_FN(#Name, js::simd_float64x2_##Name, Operands, 0),
     FLOAT64X2_FUNCTION_LIST(SIMD_FLOAT64X2_FUNCTION_ITEM)
 #undef SIMD_FLOAT64X2_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Int8x16Defn::Methods[] = {
 #define SIMD_INT8X16_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_int8x16_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_int8x16_##Name, Operands, 0, SimdInt8x16_##Name),
     INT8X16_FUNCTION_LIST(SIMD_INT8X16_FUNCTION_ITEM)
 #undef SIMD_INT8X16_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Int16x8Defn::Methods[] = {
 #define SIMD_INT16X8_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_int16x8_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_int16x8_##Name, Operands, 0, SimdInt16x8_##Name),
     INT16X8_FUNCTION_LIST(SIMD_INT16X8_FUNCTION_ITEM)
 #undef SIMD_INT16X8_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Int32x4Defn::Methods[] = {
 #define SIMD_INT32X4_FUNCTION_ITEM(Name, Func, Operands) \
     JS_INLINABLE_FN(#Name, js::simd_int32x4_##Name, Operands, 0, SimdInt32x4_##Name),
     INT32X4_FUNCTION_LIST(SIMD_INT32X4_FUNCTION_ITEM)
 #undef SIMD_INT32X4_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Uint8x16Defn::Methods[] = {
 #define SIMD_UINT8X16_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_uint8x16_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_uint8x16_##Name, Operands, 0, SimdUint8x16_##Name),
     UINT8X16_FUNCTION_LIST(SIMD_UINT8X16_FUNCTION_ITEM)
 #undef SIMD_UINT8X16_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Uint16x8Defn::Methods[] = {
 #define SIMD_UINT16X8_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_uint16x8_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_uint16x8_##Name, Operands, 0, SimdUint16x8_##Name),
     UINT16X8_FUNCTION_LIST(SIMD_UINT16X8_FUNCTION_ITEM)
 #undef SIMD_UINT16X8_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Uint32x4Defn::Methods[] = {
 #define SIMD_UINT32X4_FUNCTION_ITEM(Name, Func, Operands) \
     JS_INLINABLE_FN(#Name, js::simd_uint32x4_##Name, Operands, 0, SimdUint32x4_##Name),
     UINT32X4_FUNCTION_LIST(SIMD_UINT32X4_FUNCTION_ITEM)
 #undef SIMD_UINT32X4_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Bool8x16Defn::Methods[] = {
 #define SIMD_BOOL8X16_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_bool8x16_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_bool8x16_##Name, Operands, 0, SimdBool8x16_##Name),
     BOOL8X16_FUNCTION_LIST(SIMD_BOOL8X16_FUNCTION_ITEM)
 #undef SIMD_BOOL8X16_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Bool16x8Defn::Methods[] = {
 #define SIMD_BOOL16X8_FUNCTION_ITEM(Name, Func, Operands) \
-    JS_FN(#Name, js::simd_bool16x8_##Name, Operands, 0),
+    JS_INLINABLE_FN(#Name, js::simd_bool16x8_##Name, Operands, 0, SimdBool16x8_##Name),
     BOOL16X8_FUNCTION_LIST(SIMD_BOOL16X8_FUNCTION_ITEM)
 #undef SIMD_BOOL16X8_FUNCTION_ITEM
     JS_FS_END
 };
 
 const JSFunctionSpec Bool32x4Defn::Methods[] = {
 #define SIMD_BOOL32X4_FUNCTION_ITEM(Name, Func, Operands) \
     JS_INLINABLE_FN(#Name, js::simd_bool32x4_##Name, Operands, 0, SimdBool32x4_##Name),
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -741,42 +741,51 @@
     _(fromUint32x4)                   \
     _(fromUint32x4Bits)
 
 // All operations on Int8x16 or Uint8x16 in the asm.js world.
 // Note: this does not include conversions and casts to/from Uint8x16 because
 // this list is shared between Int8x16 and Uint8x16.
 #define FORALL_INT8X16_ASMJS_OP(_)    \
     FORALL_INT_SIMD_OP(_)             \
-    FOREACH_SMINT_SIMD_BINOP(_)
+    FOREACH_SMINT_SIMD_BINOP(_)       \
+    _(fromInt16x8Bits)                \
+    _(fromInt32x4Bits)                \
+    _(fromFloat32x4Bits)
 
 // All operations on Int16x8 or Uint16x8 in the asm.js world.
 // Note: this does not include conversions and casts to/from Uint16x8 because
 // this list is shared between Int16x8 and Uint16x8.
 #define FORALL_INT16X8_ASMJS_OP(_)    \
     FORALL_INT_SIMD_OP(_)             \
-    FOREACH_SMINT_SIMD_BINOP(_)
+    FOREACH_SMINT_SIMD_BINOP(_)       \
+    _(fromInt8x16Bits)                \
+    _(fromInt32x4Bits)                \
+    _(fromFloat32x4Bits)
 
 // All operations on Int32x4 or Uint32x4 in the asm.js world.
 // Note: this does not include conversions and casts to/from Uint32x4 because
 // this list is shared between Int32x4 and Uint32x4.
 #define FORALL_INT32X4_ASMJS_OP(_)    \
     FORALL_INT_SIMD_OP(_)             \
     FOREACH_MEMORY_X4_SIMD_OP(_)      \
+    _(fromInt8x16Bits)                \
+    _(fromInt16x8Bits)                \
     _(fromFloat32x4)                  \
     _(fromFloat32x4Bits)
 
 // All operations on Float32X4 in the asm.js world.
 #define FORALL_FLOAT32X4_ASMJS_OP(_)  \
     FORALL_FLOAT_SIMD_OP(_)           \
     FOREACH_MEMORY_X4_SIMD_OP(_)      \
-    _(fromInt32x4)                    \
+    _(fromInt8x16Bits)                \
+    _(fromInt16x8Bits)                \
     _(fromInt32x4Bits)                \
-    _(fromUint32x4)                   \
-    _(fromUint32x4Bits)
+    _(fromInt32x4)                    \
+    _(fromUint32x4)
 
 namespace js {
 
 // Complete set of SIMD types.
 // It must be kept in sync with the enumeration of values in
 // TypedObjectConstants.h; in particular we need to ensure that Count is
 // appropriately set with respect to the number of actual types.
 enum class SimdType {
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -249,16 +249,18 @@ ScalarTypeDescr::alignment(Type t)
 ScalarTypeDescr::typeName(Type type)
 {
     switch (type) {
 #define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
         case constant_: return #name_;
         JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
 #undef NUMERIC_TYPE_TO_STRING
       case Scalar::Float32x4:
+      case Scalar::Int8x16:
+      case Scalar::Int16x8:
       case Scalar::Int32x4:
       case Scalar::MaxTypedArrayViewType:
         MOZ_CRASH();
     }
     MOZ_CRASH("Invalid type");
 }
 
 bool
@@ -287,16 +289,18 @@ ScalarTypeDescr::call(JSContext* cx, uns
           type_ converted = ConvertScalar<type_>(number);                     \
           args.rval().setNumber((double) converted);                          \
           return true;                                                        \
       }
 
         JS_FOR_EACH_SCALAR_TYPE_REPR(SCALARTYPE_CALL)
 #undef SCALARTYPE_CALL
       case Scalar::Float32x4:
+      case Scalar::Int8x16:
+      case Scalar::Int16x8:
       case Scalar::Int32x4:
       case Scalar::MaxTypedArrayViewType:
         MOZ_CRASH();
     }
     return true;
 }
 
 /***************************************************************************
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -245,16 +245,20 @@ class ScalarTypeDescr : public SimpleTyp
         static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32,
                       "TypedObjectConstants.h must be consistent with Scalar::Type");
         static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64,
                       "TypedObjectConstants.h must be consistent with Scalar::Type");
         static_assert(Scalar::Uint8Clamped == JS_SCALARTYPEREPR_UINT8_CLAMPED,
                       "TypedObjectConstants.h must be consistent with Scalar::Type");
         static_assert(Scalar::Float32x4 == JS_SCALARTYPEREPR_FLOAT32X4,
                       "TypedObjectConstants.h must be consistent with Scalar::Type");
+        static_assert(Scalar::Int8x16 == JS_SCALARTYPEREPR_INT8X16,
+                      "TypedObjectConstants.h must be consistent with Scalar::Type");
+        static_assert(Scalar::Int16x8 == JS_SCALARTYPEREPR_INT16X8,
+                      "TypedObjectConstants.h must be consistent with Scalar::Type");
         static_assert(Scalar::Int32x4 == JS_SCALARTYPEREPR_INT32X4,
                       "TypedObjectConstants.h must be consistent with Scalar::Type");
 
         return Type(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32());
     }
 
     static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
 };
--- a/js/src/builtin/TypedObjectConstants.h
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -85,17 +85,19 @@
 #define JS_SCALARTYPEREPR_INT16         2
 #define JS_SCALARTYPEREPR_UINT16        3
 #define JS_SCALARTYPEREPR_INT32         4
 #define JS_SCALARTYPEREPR_UINT32        5
 #define JS_SCALARTYPEREPR_FLOAT32       6
 #define JS_SCALARTYPEREPR_FLOAT64       7
 #define JS_SCALARTYPEREPR_UINT8_CLAMPED 8
 #define JS_SCALARTYPEREPR_FLOAT32X4     10
-#define JS_SCALARTYPEREPR_INT32X4       11
+#define JS_SCALARTYPEREPR_INT8X16       11
+#define JS_SCALARTYPEREPR_INT16X8       12
+#define JS_SCALARTYPEREPR_INT32X4       13
 
 // These constants are for use exclusively in JS code. In C++ code,
 // prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows
 // you to write a switch which will receive a warning if you omit a
 // case.
 #define JS_REFERENCETYPEREPR_ANY        0
 #define JS_REFERENCETYPEREPR_OBJECT     1
 #define JS_REFERENCETYPEREPR_STRING     2
--- a/js/src/devtools/rootAnalysis/CFG.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -65,17 +65,17 @@ function allRAIIGuardedCallPoints(bodies
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
         var callee = edge.Exp[0];
         if (callee.Kind != "Var")
             continue;
         var variable = callee.Variable;
         assert(variable.Kind == "Func");
-        if (!isConstructor(variable.Name))
+        if (!isConstructor(edge.Type, variable.Name))
             continue;
         if (!("PEdgeCallInstance" in edge))
             continue;
         if (edge.PEdgeCallInstance.Exp.Kind != "Var")
             continue;
 
         Array.prototype.push.apply(points, pointsInRAIIScope(bodies, body, edge));
     }
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -67,36 +67,39 @@ def generate_hazards(config, outfilename
     jobs = []
     for i in range(int(config['jobs'])):
         command = fill(('%(js)s',
                         '%(analysis_scriptdir)s/analyzeRoots.js',
                         '%(gcFunctions_list)s',
                         '%(gcEdges)s',
                         '%(suppressedFunctions_list)s',
                         '%(gcTypes)s',
+                        '%(typeInfo)s',
                         str(i+1), '%(jobs)s',
                         'tmp.%s' % (i+1,)),
                        config)
         outfile = 'rootingHazards.%s' % (i+1,)
         output = open(outfile, 'w')
-        print_command(command, outfile=outfile, env=env(config))
+        if config['verbose']:
+            print_command(command, outfile=outfile, env=env(config))
         jobs.append((command, Popen(command, stdout=output, env=env(config))))
 
     final_status = 0
     while jobs:
         pid, status = os.wait()
         jobs = [ job for job in jobs if job[1].pid != pid ]
         final_status = final_status or status
 
     if final_status:
         raise subprocess.CalledProcessError(final_status, 'analyzeRoots.js')
 
     with open(outfilename, 'w') as output:
         command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(int(config['jobs'])) ]
-        print_command(command, outfile=outfilename)
+        if config['verbose']:
+            print_command(command, outfile=outfilename)
         subprocess.call(command, stdout=output)
 
 JOBS = { 'dbs':
              (('%(ANALYSIS_SCRIPTDIR)s/run_complete',
                '--foreground',
                '--no-logs',
                '--build-root=%(objdir)s',
                '--wrap-dir=%(sixgill)s/scripts/wrap_gcc',
@@ -106,27 +109,28 @@ JOBS = { 'dbs':
                '.'),
               ()),
 
          'list-dbs':
              (('ls', '-l'),
               ()),
 
          'callgraph':
-             (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js'),
+             (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js', '%(typeInfo)s'),
               'callgraph.txt'),
 
          'gcFunctions':
              (('%(js)s', '%(analysis_scriptdir)s/computeGCFunctions.js', '%(callgraph)s',
                '[gcFunctions]', '[gcFunctions_list]', '[gcEdges]', '[suppressedFunctions_list]'),
               ('gcFunctions.txt', 'gcFunctions.lst', 'gcEdges.txt', 'suppressedFunctions.lst')),
 
          'gcTypes':
-             (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',),
-              'gcTypes.txt'),
+             (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',
+               '[gcTypes]', '[typeInfo]'),
+              ('gcTypes.txt', 'typeInfo.txt')),
 
          'allFunctions':
              (('%(sixgill_bin)s/xdbkeys', 'src_body.xdb',),
               'allFunctions.txt'),
 
          'hazards':
              (generate_hazards, 'rootingHazards.txt'),
 
@@ -150,25 +154,27 @@ def run_job(name, config):
     if hasattr(cmdspec, '__call__'):
         cmdspec(config, outfiles)
     else:
         temp_map = {}
         cmdspec = fill(cmdspec, config)
         if isinstance(outfiles, basestring):
             stdout_filename = '%s.tmp' % name
             temp_map[stdout_filename] = outfiles
-            print_command(cmdspec, outfile=outfiles, env=env(config))
+            if config['verbose']:
+                print_command(cmdspec, outfile=outfiles, env=env(config))
         else:
             stdout_filename = None
             pc = list(cmdspec)
             outfile = 0
             for (i, name) in out_indexes(cmdspec):
                 pc[i] = outfiles[outfile]
                 outfile += 1
-            print_command(pc, env=env(config))
+            if config['verbose']:
+                print_command(pc, env=env(config))
 
         command = list(cmdspec)
         outfile = 0
         for (i, name) in out_indexes(cmdspec):
             command[i] = '%s.tmp' % name
             temp_map[command[i]] = outfiles[outfile]
             outfile += 1
 
@@ -185,25 +191,16 @@ def run_job(name, config):
                 print("Error renaming %s -> %s" % (temp, final))
                 raise
 
 config = { 'ANALYSIS_SCRIPTDIR': os.path.dirname(__file__) }
 
 defaults = [ '%s/defaults.py' % config['ANALYSIS_SCRIPTDIR'],
              '%s/defaults.py' % os.getcwd() ]
 
-for default in defaults:
-    try:
-        execfile(default, config)
-        print("Loaded %s" % default)
-    except:
-        pass
-
-data = config.copy()
-
 parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
 parser.add_argument('step', metavar='STEP', type=str, nargs='?',
                     help='run starting from this step')
 parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
                     help='source code to analyze')
 parser.add_argument('--objdir', metavar='DIR', type=str, nargs='?',
                     help='object directory of compiled files')
 parser.add_argument('--js', metavar='JSSHELL', type=str, nargs='?',
@@ -215,29 +212,42 @@ parser.add_argument('--jobs', '-j', defa
 parser.add_argument('--list', const=True, nargs='?', type=bool,
                     help='display available steps')
 parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
                     help='command to build the tree being analyzed')
 parser.add_argument('--tag', '-t', type=str, nargs='?',
                     help='name of job, also sets build command to "build.<tag>"')
 parser.add_argument('--expect-file', type=str, nargs='?',
                     help='deprecated option, temporarily still present for backwards compatibility')
+parser.add_argument('--verbose', '-v', action='store_true',
+                    help='Display cut & paste commands to run individual steps')
 
 args = parser.parse_args()
+
+for default in defaults:
+    try:
+        execfile(default, config)
+        if args.verbose:
+            print("Loaded %s" % default)
+    except:
+        pass
+
+data = config.copy()
+
 for k,v in vars(args).items():
     if v is not None:
         data[k] = v
 
 if args.tag and not args.buildcommand:
     args.buildcommand="build.%s" % args.tag
 
 if args.jobs is not None:
     data['jobs'] = args.jobs
 if not data.get('jobs'):
-    data['jobs'] = subprocess.check_output(['nproc', '--ignore=1'])
+    data['jobs'] = subprocess.check_output(['nproc', '--ignore=1']).strip()
 
 if args.buildcommand:
     data['buildcommand'] = args.buildcommand
 elif 'BUILD' in os.environ:
     data['buildcommand'] = os.environ['BUILD']
 else:
     data['buildcommand'] = 'make -j4 -s'
 
@@ -246,18 +256,18 @@ if 'ANALYZED_OBJDIR' in os.environ:
 
 if 'SOURCE' in os.environ:
     data['source'] = os.environ['SOURCE']
 if not data.get('source') and data.get('sixgill_bin'):
     path = subprocess.check_output(['sh', '-c', data['sixgill_bin'] + '/xdbkeys file_source.xdb | grep jsapi.cpp'])
     data['source'] = path.replace("/js/src/jsapi.cpp", "")
 
 steps = [ 'dbs',
+          'gcTypes',
           'callgraph',
-          'gcTypes',
           'gcFunctions',
           'allFunctions',
           'hazards',
           'explain' ]
 
 if args.list:
     for step in steps:
         command, outfilename = JOBS[step]
@@ -271,17 +281,17 @@ for step in steps:
     command, outfiles = JOBS[step]
     if isinstance(outfiles, basestring):
         data[step] = outfiles
     else:
         outfile = 0
         for (i, name) in out_indexes(command):
             data[name] = outfiles[outfile]
             outfile += 1
-        assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files and params' % step
+        assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files (%d) and params (%d)' % (step, outfile, len(outfiles))
 
 if args.step:
     steps = steps[steps.index(args.step):]
 
 if args.upto:
     steps = steps[:steps.index(args.upto)+1]
 
 for step in steps:
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -7,31 +7,34 @@ loadRelativeToScript('annotations.js');
 loadRelativeToScript('CFG.js');
 
 var sourceRoot = (os.getenv('SOURCE') || '') + '/'
 
 var functionName;
 var functionBodies;
 
 if (typeof scriptArgs[0] != 'string' || typeof scriptArgs[1] != 'string')
-    throw "Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
+    throw "Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> <typeInfo.txt> [start end [tmpfile]]";
 
 var theFunctionNameToFind;
 if (scriptArgs[0] == '--function') {
     theFunctionNameToFind = scriptArgs[1];
     scriptArgs = scriptArgs.slice(2);
 }
 
-var gcFunctionsFile = scriptArgs[0];
-var gcEdgesFile = scriptArgs[1];
-var suppressedFunctionsFile = scriptArgs[2];
-var gcTypesFile = scriptArgs[3];
-var batch = (scriptArgs[4]|0) || 1;
-var numBatches = (scriptArgs[5]|0) || 1;
-var tmpfile = scriptArgs[6] || "tmp.txt";
+var gcFunctionsFile = scriptArgs[0] || "gcFunctions.lst";
+var gcEdgesFile = scriptArgs[1] || "gcEdges.txt";
+var suppressedFunctionsFile = scriptArgs[2] || "suppressedFunctions.lst";
+var gcTypesFile = scriptArgs[3] || "gcTypes.txt";
+var typeInfoFile = scriptArgs[4] || "typeInfo.txt";
+var batch = (scriptArgs[5]|0) || 1;
+var numBatches = (scriptArgs[6]|0) || 1;
+var tmpfile = scriptArgs[7] || "tmp.txt";
+
+GCSuppressionTypes = loadTypeInfo(typeInfoFile)["Suppress GC"] || [];
 
 var gcFunctions = {};
 var text = snarf("gcFunctions.lst").split("\n");
 assert(text.pop().length == 0);
 for (var line of text)
     gcFunctions[mangled(line)] = true;
 
 var suppressedFunctions = {};
@@ -326,41 +329,67 @@ function edgeCanGC(edge)
 //
 // If so:
 //
 //  - 'why': a path from the GC call to a use of the variable after the GC
 //    call, chained through a 'why' field in the returned edge descriptor
 //
 //  - 'gcInfo': a direct pointer to the GC call edge
 //
-function findGCBeforeVariableUse(suppressed, variable, worklist)
+function findGCBeforeVariableUse(start_body, start_point, suppressed, variable)
 {
     // Scan through all edges preceding an unrooted variable use, using an
     // explicit worklist, looking for a GC call. A worklist contains an
     // incoming edge together with a description of where it or one of its
     // successors GC'd (if any).
 
+    var bodies_visited = new Map();
+
+    let worklist = [{body: start_body, ppoint: start_point, preGCLive: false, gcInfo: null, why: null}];
     while (worklist.length) {
+        // Grab an entry off of the worklist, representing a point within the
+        // CFG identified by <body,ppoint>. If this point has a descendant
+        // later in the CFG that can GC, gcInfo will be set to the information
+        // about that GC call.
+
         var entry = worklist.pop();
-        var { body, ppoint, gcInfo } = entry;
+        var { body, ppoint, gcInfo, preGCLive } = entry;
+
+        // Handle the case where there are multiple ways to reach this point
+        // (traversing backwards).
+        var visited = bodies_visited.get(body);
+        if (!visited)
+            bodies_visited.set(body, visited = new Map());
+        if (visited.has(ppoint)) {
+            var seenEntry = visited.get(ppoint);
 
-        if (body.seen) {
-            if (ppoint in body.seen) {
-                var seenEntry = body.seen[ppoint];
-                if (!gcInfo || seenEntry.gcInfo)
-                    continue;
-            }
-        } else {
-            body.seen = [];
+            // This point already knows how to GC through some other path, so
+            // we have nothing new to learn. (The other path will consider the
+            // predecessors.)
+            if (seenEntry.gcInfo)
+                continue;
+
+            // If this worklist's entry doesn't know of any way to GC, then
+            // there's no point in continuing the traversal through it. Perhaps
+            // another edge will be found that *can* GC; otherwise, the first
+            // route to the point will traverse through predecessors.
+            //
+            // Note that this means we may visit a point more than once, if the
+            // first time we visit we don't have a known reachable GC call and
+            // the second time we do.
+            if (!gcInfo)
+                continue;
         }
-        body.seen[ppoint] = {body: body, gcInfo: gcInfo};
+        visited.set(ppoint, {body: body, gcInfo: gcInfo});
 
+        // Check for hitting the entry point of the current body (which may be
+        // the outer function or a loop within it.)
         if (ppoint == body.Index[0]) {
             if (body.BlockId.Kind == "Loop") {
-                // propagate to parents that enter the loop body.
+                // Propagate to outer body parents that enter the loop body.
                 if ("BlockPPoint" in body) {
                     for (var parent of body.BlockPPoint) {
                         var found = false;
                         for (var xbody of functionBodies) {
                             if (sameBlockId(xbody.BlockId, parent.BlockId)) {
                                 assert(!found);
                                 found = true;
                                 worklist.push({body: xbody, ppoint: parent.Index,
@@ -368,17 +397,23 @@ function findGCBeforeVariableUse(suppres
                             }
                         }
                         assert(found);
                     }
                 }
             } else if (variable.Kind == "Arg" && gcInfo) {
                 // The scope of arguments starts at the beginning of the
                 // function
-                return {gcInfo: gcInfo, why: entry};
+                return entry;
+            } else if (entry.preGCLive) {
+                // We didn't find a "good" explanation beginning of the live
+                // range, but we do know the variable was live across the GC.
+                // This can happen if the live range started when a variable is
+                // used as a retparam.
+                return entry;
             }
         }
 
         var predecessors = getPredecessors(body);
         if (!(ppoint in predecessors))
             continue;
 
         for (var edge of predecessors[ppoint]) {
@@ -394,86 +429,114 @@ function findGCBeforeVariableUse(suppres
 
             if (edge_kills) {
                 // This is a beginning of the variable's live range. If we can
                 // reach a GC call from here, then we're done -- we have a path
                 // from the beginning of the live range, through the GC call,
                 // to a use after the GC call that proves its live range
                 // extends at least that far.
                 if (gcInfo)
-                    return {gcInfo: gcInfo, why: {body: body, ppoint: source, gcInfo: gcInfo, why: entry } }
+                    return {body: body, ppoint: source, gcInfo: gcInfo, why: entry };
 
-                // Otherwise, we want to continue searching for the true
-                // minimumUse, for use in reporting unnecessary rooting, but we
-                // truncate this particular branch of the search at this edge.
+                // Otherwise, keep searching through the graph, but truncate
+                // this particular branch of the search at this edge.
                 continue;
             }
 
+            var src_gcInfo = gcInfo;
+            var src_preGCLive = preGCLive;
             if (!gcInfo && !(source in body.suppressed) && !suppressed) {
                 var gcName = edgeCanGC(edge, body);
                 if (gcName)
-                    gcInfo = {name:gcName, body:body, ppoint:source};
+                    src_gcInfo = {name:gcName, body:body, ppoint:source};
             }
 
             if (edge_uses) {
                 // The live range starts at least this far back, so we're done
-                // for the same reason as with edge_kills.
-                if (gcInfo)
-                    return {gcInfo:gcInfo, why:entry};
+                // for the same reason as with edge_kills. The only difference
+                // is that a GC on this edge indicates a hazard, whereas if
+                // we're killing a live range in the GC call then it's not live
+                // *across* the call.
+                //
+                // However, we may want to generate a longer usage chain for
+                // the variable than is minimally necessary. For example,
+                // consider:
+                //
+                //   Value v = f();
+                //   if (v.isUndefined())
+                //     return false;
+                //   gc();
+                //   return v;
+                //
+                // The call to .isUndefined() is considered to be a use and
+                // therefore indicates that v must be live at that point. But
+                // it's more helpful to the user to continue the 'why' path to
+                // include the ancestor where the value was generated. So we
+                // will only return here if edge.Kind is Assign; otherwise,
+                // we'll pass a "preGCLive" value up through the worklist to
+                // remember that the variable *is* alive before the GC and so
+                // this function should be returning a true value.
+
+                if (src_gcInfo) {
+                    src_preGCLive = true;
+                    if (edge.Kind == 'Assign')
+                        return {body:body, ppoint:source, gcInfo:src_gcInfo, why:entry};
+                }
             }
 
             if (edge.Kind == "Loop") {
                 // Additionally propagate the search into a loop body, starting
                 // with the exit point.
                 var found = false;
                 for (var xbody of functionBodies) {
                     if (sameBlockId(xbody.BlockId, edge.BlockId)) {
                         assert(!found);
                         found = true;
                         worklist.push({body:xbody, ppoint:xbody.Index[1],
-                                       gcInfo:gcInfo, why:entry});
+                                       preGCLive: src_preGCLive, gcInfo:src_gcInfo,
+                                       why:entry});
                     }
                 }
                 assert(found);
                 break;
             }
 
             // Propagate the search to the predecessors of this edge.
-            worklist.push({body:body, ppoint:source, gcInfo:gcInfo, why:entry});
+            worklist.push({body:body, ppoint:source,
+                           preGCLive: src_preGCLive, gcInfo:src_gcInfo,
+                           why:entry});
         }
     }
 
     return null;
 }
 
 function variableLiveAcrossGC(suppressed, variable)
 {
-    // A variable is live across a GC if (1) it is used by an edge, and (2) it
-    // is used after a GC in a successor edge.
+    // A variable is live across a GC if (1) it is used by an edge (as in, it
+    // was at least initialized), and (2) it is used after a GC in a successor
+    // edge.
 
-    for (var body of functionBodies) {
-        body.seen = null;
+    for (var body of functionBodies)
         body.minimumUse = 0;
-    }
 
     for (var body of functionBodies) {
         if (!("PEdge" in body))
             continue;
         for (var edge of body.PEdge) {
             var usePoint = edgeUsesVariable(edge, variable, body);
             // Example for !edgeKillsVariable:
             //
             //   JSObject* obj = NewObject();
             //   cangc();
             //   obj = NewObject();    <-- uses 'obj', but kills previous value
             //
             if (usePoint && !edgeKillsVariable(edge, variable)) {
                 // Found a use, possibly after a GC.
-                var worklist = [{body:body, ppoint:usePoint, gcInfo:null, why:null}];
-                var call = findGCBeforeVariableUse(suppressed, variable, worklist);
+                var call = findGCBeforeVariableUse(body, usePoint, suppressed, variable);
                 if (!call)
                     continue;
 
                 call.afterGCUse = usePoint;
                 return call;
             }
         }
     }
@@ -496,17 +559,19 @@ function unsafeVariableAddressTaken(supp
                 if (edge.Kind == "Assign" || (!suppressed && edgeCanGC(edge)))
                     return {body:body, ppoint:edge.Index[0]};
             }
         }
     }
     return null;
 }
 
-function computePrintedLines(functionName)
+// Read out the brief (non-JSON, semi-human-readable) CFG description for the
+// given function and store it.
+function loadPrintedLines(functionName)
 {
     assert(!os.system("xdbfind src_body.xdb '" + functionName + "' > " + tmpfile));
     var lines = snarf(tmpfile).split('\n');
 
     for (var body of functionBodies)
         body.lines = [];
 
     // Distribute lines of output to the block they originate from.
@@ -531,54 +596,62 @@ function computePrintedLines(functionNam
                 }
             }
         }
         if (currentBody)
             currentBody.lines.push(line);
     }
 }
 
-function findLocation(body, ppoint)
+function findLocation(body, ppoint, opts={brief: false})
 {
     var location = body.PPoint[ppoint - 1].Location;
-    var text = location.CacheString + ":" + location.Line;
-    if (text.indexOf(sourceRoot) == 0)
-        return text.substring(sourceRoot.length);
-    return text;
+    var file = location.CacheString;
+
+    if (file.indexOf(sourceRoot) == 0)
+        file = file.substring(sourceRoot.length);
+
+    if (opts.brief) {
+        var m = /.*\/(.*)/.exec(file);
+        if (m)
+            file = m[1];
+    }
+
+    return file + ":" + location.Line;
 }
 
 function locationLine(text)
 {
     if (match = /:(\d+)$/.exec(text))
         return match[1];
     return 0;
 }
 
 function printEntryTrace(functionName, entry)
 {
     var gcPoint = entry.gcInfo ? entry.gcInfo.ppoint : 0;
 
     if (!functionBodies[0].lines)
-        computePrintedLines(functionName);
+        loadPrintedLines(functionName);
 
     while (entry) {
         var ppoint = entry.ppoint;
-        var lineText = findLocation(entry.body, ppoint);
+        var lineText = findLocation(entry.body, ppoint, {"brief": true});
 
         var edgeText = "";
         if (entry.why && entry.why.body == entry.body) {
             // If the next point in the trace is in the same block, look for an edge between them.
             var next = entry.why.ppoint;
 
             if (!entry.body.edgeTable) {
                 var table = {};
                 entry.body.edgeTable = table;
                 for (var line of entry.body.lines) {
-                    if (match = /\((\d+),(\d+),/.exec(line))
-                        table[match[1] + "," + match[2]] = line; // May be multiple?
+                    if (match = /\((\d+,\d+),/.exec(line))
+                        table[match[1]] = line; // May be multiple?
                 }
             }
 
             edgeText = entry.body.edgeTable[ppoint + "," + next];
             assert(edgeText);
             if (ppoint == gcPoint)
                 edgeText += " [[GC call]]";
         } else {
@@ -654,17 +727,17 @@ function processBodies(functionName)
             var result = variableLiveAcrossGC(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.gcInfo.body, result.gcInfo.ppoint);
                 print("\nFunction '" + functionName + "'" +
                       " has unrooted '" + name + "'" +
                       " of type '" + typeDesc(variable.Type) + "'" +
                       " live across GC call " + result.gcInfo.name +
                       " at " + lineText);
-                printEntryTrace(functionName, result.why);
+                printEntryTrace(functionName, result);
             }
             result = unsafeVariableAddressTaken(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.body, result.ppoint);
                 print("\nFunction '" + functionName + "'" +
                       " takes unsafe address of unrooted '" + name + "'" +
                       " at " + lineText);
                 printEntryTrace(functionName, {body:result.body, ppoint:result.ppoint});
@@ -678,19 +751,19 @@ if (batch == 1)
 
 var xdb = xdbLibrary();
 xdb.open("src_body.xdb");
 
 var minStream = xdb.min_data_stream()|0;
 var maxStream = xdb.max_data_stream()|0;
 
 var N = (maxStream - minStream) + 1;
-var each = Math.floor(N/numBatches);
-var start = minStream + each * (batch - 1);
-var end = Math.min(minStream + each * batch - 1, maxStream);
+var start = Math.floor((batch - 1) / numBatches * N) + minStream;
+var start_next = Math.floor(batch / numBatches * N) + minStream;
+var end = start_next - 1;
 
 function process(name, json) {
     functionName = name;
     functionBodies = JSON.parse(json);
 
     for (var body of functionBodies)
         body.suppressed = [];
     for (var body of functionBodies) {
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -1,12 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
+// RAII types within which we should assume GC is suppressed, eg
+// AutoSuppressGC.
+var GCSuppressionTypes = [];
+
 // Ignore calls made through these function pointers
 var ignoreIndirectCalls = {
     "mallocSizeOf" : true,
     "aMallocSizeOf" : true,
     "_malloc_message" : true,
     "je_malloc_message" : true,
     "chunk_dalloc" : true,
     "chunk_alloc" : true,
@@ -76,16 +80,17 @@ var ignoreCallees = {
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
     "z_stream_s.zalloc" : true,
     "GrGLInterface.fCallback" : true,
     "std::strstreambuf._M_alloc_fun" : true,
     "std::strstreambuf._M_free_fun" : true,
     "struct js::gc::Callback<void (*)(JSRuntime*, void*)>.op" : true,
+    "mozilla::ThreadSharedFloatArrayBufferList::Storage.mFree" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
@@ -141,20 +146,27 @@ function ignoreEdgeAddressTaken(edge)
             if (/js::Invoke\(/.test(name))
                 return true;
         }
     }
 
     return false;
 }
 
+// Return whether csu.method is one that we claim can never GC.
+function isSuppressedVirtualMethod(csu, method)
+{
+    return csu == "nsISupports" && (method == "AddRef" || method == "Release");
+}
+
 // Ignore calls of these functions (so ignore any stack containing these)
 var ignoreFunctions = {
     "ptio.c:pt_MapError" : true,
     "je_malloc_printf" : true,
+    "vprintf_stderr" : true,
     "PR_ExplodeTime" : true,
     "PR_ErrorInstallTable" : true,
     "PR_SetThreadPrivate" : true,
     "JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoSuppressGCAnalysis instead
     "uint8 NS_IsMainThread()" : true,
 
     // Has an indirect call under it by the name "__f", which seemed too
     // generic to ignore by itself.
@@ -175,16 +187,17 @@ var ignoreFunctions = {
 
     // FIXME!
     "NS_DebugBreak": true,
 
     // These are a little overzealous -- these destructors *can* GC if they end
     // up wrapping a pending exception. See bug 898815 for the heavyweight fix.
     "void js::AutoCompartment::~AutoCompartment(int32)" : true,
     "void JSAutoCompartment::~JSAutoCompartment(int32)" : true,
+    "void js::AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM()" : true,
 
     // Bug 948646 - the only thing AutoJSContext's constructor calls
     // is an Init() routine whose entire body is covered with an
     // AutoSuppressGCAnalysis. AutoSafeJSContext is the same thing, just with
     // a different value for the 'aSafe' parameter.
     "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
     "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
 
@@ -207,16 +220,29 @@ var ignoreFunctions = {
     // code we fall back to only the dynamic checks.
     "void test::RingbufferDumper::OnTestPartResult(testing::TestPartResult*)" : true,
 
     "float64 JS_GetCurrentEmbedderTime()" : true,
 
     "uint64 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true,
     "uint32 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true,
     "void js::Nursery::freeMallocedBuffers()" : true,
+
+    // It would be cool to somehow annotate that nsTHashtable<T> will use
+    // nsTHashtable<T>::s_MatchEntry for its matchEntry function pointer, but
+    // there is no mechanism for that. So we will just annotate a particularly
+    // troublesome logging-related usage.
+    "EntryType* nsTHashtable<EntryType>::PutEntry(nsTHashtable<EntryType>::KeyType) [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
+    "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
+    "EntryType* nsTHashtable<EntryType>::PutEntry(nsTHashtable<EntryType>::KeyType) [with EntryType = nsBaseHashtableET<nsPtrHashKey<const mozilla::BlockingResourceBase>, nsAutoPtr<mozilla::DeadlockDetector<mozilla::BlockingResourceBase>::OrderingEntry> >; nsTHashtable<EntryType>::KeyType = const mozilla::BlockingResourceBase*]" : true,
+    "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsPtrHashKey<const mozilla::BlockingResourceBase>, nsAutoPtr<mozilla::DeadlockDetector<mozilla::BlockingResourceBase>::OrderingEntry> >; nsTHashtable<EntryType>::KeyType = const mozilla::BlockingResourceBase*]" : true,
+
+    // The big hammers.
+    "PR_GetCurrentThread" : true,
+    "calloc" : true,
 };
 
 function isProtobuf(name)
 {
     return name.match(/\bgoogle::protobuf\b/) ||
            name.match(/\bmozilla::devtools::protobuf\b/);
 }
 
@@ -308,26 +334,41 @@ function isRootedTypeName(name)
 }
 
 function isUnsafeStorage(typeName)
 {
     typeName = stripUCSAndNamespace(typeName);
     return typeName.startsWith('UniquePtr<');
 }
 
-function isSuppressConstructor(varName)
+function isSuppressConstructor(edgeType, varName)
 {
-    // varName[1] contains the unqualified name
-    return [
-        "AutoSuppressGC",
-        "AutoAssertGCCallback",
-        "AutoEnterAnalysis",
-        "AutoSuppressGCAnalysis",
-        "AutoIgnoreRootingHazards"
-    ].indexOf(varName[1]) != -1;
+    // Check whether this could be a constructor
+    if (edgeType.Kind != 'Function')
+        return false;
+    if (!('TypeFunctionCSU' in edgeType))
+        return false;
+    if (edgeType.Type.Kind != 'Void')
+        return false;
+
+    // Check whether the type is a known suppression type.
+    var type = edgeType.TypeFunctionCSU.Type.Name;
+    if (GCSuppressionTypes.indexOf(type) == -1)
+        return false;
+
+    // And now make sure this is the constructor, not some other method on a
+    // suppression type. varName[0] contains the qualified name.
+    var [ mangled, unmangled ] = splitFunction(varName[0]);
+    if (mangled.search(/C\dE/) == -1)
+        return false; // Mangled names of constructors have C<num>E
+    var m = unmangled.match(/([~\w]+)(?:<.*>)?\(/);
+    if (!m)
+        return false;
+    var type_stem = type.replace(/\w+::/g, '').replace(/\<.*\>/g, '');
+    return m[1] == type_stem;
 }
 
 // nsISupports subclasses' methods may be scriptable (or overridden
 // via binary XPCOM), and so may GC. But some fields just aren't going
 // to get overridden with something that can GC.
 function isOverridableField(initialCSU, csu, field)
 {
     if (csu != 'nsISupports')
--- a/js/src/devtools/rootAnalysis/build/sixgill.manifest
+++ b/js/src/devtools/rootAnalysis/build/sixgill.manifest
@@ -1,10 +1,10 @@
 [
-   {
-      "hg_id" : "cd93f15a30ce",
-      "algorithm" : "sha512",
-      "digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
-      "filename" : "sixgill.tar.xz",
-      "size" : 2626640,
-      "unpack" : true
-   }
+{
+"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+"size" : 2631908,
+"hg_id" : "8cb9c3fb039a+ tip",
+"unpack" : true,
+"filename" : "sixgill.tar.xz",
+"algorithm" : "sha512"
+}
 ]
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -7,179 +7,198 @@ loadRelativeToScript('annotations.js');
 loadRelativeToScript('CFG.js');
 
 var theFunctionNameToFind;
 if (scriptArgs[0] == '--function') {
     theFunctionNameToFind = scriptArgs[1];
     scriptArgs = scriptArgs.slice(2);
 }
 
-var subclasses = {};
-var superclasses = {};
-var classFunctions = {};
+var typeInfo_filename = scriptArgs[0] || "typeInfo.txt";
 
-var fieldCallSeen = {};
+var subclasses = new Map(); // Map from csu => set of immediate subclasses
+var superclasses = new Map(); // Map from csu => set of immediate superclasses
+var classFunctions = new Map(); // Map from "csu:name" => set of full method name
 
-function addClassEntry(index, name, other)
+var virtualResolutionsSeen = new Set();
+
+function addEntry(map, name, entry)
 {
-    if (!(name in index)) {
-        index[name] = [other];
-        return;
-    }
-
-    for (var entry of index[name]) {
-        if (entry == other)
-            return;
-    }
-
-    index[name].push(other);
+    if (!map.has(name))
+        map.set(name, new Set());
+    map.get(name).add(entry);
 }
 
 // CSU is "Class/Struct/Union"
 function processCSU(csuName, csu)
 {
     if (!("FunctionField" in csu))
         return;
     for (var field of csu.FunctionField) {
         if (1 in field.Field) {
             var superclass = field.Field[1].Type.Name;
             var subclass = field.Field[1].FieldCSU.Type.Name;
             assert(subclass == csuName);
-            addClassEntry(subclasses, superclass, subclass);
-            addClassEntry(superclasses, subclass, superclass);
+            addEntry(subclasses, superclass, subclass);
+            addEntry(superclasses, subclass, superclass);
         }
         if ("Variable" in field) {
             // Note: not dealing with overloading correctly.
             var name = field.Variable.Name[0];
             var key = csuName + ":" + field.Field[0].Name[0];
-            if (!(key in classFunctions))
-                classFunctions[key] = [];
-            classFunctions[key].push(name);
+            addEntry(classFunctions, key, name);
         }
     }
 }
 
-function findVirtualFunctions(initialCSU, field, suppressed)
+// Return the nearest ancestor method definition, or all nearest definitions in
+// the case of multiple inheritance.
+function nearestAncestorMethods(csu, method)
 {
-    var worklist = [initialCSU];
-    var functions = [];
+    var key = csu + ":" + method;
 
-    // Virtual call targets on subclasses of nsISupports may be incomplete,
-    // if the interface is scriptable. Just treat all indirect calls on
-    // nsISupports objects as potentially GC'ing, except AddRef/Release
-    // which should never enter the JS engine (even when calling dtors).
-    while (worklist.length) {
-        var csu = worklist.pop();
-        if (csu == "nsISupports" && (field == "AddRef" || field == "Release")) {
-            suppressed[0] = true;
-            return [];
-        }
-        if (isOverridableField(initialCSU, csu, field)) {
-            // We will still resolve the virtual function call, because it's
-            // nice to have as complete a callgraph as possible for other uses.
-            // But push a token saying that we can run arbitrary code.
-            functions.push(null);
-        }
+    if (classFunctions.has(key))
+        return new Set(classFunctions.get(key));
 
-        if (csu in superclasses) {
-            for (var superclass of superclasses[csu])
-                worklist.push(superclass);
-        }
-    }
-
-    worklist = [csu];
-    while (worklist.length) {
-        var csu = worklist.pop();
-        var key = csu + ":" + field;
-
-        if (key in classFunctions) {
-            for (var name of classFunctions[key])
-                functions.push(name);
-        }
-
-        if (csu in subclasses) {
-            for (var subclass of subclasses[csu])
-                worklist.push(subclass);
-        }
+    var functions = new Set();
+    if (superclasses.has(csu)) {
+        for (var parent of superclasses.get(csu))
+            functions.update(nearestAncestorMethods(parent, method));
     }
 
     return functions;
 }
 
-var memoized = {};
+// Return [ instantations, suppressed ], where instantiations is a Set of all
+// possible implementations of 'field' given static type 'initialCSU', plus
+// null if arbitrary other implementations are possible, and suppressed is true
+// if we the method is assumed to be non-GC'ing by annotation.
+function findVirtualFunctions(initialCSU, field)
+{
+    var worklist = [initialCSU];
+    var functions = new Set();
+
+    // Loop through all methods of initialCSU (by looking at all methods of ancestor csus).
+    //
+    // If field is nsISupports::AddRef or ::Release, return an empty list and a
+    // boolean that says we assert that it cannot GC.
+    //
+    // If this is a method that is annotated to be dangerous (eg, it could be
+    // overridden with an implementation that could GC), then use null as a
+    // signal value that it should be considered to GC, even though we'll also
+    // collect all of the instantiations for other purposes.
+
+    while (worklist.length) {
+        var csu = worklist.pop();
+        if (isSuppressedVirtualMethod(csu, field))
+            return [ new Set(), true ];
+        if (isOverridableField(initialCSU, csu, field)) {
+            // We will still resolve the virtual function call, because it's
+            // nice to have as complete a callgraph as possible for other uses.
+            // But push a token saying that we can run arbitrary code.
+            functions.add(null);
+        }
+
+        if (superclasses.has(csu))
+            worklist.push(...superclasses.get(csu));
+    }
+
+    // Now return a list of all the instantiations of the method named 'field'
+    // that could execute on an instance of initialCSU or a descendant class.
+
+    // Start with the class itself, or if it doesn't define the method, all
+    // nearest ancestor definitions.
+    functions.update(nearestAncestorMethods(initialCSU, field));
+
+    // Then recurse through all descendants to add in their definitions.
+    var worklist = [initialCSU];
+    while (worklist.length) {
+        var csu = worklist.pop();
+        var key = csu + ":" + field;
+
+        if (classFunctions.has(key))
+            functions.update(classFunctions.get(key));
+
+        if (subclasses.has(csu))
+            worklist.push(...subclasses.get(csu));
+    }
+
+    return [ functions, false ];
+}
+
+var memoized = new Map();
 var memoizedCount = 0;
 
 function memo(name)
 {
-    if (!(name in memoized)) {
-        memoizedCount++;
-        memoized[name] = "" + memoizedCount;
-        print("#" + memoizedCount + " " + name);
+    if (!memoized.has(name)) {
+        let id = memoized.size + 1;
+        memoized.set(name, "" + id);
+        print(`#${id} ${name}`);
     }
-    return memoized[name];
+    return memoized.get(name);
 }
 
-var seenCallees = null;
-var seenSuppressedCallees = null;
-
 // Return a list of all callees that the given edge might be a call to. Each
 // one is represented by an object with a 'kind' field that is one of
-// ('direct', 'field', 'indirect', 'unknown').
+// ('direct', 'field', 'resolved-field', 'indirect', 'unknown'), though note
+// that 'resolved-field' is really a global record of virtual method
+// resolutions, indepedent of this particular edge.
 function getCallees(edge)
 {
     if (edge.Kind != "Call")
         return [];
 
     var callee = edge.Exp[0];
     var callees = [];
     if (callee.Kind == "Var") {
         assert(callee.Variable.Kind == "Func");
         callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]});
     } else {
         assert(callee.Kind == "Drf");
         if (callee.Exp[0].Kind == "Fld") {
             var field = callee.Exp[0].Field;
             var fieldName = field.Name[0];
             var csuName = field.FieldCSU.Type.Name;
-            var functions = null;
+            var functions;
             if ("FieldInstanceFunction" in field) {
-                var suppressed = [ false ];
-                functions = findVirtualFunctions(csuName, fieldName, suppressed);
-                if (suppressed[0]) {
+                let suppressed;
+                [ functions, suppressed ] = findVirtualFunctions(csuName, fieldName, suppressed);
+                if (suppressed) {
                     // Field call known to not GC; mark it as suppressed so
                     // direct invocations will be ignored
                     callees.push({'kind': "field", 'csu': csuName, 'field': fieldName,
                                   'suppressed': true});
                 }
+            } else {
+                functions = new Set([null]); // field call
             }
-            if (functions) {
-                // Known set of virtual call targets. Treat them as direct
-                // calls to all possible resolved types, but also record edges
-                // from this field call to each final callee. When the analysis
-                // is checking whether an edge can GC and it sees an unrooted
-                // pointer held live across this field call, it will know
-                // whether any of the direct callees can GC or not.
-                var targets = [];
-                var fullyResolved = true;
-                for (var name of functions) {
-                    if (name === null) {
-                        // virtual call on an nsISupports object
-                        callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
-                        fullyResolved = false;
-                    } else {
-                        callees.push({'kind': "direct", 'name': name});
-                        targets.push({'kind': "direct", 'name': name});
-                    }
+
+            // Known set of virtual call targets. Treat them as direct calls to
+            // all possible resolved types, but also record edges from this
+            // field call to each final callee. When the analysis is checking
+            // whether an edge can GC and it sees an unrooted pointer held live
+            // across this field call, it will know whether any of the direct
+            // callees can GC or not.
+            var targets = [];
+            var fullyResolved = true;
+            for (var name of functions) {
+                if (name === null) {
+                    // Unknown set of call targets, meaning either a function
+                    // pointer call ("field call") or a virtual method that can
+                    // be overridden in extensions.
+                    callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
+                    fullyResolved = false;
+                } else {
+                    callees.push({'kind': "direct", 'name': name});
+                    targets.push({'kind': "direct", 'name': name});
                 }
-                if (fullyResolved)
-                    callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
-            } else {
-                // Unknown set of call targets. Non-virtual field call.
-                callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
             }
+            if (fullyResolved)
+                callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
         } else if (callee.Exp[0].Kind == "Var") {
             // indirect call through a variable.
             callees.push({'kind': "indirect", 'variable': callee.Exp[0].Variable.Name[0]});
         } else {
             // unknown call target.
             callees.push({'kind': "unknown"});
         }
     }
@@ -214,82 +233,87 @@ function getAnnotations(body)
 
     return all_annotations;
 }
 
 function getTags(functionName, body) {
     var tags = new Set();
     var annotations = getAnnotations(body);
     if (functionName in annotations) {
-        print("crawling through");
         for (var [ annName, annValue ] of annotations[functionName]) {
             if (annName == 'Tag')
                 tags.add(annValue);
         }
     }
     return tags;
 }
 
 function processBody(functionName, body)
 {
     if (!('PEdge' in body))
         return;
 
     for (var tag of getTags(functionName, body).values())
         print("T " + memo(functionName) + " " + tag);
 
+    // Set of all callees that have been output so far, in order to suppress
+    // repeated callgraph edges from being recorded. Use a separate set for
+    // suppressed callees, since we don't want a suppressed edge (within one
+    // RAII scope) to prevent an unsuppressed edge from being recorded. The
+    // seen array is indexed by a boolean 'suppressed' variable.
+    var seen = [ new Set(), new Set() ];
+
     lastline = null;
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
-        var edgeSuppressed = false;
-        var seen = seenCallees;
-        if (edge.Index[0] in body.suppressed) {
-            edgeSuppressed = true;
-            seen = seenSuppressedCallees;
-        }
+
+        // Whether this call is within the RAII scope of a GC suppression class
+        var edgeSuppressed = (edge.Index[0] in body.suppressed);
+
         for (var callee of getCallees(edge)) {
-            var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : "";
+            var suppressed = Boolean(edgeSuppressed || callee.suppressed);
+            var prologue = suppressed ? "SUPPRESS_GC " : "";
             prologue += memo(functionName) + " ";
             if (callee.kind == 'direct') {
-                if (!(callee.name in seen)) {
-                    seen[callee.name] = true;
+                if (!seen[+suppressed].has(callee.name)) {
+                    seen[+suppressed].add(callee.name);
                     printOnce("D " + prologue + memo(callee.name));
                 }
             } else if (callee.kind == 'field') {
                 var { csu, field } = callee;
                 printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field);
             } else if (callee.kind == 'resolved-field') {
-                // Fully-resolved field call (usually a virtual method). Record
-                // the callgraph edges. Do not consider suppression, since it
-                // is local to this callsite and we are writing out a global
+                // Fully-resolved field (virtual method) call. Record the
+                // callgraph edges. Do not consider suppression, since it is
+                // local to this callsite and we are writing out a global
                 // record here.
                 //
                 // Any field call that does *not* have an R entry must be
                 // assumed to call anything.
                 var { csu, field, callees } = callee;
                 var fullFieldName = csu + "." + field;
-                if (!(fullFieldName in fieldCallSeen)) {
-                    fieldCallSeen[fullFieldName] = true;
+                if (!virtualResolutionsSeen.has(fullFieldName)) {
+                    virtualResolutionsSeen.add(fullFieldName);
                     for (var target of callees)
                         printOnce("R " + memo(fullFieldName) + " " + memo(target.name));
                 }
             } else if (callee.kind == 'indirect') {
                 printOnce("I " + prologue + "VARIABLE " + callee.variable);
             } else if (callee.kind == 'unknown') {
                 printOnce("I " + prologue + "VARIABLE UNKNOWN");
             } else {
                 printErr("invalid " + callee.kind + " callee");
                 debugger;
             }
         }
     }
 }
 
-var callgraph = {};
+GCSuppressionTypes = loadTypeInfo(typeInfo_filename)["Suppress GC"] || [];
 
 var xdb = xdbLibrary();
 xdb.open("src_comp.xdb");
 
 var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
 
 for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
@@ -317,24 +341,22 @@ if (theFunctionNameToFind) {
     }
     minStream = maxStream = index;
 }
 
 function process(functionName, functionBodies)
 {
     for (var body of functionBodies)
         body.suppressed = [];
+
     for (var body of functionBodies) {
         for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
             pbody.suppressed[id] = true;
     }
 
-    seenCallees = {};
-    seenSuppressedCallees = {};
-
     for (var body of functionBodies)
         processBody(functionName, body);
 
     // GCC generates multiple constructors and destructors ("in-charge" and
     // "not-in-charge") to handle virtual base classes. They are normally
     // identical, and it appears that GCC does some magic to alias them to the
     // same thing. But this aliasing is not visible to the analysis. So we'll
     // add a dummy call edge from "foo" -> "foo *INTERNAL* ", since only "foo"
@@ -346,17 +368,17 @@ function process(functionName, functionB
     var markerPos = functionName.indexOf(internalMarker);
     if (markerPos > 0) {
         var inChargeXTor = functionName.replace(internalMarker, "");
         print("D " + memo(inChargeXTor) + " " + memo(functionName));
 
         // Bug 1056410: Oh joy. GCC does something even funkier internally,
         // where it generates calls to ~Foo() but a body for ~Foo(int32) even
         // though it uses the same mangled name for both. So we need to add a
-        // synthetic edge from the former to the latter.
+        // synthetic edge from ~Foo() -> ~Foo(int32).
         //
         // inChargeXTor will have the (int32).
         if (functionName.indexOf("::~") > 0) {
             var calledDestructor = inChargeXTor.replace("(int32)", "()");
             print("D " + memo(calledDestructor) + " " + memo(inChargeXTor));
         }
     }
 
@@ -364,41 +386,47 @@ function process(functionName, functionB
     // different kinds of constructors/destructors are:
     // C1	# complete object constructor
     // C2	# base object constructor
     // C3	# complete object allocating constructor
     // D0	# deleting destructor
     // D1	# complete object destructor
     // D2	# base object destructor
     //
-    // In actual practice, I have observed a C4 constructor generated by gcc
+    // In actual practice, I have observed C4 and D4 xtors generated by gcc
     // 4.9.3 (but not 4.7.3). The gcc source code says:
     //
     //   /* This is the old-style "[unified]" constructor.
     //      In some cases, we may emit this function and call
     //      it from the clones in order to share code and save space.  */
     //
     // Unfortunately, that "call... from the clones" does not seem to appear in
-    // the CFG we get from GCC. So if we see a C4 constructor, inject an edge
-    // to it from C1, C2, and C3. (Note that C3 isn't even used in current GCC,
-    // but add the edge anyway just in case.)
-    if (functionName.indexOf("C4E") != -1) {
+    // the CFG we get from GCC. So if we see a C4 constructor or D4 destructor,
+    // inject an edge to it from C1, C2, and C3 (or D1, D2, and D3). (Note that
+    // C3 isn't even used in current GCC, but add the edge anyway just in
+    // case.)
+    if (functionName.indexOf("C4E") != -1 || functionName.indexOf("D4Ev") != -1) {
         var [ mangled, unmangled ] = splitFunction(functionName);
         // E terminates the method name (and precedes the method parameters).
-        if (mangled.indexOf("C4E") != -1) {
-            // If "C4E" shows up in the mangled name for another reason, this
-            // will create bogus edges in the callgraph. But that shouldn't
-            // matter too much, and is somewhat difficult to avoid, so we will
-            // live with it.
-            var C1 = mangled.replace("C4E", "C1E");
-            var C2 = mangled.replace("C4E", "C2E");
-            var C3 = mangled.replace("C4E", "C3E");
-            print("D " + memo(C1) + " " + memo(mangled));
-            print("D " + memo(C2) + " " + memo(mangled));
-            print("D " + memo(C3) + " " + memo(mangled));
+        // If eg "C4E" shows up in the mangled name for another reason, this
+        // will create bogus edges in the callgraph. But will affect little and
+        // is somewhat difficult to avoid, so we will live with it.
+        for (let [synthetic, variant] of [['C4E', 'C1E'],
+                                          ['C4E', 'C2E'],
+                                          ['C4E', 'C3E'],
+                                          ['D4Ev', 'D1Ev'],
+                                          ['D4Ev', 'D2Ev'],
+                                          ['D4Ev', 'D3Ev']])
+        {
+            if (mangled.indexOf(synthetic) == -1)
+                continue;
+
+            let variant_mangled = mangled.replace(synthetic, variant);
+            let variant_full = variant_mangled + "$" + unmangled;
+            print("D " + memo(variant_full) + " " + memo(functionName));
         }
     }
 }
 
 for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var data = xdb.read_entry(name);
     process(name.readString(), JSON.parse(data.readString()));
--- a/js/src/devtools/rootAnalysis/computeGCFunctions.js
+++ b/js/src/devtools/rootAnalysis/computeGCFunctions.js
@@ -16,26 +16,30 @@ var gcFunctions_filename = scriptArgs[1]
 var gcFunctionsList_filename = scriptArgs[2] || "gcFunctions.lst";
 var gcEdges_filename = scriptArgs[3] || "gcEdges.txt";
 var suppressedFunctionsList_filename = scriptArgs[4] || "suppressedFunctions.lst";
 
 loadCallgraph(callgraph_filename);
 
 printErr("Writing " + gcFunctions_filename);
 redirect(gcFunctions_filename);
+
 for (var name in gcFunctions) {
-    print("");
-    print("GC Function: " + name + "$" + readableNames[name][0]);
-    do {
-        name = gcFunctions[name];
-        if (name in readableNames)
-            print("    " + readableNames[name][0]);
-        else
-            print("    " + name);
-    } while (name in gcFunctions);
+    for (let readable of readableNames[name]) {
+        print("");
+        print("GC Function: " + name + "$" + readable);
+        let current = name;
+        do {
+            current = gcFunctions[current];
+            if (current in readableNames)
+                print("    " + readableNames[current][0]);
+            else
+                print("    " + current);
+        } while (current in gcFunctions);
+    }
 }
 
 printErr("Writing " + gcFunctionsList_filename);
 redirect(gcFunctionsList_filename);
 for (var name in gcFunctions) {
     for (var readable of readableNames[name])
         print(name + "$" + readable);
 }
--- a/js/src/devtools/rootAnalysis/computeGCTypes.js
+++ b/js/src/devtools/rootAnalysis/computeGCTypes.js
@@ -1,23 +1,29 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 
+var gcTypes_filename = scriptArgs[0] || "gcTypes.txt";
+var typeInfo_filename = scriptArgs[1] || "typeInfo.txt";
+
 var annotations = {
     'GCPointers': [],
     'GCThings': [],
     'NonGCTypes': {}, // unused
     'NonGCPointers': {},
     'RootedPointers': {},
+    'GCSuppressors': {},
 };
 
+var gDescriptors = new Map; // Map from descriptor string => Set of typeName
+
 var structureParents = {}; // Map from field => list of <parent, fieldName>
 var pointerParents = {}; // Map from field => list of <parent, fieldName>
 var baseClasses = {}; // Map from struct name => list of base class name strings
 
 var gcTypes = {}; // map from parent struct => Set of GC typed children
 var gcPointers = {}; // map from parent struct => Set of GC typed children
 var gcFields = new Map;
 
@@ -59,16 +65,18 @@ function processCSU(csu, body)
         else if (tag == 'Invalidated by GC')
             annotations.GCPointers.push(csu);
         else if (tag == 'GC Thing')
             annotations.GCThings.push(csu);
         else if (tag == 'Suppressed GC Pointer')
             annotations.NonGCPointers[csu] = true;
         else if (tag == 'Rooted Pointer')
             annotations.RootedPointers[csu] = true;
+        else if (tag == 'Suppress GC')
+            annotations.GCSuppressors[csu] = true;
     }
 }
 
 // csu.field is of type inner
 function addNestedStructure(csu, inner, field)
 {
     if (!(inner in structureParents))
         structureParents[inner] = [];
@@ -202,16 +210,36 @@ function addGCType(typeName, child, why,
     markGCType(typeName, '<annotation>', '(annotation)', 0, 0, "");
 }
 
 function addGCPointer(typeName)
 {
     markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
 }
 
+// Add an arbitrary descriptor to a type, and apply it recursively to all base
+// structs and structs that contain the given typeName as a field.
+function addDescriptor(typeName, descriptor)
+{
+    if (!gDescriptors.has(descriptor))
+        gDescriptors.set(descriptor, new Set);
+    let descriptorTypes = gDescriptors.get(descriptor);
+    if (!descriptorTypes.has(typeName)) {
+        descriptorTypes.add(typeName);
+        if (typeName in structureParents) {
+            for (let [holder, field] of structureParents[typeName])
+                addDescriptor(holder, descriptor);
+        }
+        if (typeName in baseClasses) {
+            for (let base of baseClasses[typeName])
+                addDescriptor(base, descriptor);
+        }
+    }
+}
+
 for (var type of listNonGCPointers())
     annotations.NonGCPointers[type] = true;
 
 function explain(csu, indent, seen) {
     if (!seen)
         seen = new Set();
     seen.add(csu);
     if (!gcFields.has(csu))
@@ -241,16 +269,31 @@ function explain(csu, indent, seen) {
         }
         msg += child;
         print(msg);
         if (!seen.has(child))
             explain(child, indent + "  ", seen);
     }
 }
 
+var origOut = os.file.redirect(gcTypes_filename);
+
 for (var csu in gcTypes) {
     print("GCThing: " + csu);
     explain(csu, "  ");
 }
 for (var csu in gcPointers) {
     print("GCPointer: " + csu);
     explain(csu, "  ");
 }
+
+// Redirect output to the typeInfo file and close the gcTypes file.
+os.file.close(os.file.redirect(typeInfo_filename));
+
+for (let csu in annotations.GCSuppressors)
+    addDescriptor(csu, 'Suppress GC');
+
+for (let [descriptor, types] of gDescriptors) {
+    for (let csu of types)
+        print(descriptor + "$$" + csu);
+}
+
+os.file.close(os.file.redirect(origOut));
--- a/js/src/devtools/rootAnalysis/explain.py
+++ b/js/src/devtools/rootAnalysis/explain.py
@@ -1,13 +1,15 @@
 #!/usr/bin/python
 
 import re
 import argparse
 
+from collections import defaultdict
+
 parser = argparse.ArgumentParser(description='Process some integers.')
 parser.add_argument('rootingHazards', nargs='?', default='rootingHazards.txt')
 parser.add_argument('gcFunctions', nargs='?', default='gcFunctions.txt')
 parser.add_argument('hazards', nargs='?', default='hazards.txt')
 parser.add_argument('extra', nargs='?', default='unnecessary.txt')
 parser.add_argument('refs', nargs='?', default='refs.txt')
 args = parser.parse_args()
 
@@ -17,17 +19,17 @@ try:
     with open(args.rootingHazards) as rootingHazards, \
         open(args.hazards, 'w') as hazards, \
         open(args.extra, 'w') as extra, \
         open(args.refs, 'w') as refs:
         current_gcFunction = None
 
         # Map from a GC function name to the list of hazards resulting from
         # that GC function
-        hazardousGCFunctions = {}
+        hazardousGCFunctions = defaultdict(list)
 
         # List of tuples (gcFunction, index of hazard) used to maintain the
         # ordering of the hazards
         hazardOrder = []
 
         for line in rootingHazards:
             m = re.match(r'^Time: (.*)', line)
             mm = re.match(r'^Run on:', line)
@@ -48,17 +50,17 @@ try:
                 print >>refs, line
                 continue
 
             m = re.match(r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at \S+:\d+$", line)
             if m:
                 # Function names are surrounded by single quotes. Field calls
                 # are unquoted.
                 current_gcFunction = m.group(2)
-                hazardousGCFunctions.setdefault(current_gcFunction, []).append(line)
+                hazardousGCFunctions[current_gcFunction].append(line)
                 hazardOrder.append((current_gcFunction, len(hazardousGCFunctions[current_gcFunction]) - 1))
                 num_hazards += 1
                 continue
 
             if current_gcFunction:
                 if not line.strip():
                     # Blank line => end of this hazard
                     current_gcFunction = None
@@ -79,22 +81,23 @@ try:
                     if m.group(1) in hazardousGCFunctions:
                         current_func = m.group(1)
                         explanation = line
                 elif current_func:
                     explanation += line
             if current_func:
                 gcExplanations[current_func] = explanation
 
-            for gcFunction, index in hazardOrder:
-                gcHazards = hazardousGCFunctions[gcFunction]
-                if gcFunction in gcExplanations:
-                    print >>hazards, (gcHazards[index] + gcExplanations[gcFunction])
-                else:
-                    print >>hazards, gcHazards[index]
+        for gcFunction, index in hazardOrder:
+            gcHazards = hazardousGCFunctions[gcFunction]
+
+            if gcFunction in gcExplanations:
+                print >>hazards, (gcHazards[index] + gcExplanations[gcFunction])
+            else:
+                print >>hazards, gcHazards[index]
 
 except IOError as e:
     print 'Failed: %s' % str(e)
 
 print("Wrote %s" % args.hazards)
 print("Wrote %s" % args.extra)
 print("Wrote %s" % args.refs)
 print("Found %d hazards and %d unsafe references" % (num_hazards, num_refs))
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -48,23 +48,18 @@ function addGCFunction(caller, reason)
         return true;
     }
 
     return false;
 }
 
 function addCallEdge(caller, callee, suppressed)
 {
-    if (!(caller in calleeGraph))
-        calleeGraph[caller] = [];
-    calleeGraph[caller].push({callee:callee, suppressed:suppressed});
-
-    if (!(callee in callerGraph))
-        callerGraph[callee] = [];
-    callerGraph[callee].push({caller:caller, suppressed:suppressed});
+    addToKeyedList(calleeGraph, caller, {callee:callee, suppressed:suppressed});
+    addToKeyedList(callerGraph, callee, {caller:caller, suppressed:suppressed});
 }
 
 // Map from identifier to full "mangled|readable" name. Or sometimes to a
 // Class.Field name.
 var functionNames = [""];
 
 // Map from identifier to mangled name (or to a Class.Field)
 var idToMangled = [""];
--- a/js/src/devtools/rootAnalysis/run-test.py
+++ b/js/src/devtools/rootAnalysis/run-test.py
@@ -1,152 +1,89 @@
 #!/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/.
 
-import sys
 import os
-import re
-import json
+import site
 import subprocess
+import argparse
 
-testdir = os.path.abspath(os.path.dirname(__file__))
+testdir = os.path.abspath(os.path.join(os.path.dirname(__file__), 't'))
+site.addsitedir(testdir)
+from testlib import Test, equal
+
+scriptdir = os.path.abspath(os.path.dirname(__file__))
 
-cfg = {}
-cfg['SIXGILL_ROOT']   = os.environ.get('SIXGILL',
-                                       os.path.join(testdir, "sixgill"))
-cfg['SIXGILL_BIN']    = os.environ.get('SIXGILL_BIN',
-                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "bin"))
-cfg['SIXGILL_PLUGIN'] = os.environ.get('SIXGILL_PLUGIN',
-                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "libexec", "sixgill", "gcc", "xgill.so"))
-cfg['CC']             = os.environ.get("CC",
-                                       "gcc")
-cfg['CXX']            = os.environ.get("CXX",
-                                       cfg.get('CC', 'g++'))
-cfg['JS_BIN']         = os.environ["JS"]
+parser = argparse.ArgumentParser(description='run hazard analysis tests')
+parser.add_argument(
+    '--js', default=os.environ.get('JS'),
+    help='JS binary to run the tests with')
+parser.add_argument(
+    '--sixgill', default=os.environ.get('SIXGILL', os.path.join(testdir, "sixgill")),
+    help='Path to root of sixgill installation')
+parser.add_argument(
+    '--sixgill-bin', default=os.environ.get('SIXGILL_BIN'),
+    help='Path to sixgill binary dir')
+parser.add_argument(
+    '--sixgill-plugin', default=os.environ.get('SIXGILL_PLUGIN'),
+    help='Full path to sixgill gcc plugin')
+parser.add_argument(
+    '--gccdir', default=os.environ.get('GCCDIR'),
+    help='Path to GCC installation dir')
+parser.add_argument(
+    '--cc', default=os.environ.get('CC'),
+    help='Path to gcc')
+parser.add_argument(
+    '--cxx', default=os.environ.get('CXX'),
+    help='Path to g++')
+parser.add_argument(
+    '--verbose', '-v', action='store_true',
+    help='Display verbose output, including commands executed')
+parser.add_argument(
+    'tests', nargs='*', default=['sixgill-tree', 'suppression', 'hazards', 'exceptions'],
+    help='tests to run')
+
+cfg = parser.parse_args()
+
+if not cfg.js:
+    exit('Must specify JS binary through environment variable or --js option')
+if not cfg.cc:
+    if cfg.gccdir:
+        cfg.cc = os.path.join(cfg.gccdir, "bin", "gcc")
+    else:
+        cfg.cc = "gcc"
+if not cfg.cxx:
+    if cfg.gccdir:
+        cfg.cxx = os.path.join(cfg.gccdir, "bin", "g++")
+    else:
+        cfg.cxx = "g++"
+if not cfg.sixgill_bin:
+    cfg.sixgill_bin = os.path.join(cfg.sixgill, "usr", "bin")
+if not cfg.sixgill_plugin:
+    cfg.sixgill_plugin = os.path.join(cfg.sixgill, "usr", "libexec", "sixgill", "gcc", "xgill.so")
+
+subprocess.check_call([cfg.js, '-e', 'if (!getBuildConfiguration()["has-ctypes"]) quit(1)'])
 
 def binpath(prog):
-    return os.path.join(cfg['SIXGILL_BIN'], prog)
-
-if not os.path.exists("test-output"):
-    os.mkdir("test-output")
-
-# Simplified version of the body info.
-class Body(dict):
-    def __init__(self, body):
-        self['BlockIdKind'] = body['BlockId']['Kind']
-        if 'Variable' in body['BlockId']:
-            self['BlockName'] = body['BlockId']['Variable']['Name'][0]
-        self['LineRange'] = [ body['Location'][0]['Line'], body['Location'][1]['Line'] ]
-        self['Filename'] = body['Location'][0]['CacheString']
-        self['Edges'] = body.get('PEdge', [])
-        self['Points'] = { i+1: body['PPoint'][i]['Location']['Line'] for i in range(len(body['PPoint'])) }
-        self['Index'] = body['Index']
-        self['Variables'] = { x['Variable']['Name'][0]: x['Type'] for x in body['DefineVariable'] }
+    return os.path.join(cfg.sixgill_bin, prog)
 
-        # Indexes
-        self['Line2Points'] = {}
-        for point, line in self['Points'].items():
-            self['Line2Points'].setdefault(line, []).append(point)
-        self['SrcPoint2Edges'] = {}
-        for edge in self['Edges']:
-            (src, dst) = edge['Index']
-            self['SrcPoint2Edges'].setdefault(src, []).append(edge)
-        self['Line2Edges'] = {}
-        for (src, edges) in self['SrcPoint2Edges'].items():
-            line = self['Points'][src]
-            self['Line2Edges'].setdefault(line, []).extend(edges)
-
-    def edges_from_line(self, line):
-        return self['Line2Edges'][line]
-
-    def edge_from_line(self, line):
-        edges = self.edges_from_line(line)
-        assert(len(edges) == 1)
-        return edges[0]
-
-    def edges_from_point(self, point):
-        return self['SrcPoint2Edges'][point]
-
-    def edge_from_point(self, point):
-        edges = self.edges_from_point(point)
-        assert(len(edges) == 1)
-        return edges[0]
-
-    def assignment_point(self, varname):
-        for edge in self['Edges']:
-            if edge['Kind'] != 'Assign':
-                continue
-            dst = edge['Exp'][0]
-            if dst['Kind'] != 'Var':
-                continue
-            if dst['Variable']['Name'][0] == varname:
-                return edge['Index'][0]
-        raise Exception("assignment to variable %s not found" % varname)
+try:
+    os.mkdir(os.path.join('t', 'out'))
+except OSError:
+    pass
 
-    def assignment_line(self, varname):
-        return self['Points'][self.assignment_point(varname)]
-
-tests = ['test']
-for name in tests:
+for name in cfg.tests:
+    name = os.path.basename(name)
     indir = os.path.join(testdir, name)
-    outdir = os.path.join(testdir, "test-output", name)
-    if not os.path.exists(outdir):
+    outdir = os.path.join(testdir, 'out', name)
+    try:
         os.mkdir(outdir)
-
-    def compile(source):
-        cmd = "{CXX} -c {source} -fplugin={sixgill}".format(source=os.path.join(indir, source),
-                                                            CXX=cfg['CXX'], sixgill=cfg['SIXGILL_PLUGIN'])
-        print("Running %s" % cmd)
-        subprocess.check_call(["sh", "-c", cmd])
-
-    def load_db_entry(dbname, pattern):
-        if not isinstance(pattern, basestring):
-            output = subprocess.check_output([binpath("xdbkeys"), dbname + ".xdb"])
-            entries = output.splitlines()
-            matches = [f for f in entries if re.search(pattern, f)]
-            if len(matches) == 0:
-                raise Exception("entry not found")
-            if len(matches) > 1:
-                raise Exception("multiple entries found")
-            pattern = matches[0]
-
-        output = subprocess.check_output([binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
-        return json.loads(output)
+    except OSError:
+        pass
 
-    def computeGCTypes():
-        file("defaults.py", "w").write('''\
-analysis_scriptdir = '{testdir}'
-sixgill_bin = '{bindir}'
-'''.format(testdir=testdir, bindir=cfg['SIXGILL_BIN']))
-        cmd = [
-            os.path.join(testdir, "analyze.py"),
-            "gcTypes", "--upto", "gcTypes",
-            "--source=%s" % indir,
-            "--objdir=%s" % outdir,
-            "--js=%s" % cfg['JS_BIN'],
-        ]
-        print("Running " + " ".join(cmd))
-        output = subprocess.check_call(cmd)
-
-    def loadGCTypes():
-        gctypes = {'GCThings': [], 'GCPointers': []}
-        for line in file(os.path.join(outdir, "gcTypes.txt")):
-            m = re.match(r'^(GC\w+): (.*)', line)
-            if m:
-                gctypes[m.group(1) + 's'].append(m.group(2))
-        return gctypes
-
-    def process_body(body):
-        return Body(body)
-
-    def process_bodies(bodies):
-        return [ process_body(b) for b in bodies ]
-
-    def equal(got, expected):
-        if got != expected:
-            print("Got '%s', expected '%s'" % (got, expected))
+    test = Test(indir, outdir, cfg)
 
     os.chdir(outdir)
     subprocess.call(["sh", "-c", "rm *.xdb"])
-    execfile(os.path.join(indir, "test.py"))
+    execfile(os.path.join(indir, "test.py"), {'test': test, 'equal': equal})
     print("TEST-PASSED: %s" % name)
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/exceptions/source.cpp
@@ -0,0 +1,42 @@
+#define ANNOTATE(property) __attribute__((tag(property)))
+
+struct Cell { int f; } ANNOTATE("GC Thing");
+
+extern void GC() ANNOTATE("GC Call");
+
+void GC()
+{
+    // If the implementation is too trivial, the function body won't be emitted at all.
+    asm("");
+}
+
+class RAII_GC {
+  public:
+    RAII_GC() {}
+    ~RAII_GC() { GC(); }
+};
+
+// ~AutoSomething calls GC because of the RAII_GC field. The constructor,
+// though, should *not* GC -- unless it throws an exception. Which is not
+// possible when compiled with -fno-exceptions.
+class AutoSomething {
+    RAII_GC gc;
+  public:
+    AutoSomething() : gc() {
+        asm(""); // Ooh, scary, this might throw an exception
+    }
+    ~AutoSomething() {
+        asm("");
+    }
+};
+
+extern void usevar(Cell* cell);
+
+void f() {
+    Cell* thing = nullptr; // Live range starts here
+
+    {
+        AutoSomething smth; // Constructor can GC only if exceptions are enabled
+        usevar(thing); // Live range ends here
+    } // In particular, 'thing' is dead at the destructor, so no hazard
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/exceptions/test.py
@@ -0,0 +1,19 @@
+test.compile("source.cpp", '-fno-exceptions')
+test.run_analysis_script('gcTypes')
+
+hazards = test.load_hazards()
+assert(len(hazards) == 0)
+
+# If we compile with exceptions, then there *should* be a hazard because
+# AutoSomething::AutoSomething might throw an exception, which would cause the
+# partially-constructed value to be torn down, which will call ~RAII_GC.
+
+test.compile("source.cpp", '-fexceptions')
+test.run_analysis_script('gcTypes')
+
+hazards = test.load_hazards()
+assert(len(hazards) == 1)
+hazard = hazards[0]
+assert(hazard.function == 'void f()')
+assert(hazard.variable == 'thing')
+assert("AutoSomething::AutoSomething" in hazard.GCFunction)
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/hazards/source.cpp
@@ -0,0 +1,89 @@
+#define ANNOTATE(property) __attribute__((tag(property)))
+
+struct Cell { int f; } ANNOTATE("GC Thing");
+
+class AutoSuppressGC_Base {
+  public:
+    AutoSuppressGC_Base() {}
+    ~AutoSuppressGC_Base() {}
+} ANNOTATE("Suppress GC");
+
+class AutoSuppressGC_Child : public AutoSuppressGC_Base {
+  public:
+    AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
+};
+
+class AutoSuppressGC {
+    AutoSuppressGC_Child helpImBeingSuppressed;
+
+  public:
+    AutoSuppressGC() {}
+};
+
+extern void GC() ANNOTATE("GC Call");
+extern void invisible();
+
+void GC()
+{
+    // If the implementation is too trivial, the function body won't be emitted at all.
+    asm("");
+    invisible();
+}
+
+extern void foo(Cell*);
+
+void suppressedFunction() {
+    GC(); // Calls GC, but is always called within AutoSuppressGC
+}
+
+void halfSuppressedFunction() {
+    GC(); // Calls GC, but is sometimes called within AutoSuppressGC
+}
+
+void unsuppressedFunction() {
+    GC(); // Calls GC, never within AutoSuppressGC
+}
+
+volatile static int x = 3;
+volatile static int* xp = &x;
+struct GCInDestructor {
+    ~GCInDestructor() {
+        invisible();
+        asm("");
+        *xp = 4;
+        GC();
+    }
+};
+
+Cell*
+f()
+{
+    GCInDestructor kaboom;
+
+    Cell cell;
+    Cell* cell1 = &cell;
+    Cell* cell2 = &cell;
+    Cell* cell3 = &cell;
+    Cell* cell4 = &cell;
+    {
+        AutoSuppressGC nogc;
+        suppressedFunction();
+        halfSuppressedFunction();
+    }
+    foo(cell1);
+    halfSuppressedFunction();
+    foo(cell2);
+    unsuppressedFunction();
+    {
+        // Old bug: it would look from the first AutoSuppressGC constructor it
+        // found to the last destructor. This statement *should* have no effect.
+        AutoSuppressGC nogc;
+    }
+    foo(cell3);
+    Cell* cell5 = &cell;
+    foo(cell5);
+
+    // Hazard in return value due to ~GCInDestructor
+    Cell* cell6 = &cell;
+    return cell6;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/hazards/test.py
@@ -0,0 +1,36 @@
+test.compile("source.cpp")
+test.run_analysis_script('gcTypes')
+
+# gcFunctions should be the inverse, but we get to rely on unmangled names here.
+gcFunctions = test.load_gcFunctions()
+print(gcFunctions)
+assert('void GC()' in gcFunctions)
+assert('void suppressedFunction()' not in gcFunctions)
+assert('void halfSuppressedFunction()' in gcFunctions)
+assert('void unsuppressedFunction()' in gcFunctions)
+assert('Cell* f()' in gcFunctions)
+
+hazards = test.load_hazards()
+hazmap = {haz.variable: haz for haz in hazards}
+assert('cell1' not in hazmap)
+assert('cell2' in hazmap)
+assert('cell3' in hazmap)
+assert('cell4' not in hazmap)
+assert('cell5' not in hazmap)
+assert('cell6' not in hazmap)
+assert('<returnvalue>' in hazmap)
+
+# All hazards should be in f()
+assert(hazmap['cell2'].function == 'Cell* f()')
+assert(len(set(haz.function for haz in hazards)) == 1)
+
+# Check that the correct GC call is reported for each hazard. (cell3 has a
+# hazard from two different GC calls; it doesn't really matter which is
+# reported.)
+assert(hazmap['cell2'].GCFunction == 'void halfSuppressedFunction()')
+assert(hazmap['cell3'].GCFunction in ('void halfSuppressedFunction()', 'void unsuppressedFunction()'))
+assert(hazmap['<returnvalue>'].GCFunction == 'void GCInDestructor::~GCInDestructor()')
+
+# Type names are handy to have in the report.
+assert(hazmap['cell2'].type == 'Cell*')
+assert(hazmap['<returnvalue>'].type == 'Cell*')
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp
@@ -0,0 +1,70 @@
+#define ANNOTATE(property) __attribute__((tag(property)))
+
+namespace js {
+namespace gc {
+struct Cell { int f; } ANNOTATE("GC Thing");
+}
+}
+
+struct Bogon {
+};
+
+struct JustACell : public js::gc::Cell {
+    bool iHaveNoDataMembers() { return true; }
+};
+
+struct JSObject : public js::gc::Cell, public Bogon {
+    int g;
+};
+
+struct SpecialObject : public JSObject {
+    int z;
+};
+
+struct ErrorResult {
+    bool hasObj;
+    JSObject *obj;
+    void trace() {}
+} ANNOTATE("Suppressed GC Pointer");
+
+struct OkContainer {
+    ErrorResult res;
+    bool happy;
+};
+
+struct UnrootedPointer {
+    JSObject *obj;
+};
+
+template <typename T>
+class Rooted {
+    T data;
+} ANNOTATE("Rooted Pointer");
+
+extern void js_GC() ANNOTATE("GC Call") ANNOTATE("Slow");
+
+void js_GC() {}
+
+void root_arg(JSObject *obj, JSObject *random)
+{
+  // Use all these types so they get included in the output.
+  SpecialObject so;
+  UnrootedPointer up;
+  Bogon b;
+  OkContainer okc;
+  Rooted<JSObject*> ro;
+  Rooted<SpecialObject*> rso;
+
+  obj = random;
+
+  JSObject *other1 = obj;
+  js_GC();
+
+  float MARKER1 = 0;
+  JSObject *other2 = obj;
+  other1->f = 1;
+  other2->f = -1;
+
+  unsigned int u1 = 1;
+  unsigned int u2 = -1;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/sixgill-tree/test.py
@@ -0,0 +1,60 @@
+import re
+
+test.compile("source.cpp")
+test.computeGCTypes()
+body = test.process_body(test.load_db_entry("src_body", re.compile(r'root_arg'))[0])
+
+# Rendering positive and negative integers
+marker1 = body.assignment_line('MARKER1')
+equal(body.edge_from_line(marker1 + 2)['Exp'][1]['String'], '1')
+equal(body.edge_from_line(marker1 + 3)['Exp'][1]['String'], '-1')
+
+equal(body.edge_from_point(body.assignment_point('u1'))['Exp'][1]['String'], '1')
+equal(body.edge_from_point(body.assignment_point('u2'))['Exp'][1]['String'], '4294967295')
+
+assert('obj' in body['Variables'])
+assert('random' in body['Variables'])
+assert('other1' in body['Variables'])
+assert('other2' in body['Variables'])
+
+# Test function annotations
+js_GC = test.process_body(test.load_db_entry("src_body", re.compile(r'js_GC'))[0])
+annotations = js_GC['Variables']['void js_GC()']['Annotation']
+assert(annotations)
+found_call_tag = False
+for annotation in annotations:
+    (annType, value) = annotation['Name']
+    if annType == 'Tag' and value == 'GC Call':
+        found_call_tag = True
+assert(found_call_tag)
+
+# Test type annotations
+
+# js::gc::Cell first
+cell = test.load_db_entry("src_comp", 'js::gc::Cell')[0]
+assert(cell['Kind'] == 'Struct')
+annotations = cell['Annotation']
+assert(len(annotations) == 1)
+(tag, value) = annotations[0]['Name']
+assert(tag == 'Tag')
+assert(value == 'GC Thing')
+
+# Check JSObject inheritance.
+JSObject = test.load_db_entry("src_comp", 'JSObject')[0]
+bases = [ b['Base'] for b in JSObject['CSUBaseClass'] ]
+assert('js::gc::Cell' in bases)
+assert('Bogon' in bases)
+assert(len(bases) == 2)
+
+# Check type analysis
+gctypes = test.load_gcTypes()
+assert('js::gc::Cell' in gctypes['GCThings'])
+assert('JustACell' in gctypes['GCThings'])
+assert('JSObject' in gctypes['GCThings'])
+assert('SpecialObject' in gctypes['GCThings'])
+assert('UnrootedPointer' in gctypes['GCPointers'])
+assert('Bogon' not in gctypes['GCThings'])
+assert('Bogon' not in gctypes['GCPointers'])
+assert('ErrorResult' not in gctypes['GCPointers'])
+assert('OkContainer' not in gctypes['GCPointers'])
+assert('class Rooted<JSObject*>' not in gctypes['GCPointers'])
copy from js/src/devtools/rootAnalysis/run-test.py
copy to js/src/devtools/rootAnalysis/t/sixgill.py
--- a/js/src/devtools/rootAnalysis/run-test.py
+++ b/js/src/devtools/rootAnalysis/t/sixgill.py
@@ -1,65 +1,41 @@
 #!/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/.
 
-import sys
-import os
-import re
-import json
-import subprocess
-
-testdir = os.path.abspath(os.path.dirname(__file__))
-
-cfg = {}
-cfg['SIXGILL_ROOT']   = os.environ.get('SIXGILL',
-                                       os.path.join(testdir, "sixgill"))
-cfg['SIXGILL_BIN']    = os.environ.get('SIXGILL_BIN',
-                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "bin"))
-cfg['SIXGILL_PLUGIN'] = os.environ.get('SIXGILL_PLUGIN',
-                                       os.path.join(cfg['SIXGILL_ROOT'], "usr", "libexec", "sixgill", "gcc", "xgill.so"))
-cfg['CC']             = os.environ.get("CC",
-                                       "gcc")
-cfg['CXX']            = os.environ.get("CXX",
-                                       cfg.get('CC', 'g++'))
-cfg['JS_BIN']         = os.environ["JS"]
-
-def binpath(prog):
-    return os.path.join(cfg['SIXGILL_BIN'], prog)
-
-if not os.path.exists("test-output"):
-    os.mkdir("test-output")
+from collections import defaultdict
 
 # Simplified version of the body info.
 class Body(dict):
     def __init__(self, body):
         self['BlockIdKind'] = body['BlockId']['Kind']
         if 'Variable' in body['BlockId']:
-            self['BlockName'] = body['BlockId']['Variable']['Name'][0]
-        self['LineRange'] = [ body['Location'][0]['Line'], body['Location'][1]['Line'] ]
-        self['Filename'] = body['Location'][0]['CacheString']
+            self['BlockName'] = body['BlockId']['Variable']['Name'][0].split("$")[-1]
+        loc = body['Location']
+        self['LineRange'] = (loc[0]['Line'], loc[1]['Line'])
+        self['Filename'] = loc[0]['CacheString']
         self['Edges'] = body.get('PEdge', [])
-        self['Points'] = { i+1: body['PPoint'][i]['Location']['Line'] for i in range(len(body['PPoint'])) }
+        self['Points'] = { i: p['Location']['Line'] for i, p in enumerate(body['PPoint'], 1) }
         self['Index'] = body['Index']
-        self['Variables'] = { x['Variable']['Name'][0]: x['Type'] for x in body['DefineVariable'] }
+        self['Variables'] = { x['Variable']['Name'][0].split("$")[-1]: x['Type'] for x in body['DefineVariable'] }
 
         # Indexes
-        self['Line2Points'] = {}
+        self['Line2Points'] = defaultdict(list)
         for point, line in self['Points'].items():
-            self['Line2Points'].setdefault(line, []).append(point)
-        self['SrcPoint2Edges'] = {}
+            self['Line2Points'][line].append(point)
+        self['SrcPoint2Edges'] = defaultdict(list)
         for edge in self['Edges']:
-            (src, dst) = edge['Index']
-            self['SrcPoint2Edges'].setdefault(src, []).append(edge)
-        self['Line2Edges'] = {}
+            src, dst = edge['Index']
+            self['SrcPoint2Edges'][src].append(edge)
+        self['Line2Edges'] = defaultdict(list)
         for (src, edges) in self['SrcPoint2Edges'].items():
             line = self['Points'][src]
-            self['Line2Edges'].setdefault(line, []).extend(edges)
+            self['Line2Edges'][line].extend(edges)
 
     def edges_from_line(self, line):
         return self['Line2Edges'][line]
 
     def edge_from_line(self, line):
         edges = self.edges_from_line(line)
         assert(len(edges) == 1)
         return edges[0]
@@ -80,73 +56,8 @@ class Body(dict):
             if dst['Kind'] != 'Var':
                 continue
             if dst['Variable']['Name'][0] == varname:
                 return edge['Index'][0]
         raise Exception("assignment to variable %s not found" % varname)
 
     def assignment_line(self, varname):
         return self['Points'][self.assignment_point(varname)]
-
-tests = ['test']
-for name in tests:
-    indir = os.path.join(testdir, name)
-    outdir = os.path.join(testdir, "test-output", name)
-    if not os.path.exists(outdir):
-        os.mkdir(outdir)
-
-    def compile(source):
-        cmd = "{CXX} -c {source} -fplugin={sixgill}".format(source=os.path.join(indir, source),
-                                                            CXX=cfg['CXX'], sixgill=cfg['SIXGILL_PLUGIN'])
-        print("Running %s" % cmd)
-        subprocess.check_call(["sh", "-c", cmd])
-
-    def load_db_entry(dbname, pattern):
-        if not isinstance(pattern, basestring):
-            output = subprocess.check_output([binpath("xdbkeys"), dbname + ".xdb"])
-            entries = output.splitlines()
-            matches = [f for f in entries if re.search(pattern, f)]
-            if len(matches) == 0:
-                raise Exception("entry not found")
-            if len(matches) > 1:
-                raise Exception("multiple entries found")
-            pattern = matches[0]
-
-        output = subprocess.check_output([binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
-        return json.loads(output)
-
-    def computeGCTypes():
-        file("defaults.py", "w").write('''\
-analysis_scriptdir = '{testdir}'
-sixgill_bin = '{bindir}'
-'''.format(testdir=testdir, bindir=cfg['SIXGILL_BIN']))
-        cmd = [
-            os.path.join(testdir, "analyze.py"),
-            "gcTypes", "--upto", "gcTypes",
-            "--source=%s" % indir,
-            "--objdir=%s" % outdir,
-            "--js=%s" % cfg['JS_BIN'],
-        ]
-        print("Running " + " ".join(cmd))
-        output = subprocess.check_call(cmd)
-
-    def loadGCTypes():
-        gctypes = {'GCThings': [], 'GCPointers': []}
-        for line in file(os.path.join(outdir, "gcTypes.txt")):
-            m = re.match(r'^(GC\w+): (.*)', line)
-            if m:
-                gctypes[m.group(1) + 's'].append(m.group(2))
-        return gctypes
-
-    def process_body(body):
-        return Body(body)
-
-    def process_bodies(bodies):
-        return [ process_body(b) for b in bodies ]
-
-    def equal(got, expected):
-        if got != expected:
-            print("Got '%s', expected '%s'" % (got, expected))
-
-    os.chdir(outdir)
-    subprocess.call(["sh", "-c", "rm *.xdb"])
-    execfile(os.path.join(indir, "test.py"))
-    print("TEST-PASSED: %s" % name)
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/suppression/source.cpp
@@ -0,0 +1,64 @@
+#define ANNOTATE(property) __attribute__((tag(property)))
+
+struct Cell { int f; } ANNOTATE("GC Thing");
+
+class AutoSuppressGC_Base {
+  public:
+    AutoSuppressGC_Base() {}
+    ~AutoSuppressGC_Base() {}
+} ANNOTATE("Suppress GC");
+
+class AutoSuppressGC_Child : public AutoSuppressGC_Base {
+  public:
+    AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
+};
+
+class AutoSuppressGC {
+    AutoSuppressGC_Child helpImBeingSuppressed;
+
+  public:
+    AutoSuppressGC() {}
+};
+
+extern void GC() ANNOTATE("GC Call");
+
+void GC()
+{
+    // If the implementation is too trivial, the function body won't be emitted at all.
+    asm("");
+}
+
+extern void foo(Cell*);
+
+void suppressedFunction() {
+    GC(); // Calls GC, but is always called within AutoSuppressGC
+}
+
+void halfSuppressedFunction() {
+    GC(); // Calls GC, but is sometimes called within AutoSuppressGC
+}
+
+void unsuppressedFunction() {
+    GC(); // Calls GC, never within AutoSuppressGC
+}
+
+void f() {
+    Cell* cell1 = nullptr;
+    Cell* cell2 = nullptr;
+    Cell* cell3 = nullptr;
+    {
+        AutoSuppressGC nogc;
+        suppressedFunction();
+        halfSuppressedFunction();
+    }
+    foo(cell1);
+    halfSuppressedFunction();
+    foo(cell2);
+    unsuppressedFunction();
+    {
+        // Old bug: it would look from the first AutoSuppressGC constructor it
+        // found to the last destructor. This statement *should* have no effect.
+        AutoSuppressGC nogc;
+    }
+    foo(cell3);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/suppression/test.py
@@ -0,0 +1,23 @@
+test.compile("source.cpp")
+test.run_analysis_script('gcTypes', upto='gcFunctions')
+
+# The suppressions file uses only mangled names since it's for internal use,
+# though I may change that soon given (1) the unfortunate non-uniqueness of
+# mangled constructor names, and (2) the usefulness of this file for
+# mrgiggles's reporting.
+suppressed = test.load_suppressed_functions()
+
+# Only one of these is fully suppressed (ie, *always* called within the scope
+# of an AutoSuppressGC).
+assert(len(filter(lambda f: 'suppressedFunction' in f, suppressed)) == 1)
+assert(len(filter(lambda f: 'halfSuppressedFunction' in f, suppressed)) == 0)
+assert(len(filter(lambda f: 'unsuppressedFunction' in f, suppressed)) == 0)
+
+# gcFunctions should be the inverse, but we get to rely on unmangled names here.
+gcFunctions = test.load_gcFunctions()
+print(gcFunctions)
+assert('void GC()' in gcFunctions)
+assert('void suppressedFunction()' not in gcFunctions)
+assert('void halfSuppressedFunction()' in gcFunctions)
+assert('void unsuppressedFunction()' in gcFunctions)
+assert('void f()' in gcFunctions)
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/t/testlib.py
@@ -0,0 +1,120 @@
+import json
+import os
+import re
+import subprocess
+
+from sixgill import Body
+from collections import defaultdict, namedtuple
+
+scriptdir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+
+HazardSummary = namedtuple('HazardSummary', ['function', 'variable', 'type', 'GCFunction', 'location'])
+
+
+def equal(got, expected):
+    if got != expected:
+        print("Got '%s', expected '%s'" % (got, expected))
+
+def extract_unmangled(func):
+    return func.split('$')[-1]
+
+
+class Test(object):
+    def __init__(self, indir, outdir, cfg):
+        self.indir = indir
+        self.outdir = outdir
+        self.cfg = cfg
+
+    def infile(self, path):
+        return os.path.join(self.indir, path)
+
+    def binpath(self, prog):
+        return os.path.join(self.cfg.sixgill_bin, prog)
+
+    def compile(self, source, options = ''):
+        cmd = "{CXX} -c {source} -O3 -std=c++11 -fplugin={sixgill} -fplugin-arg-xgill-mangle=1 {options}".format(
+            source=self.infile(source),
+            CXX=self.cfg.cxx, sixgill=self.cfg.sixgill_plugin,
+            options=options)
+        if self.cfg.verbose:
+            print("Running %s" % cmd)
+        subprocess.check_call(["sh", "-c", cmd])
+
+    def load_db_entry(self, dbname, pattern):
+        '''Look up an entry from an XDB database file, 'pattern' may be an exact
+        matching string, or an re pattern object matching a single entry.'''
+
+        if not isinstance(pattern, basestring):
+            output = subprocess.check_output([self.binpath("xdbkeys"), dbname + ".xdb"])
+            matches = filter(lambda _: re.search(pattern, _), output.splitlines())
+            if len(matches) == 0:
+                raise Exception("entry not found")
+            if len(matches) > 1:
+                raise Exception("multiple entries found")
+            pattern = matches[0]
+
+        output = subprocess.check_output([self.binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
+        return json.loads(output)
+
+    def run_analysis_script(self, phase, upto=None):
+        file("defaults.py", "w").write('''\
+analysis_scriptdir = '{scriptdir}'
+sixgill_bin = '{bindir}'
+'''.format(scriptdir=scriptdir, bindir=self.cfg.sixgill_bin))
+        cmd = [os.path.join(scriptdir, "analyze.py"), phase]
+        if upto:
+            cmd += ["--upto", upto]
+        cmd.append("--source=%s" % self.indir)
+        cmd.append("--objdir=%s" % self.outdir)
+        cmd.append("--js=%s" % self.cfg.js)
+        if self.cfg.verbose:
+            cmd.append("--verbose")
+            print("Running " + " ".join(cmd))
+        subprocess.check_call(cmd)
+
+    def computeGCTypes(self):
+        self.run_analysis_script("gcTypes", upto="gcTypes")
+
+    def computeHazards(self):
+        self.run_analysis_script("gcTypes")
+
+    def load_text_file(self, filename, extract=lambda l: l):
+        fullpath = os.path.join(self.outdir, filename)
+        values = (extract(line.strip()) for line in file(fullpath))
+        return filter(lambda _: _ is not None, values)
+
+    def load_suppressed_functions(self):
+        return set(self.load_text_file("suppressedFunctions.lst"))
+
+    def load_gcTypes(self):
+        def grab_type(line):
+            m = re.match(r'^(GC\w+): (.*)', line)
+            if m:
+                return (m.group(1) + 's', m.group(2))
+            return None
+
+        gctypes = defaultdict(list)
+        for collection, typename in self.load_text_file('gcTypes.txt', extract=grab_type):
+            gctypes[collection].append(typename)
+        return gctypes
+
+    def load_gcFunctions(self):
+        return self.load_text_file('gcFunctions.lst', extract=extract_unmangled)
+
+    def load_hazards(self):
+        def grab_hazard(line):
+            m = re.match(r"Function '(.*?)' has unrooted '(.*?)' of type '(.*?)' live across GC call '(.*?)' at (.*)", line)
+            if m:
+                info = list(m.groups())
+                info[0] = info[0].split("$")[-1]
+                info[3] = info[3].split("$")[-1]
+                return HazardSummary(*info)
+            return None
+
+        return self.load_text_file('rootingHazards.txt', extract=grab_hazard)
+
+    def process_body(self, body):
+        return Body(body)
+
+    def process_bodies(self, bodies):
+        return [self.process_body(b) for b in bodies]
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/test/source.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-#define ANNOTATE(property) __attribute__((tag(property)))
-
-namespace js {
-namespace gc {
-struct Cell { int f; } ANNOTATE("GC Thing");
-}
-}
-
-struct Bogon {
-};
-
-struct JustACell : public js::gc::Cell {
-    bool iHaveNoDataMembers() { return true; }
-};
-
-struct JSObject : public js::gc::Cell, public Bogon {
-    int g;
-};
-
-struct SpecialObject : public JSObject {
-    int z;
-};
-
-struct ErrorResult {
-    bool hasObj;
-    JSObject *obj;
-    void trace() {}
-} ANNOTATE("Suppressed GC Pointer");
-
-struct OkContainer {
-    ErrorResult res;
-    bool happy;
-};
-
-struct UnrootedPointer {
-    JSObject *obj;
-};
-
-template <typename T>
-class Rooted {
-    T data;
-} ANNOTATE("Rooted Pointer");
-
-extern void js_GC() ANNOTATE("GC Call") ANNOTATE("Slow");
-
-void js_GC() {}
-
-void root_arg(JSObject *obj, JSObject *random)
-{
-  // Use all these types so they get included in the output.
-  SpecialObject so;
-  UnrootedPointer up;
-  Bogon b;
-  OkContainer okc;
-  Rooted<JSObject*> ro;
-  Rooted<SpecialObject*> rso;
-
-  obj = random;
-
-  JSObject *other1 = obj;
-  js_GC();
-
-  float MARKER1 = 0;
-  JSObject *other2 = obj;
-  other1->f = 1;
-  other2->f = -1;
-
-  unsigned int u1 = 1;
-  unsigned int u2 = -1;
-}
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/test/test.py
+++ /dev/null
@@ -1,58 +0,0 @@
-compile("source.cpp")
-computeGCTypes()
-body = process_body(load_db_entry("src_body", re.compile(r'root_arg'))[0])
-
-# Rendering positive and negative integers
-marker1 = body.assignment_line('MARKER1')
-equal(body.edge_from_line(marker1 + 2)['Exp'][1]['String'], '1')
-equal(body.edge_from_line(marker1 + 3)['Exp'][1]['String'], '-1')
-
-equal(body.edge_from_point(body.assignment_point('u1'))['Exp'][1]['String'], '1')
-equal(body.edge_from_point(body.assignment_point('u2'))['Exp'][1]['String'], '4294967295')
-
-assert('obj' in body['Variables'])
-assert('random' in body['Variables'])
-assert('other1' in body['Variables'])
-assert('other2' in body['Variables'])
-
-# Test function annotations
-js_GC = process_body(load_db_entry("src_body", re.compile(r'js_GC'))[0])
-annotations = js_GC['Variables']['void js_GC()']['Annotation']
-assert(annotations)
-found_call_tag = False
-for annotation in annotations:
-    (annType, value) = annotation['Name']
-    if annType == 'Tag' and value == 'GC Call':
-        found_call_tag = True
-assert(found_call_tag)
-
-# Test type annotations
-
-# js::gc::Cell first
-cell = load_db_entry("src_comp", 'js::gc::Cell')[0]
-assert(cell['Kind'] == 'Struct')
-annotations = cell['Annotation']
-assert(len(annotations) == 1)
-(tag, value) = annotations[0]['Name']
-assert(tag == 'Tag')
-assert(value == 'GC Thing')
-
-# Check JSObject inheritance.
-JSObject = load_db_entry("src_comp", 'JSObject')[0]
-bases = [ b['Base'] for b in JSObject['CSUBaseClass'] ]
-assert('js::gc::Cell' in bases)
-assert('Bogon' in bases)
-assert(len(bases) == 2)
-
-# Check type analysis
-gctypes = loadGCTypes()
-assert('js::gc::Cell' in gctypes['GCThings'])
-assert('JustACell' in gctypes['GCThings'])
-assert('JSObject' in gctypes['GCThings'])
-assert('SpecialObject' in gctypes['GCThings'])
-assert('UnrootedPointer' in gctypes['GCPointers'])
-assert('Bogon' not in gctypes['GCThings'])
-assert('Bogon' not in gctypes['GCPointers'])
-assert('ErrorResult' not in gctypes['GCPointers'])
-assert('OkContainer' not in gctypes['GCPointers'])
-assert('class Rooted<JSObject*>' not in gctypes['GCPointers'])
--- a/js/src/devtools/rootAnalysis/utility.js
+++ b/js/src/devtools/rootAnalysis/utility.js
@@ -1,16 +1,25 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 // gcc appends this to mangled function names for "not in charge"
 // constructors/destructors.
 var internalMarker = " *INTERNAL* ";
 
+if (! Set.prototype.hasOwnProperty("update")) {
+    Object.defineProperty(Set.prototype, "update", {
+        value: function (collection) {
+            for (let elt of collection)
+                this.add(elt);
+        }
+    });
+}
+
 function assert(x, msg)
 {
     if (x)
         return;
     debugger;
     if (msg)
         throw "assertion failed: " + msg + "\n" + (Error().stack);
     else
@@ -177,8 +186,26 @@ function* readFileLines_gen(filename)
     if (fp.isNull())
         throw "Unable to open '" + filename + "'"
 
     while (libc.getline(linebuf.address(), bufsize.address(), fp) > 0)
         yield linebuf.readString();
     libc.fclose(fp);
     libc.free(ctypes.void_t.ptr(linebuf));
 }
+
+function addToKeyedList(collection, key, entry)
+{
+    if (!(key in collection))
+        collection[key] = [];
+    collection[key].push(entry);
+}
+
+function loadTypeInfo(filename)
+{
+    var info = {};
+    for (var line of readFileLines_gen(filename)) {
+        line = line.replace(/\n/, "");
+        let [property, name] = line.split("$$");
+        addToKeyedList(info, property, name);
+    }
+    return info;
+}
--- a/js/src/gc/Iteration.cpp
+++ b/js/src/gc/Iteration.cpp
@@ -90,46 +90,44 @@ js::IterateChunks(JSRuntime* rt, void* d
         chunkCallback(rt, data, chunk);
 }
 
 void
 js::IterateScripts(JSRuntime* rt, JSCompartment* compartment,
                    void* data, IterateScriptCallback scriptCallback)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
-    rt->gc.evictNursery();
-    MOZ_ASSERT(rt->gc.nursery.isEmpty());
-
+    AutoEmptyNursery empty(rt);
     AutoPrepareForTracing prep(rt, SkipAtoms);
 
     if (compartment) {
-        for (ZoneCellIterUnderGC i(compartment->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        Zone* zone = compartment->zone();
+        for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next()) {
             if (script->compartment() == compartment)
                 scriptCallback(rt, data, script);
         }
     } else {
         for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-            for (ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next())
-                scriptCallback(rt, data, i.get<JSScript>());
+            for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next())
+                scriptCallback(rt, data, script);
         }
     }
 }
 
 void
 js::IterateGrayObjects(Zone* zone, GCThingCallback cellCallback, void* data)
 {
-    zone->runtimeFromMainThread()->gc.evictNursery();
-    AutoPrepareForTracing prep(zone->runtimeFromMainThread(), SkipAtoms);
+    JSRuntime* rt = zone->runtimeFromMainThread();
+    AutoEmptyNursery empty(rt);
+    AutoPrepareForTracing prep(rt, SkipAtoms);
 
     for (auto thingKind : ObjectAllocKinds()) {
-        for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
-            JSObject* obj = i.get<JSObject>();
+        for (auto obj = zone->cellIter<JSObject>(thingKind, empty); !obj.done(); obj.next()) {
             if (obj->asTenured().isMarked(GRAY))
-                cellCallback(data, JS::GCCellPtr(obj));
+                cellCallback(data, JS::GCCellPtr(obj.get()));
         }
     }
 }
 
 JS_PUBLIC_API(void)
 JS_IterateCompartments(JSRuntime* rt, void* data,
                        JSIterateCompartmentCallback compartmentCallback)
 {
--- a/js/src/gc/Memory.cpp
+++ b/js/src/gc/Memory.cpp
@@ -430,17 +430,17 @@ InitMemorySubsystem()
     if (pageSize == 0)
         pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE));
 }
 
 static inline void*
 MapMemoryAt(void* desired, size_t length, int prot = PROT_READ | PROT_WRITE,
             int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0)
 {
-#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__))
+#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) || defined(__aarch64__)
     MOZ_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0);
 #endif
     void* region = mmap(desired, length, prot, flags, fd, offset);
     if (region == MAP_FAILED)
         return nullptr;
     /*
      * mmap treats the given address as a hint unless the MAP_FIXED flag is
      * used (which isn't usually what you want, as this overrides existing
@@ -480,16 +480,51 @@ MapMemory(size_t length, int prot = PROT
      * as out of memory.
      */
     if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) {
         if (munmap(region, length))
             MOZ_ASSERT(errno == ENOMEM);
         return nullptr;
     }
     return region;
+#elif defined(__aarch64__)
+   /*
+    * There might be similar virtual address issue on arm64 which depends on
+    * hardware and kernel configurations. But the work around is slightly
+    * different due to the different mmap behavior.
+    *
+    * TODO: Merge with the above code block if this implementation works for
+    * ia64 and sparc64.
+    */
+    const uintptr_t start = UINT64_C(0x0000070000000000);
+    const uintptr_t end   = UINT64_C(0x0000800000000000);
+    const uintptr_t step  = ChunkSize;
+   /*
+    * Optimization options if there are too many retries in practice:
+    * 1. Examine /proc/self/maps to find an available address. This file is
+    *    not always available, however. In addition, even if we examine
+    *    /proc/self/maps, we may still need to retry several times due to
+    *    racing with other threads.
+    * 2. Use a global/static variable with lock to track the addresses we have
+    *    allocated or tried.
+    */
+    uintptr_t hint;
+    void* region = MAP_FAILED;
+    for (hint = start; region == MAP_FAILED && hint + length <= end; hint += step) {
+        region = mmap((void*)hint, length, prot, flags, fd, offset);
+        if (region != MAP_FAILED) {
+            if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000) {
+                if (munmap(region, length)) {
+                    MOZ_ASSERT(errno == ENOMEM);
+                }
+                region = MAP_FAILED;
+            }
+        }
+    }
+    return region == MAP_FAILED ? nullptr : region;
 #else
     void* region = MozTaggedAnonymousMmap(nullptr, length, prot, flags, fd, offset, "js-gc-heap");
     if (region == MAP_FAILED)
         return nullptr;
     return region;
 #endif
 }
 
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -2,16 +2,17 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "gc/Statistics.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/PodOperations.h"
 
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdio.h>
 
 #include "jsprf.h"
@@ -22,16 +23,17 @@
 #include "vm/HelperThreads.h"
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::gcstats;
 
+using mozilla::DebugOnly;
 using mozilla::MakeRange;
 using mozilla::PodArrayZero;
 using mozilla::PodZero;
 
 /*
  * If this fails, then you can either delete this assertion and allow all
  * larger-numbered reasons to pile up in the last telemetry bucket, or switch
  * to GC_REASON_3 and bump the max value.
@@ -756,17 +758,17 @@ Statistics::Statistics(JSRuntime* rt)
     fp(nullptr),
     gcDepth(0),
     nonincrementalReason_(nullptr),
     timedGCStart(0),
     preBytes(0),
     maxPauseInInterval(0),
     phaseNestingDepth(0),
     activeDagSlot(PHASE_DAG_NONE),
-    suspendedPhaseNestingDepth(0),
+    suspended(0),
     sliceCallback(nullptr),
     nurseryCollectionCallback(nullptr),
     aborted(false)
 {
     PodArrayZero(phaseTotals);
     PodArrayZero(counts);
     PodArrayZero(phaseStartTimes);
     for (auto d : MakeRange(NumTimingArrays))
@@ -1064,17 +1066,17 @@ Statistics::startTimingMutator()
 {
     if (phaseNestingDepth != 0) {
         // Should only be called from outside of GC.
         MOZ_ASSERT(phaseNestingDepth == 1);
         MOZ_ASSERT(phaseNesting[0] == PHASE_MUTATOR);
         return false;
     }
 
-    MOZ_ASSERT(suspendedPhaseNestingDepth == 0);
+    MOZ_ASSERT(suspended == 0);
 
     timedGCTime = 0;
     phaseStartTimes[PHASE_MUTATOR] = 0;
     phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR] = 0;
     timedGCStart = 0;
 
     beginPhase(PHASE_MUTATOR);
     return true;
@@ -1090,29 +1092,56 @@ Statistics::stopTimingMutator(double& mu
     endPhase(PHASE_MUTATOR);
     mutator_ms = t(phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR]);
     gc_ms = t(timedGCTime);
 
     return true;
 }
 
 void
+Statistics::suspendPhases(Phase suspension)
+{
+    MOZ_ASSERT(suspension == PHASE_EXPLICIT_SUSPENSION || suspension == PHASE_IMPLICIT_SUSPENSION);
+    while (phaseNestingDepth) {
+        MOZ_ASSERT(suspended < mozilla::ArrayLength(suspendedPhases));
+        Phase parent = phaseNesting[phaseNestingDepth - 1];
+        suspendedPhases[suspended++] = parent;
+        recordPhaseEnd(parent);
+    }
+    suspendedPhases[suspended++] = suspension;
+}
+
+void
+Statistics::resumePhases()
+{
+    DebugOnly<Phase> popped = suspendedPhases[--suspended];
+    MOZ_ASSERT(popped == PHASE_EXPLICIT_SUSPENSION || popped == PHASE_IMPLICIT_SUSPENSION);
+    while (suspended &&
+           suspendedPhases[suspended - 1] != PHASE_EXPLICIT_SUSPENSION &&
+           suspendedPhases[suspended - 1] != PHASE_IMPLICIT_SUSPENSION)
+    {
+        Phase resumePhase = suspendedPhases[--suspended];
+        if (resumePhase == PHASE_MUTATOR)
+            timedGCTime += PRMJ_Now() - timedGCStart;
+        beginPhase(resumePhase);
+    }
+}
+
+void
 Statistics::beginPhase(Phase phase)
 {
     Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
 
     // Re-entry is allowed during callbacks, so pause callback phases while
     // other phases are in progress, auto-resuming after they end. As a result,
     // nested GC time will not be accounted against the callback phases.
     //
     // Reuse this mechanism for managing PHASE_MUTATOR.
     if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
-        MOZ_ASSERT(suspendedPhaseNestingDepth < mozilla::ArrayLength(suspendedPhases));
-        suspendedPhases[suspendedPhaseNestingDepth++] = parent;
-        recordPhaseEnd(parent);
+        suspendPhases(PHASE_IMPLICIT_SUSPENSION);
         parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
     }
 
     // Guard against any other re-entry.
     MOZ_ASSERT(!phaseStartTimes[phase]);
 
     MOZ_ASSERT(phases[phase].index == phase);
     MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
@@ -1149,22 +1178,18 @@ Statistics::endPhase(Phase phase)
 {
     recordPhaseEnd(phase);
 
     if (phases[phase].parent == PHASE_MULTI_PARENTS)
         activeDagSlot = PHASE_DAG_NONE;
 
     // When emptying the stack, we may need to resume a callback phase
     // (PHASE_GC_BEGIN/END) or return to timing the mutator (PHASE_MUTATOR).
-    if (phaseNestingDepth == 0 && suspendedPhaseNestingDepth > 0) {
-        Phase resumePhase = suspendedPhases[--suspendedPhaseNestingDepth];
-        if (resumePhase == PHASE_MUTATOR)
-            timedGCTime += PRMJ_Now() - timedGCStart;
-        beginPhase(resumePhase);
-    }
+    if (phaseNestingDepth == 0 && suspended > 0 && suspendedPhases[suspended - 1] == PHASE_IMPLICIT_SUSPENSION)
+        resumePhases();
 }
 
 void
 Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
 {
     phaseNestingDepth--;
 
     if (!slices.empty())
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -81,16 +81,18 @@ enum Phase : uint8_t {
     PHASE_MARK_CCWS,
     PHASE_MARK_ROOTERS,
     PHASE_MARK_RUNTIME_DATA,
     PHASE_MARK_EMBEDDING,
     PHASE_MARK_COMPARTMENTS,
 
     PHASE_LIMIT,
     PHASE_NONE = PHASE_LIMIT,
+    PHASE_EXPLICIT_SUSPENSION = PHASE_LIMIT,
+    PHASE_IMPLICIT_SUSPENSION,
     PHASE_MULTI_PARENTS
 };
 
 enum Stat {
     STAT_NEW_CHUNK,
     STAT_DESTROY_CHUNK,
     STAT_MINOR_GC,
 
@@ -164,16 +166,32 @@ struct Statistics
 
     explicit Statistics(JSRuntime* rt);
     ~Statistics();
 
     void beginPhase(Phase phase);
     void endPhase(Phase phase);
     void endParallelPhase(Phase phase, const GCParallelTask* task);
 
+    // Occasionally, we may be in the middle of something that is tracked by
+    // this class, and we need to do something unusual (eg evict the nursery)
+    // that doesn't normally nest within the current phase. Suspend the
+    // currently tracked phase stack, at which time the caller is free to do
+    // other tracked operations.
+    //
+    // This also happens internally with PHASE_GC_BEGIN and other "non-GC"
+    // phases. While in these phases, any beginPhase will automatically suspend
+    // the non-GC phase, until that inner stack is complete, at which time it
+    // will automatically resume the non-GC phase. Explicit suspensions do not
+    // get auto-resumed.
+    void suspendPhases(Phase suspension = PHASE_EXPLICIT_SUSPENSION);
+
+    // Resume a suspended stack of phases.
+    void resumePhases();
+
     void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
                     SliceBudget budget, JS::gcreason::Reason reason);
     void endSlice();
     void setSliceCycleCount(unsigned cycleCount);
 
     MOZ_MUST_USE bool startTimingMutator();
     MOZ_MUST_USE bool stopTimingMutator(double& mutator_ms, double& gc_ms);
 
@@ -313,23 +331,24 @@ struct Statistics
     mutable int64_t maxPauseInInterval;
 
     /* Phases that are currently on stack. */
     Phase phaseNesting[MAX_NESTING];
     size_t phaseNestingDepth;
     size_t activeDagSlot;
 
     /*
-     * To avoid recursive nesting, we discontinue a callback phase when any
-     * other phases are started. Remember what phase to resume when the inner
-     * phases are complete. (And because GCs can nest within the callbacks any
-     * number of times, we need a whole stack of of phases to resume.)
+     * Certain phases can interrupt the phase stack, eg callback phases. When
+     * this happens, we move the suspended phases over to a sepearate list,
+     * terminated by a dummy PHASE_SUSPENSION phase (so that we can nest
+     * suspensions by suspending multiple stacks with a PHASE_SUSPENSION in
+     * between).
      */
-    Phase suspendedPhases[MAX_NESTING];
-    size_t suspendedPhaseNestingDepth;
+    Phase suspendedPhases[MAX_NESTING * 3];
+    size_t suspended;
 
     /* Sweep times for SCCs of compartments. */
     Vector<int64_t, 0, SystemAllocPolicy> sccTimes;
 
     JS::GCSliceCallback sliceCallback;
     JS::GCNurseryCollectionCallback nurseryCollectionCallback;
 
     /*
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -148,23 +148,23 @@ Zone::sweepBreakpoints(FreeOp* fop)
         return;
 
     /*
      * Sweep all compartments in a zone at the same time, since there is no way
      * to iterate over the scripts belonging to a single compartment in a zone.
      */
 
     MOZ_ASSERT(isGCSweepingOrCompacting());
-    for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
+    for (auto iter = cellIter<JSScript>(); !iter.done(); iter.next()) {
+        JSScript* script = iter;
         if (!script->hasAnyBreakpointsOrStepMode())
             continue;
 
         bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
-        MOZ_ASSERT(script == i.get<JSScript>());
+        MOZ_ASSERT(script == iter);
         for (unsigned i = 0; i < script->length(); i++) {
             BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
             if (!site)
                 continue;
 
             Breakpoint* nextbp;
             for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
                 nextbp = bp->nextInSite();
@@ -202,30 +202,27 @@ Zone::discardJitCode(FreeOp* fop)
         return;
 
     if (isPreservingCode()) {
         PurgeJITCaches(this);
     } else {
 
 #ifdef DEBUG
         /* Assert no baseline scripts are marked as active. */
-        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = cellIter<JSScript>(); !script.done(); script.next())
             MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
-        }
 #endif
 
         /* Mark baseline scripts on the stack as active. */
         jit::MarkActiveBaselineScripts(this);
 
         /* Only mark OSI points if code is being discarded. */
         jit::InvalidateAll(fop, this);
 
-        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = cellIter<JSScript>(); !script.done(); script.next())  {
             jit::FinishInvalidation(fop, script);
 
             /*
              * Discard baseline script if it's not marked as active. Note that
              * this also resets the active flag.
              */
             jit::FinishDiscardBaselineScript(fop, script);
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -79,16 +79,19 @@ struct UniqueIdGCPolicy {
 using UniqueIdMap = GCHashMap<Cell*,
                               uint64_t,
                               PointerHasher<Cell*, 3>,
                               SystemAllocPolicy,
                               UniqueIdGCPolicy>;
 
 extern uint64_t NextCellUniqueId(JSRuntime* rt);
 
+template <typename T>
+class ZoneCellIter;
+
 } // namespace gc
 } // namespace js
 
 namespace JS {
 
 // A zone is a collection of compartments. Every compartment belongs to exactly
 // one zone. In Firefox, there is roughly one zone per tab along with a system
 // zone for everything else. Zones mainly serve as boundaries for garbage
@@ -152,16 +155,23 @@ struct Zone : public JS::shadow::Zone,
     void updateMallocCounter(size_t nbytes) {
         // Note: this code may be run from worker threads. We tolerate any
         // thread races when updating gcMallocBytes.
         gcMallocBytes -= ptrdiff_t(nbytes);
         if (MOZ_UNLIKELY(isTooMuchMalloc()))
             onTooMuchMalloc();
     }
 
+    // Iterate over all cells in the zone. See the definition of ZoneCellIter
+    // in jsgcinlines.h for the possible arguments and documentation.
+    template <typename T, typename... Args>
+    js::gc::ZoneCellIter<T> cellIter(Args... args) {
+        return js::gc::ZoneCellIter<T>(const_cast<Zone*>(this), mozilla::Forward<Args>(args)...);
+    }
+
     bool isTooMuchMalloc() const { return gcMallocBytes <= 0; }
     void onTooMuchMalloc();
 
     MOZ_MUST_USE void* onOutOfMemory(js::AllocFunction allocFunc, size_t nbytes,
                                                void* reallocPtr = nullptr) {
         if (!js::CurrentThreadCanAccessRuntime(runtime_))
             return nullptr;
         return runtimeFromMainThread()->onOutOfMemory(allocFunc, nbytes, reallocPtr);
--- a/js/src/jit-test/lib/simd.js
+++ b/js/src/jit-test/lib/simd.js
@@ -4,21 +4,21 @@ if (!this.hasOwnProperty("SIMD"))
 function booleanBinaryX4(op, v, w) {
     var arr = [];
     var [varr, warr] = [simdToArray(v), simdToArray(w)];
     for (var i = 0; i < 4; i++)
         arr[i] = op(varr[i], warr[i]);
     return arr;
 }
 
-function binaryX4(op, v, w) {
+function binaryX(op, v, w) {
     var arr = [];
     var [varr, warr] = [simdToArray(v), simdToArray(w)];
     [varr, warr] = [varr.map(Math.fround), warr.map(Math.fround)];
-    for (var i = 0; i < 4; i++)
+    for (var i = 0; i < varr.length; i++)
         arr[i] = op(varr[i], warr[i]);
     return arr.map(Math.fround);
 }
 
 function unaryX4(op, v, coerceFunc) {
     var arr = [];
     var varr = simdToArray(v).map(coerceFunc);
     for (var i = 0; i < 4; i++)
--- a/js/src/jit-test/tests/SIMD/binary-arith.js
+++ b/js/src/jit-test/tests/SIMD/binary-arith.js
@@ -4,20 +4,27 @@ setJitCompilerOption("ion.warmup.trigger
 
 function f() {
     var i1 = SIMD.Int32x4(1, 2, 3, 4);
     var i2 = SIMD.Int32x4(4, 3, 2, 1);
 
     var f1 = SIMD.Float32x4(1, 2, 3, 4);
     var f2 = SIMD.Float32x4(4, 3, 2, 1);
 
+    var i8_1 = SIMD.Int8x16(1, 2, 3, 4, 20, 30, 40, 50, 100, 115, 120, 125);
+    var i8_2 = SIMD.Int8x16(4, 3, 2, 1,  8,  7,  6,  5,  12,  11,  10,   9);
+
     for (var i = 0; i < 150; i++) {
-        assertEqX4(SIMD.Float32x4.add(f1, f2), binaryX4((x, y) => x + y, f1, f2));
-        assertEqX4(SIMD.Float32x4.sub(f1, f2), binaryX4((x, y) => x - y, f1, f2));
-        assertEqX4(SIMD.Float32x4.mul(f1, f2), binaryX4((x, y) => x * y, f1, f2));
+        assertEqX4(SIMD.Float32x4.add(f1, f2), binaryX((x, y) => x + y, f1, f2));
+        assertEqX4(SIMD.Float32x4.sub(f1, f2), binaryX((x, y) => x - y, f1, f2));
+        assertEqX4(SIMD.Float32x4.mul(f1, f2), binaryX((x, y) => x * y, f1, f2));
 
-        assertEqX4(SIMD.Int32x4.add(i1, i2), binaryX4((x, y) => x + y, i1, i2));
-        assertEqX4(SIMD.Int32x4.sub(i1, i2), binaryX4((x, y) => x - y, i1, i2));
-        assertEqX4(SIMD.Int32x4.mul(i1, i2), binaryX4((x, y) => x * y, i1, i2));
+        assertEqX4(SIMD.Int32x4.add(i1, i2), binaryX((x, y) => x + y, i1, i2));
+        assertEqX4(SIMD.Int32x4.sub(i1, i2), binaryX((x, y) => x - y, i1, i2));
+        assertEqX4(SIMD.Int32x4.mul(i1, i2), binaryX((x, y) => x * y, i1, i2));