Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 01 Jun 2016 15:09:07 +0200
changeset 299894 22047a4eea784c15026c77911c0bd6ea1b70fa68
parent 299893 08084e9e30d23d2154b0e994a6e2733633c381fb (current diff)
parent 299888 111970c738234569c8c180319155327316335deb (diff)
child 299895 e88afebe9226340f67730f2c867a78a33e7fa4e0
child 300055 199560989621a1fb7790aa9f44cfb07aea49a755
push id30303
push userkwierso@gmail.com
push dateWed, 01 Jun 2016 23:25:45 +0000
treeherdermozilla-central@22047a4eea78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to fx-team
browser/components/sessionstore/SessionStore.jsm
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));
+
+        assertEqX4(SIMD.Int8x16.add(i8_1, i8_2), binaryX((x, y) => (x + y) << 24 >> 24, i8_1, i8_2));
+        assertEqX4(SIMD.Int8x16.sub(i8_1, i8_2), binaryX((x, y) => (x - y) << 24 >> 24, i8_1, i8_2));
+        assertEqX4(SIMD.Int8x16.mul(i8_1, i8_2), binaryX((x, y) => (x * y) << 24 >> 24, i8_1, i8_2));
     }
 }
 
 f();
--- a/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js
+++ b/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js
@@ -17,17 +17,17 @@ function minNum(x, y) {
         return x;
     return Math.min(x, y);
 }