Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 04 Nov 2015 12:45:40 +0100
changeset 306568 6e5296397487dc014b07a9cc9dfc7b938e2b4f17
parent 306567 e9f27a4b9e2edf15fa506cc27fc0e6ff0f011e2f (current diff)
parent 306504 6077f51254c69a1e14e1b61acba4af451bf1783e (diff)
child 306569 c5593fc2699b3c8d52785eca4353fc170c08a6a9
push id7160
push usergarndt@mozilla.com
push dateWed, 04 Nov 2015 22:20:12 +0000
milestone45.0a1
Merge mozilla-central to b2g-inbound
devtools/client/themes/toolbars.inc.css
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
mobile/android/base/resources/xml-v11/preferences_customize.xml
mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
mobile/android/base/resources/xml/preferences_customize.xml
mobile/android/base/resources/xml/preferences_customize_tablet.xml
mobile/android/base/resources/xml/preferences_devtools.xml
mobile/android/base/resources/xml/preferences_display.xml
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testClearPrivateData.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testImportFromAndroid.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testMasterPassword.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testSettingsMenuItems.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testSystemPages.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testTitleBar.java
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
+Bug 1214058 New xpcshell test not getting picked up
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -1233,35 +1233,37 @@ Accessible::ApplyARIAState(uint64_t* aSt
 
     // We only force the readonly bit off if we have a real mapping for the aria
     // role. This preserves the ability for screen readers to use readonly
     // (primarily on the document) as the hint for creating a virtual buffer.
     if (mRoleMapEntry->role != roles::NOTHING)
       *aState &= ~states::READONLY;
 
     if (mContent->HasID()) {
-      // If has a role & ID and aria-activedescendant on the container, assume focusable
-      nsIContent *ancestorContent = mContent;
-      while ((ancestorContent = ancestorContent->GetParent()) != nullptr) {
-        if (ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
-            // ancestor has activedescendant property, this content could be active
+      // If has a role & ID and aria-activedescendant on the container, assume
+      // focusable.
+      const Accessible* ancestor = this;
+      while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+        dom::Element* el = ancestor->Elm();
+        if (el &&
+            el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
           *aState |= states::FOCUSABLE;
           break;
         }
       }
     }
   }
 
   if (*aState & states::FOCUSABLE) {
-    // Special case: aria-disabled propagates from ancestors down to any focusable descendant
-    nsIContent *ancestorContent = mContent;
-    while ((ancestorContent = ancestorContent->GetParent()) != nullptr) {
-      if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
-                                       nsGkAtoms::_true, eCaseMatters)) {
-          // ancestor has aria-disabled property, this is disabled
+    // Propogate aria-disabled from ancestors down to any focusable descendant.
+    const Accessible* ancestor = this;
+    while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+      dom::Element* el = ancestor->Elm();
+      if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
+                                nsGkAtoms::_true, eCaseMatters)) {
         *aState |= states::UNAVAILABLE;
         break;
       }
     }
   }
 
   // special case: A native button element whose role got transformed by ARIA to a toggle button
   // Also applies to togglable button menus, like in the Dev Tools Web Console.
--- a/accessible/tests/mochitest/attributes/test_obj_group.html
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -186,16 +186,22 @@
       testGroupAttrs("combo1_opt3", 3, 4);
       testGroupAttrs("combo1_opt4", 4, 4);
 
       //////////////////////////////////////////////////////////////////////////
       // ARIA table
       testGroupAttrs("table_cell", 3, 4);
       testGroupAttrs("table_row", 2, 2);
 
+      //////////////////////////////////////////////////////////////////////////
+      // ARIA list constructed by ARIA owns
+      testGroupAttrs("t1_li1", 1, 3);
+      testGroupAttrs("t1_li2", 2, 3);
+      testGroupAttrs("t1_li3", 3, 3);
+
       // Test that group position information updates after deleting node.
       testGroupAttrs("tree4_ti1", 1, 2, 1);
       testGroupAttrs("tree4_ti2", 2, 2, 1);
       var tree4element = document.getElementById("tree4_ti1");
       var tree4acc = getAccessible("tree4");
       tree4element.parentNode.removeChild(tree4element);
       waitForEvent(EVENT_REORDER, tree4acc, function() {
         testGroupAttrs("tree4_ti2", 1, 1, 1);
@@ -448,10 +454,16 @@
     <input type="radio" id="radio5" name="group3">
   </form>
 
   <div role="table" aria-colcount="4" aria-rowcount="2">
     <div role="row" id="table_row" aria-rowindex="2">
       <div role="cell" id="table_cell" aria-colindex="3">cell</div>
     </div>
   </div>
+
+  <div role="list" aria-owns="t1_li1 t1_li2 t1_li3">
+    <div role="listitem" id="t1_li2">Apples</div>
+    <div role="listitem" id="t1_li1">Oranges</div>
+  </span>
+  <div role="listitem" id="t1_li3">Bananas</div>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
+++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
@@ -46,41 +46,42 @@ https://bugzilla.mozilla.org/show_bug.cg
         new focusChecker(aNewItemID)
       ];
 
       this.invoke = function insertItemNFocus_invoke()
       {
         var container  = getNode(aID);
         var itemNode = document.createElement("div");
         itemNode.setAttribute("id", aNewItemID);
-        itemNode.textContent = "item3";
+        itemNode.textContent = aNewItemID;
         container.appendChild(itemNode);
 
         container.setAttribute("aria-activedescendant", aNewItemID);
       }
 
       this.getID = function insertItemNFocus_getID()
       {
         return "insert new node and focus it with ID: " + aNewItemID;
       }
     }
 
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
 
-      gQueue.push(new synthFocus("container", new focusChecker("item1")));
-      gQueue.push(new changeARIAActiveDescendant("container", "item2"));
+      gQueue.push(new synthFocus("listbox", new focusChecker("item1")));
+      gQueue.push(new changeARIAActiveDescendant("listbox", "item2"));
+      gQueue.push(new changeARIAActiveDescendant("listbox", "item3"));
 
       gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry")));
       gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2"));
 
       todo(false, "No focus for inserted element, bug 687011");
-      //gQueue.push(new insertItemNFocus("container", "item3"));
+      //gQueue.push(new insertItemNFocus("listbox", "item4"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -96,20 +97,22 @@ https://bugzilla.mozilla.org/show_bug.cg
      title="Focus may be missed when ARIA active-descendant is changed on active composite widget">
     Mozilla Bug 761102
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <div role="listbox" aria-activedescendant="item1" id="container" tabindex="1">
+  <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1"
+       aria-owns="item3">
     <div role="listitem" id="item1">item1</div>
     <div role="listitem" id="item2">item2</div>
   </div>
+  <div role="listitem" id="item3">item3</div>
 
   <div role="combobox" id="combobox">
     <input id="combobox_entry">
     <ul>
       <li role="option" id="combobox_option1">option1</li>
       <li role="option" id="combobox_option2">option2</li>
     </ul>
   </div>
--- a/accessible/tests/mochitest/states/test_aria.html
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -495,24 +495,26 @@
 
   <div role="listbox">
     <div id="aria_selectable_listitem" role="option" aria-selected="true">Item1</div>
   </div>
 
   <!-- Test that aria-disabled state gets propagated to all descendants -->
   <div id="group" role="group" aria-disabled="true">
     <button>hi</button>
-    <div tabindex="0" role="listbox" aria-activedescendant="item1">
+    <div tabindex="0" role="listbox" aria-activedescendant="item1"
+         aria-owns="item5">
       <div role="option" id="item1">Item 1</div>
       <div role="option" id="item2">Item 2</div>
       <div role="option" id="item3">Item 3</div>
       <div role="option" id="item4">Item 4</div>
     </div>
     <div role="slider" tabindex="0">A slider</div>
   </div>
+  <div role="option" id="item5">Item 5</div>
 
   <!-- Test active state -->
   <div id="as_listbox" tabindex="0" role="listbox"
        aria-activedescendant="as_item1">
     <div role="option" id="as_item1">Item 1</div>
     <div role="option" id="as_item2">Item 2</div>
   </div>
 
--- a/addon-sdk/source/lib/sdk/content/context-menu.js
+++ b/addon-sdk/source/lib/sdk/content/context-menu.js
@@ -178,17 +178,17 @@ CONTEXTS.SelectorContext = Class({
     Context.prototype.initialize.call(this, id);
     this.selector = selector;
   },
 
   adjustPopupNode: function adjustPopupNode(popupNode) {
     let selector = this.selector;
 
     while (!(popupNode instanceof Ci.nsIDOMDocument)) {
-      if (popupNode.mozMatchesSelector(selector))
+      if (popupNode.matches(selector))
         return popupNode;
 
       popupNode = popupNode.parentNode;
     }
 
     return null;
   },
 
--- a/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
+++ b/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
@@ -289,31 +289,31 @@ exports.testStringOverload = createProxy
         return document.querySelectorAll(this);
       };
       assert("input".update().length == 3, "String.prototype overload works");
       done();
     }
   );
 });
 
-exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {
+exports["test Element.matches()"] = createProxyTest("", function (helper) {
   helper.createWorker(
     'new ' + function ContentScriptScope() {
-      // Check mozMatchesSelector XrayWrappers bug:
-      // mozMatchesSelector returns bad results when we are not calling it from the node itself
-      // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
-      assert(document.createElement( "div" ).mozMatchesSelector("div"),
-             "mozMatchesSelector works while being called from the node");
-      assert(document.documentElement.mozMatchesSelector.call(
+      // Check matches XrayWrappers bug (Bug 658909):
+      // Test that Element.matches() does not return bad results when we are
+      // not calling it from the node itself.
+      assert(document.createElement( "div" ).matches("div"),
+             "matches works while being called from the node");
+      assert(document.documentElement.matches.call(
                document.createElement( "div" ),
                "div"
              ),
-             "mozMatchesSelector works while being called from a " +
+             "matches works while being called from a " +
              "function reference to " +
-             "document.documentElement.mozMatchesSelector.call");
+             "document.documentElement.matches.call");
       done();
     }
   );
 });
 
 exports["test Events Overload"] = createProxyTest("", function (helper) {
 
   helper.createWorker(
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -289,31 +289,31 @@ exports.testStringOverload = createProxy
         return document.querySelectorAll(this);
       };
       assert("input".update().length == 3, "String.prototype overload works");
       done();
     }
   );
 });
 
-exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {
+exports["test Element.matches()"] = createProxyTest("", function (helper) {
   helper.createWorker(
     'new ' + function ContentScriptScope() {
-      // Check mozMatchesSelector XrayWrappers bug:
-      // mozMatchesSelector returns bad results when we are not calling it from the node itself
-      // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
-      assert(document.createElement( "div" ).mozMatchesSelector("div"),
-             "mozMatchesSelector works while being called from the node");
-      assert(document.documentElement.mozMatchesSelector.call(
+      // Check matches XrayWrappers bug (Bug 658909):
+      // Test that Element.matches() does not return bad results when we are
+      // not calling it from the node itself.
+      assert(document.createElement( "div" ).matches("div"),
+             "matches works while being called from the node");
+      assert(document.documentElement.matches.call(
                document.createElement( "div" ),
                "div"
              ),
-             "mozMatchesSelector works while being called from a " +
+             "matches works while being called from a " +
              "function reference to " +
-             "document.documentElement.mozMatchesSelector.call");
+             "document.documentElement.matches.call");
       done();
     }
   );
 });
 
 exports["test Events Overload"] = createProxyTest("", function (helper) {
 
   helper.createWorker(
--- a/browser/base/content/aboutcerterror/aboutCertError.xhtml
+++ b/browser/base/content/aboutcerterror/aboutCertError.xhtml
@@ -90,17 +90,17 @@
         if (cssClass == "expertBadCert") {
           toggleVisibility('advancedPanel');
         }
 
         // Disallow overrides if this is a Strict-Transport-Security
         // host and the cert is bad (STS Spec section 7.3) or if the
         // certerror is in a frame (bug 633691).
         if (cssClass == "badStsCert" || window != top) {
-          document.getElementById("advancedPanel").setAttribute("hidden", "true");
+          document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
         }
         if (cssClass != "badStsCert") {
           document.getElementById("badStsCertExplanation").setAttribute("hidden", "true");
         }
 
         var tech = document.getElementById("technicalContentText");
         if (tech)
           tech.textContent = getDescription();
--- a/browser/base/content/test/general/browser_aboutCertError.js
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // This is testing the aboutCertError page (Bug 1207107).  It's a start,
-// but should be expanded to include cert_domain_link / badStsCert
+// but should be expanded to include cert_domain_link
 
 const GOOD_PAGE = "https://example.com/";
 const BAD_CERT = "https://expired.example.com/";
+const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
 
 add_task(function* checkReturnToAboutHome() {
   info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home");
   let browser;
   let certErrorLoaded;
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
     gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
     browser = gBrowser.selectedBrowser;
@@ -63,16 +64,36 @@ add_task(function* checkReturnToPrevious
 
   is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
   is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
   is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
 
   gBrowser.removeCurrentTab();
 });
 
+add_task(function* checkBadStsCert() {
+  info("Loading a badStsCert and making sure exception button doesn't show up");
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+  let browser = gBrowser.selectedBrowser;
+
+  info("Loading and waiting for the cert error");
+  let certErrorLoaded = waitForCertErrorLoad(browser);
+  BrowserTestUtils.loadURI(browser, BAD_STS_CERT);
+  yield certErrorLoaded;
+
+  let exceptionButtonHidden = yield ContentTask.spawn(browser, null, function* () {
+    let doc = content.document;
+    let exceptionButton = doc.getElementById("exceptionDialogButton");
+    return exceptionButton.hidden;
+  });
+  ok(exceptionButtonHidden, "Exception button is hidden");
+
+  gBrowser.removeCurrentTab();
+});
+
 function waitForCertErrorLoad(browser) {
   return new Promise(resolve => {
     info("Waiting for DOMContentLoaded event");
     browser.addEventListener("DOMContentLoaded", function load() {
       browser.removeEventListener("DOMContentLoaded", load, false, true);
       resolve();
     }, false, true);
   });
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -35,20 +35,18 @@ body,
 
 .loop-logo {
   background: url("../shared/img/helloicon.svg") no-repeat;
   width: 100px;
   height: 100px;
 }
 
 .mozilla-logo {
-  background: url("../img/mozilla-logo.svg#logo") no-repeat;
-  background-size: contain;
+  height: 26px;
   width: 100px;
-  height: 30px;
 }
 
 /* Standalone Overlay wrapper */
 .standalone-overlay-wrapper {
   position: absolute;
   left: 0;
   top: 0;
   bottom: 0;
@@ -90,19 +88,16 @@ html[dir="rtl"] .standalone-overlay-wrap
   width: 16px;
   height: 16px;
   background: transparent url("../shared/img/svg/glyph-help-16x16.svg") no-repeat;
 }
 
 .standalone-overlay-wrapper > .standalone-moz-logo {
   width: 50px;
   height: 13px;
-  background-size: contain;
-  background-image: url("../img/mozilla-logo.svg#logo-white");
-  background-repeat: no-repeat;
   position: absolute;
   bottom: 0;
   right: 0;
 }
 
 html[dir="rtl"] .standalone-overlay-wrapper > .standalone-moz-logo {
   left: 0;
   right: auto;
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -117,17 +117,17 @@ loop.standaloneRoomViews = (function(moz
               React.createElement("p", {className: "loop-logo"}), 
               
                 this.state.failureReason ?
                   this._renderFailureText() :
                   this._renderJoinButton()
               
             ), 
             React.createElement(ToSView, {dispatcher: this.props.dispatcher}), 
-            React.createElement("p", {className: "mozilla-logo"})
+            React.createElement("img", {className: "mozilla-logo", src: "img/mozilla-logo.svg#logo"})
           )
         )
       );
     }
   });
 
   /**
    * Handles display of failures, determining the correct messages and
@@ -649,17 +649,17 @@ loop.standaloneRoomViews = (function(moz
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     render: function() {
       return (
         React.createElement("div", {className: "standalone-overlay-wrapper"}, 
           React.createElement("div", {className: "hello-logo"}), 
           React.createElement(GeneralSupportURL, {dispatcher: this.props.dispatcher}), 
-          React.createElement("div", {className: "standalone-moz-logo"})
+          React.createElement("img", {className: "standalone-moz-logo", src: "img/mozilla-logo.svg#logo-white"})
         )
       );
     }
   });
 
   var GeneralSupportURL = React.createClass({displayName: "GeneralSupportURL",
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -117,17 +117,17 @@ loop.standaloneRoomViews = (function(moz
               <p className="loop-logo" />
               {
                 this.state.failureReason ?
                   this._renderFailureText() :
                   this._renderJoinButton()
               }
             </div>
             <ToSView dispatcher={this.props.dispatcher} />
-            <p className="mozilla-logo" />
+            <img className="mozilla-logo" src="img/mozilla-logo.svg#logo" />
           </div>
         </div>
       );
     }
   });
 
   /**
    * Handles display of failures, determining the correct messages and
@@ -649,17 +649,17 @@ loop.standaloneRoomViews = (function(moz
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     render: function() {
       return (
         <div className="standalone-overlay-wrapper">
           <div className="hello-logo"></div>
           <GeneralSupportURL dispatcher={this.props.dispatcher} />
-          <div className="standalone-moz-logo" />
+          <img className="standalone-moz-logo" src="img/mozilla-logo.svg#logo-white" />
         </div>
       );
     }
   });
 
   var GeneralSupportURL = React.createClass({
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -62,16 +62,17 @@ app.get("/content/config.js", getConfigF
 app.get("/content/c/config.js", getConfigFile);
 
 // Various mappings to let us end up with:
 // /test - for the test files
 // /ui - for the ui showcase
 // /content - for the standalone files.
 
 app.use("/ui", express.static(path.join(__dirname, "..", "ui")));
+app.use("/ui/img/", express.static(path.join(__dirname, "..", "standalone", "content", "img")));
 app.use("/ui/loop/", express.static(path.join(__dirname, "..", "content")));
 app.use("/ui/shared/", express.static(path.join(__dirname, "..", "content",
                                                 "shared")));
 
 // This exists exclusively for the unit tests. They are served the
 // whole loop/ directory structure and expect some files in the standalone directory.
 app.use("/standalone/content", express.static(path.join(__dirname, "content")));
 
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2929,18 +2929,18 @@ var SessionStoreInternal = {
 
     // It's important to set the window state to dirty so that
     // we collect their data for the first time when saving state.
     DirtyWindows.add(window);
 
     // In case we didn't collect/receive data for any tabs yet we'll have to
     // fill the array with at least empty tabData objects until |_tPos| or
     // we'll end up with |null| entries.
-    for (let tab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) {
-      let emptyState = {entries: [], lastAccessed: tab.lastAccessed};
+    for (let otherTab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) {
+      let emptyState = {entries: [], lastAccessed: otherTab.lastAccessed};
       this._windows[window.__SSi].tabs.push(emptyState);
     }
 
     // Update the tab state in case we shut down without being notified.
     this._windows[window.__SSi].tabs[tab._tPos] = tabData;
 
     // Prepare the tab so that it can be properly restored. We'll pin/unpin
     // and show/hide tabs as necessary. We'll also attach a copy of the tab's
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -2157,16 +2157,16 @@ if (AppConstants.MOZ_SERVICES_HEALTHREPO
 
     // our fields are dynamic
     fields: { },
 
     // We need a custom serializer because the default one doesn't accept unknown fields
     _serializeJSONDaily: function(data) {
       let result = {_v: this.version };
 
-      for (let [field, data] of data) {
-        result[field] = data;
+      for (let [field, value] of data) {
+        result[field] = value;
       }
 
       return result;
     }
   });
 }
--- a/browser/config/tooltool-manifests/linux64/asan.manifest
+++ b/browser/config/tooltool-manifests/linux64/asan.manifest
@@ -3,10 +3,18 @@
 "clang_version": "r200213"
 }, 
 {
 "size": 71282740, 
 "digest": "ee9edb1ef3afd9ab29e39565145545ad57e8d8d2538be4d822d7dbd64038f4529b0b287cecf48bf83def52a26ac2c6faa331686c3ad5e8b4ba4c22686ee0808f", 
 "algorithm": "sha512", 
 "filename": "clang.tar.bz2",
 "unpack": true
+},
+{
+"size": 12057960,
+"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
 }
 ]
--- a/browser/locales/en-US/chrome/browser/devtools/animationinspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/animationinspector.properties
@@ -26,16 +26,21 @@ player.transitionNameLabel=Transition
 # displayed before the animation duration.
 player.animationDurationLabel=Duration:
 
 # LOCALIZATION NOTE (player.animationDelayLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation delay.
 player.animationDelayLabel=Delay:
 
+# LOCALIZATION NOTE (player.animationRateLabel):
+# This string is displayed in each animation player widget. It is the label
+# displayed before the animation playback rate.
+player.animationRateLabel=Playback rate:
+
 # LOCALIZATION NOTE (player.animationIterationCountLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the number of times the animation is set to repeat.
 player.animationIterationCountLabel=Repeats:
 
 # LOCALIZATION NOTE (player.infiniteIterationCount):
 # In case the animation repeats infinitely, this string is displayed next to the
 # player.animationIterationCountLabel string, instead of a number.
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -183,18 +183,18 @@ case "$target" in
     AC_SUBST(ANDROID_NDK)
     AC_SUBST(ANDROID_TOOLCHAIN)
     AC_SUBST(ANDROID_PLATFORM)
 
     ;;
 esac
 
 ])
-    
-AC_DEFUN([MOZ_ANDROID_STLPORT],
+
+AC_DEFUN([MOZ_ANDROID_CPU_ARCH],
 [
 
 if test "$OS_TARGET" = "Android" -a -z "$gonkdir"; then
     case "${CPU_ARCH}-${MOZ_ARCH}" in
     arm-armv7*)
         ANDROID_CPU_ARCH=armeabi-v7a
         ;;
     arm-*)
@@ -204,17 +204,23 @@ if test "$OS_TARGET" = "Android" -a -z "
         ANDROID_CPU_ARCH=x86
         ;;
     mips-*) # When target_cpu is mipsel, CPU_ARCH is mips
         ANDROID_CPU_ARCH=mips
         ;;
     esac
 
     AC_SUBST(ANDROID_CPU_ARCH)
+fi
+])
 
+AC_DEFUN([MOZ_ANDROID_STLPORT],
+[
+
+if test "$OS_TARGET" = "Android" -a -z "$gonkdir"; then
     cpu_arch_dir="$ANDROID_CPU_ARCH"
     if test "$MOZ_THUMB2" = 1; then
         cpu_arch_dir="$cpu_arch_dir/thumb"
     fi
 
     if test -z "$STLPORT_CPPFLAGS$STLPORT_LIBS"; then
         case "$android_cxx_stl" in
         libstdc++)
--- a/build/sanitizers/lsan_suppressions.txt
+++ b/build/sanitizers/lsan_suppressions.txt
@@ -104,15 +104,16 @@ leak:nsBaseWidget::StoreWindowClipRegion
 ###
 
 leak:libcairo.so
 leak:libdl.so
 leak:libdricore.so
 leak:libGL.so
 leak:libglib-2.0.so
 leak:libp11-kit.so
+leak:libpixman-1.so
 leak:libpulse.so
 leak:libpulsecommon-1.1.so
 leak:libresolv.so
 leak:libstdc++.so
 leak:libXrandr.so
 leak:pthread_setspecific_internal
 leak:swrast_dri.so
--- a/build/unix/mozconfig.gtk
+++ b/build/unix/mozconfig.gtk
@@ -1,30 +1,29 @@
+# To do try builds with Gtk+2, uncomment the following line, and remove
+# everything after that.
+#ac_add_options --enable-default-toolkit=cairo-gtk2
+
 TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
 
-# $TOOLTOOL_DIR/gtk3 comes from tooltool, when the tooltool manifest contains it.
-if [ -d "$TOOLTOOL_DIR/gtk3" ]; then
-  if [ -z "$PKG_CONFIG_LIBDIR" ]; then
-    echo PKG_CONFIG_LIBDIR must be set >&2
-    exit 1
-  fi
-  export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3"
-  export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig"
-  PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config"
-  export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}"
-  # Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages.
-  LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}"
-  ac_add_options --enable-default-toolkit=cairo-gtk3
+# $TOOLTOOL_DIR/gtk3 comes from tooltool, and must be included in the tooltool manifest.
+if [ -z "$PKG_CONFIG_LIBDIR" ]; then
+  echo PKG_CONFIG_LIBDIR must be set >&2
+  exit 1
+fi
+export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3"
+export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig"
+PKG_CONFIG="$TOOLTOOL_DIR/gtk3/usr/local/bin/pkg-config"
+export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}"
+# Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages.
+LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}"
+ac_add_options --enable-default-toolkit=cairo-gtk3
 
-  # Set things up to use Gtk+3 from the tooltool package
-  mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts"
-  mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc"
-  mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib"
-  mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
-  mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders"
-  mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib"
+# Set things up to use Gtk+3 from the tooltool package
+mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts"
+mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc"
+mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib"
+mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
+mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders"
+mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib"
 
-  # Until a tooltool with bug 1188571 landed is available everywhere
-  $TOOLTOOL_DIR/gtk3/setup.sh
-else
-  PKG_CONFIG=pkg-config
-  ac_add_options --enable-default-toolkit=cairo-gtk2
-fi
+# Until a tooltool with bug 1188571 landed is available everywhere
+$TOOLTOOL_DIR/gtk3/setup.sh
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1336,34 +1336,30 @@ endif
 #
 #   We use $(CURDIR) in the rule's target to ensure that we don't find
 #   a dependency directory in the source tree via VPATH (perhaps from
 #   a previous build in the source tree) and thus neglect to create a
 #   dependency directory in the object directory, where we really need
 #   it.
 
 ifneq (,$(filter-out all chrome default export realchrome clean clobber clobber_all distclean realclean,$(MAKECMDGOALS)))
-MDDEPEND_FILES		:= $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES) $(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS)))))))
+MDDEPEND_FILES		:= $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(addsuffix .pp,$(notdir $(sort $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS)))))))
 
 ifneq (,$(MDDEPEND_FILES))
 $(call include_deps,$(MDDEPEND_FILES))
 endif
 
 endif
 
-
-ifneq (,$(filter export,$(MAKECMDGOALS)))
-MDDEPEND_FILES		:= $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_EXPORT_MDDEPEND_FILES))))
+MDDEPEND_FILES		:= $(strip $(wildcard $(addprefix $(MDDEPDIR)/,$(EXTRA_MDDEPEND_FILES))))
 
 ifneq (,$(MDDEPEND_FILES))
 $(call include_deps,$(MDDEPEND_FILES))
 endif
 
-endif
-
 #############################################################################
 
 -include $(topsrcdir)/$(MOZ_BUILD_APP)/app-rules.mk
 -include $(MY_RULES)
 
 #
 # Generate Emacs tags in a file named TAGS if ETAGS was set in $(MY_CONFIG)
 # or in $(MY_RULES)
--- a/configure.in
+++ b/configure.in
@@ -316,17 +316,21 @@ if test -n "$gonkdir" ; then
     AC_DEFINE_UNQUOTED(ANDROID_VERSION, $ANDROID_VERSION)
     AC_SUBST(ANDROID_VERSION)
     AC_DEFINE(HAVE_SYS_UIO_H)
     AC_DEFINE(HAVE_PTHREADS)
     MOZ_CHROME_FILE_FORMAT=omni
     direct_nspr_config=1
     android_cxx_stl=mozstlport
 else
-    MOZ_ANDROID_NDK
+    if test "$COMPILE_ENVIRONMENT"; then
+        MOZ_ANDROID_NDK
+    else
+        AC_DEFINE(ANDROID)
+    fi # COMPILE_ENVIRONMENT
 
     case "$target" in
     *-android*|*-linuxandroid*)
         if test -z "$ANDROID_PACKAGE_NAME" ; then
             ANDROID_PACKAGE_NAME='org.mozilla.$(MOZ_APP_NAME)'
         fi
         MOZ_CHROME_FILE_FORMAT=omni
         ZLIB_DIR=yes
@@ -1192,17 +1196,24 @@ dnl ====================================
 INTEL_ARCHITECTURE=
 case "$OS_TEST" in
     x86_64|i?86)
       INTEL_ARCHITECTURE=1
 esac
 
 dnl Configure platform-specific CPU architecture compiler options.
 dnl ==============================================================
-MOZ_ARCH_OPTS
+if test "$COMPILE_ENVIRONMENT"; then
+    MOZ_ARCH_OPTS
+else
+    if test "$OS_TARGET" = Android -a x"$MOZ_WIDGET_TOOLKIT" != x"gonk"; then
+        dnl Default Android builds to ARMv7.
+        MOZ_ARCH=armv7-a
+    fi
+fi # COMPILE_ENVIRONMENT
 
 dnl =================================================================
 dnl Set up and test static assertion macros used to avoid AC_TRY_RUN,
 dnl which is bad when cross compiling.
 dnl =================================================================
 if test "$COMPILE_ENVIRONMENT"; then
 configure_static_assert_macros='
 #define CONFIGURE_STATIC_ASSERT(condition) CONFIGURE_STATIC_ASSERT_IMPL(condition, __LINE__)
@@ -1241,17 +1252,20 @@ if test "$ac_cv_static_assertion_macros_
 fi
 fi # COMPILE_ENVIRONMENT
 
 dnl ========================================================
 dnl Android libstdc++, placed here so it can use MOZ_ARCH
 dnl computed above.
 dnl ========================================================
 
-MOZ_ANDROID_STLPORT
+MOZ_ANDROID_CPU_ARCH
+if test "$COMPILE_ENVIRONMENT"; then
+    MOZ_ANDROID_STLPORT
+fi # COMPILE_ENVIRONMENT
 
 dnl ========================================================
 dnl Suppress Clang Argument Warnings
 dnl ========================================================
 if test -n "${CLANG_CC}${CLANG_CL}"; then
     _WARNINGS_CFLAGS="-Qunused-arguments ${_WARNINGS_CFLAGS}"
     CPPFLAGS="-Qunused-arguments ${CPPFLAGS}"
 fi
@@ -2527,16 +2541,17 @@ case "$target" in
            MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,--version-script,$(BUILD_TOOLS)/gnu-ld-scripts/components-version-script'
         fi
         ;;
 esac
 
 if test -z "$COMPILE_ENVIRONMENT"; then
     SKIP_COMPILER_CHECKS=1
     SKIP_LIBRARY_CHECKS=1
+    PKG_SKIP_STRIP=1
 else
     MOZ_COMPILER_OPTS
 fi # COMPILE_ENVIRONMENT
 
 if test -z "$SKIP_COMPILER_CHECKS"; then
 dnl Checks for typedefs, structures, and compiler characteristics.
 dnl ========================================================
 AC_HEADER_STDC
--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -81,16 +81,18 @@ var getServerTraits = Task.async(functio
     { name: "hasToggleSeveral", actor: "animations",
       method: "toggleSeveral" },
     { name: "hasSetCurrentTime", actor: "animationplayer",
       method: "setCurrentTime" },
     { name: "hasMutationEvents", actor: "animations",
      method: "stopAnimationPlayerUpdates" },
     { name: "hasSetPlaybackRate", actor: "animationplayer",
       method: "setPlaybackRate" },
+    { name: "hasSetPlaybackRates", actor: "animations",
+      method: "setPlaybackRates" },
     { name: "hasTargetNode", actor: "domwalker",
       method: "getNodeFromActor" },
     { name: "hasSetCurrentTimes", actor: "animations",
       method: "setCurrentTimes" },
     { name: "hasGetFrames", actor: "animationplayer",
       method: "getFrames" }
   ];
 
@@ -279,16 +281,33 @@ var AnimationsController = {
         if (shouldPause) {
           yield animation.pause();
         }
         yield animation.setCurrentTime(time);
       }
     }
   }),
 
+  /**
+   * Set all known animations' playback rates to the provided rate.
+   * @param {Number} rate.
+   * @return {Promise} Resolves when the rate has been set.
+   */
+  setPlaybackRateAll: Task.async(function*(rate) {
+    if (this.traits.hasSetPlaybackRates) {
+      // If the backend can set all playback rates at the same time, use that.
+      yield this.animationsFront.setPlaybackRates(this.animationPlayers, rate);
+    } else if (this.traits.hasSetPlaybackRate) {
+      // Otherwise, fall back to setting each rate individually.
+      for (let animation of this.animationPlayers) {
+        yield animation.setPlaybackRate(rate);
+      }
+    }
+  }),
+
   // AnimationPlayerFront objects are managed by this controller. They are
   // retrieved when refreshAnimationPlayers is called, stored in the
   // animationPlayers array, and destroyed when refreshAnimationPlayers is
   // called again.
   animationPlayers: [],
 
   refreshAnimationPlayers: Task.async(function*(nodeFront) {
     yield this.destroyAnimationPlayers();
--- a/devtools/client/animationinspector/animation-inspector.xhtml
+++ b/devtools/client/animationinspector/animation-inspector.xhtml
@@ -17,16 +17,17 @@
   <body class="theme-sidebar devtools-monospace" role="application" empty="true">
     <div id="global-toolbar" class="theme-toolbar">
       <span class="label">&allAnimations;</span>
       <button id="toggle-all" standalone="true" class="devtools-button pause-button"></button>
     </div>
     <div id="timeline-toolbar" class="theme-toolbar">
       <button id="rewind-timeline" standalone="true" class="devtools-button"></button>
       <button id="pause-resume-timeline" standalone="true" class="devtools-button pause-button paused"></button>
+      <span id="timeline-rate"></span>
       <span id="timeline-current-time" class="label"></span>
     </div>
     <div id="players"></div>
     <div id="error-message">
       <p>&invalidElement;</p>
       <p>&selectElement;</p>
       <button id="element-picker" standalone="true" class="devtools-button"></button>
     </div>
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -1,19 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* globals AnimationsController, document, performance, promise,
-   gToolbox, gInspector, requestAnimationFrame, cancelAnimationFrame, L10N */
+/* globals AnimationsController, document, promise, gToolbox, gInspector */
 
 "use strict";
 
-const {AnimationsTimeline} = require("devtools/client/animationinspector/components");
+const {
+  AnimationsTimeline,
+  RateSelector
+} = require("devtools/client/animationinspector/components");
 const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
 
 var $ = (selector, target = document) => target.querySelector(selector);
 
 /**
  * The main animations panel UI.
  */
 var AnimationsPanel = {
@@ -34,41 +36,47 @@ var AnimationsPanel = {
 
     this.playersEl = $("#players");
     this.errorMessageEl = $("#error-message");
     this.pickerButtonEl = $("#element-picker");
     this.toggleAllButtonEl = $("#toggle-all");
     this.playTimelineButtonEl = $("#pause-resume-timeline");
     this.rewindTimelineButtonEl = $("#rewind-timeline");
     this.timelineCurrentTimeEl = $("#timeline-current-time");
+    this.rateSelectorEl = $("#timeline-rate");
 
     // If the server doesn't support toggling all animations at once, hide the
     // whole global toolbar.
     if (!AnimationsController.traits.hasToggleAll) {
       $("#global-toolbar").style.display = "none";
     }
 
     // Binding functions that need to be called in scope.
     for (let functionName of ["onPickerStarted", "onPickerStopped",
       "refreshAnimationsUI", "toggleAll", "onTabNavigated",
-      "onTimelineDataChanged", "playPauseTimeline", "rewindTimeline"]) {
+      "onTimelineDataChanged", "playPauseTimeline", "rewindTimeline",
+      "onRateChanged"]) {
       this[functionName] = this[functionName].bind(this);
     }
     let hUtils = gToolbox.highlighterUtils;
     this.togglePicker = hUtils.togglePicker.bind(hUtils);
 
     this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
     this.animationsTimelineComponent.init(this.playersEl);
 
+    if (AnimationsController.traits.hasSetPlaybackRate) {
+      this.rateSelectorComponent = new RateSelector();
+      this.rateSelectorComponent.init(this.rateSelectorEl);
+    }
+
     this.startListeners();
 
     yield this.refreshAnimationsUI();
 
     this.initialized.resolve();
-
     this.emit(this.PANEL_INITIALIZED);
   }),
 
   destroy: Task.async(function*() {
     if (!this.initialized) {
       return;
     }
 
@@ -78,20 +86,25 @@ var AnimationsPanel = {
     }
     this.destroyed = promise.defer();
 
     this.stopListeners();
 
     this.animationsTimelineComponent.destroy();
     this.animationsTimelineComponent = null;
 
+    if (this.rateSelectorComponent) {
+      this.rateSelectorComponent.destroy();
+      this.rateSelectorComponent = null;
+    }
+
     this.playersEl = this.errorMessageEl = null;
     this.toggleAllButtonEl = this.pickerButtonEl = null;
     this.playTimelineButtonEl = this.rewindTimelineButtonEl = null;
-    this.timelineCurrentTimeEl = null;
+    this.timelineCurrentTimeEl = this.rateSelectorEl = null;
 
     this.destroyed.resolve();
   }),
 
   startListeners: function() {
     AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimationsUI);
 
@@ -102,16 +115,20 @@ var AnimationsPanel = {
     this.toggleAllButtonEl.addEventListener("click", this.toggleAll);
     this.playTimelineButtonEl.addEventListener("click", this.playPauseTimeline);
     this.rewindTimelineButtonEl.addEventListener("click", this.rewindTimeline);
 
     gToolbox.target.on("navigate", this.onTabNavigated);
 
     this.animationsTimelineComponent.on("timeline-data-changed",
       this.onTimelineDataChanged);
+
+    if (this.rateSelectorComponent) {
+      this.rateSelectorComponent.on("rate-changed", this.onRateChanged);
+    }
   },
 
   stopListeners: function() {
     AnimationsController.off(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimationsUI);
 
     this.pickerButtonEl.removeEventListener("click", this.togglePicker);
     gToolbox.off("picker-started", this.onPickerStarted);
@@ -120,16 +137,20 @@ var AnimationsPanel = {
     this.toggleAllButtonEl.removeEventListener("click", this.toggleAll);
     this.playTimelineButtonEl.removeEventListener("click", this.playPauseTimeline);
     this.rewindTimelineButtonEl.removeEventListener("click", this.rewindTimeline);
 
     gToolbox.target.off("navigate", this.onTabNavigated);
 
     this.animationsTimelineComponent.off("timeline-data-changed",
       this.onTimelineDataChanged);
+
+    if (this.rateSelectorComponent) {
+      this.rateSelectorComponent.off("rate-changed", this.onRateChanged);
+    }
   },
 
   togglePlayers: function(isVisible) {
     if (isVisible) {
       document.body.removeAttribute("empty");
       document.body.setAttribute("timeline", "true");
     } else {
       document.body.setAttribute("empty", "true");
@@ -168,23 +189,33 @@ var AnimationsPanel = {
    * pause them.
    */
   rewindTimeline: function() {
     AnimationsController.setCurrentTimeAll(0, true)
                         .then(() => this.refreshAnimationsStateAndUI())
                         .catch(e => console.error(e));
   },
 
+  /**
+   * Set the playback rate of all current animations shown in the timeline to
+   * the value of this.rateSelectorEl.
+   */
+  onRateChanged: function(e, rate) {
+    AnimationsController.setPlaybackRateAll(rate)
+                        .then(() => this.refreshAnimationsStateAndUI())
+                        .catch(e => console.error(e));
+  },
+
   onTabNavigated: function() {
     this.toggleAllButtonEl.classList.remove("paused");
   },
 
   onTimelineDataChanged: function(e, data) {
     this.timelineData = data;
-    let {isMoving, isPaused, isUserDrag, time} = data;
+    let {isMoving, isUserDrag, time} = data;
 
     this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
 
     // If the timeline data changed as a result of the user dragging the
     // scrubber, then pause all animations and set their currentTimes.
     // (Note that we want server-side requests to be sequenced, so we only do
     // this after the previous currentTime setting was done).
     if (isUserDrag && !this.setCurrentTimeAllPromise) {
@@ -227,16 +258,21 @@ var AnimationsPanel = {
     // Empty the whole panel first.
     this.togglePlayers(true);
 
     // Re-render the timeline component.
     this.animationsTimelineComponent.render(
       AnimationsController.animationPlayers,
       AnimationsController.documentCurrentTime);
 
+    // Re-render the rate selector component.
+    if (this.rateSelectorComponent) {
+      this.rateSelectorComponent.render(AnimationsController.animationPlayers);
+    }
+
     // If there are no players to show, show the error message instead and
     // return.
     if (!AnimationsController.animationPlayers.length) {
       this.togglePlayers(false);
       this.emit(this.UI_UPDATED_EVENT);
       done();
       return;
     }
--- a/devtools/client/animationinspector/components.js
+++ b/devtools/client/animationinspector/components.js
@@ -29,16 +29,18 @@ const {
   TargetNodeHighlighter
 } = require("devtools/client/animationinspector/utils");
 
 const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
 // The minimum spacing between 2 time graduation headers in the timeline (px).
 const TIME_GRADUATION_MIN_SPACING = 40;
+// List of playback rate presets displayed in the timeline toolbar.
+const PLAYBACK_RATES = [.1, .25, .5, 1, 2, 5, 10];
 // The size of the fast-track icon (for compositor-running animations), this is
 // used to position the icon correctly.
 const FAST_TRACK_ICON_SIZE = 20;
 
 /**
  * UI component responsible for displaying a preview of the target dom node of
  * a given animation.
  * @param {InspectorPanel} inspector Requires a reference to the inspector-panel
@@ -182,61 +184,74 @@ AnimationTargetNode.prototype = {
       }
     });
 
     if (!this.options.compact) {
       this.classEl.appendChild(document.createTextNode("\""));
       this.previewEl.appendChild(document.createTextNode(">"));
     }
 
+    this.startListeners();
+  },
+
+  startListeners: function() {
     // Init events for highlighting and selecting the node.
     this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
     this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
     this.previewEl.addEventListener("click", this.onSelectNodeClick);
     this.highlightNodeEl.addEventListener("click", this.onHighlightNodeClick);
 
     // Start to listen for markupmutation events.
     this.inspector.on("markupmutation", this.onMarkupMutations);
 
     // Listen to the target node highlighter.
     TargetNodeHighlighter.on("highlighted", this.onTargetHighlighterLocked);
   },
 
-  destroy: function() {
-    TargetNodeHighlighter.unhighlight().catch(e => console.error(e));
-
+  stopListeners: function() {
     TargetNodeHighlighter.off("highlighted", this.onTargetHighlighterLocked);
     this.inspector.off("markupmutation", this.onMarkupMutations);
     this.previewEl.removeEventListener("mouseover", this.onPreviewMouseOver);
     this.previewEl.removeEventListener("mouseout", this.onPreviewMouseOut);
     this.previewEl.removeEventListener("click", this.onSelectNodeClick);
     this.highlightNodeEl.removeEventListener("click", this.onHighlightNodeClick);
+  },
+
+  destroy: function() {
+    TargetNodeHighlighter.unhighlight().catch(e => console.error(e));
+
+    this.stopListeners();
 
     this.el.remove();
     this.el = this.tagNameEl = this.idEl = this.classEl = null;
     this.highlightNodeEl = this.previewEl = null;
     this.nodeFront = this.inspector = this.playerFront = null;
   },
 
   get highlighterUtils() {
-    return this.inspector.toolbox.highlighterUtils;
+    if (this.inspector && this.inspector.toolbox) {
+      return this.inspector.toolbox.highlighterUtils;
+    }
+    return null;
   },
 
   onPreviewMouseOver: function() {
-    if (!this.nodeFront) {
+    if (!this.nodeFront || !this.highlighterUtils) {
       return;
     }
-    this.highlighterUtils.highlightNodeFront(this.nodeFront);
+    this.highlighterUtils.highlightNodeFront(this.nodeFront)
+                         .catch(e => console.error(e));
   },
 
   onPreviewMouseOut: function() {
-    if (!this.nodeFront) {
+    if (!this.nodeFront || !this.highlighterUtils) {
       return;
     }
-    this.highlighterUtils.unhighlight();
+    this.highlighterUtils.unhighlight()
+                         .catch(e => console.error(e));
   },
 
   onSelectNodeClick: function() {
     if (!this.nodeFront) {
       return;
     }
     this.inspector.selection.setNodeFront(this.nodeFront, "animationinspector");
   },
@@ -327,16 +342,100 @@ AnimationTargetNode.prototype = {
       this.classEl.style.display = "none";
     }
 
     this.emit("target-retrieved");
   })
 };
 
 /**
+ * UI component responsible for displaying a playback rate selector UI.
+ * The rendering logic is such that a predefined list of rates is generated.
+ * If *all* animations passed to render share the same rate, then that rate is
+ * selected in the <select> element, otherwise, the empty value is selected.
+ * If the rate that all animations share isn't part of the list of predefined
+ * rates, than that rate is added to the list.
+ */
+function RateSelector() {
+  this.onRateChanged = this.onRateChanged.bind(this);
+  EventEmitter.decorate(this);
+}
+
+exports.RateSelector = RateSelector;
+
+RateSelector.prototype = {
+  init: function(containerEl) {
+    this.selectEl = createNode({
+      parent: containerEl,
+      nodeType: "select",
+      attributes: {"class": "devtools-button"}
+    });
+
+    this.selectEl.addEventListener("change", this.onRateChanged);
+  },
+
+  destroy: function() {
+    this.selectEl.removeEventListener("change", this.onRateChanged);
+    this.selectEl.remove();
+    this.selectEl = null;
+  },
+
+  getAnimationsRates: function(animations) {
+    return sortedUnique(animations.map(a => a.state.playbackRate));
+  },
+
+  getAllRates: function(animations) {
+    let animationsRates = this.getAnimationsRates(animations);
+    if (animationsRates.length > 1) {
+      return PLAYBACK_RATES;
+    }
+
+    return sortedUnique(PLAYBACK_RATES.concat(animationsRates));
+  },
+
+  render: function(animations) {
+    let allRates = this.getAnimationsRates(animations);
+    let hasOneRate = allRates.length === 1;
+
+    this.selectEl.innerHTML = "";
+
+    if (!hasOneRate) {
+      // When the animations displayed have mixed playback rates, we can't
+      // select any of the predefined ones, instead, insert an empty rate.
+      createNode({
+        parent: this.selectEl,
+        nodeType: "option",
+        attributes: {value: "", selector: "true"},
+        textContent: "-"
+      });
+    }
+    for (let rate of this.getAllRates(animations)) {
+      let option = createNode({
+        parent: this.selectEl,
+        nodeType: "option",
+        attributes: {value: rate},
+        textContent: L10N.getFormatStr("player.playbackRateLabel", rate)
+      });
+
+      // If there's only one rate and this is the option for it, select it.
+      if (hasOneRate && rate === allRates[0]) {
+        option.setAttribute("selected", "true");
+      }
+    }
+  },
+
+  onRateChanged: function() {
+    let rate = parseFloat(this.selectEl.value);
+    if (!isNaN(rate)) {
+      this.emit("rate-changed", rate);
+    }
+  }
+};
+
+/**
  * The TimeScale helper object is used to know which size should something be
  * displayed with in the animation panel, depending on the animations that are
  * currently displayed.
  * If there are 5 animations displayed, and the first one starts at 10000ms and
  * the last one ends at 20000ms, then this helper can be used to convert any
  * time in this range to a distance in pixels.
  *
  * For the helper to know how to convert, it needs to know all the animations.
@@ -853,26 +952,54 @@ AnimationTimeBlock.prototype = {
         }
       });
     }
   },
 
   getTooltipText: function(state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
                             L10N.numberWithDecimals(time / 1000, 2));
-    // The type isn't always available, older servers don't send it.
-    let title =
+
+    let text = "";
+
+    // Adding the name (the type isn't always available, older servers don't
+    // send it).
+    text +=
       state.type
       ? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
       : state.name;
-    let delay = L10N.getStr("player.animationDelayLabel") + " " +
-                getTime(state.delay);
-    let duration = L10N.getStr("player.animationDurationLabel") + " " +
-                   getTime(state.duration);
-    let iterations = L10N.getStr("player.animationIterationCountLabel") + " " +
-                     (state.iterationCount ||
-                      L10N.getStr("player.infiniteIterationCountText"));
-    let compositor = state.isRunningOnCompositor
-                     ? L10N.getStr("player.runningOnCompositorTooltip")
-                     : "";
-    return [title, duration, iterations, delay, compositor].join("\n");
+    text += "\n";
+
+    // Adding the delay.
+    text += L10N.getStr("player.animationDelayLabel") + " ";
+    text += getTime(state.delay);
+    text += "\n";
+
+    // Adding the duration.
+    text += L10N.getStr("player.animationDurationLabel") + " ";
+    text += getTime(state.duration);
+    text += "\n";
+
+    // Adding the iteration count (the infinite symbol, or an integer).
+    // XXX: see bug 1219608 to remove this if the count is 1.
+    text += L10N.getStr("player.animationIterationCountLabel") + " ";
+    text += state.iterationCount ||
+            L10N.getStr("player.infiniteIterationCountText");
+    text += "\n";
+
+    // Adding the playback rate if it's different than 1.
+    if (state.playbackRate !== 1) {
+      text += L10N.getStr("player.animationRateLabel") + " ";
+      text += state.playbackRate;
+      text += "\n";
+    }
+
+    // Adding a note that the animation is running on the compositor thread if
+    // needed.
+    if (state.isRunningOnCompositor) {
+      text += L10N.getStr("player.runningOnCompositorTooltip");
+    }
+
+    return text;
   }
 };
+
+let sortedUnique = arr => [...new Set(arr)].sort((a, b) => a > b);
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -23,16 +23,17 @@ support-files =
 [browser_animation_running_on_compositor.js]
 [browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
 [browser_animation_shows_player_on_valid_node.js]
 [browser_animation_target_highlight_select.js]
 [browser_animation_target_highlighter_lock.js]
 [browser_animation_timeline_currentTime.js]
 [browser_animation_timeline_header.js]
 [browser_animation_timeline_pause_button.js]
+[browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
 [browser_animation_timeline_shows_delay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_rate_selector.js
@@ -0,0 +1,54 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the timeline toolbar contains a playback rate selector UI and that
+// it can be used to change the playback rate of animations in the timeline.
+// Also check that it displays the rate of the current animations in case they
+// all have the same rate, or that it displays the empty value in case they
+// have mixed rates.
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+
+  let {panel, controller, inspector, toolbox} = yield openAnimationInspector();
+
+  // In this test, we disable the highlighter on purpose because of the way
+  // events are simulated to select an option in the playbackRate <select>.
+  // Indeed, this may cause mousemove events to be triggered on the nodes that
+  // are underneath the <select>, and these are AnimationTargetNode instances.
+  // Simulating mouse events on them will cause the highlighter to emit requests
+  // and this might cause the test to fail if they happen after it has ended.
+  disableHighlighter(toolbox);
+
+  let select = panel.rateSelectorEl.firstChild;
+
+  ok(select, "The rate selector exists");
+
+  info("Change all of the current animations' rates to 0.5");
+  yield changeTimelinePlaybackRate(panel, .5);
+  checkAllAnimationsRatesChanged(controller, select, .5);
+
+  info("Select just one animated node and change its rate only");
+  yield selectNode(".animated", inspector);
+
+  yield changeTimelinePlaybackRate(panel, 2);
+  checkAllAnimationsRatesChanged(controller, select, 2);
+
+  info("Select the <body> again, it should now have mixed-rates animations");
+  yield selectNode("body", inspector);
+
+  is(select.value, "", "The selected rate is empty");
+
+  info("Change the rate for these mixed-rate animations");
+  yield changeTimelinePlaybackRate(panel, 1);
+  checkAllAnimationsRatesChanged(controller, select, 1);
+});
+
+function checkAllAnimationsRatesChanged({animationPlayers}, select, rate) {
+  ok(animationPlayers.every(({state}) => state.playbackRate === rate),
+     "All animations' rates have been set to " + rate);
+  is(select.value, rate, "The right value is displayed in the select");
+}
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -475,29 +475,88 @@ function* assertScrubberMoving(panel, is
     // for some time and make sure timeline-data-changed isn't emitted.
     let hasMoved = false;
     timeline.once("timeline-data-changed", () => hasMoved = true);
     yield new Promise(r => setTimeout(r, 500));
     ok(!hasMoved, "The scrubber is not moving");
   }
 }
 
+/**
+ * Click the play/pause button in the timeline toolbar and wait for animations
+ * to update.
+ * @param {AnimationsPanel} panel
+ */
 function* clickTimelinePlayPauseButton(panel) {
   let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
 
   let btn = panel.playTimelineButtonEl;
   let win = btn.ownerDocument.defaultView;
   EventUtils.sendMouseEvent({type: "click"}, btn, win);
 
   yield onUiUpdated;
   yield waitForAllAnimationTargets(panel);
 }
 
+/**
+ * Click the rewind button in the timeline toolbar and wait for animations to
+ * update.
+ * @param {AnimationsPanel} panel
+ */
 function* clickTimelineRewindButton(panel) {
   let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
 
   let btn = panel.rewindTimelineButtonEl;
   let win = btn.ownerDocument.defaultView;
   EventUtils.sendMouseEvent({type: "click"}, btn, win);
 
   yield onUiUpdated;
   yield waitForAllAnimationTargets(panel);
 }
+
+/**
+ * Select a rate inside the playback rate selector in the timeline toolbar and
+ * wait for animations to update.
+ * @param {AnimationsPanel} panel
+ * @param {Number} rate The new rate value to be selected
+ */
+function* changeTimelinePlaybackRate(panel, rate) {
+  let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
+
+  let select = panel.rateSelectorEl.firstChild;
+  let win = select.ownerDocument.defaultView;
+
+  // Get the right option.
+  let option = [...select.options].filter(o => o.value === rate + "")[0];
+  if (!option) {
+    ok(false,
+       "Could not find an option for rate " + rate + " in the rate selector. " +
+       "Values are: " + [...select.options].map(o => o.value));
+    return;
+  }
+
+  // Simulate the right events to select the option in the drop-down.
+  EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"}, win);
+  EventUtils.synthesizeMouseAtCenter(option, {type: "mouseup"}, win);
+
+  yield onUiUpdated;
+  yield waitForAllAnimationTargets(panel);
+
+  // Simulate a mousemove outside of the rate selector area to avoid subsequent
+  // tests from failing because of unwanted mouseover events.
+  EventUtils.synthesizeMouseAtCenter(win.document.querySelector("#timeline-toolbar"),
+                                     {type: "mousemove"}, win);
+}
+
+/**
+ * Prevent the toolbox common highlighter from making backend requests.
+ * @param {Toolbox} toolbox
+ */
+function disableHighlighter(toolbox) {
+  toolbox._highlighter = {
+    showBoxModel: () => new Promise(r => r()),
+    hideBoxModel: () => new Promise(r => r()),
+    pick: () => new Promise(r => r()),
+    cancelPick: () => new Promise(r => r()),
+    destroy: () => {},
+    traits: {}
+  };
+}
--- a/devtools/client/framework/gDevTools.jsm
+++ b/devtools/client/framework/gDevTools.jsm
@@ -18,17 +18,18 @@ loader.lazyRequireGetter(this, "Toolbox"
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
 
-const DefaultTools = require("devtools/client/definitions").defaultTools;
+const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
+  require("devtools/client/definitions");
 const EventEmitter = require("devtools/shared/event-emitter");
 const Telemetry = require("devtools/client/shared/telemetry");
 const {JsonView} = require("devtools/client/jsonview/main");
 
 const TABS_OPEN_PEAK_HISTOGRAM = "DEVTOOLS_TABS_OPEN_PEAK_LINEAR";
 const TABS_OPEN_AVG_HISTOGRAM = "DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR";
 const TABS_PINNED_PEAK_HISTOGRAM = "DEVTOOLS_TABS_PINNED_PEAK_LINEAR";
 const TABS_PINNED_AVG_HISTOGRAM = "DEVTOOLS_TABS_PINNED_AVERAGE_LINEAR";
@@ -281,20 +282,27 @@ DevTools.prototype = {
       theme = this._themes.get(theme);
     }
     else {
       themeId = theme.id;
     }
 
     let currTheme = Services.prefs.getCharPref("devtools.theme");
 
-    // Change the current theme if it's being dynamically removed together
-    // with the owner (bootstrapped) extension.
-    // But, do not change it if the application is just shutting down.
-    if (!Services.startup.shuttingDown && theme.id == currTheme) {
+    // Note that we can't check if `theme` is an item
+    // of `DefaultThemes` as we end up reloading definitions
+    // module and end up with different theme objects
+    let isCoreTheme = DefaultThemes.some(t => t.id === themeId);
+
+    // Reset the theme if an extension theme that's currently applied
+    // is being removed.
+    // Ignore shutdown since addons get disabled during that time.
+    if (!Services.startup.shuttingDown &&
+        !isCoreTheme &&
+        theme.id == currTheme) {
       Services.prefs.setCharPref("devtools.theme", "light");
 
       let data = {
         pref: "devtools.theme",
         newValue: "light",
         oldValue: currTheme
       };
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -145,18 +145,19 @@ devtools.jar:
     content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
     content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
     content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/aboutdebugging.js (aboutdebugging/aboutdebugging.js)
 %   skin devtools classic/1.0 %skin/
 *   skin/themes/common.css (themes/common.css)
-*   skin/themes/dark-theme.css (themes/dark-theme.css)
-*   skin/themes/light-theme.css (themes/light-theme.css)
+    skin/themes/dark-theme.css (themes/dark-theme.css)
+    skin/themes/light-theme.css (themes/light-theme.css)
+    skin/themes/toolbars.css (themes/toolbars.css)
     skin/themes/variables.css (themes/variables.css)
     skin/themes/images/add.svg (themes/images/add.svg)
     skin/themes/images/filters.svg (themes/images/filters.svg)
     skin/themes/images/filter-swatch.svg (themes/images/filter-swatch.svg)
     skin/themes/images/pseudo-class.svg (themes/images/pseudo-class.svg)
     skin/themes/images/controls.png (themes/images/controls.png)
     skin/themes/images/controls@2x.png (themes/images/controls@2x.png)
     skin/themes/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -80,16 +80,17 @@ skip-if = os == 'linux' # Bug 1172120
 [browser_perf-overview-render-03.js]
 [browser_perf-overview-render-04.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-overview-selection-01.js]
 [browser_perf-overview-selection-02.js]
 [browser_perf-overview-selection-03.js]
 [browser_perf-overview-time-interval.js]
 [browser_perf-private-browsing.js]
+skip-if = os == 'linux' # bug 1210140
 [browser_perf-states.js]
 skip-if = debug # bug 1203888
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-01.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-02.js]
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -129,8 +129,9 @@ skip-if = e10s # Bug 1086492 - Disable t
 [browser_toolbar_basic.js]
 [browser_toolbar_tooltip.js]
 [browser_toolbar_webconsole_errors_count.js]
 skip-if = buildapp == 'mulet' || e10s # The developertoolbar error count isn't correct with e10s
 [browser_treeWidget_basic.js]
 [browser_treeWidget_keyboard_interaction.js]
 [browser_treeWidget_mouse_interaction.js]
 [browser_devices.js]
+[browser_theme_switching.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_theme_switching.js
@@ -0,0 +1,32 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var toolbox;
+
+add_task(function*() {
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = yield gDevTools.showToolbox(target);
+  let root = toolbox.frame.contentDocument.documentElement;
+
+  let platform = root.getAttribute("platform");
+  let expectedPlatform = getPlatform();
+  is(platform, expectedPlatform, ":root[platform] is correct");
+
+  let theme = Services.prefs.getCharPref("devtools.theme");
+  let className = "theme-" + theme;
+  ok(root.classList.contains(className), ":root has " + className + " class (current theme)");
+
+  yield toolbox.destroy();
+});
+
+function getPlatform() {
+  let {OS} = Services.appinfo;
+  if (OS == "WINNT") {
+    return "win";
+  } else if (OS == "Darwin") {
+    return "mac";
+  } else {
+    return "linux";
+  }
+}
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -102,16 +102,27 @@
 
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
   Cu.import("resource://gre/modules/Services.jsm");
   Cu.import("resource://devtools/client/framework/gDevTools.jsm");
   const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
   const StylesheetUtils = require("sdk/stylesheet/utils");
 
+  let os;
+  let platform = navigator.platform;
+  if (platform.startsWith("Win")) {
+    os = "win";
+  } else if (platform.startsWith("Mac")) {
+    os = "mac";
+  } else {
+    os = "linux";
+  }
+  documentElement.setAttribute("platform", os);
+
   if (documentElement.hasAttribute("force-theme")) {
     switchTheme(documentElement.getAttribute("force-theme"));
   } else {
     switchTheme(Services.prefs.getCharPref("devtools.theme"));
 
     gDevTools.on("pref-changed", handlePrefChange);
     window.addEventListener("unload", function() {
       gDevTools.off("pref-changed", handlePrefChange);
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -156,16 +156,23 @@ body {
     background-image: url("images/debugger-play@2x.png");
   }
 
   #rewind-timeline::before {
     background-image: url("images/rewind@2x.png");
   }
 }
 
+#timeline-rate select {
+  -moz-appearance: none;
+  text-align: center;
+  color: inherit;
+  font-family: inherit;
+}
+
 /* Animation timeline component */
 
 .animation-timeline {
   height: 100%;
   overflow: hidden;
   position: relative;
   /* The timeline gets its background-image from a canvas element created in
      /devtools/client/animationinspector/utils.js drawGraphElementBackground
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -1,14 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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 url(variables.css);
+@import url(toolbars.css);
 
 body {
   margin: 0;
 }
 
 .theme-body {
   background: var(--theme-body-background);
   color: var(--theme-body-color);
@@ -369,11 +370,8 @@ div.CodeMirror span.eval-text {
 }
 
 .CodeMirror-hints,
 .CodeMirror-Tern-tooltip {
   box-shadow: 0 0 4px rgba(255, 255, 255, .3);
   background-color: #0f171f;
   color: var(--theme-body-color);
 }
-
-
-%include toolbars.inc.css
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -1,14 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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 url(variables.css);
+@import url(toolbars.css);
 
 body {
   margin: 0;
 }
 
 .theme-body {
   background: var(--theme-body-background);
   color: var(--theme-body-color);
@@ -367,11 +368,8 @@ div.CodeMirror span.eval-text {
   border-bottom: 0;
 }
 
 .CodeMirror-hints,
 .CodeMirror-Tern-tooltip {
   box-shadow: 0 0 4px rgba(128, 128, 128, .5);
   background-color: var(--theme-sidebar-background);
 }
-
-
-%include toolbars.inc.css
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -82,17 +82,17 @@ html, body, #app, #memory-tool {
     background-image: url(images/command-screenshot@2x.png);
   }
 }
 
 /**
  * TODO bug 1213100
  * Once we figure out how to store invertable buttons (pseudo element like in
  * this case?) we should add a .invertable class to handle this generally,
- * rather than the definitions in toolbars.inc.css.
+ * rather than the definitions in toolbars.css.
  *
  * @see bug 1173397 for another inverted related bug
  */
 .theme-light .devtools-toolbar > .devtools-toolbarbutton.take-snapshot::before {
   filter: url(images/filters.svg#invert);
 }
 
 /**
rename from devtools/client/themes/toolbars.inc.css
rename to devtools/client/themes/toolbars.css
--- a/devtools/client/themes/toolbars.inc.css
+++ b/devtools/client/themes/toolbars.css
@@ -1,13 +1,12 @@
-%if 0
+/* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
-%endif
 
 /* CSS Variables specific to the devtools toolbar that aren't defined by the themes */
 .theme-light {
   --searchbox-background-color: #ffee99;
   --searchbox-border-color: #ffbf00;
   --searcbox-no-match-background-color: #ffe5e5;
   --searcbox-no-match-border-color: #e52e2e;
 }
@@ -327,25 +326,26 @@
 
 /* Text input */
 
 .devtools-textinput,
 .devtools-searchinput {
   -moz-appearance: none;
   margin: 1px 3px;
   border: 1px solid;
-%ifdef XP_MACOSX
-  border-radius: 20px;
-%else
   border-radius: 2px;
-%endif
   padding: 4px 6px;
   border-color: var(--theme-splitter-color);
 }
 
+:root[platform="mac"] .devtools-textinput,
+:root[platform="mac"] .devtools-searchinput {
+  border-radius: 20px;
+}
+
 .devtools-searchinput {
   padding: 0;
   -moz-padding-start: 22px;
   -moz-padding-end: 4px;
   background-position: 8px center;
   background-size: 11px 11px;
   background-repeat: no-repeat;
   font-size: inherit;
@@ -396,24 +396,24 @@
   -moz-padding-start: 0;
 }
 .devtools-toolbar > .devtools-searchbox:last-child {
   -moz-padding-end: 0;
 }
 
 .devtools-rule-searchbox {
   -moz-box-flex: 1;
-  padding-right: 23px;
   width: 100%;
   font: inherit;
 }
 
 .devtools-rule-searchbox[filled] {
   background-color: var(--searchbox-background-color);
   border-color: var(--searchbox-border-color);
+  padding-right: 23px;
 }
 
 .devtools-style-searchbox-no-match {
   background-color: var(--searcbox-no-match-background-color) !important;
   border-color: var(--searcbox-no-match-border-color) !important;
 }
 
 .devtools-no-search-result {
--- a/devtools/client/tilt/TiltWorkerCrafter.js
+++ b/devtools/client/tilt/TiltWorkerCrafter.js
@@ -260,17 +260,18 @@ self.random = {
   /**
    * From http://baagoe.com/en/RandomMusings/javascript
    * Johannes Baagoe <baagoe@baagoe.com>, 2010
    */
   mash: function RNG_mash(data)
   {
     let h, n = 0xefc8249d;
 
-    for (let i = 0, data = data.toString(), len = data.length; i < len; i++) {
+    data = data.toString();
+    for (let i = 0, len = data.length; i < len; i++) {
       n += data.charCodeAt(i);
       h = 0.02519603282416938 * n;
       n = h >>> 0;
       h -= n;
       h *= n;
       n = h >>> 0;
       h -= n;
       n += h * 0x100000000; // 2^32
--- a/devtools/client/webide/modules/simulator-process.js
+++ b/devtools/client/webide/modules/simulator-process.js
@@ -223,18 +223,18 @@ Object.defineProperty(ASPp, "b2gBinary",
       binaries[OS].split("/").forEach(node => file.append(node));
     }
     // If the binary doesn't exists, it may be because of a simulator
     // based on mulet, which has a different binary name.
     if (!file.exists()) {
       file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
       file.append("firefox");
       let binaries = {
-        win32: "firefox-bin.exe",
-        mac64: "B2G.app/Contents/MacOS/firefox-bin",
+        win32: "firefox.exe",
+        mac64: "FirefoxNightly.app/Contents/MacOS/firefox-bin",
         linux32: "firefox-bin",
         linux64: "firefox-bin",
       };
       binaries[OS].split("/").forEach(node => file.append(node));
     }
     return file;
   }
 });
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -405,17 +405,17 @@ var AnimationPlayerActor = ActorClass({
     request: {},
     response: {}
   }),
 
   /**
    * Set the current time of the animation player.
    */
   setCurrentTime: method(function(currentTime) {
-    this.player.currentTime = currentTime;
+    this.player.currentTime = currentTime * this.player.playbackRate;
   }, {
     request: {
       currentTime: Arg(0, "number")
     },
     response: {}
   }),
 
   /**
@@ -844,16 +844,33 @@ var AnimationsActor = exports.Animations
     }));
   }, {
     request: {
       players: Arg(0, "array:animationplayer"),
       time: Arg(1, "number"),
       shouldPause: Arg(2, "boolean")
     },
     response: {}
+  }),
+
+  /**
+   * Set the playback rate of several animations at the same time.
+   * @param {Array} players A list of AnimationPlayerActor.
+   * @param {Number} rate The new rate.
+   */
+  setPlaybackRates: method(function(players, rate) {
+    for (let player of players) {
+      player.setPlaybackRate(rate);
+    }
+  }, {
+    request: {
+      players: Arg(0, "array:animationplayer"),
+      rate: Arg(1, "number")
+    },
+    response: {}
   })
 });
 
 var AnimationsFront = exports.AnimationsFront = FrontClass(AnimationsActor, {
   initialize: function(client, {animationsActor}) {
     Front.prototype.initialize.call(this, client, {actor: animationsActor});
     this.manage(this);
   },
--- a/devtools/server/actors/webapps.js
+++ b/devtools/server/actors/webapps.js
@@ -225,16 +225,22 @@ function WebappsActor(aConnection) {
   this._uploads = [];
   this._actorPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._actorPool);
 }
 
 WebappsActor.prototype = {
   actorPrefix: "webapps",
 
+  // For now, launch and close requests are only supported on B2G products
+  // like devices, mulet/simulators, graphene and b2gdroid.
+  // We set that attribute on the prototype in order to allow test
+  // to enable this feature.
+  supportsLaunch: require("devtools/shared/system").constants.MOZ_B2G,
+
   disconnect: function () {
     try {
       this.unwatchApps();
     } catch(e) {}
 
     // When we stop using this actor, we should ensure removing all files.
     for (let upload of this._uploads) {
       upload.remove();
@@ -868,19 +874,17 @@ WebappsActor.prototype = {
     let manifestURL = aRequest.manifestURL;
     if (!manifestURL) {
       return { error: "missingParameter",
                message: "missing parameter manifestURL" };
     }
 
     let deferred = promise.defer();
 
-    if (Services.appinfo.ID &&
-        Services.appinfo.ID != "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}" &&
-        Services.appinfo.ID != "{d1bfe7d9-c01e-4237-998b-7b5f960a4314}") {
+    if (!this.supportsLaunch) {
       return { error: "notSupported",
                message: "Not B2G. Can't launch app." };
     }
 
     DOMApplicationRegistry.launch(
       aRequest.manifestURL,
       aRequest.startPoint || "",
       Date.now(),
--- a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
+++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
@@ -1,15 +1,16 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that a player's playbackRate can be changed.
+// Check that a player's playbackRate can be changed, and that multiple players
+// can have their rates changed at the same time.
 
 add_task(function*() {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   info("Retrieve an animated node");
   let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
 
@@ -27,11 +28,24 @@ add_task(function*() {
 
   info("Change the rate back to 1");
   yield player.setPlaybackRate(1);
 
   info("Query the state again");
   state = yield player.getCurrentState();
   is(state.playbackRate, 1, "The playbackRate was changed back");
 
+  info("Retrieve several animation players and set their rates");
+  node = yield walker.querySelector(walker.rootNode, "body");
+  let players = yield animations.getAnimationPlayersForNode(node);
+
+  info("Change all animations in <body> to .5 rate");
+  yield animations.setPlaybackRates(players, .5);
+
+  info("Query their states and check they are correct");
+  for (let player of players) {
+    let state = yield player.getCurrentState();
+    is(state.playbackRate, .5, "The playbackRate was updated");
+  }
+
   yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
--- a/devtools/shared/apps/tests/unit/head_apps.js
+++ b/devtools/shared/apps/tests/unit/head_apps.js
@@ -85,16 +85,20 @@ function setup() {
   Components.utils.import('resource://gre/modules/Webapps.jsm');
   DOMApplicationRegistry.allAppsLaunchable = true;
 
   // Mock WebappOSUtils
   Cu.import("resource://gre/modules/WebappOSUtils.jsm");
   WebappOSUtils.getPackagePath = function(aApp) {
     return aApp.basePath + "/" + aApp.id;
   }
+
+  // Enable launch/close method of the webapps actor
+  let {WebappsActor} = require("devtools/server/actors/webapps");
+  WebappsActor.prototype.supportsLaunch = true;
 }
 
 function do_get_webappsdir() {
   var webappsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   webappsDir.append("test_webapps");
   if (!webappsDir.exists())
     webappsDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
 
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -121,18 +121,18 @@ exports.getReportEdges = getReportEdges;
 
 function recursiveWalk(breakdown, edge, report, visitor) {
   if (breakdown.by === "count") {
     visitor.enter(breakdown, report, edge);
     visitor.count(breakdown, report, edge);
     visitor.exit(breakdown, report, edge);
   } else {
     visitor.enter(breakdown, report, edge);
-    for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) {
-      recursiveWalk(breakdown, edge, referent, visitor);
+    for (let { edge, referent, breakdown: subBreakdown } of getReportEdges(breakdown, report)) {
+      recursiveWalk(subBreakdown, edge, referent, visitor);
     }
     visitor.exit(breakdown, report, edge);
   }
 };
 
 /**
  * Walk the given `report` that was generated by taking a census with the
  * specified `breakdown`.
--- a/dom/base/ConsoleReportCollector.cpp
+++ b/dom/base/ConsoleReportCollector.cpp
@@ -74,13 +74,34 @@ ConsoleReportCollector::FlushConsoleRepo
                                     aDocument, report.mPropertiesFile,
                                     report.mMessageName.get(),
                                     params.get(),
                                     paramsLength, uri, EmptyString(),
                                     report.mLineNumber, report.mColumnNumber);
   }
 }
 
+void
+ConsoleReportCollector::FlushConsoleReports(nsIConsoleReportCollector* aCollector)
+{
+  MOZ_ASSERT(aCollector);
+
+  nsTArray<PendingReport> reports;
+
+  {
+    MutexAutoLock lock(mMutex);
+    mPendingReports.SwapElements(reports);
+  }
+
+  for (uint32_t i = 0; i < reports.Length(); ++i) {
+    PendingReport& report = reports[i];
+    aCollector->AddConsoleReport(report.mErrorFlags, report.mCategory,
+                                 report.mPropertiesFile, report.mSourceFileURI,
+                                 report.mLineNumber, report.mColumnNumber,
+                                 report.mMessageName, report.mStringParams);
+  }
+}
+
 ConsoleReportCollector::~ConsoleReportCollector()
 {
 }
 
 } // namespace mozilla
--- a/dom/base/ConsoleReportCollector.h
+++ b/dom/base/ConsoleReportCollector.h
@@ -24,16 +24,19 @@ public:
                    const nsACString& aSourceFileURI,
                    uint32_t aLineNumber, uint32_t aColumnNumber,
                    const nsACString& aMessageName,
                    const nsTArray<nsString>& aStringParams) override;
 
   void
   FlushConsoleReports(nsIDocument* aDocument) override;
 
+  void
+  FlushConsoleReports(nsIConsoleReportCollector* aCollector) override;
+
 private:
   ~ConsoleReportCollector();
 
   struct PendingReport
   {
     PendingReport(uint32_t aErrorFlags, const nsACString& aCategory,
                nsContentUtils::PropertiesFile aPropertiesFile,
                const nsACString& aSourceFileURI, uint32_t aLineNumber,
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -27,16 +27,17 @@
 #include "nsIMutableArray.h"
 #include "nsContentPermissionHelper.h"
 #include "nsJSUtils.h"
 #include "nsISupportsPrimitives.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsWeakPtr.h"
+#include "ScriptSettings.h"
 
 using mozilla::Unused;          // <snicker>
 using namespace mozilla::dom;
 using namespace mozilla;
 
 #define kVisibilityChange "visibilitychange"
 
 NS_IMPL_ISUPPORTS(VisibilityChangeListener, nsIDOMEventListener)
@@ -645,28 +646,33 @@ nsContentPermissionRequestProxy::Allow(J
   nsTArray<PermissionChoice> choices;
   if (aChoices.isNullOrUndefined()) {
     // No choice is specified.
   } else if (aChoices.isObject()) {
     // Iterate through all permission types.
     for (uint32_t i = 0; i < mPermissionRequests.Length(); ++i) {
       nsCString type = mPermissionRequests[i].type();
 
-      mozilla::AutoSafeJSContext cx;
+      AutoJSAPI jsapi;
+      jsapi.Init();
+
+      JSContext* cx = jsapi.cx();
       JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
       JSAutoCompartment ac(cx, obj);
 
       JS::Rooted<JS::Value> val(cx);
 
       if (!JS_GetProperty(cx, obj, type.BeginReading(), &val) ||
           !val.isString()) {
-        // no setting for the permission type, skip it
+        // no setting for the permission type, clear exception and skip it
+        jsapi.ClearException();
       } else {
         nsAutoJSString choice;
         if (!choice.init(cx, val)) {
+          jsapi.ClearException();
           return NS_ERROR_FAILURE;
         }
         choices.AppendElement(PermissionChoice(type, choice));
       }
     }
   } else {
     MOZ_ASSERT(false, "SelectedChoices should be undefined or an JS object");
     return NS_ERROR_FAILURE;
--- a/dom/base/nsIConsoleReportCollector.h
+++ b/dom/base/nsIConsoleReportCollector.h
@@ -61,21 +61,29 @@ public:
                    Params... aParams)
   {
     nsTArray<nsString> params;
     mozilla::dom::StringArrayAppender::Append(params, sizeof...(Params), aParams...);
     AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile, aSourceFileURI,
                      aLineNumber, aColumnNumber, aMessageName, params);
   }
 
-  // Flush all pending reports to the console.
+  // Flush all pending reports to the console.  Main thread only.
   //
   // aDocument      An optional document representing where to flush the
   //                reports.  If provided, then the corresponding window's
   //                web console will get the reports.  Otherwise the reports
   //                go to the browser console.
   virtual void
   FlushConsoleReports(nsIDocument* aDocument) = 0;
+
+  // Flush all pending reports to another collector.  May be called from any
+  // thread.
+  //
+  // aCollector     A required collector object that will effectively take
+  //                ownership of our currently console reports.
+  virtual void
+  FlushConsoleReports(nsIConsoleReportCollector* aCollector) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIConsoleReportCollector, NS_NSICONSOLEREPORTCOLLECTOR_IID)
 
 #endif // nsIConsoleReportCollector_h
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -324,11 +324,17 @@ JSObject* GetDefaultScopeFromJSContext(J
   // the cx, so in those cases we need to fetch it via the scx
   // instead.
   nsIScriptContext *scx = GetScriptContextFromJSContext(cx);
   return  scx ? scx->GetWindowProxy() : nullptr;
 }
 
 bool nsAutoJSString::init(const JS::Value &v)
 {
-  return init(nsContentUtils::RootingCxForThread(), v);
+  JSContext* cx = nsContentUtils::RootingCxForThread();
+  if (!init(nsContentUtils::RootingCxForThread(), v)) {
+    JS_ClearPendingException(cx);
+    return false;
+  }
+
+  return true;
 }
 
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -97,16 +97,18 @@ skip-if = (os == "android") || (toolkit 
 [test_browserElement_oop_TargetBlank.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_TargetTop.html]
 [test_browserElement_oop_Titlechange.html]
 [test_browserElement_oop_TopBarrier.html]
 [test_browserElement_oop_VisibilityChange.html]
 [test_browserElement_oop_XFrameOptions.html]
 [test_browserElement_oop_XFrameOptionsAllowFrom.html]
+# bug 1189592
+skip-if = asan
 [test_browserElement_oop_XFrameOptionsDeny.html]
 [test_browserElement_oop_XFrameOptionsSameOrigin.html]
 # Disabled until bug 930449 makes it stop timing out
 [test_browserElement_oop_ContextmenuEvents.html]
 disabled = bug 930449
 # Disabled until bug 924771 makes them stop timing out
 [test_browserElement_oop_CloseFromOpener.html]
 disabled = bug 924771
--- a/dom/canvas/CanvasImageCache.cpp
+++ b/dom/canvas/CanvasImageCache.cpp
@@ -16,45 +16,54 @@
 #include "gfx2DGlue.h"
 
 namespace mozilla {
 
 using namespace dom;
 using namespace gfx;
 
 struct ImageCacheKey {
-  ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas)
-    : mImage(aImage), mCanvas(aCanvas) {}
+  ImageCacheKey(Element* aImage,
+                HTMLCanvasElement* aCanvas,
+                bool aIsAccelerated)
+    : mImage(aImage)
+    , mCanvas(aCanvas)
+    , mIsAccelerated(aIsAccelerated)
+  {}
   Element* mImage;
   HTMLCanvasElement* mCanvas;
+  bool mIsAccelerated;
 };
 
 struct ImageCacheEntryData {
   ImageCacheEntryData(const ImageCacheEntryData& aOther)
     : mImage(aOther.mImage)
     , mILC(aOther.mILC)
     , mCanvas(aOther.mCanvas)
+    , mIsAccelerated(aOther.mIsAccelerated)
     , mRequest(aOther.mRequest)
     , mSourceSurface(aOther.mSourceSurface)
     , mSize(aOther.mSize)
   {}
   explicit ImageCacheEntryData(const ImageCacheKey& aKey)
     : mImage(aKey.mImage)
     , mILC(nullptr)
     , mCanvas(aKey.mCanvas)
+    , mIsAccelerated(aKey.mIsAccelerated)
   {}
 
   nsExpirationState* GetExpirationState() { return &mState; }
 
   size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
 
   // Key
   RefPtr<Element> mImage;
   nsIImageLoadingContent* mILC;
   RefPtr<HTMLCanvasElement> mCanvas;
+  bool mIsAccelerated;
   // Value
   nsCOMPtr<imgIRequest> mRequest;
   RefPtr<SourceSurface> mSourceSurface;
   IntSize mSize;
   nsExpirationState mState;
 };
 
 class ImageCacheEntry : public PLDHashEntryHdr {
@@ -65,56 +74,71 @@ public:
   explicit ImageCacheEntry(const KeyType* aKey) :
       mData(new ImageCacheEntryData(*aKey)) {}
   ImageCacheEntry(const ImageCacheEntry &toCopy) :
       mData(new ImageCacheEntryData(*toCopy.mData)) {}
   ~ImageCacheEntry() {}
 
   bool KeyEquals(KeyTypePointer key) const
   {
-    return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas;
+    return mData->mImage == key->mImage &&
+           mData->mCanvas == key->mCanvas &&
+           mData->mIsAccelerated == key->mIsAccelerated;
   }
 
   static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
   static PLDHashNumber HashKey(KeyTypePointer key)
   {
-    return HashGeneric(key->mImage, key->mCanvas);
+    return HashGeneric(key->mImage, key->mCanvas, key->mIsAccelerated);
   }
   enum { ALLOW_MEMMOVE = true };
 
   nsAutoPtr<ImageCacheEntryData> mData;
 };
 
+struct SimpleImageCacheKey {
+  SimpleImageCacheKey(const imgIRequest* aImage,
+                      bool aIsAccelerated)
+    : mImage(aImage)
+    , mIsAccelerated(aIsAccelerated)
+  {}
+  const imgIRequest* mImage;
+  bool mIsAccelerated;
+};
+
 class SimpleImageCacheEntry : public PLDHashEntryHdr {
 public:
-  typedef imgIRequest& KeyType;
-  typedef const imgIRequest* KeyTypePointer;
+  typedef SimpleImageCacheKey KeyType;
+  typedef const SimpleImageCacheKey* KeyTypePointer;
 
   explicit SimpleImageCacheEntry(KeyTypePointer aKey)
-    : mRequest(const_cast<imgIRequest*>(aKey))
+    : mRequest(const_cast<imgIRequest*>(aKey->mImage))
+    , mIsAccelerated(aKey->mIsAccelerated)
   {}
   SimpleImageCacheEntry(const SimpleImageCacheEntry &toCopy)
     : mRequest(toCopy.mRequest)
+    , mIsAccelerated(toCopy.mIsAccelerated)
     , mSourceSurface(toCopy.mSourceSurface)
   {}
   ~SimpleImageCacheEntry() {}
 
   bool KeyEquals(KeyTypePointer key) const
   {
-    return key == mRequest;
+    return key->mImage == mRequest && key->mIsAccelerated == mIsAccelerated;
   }
 
-  static KeyTypePointer KeyToPointer(KeyType key) { return &key; }
+  static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
   static PLDHashNumber HashKey(KeyTypePointer key)
   {
-    return NS_PTR_TO_UINT32(key) >> 2;
+    return HashGeneric(key->mImage, key->mIsAccelerated);
   }
   enum { ALLOW_MEMMOVE = true };
 
   nsCOMPtr<imgIRequest> mRequest;
+  bool mIsAccelerated;
   RefPtr<SourceSurface> mSourceSurface;
 };
 
 static bool sPrefsInitialized = false;
 static int32_t sCanvasImageCacheLimit = 0;
 
 class ImageCacheObserver;
 
@@ -125,18 +149,18 @@ public:
   ImageCache();
   ~ImageCache();
 
   virtual void NotifyExpired(ImageCacheEntryData* aObject)
   {
     mTotal -= aObject->SizeInBytes();
     RemoveObject(aObject);
     // Deleting the entry will delete aObject since the entry owns aObject
-    mSimpleCache.RemoveEntry(*aObject->mRequest);
-    mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas));
+    mSimpleCache.RemoveEntry(SimpleImageCacheKey(aObject->mRequest, aObject->mIsAccelerated));
+    mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas, aObject->mIsAccelerated));
   }
 
   nsTHashtable<ImageCacheEntry> mCache;
   nsTHashtable<SimpleImageCacheEntry> mSimpleCache;
   size_t mTotal;
   RefPtr<ImageCacheObserver> mImageCacheObserver;
 };
 
@@ -232,99 +256,104 @@ ImageCache::~ImageCache() {
   mImageCacheObserver->Destroy();
 }
 
 void
 CanvasImageCache::NotifyDrawImage(Element* aImage,
                                   HTMLCanvasElement* aCanvas,
                                   imgIRequest* aRequest,
                                   SourceSurface* aSource,
-                                  const IntSize& aSize)
+                                  const IntSize& aSize,
+                                  bool aIsAccelerated)
 {
   if (!gImageCache) {
     gImageCache = new ImageCache();
     nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver());
   }
 
-  ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas));
+  ImageCacheEntry* entry =
+    gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated));
   if (entry) {
     if (entry->mData->mSourceSurface) {
       // We are overwriting an existing entry.
       gImageCache->mTotal -= entry->mData->SizeInBytes();
       gImageCache->RemoveObject(entry->mData);
-      gImageCache->mSimpleCache.RemoveEntry(*entry->mData->mRequest);
+      gImageCache->mSimpleCache.RemoveEntry(SimpleImageCacheKey(entry->mData->mRequest, entry->mData->mIsAccelerated));
     }
     gImageCache->AddObject(entry->mData);
 
     nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
     if (ilc) {
       ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                       getter_AddRefs(entry->mData->mRequest));
     }
     entry->mData->mILC = ilc;
     entry->mData->mSourceSurface = aSource;
     entry->mData->mSize = aSize;
 
     gImageCache->mTotal += entry->mData->SizeInBytes();
 
     if (entry->mData->mRequest) {
       SimpleImageCacheEntry* simpleentry =
-        gImageCache->mSimpleCache.PutEntry(*entry->mData->mRequest);
+        gImageCache->mSimpleCache.PutEntry(SimpleImageCacheKey(entry->mData->mRequest, aIsAccelerated));
       simpleentry->mSourceSurface = aSource;
     }
   }
 
   if (!sCanvasImageCacheLimit)
     return;
 
   // Expire the image cache early if its larger than we want it to be.
   while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit))
     gImageCache->AgeOneGeneration();
 }
 
 SourceSurface*
 CanvasImageCache::Lookup(Element* aImage,
                          HTMLCanvasElement* aCanvas,
-                         gfx::IntSize* aSize)
+                         gfx::IntSize* aSize,
+                         bool aIsAccelerated)
 {
   if (!gImageCache)
     return nullptr;
 
-  ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas));
+  ImageCacheEntry* entry =
+    gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas, aIsAccelerated));
   if (!entry || !entry->mData->mILC)
     return nullptr;
 
   nsCOMPtr<imgIRequest> request;
   entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request));
   if (request != entry->mData->mRequest)
     return nullptr;
 
   gImageCache->MarkUsed(entry->mData);
 
   *aSize = entry->mData->mSize;
   return entry->mData->mSourceSurface;
 }
 
 SourceSurface*
-CanvasImageCache::SimpleLookup(Element* aImage)
+CanvasImageCache::SimpleLookup(Element* aImage,
+                               bool aIsAccelerated)
 {
   if (!gImageCache)
     return nullptr;
 
   nsCOMPtr<imgIRequest> request;
   nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
   if (!ilc)
     return nullptr;
 
   ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                   getter_AddRefs(request));
   if (!request)
     return nullptr;
 
-  SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(*request);
+  SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(SimpleImageCacheKey(request, aIsAccelerated));
   if (!entry)
     return nullptr;
 
   return entry->mSourceSurface;
 }
 
 NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
 
--- a/dom/canvas/CanvasImageCache.h
+++ b/dom/canvas/CanvasImageCache.h
@@ -28,31 +28,34 @@ public:
    * Notify that image element aImage was (or is about to be) drawn to aCanvas
    * using the first frame of aRequest's image. The data for the surface is
    * in aSurface, and the image size is in aSize.
    */
   static void NotifyDrawImage(dom::Element* aImage,
                               dom::HTMLCanvasElement* aCanvas,
                               imgIRequest* aRequest,
                               SourceSurface* aSource,
-                              const gfx::IntSize& aSize);
+                              const gfx::IntSize& aSize,
+                              bool aIsAccelerated);
 
   /**
    * Check whether aImage has recently been drawn into aCanvas. If we return
    * a non-null surface, then the image was recently drawn into the canvas
    * (with the same image request) and the returned surface contains the image
    * data, and the image size will be returned in aSize.
    */
   static SourceSurface* Lookup(dom::Element* aImage,
                                dom::HTMLCanvasElement* aCanvas,
-                               gfx::IntSize* aSize);
+                               gfx::IntSize* aSize,
+                               bool aIsAccelerated);
 
   /**
    * This is the same as Lookup, except it works on any image recently drawn
    * into any canvas. Security checks need to be done again if using the
    * results from this.
    */
-  static SourceSurface* SimpleLookup(dom::Element* aImage);
+  static SourceSurface* SimpleLookup(dom::Element* aImage,
+                                     bool aIsAccelerated);
 };
 
 } // namespace mozilla
 
 #endif /* CANVASIMAGECACHE_H_ */
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4307,17 +4307,18 @@ CanvasRenderingContext2D::CachedSurfaceF
   }
 
   nsCOMPtr<nsIPrincipal> principal;
   if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
       !principal) {
     return res;
   }
 
-  res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement);
+  res.mSourceSurface =
+    CanvasImageCache::SimpleLookup(aElement, mIsSkiaGL);
   if (!res.mSourceSurface) {
     return res;
   }
 
   int32_t corsmode = imgIRequest::CORS_NONE;
   if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
     res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
   }
@@ -4413,17 +4414,17 @@ CanvasRenderingContext2D::DrawImage(cons
       HTMLImageElement* img = &image.GetAsHTMLImageElement();
       element = img;
     } else {
       HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
       element = video;
     }
 
     srcSurf =
-      CanvasImageCache::Lookup(element, mCanvasElement, &imgSize);
+      CanvasImageCache::Lookup(element, mCanvasElement, &imgSize, mIsSkiaGL);
   }
 
   nsLayoutUtils::DirectDrawInfo drawInfo;
 
 #ifdef USE_SKIA_GPU
   if (mRenderingMode == RenderingMode::OpenGLBackendMode &&
       !srcSurf &&
       image.IsHTMLVideoElement() &&
@@ -4561,17 +4562,17 @@ CanvasRenderingContext2D::DrawImage(cons
       CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
                                             res.mPrincipal, res.mIsWriteOnly,
                                             res.mCORSUsed);
     }
 
     if (res.mSourceSurface) {
       if (res.mImageRequest) {
         CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest,
-                                          res.mSourceSurface, imgSize);
+                                          res.mSourceSurface, imgSize, mIsSkiaGL);
       }
 
       srcSurf = res.mSourceSurface;
     } else {
       drawInfo = res.mDrawInfo;
     }
   }
 
--- a/dom/canvas/test/_webgl-conformance.ini
+++ b/dom/canvas/test/_webgl-conformance.ini
@@ -503,16 +503,17 @@ support-files = webgl-conformance/../web
 skip-if = os == 'android'
 [webgl-conformance/_wrappers/test_conformance__canvas__buffer-preserve-test.html]
 [webgl-conformance/_wrappers/test_conformance__canvas__canvas-test.html]
 [webgl-conformance/_wrappers/test_conformance__canvas__canvas-zero-size.html]
 [webgl-conformance/_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html]
 skip-if = os == 'mac'
 [webgl-conformance/_wrappers/test_conformance__canvas__drawingbuffer-test.html]
 [webgl-conformance/_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html]
+skip-if = os == 'mac'
 [webgl-conformance/_wrappers/test_conformance__context__constants.html]
 [webgl-conformance/_wrappers/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html]
 skip-if = (os == 'b2g')
 [webgl-conformance/_wrappers/test_conformance__context__context-lost-restored.html]
 [webgl-conformance/_wrappers/test_conformance__context__context-lost.html]
 [webgl-conformance/_wrappers/test_conformance__context__context-type-test.html]
 [webgl-conformance/_wrappers/test_conformance__context__incorrect-context-object-behaviour.html]
 [webgl-conformance/_wrappers/test_conformance__context__methods.html]
--- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini
@@ -103,11 +103,14 @@ skip-if = (os == 'b2g')
 # Failures after enabling color_buffer_[half_]float.
 fail-if = (os == 'linux')
 
 ########################################################################
 # Mac
 [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html]
 # Intermittent crash on OSX.
 skip-if = os == 'mac'
+[_wrappers/test_conformance__canvas__viewport-unchanged-upon-resize.html]
+# New OSX r7 machines and 10.10.5 is causing perma failure (bug 1216549)
+skip-if = os == 'mac'
 
 ########################################################################
 # Win
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2552,16 +2552,18 @@ EventStateManager::DoScrollText(nsIScrol
   bool isDeltaModePixel =
     (aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL);
 
   nsIScrollableFrame::ScrollMode mode;
   switch (aEvent->scrollType) {
     case WidgetWheelEvent::SCROLL_DEFAULT:
       if (isDeltaModePixel) {
         mode = nsIScrollableFrame::NORMAL;
+      } else if (aEvent->mFlags.mHandledByAPZ) {
+        mode = nsIScrollableFrame::SMOOTH_MSD;
       } else {
         mode = nsIScrollableFrame::SMOOTH;
       }
       break;
     case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
       mode = nsIScrollableFrame::INSTANT;
       break;
     case WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY:
@@ -3114,16 +3116,24 @@ EventStateManager::PostHandleEvent(nsPre
       nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll);
 
       // When APZ is enabled, the actual scroll animation might be handled by
       // the compositor.
       WheelPrefs::Action action;
       if (pluginFrame) {
         MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction());
         action = WheelPrefs::ACTION_SEND_TO_PLUGIN;
+      } else if (nsLayoutUtils::IsScrollFrameWithSnapping(frameToScroll)) {
+        // If the target has scroll-snapping points then we want to handle
+        // the wheel event on the main thread even if we have APZ enabled. Do
+        // so and let the APZ know that it should ignore this event.
+        if (wheelEvent->mFlags.mHandledByAPZ) {
+          wheelEvent->mFlags.mDefaultPrevented = true;
+        }
+        action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
       } else if (wheelEvent->mFlags.mHandledByAPZ) {
         action = WheelPrefs::ACTION_NONE;
       } else {
         action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
       }
       switch (action) {
         case WheelPrefs::ACTION_SCROLL: {
           // For scrolling of default action, we should honor the mouse wheel
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -454,35 +454,32 @@ HangMonitorParent::HangMonitorParent(Pro
    mMonitor("HangMonitorParent lock"),
    mShutdownDone(false),
    mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock")
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
 }
 
-static PLDHashOperator
-DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData)
-{
-#ifdef MOZ_CRASHREPORTER
-  if (!aCrashId.IsEmpty()) {
-    CrashReporter::DeleteMinidumpFilesForID(aCrashId);
-  }
-#endif
-  return PL_DHASH_NEXT;
-}
-
 HangMonitorParent::~HangMonitorParent()
 {
   // For some reason IPDL doesn't automatically delete the channel for a
   // bridged protocol (bug 1090570). So we have to do it ourselves.
   XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(GetTransport()));
 
+#ifdef MOZ_CRASHREPORTER
   MutexAutoLock lock(mBrowserCrashDumpHashLock);
-  mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr);
+
+  for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) {
+    nsString crashId = iter.UserData();
+    if (!crashId.IsEmpty()) {
+      CrashReporter::DeleteMinidumpFilesForID(crashId);
+    }
+  }
+#endif
 }
 
 void
 HangMonitorParent::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mMonitor);
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -594,47 +594,29 @@ ProcessPriorityManagerImpl::ObserveConte
     pppm->ShutDown();
 
     mParticularManagers.Remove(childID);
 
     mHighPriorityChildIDs.RemoveEntry(childID);
   }
 }
 
-static PLDHashOperator
-FreezeParticularProcessPriorityManagers(
-  const uint64_t& aKey,
-  RefPtr<ParticularProcessPriorityManager> aValue,
-  void* aUserData)
-{
-  aValue->Freeze();
-  return PL_DHASH_NEXT;
-}
-
-static PLDHashOperator
-UnfreezeParticularProcessPriorityManagers(
-  const uint64_t& aKey,
-  RefPtr<ParticularProcessPriorityManager> aValue,
-  void* aUserData)
-{
-  aValue->Unfreeze();
-  return PL_DHASH_NEXT;
-}
-
 void
 ProcessPriorityManagerImpl::ObserveScreenStateChanged(const char16_t* aData)
 {
   if (NS_LITERAL_STRING("on").Equals(aData)) {
     sFrozen = false;
-    mParticularManagers.EnumerateRead(
-      &UnfreezeParticularProcessPriorityManagers, nullptr);
+    for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) {
+      iter.UserData()->Unfreeze();
+    }
   } else {
     sFrozen = true;
-    mParticularManagers.EnumerateRead(
-      &FreezeParticularProcessPriorityManagers, nullptr);
+    for (auto iter = mParticularManagers.Iter(); !iter.Done(); iter.Next()) {
+      iter.UserData()->Freeze();
+    }
   }
 }
 
 bool
 ProcessPriorityManagerImpl::ChildProcessHasHighPriority( void )
 {
   return mHighPriorityChildIDs.Count() > 0;
 }
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -193,24 +193,24 @@ VideoData::ShallowCopyUpdateTimestampAnd
                                         aOther->mDisplay,
                                         aOther->mFrameID);
   v->mDiscontinuity = aOther->mDiscontinuity;
   v->mImage = aOther->mImage;
   return v.forget();
 }
 
 /* static */
-void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
+bool VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
                                     const VideoInfo& aInfo,
                                     const YCbCrBuffer &aBuffer,
                                     const IntRect& aPicture,
                                     bool aCopyData)
 {
   if (!aVideoImage) {
-    return;
+    return false;
   }
   const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0];
   const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1];
   const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2];
 
   PlanarYCbCrData data;
   data.mYChannel = Y.mData + Y.mOffset;
   data.mYSize = IntSize(Y.mWidth, Y.mHeight);
@@ -224,19 +224,19 @@ void VideoData::SetVideoDataToImage(Plan
   data.mCrSkip = Cr.mSkip;
   data.mPicX = aPicture.x;
   data.mPicY = aPicture.y;
   data.mPicSize = aPicture.Size();
   data.mStereoMode = aInfo.mStereoMode;
 
   aVideoImage->SetDelayedConversion(true);
   if (aCopyData) {
-    aVideoImage->SetData(data);
+    return aVideoImage->SetData(data);
   } else {
-    aVideoImage->SetDataNoCopy(data);
+    return aVideoImage->SetDataNoCopy(data);
   }
 }
 
 /* static */
 already_AddRefed<VideoData>
 VideoData::Create(const VideoInfo& aInfo,
                   ImageContainer* aContainer,
                   Image* aImage,
@@ -325,34 +325,34 @@ VideoData::Create(const VideoInfo& aInfo
   if (!v->mImage) {
     return nullptr;
   }
   NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR ||
                v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
                "Wrong format?");
   PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get());
 
-  if (!aImage) {
-    VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
-                                   true /* aCopyData */);
-  } else {
-    VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
-                                   false /* aCopyData */);
+  bool shouldCopyData = (aImage == nullptr);
+  if (!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
+                                      shouldCopyData)) {
+    return nullptr;
   }
 
 #ifdef MOZ_WIDGET_GONK
   if (!videoImage->IsValid() && !aImage && IsYV12Format(Y, Cb, Cr)) {
     // Failed to allocate gralloc. Try fallback.
     v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
     if (!v->mImage) {
       return nullptr;
     }
     videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get());
-    VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
-                                   true /* aCopyData */);
+    if(!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
+                                       true /* aCopyData */)) {
+      return nullptr;
+    }
   }
 #endif
   return v.forget();
 }
 
 /* static */
 already_AddRefed<VideoData>
 VideoData::Create(const VideoInfo& aInfo,
@@ -468,17 +468,19 @@ VideoData::Create(const VideoInfo& aInfo
                "Wrong format?");
   typedef mozilla::layers::GrallocImage GrallocImage;
   GrallocImage* videoImage = static_cast<GrallocImage*>(v->mImage.get());
   GrallocImage::GrallocData data;
 
   data.mPicSize = aPicture.Size();
   data.mGraphicBuffer = aBuffer;
 
-  videoImage->SetData(data);
+  if (!videoImage->SetData(data)) {
+    return nullptr;
+  }
 
   return v.forget();
 }
 #endif  // MOZ_OMX_DECODER
 
 // Alignment value - 1. 0 means that data isn't aligned.
 // For 32-bytes aligned, use 31U.
 #define RAW_DATA_ALIGNMENT 31U
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -278,17 +278,17 @@ public:
   // specified timestamp and duration. All data from aOther is copied
   // into the new VideoData, as ShallowCopyUpdateDuration() does.
   static already_AddRefed<VideoData>
   ShallowCopyUpdateTimestampAndDuration(const VideoData* aOther, int64_t aTimestamp,
                                         int64_t aDuration);
 
   // Initialize PlanarYCbCrImage. Only When aCopyData is true,
   // video data is copied to PlanarYCbCrImage.
-  static void SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
+  static bool SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
                                   const VideoInfo& aInfo,
                                   const YCbCrBuffer &aBuffer,
                                   const IntRect& aPicture,
                                   bool aCopyData);
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   // Dimensions at which to display the video frame. The picture region
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -740,16 +740,19 @@ MediaDecoder::NotifyDecodeError()
   AbstractThread::MainThread()->Dispatch(r.forget());
 }
 
 void
 MediaDecoder::NotifyDataEnded(nsresult aStatus)
 {
   RefPtr<MediaDecoder> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+    if (self->mShuttingDown) {
+      return;
+    }
     self->NotifyDownloadEnded(aStatus);
     if (NS_SUCCEEDED(aStatus)) {
       HTMLMediaElement* element = self->mOwner->GetMediaElement();
       if (element) {
         element->DownloadSuspended();
       }
       // NotifySuspendedStatusChanged will tell the element that download
       // has been suspended "by the cache", which is true since we never
--- a/dom/media/VideoSegment.cpp
+++ b/dom/media/VideoSegment.cpp
@@ -75,17 +75,20 @@ VideoFrame::CreateBlackImage(const gfx::
   data.mCrChannel = data.mCbChannel + aSize.height * data.mCbCrStride / 2;
   data.mCbCrSize = gfx::IntSize(aSize.width / 2, aSize.height / 2);
   data.mPicX = 0;
   data.mPicY = 0;
   data.mPicSize = gfx::IntSize(aSize.width, aSize.height);
   data.mStereoMode = StereoMode::MONO;
 
   // SetData copies data, so we can free data.
-  planar->SetData(data);
+  if (!planar->SetData(data)) {
+    MOZ_ASSERT(false);
+    return nullptr;
+  }
 
   return image.forget();
 }
 #endif // !defined(MOZILLA_XPCOMRT_API)
 
 VideoChunk::VideoChunk()
 {}
 
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -53,16 +53,22 @@ if (SpecialPowers.Services.appinfo.name 
 
 // Used by test_bug654550.html, for videoStats preference
 var gVideoTests = [
   { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266 },
   { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 },
   { name:"bogus.duh", type:"bogus/duh" }
 ];
 
+// Temp hack for trackIDs and captureStream() -- bug 1215769
+var gLongerTests = [
+  { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 },
+  { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56 },
+];
+
 // Used by test_progress to ensure we get the correct progress information
 // during resource download.
 var gProgressTests = [
   { name:"r11025_u8_c1.wav", type:"audio/x-wav", duration:1.0, size:11069 },
   { name:"big.wav", type:"audio/x-wav", duration:9.278981, size:102444 },
   { name:"seek.ogv", type:"video/ogg", duration:3.966, size:285310 },
   { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.266, size:28942 },
   { name:"seek.webm", type:"video/webm", duration:3.966, size:215529 },
--- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
@@ -9,17 +9,17 @@
 <script type="application/javascript;version=1.8">
 var manager = new MediaTestManager;
 
 createHTML({
   bug: "1081409",
   title: "Captured video-only over peer connection",
   visible: true
 }).then(() => new Promise(resolve => {
-  manager.runTests(getPlayableVideos(gSmallTests), startTest);
+  manager.runTests(getPlayableVideos(gLongerTests), startTest);
   manager.onFinished = () => {
     // Tear down before SimpleTest.finish.
     if ("nsINetworkInterfaceListService" in SpecialPowers.Ci) {
       getNetworkUtils().tearDownNetwork();
     }
     resolve();
   };
 }))
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -245,20 +245,26 @@ MediaEngineDefaultVideoSource::Notify(ns
 #ifdef MOZ_WEBRTC
   uint64_t timestamp = PR_Now();
   YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth,
 		     data.mYChannel,
 		     reinterpret_cast<unsigned char*>(&timestamp), sizeof(timestamp),
 		     0, 0);
 #endif
 
-  ycbcr_image->SetData(data);
+  bool setData = ycbcr_image->SetData(data);
+  MOZ_ASSERT(setData);
+
   // SetData copies data, so we can free the frame
   ReleaseFrame(data);
 
+  if (!setData) {
+    return NS_ERROR_FAILURE;
+  }
+
   MonitorAutoLock lock(mMonitor);
 
   // implicitly releases last image
   mImage = ycbcr_image.forget();
 
   return NS_OK;
 }
 
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -310,17 +310,20 @@ MediaEngineRemoteVideoSource::DeliverFra
   data.mCbChannel = frame + mHeight * data.mYStride;
   data.mCrChannel = data.mCbChannel + ((mHeight+1)/2) * data.mCbCrStride;
   data.mCbCrSize = IntSize((mWidth+1)/ 2, (mHeight+1)/ 2);
   data.mPicX = 0;
   data.mPicY = 0;
   data.mPicSize = IntSize(mWidth, mHeight);
   data.mStereoMode = StereoMode::MONO;
 
-  videoImage->SetData(data);
+  if (!videoImage->SetData(data)) {
+    MOZ_ASSERT(false);
+    return 0;
+  }
 
 #ifdef DEBUG
   static uint32_t frame_num = 0;
   LOGFRAME(("frame %d (%dx%d); timestamp %u, ntp_time %" PRIu64 ", render_time %" PRIu64,
             frame_num++, mWidth, mHeight, time_stamp, ntp_time, render_time));
 #endif
 
   // we don't touch anything in 'this' until here (except for snapshot,
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -1439,59 +1439,35 @@ PluginInstanceParent::NPP_Print(NPPrint*
 }
 
 PPluginScriptableObjectParent*
 PluginInstanceParent::AllocPPluginScriptableObjectParent()
 {
     return new PluginScriptableObjectParent(Proxy);
 }
 
-#ifdef DEBUG
-namespace {
-
-struct ActorSearchData
-{
-    PluginScriptableObjectParent* actor;
-    bool found;
-};
-
-PLDHashOperator
-ActorSearch(NPObject* aKey,
-            PluginScriptableObjectParent* aData,
-            void* aUserData)
-{
-    ActorSearchData* asd = reinterpret_cast<ActorSearchData*>(aUserData);
-    if (asd->actor == aData) {
-        asd->found = true;
-        return PL_DHASH_STOP;
-    }
-    return PL_DHASH_NEXT;
-}
-
-} // namespace
-#endif // DEBUG
-
 bool
 PluginInstanceParent::DeallocPPluginScriptableObjectParent(
                                          PPluginScriptableObjectParent* aObject)
 {
     PluginScriptableObjectParent* actor =
         static_cast<PluginScriptableObjectParent*>(aObject);
 
     NPObject* object = actor->GetObject(false);
     if (object) {
         NS_ASSERTION(mScriptableObjects.Get(object, nullptr),
                      "NPObject not in the hash!");
         mScriptableObjects.Remove(object);
     }
 #ifdef DEBUG
     else {
-        ActorSearchData asd = { actor, false };
-        mScriptableObjects.EnumerateRead(ActorSearch, &asd);
-        NS_ASSERTION(!asd.found, "Actor in the hash with a null NPObject!");
+        for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) {
+            NS_ASSERTION(actor != iter.UserData(),
+                         "Actor in the hash with a null NPObject!");
+        }
     }
 #endif
 
     delete actor;
     return true;
 }
 
 bool
--- a/dom/presentation/interfaces/nsITCPPresentationServer.idl
+++ b/dom/presentation/interfaces/nsITCPPresentationServer.idl
@@ -17,26 +17,27 @@ interface nsIPresentationControlChannel;
 [scriptable, uuid(296fd171-e4d0-4de0-99ff-ad8ed52ddef3)]
 interface nsITCPDeviceInfo: nsISupports
 {
   readonly attribute AUTF8String id;
   readonly attribute AUTF8String address;
   readonly attribute uint16_t port;
 };
 
-[scriptable, uuid(fbb890a9-9e95-47d1-a425-86fd95881d81)]
+[scriptable, uuid(09bddfaf-fcc2-4dc9-b33e-a509a1c2fb6d)]
 interface nsITCPPresentationServerListener: nsISupports
 {
   /**
-   * Callback while the server socket stops listening.
-   * @param   aReason
-   *          The reason of the socket close. NS_OK for manually |close|.
-   *          <other-error> on failure.
+   * Callback while the server socket changes port.
+   * This event won't be cached so you should get current port after setting
+   * this listener to make sure the value is updated.
+   * @param   aPort
+   *          The port of the server socket.
    */
-  void onClose(in nsresult aReason);
+  void onPortChange(in uint16_t aPort);
 
   /**
    * Callback while the remote host is requesting to start a presentation session.
    * @param aDeviceInfo The device information related to the remote host.
    * @param aUrl The URL requested to open by remote device.
    * @param aPresentationId The Id for representing this session.
    * @param aControlChannel The control channel for this session.
    */
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -8,38 +8,35 @@
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsAutoPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "nsIPropertyBag2.h"
 #endif // MOZ_WIDGET_ANDROID
 
 #define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
 #define PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS "dom.presentation.discovery.timeout_ms"
 #define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
 #define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
 
 #define SERVICE_TYPE "_mozilla_papi._tcp."
 
-inline static PRLogModuleInfo*
-GetProviderLog()
-{
-  static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider");
-  return log;
-}
+static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider");
+
 #undef LOG_I
-#define LOG_I(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+#define LOG_I(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Debug, (__VA_ARGS__))
 #undef LOG_E
-#define LOG_E(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Error, (__VA_ARGS__))
+#define LOG_E(...) MOZ_LOG(sMulticastDNSProviderLogModule, mozilla::LogLevel::Error, (__VA_ARGS__))
 
 namespace mozilla {
 namespace dom {
 namespace presentation {
 
 static const char* kObservedPrefs[] = {
   PREF_PRESENTATION_DISCOVERY,
   PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS,
@@ -256,31 +253,46 @@ MulticastDNSDeviceProvider::RegisterServ
 {
   LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable);
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDiscoverable) {
     return NS_OK;
   }
 
-  MOZ_ASSERT(!mRegisterRequest);
+  nsresult rv;
 
-  nsresult rv;
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) {
-    return rv;
-  }
   uint16_t servicePort;
   if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) {
     return rv;
   }
 
   /**
+    * If |servicePort| is non-zero, it means PresentationServer is running.
+    * Otherwise, we should make it start serving.
+    */
+  if (!servicePort) {
+    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) {
+      return rv;
+    }
+    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->StartService(0)))) {
+      return rv;
+    }
+    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) {
+      return rv;
+    }
+  }
+
+  // Cancel on going service registration.
+  if (mRegisterRequest) {
+    mRegisterRequest->Cancel(NS_OK);
+    mRegisterRequest = nullptr;
+  }
+
+  /**
    * Register the presentation control channel server as an mDNS service.
    */
   nsCOMPtr<nsIDNSServiceInfo> serviceInfo =
     do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(
@@ -750,22 +762,19 @@ NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo,
                                                  int32_t aErrorCode)
 {
   LOG_E("OnRegistrationFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   mRegisterRequest = nullptr;
 
-  nsresult rv;
-
   if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) {
-    if (NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
-      return rv;
-    }
+    return NS_DispatchToMainThread(
+             NS_NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterService));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo,
                                                    int32_t aErrorCode)
@@ -850,27 +859,23 @@ MulticastDNSDeviceProvider::OnResolveFai
   LOG_E("OnResolveFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   return NS_OK;
 }
 
 // nsITCPPresentationServerListener
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnClose(nsresult aReason)
+MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort)
 {
-  LOG_I("OnClose: %x", aReason);
+  LOG_I("OnPortChange: %d", aPort);
   MOZ_ASSERT(NS_IsMainThread());
 
-  UnregisterService(aReason);
-
-  nsresult rv;
-
-  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
-    return rv;
+  if (mDiscoverable) {
+    RegisterService();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
                                              const nsAString& aUrl,
--- a/dom/presentation/provider/TCPPresentationServer.js
+++ b/dom/presentation/provider/TCPPresentationServer.js
@@ -35,26 +35,21 @@ TCPPresentationServer.prototype = {
   _controlChannels: [],
 
   startService: function(aPort) {
     if (this._isServiceInit()) {
       DEBUG && log("TCPPresentationServer - server socket has been initialized");
       throw Cr.NS_ERROR_FAILURE;
     }
 
-    if (typeof aPort === "undefined") {
-      DEBUG && log("TCPPresentationServer - aPort should not be undefined");
-      throw Cr.NS_ERROR_FAILURE;
-    }
-
     /**
      * 0 or undefined indicates opt-out parameter, and a port will be selected
      * automatically.
      */
-    let serverSocketPort = (aPort !== 0) ? aPort : -1;
+    let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1;
 
     this._serverSocket = Cc["@mozilla.org/network/server-socket;1"]
                          .createInstance(Ci.nsIServerSocket);
 
     if (!this._serverSocket) {
       DEBUG && log("TCPPresentationServer - create server socket fail.");
       throw Cr.NS_ERROR_FAILURE;
     }
@@ -65,17 +60,22 @@ TCPPresentationServer.prototype = {
     } catch (e) {
       // NS_ERROR_SOCKET_ADDRESS_IN_USE
       DEBUG && log("TCPPresentationServer - init server socket fail: " + e);
       throw Cr.NS_ERROR_FAILURE;
     }
 
     this._port = this._serverSocket.port;
 
-    DEBUG && log("TCPPresentationServer - service start on port: " + aPort);
+    DEBUG && log("TCPPresentationServer - service start on port: " + this._port);
+
+    // Monitor network interface change to restart server socket.
+    // Only B2G has nsINetworkManager
+    Services.obs.addObserver(this, "network-active-changed", false);
+    Services.obs.addObserver(this, "network:offline-status-changed", false);
   },
 
   get id() {
     return this._id;
   },
 
   set id(aId) {
     this._id = aId;
@@ -173,41 +173,84 @@ TCPPresentationServer.prototype = {
     if (index !== -1) {
       delete this._controlChannels[index];
     }
   },
 
   // nsIServerSocketListener (Triggered by nsIServerSocket.init)
   onStopListening: function(aServerSocket, aStatus) {
     DEBUG && log("TCPPresentationServer - onStopListening: " + aStatus);
-
-    if (this._serverSocket) {
-      DEBUG && log("TCPPresentationServer - should be non-manually closed");
-      this.close();
-    } else if (aStatus === Cr.NS_BINDING_ABORTED) {
-      DEBUG && log("TCPPresentationServer - should be manually closed");
-      aStatus = Cr.NS_OK;
-    }
-
-    this._listener && this._listener.onClose(aStatus);
   },
 
   close: function() {
     DEBUG && log("TCPPresentationServer - close");
-    if (this._serverSocket) {
+    if (this._isServiceInit()) {
       DEBUG && log("TCPPresentationServer - close server socket");
       this._serverSocket.close();
       this._serverSocket = null;
+
+      Services.obs.removeObserver(this, "network-active-changed");
+      Services.obs.removeObserver(this, "network:offline-status-changed");
     }
     this._port = 0;
   },
 
+  // nsIObserver
+  observe: function(aSubject, aTopic, aData) {
+    DEBUG && log("TCPPresentationServer - observe: " + aTopic);
+    switch (aTopic) {
+      case "network-active-changed": {
+        if (!aSubject) {
+          DEBUG && log("No active network");
+          return;
+        }
+
+        /**
+         * Restart service only when original status is online because other
+         * cases will be handled by "network:offline-status-changed".
+         */
+        if (!Services.io.offline) {
+          this._restartService();
+        }
+        break;
+      }
+      case "network:offline-status-changed": {
+        if (aData == "offline") {
+          DEBUG && log("network offline");
+          return;
+        }
+        this._restartService();
+        break;
+      }
+    }
+  },
+
+  _restartService: function() {
+    DEBUG && log("TCPPresentationServer - restart service");
+
+    // restart server socket
+    if (this._isServiceInit()) {
+      let port = this._port;
+      this.close();
+
+      try {
+        this.startService();
+        if (this._listener && this._port !== port) {
+           this._listener.onPortChange(this._port);
+        }
+      } catch (e) {
+        DEBUG && log("TCPPresentationServer - restart service fail: " + e);
+      }
+    }
+  },
+
   classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"),
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener,
-                                          Ci.nsITCPPresentationServer]),
+                                          Ci.nsITCPPresentationServer,
+                                          Ci.nsIObserver]),
 };
 
 function ChannelDescription(aInit) {
   this._type = aInit.type;
   switch (this._type) {
     case Ci.nsIPresentationChannelDescription.TYPE_TCP:
       this._tcpAddresses = Cc["@mozilla.org/array;1"]
                            .createInstance(Ci.nsIMutableArray);
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -997,17 +997,18 @@ function serverClosed() {
   Assert.equal(listener.devices.length, 0);
 
   provider.listener = listener;
   Assert.equal(mockObj.serviceRegistered, 1);
   Assert.equal(mockObj.serviceUnregistered, 0);
   Assert.equal(listener.devices.length, 1);
 
   let serverListener = provider.QueryInterface(Ci.nsITCPPresentationServerListener);
-  serverListener.onClose(Cr.NS_ERROR_UNEXPECTED);
+  let randomPort = 9527;
+  serverListener.onPortChange(randomPort);
 
   Assert.equal(mockObj.serviceRegistered, 2);
   Assert.equal(mockObj.serviceUnregistered, 1);
   Assert.equal(listener.devices.length, 1);
 
   // Unregister
   provider.listener = null;
   Assert.equal(mockObj.serviceRegistered, 2);
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -178,49 +178,50 @@ function testPresentationServer() {
       Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
       yayFuncs.presenterControlChannelClose();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
-  let expectedReason;
   tps.listener = {
-    onClose: function(aReason) {
-      Assert.equal(aReason, Cr.NS_ERROR_ABORT, 'TCPPresentationServer close as expected');
-      Services.io.offline = false;
+    onPortChange: function(aPort) {
+      Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid');
+      tps.close();
       run_next_test();
     },
-  }
+  };
 
-  // Let the server socket be closed non-manually
+  // Let the server socket restart automatically.
   Services.io.offline = true;
+  Services.io.offline = false;
 }
 
 function oneMoreLoop() {
   try {
     tps.startService(PRESENTER_CONTROL_CHANNEL_PORT);
     testPresentationServer();
   } catch (e) {
     Assert.ok(false, 'TCP presentation init fail:' + e);
     run_next_test();
   }
 }
 
 
 function shutdown()
 {
   tps.listener = {
-    onClose: function(aReason) {
-      Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success');
-      run_next_test();
+    onPortChange: function(aPort) {
+      Assert.ok(false, 'TCPPresentationServer port changed');
     },
-  }
+  };
   tps.close();
+  Assert.equal(tps.port, 0, "TCPPresentationServer closed");
+  run_next_test();
 }
 
 // Test manually close control channel with NS_ERROR_FAILURE
 function changeCloseReason() {
   CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE;
   run_next_test();
 }
 
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -108,20 +108,17 @@ namespace {
 
 void
 AsyncLog(nsIInterceptedChannel *aInterceptedChannel,
          const nsACString& aRespondWithScriptSpec,
          uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber,
          const nsACString& aMessageName, const nsTArray<nsString>& aParams)
 {
   MOZ_ASSERT(aInterceptedChannel);
-  // Since the intercepted channel is kept alive and paused while handling
-  // the FetchEvent, we are guaranteed the reporter is stable on the worker
-  // thread.
-  nsIConsoleReportCollector* reporter =
+  nsCOMPtr<nsIConsoleReportCollector> reporter =
     aInterceptedChannel->GetConsoleReportCollector();
   if (reporter) {
     reporter->AddConsoleReport(nsIScriptError::errorFlag,
                                NS_LITERAL_CSTRING("Service Worker Interception"),
                                nsContentUtils::eDOM_PROPERTIES,
                                aRespondWithScriptSpec,
                                aRespondWithLineNumber,
                                aRespondWithColumnNumber,
--- a/embedding/components/commandhandler/nsCommandGroup.cpp
+++ b/embedding/components/commandhandler/nsCommandGroup.cpp
@@ -22,24 +22,22 @@ public:
     nsControllerCommandGroup::GroupsHashtable& aInHashTable);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISIMPLEENUMERATOR
 
 protected:
   virtual ~nsGroupsEnumerator();
 
-  static PLDHashOperator HashEnum(const nsACString& aKey,
-                                  nsTArray<nsCString>* aData, void* aClosure);
   nsresult Initialize();
 
 protected:
   nsControllerCommandGroup::GroupsHashtable& mHashTable;
   int32_t mIndex;
-  char** mGroupNames;  // array of pointers to char16_t* in the hash table
+  const char** mGroupNames;  // array of pointers to char16_t* in the hash table
   bool mInitted;
 };
 
 /* Implementation file */
 NS_IMPL_ISUPPORTS(nsGroupsEnumerator, nsISimpleEnumerator)
 
 nsGroupsEnumerator::nsGroupsEnumerator(
       nsControllerCommandGroup::GroupsHashtable& aInHashTable)
@@ -87,54 +85,45 @@ nsGroupsEnumerator::GetNext(nsISupports*
     }
   }
 
   mIndex++;
   if (mIndex >= static_cast<int32_t>(mHashTable.Count())) {
     return NS_ERROR_FAILURE;
   }
 
-  char* thisGroupName = mGroupNames[mIndex];
+  const char* thisGroupName = mGroupNames[mIndex];
 
   nsCOMPtr<nsISupportsCString> supportsString =
     do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   supportsString->SetData(nsDependentCString(thisGroupName));
   return CallQueryInterface(supportsString, aResult);
 }
 
-/* static */
-/* return false to stop */
-PLDHashOperator
-nsGroupsEnumerator::HashEnum(const nsACString& aKey, nsTArray<nsCString>* aData,
-                             void* aClosure)
-{
-  nsGroupsEnumerator* groupsEnum = static_cast<nsGroupsEnumerator*>(aClosure);
-  groupsEnum->mGroupNames[groupsEnum->mIndex] = (char*)aKey.Data();
-  groupsEnum->mIndex++;
-  return PL_DHASH_NEXT;
-}
-
 nsresult
 nsGroupsEnumerator::Initialize()
 {
   if (mInitted) {
     return NS_OK;
   }
 
-  mGroupNames = new char*[mHashTable.Count()];
+  mGroupNames = new const char*[mHashTable.Count()];
   if (!mGroupNames) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   mIndex = 0;
-  mHashTable.EnumerateRead(HashEnum, this);
+  for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) {
+    mGroupNames[mIndex] = iter.Key().Data();
+    mIndex++;
+  }
 
   mIndex = -1;
   mInitted = true;
   return NS_OK;
 }
 
 class nsNamedGroupEnumerator : public nsISimpleEnumerator
 {
--- a/embedding/components/commandhandler/nsCommandManager.cpp
+++ b/embedding/components/commandhandler/nsCommandManager.cpp
@@ -29,39 +29,29 @@ nsCommandManager::nsCommandManager()
   : mWindow(nullptr)
 {
 }
 
 nsCommandManager::~nsCommandManager()
 {
 }
 
-static PLDHashOperator
-TraverseCommandObservers(const char* aKey,
-                         nsCommandManager::ObserverList* aObservers,
-                         void* aClosure)
-{
-  nsCycleCollectionTraversalCallback* cb =
-    static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
-
-  int32_t i, numItems = aObservers->Length();
-  for (i = 0; i < numItems; ++i) {
-    cb->NoteXPCOMChild(aObservers->ElementAt(i));
-  }
-
-  return PL_DHASH_NEXT;
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsCommandManager)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCommandManager)
   tmp->mObserversTable.Clear();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCommandManager)
-  tmp->mObserversTable.EnumerateRead(TraverseCommandObservers, &cb);
+  for (auto iter = tmp->mObserversTable.Iter(); !iter.Done(); iter.Next()) {
+    nsCommandManager::ObserverList* observers = iter.UserData();
+    int32_t numItems = observers->Length();
+    for (int32_t i = 0; i < numItems; ++i) {
+      cb.NoteXPCOMChild(observers->ElementAt(i));
+    }
+  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCommandManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCommandManager)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCommandManager)
   NS_INTERFACE_MAP_ENTRY(nsICommandManager)
   NS_INTERFACE_MAP_ENTRY(nsPICommandUpdater)
--- a/embedding/components/commandhandler/nsControllerCommandTable.cpp
+++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp
@@ -173,38 +173,29 @@ nsControllerCommandTable::GetCommandStat
     NS_WARNING("Controller command table asked to do a command that it does "
                "not handle");
     return NS_OK;
   }
   return commandHandler->GetCommandStateParams(aCommandName, aParams,
                                                aCommandRefCon);
 }
 
-static PLDHashOperator
-AddCommand(const nsACString& aKey, nsIControllerCommand* aData, void* aArg)
-{
-  // aArg is a pointer to a array of strings. It gets incremented after
-  // allocating each one so that it points to the next location for AddCommand
-  // to assign a string to.
-  char*** commands = static_cast<char***>(aArg);
-  (**commands) = ToNewCString(aKey);
-  (*commands)++;
-  return PL_DHASH_NEXT;
-}
-
 NS_IMETHODIMP
 nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount,
                                                char*** aCommands)
 {
   char** commands =
     static_cast<char**>(moz_xmalloc(sizeof(char*) * mCommandsTable.Count()));
   *aCount = mCommandsTable.Count();
   *aCommands = commands;
 
-  mCommandsTable.EnumerateRead(AddCommand, &commands);
+  for (auto iter = mCommandsTable.Iter(); !iter.Done(); iter.Next()) {
+    *commands = ToNewCString(iter.Key());
+    commands++;
+  }
   return NS_OK;
 }
 
 nsresult
 NS_NewControllerCommandTable(nsIControllerCommandTable** aResult)
 {
   NS_PRECONDITION(aResult != nullptr, "null ptr");
   if (!aResult) {
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp
+++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp
@@ -98,22 +98,16 @@ struct nsWebBrowserPersist::URIData
     nsCOMPtr<nsIURI> mDataPath;
     nsCOMPtr<nsIURI> mRelativeDocumentURI;
     nsCString mRelativePathToData;
     nsCString mCharset;
 
     nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut);
 };
 
-struct nsWebBrowserPersist::URIFixupData
-{
-    RefPtr<FlatURIMap> mFlatMap;
-    nsCOMPtr<nsIURI> mTargetBaseURI;
-};
-
 // Information about the output stream
 struct nsWebBrowserPersist::OutputData
 {
     nsCOMPtr<nsIURI> mFile;
     nsCOMPtr<nsIURI> mOriginalLocation;
     nsCOMPtr<nsIOutputStream> mStream;
     int64_t mSelfProgress;
     int64_t mSelfProgressMax;
@@ -588,23 +582,79 @@ nsWebBrowserPersist::SerializeNextFile()
     // First, handle gathered URIs.
     // Count how many URIs in the URI map require persisting
     uint32_t urisToPersist = 0;
     if (mURIMap.Count() > 0) {
         // This is potentially O(n^2), when taking into account the
         // number of times this method is called.  If it becomes a
         // bottleneck, the count of not-yet-persisted URIs could be
         // maintained separately.
-        mURIMap.EnumerateRead(EnumCountURIsToPersist, &urisToPersist);
+        for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+            URIData *data = iter.UserData();
+            if (data->mNeedsPersisting && !data->mSaved) {
+                urisToPersist++;
+            }
+        }
     }
 
     if (urisToPersist > 0) {
         // Persist each file in the uri map. The document(s)
         // will be saved after the last one of these is saved.
-        mURIMap.EnumerateRead(EnumPersistURIs, this);
+        for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+            URIData *data = iter.UserData();
+
+            if (!data->mNeedsPersisting || data->mSaved) {
+                continue;
+            }
+
+            nsresult rv;
+
+            // Create a URI from the key.
+            nsCOMPtr<nsIURI> uri;
+            rv = NS_NewURI(getter_AddRefs(uri), iter.Key(),
+                           data->mCharset.get());
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+                break;
+            }
+
+            // Make a URI to save the data to.
+            nsCOMPtr<nsIURI> fileAsURI;
+            rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI));
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+                break;
+            }
+            rv = AppendPathToURI(fileAsURI, data->mFilename);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+                break;
+            }
+
+            // The Referrer Policy doesn't matter here since the referrer is
+            // nullptr.
+            rv = SaveURIInternal(uri, nullptr, nullptr,
+                                 mozilla::net::RP_Default, nullptr, nullptr,
+                                 fileAsURI, true, mIsPrivate);
+            // If SaveURIInternal fails, then it will have called EndDownload,
+            // which means that |data| is no longer valid memory. We MUST bail.
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+                break;
+            }
+
+            if (rv == NS_OK) {
+                // Store the actual object because once it's persisted this
+                // will be fixed up with the right file extension.
+                data->mFile = fileAsURI;
+                data->mSaved = true;
+            } else {
+                data->mNeedsFixup = false;
+            }
+
+            if (mSerializingOutput) {
+                break;
+            }
+        }
     }
 
     // If there are downloads happening, wait until they're done; the
     // OnStopRequest handler will call this method again.
     if (mOutputMap.Count() > 0) {
         return;
     }
 
@@ -647,27 +697,28 @@ nsWebBrowserPersist::SerializeNextFile()
     if (mTargetBaseURI) {
         rv = mTargetBaseURI->GetSpec(targetBaseSpec);
         if (NS_FAILED(rv)) {
             SendErrorStatusChange(true, rv, nullptr, nullptr);
             EndDownload(rv);
             return;
         }
     }
-    
+
     // mFlatURIMap must be rebuilt each time through SerializeNextFile, as
     // mTargetBaseURI is used to create the relative URLs and will be different
     // with each serialized document.
     RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec);
-    
-    URIFixupData fixupData;
-    fixupData.mFlatMap = flatMap;
-    fixupData.mTargetBaseURI = mTargetBaseURI;
-    
-    mURIMap.EnumerateRead(EnumCopyURIsToFlatMap, &fixupData);
+    for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+        nsAutoCString mapTo;
+        nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo);
+        if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
+            flatMap->Add(iter.Key(), mapTo);
+        }
+    }
     mFlatURIMap = flatMap.forget();
 
     nsCOMPtr<nsIFile> localFile;
     GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile));
     if (localFile) {
         // if we're not replacing an existing file but the file
         // exists, something is wrong
         bool fileExists = false;
@@ -1750,33 +1801,45 @@ nsWebBrowserPersist::FinishSaveDocumentI
         // Done walking DOMs; on to the serialization phase.
         SerializeNextFile();
     }
 }
 
 void nsWebBrowserPersist::Cleanup()
 {
     mURIMap.Clear();
-    mOutputMap.EnumerateRead(EnumCleanupOutputMap, this);
+    for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+        nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key());
+        if (channel) {
+            channel->Cancel(NS_BINDING_ABORTED);
+        }
+    }
     mOutputMap.Clear();
-    mUploadList.EnumerateRead(EnumCleanupUploadList, this);
+
+    for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) {
+        nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key());
+        if (channel) {
+            channel->Cancel(NS_BINDING_ABORTED);
+        }
+    }
     mUploadList.Clear();
+
     uint32_t i;
-    for (i = 0; i < mDocList.Length(); i++)
-    {
+    for (i = 0; i < mDocList.Length(); i++) {
         DocData *docData = mDocList.ElementAt(i);
         delete docData;
     }
     mDocList.Clear();
-    for (i = 0; i < mCleanupList.Length(); i++)
-    {
+
+    for (i = 0; i < mCleanupList.Length(); i++) {
         CleanupData *cleanupData = mCleanupList.ElementAt(i);
         delete cleanupData;
     }
     mCleanupList.Clear();
+
     mFilenameList.Clear();
 }
 
 void nsWebBrowserPersist::CleanupLocalFiles()
 {
     // Two passes, the first pass cleans up files, the second pass tests
     // for and then deletes empty directories. Directories that are not
     // empty after the first pass must contain files from something else
@@ -2314,233 +2377,93 @@ nsWebBrowserPersist::EndDownload(nsresul
     mCompleted = true;
     Cleanup();
 
     mProgressListener = nullptr;
     mProgressListener2 = nullptr;
     mEventSink = nullptr;
 }
 
-struct MOZ_STACK_CLASS FixRedirectData
-{
-    nsCOMPtr<nsIChannel> mNewChannel;
-    nsCOMPtr<nsIURI> mOriginalURI;
-    nsCOMPtr<nsISupports> mMatchingKey;
-};
-
 nsresult
 nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel)
 {
     NS_ENSURE_ARG_POINTER(aNewChannel);
+
+    // Iterate through existing open channels looking for one with a URI
+    // matching the one specified.
     nsCOMPtr<nsIURI> originalURI;
-
-    // Enumerate through existing open channels looking for one with
-    // a URI matching the one specified.
-
-    FixRedirectData data;
-    data.mNewChannel = aNewChannel;
-    data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI));
-    mOutputMap.EnumerateRead(EnumFixRedirect, &data);
-
-    // If a match is found, remove the data entry with the old channel key
-    // and re-add it with the new channel key.
-
-    if (data.mMatchingKey)
-    {
-        nsAutoPtr<OutputData> outputData;
-        mOutputMap.RemoveAndForget(data.mMatchingKey, outputData);
-        NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE);
-
-        // Store data again with new channel unless told to ignore redirects
-        if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA))
-        {
-            nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel);
-            mOutputMap.Put(keyPtr, outputData.forget());
+    aNewChannel->GetOriginalURI(getter_AddRefs(originalURI));
+    for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+        nsISupports* key = iter.Key();
+        nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key);
+        nsCOMPtr<nsIURI> thisURI;
+
+        thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
+
+        // Compare this channel's URI to the one passed in.
+        bool matchingURI = false;
+        thisURI->Equals(originalURI, &matchingURI);
+        if (matchingURI) {
+            // If a match is found, remove the data entry with the old channel
+            // key and re-add it with the new channel key.
+            nsAutoPtr<OutputData> outputData;
+            mOutputMap.RemoveAndForget(key, outputData);
+            NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE);
+
+            // Store data again with new channel unless told to ignore redirects
+            if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) {
+                nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel);
+                mOutputMap.Put(keyPtr, outputData.forget());
+            }
+
+            break;
         }
     }
-
     return NS_OK;
 }
 
-PLDHashOperator
-nsWebBrowserPersist::EnumFixRedirect(nsISupports *aKey, OutputData *aData, void* aClosure)
-{
-    FixRedirectData *data = static_cast<FixRedirectData*>(aClosure);
-
-    nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(aKey);
-    nsCOMPtr<nsIURI> thisURI;
-
-    thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
-
-    // Compare this channel's URI to the one passed in.
-    bool matchingURI = false;
-    thisURI->Equals(data->mOriginalURI, &matchingURI);
-    if (matchingURI)
-    {
-        data->mMatchingKey = aKey;
-        return PL_DHASH_STOP;
-    }
-
-    return PL_DHASH_NEXT;
-}
-
 void
 nsWebBrowserPersist::CalcTotalProgress()
 {
     mTotalCurrentProgress = 0;
     mTotalMaxProgress = 0;
 
-    if (mOutputMap.Count() > 0)
-    {
+    if (mOutputMap.Count() > 0) {
         // Total up the progress of each output stream
-        mOutputMap.EnumerateRead(EnumCalcProgress, this);
+        for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+            // Only count toward total progress if destination file is local.
+            OutputData* data = iter.UserData();
+            nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile);
+            if (fileURL) {
+                mTotalCurrentProgress += data->mSelfProgress;
+                mTotalMaxProgress += data->mSelfProgressMax;
+            }
+        }
     }
 
-    if (mUploadList.Count() > 0)
-    {
+    if (mUploadList.Count() > 0) {
         // Total up the progress of each upload
-        mUploadList.EnumerateRead(EnumCalcUploadProgress, this);
+        for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) {
+            UploadData* data = iter.UserData();
+            if (data) {
+                mTotalCurrentProgress += data->mSelfProgress;
+                mTotalMaxProgress += data->mSelfProgressMax;
+            }
+        }
     }
 
     // XXX this code seems pretty bogus and pointless
     if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0)
     {
         // No output streams so we must be complete
         mTotalCurrentProgress = 10000;
         mTotalMaxProgress = 10000;
     }
 }
 
-PLDHashOperator
-nsWebBrowserPersist::EnumCalcProgress(nsISupports *aKey, OutputData *aData, void* aClosure)
-{
-    nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure);
-
-    // only count toward total progress if destination file is local
-    nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aData->mFile);
-    if (fileURL)
-    {
-        pthis->mTotalCurrentProgress += aData->mSelfProgress;
-        pthis->mTotalMaxProgress += aData->mSelfProgressMax;
-    }
-    return PL_DHASH_NEXT;
-}
-
-PLDHashOperator
-nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData, void* aClosure)
-{
-    if (aData && aClosure)
-    {
-        nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure);
-        pthis->mTotalCurrentProgress += aData->mSelfProgress;
-        pthis->mTotalMaxProgress += aData->mSelfProgressMax;
-    }
-    return PL_DHASH_NEXT;
-}
-
-PLDHashOperator
-nsWebBrowserPersist::EnumCountURIsToPersist(const nsACString &aKey, URIData *aData, void* aClosure)
-{
-    uint32_t *count = static_cast<uint32_t*>(aClosure);
-    if (aData->mNeedsPersisting && !aData->mSaved)
-    {
-        (*count)++;
-    }
-    return PL_DHASH_NEXT;
-}
-
-PLDHashOperator
-nsWebBrowserPersist::EnumPersistURIs(const nsACString &aKey, URIData *aData, void* aClosure)
-{
-    if (!aData->mNeedsPersisting || aData->mSaved)
-    {
-        return PL_DHASH_NEXT;
-    }
-
-    nsWebBrowserPersist *pthis = static_cast<nsWebBrowserPersist*>(aClosure);
-    nsresult rv;
-
-    // Create a URI from the key
-    nsAutoCString key = nsAutoCString(aKey);
-    nsCOMPtr<nsIURI> uri;
-    rv = NS_NewURI(getter_AddRefs(uri),
-                   nsDependentCString(key.get(), key.Length()),
-                   aData->mCharset.get());
-    NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP);
-
-    // Make a URI to save the data to
-    nsCOMPtr<nsIURI> fileAsURI;
-    rv = aData->mDataPath->Clone(getter_AddRefs(fileAsURI));
-    NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP);
-    rv = pthis->AppendPathToURI(fileAsURI, aData->mFilename);
-    NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP);
-
-    // The Referrer Policy doesn't matter here since the referrer is nullptr.
-    rv = pthis->SaveURIInternal(uri, nullptr, nullptr, mozilla::net::RP_Default,
-                                nullptr, nullptr, fileAsURI, true, pthis->mIsPrivate);
-    // if SaveURIInternal fails, then it will have called EndDownload,
-    // which means that |aData| is no longer valid memory.  we MUST bail.
-    NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP);
-
-    if (rv == NS_OK)
-    {
-        // Store the actual object because once it's persisted this
-        // will be fixed up with the right file extension.
-
-        aData->mFile = fileAsURI;
-        aData->mSaved = true;
-    }
-    else
-    {
-        aData->mNeedsFixup = false;
-    }
-
-    if (pthis->mSerializingOutput)
-        return PL_DHASH_STOP;
-
-    return PL_DHASH_NEXT;
-}
-
-PLDHashOperator
-nsWebBrowserPersist::EnumCleanupOutputMap(nsISupports *aKey, OutputData *aData, void* aClosure)
-{
-    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aKey);
-    if (channel)
-    {
-        channel->Cancel(NS_BINDING_ABORTED);
-    }
-    return PL_DHASH_NEXT;
-}
-
-PLDHashOperator
-nsWebBrowserPersist::EnumCleanupUploadList(nsISupports *aKey, UploadData *aData, void* aClosure)
-{
-    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aKey);
-    if (channel)
-    {
-        channel->Cancel(NS_BINDING_ABORTED);
-    }
-    return PL_DHASH_NEXT;
-}
-
-/* static */ PLDHashOperator
-nsWebBrowserPersist::EnumCopyURIsToFlatMap(const nsACString &aKey,
-                                          URIData *aData,
-                                          void* aClosure)
-{
-    URIFixupData *fixupData = static_cast<URIFixupData*>(aClosure);
-    FlatURIMap* theMap = fixupData->mFlatMap;
-    nsAutoCString mapTo;
-    nsresult rv = aData->GetLocalURI(fixupData->mTargetBaseURI, mapTo);
-    if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
-        theMap->Add(aKey, mapTo);
-    }
-    return PL_DHASH_NEXT;
-}
-
 nsresult
 nsWebBrowserPersist::StoreURI(
     const char *aURI, bool aNeedsPersisting, URIData **aData)
 {
     NS_ENSURE_ARG_POINTER(aURI);
 
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_NewURI(getter_AddRefs(uri),
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h
+++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h
@@ -131,34 +131,16 @@ private:
 
     void EndDownload(nsresult aResult);
     void FinishDownload();
     void SerializeNextFile();
     void CalcTotalProgress();
 
     void SetApplyConversionIfNeeded(nsIChannel *aChannel);
 
-    // Hash table enumerators
-    static PLDHashOperator EnumPersistURIs(
-        const nsACString &aKey, URIData *aData, void* aClosure);
-    static PLDHashOperator EnumCleanupOutputMap(
-        nsISupports *aKey, OutputData *aData, void* aClosure);
-    static PLDHashOperator EnumCleanupUploadList(
-        nsISupports *aKey, UploadData *aData, void* aClosure);
-    static PLDHashOperator EnumCalcProgress(
-        nsISupports *aKey, OutputData *aData, void* aClosure);
-    static PLDHashOperator EnumCalcUploadProgress(
-        nsISupports *aKey, UploadData *aData, void* aClosure);
-    static PLDHashOperator EnumFixRedirect(
-        nsISupports *aKey, OutputData *aData, void* aClosure);
-    static PLDHashOperator EnumCountURIsToPersist(
-        const nsACString &aKey, URIData *aData, void* aClosure);
-    static PLDHashOperator EnumCopyURIsToFlatMap(
-        const nsACString &aKey, URIData *aData, void* aClosure);
-
     nsCOMPtr<nsIURI>          mCurrentDataPath;
     bool                      mCurrentDataPathIsRelative;
     nsCString                 mCurrentRelativePathToData;
     nsCOMPtr<nsIURI>          mCurrentBaseURI;
     nsCString                 mCurrentCharset;
     nsCOMPtr<nsIURI>          mTargetBaseURI;
     uint32_t                  mCurrentThingsToPersist;
 
--- a/gfx/layers/GrallocImages.cpp
+++ b/gfx/layers/GrallocImages.cpp
@@ -52,30 +52,30 @@ GrallocImage::GrallocImage()
 {
   mFormat = ImageFormat::GRALLOC_PLANAR_YCBCR;
 }
 
 GrallocImage::~GrallocImage()
 {
 }
 
-void
+bool
 GrallocImage::SetData(const Data& aData)
 {
   MOZ_ASSERT(!mTextureClient, "TextureClient is already set");
   NS_PRECONDITION(aData.mYSize.width % 2 == 0, "Image should have even width");
   NS_PRECONDITION(aData.mYSize.height % 2 == 0, "Image should have even height");
   NS_PRECONDITION(aData.mYStride % 16 == 0, "Image should have stride of multiple of 16 pixels");
 
   mData = aData;
   mSize = aData.mPicSize;
 
   if (gfxPlatform::GetPlatform()->IsInGonkEmulator()) {
     // Emulator does not support HAL_PIXEL_FORMAT_YV12.
-    return;
+    return false;
   }
 
   RefPtr<GrallocTextureClientOGL> textureClient =
        new GrallocTextureClientOGL(ImageBridgeChild::GetSingleton(),
                                    gfx::SurfaceFormat::UNKNOWN,
                                    gfx::BackendType::NONE);
   // GrallocImages are all YUV and don't support alpha.
   textureClient->SetIsOpaque(true);
@@ -83,25 +83,25 @@ GrallocImage::SetData(const Data& aData)
     textureClient->AllocateGralloc(mData.mYSize,
                                    HAL_PIXEL_FORMAT_YV12,
                                    GraphicBuffer::USAGE_SW_READ_OFTEN |
                                    GraphicBuffer::USAGE_SW_WRITE_OFTEN |
                                    GraphicBuffer::USAGE_HW_TEXTURE);
   sp<GraphicBuffer> graphicBuffer = textureClient->GetGraphicBuffer();
   if (!result || !graphicBuffer.get()) {
     mTextureClient = nullptr;
-    return;
+    return false;
   }
 
   mTextureClient = textureClient;
 
   void* vaddr;
   if (graphicBuffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
                           &vaddr) != OK) {
-    return;
+    return false;
   }
 
   uint8_t* yChannel = static_cast<uint8_t*>(vaddr);
   gfx::IntSize ySize = aData.mYSize;
   int32_t yStride = graphicBuffer->getStride();
 
   uint8_t* vChannel = yChannel + (yStride * ySize.height);
   gfx::IntSize uvSize = gfx::IntSize(ySize.width / 2,
@@ -139,22 +139,24 @@ GrallocImage::SetData(const Data& aData)
   graphicBuffer->unlock();
   // Initialze the channels' addresses.
   // Do not cache the addresses when gralloc buffer is not locked.
   // gralloc hal could map gralloc buffer only when the buffer is locked,
   // though some gralloc hals implementation maps it when it is allocated.
   mData.mYChannel     = nullptr;
   mData.mCrChannel    = nullptr;
   mData.mCbChannel    = nullptr;
+  return true;
 }
 
-void GrallocImage::SetData(const GrallocData& aData)
+bool GrallocImage::SetData(const GrallocData& aData)
 {
   mTextureClient = static_cast<GrallocTextureClientOGL*>(aData.mGraphicBuffer.get());
   mSize = aData.mPicSize;
+  return true;
 }
 
 /**
  * Converts YVU420 semi planar frames to RGB565, possibly taking different
  * stride values.
  * Needed because the Android ColorConverter class assumes that the Y and UV
  * channels have equal stride.
  */
--- a/gfx/layers/GrallocImages.h
+++ b/gfx/layers/GrallocImages.h
@@ -61,23 +61,23 @@ public:
   GrallocImage();
 
   virtual ~GrallocImage();
 
   /**
    * This makes a copy of the data buffers, in order to support functioning
    * in all different layer managers.
    */
-  virtual void SetData(const Data& aData);
+  virtual bool SetData(const Data& aData);
 
   /**
    *  Share the SurfaceDescriptor without making the copy, in order
    *  to support functioning in all different layer managers.
    */
-  virtual void SetData(const GrallocData& aData);
+  virtual bool SetData(const GrallocData& aData);
 
   // From [android 4.0.4]/hardware/msm7k/libgralloc-qsd8k/gralloc_priv.h
   enum {
     /* OEM specific HAL formats */
     HAL_PIXEL_FORMAT_YCbCr_422_P            = 0x102,
     HAL_PIXEL_FORMAT_YCbCr_420_P            = 0x103,
     HAL_PIXEL_FORMAT_YCbCr_420_SP           = 0x109,
     HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO    = 0x10A,
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -479,66 +479,68 @@ CopyPlane(uint8_t *aDst, const uint8_t *
         src += aSkip;
       }
       aSrc += aStride;
       aDst += aStride;
     }
   }
 }
 
-void
+bool
 PlanarYCbCrImage::CopyData(const Data& aData)
 {
   mData = aData;
 
   // update buffer size
   size_t size = mData.mCbCrStride * mData.mCbCrSize.height * 2 +
                 mData.mYStride * mData.mYSize.height;
 
   // get new buffer
   mBuffer = AllocateBuffer(size);
   if (!mBuffer)
-    return;
+    return false;
 
   // update buffer size
   mBufferSize = size;
 
   mData.mYChannel = mBuffer;
   mData.mCbChannel = mData.mYChannel + mData.mYStride * mData.mYSize.height;
   mData.mCrChannel = mData.mCbChannel + mData.mCbCrStride * mData.mCbCrSize.height;
 
   CopyPlane(mData.mYChannel, aData.mYChannel,
             mData.mYSize, mData.mYStride, mData.mYSkip);
   CopyPlane(mData.mCbChannel, aData.mCbChannel,
             mData.mCbCrSize, mData.mCbCrStride, mData.mCbSkip);
   CopyPlane(mData.mCrChannel, aData.mCrChannel,
             mData.mCbCrSize, mData.mCbCrStride, mData.mCrSkip);
 
   mSize = aData.mPicSize;
+  return true;
 }
 
-void
+bool
 PlanarYCbCrImage::SetData(const Data &aData)
 {
-  CopyData(aData);
+  return CopyData(aData);
 }
 
 gfxImageFormat
 PlanarYCbCrImage::GetOffscreenFormat()
 {
   return mOffscreenFormat == gfxImageFormat::Unknown ?
     gfxPlatform::GetPlatform()->GetOffscreenFormat() :
     mOffscreenFormat;
 }
 
-void
+bool
 PlanarYCbCrImage::SetDataNoCopy(const Data &aData)
 {
   mData = aData;
   mSize = aData.mPicSize;
+  return true;
 }
 
 uint8_t*
 PlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize)
 {
   // get new buffer
   mBuffer = AllocateBuffer(aSize);
   if (mBuffer) {
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -651,26 +651,26 @@ public:
   };
 
   virtual ~PlanarYCbCrImage();
 
   /**
    * This makes a copy of the data buffers, in order to support functioning
    * in all different layer managers.
    */
-  virtual void SetData(const Data& aData);
+  virtual bool SetData(const Data& aData);
 
   /**
    * This doesn't make a copy of the data buffers. Can be used when mBuffer is
    * pre allocated with AllocateAndGetNewBuffer(size) and then SetDataNoCopy is
    * called to only update the picture size, planes etc. fields in mData.
    * The GStreamer media backend uses this to decode into PlanarYCbCrImage(s)
    * directly.
    */
-  virtual void SetDataNoCopy(const Data &aData);
+  virtual bool SetDataNoCopy(const Data &aData);
 
   /**
    * This allocates and returns a new buffer
    */
   virtual uint8_t* AllocateAndGetNewBuffer(uint32_t aSize);
 
   /**
    * Ask this Image to not convert YUV to RGB during SetData, and make
@@ -704,17 +704,17 @@ public:
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
 protected:
   /**
    * Make a copy of the YCbCr data into local storage.
    *
    * @param aData           Input image data.
    */
-  void CopyData(const Data& aData);
+  bool CopyData(const Data& aData);
 
   /**
    * Return a buffer to store image data in.
    * The default implementation returns memory that can
    * be freed wit delete[]
    */
   virtual uint8_t* AllocateBuffer(uint32_t aSize);
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1046,16 +1046,21 @@ APZCTreeManager::ProcessEvent(WidgetInpu
   return result;
 }
 
 nsEventStatus
 APZCTreeManager::ProcessMouseEvent(WidgetMouseEventBase& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Note, we call this before having transformed the reference point.
+  UpdateWheelTransaction(aEvent);
+
   MouseInput input(aEvent);
   input.mOrigin = ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y);
 
   nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
 
   aEvent.refPoint.x = input.mOrigin.x;
   aEvent.refPoint.y = input.mOrigin.y;
   aEvent.mFlags.mHandledByAPZ = true;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3346,21 +3346,18 @@ AsyncPanZoomController::CurrentTouchBloc
 
 PanGestureBlockState*
 AsyncPanZoomController::CurrentPanGestureBlock()
 {
   return GetInputQueue()->CurrentPanGestureBlock();
 }
 
 void
-AsyncPanZoomController::ResetInputState()
+AsyncPanZoomController::ResetTouchInputState()
 {
-  // This may be called during non-touch input blocks as well. We send
-  // a fake cancel touch event here but on the assumption that none of the
-  // code in GEL assumes a CurrentTouchBlock()
   MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0);
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   if (listener) {
     listener->HandleInputEvent(cancel);
   }
   CancelAnimationAndGestureState();
 }
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -829,19 +829,19 @@ public:
    * Given the number of touch points in an input event and touch block they
    * belong to, check if the event can result in a panning/zooming behavior.
    * This is primarily used to figure out when to dispatch the pointercancel
    * event for the pointer events spec.
    */
   bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints);
 
   /**
-   * Clear internal state relating to input handling.
+   * Clear internal state relating to touch input handling.
    */
-  void ResetInputState();
+  void ResetTouchInputState();
 
 private:
   void CancelAnimationAndGestureState();
 
   RefPtr<InputQueue> mInputQueue;
   TouchBlockState* CurrentTouchBlock();
   bool HasReadyTouchBlock();
 
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -646,17 +646,19 @@ InputQueue::ProcessInputBlocks() {
         curBlock->GetTargetApzc().get());
     RefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc();
     // target may be null here if the initial target was unconfirmed and then
     // we later got a confirmed null target. in that case drop the events.
     if (!target) {
       curBlock->DropEvents();
     } else if (curBlock->IsDefaultPrevented()) {
       curBlock->DropEvents();
-      target->ResetInputState();
+      if (curBlock->AsTouchBlock()) {
+        target->ResetTouchInputState();
+      }
     } else {
       UpdateActiveApzc(curBlock->GetTargetApzc());
       curBlock->HandleEvents();
     }
     MOZ_ASSERT(!curBlock->HasEvents());
 
     if (mInputBlockQueue.Length() == 1 && curBlock->MustStayActive()) {
       // Some types of blocks (e.g. touch blocks) accumulate events until the
@@ -672,17 +674,17 @@ InputQueue::ProcessInputBlocks() {
     mInputBlockQueue.RemoveElementAt(0);
   } while (!mInputBlockQueue.IsEmpty());
 }
 
 void
 InputQueue::UpdateActiveApzc(const RefPtr<AsyncPanZoomController>& aNewActive) {
   if (mLastActiveApzc && mLastActiveApzc != aNewActive
       && mTouchCounter.GetActiveTouchCount() > 0) {
-    mLastActiveApzc->ResetInputState();
+    mLastActiveApzc->ResetTouchInputState();
   }
   mLastActiveApzc = aNewActive;
 }
 
 void
 InputQueue::Clear()
 {
   APZThreadUtils::AssertOnControllerThread();
--- a/gfx/layers/basic/BasicImages.cpp
+++ b/gfx/layers/basic/BasicImages.cpp
@@ -43,17 +43,17 @@ public:
   {
     if (mDecodedBuffer) {
       // Right now this only happens if the Image was never drawn, otherwise
       // this will have been tossed away at surface destruction.
       mRecycleBin->RecycleBuffer(mDecodedBuffer.forget(), mSize.height * mStride);
     }
   }
 
-  virtual void SetData(const Data& aData) override;
+  virtual bool SetData(const Data& aData) override;
   virtual void SetDelayedConversion(bool aDelayed) override { mDelayedConversion = aDelayed; }
 
   already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override;
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
@@ -86,53 +86,55 @@ public:
       image = new BasicPlanarYCbCrImage(aScaleHint, gfxPlatform::GetPlatform()->GetOffscreenFormat(), aRecycleBin);
       return image.forget();
     }
 
     return ImageFactory::CreateImage(aFormat, aScaleHint, aRecycleBin);
   }
 };
 
-void
+bool
 BasicPlanarYCbCrImage::SetData(const Data& aData)
 {
   PlanarYCbCrImage::SetData(aData);
 
   if (mDelayedConversion) {
-    return;
+    return false;
   }
 
   // Do some sanity checks to prevent integer overflow
   if (aData.mYSize.width > PlanarYCbCrImage::MAX_DIMENSION ||
       aData.mYSize.height > PlanarYCbCrImage::MAX_DIMENSION) {
     NS_ERROR("Illegal image source width or height");
-    return;
+    return false;
   }
 
   gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat());
 
   gfx::IntSize size(mScaleHint);
   gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size);
   if (size.width > PlanarYCbCrImage::MAX_DIMENSION ||
       size.height > PlanarYCbCrImage::MAX_DIMENSION) {
     NS_ERROR("Illegal image dest width or height");
-    return;
+    return false;
   }
 
   gfxImageFormat iFormat = gfx::SurfaceFormatToImageFormat(format);
   mStride = gfxASurface::FormatStrideForWidth(iFormat, size.width);
   mDecodedBuffer = AllocateBuffer(size.height * mStride);
   if (!mDecodedBuffer) {
     // out of memory
-    return;
+    return false;
   }
 
   gfx::ConvertYCbCrToRGB(aData, format, size, mDecodedBuffer, mStride);
   SetOffscreenFormat(iFormat);
   mSize = size;
+
+  return true;
 }
 
 already_AddRefed<gfx::SourceSurface>
 BasicPlanarYCbCrImage::GetAsSourceSurface()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be main thread");
 
   if (mSourceSurface) {
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -1645,16 +1645,17 @@ CompositorParent::SetControllerForLayerT
                              NewRunnableFunction(&UpdateControllerForLayersId,
                                                  aLayersId,
                                                  aController));
 }
 
 /*static*/ APZCTreeManager*
 CompositorParent::GetAPZCTreeManager(uint64_t aLayersId)
 {
+  EnsureLayerTreeMapReady();
   const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
   if (state && state->mParent) {
     return state->mParent->mApzcTreeManager;
   }
   return nullptr;
 }
 
 float
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
@@ -75,38 +75,39 @@ SharedPlanarYCbCrImage::GetAsSourceSurfa
 {
   if (!mTextureClient) {
     NS_WARNING("Can't get as surface");
     return nullptr;
   }
   return PlanarYCbCrImage::GetAsSourceSurface();
 }
 
-void
+bool
 SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData)
 {
   // If mTextureClient has not already been allocated (through Allocate(aData))
   // allocate it. This code path is slower than the one used when Allocate has
   // been called since it will trigger a full copy.
   PlanarYCbCrData data = aData;
   if (!mTextureClient && !Allocate(data)) {
-    return;
+    return false;
   }
 
   MOZ_ASSERT(mTextureClient->AsTextureClientYCbCr());
   if (!mTextureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) {
     MOZ_ASSERT(false, "Failed to lock the texture.");
-    return;
+    return false;
   }
   TextureClientAutoUnlock unlock(mTextureClient);
   if (!mTextureClient->AsTextureClientYCbCr()->UpdateYCbCr(aData)) {
     MOZ_ASSERT(false, "Failed to copy YCbCr data into the TextureClient");
-    return;
+    return false;
   }
   mTextureClient->MarkImmutable();
+  return true;
 }
 
 // needs to be overriden because the parent class sets mBuffer which we
 // do not want to happen.
 uint8_t*
 SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize)
 {
   MOZ_ASSERT(!mTextureClient, "This image already has allocated data");
@@ -126,22 +127,22 @@ SharedPlanarYCbCrImage::AllocateAndGetNe
 
   // update buffer size
   mBufferSize = size;
 
   YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
   return serializer.GetData();
 }
 
-void
+bool
 SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData)
 {
   MOZ_ASSERT(mTextureClient, "This Image should have already allocated data");
   if (!mTextureClient) {
-    return;
+    return false;
   }
   mData = aData;
   mSize = aData.mPicSize;
   /* SetDataNoCopy is used to update YUV plane offsets without (re)allocating
    * memory previously allocated with AllocateAndGetNewBuffer().
    * serializer.GetData() returns the address of the memory previously allocated
    * with AllocateAndGetNewBuffer(), that we subtract from the Y, Cb, Cr
    * channels to compute 0-based offsets to pass to InitializeBufferInfo.
@@ -154,16 +155,17 @@ SharedPlanarYCbCrImage::SetDataNoCopy(co
   serializer.InitializeBufferInfo(yOffset,
                                   cbOffset,
                                   crOffset,
                                   aData.mYStride,
                                   aData.mCbCrStride,
                                   aData.mYSize,
                                   aData.mCbCrSize,
                                   aData.mStereoMode);
+  return true;
 }
 
 uint8_t*
 SharedPlanarYCbCrImage::AllocateBuffer(uint32_t aSize)
 {
   MOZ_ASSERT(!mTextureClient,
              "This image already has allocated data");
   mTextureClient = TextureClient::CreateWithBufferSize(mCompositable->GetForwarder(),
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.h
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h
@@ -30,18 +30,18 @@ public:
 protected:
   ~SharedPlanarYCbCrImage();
 
 public:
   virtual TextureClient* GetTextureClient(CompositableClient* aClient) override;
   virtual uint8_t* GetBuffer() override;
 
   virtual already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override;
-  virtual void SetData(const PlanarYCbCrData& aData) override;
-  virtual void SetDataNoCopy(const Data &aData) override;
+  virtual bool SetData(const PlanarYCbCrData& aData) override;
+  virtual bool SetDataNoCopy(const Data &aData) override;
 
   virtual bool Allocate(PlanarYCbCrData& aData);
   virtual uint8_t* AllocateBuffer(uint32_t aSize) override;
   // needs to be overriden because the parent class sets mBuffer which we
   // do not want to happen.
   virtual uint8_t* AllocateAndGetNewBuffer(uint32_t aSize) override;
 
   virtual bool IsValid() override;
--- a/gfx/src/nsCoord.h
+++ b/gfx/src/nsCoord.h
@@ -6,16 +6,17 @@
 #ifndef NSCOORD_H
 #define NSCOORD_H
 
 #include "nsAlgorithm.h"
 #include "nscore.h"
 #include "nsMathUtils.h"
 #include <math.h>
 #include <float.h>
+#include <stdlib.h>
 
 #include "nsDebug.h"
 #include <algorithm>
 
 /*
  * Basic type used for the geometry classes.
  *
  * Normally all coordinates are maintained in an app unit coordinate
@@ -52,16 +53,32 @@ typedef int32_t nscoord;
 
 inline void VERIFY_COORD(nscoord aCoord) {
 #ifdef NS_COORD_IS_FLOAT
   NS_ASSERTION(floorf(aCoord) == aCoord,
                "Coords cannot have fractions");
 #endif
 }
 
+/**
+ * Divide aSpace by aN.  Assign the resulting quotient to aQuotient and
+ * return the remainder.
+ */
+inline nscoord NSCoordDivRem(nscoord aSpace, size_t aN, nscoord* aQuotient)
+{
+#ifdef NS_COORD_IS_FLOAT
+  *aQuotient = aSpace / aN;
+  return 0.0f;
+#else
+  div_t result = div(aSpace, aN);
+  *aQuotient = nscoord(result.quot);
+  return nscoord(result.rem);
+#endif
+}
+
 inline nscoord NSCoordMulDiv(nscoord aMult1, nscoord aMult2, nscoord aDiv) {
 #ifdef NS_COORD_IS_FLOAT
   return (aMult1 * aMult2 / aDiv);
 #else
   return (int64_t(aMult1) * int64_t(aMult2) / int64_t(aDiv));
 #endif
 }
 
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -1292,17 +1292,17 @@ js::AsmJSFunctionToString(JSContext* cx,
 
             // asm.js functions can't be anonymous
             MOZ_ASSERT(fun->atom());
             if (!out.append(fun->atom()))
                 return nullptr;
 
             size_t nameEnd = begin + fun->atom()->length();
             Rooted<JSFlatString*> src(cx, source->substring(cx, nameEnd, end));
-            if (!AppendUseStrictSource(cx, fun, src, out))
+            if (!src || !AppendUseStrictSource(cx, fun, src, out))
                 return nullptr;
         } else {
             Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
             if (!src)
                 return nullptr;
             if (!out.append(src))
                 return nullptr;
         }
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -1923,16 +1923,37 @@ NewUDateFormat(JSContext* cx, HandleObje
 
     uPattern = Char16ToUChar(patternChars.twoByteRange().start().get());
     if (!uPattern)
         return nullptr;
     uPatternLength = u_strlen(uPattern);
 
     UErrorCode status = U_ZERO_ERROR;
 
+    if (!uTimeZone) {
+#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
+        // JS::ResetTimeZone() recomputes the JS language's LocalTZA value.  It
+        // *should* also recreate ICU's default time zone (used for formatting
+        // when no time zone has been specified), but this operation is slow.
+        // Doing it eagerly introduces a perf regression -- see bug 1220693.
+        // Therefore we perform it lazily, responding to the value of a global
+        // atomic variable that records whether ICU's default time zone is
+        // accurate.  Baroque, but it's the only way to get the job done.
+        //
+        // Beware: this is kosher *only* if every place using ICU's default
+        // time zone performs the atomic compare-exchange and possible
+        // recreation song and dance routine here.
+        if (js::DefaultTimeZoneStatus.compareExchange(IcuTimeZoneStatus::NeedsUpdate,
+                                                      IcuTimeZoneStatus::Updating))
+        {
+            icu::TimeZone::recreateDefault();
+        }
+#endif
+    }
+
     // If building with ICU headers before 50.1, use UDAT_IGNORE instead of
     // UDAT_PATTERN.
     UDateFormat* df =
         udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
                   uPattern, uPatternLength, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return nullptr;
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -788,17 +788,20 @@ ModuleObject::setEvaluated()
     setReservedSlot(EvaluatedSlot, TrueHandleValue);
 }
 
 /* static */ bool
 ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval)
 {
     RootedScript script(cx, self->script());
     RootedModuleEnvironmentObject scope(cx, self->environment());
-    MOZ_ASSERT(scope);
+    if (!scope) {
+        JS_ReportError(cx, "Module declarations have not yet been instantiated");
+        return false;
+    }
 
     return Execute(cx, script, *scope, rval.address());
 }
 
 /* static */ ModuleNamespaceObject*
 ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleArrayObject exports)
 {
     MOZ_ASSERT(!self->namespace_());
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -78,17 +78,17 @@ js::obj_propertyIsEnumerable(JSContext* 
             unsigned attrs = GetShapeAttributes(obj, shape);
             args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0);
             return true;
         }
     }
 
     /* Step 1. */
     RootedId idRoot(cx);
-    if (!ValueToId<CanGC>(cx, idValue, &idRoot))
+    if (!ToPropertyKey(cx, idValue, &idRoot))
         return false;
 
     /* Step 2. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 3. */
@@ -526,17 +526,17 @@ js::obj_hasOwnProperty(JSContext* cx, un
         {
             args.rval().setBoolean(!!prop);
             return true;
         }
     }
 
     /* Step 1. */
     RootedId idRoot(cx);
-    if (!ValueToId<CanGC>(cx, idValue, &idRoot))
+    if (!ToPropertyKey(cx, idValue, &idRoot))
         return false;
 
     /* Step 2. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 3. */
@@ -769,17 +769,17 @@ js::obj_defineProperty(JSContext* cx, un
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-3.
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj))
         return false;
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(1), &id))
+    if (!ToPropertyKey(cx, args.get(1), &id))
         return false;
 
     // Steps 4-5.
     Rooted<PropertyDescriptor> desc(cx);
     if (!ToPropertyDescriptor(cx, args.get(2), true, &desc))
         return false;
 
     // Steps 6-8.
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2486,18 +2486,19 @@ ASTSerializer::tryStatement(ParseNode* p
 bool
 ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst)
 {
     if (!pn) {
         dst.setMagic(JS_SERIALIZE_NO_NODE);
         return true;
     }
 
-    return (pn->isKind(PNK_VAR))
-           ? variableDeclaration(pn, false, dst)
+    bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST);
+    return (lexical || pn->isKind(PNK_VAR))
+           ? variableDeclaration(pn, lexical, dst)
            : expression(pn, dst);
 }
 
 bool
 ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
                          MutableHandleValue dst)
 {
     RootedValue expr(cx);
@@ -2636,42 +2637,41 @@ ASTSerializer::statement(ParseNode* pn, 
         MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos));
         MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos));
         MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos));
 
         RootedValue stmt(cx);
         if (!statement(pn->pn_right, &stmt))
             return false;
 
-        if (head->isKind(PNK_FORIN)) {
+        if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) {
             RootedValue var(cx);
-            return (!head->pn_kid1
-                    ? pattern(head->pn_kid2, &var)
-                    : head->pn_kid1->isKind(PNK_LEXICALSCOPE)
-                    ? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
-                    : variableDeclaration(head->pn_kid1, false, &var)) &&
-                forIn(pn, head, var, stmt, dst);
-        }
-
-        if (head->isKind(PNK_FOROF)) {
-            RootedValue var(cx);
-            return (!head->pn_kid1
-                    ? pattern(head->pn_kid2, &var)
-                    : head->pn_kid1->isKind(PNK_LEXICALSCOPE)
-                    ? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
-                    : variableDeclaration(head->pn_kid1, false, &var)) &&
-                forOf(pn, head, var, stmt, dst);
+            if (!head->pn_kid1) {
+                if (!pattern(head->pn_kid2, &var))
+                    return false;
+            } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) {
+                if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var))
+                    return false;
+            } else {
+                if (!variableDeclaration(head->pn_kid1,
+                                         head->pn_kid1->isKind(PNK_LET) ||
+                                         head->pn_kid1->isKind(PNK_CONST),
+                                         &var))
+                {
+                    return false;
+                }
+            }
+            if (head->isKind(PNK_FORIN))
+                return forIn(pn, head, var, stmt, dst);
+            return forOf(pn, head, var, stmt, dst);
         }
 
         RootedValue init(cx), test(cx), update(cx);
 
-        return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK)
-                       ? head->pn_kid1
-                       : nullptr,
-                       &init) &&
+        return forInit(head->pn_kid1, &init) &&
                optExpression(head->pn_kid2, &test) &&
                optExpression(head->pn_kid3, &update) &&
                builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst);
       }
 
       case PNK_BREAK:
       case PNK_CONTINUE:
       {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2318,17 +2318,16 @@ BytecodeEmitter::checkSideEffects(ParseN
 
       case PNK_ARGSBODY:
         *answer = true;
         return true;
 
       case PNK_FORIN:           // by PNK_FOR
       case PNK_FOROF:           // by PNK_FOR
       case PNK_FORHEAD:         // by PNK_FOR
-      case PNK_FRESHENBLOCK:    // by PNK_FOR
       case PNK_CLASSMETHOD:     // by PNK_CLASS
       case PNK_CLASSNAMES:      // by PNK_CLASS
       case PNK_CLASSMETHODLIST: // by PNK_CLASS
       case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT
       case PNK_IMPORT_SPEC:      // by PNK_IMPORT
       case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT
       case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT
       case PNK_EXPORT_SPEC:      // by PNK_EXPORT
@@ -5284,16 +5283,25 @@ BytecodeEmitter::emitIterator()
         return false;
     checkTypeSet(JSOP_CALL);
     return true;
 }
 
 bool
 BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl)
 {
+    // ES6 specifies that loop variables get a fresh binding in each iteration.
+    // This is currently implemented for C-style for(;;) loops, but not
+    // for-in/of loops, though a similar approach should work. See bug 449811.
+    //
+    // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a
+    // scope containing an uninitialized `x`. If EXPR accesses `x`, we should
+    // get a ReferenceError due to the TDZ violation. This is not yet
+    // implemented. See bug 1069480.
+
     *letDecl = pn->isKind(PNK_LEXICALSCOPE);
     MOZ_ASSERT_IF(*letDecl, pn->isLexical());
 
     // If the left part is 'var x', emit code to define x if necessary using a
     // prologue opcode, but do not emit a pop. If it is 'let x', enterBlockScope
     // will initialize let bindings in emitForOf and emitForIn with
     // undefineds.
     //
@@ -5313,17 +5321,16 @@ BytecodeEmitter::emitForInOrOfVariables(
                 return false;
         }
         emittingForInit = false;
     }
 
     return true;
 }
 
-
 bool
 BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top)
 {
     MOZ_ASSERT(type == StmtType::FOR_OF_LOOP || type == StmtType::SPREAD);
     MOZ_ASSERT_IF(type == StmtType::FOR_OF_LOOP, pn && pn->pn_left->isKind(PNK_FOROF));
     MOZ_ASSERT_IF(type == StmtType::SPREAD, !pn);
 
     ParseNode* forHead = pn ? pn->pn_left : nullptr;
@@ -5586,47 +5593,68 @@ BytecodeEmitter::emitForIn(ParseNode* pn
     if (letDecl) {
         if (!leaveNestedScope(&letStmt))
             return false;
     }
 
     return true;
 }
 
-bool
-BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top)
+/* C-style `for (init; cond; update) ...` loop. */
+bool
+BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top)
 {
     LoopStmtInfo stmtInfo(cx);
     pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top);
 
     ParseNode* forHead = pn->pn_left;
     ParseNode* forBody = pn->pn_right;
 
-    /* C-style for (init; cond; update) ... loop. */
+    // If the head of this for-loop declared any lexical variables, the parser
+    // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the
+    // implicit scope of those variables. By the time we get here, we have
+    // already entered that scope. So far, so good.
+    //
+    // ### Scope freshening
+    //
+    // Each iteration of a `for (let V...)` loop creates a fresh loop variable
+    // binding for V, even if the loop is a C-style `for(;;)` loop:
+    //
+    //     var funcs = [];
+    //     for (let i = 0; i < 2; i++)
+    //         funcs.push(function() { return i; });
+    //     assertEq(funcs[0](), 0);  // the two closures capture...
+    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
+    //
+    // This is implemented by "freshening" the implicit block -- changing the
+    // scope chain to a fresh clone of the instantaneous block object -- each
+    // iteration, just before evaluating the "update" in for(;;) loops.
+    //
+    // No freshening occurs in `for (const ...;;)` as there's no point: you
+    // can't reassign consts. This is observable through the Debugger API. (The
+    // ES6 spec also skips cloning the environment in this case.)
     bool forLoopRequiresFreshening = false;
     if (ParseNode* init = forHead->pn_kid1) {
-        if (init->isKind(PNK_FRESHENBLOCK)) {
-            // The loop's init declaration was hoisted into an enclosing lexical
-            // scope node.  Note that the block scope must be freshened each
-            // iteration.
-            forLoopRequiresFreshening = true;
-        } else {
-            emittingForInit = true;
-            if (!updateSourceCoordNotes(init->pn_pos.begin))
-                return false;
-            if (!emitTree(init))
-                return false;
-            emittingForInit = false;
-
-            if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) {
-                // 'init' is an expression, not a declaration. emitTree left
-                // its value on the stack.
-                if (!emit1(JSOP_POP))
-                    return false;
-            }
+        forLoopRequiresFreshening = init->isKind(PNK_LET);
+
+        // Emit the `init` clause, whether it's an expression or a variable
+        // declaration. (The loop variables were hoisted into an enclosing
+        // scope, but we still need to emit code for the initializers.)
+        emittingForInit = true;
+        if (!updateSourceCoordNotes(init->pn_pos.begin))
+            return false;
+        if (!emitTree(init))
+            return false;
+        emittingForInit = false;
+
+        if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) {
+            // 'init' is an expression, not a declaration. emitTree left its
+            // value on the stack.
+            if (!emit1(JSOP_POP))
+                return false;
         }
     }
 
     /*
      * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
      * Use tmp to hold the biased srcnote "top" offset, which differs
      * from the top local variable by the length of the JSOP_GOTO
      * emitted in between tmp and top if this loop has a condition.
@@ -5744,17 +5772,17 @@ BytecodeEmitter::emitFor(ParseNode* pn, 
 {
     if (pn->pn_left->isKind(PNK_FORIN))
         return emitForIn(pn, top);
 
     if (pn->pn_left->isKind(PNK_FOROF))
         return emitForOf(StmtType::FOR_OF_LOOP, pn, top);
 
     MOZ_ASSERT(pn->pn_left->isKind(PNK_FORHEAD));
-    return emitNormalFor(pn, top);
+    return emitCStyleFor(pn, top);
 }
 
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -581,17 +581,17 @@ struct BytecodeEmitter
     bool emitSelfHostedCallFunction(ParseNode* pn);
     bool emitSelfHostedResumeGenerator(ParseNode* pn);
     bool emitSelfHostedForceInterpreter(ParseNode* pn);
 
     bool emitDo(ParseNode* pn);
     bool emitFor(ParseNode* pn, ptrdiff_t top);
     bool emitForIn(ParseNode* pn, ptrdiff_t top);
     bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl);
-    bool emitNormalFor(ParseNode* pn, ptrdiff_t top);
+    bool emitCStyleFor(ParseNode* pn, ptrdiff_t top);
     bool emitWhile(ParseNode* pn, ptrdiff_t top);
 
     bool emitBreak(PropertyName* label);
     bool emitContinue(PropertyName* label);
 
     bool emitDefaultsAndDestructuring(ParseNode* pn);
     bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp);
 
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -403,17 +403,16 @@ ContainsHoistedDeclaration(ExclusiveCont
       case PNK_GENEXP:
       case PNK_ARRAYCOMP:
       case PNK_ARGSBODY:
       case PNK_CATCHLIST:
       case PNK_CATCH:
       case PNK_FORIN:
       case PNK_FOROF:
       case PNK_FORHEAD:
-      case PNK_FRESHENBLOCK:
       case PNK_CLASSMETHOD:
       case PNK_CLASSMETHODLIST:
       case PNK_CLASSNAMES:
       case PNK_NEWTARGET:
       case PNK_POSHOLDER:
       case PNK_SUPERCALL:
         MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
                   "some parent node without recurring to test this node");
@@ -1705,17 +1704,16 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_DEBUGGER:
       case PNK_BREAK:
       case PNK_CONTINUE:
       case PNK_TEMPLATE_STRING:
       case PNK_THIS:
       case PNK_GENERATOR:
       case PNK_EXPORT_BATCH_SPEC:
       case PNK_OBJECT_PROPERTY_NAME:
-      case PNK_FRESHENBLOCK:
       case PNK_POSHOLDER:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
         return true;
 
       case PNK_TYPEOFNAME:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
         MOZ_ASSERT(!pn->pn_kid->maybeExpr());
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -566,20 +566,16 @@ class FullParseHandler
 
     ParseNode* newForHead(ParseNodeKind kind, ParseNode* pn1, ParseNode* pn2, ParseNode* pn3,
                           const TokenPos& pos)
     {
         MOZ_ASSERT(kind == PNK_FORIN || kind == PNK_FOROF || kind == PNK_FORHEAD);
         return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos);
     }
 
-    ParseNode* newFreshenBlock(const TokenPos& pos) {
-        return new_<NullaryNode>(PNK_FRESHENBLOCK, pos);
-    }
-
     ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) {
         TokenPos pos(begin, caseList->pn_pos.end);
         return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList);
     }
 
     ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) {
         TokenPos pos(begin, body->pn_pos.end);
         return new_<BinaryNode>(expr ? PNK_CASE : PNK_DEFAULT, JSOP_NOP, pos, expr, body);
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -369,17 +369,16 @@ class NameResolver
           case PNK_THIS:
           case PNK_ELISION:
           case PNK_GENERATOR:
           case PNK_NUMBER:
           case PNK_BREAK:
           case PNK_CONTINUE:
           case PNK_DEBUGGER:
           case PNK_EXPORT_BATCH_SPEC:
-          case PNK_FRESHENBLOCK:
           case PNK_OBJECT_PROPERTY_NAME:
           case PNK_POSHOLDER:
             MOZ_ASSERT(cur->isArity(PN_NULLARY));
             break;
 
           case PNK_TYPEOFNAME:
             MOZ_ASSERT(cur->isArity(PN_UNARY));
             MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME));
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -209,17 +209,16 @@ PushNodeChildren(ParseNode* pn, NodeStac
       case PNK_ELISION:
       case PNK_GENERATOR:
       case PNK_NUMBER:
       case PNK_BREAK:
       case PNK_CONTINUE:
       case PNK_DEBUGGER:
       case PNK_EXPORT_BATCH_SPEC:
       case PNK_OBJECT_PROPERTY_NAME:
-      case PNK_FRESHENBLOCK:
       case PNK_POSHOLDER:
         MOZ_ASSERT(pn->isArity(PN_NULLARY));
         MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
         MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");
         return PushResult::Recyclable;
 
       // Nodes with a single non-null child.
       case PNK_TYPEOFNAME:
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -160,17 +160,16 @@ class PackedScopeCoordinate
     F(EXPORT_FROM) \
     F(EXPORT_DEFAULT) \
     F(EXPORT_SPEC_LIST) \
     F(EXPORT_SPEC) \
     F(EXPORT_BATCH_SPEC) \
     F(FORIN) \
     F(FOROF) \
     F(FORHEAD) \
-    F(FRESHENBLOCK) \
     F(ARGSBODY) \
     F(SPREAD) \
     F(MUTATEPROTO) \
     F(CLASS) \
     F(CLASSMETHOD) \
     F(CLASSMETHODLIST) \
     F(CLASSNAMES) \
     F(NEWTARGET) \
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -4058,17 +4058,18 @@ Parser<FullParseHandler>::pushLetScope(H
     if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid)))
         return null();
 
     return pn;
 }
 
 template <>
 SyntaxParseHandler::Node
-Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt)
+Parser<SyntaxParseHandler>::pushLetScope(HandleStaticBlockObject blockObj,
+                                         AutoPushStmtInfoPC& stmt)
 {
     JS_ALWAYS_FALSE(abortIfSyntaxParser());
     return SyntaxParseHandler::NodeFailure;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling)
@@ -5182,29 +5183,47 @@ Parser<FullParseHandler>::forStatement(Y
                 if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH))
                     return null();
             }
         }
     }
 
     MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
 
-    /*
-     * True if we have 'for (var/let/const ...)'.
-     */
+    // True if we have 'for (var/let/const ...)'.
     bool isForDecl = false;
 
+    // The next three variables are used to implement `for (let/const ...)`.
+    //
+    // We generate an implicit block, wrapping the whole loop, to store loop
+    // variables declared this way. Note that if the loop uses `for (var...)`
+    // instead, those variables go on some existing enclosing scope, so no
+    // implicit block scope is created.
+    //
+    // All three variables remain null/none if the loop is any other form.
+    //
+    // blockObj is the static block object for the implicit block scope.
+    RootedStaticBlockObject blockObj(context);
+
+    // letStmt is the BLOCK StmtInfo for the implicit block.
+    //
+    // Caution: `letStmt.emplace()` creates some Rooted objects. Rooteds must
+    // be created/destroyed in FIFO order. Therefore adding a Rooted in this
+    // function, between this point and the .emplace() call below, would trip
+    // assertions.
+    Maybe<AutoPushStmtInfoPC> letStmt;
+
+    // The PNK_LEXICALSCOPE node containing blockObj's ObjectBox.
+    ParseNode* forLetImpliedBlock = nullptr;
+
     // True if a 'let' token at the head is parsed as an identifier instead of
     // as starting a declaration.
     bool letIsIdentifier = false;
 
-    /* Non-null when isForDecl is true for a 'for (let ...)' statement. */
-    RootedStaticBlockObject blockObj(context);
-
-    /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */
+    // Set to 'x' in 'for (x; ...; ...)' or 'for (x in ...)'.
     ParseNode* pn1;
 
     TokenStream::Modifier modifier = TokenStream::Operand;
     {
         TokenKind tt;
         if (!tokenStream.peekToken(&tt, TokenStream::Operand))
             return null();
         if (tt == TOK_SEMI) {
@@ -5244,19 +5263,29 @@ Parser<FullParseHandler>::forStatement(Y
                     bool constDecl = tt == TOK_CONST;
                     isForDecl = true;
                     blockObj = StaticBlockObject::create(context);
                     if (!blockObj)
                         return null();
 
                     // Initialize the enclosing scope manually for the call to
                     // |variables| below.
+                    blockObj = StaticBlockObject::create(context);
+                    if (!blockObj)
+                        return null();
                     blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
+                    letStmt.emplace(*this, StmtType::BLOCK);
+                    forLetImpliedBlock = pushLetScope(blockObj, *letStmt);
+                    if (!forLetImpliedBlock)
+                        return null();
+                    (*letStmt)->isForLetBlock = true;
+
+                    MOZ_ASSERT(CurrentLexicalStaticBlock(pc) == blockObj);
                     pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
-                                    nullptr, blockObj, DontHoistVars);
+                                    nullptr, blockObj, HoistVars);
                 } else {
                     pn1 = expr(InProhibited, yieldHandling, TripledotProhibited);
                 }
             } else {
                 // Pass |InProhibited| when parsing an expression so that |in|
                 // isn't parsed in a RelationalExpression as a binary operator.
                 // In this context, |in| is part of a for-in loop -- *not* part
                 // of a binary expression.
@@ -5264,70 +5293,20 @@ Parser<FullParseHandler>::forStatement(Y
             }
             if (!pn1)
                 return null();
             modifier = TokenStream::None;
         }
     }
 
     MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST));
-    MOZ_ASSERT(!!blockObj == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST))));
-
-    // If the head of a for-loop declares any lexical variables, we generate an
-    // implicit block to store them. We implement this by desugaring. These:
-    //
-    //     for (let/const <bindings>; <test>; <update>) <stmt>
-    //     for (let <pattern> in <expr>) <stmt>
-    //     for (let <pattern> of <expr>) <stmt>
-    //
-    // transform into roughly the same parse trees as these (using deprecated
-    // let-block syntax):
-    //
-    //     let (<bindings>) { for (; <test>; <update>) <stmt> }
-    //     let (<pattern>) { for (<pattern> in <expr>) <stmt> }
-    //     let (<pattern>) { for (<pattern> of <expr>) <stmt> }
-    //
-    // This desugaring is not ES6 compliant. Initializers in the head of a
-    // let-block are evaluated *outside* the scope of the variables being
-    // initialized. ES6 mandates that they be evaluated in the same scope,
-    // triggering used-before-initialization temporal dead zone errors as
-    // necessary. See bug 1216623 on scoping and bug 1069480 on TDZ.
-    //
-    // Additionally, in ES6, each iteration of a for-loop creates a fresh
-    // binding of the loop variables. For example:
-    //
-    //     var funcs = [];
-    //     for (let i = 0; i < 2; i++)
-    //         funcs.push(function() { return i; });
-    //     assertEq(funcs[0](), 0);  // the two closures capture...
-    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
-    //
-    // These semantics are implemented by "freshening" the implicit block --
-    // changing the scope chain to a fresh clone of the instantaneous block
-    // object -- each iteration, just before evaluating the "update" in
-    // for(;;) loops. We don't implement this freshening for for-in/of loops
-    // yet: bug 449811.
-    //
-    // No freshening occurs in `for (const ...;;)` as there's no point: you
-    // can't reassign consts. This is observable through the Debugger API. (The
-    // ES6 spec also skips cloning the environment in this case.)
-    //
-    // If the for-loop head includes a lexical declaration, then we create an
-    // implicit block scope, and:
-    //
-    //   * forLetImpliedBlock is the node for the implicit block scope.
-    //   * forLetDecl is the node for the decl 'let/const <pattern>'.
-    //
-    // Otherwise both are null.
-    ParseNode* forLetImpliedBlock = nullptr;
-    ParseNode* forLetDecl = nullptr;
+    MOZ_ASSERT(letStmt.isSome() == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST))));
 
     // If there's an |in| keyword here, it's a for-in loop, by dint of careful
     // parsing of |pn1|.
-    Maybe<AutoPushStmtInfoPC> letStmt; /* used if blockObj != nullptr. */
     ParseNode* pn2;      /* forHead->pn_kid2 */
     ParseNode* pn3;      /* forHead->pn_kid3 */
     ParseNodeKind headKind = PNK_FORHEAD;
     if (pn1) {
         bool isForIn, isForOf;
         if (!matchInOrOf(&isForIn, &isForOf))
             return null();
 
@@ -5388,52 +5367,35 @@ Parser<FullParseHandler>::forStatement(Y
                 // loop isn't valid ES6 and has never been permitted in
                 // SpiderMonkey.
                 report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT,
                        headKind == PNK_FOROF ? "of" : "in");
                 return null();
             }
         } else {
             /* Not a declaration. */
-            MOZ_ASSERT(!blockObj);
+            MOZ_ASSERT(!letStmt);
             pn2 = pn1;
             pn1 = nullptr;
 
             if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment))
                 return null();
         }
 
         pn3 = (headKind == PNK_FOROF)
               ? assignExpr(InAllowed, yieldHandling, TripledotProhibited)
               : expr(InAllowed, yieldHandling, TripledotProhibited);
         if (!pn3)
             return null();
         modifier = TokenStream::None;
 
-        if (blockObj) {
-            /*
-             * Now that the pn3 has been parsed, push the let scope. To hold
-             * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node
-             * created by pushLetScope around the for's initializer. This also
-             * serves to indicate the let-decl to the emitter.
-             */
-            letStmt.emplace(*this, StmtType::BLOCK);
-            ParseNode* block = pushLetScope(blockObj, *letStmt);
-            if (!block)
-                return null();
-            (*letStmt)->isForLetBlock = true;
-            block->pn_expr = pn1;
-            block->pn_pos = pn1->pn_pos;
-            pn1 = block;
-        }
-
         if (isForDecl) {
             /*
              * pn2 is part of a declaration. Make a copy that can be passed to
-             * EmitAssignment. Take care to do this after pushLetScope.
+             * BytecodeEmitter::emitAssignment.
              */
             pn2 = cloneLeftHandSide(pn2);
             if (!pn2)
                 return null();
         }
 
         ParseNodeKind kind2 = pn2->getKind();
         MOZ_ASSERT(kind2 != PNK_ASSIGN, "forStatement TOK_ASSIGN");
@@ -5445,57 +5407,23 @@ Parser<FullParseHandler>::forStatement(Y
     } else {
         if (isForEach) {
             reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP);
             return null();
         }
 
         MOZ_ASSERT(headKind == PNK_FORHEAD);
 
-        if (blockObj) {
+        if (letStmt) {
             // Ensure here that the previously-unchecked assignment mandate for
             // const declarations holds.
             if (!checkForHeadConstInitializers(pn1)) {
                 report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL);
                 return null();
             }
-
-            // Desugar
-            //
-            //   for (let INIT; TEST; UPDATE) STMT
-            //
-            // into
-            //
-            //   let (INIT) { for (; TEST; UPDATE) STMT }
-            //
-            // to provide a block scope for INIT.
-            letStmt.emplace(*this, StmtType::BLOCK);
-            forLetImpliedBlock = pushLetScope(blockObj, *letStmt);
-            if (!forLetImpliedBlock)
-                return null();
-            (*letStmt)->isForLetBlock = true;
-
-            forLetDecl = pn1;
-
-            // The above transformation isn't enough to implement |INIT|
-            // scoping, because each loop iteration must see separate bindings
-            // of |INIT|.  We handle this by replacing the block on the scope
-            // chain with a new block, copying the old one's contents, each
-            // iteration.  We supply a special PNK_FRESHENBLOCK node as the
-            // |let INIT| node for |for(let INIT;;)| loop heads to distinguish
-            // such nodes from *actual*, non-desugared use of the above syntax.
-            // (We don't do this for PNK_CONST nodes because the spec says no
-            // freshening happens -- observable with the Debugger API.)
-            if (pn1->isKind(PNK_CONST)) {
-                pn1 = nullptr;
-            } else {
-                pn1 = handler.newFreshenBlock(pn1->pn_pos);
-                if (!pn1)
-                    return null();
-            }
         }
 
         /* Parse the loop condition or null into pn2. */
         MUST_MATCH_TOKEN_MOD(TOK_SEMI, modifier, JSMSG_SEMI_AFTER_FOR_INIT);
         TokenKind tt;
         if (!tokenStream.peekToken(&tt, TokenStream::Operand))
             return null();
         if (tt == TOK_SEMI) {
@@ -5537,17 +5465,17 @@ Parser<FullParseHandler>::forStatement(Y
 
     ParseNode* forLoop = handler.newForStatement(begin, forHead, body, iflags);
     if (!forLoop)
         return null();
 
     if (forLetImpliedBlock) {
         forLetImpliedBlock->pn_expr = forLoop;
         forLetImpliedBlock->pn_pos = forLoop->pn_pos;
-        return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos);
+        return forLetImpliedBlock;
     }
     return forLoop;
 }
 
 template <>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler>::forStatement(YieldHandling yieldHandling)
 {
@@ -8288,16 +8216,17 @@ Parser<ParseHandler>::comprehensionFor(G
     TokenPos headPos(begin, pos().end);
 
     AutoPushStmtInfoPC stmtInfo(*this, StmtType::BLOCK);
     BindData<ParseHandler> data(context);
 
     RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context));
     if (!blockObj)
         return null();
+
     // Initialize the enclosing scope manually for the call to |bind|
     // below, which is before the call to |pushLetScope|.
     blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
 
     data.initLexical(DontHoistVars, JSOP_DEFLET, blockObj, JSMSG_TOO_MANY_LOCALS);
     Node decls = handler.newList(PNK_LET, lhs);
     if (!decls)
         return null();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/bug1219954.js
@@ -0,0 +1,12 @@
+"use strict";
+
+if (!('oomTest' in this))
+    quit();
+
+let g = (function() {
+    "use asm";
+    function f() {}
+    return f;
+})();
+
+oomTest(() => "" + g);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1219363.js
@@ -0,0 +1,9 @@
+var x = [1, 2, , 4]
+x[100000] = 1;
+var y = Object.create(x);
+y.a = 1;
+y.b = 1;
+var arr = [];
+for (var z in y)
+    arr.push(z);
+assertEq(arr.join(), "a,b,0,1,3,100000");
--- a/js/src/jit-test/tests/basic/bug646968-3.js
+++ b/js/src/jit-test/tests/basic/bug646968-3.js
@@ -1,16 +1,16 @@
-var s, x = 0;
+var s, v = "NOPE";
 
 s = '';
-for (let x = x; x < 3; x++)
+for (let v = 0, x = v; x < 3; x++)
     s += x;
 assertEq(s, '012');
 
 s = '';
-for (let x = eval('x'); x < 3; x++)
+for (let v = 0, x = eval('v'); x < 3; x++)
     s += x;
 assertEq(s, '012');
 
 s = ''
-for (let x = function () { with ({}) return x; }(); x < 3; x++)
+for (let v = 0, x = function () { with ({}) return v; }(); x < 3; x++)
     s += x;
 assertEq(s, '012');
--- a/js/src/jit-test/tests/basic/bug646968-4.js
+++ b/js/src/jit-test/tests/basic/bug646968-4.js
@@ -1,4 +1,8 @@
-var s = '', x = {a: 1, b: 2, c: 3};
+// Scoping: `x` in the head of a `for (let x...)` loop refers to the loop variable.
+
+// For now, this means it evaluates to undefined. It ought to throw a
+// ReferenceError instead, but the TDZ isn't implemented here (bug 1069480).
+var s = "", x = {a: 1, b: 2, c: 3};
 for (let x in eval('x'))
     s += x;
-assertEq(s, 'abc');
+assertEq(s, "");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug646968-6.js
@@ -0,0 +1,16 @@
+// In `for (let x = EXPR; ;)`, if `x` appears within EXPR, it refers to the
+// loop variable. Actually doing this is typically a TDZ error.
+
+load(libdir + "asserts.js");
+
+assertThrowsInstanceOf(() => {
+    for (let x = x; null.foo; null.foo++) {}
+}, ReferenceError);
+
+assertThrowsInstanceOf(() => {
+    for (let x = eval('x'); null.foo; null.foo++) {}
+}, ReferenceError);
+
+assertThrowsInstanceOf(() => {
+    for (let x = function () { with ({}) return x; }(); null.foo; null.foo++) {}
+}, ReferenceError);
deleted file mode 100644
--- a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var x = "foobar";
-{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); }
-
-var x = "foobar";
-{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); }
--- a/js/src/jit-test/tests/basic/testLet.js
+++ b/js/src/jit-test/tests/basic/testLet.js
@@ -1,14 +1,15 @@
 var otherGlobal = newGlobal();
 
 function test(str, arg, result)
 {
     arg = arg || 'ponies';
-    result = result || 'ponies';
+    if (arguments.length < 3)
+        result = 'ponies';
 
     var fun = new Function('x', str);
 
     var got = fun.toSource();
     var expect = '(function anonymous(x) {\n' + str + '\n})';
     if (got !== expect) {
         print("GOT:    " + got);
         print("EXPECT: " + expect);
@@ -131,33 +132,27 @@ test('this.y = x;if (x) {let y = 1;retur
 
 // for(;;)
 test('for (;;) {return x;}');
 test('for (let y = 1;;) {return x;}');
 test('for (let y = 1;; ++y) {return x;}');
 test('for (let y = 1; ++y;) {return x;}');
 test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}');
 test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1);
-test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6);
-test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
-test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
+test('var sum = 0;for (let x = 1; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
 test('for (var y = 1;;) {return x;}');
 test('for (var y = 1;; ++y) {return x;}');
 test('for (var y = 1; ++y;) {return x;}');
 test('for (var X = 1, [y, z] = x, a = x; z < 4; ++z) {return X + y;}', [2,3], 3);
 test('var sum = 0;for (var y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x, y = 10; X < 4; ++X) {sum += X;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1);
 test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6);
 test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
 test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
-test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}');
-test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie');
 test('for (let y = x;;) {let x;return y;}');
 test('for (let y = x;;) {let y;return x;}');
 test('for (let y;;) {let y;return x;}');
 test('for (let a = x;;) {let c = x, d = x;return c;}');
 test('for (let [a, b] = x;;) {let c = x, d = x;return c;}');
 test('for (let [a] = (1, [x]);;) {return a;}');
 test('for (let [a] = (1, x, 1, x);;) {return a;}', ['ponies']);
 isParseError('for (let x = 1, x = 2;;) {}');
@@ -165,38 +160,41 @@ isParseError('for (let [x, y] = a, {a:x}
 isParseError('for (let [x, y, x] = a;;) {}');
 isParseError('for (let [x, [y, [x]]] = a;;) {}');
 
 // for(in)
 test('for (let i in x) {return x;}');
 test('for (let i in x) {let y;return x;}');
 test('for each (let [a, b] in x) {let y;return x;}');
 test('for (let i in x) {let i = x;return i;}');
-test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]);
-test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]);
 test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011');
 test('var res = "";for (let i in x) {res += x[i];}return res;');
 test('var res = "";for (var i in x) {res += x[i];}return res;');
-test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}');
-test('for (let x in eval("x")) {return x;}', {ponies:true});
-test('for (let x in x) {return eval("x");}', {ponies:true});
-test('for (let x in eval("x")) {return eval("x");}', {ponies:true});
+isParseError('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}');
 test('for (let i in x) {break;}return x;');
 test('for (let i in x) {break;}return eval("x");');
-test('for (let x in x) {break;}return x;');
-test('for (let x in x) {break;}return eval("x");');
 test('a:for (let i in x) {for (let j in x) {break a;}}return x;');
 test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");');
 test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true});
-test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}');
-test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']);
 isParseError('for (let [x, x] in o) {}');
 isParseError('for (let [x, y, x] in o) {}');
 isParseError('for (let [x, [y, [x]]] in o) {}');
 
+// for(let ... in ...) scoping bugs (bug 1069480)
+test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']], undefined);
+test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']], undefined);
+test('for (let x in eval("x")) {return x;}', {ponies:true}, undefined);
+test('for (let x in x) {return eval("x");}', {ponies:true}, undefined);
+test('for (let x in eval("x")) {return eval("x");}', {ponies:true}, undefined);
+test('for (let x in x) {break;}return x;');
+test('for (let x in x) {break;}return eval("x");');
+test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}', undefined, undefined);
+test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies'], undefined);
+test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}', undefined, undefined);
+
 // genexps
 test('return (i for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in x)).next();', {ponies:true});
 test('return (eval("i") for (i in eval("x"))).next();', {ponies:true});
 test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true});
 
 // array comprehension
 test('return [i for (i in x)][0];', {ponies:true});
@@ -217,11 +215,18 @@ isReferenceError('inner(); let x; functi
 isReferenceError('inner(); let x; function inner() { function innerer() { x++; } innerer(); }');
 isReferenceError('let x; var inner = function () { y++; }; inner(); let y;');
 isReferenceError('let x = x;');
 isReferenceError('let [x] = [x];');
 isReferenceError('let {x} = {x:x};');
 isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
 isReferenceError('let x = function() {} ? x() : function() {}');
 isReferenceError('(function() { let x = (function() { return x }()); }())');
+isReferenceError('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;');
+isReferenceError('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;');
+isReferenceError('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;');
+isReferenceError('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;');
+isReferenceError('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;');
+isReferenceError('for (let x = eval("throw x");;) {}');
+isReferenceError('for (let x = x + "s"; eval("throw x");) {}');
 
 // redecl with function statements
 isParseError('let a; function a() {}');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug-1219408.js
@@ -0,0 +1,2 @@
+// |jit-test| error: Error
+parseModule("").evaluation();
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -9295,59 +9295,16 @@ CodeGenerator::visitAtomicIsLockFree(LAt
     masm.branch32(Assembler::Equal, value, Imm32(1), &Ldone);
     if (!AtomicOperations::isLockfree8())
         masm.bind(&Lfailed);
     masm.move32(Imm32(0), output);
     masm.bind(&Ldone);
 }
 
 void
-CodeGenerator::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir)
-{
-    Register elements = ToRegister(lir->elements());
-    AnyRegister output = ToAnyRegister(lir->output());
-    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
-
-    Register oldval = ToRegister(lir->oldval());
-    Register newval = ToRegister(lir->newval());
-
-    Scalar::Type arrayType = lir->mir()->arrayType();
-    int width = Scalar::byteSize(arrayType);
-
-    if (lir->index()->isConstant()) {
-        Address dest(elements, ToInt32(lir->index()) * width);
-        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
-    } else {
-        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
-        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
-    }
-}
-
-void
-CodeGenerator::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir)
-{
-    Register elements = ToRegister(lir->elements());
-    AnyRegister output = ToAnyRegister(lir->output());
-    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
-
-    Register value = ToRegister(lir->value());
-
-    Scalar::Type arrayType = lir->mir()->arrayType();
-    int width = Scalar::byteSize(arrayType);
-
-    if (lir->index()->isConstant()) {
-        Address dest(elements, ToInt32(lir->index()) * width);
-        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
-    } else {
-        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
-        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
-    }
-}
-
-void
 CodeGenerator::visitClampIToUint8(LClampIToUint8* lir)
 {
     Register output = ToRegister(lir->output());
     MOZ_ASSERT(output == ToRegister(lir->input()));
     masm.clampIntToUint8(output);
 }
 
 void
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -281,18 +281,16 @@ class CodeGenerator : public CodeGenerat
     void visitArrayConcat(LArrayConcat* lir);
     void visitArraySlice(LArraySlice* lir);
     void visitArrayJoin(LArrayJoin* lir);
     void visitLoadUnboxedScalar(LLoadUnboxedScalar* lir);
     void visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole* lir);
     void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir);
     void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir);
     void visitAtomicIsLockFree(LAtomicIsLockFree* lir);
-    void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
-    void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
     void visitClampIToUint8(LClampIToUint8* lir);
     void visitClampDToUint8(LClampDToUint8* lir);
     void visitClampVToUint8(LClampVToUint8* lir);
     void visitCallIteratorStart(LCallIteratorStart* lir);
     void visitIteratorStart(LIteratorStart* lir);
     void visitIteratorMore(LIteratorMore* lir);
     void visitIsNoIterAndBranch(LIsNoIterAndBranch* lir);
     void visitIteratorEnd(LIteratorEnd* lir);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -11864,32 +11864,28 @@ IonBuilder::tryInnerizeWindow(MDefinitio
 }
 
 bool
 IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* name,
                                TemporaryTypeSet* types)
 {
     // See the comment in tryInnerizeWindow for how this works.
 
+    // Note that it's important that we do this _before_ we'd try to
+    // do the optimizations below on obj normally, since some of those
+    // optimizations have fallback paths that are slower than the path
+    // we'd produce here.
+
     MOZ_ASSERT(*emitted == false);
 
     MDefinition* inner = tryInnerizeWindow(obj);
     if (inner == obj)
         return true;
 
     if (!forceInlineCaches()) {
-        // Note: the Baseline ICs don't know about this optimization, so it's
-        // possible the global property's HeapTypeSet has not been initialized
-        // yet. In this case we'll fall back to getPropTryCache for now.
-
-        // Note that it's important that we do this _before_ we'd try to
-        // do the optimizations below on obj normally, since some of those
-        // optimizations have fallback paths that are slower than the path
-        // we'd produce here.
-
         trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
         if (!getPropTryConstant(emitted, inner, NameToId(name), types) || *emitted)
             return *emitted;
 
         trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName);
         if (!getStaticName(&script()->global(), name, emitted) || *emitted)
             return *emitted;
 
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -441,105 +441,16 @@ MacroAssembler::loadFromTypedArray(Scala
     }
 }
 
 template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const Address& src, const ValueOperand& dest,
                                                  bool allowDouble, Register temp, Label* fail);
 template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest,
                                                  bool allowDouble, Register temp, Label* fail);
 
-template<typename T>
-void
-MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
-                                               Register oldval, Register newval,
-                                               Register temp, AnyRegister output)
-{
-    switch (arrayType) {
-      case Scalar::Int8:
-        compareExchange8SignExtend(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Uint8:
-        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Uint8Clamped:
-        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Int16:
-        compareExchange16SignExtend(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Uint16:
-        compareExchange16ZeroExtend(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Int32:
-        compareExchange32(mem, oldval, newval, output.gpr());
-        break;
-      case Scalar::Uint32:
-        // At the moment, the code in MCallOptimize.cpp requires the output
-        // type to be double for uint32 arrays.  See bug 1077305.
-        MOZ_ASSERT(output.isFloat());
-        compareExchange32(mem, oldval, newval, temp);
-        convertUInt32ToDouble(temp, output.fpu());
-        break;
-      default:
-        MOZ_CRASH("Invalid typed array type");
-    }
-}
-
-template void
-MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
-                                               Register oldval, Register newval, Register temp,
-                                               AnyRegister output);
-template void
-MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
-                                               Register oldval, Register newval, Register temp,
-                                               AnyRegister output);
-
-template<typename T>
-void
-MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
-                                              Register value, Register temp, AnyRegister output)
-{
-    switch (arrayType) {
-      case Scalar::Int8:
-        atomicExchange8SignExtend(mem, value, output.gpr());
-        break;
-      case Scalar::Uint8:
-        atomicExchange8ZeroExtend(mem, value, output.gpr());
-        break;
-      case Scalar::Uint8Clamped:
-        atomicExchange8ZeroExtend(mem, value, output.gpr());
-        break;
-      case Scalar::Int16:
-        atomicExchange16SignExtend(mem, value, output.gpr());
-        break;
-      case Scalar::Uint16:
-        atomicExchange16ZeroExtend(mem, value, output.gpr());
-        break;
-      case Scalar::Int32:
-        atomicExchange32(mem, value, output.gpr());
-        break;
-      case Scalar::Uint32:
-        // At the moment, the code in MCallOptimize.cpp requires the output
-        // type to be double for uint32 arrays.  See bug 1077305.
-        MOZ_ASSERT(output.isFloat());
-        atomicExchange32(mem, value, temp);
-        convertUInt32ToDouble(temp, output.fpu());
-        break;
-      default:
-        MOZ_CRASH("Invalid typed array type");
-    }
-}
-
-template void
-MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
-                                              Register value, Register temp, AnyRegister output);
-template void
-MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
-                                              Register value, Register temp, AnyRegister output);
-
 template <typename T>
 void
 MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output)
 {
     switch (type) {
       case JSVAL_TYPE_INT32: {
           // Handle loading an int32 into a double reg.
           if (output.type() == MIRType_Double) {
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1069,24 +1069,16 @@ class MacroAssembler : public MacroAssem
           case Scalar::Uint32:
             store32(value, dest);
             break;
           default:
             MOZ_CRASH("Invalid typed array type");
         }
     }
 
-    template<typename T>
-    void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval,
-                                        Register temp, AnyRegister output);
-
-    template<typename T>
-    void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value,
-                                       Register temp, AnyRegister output);
-
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest,
                                 unsigned numElems = 0);
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
                                 unsigned numElems = 0);
 
     // Load a property from an UnboxedPlainObject or UnboxedArrayObject.
     template <typename T>
     void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -532,17 +532,17 @@ NewStringObject(JSContext* cx, HandleStr
 {
     return StringObject::create(cx, str);
 }
 
 bool
 OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out)
 {
     RootedId id(cx);
-    return ValueToId<CanGC>(cx, key, &id) &&
+    return ToPropertyKey(cx, key, &id) &&
            HasProperty(cx, obj, id, out);
 }
 
 bool
 OperatorInI(JSContext* cx, uint32_t index, HandleObject obj, bool* out)
 {
     RootedValue key(cx, Int32Value(index));
     return OperatorIn(cx, key, obj, out);
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1685,16 +1685,58 @@ CodeGeneratorARM::visitLoadTypedArrayEle
 }
 
 void
 CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins)
 {
     MOZ_CRASH("NYI");
 }
 
+void
+CodeGeneratorARM::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register oldval = ToRegister(lir->oldval());
+    Register newval = ToRegister(lir->newval());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    }
+}
+
+void
+CodeGeneratorARM::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register value = ToRegister(lir->value());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    }
+}
 
 template<typename S, typename T>
 void
 CodeGeneratorARM::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType,
                                              const S& value, const T& mem, Register flagTemp,
                                              Register outTemp, AnyRegister output)
 {
     MOZ_ASSERT(flagTemp != InvalidReg);
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -191,16 +191,18 @@ class CodeGeneratorARM : public CodeGene
 
     void visitNegI(LNegI* lir);
     void visitNegD(LNegD* lir);
     void visitNegF(LNegF* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
     void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
+    void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
+    void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSCompareExchangeCallout(LAsmJSCompareExchangeCallout* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicExchangeCallout(LAsmJSAtomicExchangeCallout* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4822,16 +4822,105 @@ js::jit::MacroAssemblerARMCompat::atomic
                                                  const BaseIndex& mem, Register flagTemp);
 template void
 js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value,
                                                  const Address& mem, Register flagTemp);
 template void
 js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value,
                                                  const BaseIndex& mem, Register flagTemp);
 
+template<typename T>
+void
+MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                        Register oldval, Register newval,
+                                                        Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        compareExchange8SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int16:
+        compareExchange16SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint16:
+        compareExchange16ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int32:
+        compareExchange32(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        compareExchange32(mem, oldval, newval, temp);
+        convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                        Register oldval, Register newval, Register temp,
+                                                        AnyRegister output);
+template void
+MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                        Register oldval, Register newval, Register temp,
+                                                        AnyRegister output);
+
+template<typename T>
+void
+MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                       Register value, Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        atomicExchange8SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int16:
+        atomicExchange16SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint16:
+        atomicExchange16ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int32:
+        atomicExchange32(mem, value, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        atomicExchange32(mem, value, temp);
+        convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                       Register value, Register temp, AnyRegister output);
+template void
+MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                       Register value, Register temp, AnyRegister output);
+
 void
 MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch)
 {
     AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation());
     loadPtr(activation, scratch);
     storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame()));
     storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite()));
 }
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1611,16 +1611,24 @@ class MacroAssemblerARMCompat : public M
     void atomicXor16(const S& value, const T& mem, Register flagTemp) {
         atomicEffectOp(2, AtomicFetchXorOp, value, mem, flagTemp);
     }
     template <typename T, typename S>
     void atomicXor32(const S& value, const T& mem, Register flagTemp) {
         atomicEffectOp(4, AtomicFetchXorOp, value, mem, flagTemp);
     }
 
+    template<typename T>
+    void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval,
+                                        Register temp, AnyRegister output);
+
+    template<typename T>
+    void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value,
+                                       Register temp, AnyRegister output);
+
     void clampIntToUint8(Register reg) {
         // Look at (reg >> 8) if it is 0, then reg shouldn't be clamped if it is
         // <0, then we want to clamp to 0, otherwise, we wish to clamp to 255
         ScratchRegisterScope scratch(asMasm());
         as_mov(scratch, asr(reg, 8), SetCC);
         ma_mov(Imm32(0xff), reg, LeaveCC, NotEqual);
         ma_mov(Imm32(0), reg, LeaveCC, Signed);
     }
--- a/js/src/jit/arm64/CodeGenerator-arm64.cpp
+++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp
@@ -732,8 +732,52 @@ CodeGeneratorARM64::setReturnDoubleRegs(
 {
     MOZ_ASSERT(ReturnFloat32Reg.code_ == FloatRegisters::s0);
     MOZ_ASSERT(ReturnDoubleReg.code_ == FloatRegisters::d0);
     FloatRegister s1 = {FloatRegisters::s1, FloatRegisters::Single};
     regs->add(ReturnFloat32Reg);
     regs->add(s1);
     regs->add(ReturnDoubleReg);
 }
+
+void
+CodeGeneratorARM64::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register oldval = ToRegister(lir->oldval());
+    Register newval = ToRegister(lir->newval());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    }
+}
+
+void
+CodeGeneratorARM64::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register value = ToRegister(lir->value());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    }
+}
+
--- a/js/src/jit/arm64/CodeGenerator-arm64.h
+++ b/js/src/jit/arm64/CodeGenerator-arm64.h
@@ -202,16 +202,18 @@ class CodeGeneratorARM64 : public CodeGe
 
     void visitInterruptCheck(LInterruptCheck* lir);
 
     void visitNegI(LNegI* lir);
     void visitNegD(LNegD* lir);
     void visitNegF(LNegF* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
+    void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
+    void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
     void visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins);
     void visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar* ins);
     void visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr* ins);
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -255,16 +255,105 @@ MacroAssemblerCompat::branchValueIsNurse
 
 void
 MacroAssemblerCompat::breakpoint()
 {
     static int code = 0xA77;
     Brk((code++) & 0xffff);
 }
 
+template<typename T>
+void
+MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                     Register oldval, Register newval,
+                                                     Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        compareExchange8SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int16:
+        compareExchange16SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint16:
+        compareExchange16ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int32:
+        compareExchange32(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        compareExchange32(mem, oldval, newval, temp);
+        convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                     Register oldval, Register newval, Register temp,
+                                                     AnyRegister output);
+template void
+MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                     Register oldval, Register newval, Register temp,
+                                                     AnyRegister output);
+
+template<typename T>
+void
+MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                    Register value, Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        atomicExchange8SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int16:
+        atomicExchange16SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint16:
+        atomicExchange16ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int32:
+        atomicExchange32(mem, value, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        atomicExchange32(mem, value, temp);
+        convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                    Register value, Register temp, AnyRegister output);
+template void
+MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                    Register value, Register temp, AnyRegister output);
+
 //{{{ check_macroassembler_style
 // ===============================================================
 // Stack manipulation functions.
 
 void
 MacroAssembler::PushRegsInMask(LiveRegisterSet set)
 {
     for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); ) {
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -2858,16 +2858,24 @@ class MacroAssemblerCompat : public vixl
     void atomicXor16(const S& value, const T& mem) {
         atomicEffectOp(2, AtomicFetchXorOp, value, mem);
     }
     template <typename T, typename S>
     void atomicXor32(const S& value, const T& mem) {
         atomicEffectOp(4, AtomicFetchXorOp, value, mem);
     }
 
+    template<typename T>
+    void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval,
+                                        Register temp, AnyRegister output);
+
+    template<typename T>
+    void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value,
+                                       Register temp, AnyRegister output);
+
     // Emit a BLR or NOP instruction. ToggleCall can be used to patch
     // this instruction.
     CodeOffsetLabel toggledCall(JitCode* target, bool enabled) {
         // The returned offset must be to the first instruction generated,
         // for the debugger to match offset with Baseline's pcMappingEntries_.
         BufferOffset offset = nextOffset();
 
         syncStackPtr();
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -3315,16 +3315,59 @@ CodeGeneratorX86Shared::visitSimdSelect(
             masm.packedRightShiftByScalar(Imm32(31), temp);
     }
 
     masm.bitwiseAndX4(Operand(temp), output);
     masm.bitwiseAndNotX4(Operand(onFalse), temp);
     masm.bitwiseOrX4(Operand(temp), output);
 }
 
+void
+CodeGeneratorX86Shared::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register oldval = ToRegister(lir->oldval());
+    Register newval = ToRegister(lir->newval());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output);
+    }
+}
+
+void
+CodeGeneratorX86Shared::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir)
+{
+    Register elements = ToRegister(lir->elements());
+    AnyRegister output = ToAnyRegister(lir->output());
+    Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp());
+
+    Register value = ToRegister(lir->value());
+
+    Scalar::Type arrayType = lir->mir()->arrayType();
+    int width = Scalar::byteSize(arrayType);
+
+    if (lir->index()->isConstant()) {
+        Address dest(elements, ToInt32(lir->index()) * width);
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    } else {
+        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+        masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output);
+    }
+}
+
 template<typename S, typename T>
 void
 CodeGeneratorX86Shared::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value,
                                                    const T& mem, Register temp1, Register temp2, AnyRegister output)
 {
     // Uint8Clamped is explicitly not supported here
     switch (arrayType) {
       case Scalar::Int8:
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -238,16 +238,18 @@ class CodeGeneratorX86Shared : public Co
     virtual void visitGuardClass(LGuardClass* guard);
     virtual void visitEffectiveAddress(LEffectiveAddress* ins);
     virtual void visitUDivOrMod(LUDivOrMod* ins);
     virtual void visitUDivOrModConstant(LUDivOrModConstant *ins);
     virtual void visitAsmJSPassStackArg(LAsmJSPassStackArg* ins);
     virtual void visitMemoryBarrier(LMemoryBarrier* ins);
     virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
+    virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
+    virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
 
     void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool);
     void visitOffsetBoundsCheck(OffsetBoundsCheck* oolCheck);
 
     void visitNegI(LNegI* lir);
     void visitNegD(LNegD* lir);
     void visitNegF(LNegF* lir);
 
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -138,16 +138,106 @@ MacroAssemblerX86Shared::asMasm()
 }
 
 const MacroAssembler&
 MacroAssemblerX86Shared::asMasm() const
 {
     return *static_cast<const MacroAssembler*>(this);
 }
 
+template<typename T>
+void
+MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                        Register oldval, Register newval,
+                                                        Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        compareExchange8SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int16:
+        compareExchange16SignExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint16:
+        compareExchange16ZeroExtend(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Int32:
+        compareExchange32(mem, oldval, newval, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        compareExchange32(mem, oldval, newval, temp);
+        asMasm().convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                        Register oldval, Register newval, Register temp,
+                                                        AnyRegister output);
+template void
+MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                        Register oldval, Register newval, Register temp,
+                                                        AnyRegister output);
+
+template<typename T>
+void
+MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
+                                                       Register value, Register temp, AnyRegister output)
+{
+    switch (arrayType) {
+      case Scalar::Int8:
+        atomicExchange8SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint8Clamped:
+        atomicExchange8ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int16:
+        atomicExchange16SignExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Uint16:
+        atomicExchange16ZeroExtend(mem, value, output.gpr());
+        break;
+      case Scalar::Int32:
+        atomicExchange32(mem, value, output.gpr());
+        break;
+      case Scalar::Uint32:
+        // At the moment, the code in MCallOptimize.cpp requires the output
+        // type to be double for uint32 arrays.  See bug 1077305.
+        MOZ_ASSERT(output.isFloat());
+        atomicExchange32(mem, value, temp);
+        asMasm().convertUInt32ToDouble(temp, output.fpu());
+        break;
+      default:
+        MOZ_CRASH("Invalid typed array type");
+    }
+}
+
+template void
+MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
+                                                       Register value, Register temp, AnyRegister output);
+template void
+MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
+                                                       Register value, Register temp, AnyRegister output);
+
+
 //{{{ check_macroassembler_style
 // ===============================================================
 // Stack manipulation functions.
 
 void
 MacroAssembler::PushRegsInMask(LiveRegisterSet set)
 {
     FloatRegisterSet fpuSet(set.fpus().reduceSetForPush());
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h
@@ -1450,16 +1450,24 @@ class MacroAssemblerX86Shared : public A
     CodeOffsetLabel labelForPatch() {
         return CodeOffsetLabel(size());
     }
 
     void abiret() {
         ret();
     }
 
+    template<typename T>
+    void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval,
+                                        Register temp, AnyRegister output);
+
+    template<typename T>
+    void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value,
+                                       Register temp, AnyRegister output);
+
   protected:
     bool buildOOLFakeExitFrame(void* fakeReturnAddr);
 };
 
 template <> inline void
 MacroAssemblerX86Shared::loadAlignedVector<int32_t>(const Address& src, FloatRegister dest) {
     loadAlignedInt32x4(src, dest);
 }
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -176,16 +176,17 @@ static bool
 EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
                           AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
 {
     bool enumerateSymbols;
     if (flags & JSITER_SYMBOLSONLY) {
         enumerateSymbols = true;
     } else {
         /* Collect any dense elements from this object. */
+        size_t firstElemIndex = props->length();
         size_t initlen = pobj->getDenseInitializedLength();
         const Value* vp = pobj->getDenseElements();
         bool hasHoles = false;
         for (size_t i = 0; i < initlen; ++i, ++vp) {
             if (vp->isMagic(JS_ELEMENTS_HOLE)) {
                 hasHoles = true;
             } else {
                 /* Dense arrays never get so large that i would not fit into an integer id. */
@@ -201,34 +202,35 @@ EnumerateNativeProperties(JSContext* cx,
                 if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
                     return false;
             }
         }
 
         // Collect any sparse elements from this object.
         bool isIndexed = pobj->isIndexed();
         if (isIndexed) {
-            size_t numElements = props->length();
+            // If the dense elements didn't have holes, we don't need to include
+            // them in the sort.
+            if (!hasHoles)
+                firstElemIndex = props->length();
 
             for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
                 Shape& shape = r.front();
                 jsid id = shape.propid();
                 uint32_t dummy;
                 if (IdIsIndex(id, &dummy)) {
                     if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
                         return false;
                 }
             }
 
-            // If the dense elements didn't have holes, we don't need to include
-            // them in the sort.
-            size_t startIndex = hasHoles ? 0 : numElements;
+            MOZ_ASSERT(firstElemIndex <= props->length());
 
-            jsid* ids = props->begin() + startIndex;
-            size_t n = props->length() - startIndex;
+            jsid* ids = props->begin() + firstElemIndex;
+            size_t n = props->length() - firstElemIndex;
 
             AutoIdVector tmp(cx);
             if (!tmp.resize(n))
                 return false;
             PodCopy(tmp.begin(), ids, n);
 
             if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds))
                 return false;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Expressions/ToPropertyKey-symbols.js
@@ -0,0 +1,94 @@
+var symbols = [
+    Symbol(), Symbol("iterator"), Symbol.for("iterator"), Symbol.iterator
+];
+
+for (var sym of symbols) {
+    var key = {
+        toString() { return sym; }
+    };
+
+    // Test that ToPropertyKey can return a symbol in each of the following
+    // contexts.
+
+    // Computed property names.
+    var obj = {[key]: 13};
+    var found = Reflect.ownKeys(obj);
+    assertEq(found.length, 1);
+    assertEq(found[0], sym);
+
+    // Computed accessor property names.
+    var obj2 = {
+        get [key]() { return "got"; },
+        set [key](v) { this.v = v; }
+    };
+    assertEq(obj2[sym], "got");
+    obj2[sym] = 33;
+    assertEq(obj2.v, 33);
+
+    // Getting and setting properties.
+    assertEq(obj[key], 13);
+    obj[key] = 19;
+    assertEq(obj[sym], 19);
+    (function () { "use strict"; obj[key] = 20; })();
+    assertEq(obj[sym], 20);
+    obj[key]++;
+    assertEq(obj[sym], 21);
+
+    // Getting properties of primitive values.
+    Number.prototype[sym] = "success";
+    assertEq(Math.PI[key], "success");
+    delete Number.prototype[sym];
+
+    if (classesEnabled()) {
+        eval(`
+            // Getting a super property.
+            class X {
+                [sym]() { return "X"; }
+            }
+            class Y extends X {
+                [sym]() { return super[key]() + "Y"; }
+            }
+            var y = new Y();
+            assertEq(y[sym](), "XY");
+
+            // Setting a super property.
+            class Z {
+                set [sym](v) {
+                    this.self = this;
+                    this.value = v;
+                }
+            }
+            class W extends Z {
+                set [sym](v) {
+                    this.isW = true;
+                    super[key] = v;
+                }
+            }
+            var w = new W();
+            w[key] = "ok";
+            assertEq(w.self, w);
+            assertEq(w.value, "ok");
+            assertEq(w.isW, true);
+        `);
+    }
+
+    // Deleting properties.
+    obj = {[sym]: 1};
+    assertEq(delete obj[key], true);
+    assertEq(sym in obj, false);
+
+    // LHS of `in` expressions.
+    assertEq(key in {iterator: 0}, false);
+    assertEq(key in {[sym]: 0}, true);
+
+    // Methods of Object and Object.prototype
+    obj = {};
+    Object.defineProperty(obj, key, {value: "ok", enumerable: true});
+    assertEq(obj[sym], "ok");
+    assertEq(obj.hasOwnProperty(key), true);
+    assertEq(obj.propertyIsEnumerable(key), true);
+    var desc = Object.getOwnPropertyDescriptor(obj, key);
+    assertEq(desc.value, "ok");
+}
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js
@@ -0,0 +1,19 @@
+// Scoping in the head of for(let;;) statements.
+
+let x = 0;
+for (let i = 0, a = () => i; i < 4; i++) {
+  assertEq(i, x++);
+  assertEq(a(), 0);
+}
+assertEq(x, 4);
+
+x = 11;
+let q = 0;
+for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) {
+  assertEq(r, x++);
+  assertEq(s(), 11);
+}
+assertEq(x, 13);
+assertEq(q, 1);
+
+reportCompare(0, 0);
--- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
+++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
@@ -73,21 +73,17 @@ assertEq(funcs[6](), 6);
 assertEq(funcs[7](), 7);
 assertEq(funcs[8](), 8);
 assertEq(funcs[9](), 9);
 
 var outer = "OUTER V IGNORE";
 var save;
 for (let outer = (save = function() { return outer; }); ; )
   break;
-assertEq(save(), "OUTER V IGNORE",
-         "this is actually a bug: fix for(;;) loops to evaluate init RHSes " +
-         "in the block scope containing all the LHS bindings!");
-
-
+assertEq(save(), save);
 
 var funcs = [];
 function t(i, name, expect)
 {
   assertEq(funcs[i].name, name);
   assertEq(funcs[i](), expect);
 }
 
--- a/js/src/tests/ecma_6/Reflect/propertyKeys.js
+++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js
@@ -39,16 +39,22 @@ var keys = [
         },
         expected: "fallback"
     },
     {
         value: {
             [Symbol.toPrimitive](hint) { return hint; }
         },
         expected: "string"
+    },
+    {
+        value: {
+            [Symbol.toPrimitive](hint) { return Symbol.for(hint); }
+        },
+        expected: Symbol.for("string")
     }
 ];
 
 for (var {value, expected} of keys) {
     if (expected === undefined)
         expected = value;
 
     var obj = {};
--- a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js
+++ b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js
@@ -18,19 +18,19 @@ function testVarPatternCombinations(make
         assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i]));
 
         assertDecl("const " + constSrcs[i].join(",") + ";", constDecl(constPatts[i]));
 
         // variable declarations in for-loop heads
         assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);",
                    forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
         assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);",
-                   letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt)));
+                   forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
         assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);",
-                   letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt)));
+                   forStmt(constDecl(constPatts[i]), ident("foo"), ident("bar"), emptyStmt));
     }
 }
 
 testVarPatternCombinations(n => ("{a" + n + ":x" + n + "," + "b" + n + ":y" + n + "," + "c" + n + ":z" + n + "} = 0"),
                            n => ({ id: objPatt([assignProp("a" + n, ident("x" + n)),
                                                 assignProp("b" + n, ident("y" + n)),
                                                 assignProp("c" + n, ident("z" + n))]),
                                    init: lit(0) }));
--- a/js/src/vm/DateTime.cpp
+++ b/js/src/vm/DateTime.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * 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 "vm/DateTime.h"
 
+#include "mozilla/Atomics.h"
+
 #include <time.h>
 
 #include "jsutil.h"
 
 #include "js/Date.h"
 #if ENABLE_INTL_API
 #include "unicode/timezone.h"
 #endif
@@ -18,16 +20,19 @@
 using mozilla::UnspecifiedNaN;
 
 /* static */ js::DateTimeInfo
 js::DateTimeInfo::instance;
 
 /* static */ mozilla::Atomic<bool, mozilla::ReleaseAcquire>
 js::DateTimeInfo::AcquireLock::spinLock;
 
+/* extern */ mozilla::Atomic<js::IcuTimeZoneStatus, mozilla::ReleaseAcquire>
+js::DefaultTimeZoneStatus;
+
 static bool
 ComputeLocalTime(time_t local, struct tm* ptm)
 {
 #if defined(_WIN32)
     return localtime_s(ptm, &local) == 0;
 #elif defined(HAVE_LOCALTIME_R)
     return localtime_r(&local, ptm);
 #else
@@ -307,12 +312,16 @@ js::DateTimeInfo::sanityCheck()
                   rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT);
 }
 
 JS_PUBLIC_API(void)
 JS::ResetTimeZone()
 {
     js::DateTimeInfo::updateTimeZoneAdjustment();
 
-#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
-    icu::TimeZone::recreateDefault();
-#endif
+    // Trigger lazy recreation of ICU's default time zone, if needed and not
+    // already being performed.  (If it's already being performed, behavior
+    // will be safe but racy.)  See also Intl.cpp:NewUDateFormat which performs
+    // the recreation.  Note that if new places observing ICU's default time
+    // zone are added, they'll need to do the same things NewUDateFormat does.
+    js::DefaultTimeZoneStatus.compareExchange(js::IcuTimeZoneStatus::Valid,
+                                              js::IcuTimeZoneStatus::NeedsUpdate);
 }
--- a/js/src/vm/DateTime.h
+++ b/js/src/vm/DateTime.h
@@ -195,11 +195,37 @@ class DateTimeInfo
     static const int64_t RangeExpansionAmount = 30 * SecondsPerDay;
 
     int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds);
     void internalUpdateTimeZoneAdjustment();
 
     void sanityCheck();
 };
 
+enum class IcuTimeZoneStatus : uint32_t
+{
+    // ICU's current default time zone is accurate.
+    Valid = 0,
+
+    // ICU's current default time zone may not be consistent with
+    // DateTimeInfo::localTZA().
+    NeedsUpdate,
+
+    // We're in the middle of recreating ICU's default time zone.
+    Updating
+};
+
+// The user's current time zone adjustment and time zone are computed in two
+// places: via DateTimeInfo::localTZA(), and via ICU.  The mechanisms are,
+// unfortunately, separate: both must be triggered to respond to a time zone
+// change.
+//
+// Updating ICU's default time zone is a relatively slow operation.  If we
+// perform it exactly when we update DateTimeInfo::localTZA(), we regress perf
+// per bug 1220693.  Instead, we defer updating ICU until we actually need the
+// data.  We record needs-update status in this global (necessarily atomic)
+// variable.
+extern mozilla::Atomic<IcuTimeZoneStatus, mozilla::ReleaseAcquire>
+DefaultTimeZoneStatus;
+
 }  /* namespace js */
 
 #endif /* vm_DateTime_h */
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -578,17 +578,17 @@ ToIdOperation(JSContext* cx, HandleScrip
         return true;
     }
 
     JSObject* obj = ToObjectFromStack(cx, objval);
     if (!obj)
         return false;
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, idval, &id))
+    if (!ToPropertyKey(cx, idval, &id))
         return false;
 
     res.set(IdToValue(id));
     return true;
 }
 
 static MOZ_ALWAYS_INLINE bool
 GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleObject receiver,
@@ -603,44 +603,35 @@ GetObjectElementOperation(JSContext* cx,
             if (GetElementNoGC(cx, obj, receiver, index, res.address()))
                 break;
 
             if (!GetElement(cx, obj, receiver, index, res))
                 return false;
             break;
         }
 
-        if (IsSymbolOrSymbolWrapper(key)) {
-            RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key)));
-            if (!GetProperty(cx, obj, receiver, id, res))
+        if (key.isString()) {
+            JSString* str = key.toString();
+            JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
+            if (!name)
                 return false;
-            break;
-        }
-
-        if (JSAtom* name = ToAtom<NoGC>(cx, key)) {
             if (name->isIndex(&index)) {
                 if (GetElementNoGC(cx, obj, receiver, index, res.address()))
                     break;
             } else {
                 if (GetPropertyNoGC(cx, obj, receiver, name->asPropertyName(), res.address()))
                     break;
             }
         }
 
-        JSAtom* name = ToAtom<CanGC>(cx, key);
-        if (!name)
+        RootedId id(cx);
+        if (!ToPropertyKey(cx, key, &id))
             return false;
-
-        if (name->isIndex(&index)) {
-            if (!GetElement(cx, obj, receiver, index, res))
-                return false;
-        } else {
-            if (!GetProperty(cx, obj, receiver, name->asPropertyName(), res))
-                return false;
-        }
+        if (!GetProperty(cx, obj, receiver, id, res))
+            return false;
     } while (false);
 
 #if JS_HAS_NO_SUCH_METHOD
     if (op == JSOP_CALLELEM && MOZ_UNLIKELY(res.isUndefined())) {
         if (!OnUnknownMethod(cx, receiver, key, res))
             return false;
     }
 #endif
@@ -668,44 +659,35 @@ GetPrimitiveElementOperation(JSContext* 
             if (GetElementNoGC(cx, boxed, receiver, index, res.address()))
                 break;
 
             if (!GetElement(cx, boxed, receiver, index, res))
                 return false;
             break;
         }
 
-        if (IsSymbolOrSymbolWrapper(key)) {
-            RootedId id(cx, SYMBOL_TO_JSID(ToSymbolPrimitive(key)));
-            if (!GetProperty(cx, boxed, receiver, id, res))
+        if (key.isString()) {
+            JSString* str = key.toString();
+            JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
+            if (!name)
                 return false;
-            break;
-        }
-
-        if (JSAtom* name = ToAtom<NoGC>(cx, key)) {
             if (name->isIndex(&index)) {
                 if (GetElementNoGC(cx, boxed, receiver, index, res.address()))
                     break;
             } else {
                 if (GetPropertyNoGC(cx, boxed, receiver, name->asPropertyName(), res.address()))
                     break;
             }
         }
 
-        JSAtom* name = ToAtom<CanGC>(cx, key);
-        if (!name)
+        RootedId id(cx);
+        if (!ToPropertyKey(cx, key, &id))
             return false;
-
-        if (name->isIndex(&index)) {
-            if (!GetElement(cx, boxed, receiver, index, res))
-                return false;
-        } else {
-            if (!GetProperty(cx, boxed, receiver, name->asPropertyName(), res))
-                return false;
-        }
+        if (!GetProperty(cx, boxed, boxed, id, res))
+            return false;
     } while (false);
 
     // Note: we don't call a __noSuchMethod__ hook when |this| was primitive.
 
     assertSameCompartmentDebugOnly(cx, res);
     return true;
 }
 
@@ -779,17 +761,17 @@ TypeOfObjectOperation(JSObject* obj, JSR
 static MOZ_ALWAYS_INLINE bool
 InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValue val)
 {
     MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));
     MOZ_ASSERT(!obj->getClass()->getProperty);
     MOZ_ASSERT(!obj->getClass()->setProperty);
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, idval, &id))
+    if (!ToPropertyKey(cx, idval, &id))
         return false;
 
     return DefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE);
 }
 
 static MOZ_ALWAYS_INLINE bool
 InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue val)
 {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2019,17 +2019,17 @@ CASE(JSOP_AND)
     bool cond = ToBoolean(REGS.stackHandleAt(-1));
     if (!cond)
         ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
 }
 END_CASE(JSOP_AND)
 
 #define FETCH_ELEMENT_ID(n, id)                                               \
     JS_BEGIN_MACRO                                                            \
-        if (!ValueToId<CanGC>(cx, REGS.stackHandleAt(n), &(id))) \
+        if (!ToPropertyKey(cx, REGS.stackHandleAt(n), &(id)))                 \
             goto error;                                                       \
     JS_END_MACRO
 
 #define TRY_BRANCH_AFTER_COND(cond,spdec)                                     \
     JS_BEGIN_MACRO                                                            \
         MOZ_ASSERT(js_CodeSpec[*REGS.pc].length == 1);                        \
         unsigned diff_ = (unsigned) GET_UINT8(REGS.pc) - (unsigned) JSOP_IFEQ; \
         if (diff_ <= 1) {                                                     \
@@ -2463,17 +2463,17 @@ CASE(JSOP_STRICTDELELEM)
     /* Fetch the left part and resolve it to a non-null object. */
     ReservedRooted<JSObject*> obj(&rootObject0);
     FETCH_OBJECT(cx, -2, obj);
 
     ReservedRooted<Value> propval(&rootValue0, REGS.sp[-1]);
 
     ObjectOpResult result;
     ReservedRooted<jsid> id(&rootId0);
-    if (!ValueToId<CanGC>(cx, propval, &id))
+    if (!ToPropertyKey(cx, propval, &id))
         goto error;
     if (!DeleteProperty(cx, obj, id, result))
         goto error;
     if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELELEM) {
         result.reportError(cx, obj, id);
         goto error;
     }
 
@@ -4260,17 +4260,17 @@ template <bool strict>
 bool
 js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index, bool* bp)
 {
     RootedObject obj(cx, ToObjectFromStack(cx, val));
     if (!obj)
         return false;
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, index, &id))
+    if (!ToPropertyKey(cx, index, &id))
         return false;
     ObjectOpResult result;
     if (!DeleteProperty(cx, obj, id, result))
         return false;
 
     if (strict) {
         if (!result)
             return result.reportError(cx, obj, id);
@@ -4296,29 +4296,29 @@ js::CallElement(JSContext* cx, MutableHa
     return GetElementOperation(cx, JSOP_CALLELEM, lref, rref, res);
 }
 
 bool
 js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value,
                      bool strict)
 {
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, index, &id))
+    if (!ToPropertyKey(cx, index, &id))
         return false;
     RootedValue receiver(cx, ObjectValue(*obj));
     return SetObjectElementOperation(cx, obj, receiver, id, value, strict);
 }
 
 bool
 js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value,
                      bool strict, HandleScript script, jsbytecode* pc)
 {
     MOZ_ASSERT(pc);
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, index, &id))
+    if (!ToPropertyKey(cx, index, &id))
         return false;
     RootedValue receiver(cx, ObjectValue(*obj));
     return SetObjectElementOperation(cx, obj, receiver, id, value, strict, script, pc);
 }
 
 bool
 js::InitElementArray(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue value)
 {
@@ -4481,17 +4481,17 @@ js::InitGetterSetterOperation(JSContext*
     return InitGetterSetterOperation(cx, pc, obj, id, val);
 }
 
 bool
 js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval,
                               HandleObject val)
 {
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, idval, &id))
+    if (!ToPropertyKey(cx, idval, &id))
         return false;
 
     return InitGetterSetterOperation(cx, pc, obj, id, val);
 }
 
 bool
 js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv,
                         HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res)
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2018,22 +2018,25 @@ 1234567890123456789012345678901234567890
      *   Category: Variables and Scopes
      *   Type: Arguments
      *   Operands:
      *   Stack: => rest
      */ \
     macro(JSOP_REST,          224, "rest",         NULL,  1,  0,  1,  JOF_BYTE|JOF_TYPESET) \
     \
     /*
-     * Pops the top of stack value, converts it into a jsid (int or string), and
-     * pushes it onto the stack.
+     * First, throw a TypeError if baseValue is null or undefined. Then,
+     * replace the top-of-stack value propertyNameValue with
+     * ToPropertyKey(propertyNameValue). This opcode implements ES6 12.3.2.1
+     * steps 7-10.  It is also used to implement computed property names; in
+     * that case, baseValue is always an object, so the first step is a no-op.
      *   Category: Literals
      *   Type: Object
      *   Operands:
-     *   Stack: obj, id => obj, (jsid of id)
+     *   Stack: baseValue, propertyNameValue => baseValue, propertyKey
      */ \
     macro(JSOP_TOID,          225, "toid",         NULL,  1,  1,  1,  JOF_BYTE) \
     \
     /*
      * Pushes the implicit 'this' value for calls to the associated name onto
      * the stack.
      *   Category: Variables and Scopes
      *   Type: This
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1057,18 +1057,19 @@ public:
     mContainerReferenceFrame =
       const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
                                              mBuilder->FindReferenceFrameFor(mContainerFrame));
     bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
     MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame());
     mContainerAnimatedGeometryRoot = isAtRoot
       ? mContainerReferenceFrame
       : nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder);
-    MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(),
-                                                      mContainerAnimatedGeometryRoot));
+    MOZ_ASSERT(!mBuilder->IsPaintingToWindow() ||
+      nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(),
+                                             mContainerAnimatedGeometryRoot));
     NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(mBuilder),
                  "Container items never return true for ShouldFixToViewport");
     mContainerFixedPosFrame =
         FindFixedPosFrameForLayerData(mContainerAnimatedGeometryRoot, false);
     // When AllowResidualTranslation is false, display items will be drawn
     // scaled with a translation by integer pixels, so we know how the snapping
     // will work.
     mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
@@ -2114,18 +2115,17 @@ ContainerState::GetLayerCreationHint(con
   // Check whether the layer will be scrollable. This is used as a hint to
   // influence whether tiled layers are used or not.
 
   // Check whether there's any active scroll frame on the animated geometry
   // root chain.
   nsIFrame* fParent;
   for (const nsIFrame* f = aAnimatedGeometryRoot;
        f != mContainerAnimatedGeometryRoot;
-       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder,
-           fParent, mContainerAnimatedGeometryRoot)) {
+       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, fParent)) {
     fParent = nsLayoutUtils::GetCrossDocParentFrame(f);
     if (!fParent) {
       break;
     }
     nsIScrollableFrame* scrollable = do_QueryFrame(fParent);
     if (scrollable
   #ifdef MOZ_B2G
         && scrollable->WantAsyncScroll()
@@ -2796,28 +2796,28 @@ PaintedLayerDataTree::GetParentAnimatedG
   MOZ_ASSERT(aAnimatedGeometryRoot);
   MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), aAnimatedGeometryRoot));
 
   if (aAnimatedGeometryRoot == Builder()->RootReferenceFrame()) {
     return nullptr;
   }
 
   nsIFrame* agr = Builder()->FindAnimatedGeometryRootFor(
-    const_cast<nsIFrame*>(aAnimatedGeometryRoot), Builder()->RootReferenceFrame());
+    const_cast<nsIFrame*>(aAnimatedGeometryRoot));
   MOZ_ASSERT_IF(agr, nsLayoutUtils::IsAncestorFrameCrossDoc(Builder()->RootReferenceFrame(), agr));
   if (agr != aAnimatedGeometryRoot) {
     return agr;
   }
   // aAnimatedGeometryRoot is its own animated geometry root.
   // Find the animated geometry root for its cross-doc parent frame.
   nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot);
   if (!parent) {
     return nullptr;
   }
-  return Builder()->FindAnimatedGeometryRootFor(parent, Builder()->RootReferenceFrame());
+  return Builder()->FindAnimatedGeometryRootFor(parent);
 }
 
 void
 PaintedLayerDataTree::Finish()
 {
   if (mRoot) {
     mRoot->Finish(false);
   }
@@ -3786,18 +3786,17 @@ IsCaretWithCustomClip(nsDisplayItem* aIt
 static DisplayItemClip
 GetScrollClipIntersection(nsDisplayListBuilder* aBuilder, const nsIFrame* aAnimatedGeometryRoot,
                           const nsIFrame* aStopAtAnimatedGeometryRoot, bool aIsCaret)
 {
   DisplayItemClip resultClip;
   nsIFrame* fParent;
   for (const nsIFrame* f = aAnimatedGeometryRoot;
        f != aStopAtAnimatedGeometryRoot;
-       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder,
-           fParent, aStopAtAnimatedGeometryRoot)) {
+       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, fParent)) {
     fParent = nsLayoutUtils::GetCrossDocParentFrame(f);
     if (!fParent) {
       // This means aStopAtAnimatedGeometryRoot was not an ancestor
       // of aAnimatedGeometryRoot. This is a weird case but it
       // can happen, e.g. when a scrolled frame contains a frame with opacity
       // which contains a frame that is not scrolled by the scrolled frame.
       // For now, we just don't apply any specific scroll clip to this layer.
       return DisplayItemClip();
@@ -3900,18 +3899,18 @@ ContainerState::ProcessDisplayItems(nsDi
       animatedGeometryRoot = lastAnimatedGeometryRoot;
     } else {
       forceInactive = false;
       if (mManager->IsWidgetLayerManager()) {
         animatedGeometryRoot = realAnimatedGeometryRootOfItem;
         // Unlike GetAnimatedGeometryRootFor(), GetAnimatedGeometryRootForFrame() does not
         // take ShouldFixToViewport() into account, so it will return something different
         // for fixed background items.
-        animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootForFrame(
-            mBuilder, item->Frame(), item->ReferenceFrame());
+        animatedGeometryRootForScrollMetadata = nsLayoutUtils::GetAnimatedGeometryRootFor(
+          item, mBuilder, nsLayoutUtils::AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED);
       } else {
         // For inactive layer subtrees, splitting content into PaintedLayers
         // based on animated geometry roots is pointless. It's more efficient
         // to build the minimum number of layers.
         animatedGeometryRoot = mContainerAnimatedGeometryRoot;
 
       }
       if (animatedGeometryRoot != lastAnimatedGeometryRoot) {
@@ -4738,34 +4737,39 @@ void
 ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry)
 {
   if (mFlattenToSingleLayer) {
     // animated geometry roots are forced to all match, so we can't
     // use them and we don't get async scrolling.
     return;
   }
 
+  if (!mBuilder->IsPaintingToWindow()) {
+    // async scrolling not possible, and async scrolling info not computed
+    // for this paint.
+    return;
+  }
+
   nsAutoTArray<FrameMetrics,2> metricsArray;
   if (aEntry->mBaseFrameMetrics) {
     metricsArray.AppendElement(*aEntry->mBaseFrameMetrics);
 
     // The base FrameMetrics was not computed by the nsIScrollableframe, so it
     // should not have a mask layer.
     MOZ_ASSERT(!aEntry->mBaseFrameMetrics->GetMaskLayerIndex());
   }
   uint32_t baseLength = metricsArray.Length();
 
   // Any extra mask layers we need to attach to FrameMetrics.
   nsTArray<RefPtr<Layer>> maskLayers;
 
   nsIFrame* fParent;
   for (const nsIFrame* f = aEntry->mAnimatedGeometryRootForScrollMetadata;
        f != mContainerAnimatedGeometryRoot;
-       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder,
-           fParent, mContainerAnimatedGeometryRoot)) {
+       f = nsLayoutUtils::GetAnimatedGeometryRootForFrame(this->mBuilder, fParent)) {
     fParent = nsLayoutUtils::GetCrossDocParentFrame(f);
     if (!fParent) {
       // This means mContainerAnimatedGeometryRoot was not an ancestor
       // of aEntry->mAnimatedGeometryRoot. This is a weird case but it
       // can happen, e.g. when a scrolled frame contains a frame with opacity
       // which contains a frame that is not scrolled by the scrolled frame.
       // For now, we just don't apply any specific async scrolling to this layer.
       // It will async-scroll with mContainerAnimatedGeometryRoot, which
@@ -4899,18 +4903,17 @@ ContainerState::PostprocessRetainedLayer
     PaintedLayer* p = e->mLayer->AsPaintedLayer();
     if (p) {
       InvalidateVisibleBoundsChangesForScrolledLayer(p);
     }
 
     if (!e->mOpaqueRegion.IsEmpty()) {
       const nsIFrame* animatedGeometryRootToCover = animatedGeometryRootForOpaqueness;
       if (e->mOpaqueForAnimatedGeometryRootParent &&
-          nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent(),
-                                                         mContainerAnimatedGeometryRoot)
+          nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent())
             == mContainerAnimatedGeometryRoot) {
         animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
         data = FindOpaqueRegionEntry(opaqueRegions,
             animatedGeometryRootToCover, e->mFixedPosFrameForLayerData);
       }
 
       if (!data) {
         if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -56,39 +56,42 @@ struct ContainerLayerParameters {
     : mXScale(1)
     , mYScale(1)
     , mLayerContentsVisibleRect(nullptr)
     , mBackgroundColor(NS_RGBA(0,0,0,0))
     , mInTransformedSubtree(false)
     , mInActiveTransformedSubtree(false)
     , mDisableSubpixelAntialiasingInDescendants(false)
     , mInLowPrecisionDisplayPort(false)
+    , mForEventsOnly(false)
   {}
   ContainerLayerParameters(float aXScale, float aYScale)
     : mXScale(aXScale)
     , mYScale(aYScale)
     , mLayerContentsVisibleRect(nullptr)
     , mBackgroundColor(NS_RGBA(0,0,0,0))
     , mInTransformedSubtree(false)
     , mInActiveTransformedSubtree(false)
     , mDisableSubpixelAntialiasingInDescendants(false)
     , mInLowPrecisionDisplayPort(false)
+    , mForEventsOnly(false)
   {}
   ContainerLayerParameters(float aXScale, float aYScale,
                            const nsIntPoint& aOffset,
                            const ContainerLayerParameters& aParent)
     : mXScale(aXScale)
     , mYScale(aYScale)
     , mLayerContentsVisibleRect(nullptr)
     , mOffset(aOffset)
     , mBackgroundColor(aParent.mBackgroundColor)
     , mInTransformedSubtree(aParent.mInTransformedSubtree)
     , mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree)
     , mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
     , mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort)
+    , mForEventsOnly(aParent.mForEventsOnly)
   {}
 
   float mXScale, mYScale;
 
   LayoutDeviceToLayerScale2D Scale() const {
     return LayoutDeviceToLayerScale2D(mXScale, mYScale);
   }
 
@@ -107,16 +110,17 @@ struct ContainerLayerParameters {
     return LayerIntPoint::FromUntyped(mOffset);
   }
 
   nscolor mBackgroundColor;
   bool mInTransformedSubtree;
   bool mInActiveTransformedSubtree;
   bool mDisableSubpixelAntialiasingInDescendants;
   bool mInLowPrecisionDisplayPort;
+  bool mForEventsOnly;
   /**
    * When this is false, PaintedLayer coordinates are drawn to with an integer
    * translation and the scale in mXScale/mYScale.
    */
   bool AllowResidualTranslation()
   {
     // If we're in a transformed subtree, but no ancestor transform is actively
     // changing, we'll use the residual translation when drawing into the
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -5579,49 +5579,55 @@ nsContextBoxBlur::InsetBoxBlur(gfxContex
                                RectCornerRadii& aInnerClipRectRadii,
                                Rect aSkipRect, Point aShadowOffset)
 {
   if (aDestinationRect.IsEmpty()) {
     mContext = nullptr;
     return false;
   }
 
+  gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
+
   IntSize blurRadius;
   IntSize spreadRadius;
   // Convert the blur and spread radius to device pixels
   bool constrainSpreadRadius = false;
   GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
                          aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
                          blurRadius, spreadRadius, constrainSpreadRadius);
 
   // The blur and spread radius are scaled already, so scale all
   // input data to the blur. This way, we don't have to scale the min
   // inset blur to the invert of the dest context, then rescale it back
   // when we draw to the destination surface.
   gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
-  Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
-
-  Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect);
-  Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect);
-  Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect);
+  Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix());
+
+  // XXX: we could probably handle negative scales but for now it's easier just to fallback
+  if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
+    // If we don't have a rotation, we're pre-transforming all the rects.
+    aDestinationCtx->SetMatrix(gfxMatrix());
+  } else {
+    // Don't touch anything, we have a rotation.
+    transform = Matrix();
+  }
+
+  Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
+  Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
+  Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
 
   transformedDestRect.Round();
   transformedShadowClipRect.Round();
   transformedSkipRect.RoundIn();
 
   for (size_t i = 0; i < 4; i++) {
     aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
     aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
   }
 
-  {
-    gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
-    aDestinationCtx->SetMatrix(gfxMatrix());
-
-    mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
-                               transformedShadowClipRect,
-                               blurRadius, spreadRadius,
-                               aShadowColor, aHasBorderRadius,
-                               aInnerClipRectRadii, transformedSkipRect,
-                               aShadowOffset);
-  }
+  mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
+                             transformedShadowClipRect,
+                             blurRadius, spreadRadius,
+                             aShadowColor, aHasBorderRadius,
+                             aInnerClipRectRadii, transformedSkipRect,
+                             aShadowOffset);
   return true;
 }
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1036,16 +1036,19 @@ nsDisplayListBuilder::IsAnimatedGeometry
   if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame))
     return true;
   if (!aFrame->GetParent() &&
       nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) {
     // Viewport frames in a display port need to be animated geometry roots
     // for background-attachment:fixed elements.
     return true;
   }
+  if (aFrame->IsTransformed()) {
+    return true;
+  }
 
   nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
   if (!parent)
     return true;
 
   nsIAtom* parentType = parent->GetType();
   // Treat the slider thumb as being as an active scrolled root when it wants
   // its own layer so that it can move without repainting.
@@ -1074,69 +1077,65 @@ nsDisplayListBuilder::IsAnimatedGeometry
   if (aParent) {
     *aParent = parent;
   }
   return false;
 }
 
 bool
 nsDisplayListBuilder::GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame,
-                                                    const nsIFrame* aStopAtAncestor,
                                                     nsIFrame** aOutResult)
 {
-  AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor);
-  return mAnimatedGeometryRootCache.Get(lookup, aOutResult);
+  return mAnimatedGeometryRootCache.Get(const_cast<nsIFrame*>(aFrame), aOutResult);
 }
 
 static nsIFrame*
 ComputeAnimatedGeometryRootFor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-                               const nsIFrame* aStopAtAncestor = nullptr,
                                bool aUseCache = false)
 {
   nsIFrame* cursor = aFrame;
-  while (cursor != aStopAtAncestor) {
+  while (cursor != aBuilder->RootReferenceFrame()) {
     if (aUseCache) {
       nsIFrame* result;
-      if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, aStopAtAncestor, &result)) {
+      if (aBuilder->GetCachedAnimatedGeometryRoot(cursor, &result)) {
         return result;
       }
     }
     nsIFrame* next;
     if (aBuilder->IsAnimatedGeometryRoot(cursor, &next))
       return cursor;
     cursor = next;
   }
   return cursor;
 }
 
 nsIFrame*
-nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor)
+nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame)
 {
   if (aFrame == mCurrentFrame) {
     return mCurrentAnimatedGeometryRoot;
   }
 
-  nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, aStopAtAncestor, true);
-  AnimatedGeometryRootLookup lookup(aFrame, aStopAtAncestor);
-  mAnimatedGeometryRootCache.Put(lookup, result);
+  nsIFrame* result = ComputeAnimatedGeometryRootFor(this, aFrame, true);
+  mAnimatedGeometryRootCache.Put(aFrame, result);
   return result;
 }
 
 void
 nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot()
 {
   // technically we only need to clear any part of the cache that relies on
   // the AGR of mCurrentFrame (i.e. all entries in mAnimatedGeometryRootCache
   // where the key frame is a descendant of mCurrentFrame) but doing that is
   // complicated so we just clear the whole thing.
   mAnimatedGeometryRootCache.Clear();
 
   mCurrentAnimatedGeometryRoot = ComputeAnimatedGeometryRootFor(this, const_cast<nsIFrame *>(mCurrentFrame));
-  AnimatedGeometryRootLookup lookup(mCurrentFrame, nullptr);
-  mAnimatedGeometryRootCache.Put(lookup, mCurrentAnimatedGeometryRoot);
+  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), mCurrentAnimatedGeometryRoot));
+  mAnimatedGeometryRootCache.Put(const_cast<nsIFrame*>(mCurrentFrame), mCurrentAnimatedGeometryRoot);
 }
 
 void
 nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame)
 {
   if (!mWindowDraggingAllowed || !IsForPainting()) {
     return;
   }
@@ -3277,16 +3276,22 @@ nsDisplayLayerEventRegions::AddFrame(nsD
     mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
   } else if (aFrame->GetType() == nsGkAtoms::objectFrame) {
     // If the frame is a plugin frame and wants to handle wheel events as
     // default action, we should add the frame to dispatch-to-content region.
     nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
     if (pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
       mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
     }
+  } else if (gfxPlatform::GetPlatform()->SupportsApzWheelInput() &&
+             nsLayoutUtils::IsScrollFrameWithSnapping(aFrame->GetParent())) {
+    // If the frame is the inner content of a scrollable frame with snap-points
+    // then we want to handle wheel events for it on the main thread. Add it to
+    // the d-t-c region so that APZ waits for the main thread.
+    mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
   }
 
   // Touch action region
 
   uint32_t touchAction = nsLayoutUtils::GetTouchActionFromFrame(aFrame);
   if (touchAction & NS_STYLE_TOUCH_ACTION_NONE) {
     mNoActionRegion.Or(mNoActionRegion, borderBox);
   } else {
@@ -3927,19 +3932,21 @@ nsRegion nsDisplayOpacity::GetOpaqueRegi
   return nsRegion();
 }
 
 // nsDisplayOpacity uses layers for rendering
 already_AddRefed<Layer>
 nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
                              LayerManager* aManager,
                              const ContainerLayerParameters& aContainerParameters) {
+  ContainerLayerParameters params = aContainerParameters;
+  params.mForEventsOnly = mForEventsOnly;
   RefPtr<Layer> container = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
-                           aContainerParameters, nullptr,
+                           params, nullptr,
                            FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
   if (!container)
     return nullptr;
 
   container->SetOpacity(mOpacity);
   nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(container, aBuilder,
                                                            this, mFrame,
                                                            eCSSProperty_opacity);
@@ -4775,16 +4782,19 @@ nsDisplayTransform::nsDisplayTransform(n
   MOZ_COUNT_CTOR(nsDisplayTransform);
   MOZ_ASSERT(aFrame, "Must have a frame!");
   Init(aBuilder);
 }
 
 void
 nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder)
 {
+  if (mFrame == aBuilder->RootReferenceFrame()) {
+    return;
+  }
   nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
   mReferenceFrame =
     aBuilder->FindReferenceFrameFor(outerFrame);
   mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
   mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame;
 }
 
 void
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -255,17 +255,17 @@ public:
    * returning the next ancestor to check.
    */
   bool IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent = nullptr);
 
   /**
    * Returns the nearest ancestor frame to aFrame that is considered to have
    * (or will have) animated geometry. This can return aFrame.
    */
-  nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame, const nsIFrame* aStopAtAncestor = nullptr);
+  nsIFrame* FindAnimatedGeometryRootFor(nsIFrame* aFrame);
 
   /**
    * @return the root of the display list's frame (sub)tree, whose origin
    * establishes the coordinate system for the display list
    */
   nsIFrame* RootReferenceFrame() 
   {
     return mReferenceFrame;
@@ -639,21 +639,21 @@ public:
           aBuilder->FindReferenceFrameFor(aForChild,
               &aBuilder->mCurrentOffsetToReferenceFrame);
       }
       if (aBuilder->mCurrentFrame == aForChild->GetParent()) {
         if (aBuilder->IsAnimatedGeometryRoot(aForChild)) {
           aBuilder->mCurrentAnimatedGeometryRoot = aForChild;
         }
       } else {
-        // Stop at the previous animated geometry root to help cases that
-        // aren't immediate descendents.
         aBuilder->mCurrentAnimatedGeometryRoot =
-          aBuilder->FindAnimatedGeometryRootFor(aForChild, aBuilder->mCurrentAnimatedGeometryRoot);
+          aBuilder->FindAnimatedGeometryRootFor(aForChild);
       }
+      MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(),
+          aBuilder->mCurrentAnimatedGeometryRoot));
       aBuilder->mCurrentFrame = aForChild;
       aBuilder->mDirtyRect = aDirtyRect;
       aBuilder->mIsAtRootOfPseudoStackingContext = aIsRoot;
     }
     void SetDirtyRect(const nsRect& aRect) {
       mBuilder->mDirtyRect = aRect;
     }
     void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, const nsPoint& aOffset) {
@@ -971,22 +971,21 @@ public:
    * This will add the current frame to the will-change budget the first
    * time it is seen. On subsequent calls this will return the same
    * answer. This effectively implements a first-come, first-served
    * allocation of the will-change budget.
    */
   bool IsInWillChangeBudget(nsIFrame* aFrame, const nsSize& aSize);
 
   /**
-   * Look up the cached animated geometry root for aFrame subject to
-   * aStopAtAncestor. Store the nsIFrame* result into *aOutResult, and return
-   * true if the cache was hit. Return false if the cache was not hit.
+   * Look up the cached animated geometry root for aFrame subject Store the
+   * nsIFrame* result into *aOutResult, and return true if the cache was hit.
+   * Return false if the cache was not hit.
    */
   bool GetCachedAnimatedGeometryRoot(const nsIFrame* aFrame,
-                                     const nsIFrame* aStopAtAncestor,
                                      nsIFrame** aOutResult);
 
   void SetCommittedScrollInfoItemList(nsDisplayList* aScrollInfoItemStorage) {
     mCommittedScrollInfoItems = aScrollInfoItemStorage;
   }
   nsDisplayList* CommittedScrollInfoItems() const {
     return mCommittedScrollInfoItems;
   }
@@ -1108,37 +1107,18 @@ private:
   const nsIFrame*                mCurrentFrame;
   // The reference frame for mCurrentFrame.
   const nsIFrame*                mCurrentReferenceFrame;
   // The offset from mCurrentFrame to mCurrentReferenceFrame.
   nsPoint                        mCurrentOffsetToReferenceFrame;
   // The animated geometry root for mCurrentFrame.
   nsIFrame*                      mCurrentAnimatedGeometryRoot;
 
-  struct AnimatedGeometryRootLookup {
-    const nsIFrame* mFrame;
-    const nsIFrame* mStopAtFrame;
-
-    AnimatedGeometryRootLookup(const nsIFrame* aFrame, const nsIFrame* aStopAtFrame)
-      : mFrame(aFrame)
-      , mStopAtFrame(aStopAtFrame)
-    {
-    }
-
-    PLDHashNumber Hash() const {
-      return mozilla::HashBytes(this, sizeof(*this));
-    }
-
-    bool operator==(const AnimatedGeometryRootLookup& aOther) const {
-      return mFrame == aOther.mFrame && mStopAtFrame == aOther.mStopAtFrame;
-    }
-  };
   // Cache for storing animated geometry roots for arbitrary frames
-  nsDataHashtable<nsGenericHashKey<AnimatedGeometryRootLookup>, nsIFrame*>
-                                 mAnimatedGeometryRootCache;
+  nsDataHashtable<nsPtrHashKey<nsIFrame>, nsIFrame*> mAnimatedGeometryRootCache;
   // will-change budget tracker
   nsDataHashtable<nsPtrHashKey<nsPresContext>, DocumentWillChangeBudget>
                                  mWillChangeBudget;
 
   // Any frame listed in this set is already counted in the budget
   // and thus is in-budget.
   nsTHashtable<nsPtrHashKey<nsIFrame> > mBudgetSet;
 
@@ -3954,16 +3934,25 @@ public:
    * context.
    */
   bool IsLeafOf3DContext() {
     return (IsTransformSeparator() ||
             (!mFrame->Extend3DContext() &&
              mFrame->Combines3DTransformWithAncestors()));
   }
 
+  /**
+   * Whether this transform item forms a reference frame boundary.
+   * In other words, the reference frame of the contained items is our frame,
+   * and the reference frame of this item is some ancestor of our frame.
+   */
+  bool IsReferenceFrameBoundary() {
+    return !mTransformGetter && !mIsTransformSeparator;
+  }
+
 private:
   void ComputeBounds(nsDisplayListBuilder* aBuilder);
   void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder);
   void Init(nsDisplayListBuilder* aBuilder);
 
   static Matrix4x4 GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties,
                                                        const nsPoint& aOrigin,
                                                        float aAppUnitsPerPixel,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1386,22 +1386,27 @@ nsLayoutUtils::GetAfterFrameForContent(n
 /*static*/ nsIFrame*
 nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
 {
   return GetAfterFrameForContent(aFrame, aFrame->GetContent());
 }
 
 // static
 nsIFrame*
-nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType)
+nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
+                                     nsIAtom* aFrameType,
+                                     nsIFrame* aStopAt)
 {
   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
     if (frame->GetType() == aFrameType) {
       return frame;
     }
+    if (frame == aStopAt) {
+      break;
+    }
   }
   return nullptr;
 }
 
 // static
 nsIFrame*
 nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame)
 {
@@ -1861,39 +1866,48 @@ nsLayoutUtils::SetScrollbarThumbLayeriza
 bool
 nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame)
 {
   return reinterpret_cast<intptr_t>(aThumbFrame->Properties().Get(ScrollbarThumbLayerized()));
 }
 
 nsIFrame*
 nsLayoutUtils::GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder,
-                                               nsIFrame* aFrame,
-                                               const nsIFrame* aStopAtAncestor)
-{
-  return aBuilder->FindAnimatedGeometryRootFor(aFrame, aStopAtAncestor);
+                                               nsIFrame* aFrame)
+{
+  return aBuilder->FindAnimatedGeometryRootFor(aFrame);
 }
 
 nsIFrame*
 nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem,
-                                          nsDisplayListBuilder* aBuilder)
+                                          nsDisplayListBuilder* aBuilder,
+                                          uint32_t aFlags)
 {
   nsIFrame* f = aItem->Frame();
-  if (aItem->ShouldFixToViewport(aBuilder)) {
+  if (!(aFlags & AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED) &&
+      aItem->ShouldFixToViewport(aBuilder)) {
     // Make its active scrolled root be the active scrolled root of
     // the enclosing viewport, since it shouldn't be scrolled by scrolled
     // frames in its document. InvalidateFixedBackgroundFramesFromList in
     // nsGfxScrollFrame will not repaint this item when scrolling occurs.
     nsIFrame* viewportFrame =
-      nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame);
-    NS_ASSERTION(viewportFrame, "no viewport???");
-    return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame,
-        aBuilder->FindReferenceFrameFor(viewportFrame));
-  }
-  return GetAnimatedGeometryRootForFrame(aBuilder, f, aItem->ReferenceFrame());
+      nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame, aBuilder->RootReferenceFrame());
+    if (viewportFrame) {
+      return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame);
+    }
+  }
+  if (aItem->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
+      static_cast<nsDisplayTransform*>(aItem)->IsReferenceFrameBoundary() &&
+      f != aBuilder->RootReferenceFrame()) {
+    nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f);
+    if (parent) {
+      return GetAnimatedGeometryRootForFrame(aBuilder, parent);
+    }
+  }
+  return GetAnimatedGeometryRootForFrame(aBuilder, f);
 }
 
 // static
 nsIScrollableFrame*
 nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame,
                                                      Direction aDirection)
 {
   NS_ASSERTION(aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
@@ -4527,20 +4541,25 @@ nsLayoutUtils::IntrinsicForAxis(Physical
   AutoMaybeDisableFontInflation an(aFrame);
 
   // We want the size this frame will contribute to the parent's inline-size,
   // so we work in the parent's writing mode; but if aFrame is orthogonal to
   // its parent, we'll need to look at its BSize instead of min/pref-ISize.
   const nsStylePosition* stylePos = aFrame->StylePosition();
   uint8_t boxSizing = stylePos->mBoxSizing;
 
-  const nsStyleCoord& styleISize =
-    horizontalAxis ? stylePos->mWidth : stylePos->mHeight;
   const nsStyleCoord& styleMinISize =
     horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight;
+  const nsStyleCoord& styleISize =
+    (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize :
+    (horizontalAxis ? stylePos->mWidth : stylePos->mHeight);
+  MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) ||
+             styleISize.GetUnit() == eStyleUnit_Auto ||
+             styleISize.GetUnit() == eStyleUnit_Enumerated,
+             "should only use MIN_INTRINSIC_ISIZE for intrinsic values");
   const nsStyleCoord& styleMaxISize =
     horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
 
   // We build up two values starting with the content box, and then
   // adding padding, border and margin.  The result is normally
   // |result|.  Then, when we handle 'width', 'min-width', and
   // 'max-width', we use the results we've been building in |min| as a
   // minimum, overriding 'min-width'.  This ensures two things:
@@ -4746,51 +4765,86 @@ nsLayoutUtils::IntrinsicForContainer(nsR
 
 /* static */ nscoord
 nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis        aAxis,
                                           nsRenderingContext* aRC,
                                           nsIFrame*           aFrame,
                                           IntrinsicISizeType  aType,
                                           uint32_t            aFlags)
 {
-  NS_PRECONDITION(aFrame, "null frame");
-  NS_PRECONDITION(aFrame->GetParent(),
-                  "MinSizeContributionForAxis called on frame not in tree");
+  MOZ_ASSERT(aFrame);
+  MOZ_ASSERT(aFrame->IsFlexOrGridItem(),
+             "only grid/flex items have this behavior currently");
 
 #ifdef DEBUG_INTRINSIC_WIDTH
   nsFrame::IndentBy(stderr, gNoiseIndent);
   static_cast<nsFrame*>(aFrame)->ListTag(stderr);
   printf_stderr(" %s min-isize for %s WM:\n",
                 aType == MIN_ISIZE ? "min" : "pref",
                 aWM.IsVertical() ? "vertical" : "horizontal");
 #endif
 
+  const nsStylePosition* const stylePos = aFrame->StylePosition();
+  const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth
+                                                       : &stylePos->mMinHeight;
+  nscoord minSize;
+  nscoord* fixedMinSize = nullptr;
+  auto minSizeUnit = style->GetUnit();
+  if (minSizeUnit == eStyleUnit_Auto) {
+    if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) {
+      style = aAxis == eAxisHorizontal ? &stylePos->mWidth
+                                       : &stylePos->mHeight;
+      if (GetAbsoluteCoord(*style, minSize)) {
+        // We have a definite width/height.  This is the "specified size" in:
+        // https://drafts.csswg.org/css-grid/#min-size-auto
+        fixedMinSize = &minSize;
+      }
+      // XXX the "transferred size" piece is missing (bug 1218178)
+    } else {
+      // min-[width|height]:auto with overflow != visible computes to zero.
+      minSize = 0;
+      fixedMinSize = &minSize;
+    }
+  } else if (GetAbsoluteCoord(*style, minSize)) {
+    fixedMinSize = &minSize;
+  } else if (minSizeUnit != eStyleUnit_Enumerated) {
+    MOZ_ASSERT(style->HasPercent());
+    minSize = 0;
+    fixedMinSize = &minSize;
+  }
+
+  if (!fixedMinSize) {
+    // Let the caller deal with the "content size" cases.
+#ifdef DEBUG_INTRINSIC_WIDTH
+    nsFrame::IndentBy(stderr, gNoiseIndent);
+    static_cast<nsFrame*>(aFrame)->ListTag(stderr);
+    printf_stderr(" %s min-isize is indefinite.\n",
+                  aType == MIN_ISIZE ? "min" : "pref");
+#endif
+    return NS_UNCONSTRAINEDSIZE;
+  }
+
   // If aFrame is a container for font size inflation, then shrink
   // wrapping inside of it should not apply font size inflation.
   AutoMaybeDisableFontInflation an(aFrame);
 
   PhysicalAxis ourInlineAxis =
     aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
   nsIFrame::IntrinsicISizeOffsetData offsets =
     ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets()
                            : aFrame->IntrinsicBSizeOffsets();
   nscoord result = 0;
   nscoord min = 0;
-  const nsStylePosition* stylePos = aFrame->StylePosition();
-  uint8_t boxSizing = stylePos->mBoxSizing;
-  const nsStyleCoord& style = aAxis == eAxisHorizontal ? stylePos->mMinWidth
-                                                       : stylePos->mMinHeight;
-  nscoord minSize;
-  nscoord* fixedMinSize = nullptr;
-  if (GetAbsoluteCoord(style, minSize)) {
-    fixedMinSize = &minSize;
-  }
-  result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, boxSizing,
-                                  result, min, style, fixedMinSize,
-                                  style, fixedMinSize, style, aFlags, aAxis);
+
+  const nsStyleCoord& maxISize =
+    aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+  result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType,
+                                  stylePos->mBoxSizing,
+                                  result, min, *style, fixedMinSize,
+                                  *style, nullptr, maxISize, aFlags, aAxis);
 
 #ifdef DEBUG_INTRINSIC_WIDTH
   nsFrame::IndentBy(stderr, gNoiseIndent);
   static_cast<nsFrame*>(aFrame)->ListTag(stderr);
   printf_stderr(" %s min-isize is %d twips.\n",
          aType == MIN_ISIZE ? "min" : "pref", result);
 #endif
 
@@ -8657,8 +8711,20 @@ nsLayoutUtils::GetSelectionBoundingRect(
                                   true, false);
     }
     res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
       accumulator.mResultRect;
   }
 
   return res;
 }
+
+/* static */ bool
+nsLayoutUtils::IsScrollFrameWithSnapping(nsIFrame* aFrame)
+{
+  nsIScrollableFrame* sf = do_QueryFrame(aFrame);
+  if (!sf) {
+    return false;
+  }
+  ScrollbarStyles styles = sf->GetScrollbarStyles();
+  return styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE ||
+         styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE;
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -268,20 +268,23 @@ public:
   static nsIFrame* GetAfterFrame(nsIFrame* aFrame);
 
   /**
    * Given a frame, search up the frame tree until we find an
    * ancestor that (or the frame itself) is of type aFrameType, if any.
    *
    * @param aFrame the frame to start at
    * @param aFrameType the frame type to look for
+   * @param aStopAt a frame to stop at after we checked it
    * @return a frame of the given type or nullptr if no
    *         such ancestor exists
    */
-  static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType);
+  static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame,
+                                         nsIAtom* aFrameType,
+                                         nsIFrame* aStopAt = nullptr);
 
   /**
    * Given a frame, search up the frame tree until we find an
    * ancestor that (or the frame itself) is a "Page" frame, if any.
    *
    * @param aFrame the frame to start at
    * @return a frame of type nsGkAtoms::pageFrame or nullptr if no
    *         such ancestor exists
@@ -544,27 +547,34 @@ public:
    * Frames with different active geometry roots are in different PaintedLayers,
    * so that we can animate the geometry root by changing its transform (either
    * on the main thread or in the compositor).
    * The animated geometry root is required to be a descendant (or equal to)
    * aItem's ReferenceFrame(), which means that we will fall back to
    * returning aItem->ReferenceFrame() when we can't find another animated
    * geometry root.
    */
+  enum {
+    /**
+     * If the AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED flag is set, then we
+     * do not do any special processing for background attachment fixed items,
+     * instead treating them like any other frame.
+     */
+    AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED = 0x01
+  };
   static nsIFrame* GetAnimatedGeometryRootFor(nsDisplayItem* aItem,
-                                              nsDisplayListBuilder* aBuilder);
+                                              nsDisplayListBuilder* aBuilder,
+                                              uint32_t aFlags = 0);
 
   /**
    * Finds the nearest ancestor frame to aFrame that is considered to have (or
-   * will have) "animated geometry". This could be aFrame. Returns
-   * aStopAtAncestor if no closer ancestor is found.
+   * will have) "animated geometry". This could be aFrame.
    */
   static nsIFrame* GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder,
-                                                   nsIFrame* aFrame,
-                                                   const nsIFrame* aStopAtAncestor);
+                                                   nsIFrame* aFrame);
 
   /**
     * GetScrollableFrameFor returns the scrollable frame for a scrolled frame
     */
   static nsIScrollableFrame* GetScrollableFrameFor(const nsIFrame *aScrolledFrame);
 
   /**
    * GetNearestScrollableFrameForDirection locates the first ancestor of
@@ -1309,39 +1319,52 @@ public:
 
   /**
    * Get the contribution of aFrame to its containing block's intrinsic
    * size for the given physical axis.  This considers the child's intrinsic
    * width, its 'width', 'min-width', and 'max-width' properties (or 'height'
    * variations if that's what matches aAxis) and its padding, border and margin
    * in the corresponding dimension.
    */
-  enum IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE };
+  enum class IntrinsicISizeType { MIN_ISIZE, PREF_ISIZE };
+  static const auto MIN_ISIZE = IntrinsicISizeType::MIN_ISIZE;
+  static const auto PREF_ISIZE = IntrinsicISizeType::PREF_ISIZE;
   enum {
     IGNORE_PADDING = 0x01,
     BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_WIDTH_UNKNOWN if so
+    MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height
   };
   static nscoord IntrinsicForAxis(mozilla::PhysicalAxis aAxis,
                                   nsRenderingContext*   aRenderingContext,
                                   nsIFrame*             aFrame,
                                   IntrinsicISizeType    aType,
                                   uint32_t              aFlags = 0);
   /**
    * Calls IntrinsicForAxis with aFrame's parent's inline physical axis.
    */
   static nscoord IntrinsicForContainer(nsRenderingContext* aRenderingContext,
                                        nsIFrame*           aFrame,
                                        IntrinsicISizeType  aType,
                                        uint32_t            aFlags = 0);
 
   /**
-   * Get the contribution of aFrame for the given physical axis.
+   * Get the definite size contribution of aFrame for the given physical axis.
    * This considers the child's 'min-width' property (or 'min-height' if the
    * given axis is vertical), and its padding, border, and margin in the
-   * corresponding dimension.
+   * corresponding dimension.  If the 'min-' property is 'auto' (and 'overflow'
+   * is 'visible') and the corresponding 'width'/'height' is definite it returns
+   * the "specified / transferred size" for:
+   * https://drafts.csswg.org/css-grid/#min-size-auto
+   * Note that any percentage in 'width'/'height' makes it count as indefinite.
+   * If the 'min-' property is 'auto' and 'overflow' is not 'visible', then it
+   * calculates the result as if the 'min-' computed value is zero.
+   * Otherwise, return NS_UNCONSTRAINEDSIZE.
+   *
+   * @note this behavior is specific to Grid/Flexbox (currently) so aFrame
+   * should be a grid/flex item.
    */
   static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis,
                                             nsRenderingContext*   aRC,
                                             nsIFrame*             aFrame,
                                             IntrinsicISizeType    aType,
                                             uint32_t              aFlags = 0);
 
   /**
@@ -2758,16 +2781,22 @@ public:
 
   /**
    * Takes a selection, and returns selection's bounding rect which is relative
    * to its root frame.
    *
    * @param aSel      Selection to check
    */
   static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel);
+
+  /**
+   * Returns true if the given frame is a scrollframe and it has snap points.
+   */
+  static bool IsScrollFrameWithSnapping(nsIFrame* aFrame);
+
 private:
   static uint32_t sFontSizeInflationEmPerLine;
   static uint32_t sFontSizeInflationMinTwips;
   static uint32_t sFontSizeInflationLineThreshold;
   static int32_t  sFontSizeInflationMappingIntercept;
   static uint32_t sFontSizeInflationMaxRatio;
   static bool sFontSizeInflationForceEnabled;
   static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -1521,17 +1521,17 @@ nsFlexContainerFrame::
                     0, 0, flags);
 
   aFlexItem.SetHadMeasuringReflow();
 
   // If this is the first child, save its ascent, since it may be what
   // establishes the container's baseline. Also save the ascent if this child
   // needs to be baseline-aligned. (Else, we don't care about ascent/baseline.)
   if (aFlexItem.Frame() == mFrames.FirstChild() ||
-      aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) {
+      aFlexItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) {
     aFlexItem.SetAscent(childDesiredSize.BlockStartAscent());
   }
 
   // Subtract border/padding in vertical axis, to get _just_
   // the effective computed value of the "height" property.
   nscoord childDesiredHeight = childDesiredSize.Height() -
     childRSForMeasuringHeight.ComputedPhysicalBorderPadding().TopBottom();
 
@@ -1560,25 +1560,37 @@ FlexItem::FlexItem(nsHTMLReflowState& aF
     mShareOfWeightSoFar(0.0f),
     mIsFrozen(false),
     mHadMinViolation(false),
     mHadMaxViolation(false),
     mHadMeasuringReflow(false),
     mIsStretched(false),
     mIsStrut(false),
     // mNeedsMinSizeAutoResolution is initialized in CheckForMinSizeAuto()
-    mWM(aFlexItemReflowState.GetWritingMode()),
-    mAlignSelf(aFlexItemReflowState.mStylePosition->mAlignSelf)
+    mWM(aFlexItemReflowState.GetWritingMode())
+    // mAlignSelf, see below
 {
   MOZ_ASSERT(mFrame, "expecting a non-null child frame");
   MOZ_ASSERT(mFrame->GetType() != nsGkAtoms::placeholderFrame,
              "placeholder frames should not be treated as flex items");
   MOZ_ASSERT(!(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW),
              "out-of-flow frames should not be treated as flex items");
 
+  mAlignSelf = aFlexItemReflowState.mStylePosition->ComputedAlignSelf(
+                 aFlexItemReflowState.mStyleDisplay,
+                 mFrame->StyleContext()->GetParent());
+  if (MOZ_UNLIKELY(mAlignSelf == NS_STYLE_ALIGN_AUTO)) {
+    // Happens in rare edge cases when 'position' was ignored by the frame
+    // constructor (and the style system computed 'auto' based on 'position').
+    mAlignSelf = NS_STYLE_ALIGN_STRETCH;
+  }
+
+  // XXX strip off the <overflow-position> bit until we implement that
+  mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS;
+
   SetFlexBaseSizeAndMainSize(aFlexBaseSize);
   CheckForMinSizeAuto(aFlexItemReflowState, aAxisTracker);
 
   // Assert that any "auto" margin components are set to 0.
   // (We'll resolve them later; until then, we want to treat them as 0-sized.)
 #ifdef DEBUG
   {
     const nsStyleSides& styleMargin =
@@ -1587,35 +1599,29 @@ FlexItem::FlexItem(nsHTMLReflowState& aF
       if (styleMargin.GetUnit(side) == eStyleUnit_Auto) {
         MOZ_ASSERT(GetMarginComponentForSide(side) == 0,
                    "Someone else tried to resolve our auto margin");
       }
     }
   }
 #endif // DEBUG
 
-  // Resolve "align-self: auto" to parent's "align-items" value.
-  if (mAlignSelf == NS_STYLE_ALIGN_SELF_AUTO) {
-    mAlignSelf =
-      mFrame->StyleContext()->GetParent()->StylePosition()->mAlignItems;
-  }
-
   // If the flex item's inline axis is the same as the cross axis, then
   // 'align-self:baseline' is identical to 'flex-start'. If that's the case, we
   // just directly convert our align-self value here, so that we don't have to
   // handle this with special cases elsewhere.
   // Moreover: for the time being (until we support writing-modes),
   // all inline axes are horizontal -- so we can just check if the cross axis
   // is horizontal.
   // FIXME: Once we support writing-mode (vertical text), this
   // IsCrossAxisHorizontal check won't be sufficient anymore -- we'll actually
   // need to compare our inline axis vs. the cross axis.
-  if (mAlignSelf == NS_STYLE_ALIGN_ITEMS_BASELINE &&
+  if (mAlignSelf == NS_STYLE_ALIGN_BASELINE &&
       aAxisTracker.IsCrossAxisHorizontal()) {
-    mAlignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START;
+    mAlignSelf = NS_STYLE_ALIGN_FLEX_START;
   }
 }
 
 // Simplified constructor for creating a special "strut" FlexItem, for a child
 // with visibility:collapse. The strut has 0 main-size, and it only exists to
 // impose a minimum cross size on whichever FlexLine it ends up in.
 FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
                    WritingMode aContainerWM)
@@ -1638,17 +1644,17 @@ FlexItem::FlexItem(nsIFrame* aChildFrame
     mIsFrozen(true),
     mHadMinViolation(false),
     mHadMaxViolation(false),
     mHadMeasuringReflow(false),
     mIsStretched(false),
     mIsStrut(true), // (this is the constructor for making struts, after all)
     mNeedsMinSizeAutoResolution(false),
     mWM(aContainerWM),
-    mAlignSelf(NS_STYLE_ALIGN_ITEMS_FLEX_START)
+    mAlignSelf(NS_STYLE_ALIGN_FLEX_START)
 {
   MOZ_ASSERT(mFrame, "expecting a non-null child frame");
   MOZ_ASSERT(NS_STYLE_VISIBILITY_COLLAPSE ==
              mFrame->StyleVisibility()->mVisible,
              "Should only make struts for children with 'visibility:collapse'");
   MOZ_ASSERT(mFrame->GetType() != nsGkAtoms::placeholderFrame,
              "placeholder frames should not be treated as flex items");
   MOZ_ASSERT(!(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW),
@@ -1828,16 +1834,17 @@ public:
   // If aItem has any 'auto' margins in the main axis, this method updates the
   // corresponding values in its margin.
   void ResolveAutoMarginsInMainAxis(FlexItem& aItem);
 
 private:
   nscoord  mPackingSpaceRemaining;
   uint32_t mNumAutoMarginsInMainAxis;
   uint32_t mNumPackingSpacesRemaining;
+  // XXX this should be uint16_t when we add explicit fallback handling
   uint8_t  mJustifyContent;
 };
 
 // Utility class for managing our position along the cross axis along
 // the whole flex container (at a higher level than a single line).
 // The "0" position represents the cross-start edge of the flex container's
 // content-box.
 class MOZ_STACK_CLASS CrossAxisPositionTracker : public PositionTracker {
@@ -1861,16 +1868,17 @@ private:
   // deals with FlexLines, not with individual FlexItems or frames.)
   void EnterMargin(const nsMargin& aMargin) = delete;
   void ExitMargin(const nsMargin& aMargin) = delete;
   void EnterChildFrame(nscoord aChildFrameSize) = delete;
   void ExitChildFrame(nscoord aChildFrameSize) = delete;
 
   nscoord  mPackingSpaceRemaining;
   uint32_t mNumPackingSpacesRemaining;
+  // XXX this should be uint16_t when we add explicit fallback handling
   uint8_t  mAlignContent;
 };
 
 // Utility class for managing our position along the cross axis, *within* a
 // single flex line.
 class MOZ_STACK_CLASS SingleLineCrossAxisPositionTracker : public PositionTracker {
 public:
   explicit SingleLineCrossAxisPositionTracker(const FlexboxAxisTracker& aAxisTracker);
@@ -2423,16 +2431,24 @@ MainAxisPositionTracker::
                           nscoord aContentBoxMainSize)
   : PositionTracker(aAxisTracker.GetMainAxis(),
                     aAxisTracker.IsMainAxisReversed()),
     mPackingSpaceRemaining(aContentBoxMainSize), // we chip away at this below
     mNumAutoMarginsInMainAxis(0),
     mNumPackingSpacesRemaining(0),
     mJustifyContent(aJustifyContent)
 {
+  // 'auto' behaves as 'stretch' which behaves as 'flex-start' in the main axis
+  if (mJustifyContent == NS_STYLE_JUSTIFY_AUTO) {
+    mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START;
+  }
+
+  // XXX strip off the <overflow-position> bit until we implement that
+  mJustifyContent &= ~NS_STYLE_JUSTIFY_FLAG_BITS;
+
   // mPackingSpaceRemaining is initialized to the container's main size.  Now
   // we'll subtract out the main sizes of our flex items, so that it ends up
   // with the *actual* amount of packing space.
   for (const FlexItem* item = aLine->GetFirstItem(); item;
        item = item->getNext()) {
     mPackingSpaceRemaining -= item->GetOuterMainSize(mAxis);
     mNumAutoMarginsInMainAxis += item->GetNumAutoMarginsInAxis(mAxis);
   }
@@ -2441,58 +2457,71 @@ MainAxisPositionTracker::
     // No available packing space to use for resolving auto margins.
     mNumAutoMarginsInMainAxis = 0;
   }
 
   // If packing space is negative, 'space-between' behaves like 'flex-start',
   // and 'space-around' behaves like 'center'. In those cases, it's simplest to
   // just pretend we have a different 'justify-content' value and share code.
   if (mPackingSpaceRemaining < 0) {
-    if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN) {
-      mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START;
-    } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND) {
-      mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_CENTER;
+    if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN) {
+      mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START;
+    } else if (mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND) {
+      mJustifyContent = NS_STYLE_JUSTIFY_CENTER;
     }
   }
 
+  // Map 'start'/'end' to 'flex-start'/'flex-end'.
+  if (mJustifyContent == NS_STYLE_JUSTIFY_START) {
+    mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START;
+  } else if (mJustifyContent == NS_STYLE_JUSTIFY_END) {
+    mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END;
+  }
+
   // If our main axis is (internally) reversed, swap the justify-content
   // "flex-start" and "flex-end" behaviors:
   if (aAxisTracker.AreAxesInternallyReversed()) {
-    if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_START) {
-      mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_END;
-    } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_END) {
-      mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START;
+    if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_START) {
+      mJustifyContent = NS_STYLE_JUSTIFY_FLEX_END;
+    } else if (mJustifyContent == NS_STYLE_JUSTIFY_FLEX_END) {
+      mJustifyContent = NS_STYLE_JUSTIFY_FLEX_START;
     }
   }
 
   // Figure out how much space we'll set aside for auto margins or
   // packing spaces, and advance past any leading packing-space.
   if (mNumAutoMarginsInMainAxis == 0 &&
       mPackingSpaceRemaining != 0 &&
       !aLine->IsEmpty()) {
     switch (mJustifyContent) {
-      case NS_STYLE_JUSTIFY_CONTENT_FLEX_START:
+      case NS_STYLE_JUSTIFY_LEFT:
+      case NS_STYLE_JUSTIFY_RIGHT:
+      case NS_STYLE_JUSTIFY_BASELINE:
+      case NS_STYLE_JUSTIFY_LAST_BASELINE:
+      case NS_STYLE_JUSTIFY_SPACE_EVENLY:
+        NS_WARNING("NYI: justify-content:left/right/baseline/last-baseline/space-evenly");
+      case NS_STYLE_JUSTIFY_FLEX_START:
         // All packing space should go at the end --> nothing to do here.
         break;
-      case NS_STYLE_JUSTIFY_CONTENT_FLEX_END:
+      case NS_STYLE_JUSTIFY_FLEX_END:
         // All packing space goes at the beginning
         mPosition += mPackingSpaceRemaining;
         break;
-      case NS_STYLE_JUSTIFY_CONTENT_CENTER:
+      case NS_STYLE_JUSTIFY_CENTER:
         // Half the packing space goes at the beginning
         mPosition += mPackingSpaceRemaining / 2;
         break;
-      case NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN:
+      case NS_STYLE_JUSTIFY_SPACE_BETWEEN:
         MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                    "negative packing space should make us use 'flex-start' "
                    "instead of 'space-between'");
         // 1 packing space between each flex item, no packing space at ends.
         mNumPackingSpacesRemaining = aLine->NumItems() - 1;
         break;
-      case NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND:
+      case NS_STYLE_JUSTIFY_SPACE_AROUND:
         MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                    "negative packing space should make us use 'center' "
                    "instead of 'space-around'");
         // 1 packing space between each flex item, plus half a packing space
         // at beginning & end.  So our number of full packing-spaces is equal
         // to the number of flex items.
         mNumPackingSpacesRemaining = aLine->NumItems();
         if (mNumPackingSpacesRemaining > 0) {
@@ -2543,18 +2572,18 @@ MainAxisPositionTracker::ResolveAutoMarg
     }
   }
 }
 
 void
 MainAxisPositionTracker::TraversePackingSpace()
 {
   if (mNumPackingSpacesRemaining) {
-    MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_BETWEEN ||
-               mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_SPACE_AROUND,
+    MOZ_ASSERT(mJustifyContent == NS_STYLE_JUSTIFY_SPACE_BETWEEN ||
+               mJustifyContent == NS_STYLE_JUSTIFY_SPACE_AROUND,
                "mNumPackingSpacesRemaining only applies for "
                "space-between/space-around");
 
     MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                "ran out of packing space earlier than we expected");
 
     // NOTE: This integer math will skew the distribution of remainder
     // app-units towards the end, which is fine.
@@ -2576,16 +2605,24 @@ CrossAxisPositionTracker::
   : PositionTracker(aAxisTracker.GetCrossAxis(),
                     aAxisTracker.IsCrossAxisReversed()),
     mPackingSpaceRemaining(0),
     mNumPackingSpacesRemaining(0),
     mAlignContent(aAlignContent)
 {
   MOZ_ASSERT(aFirstLine, "null first line pointer");
 
+  // 'auto' behaves as 'stretch'
+  if (mAlignContent == NS_STYLE_ALIGN_AUTO) {
+    mAlignContent = NS_STYLE_ALIGN_STRETCH;
+  }
+
+  // XXX strip of the <overflow-position> bit until we implement that
+  mAlignContent &= ~NS_STYLE_ALIGN_FLAG_BITS;
+
   if (aIsCrossSizeDefinite && !aFirstLine->getNext()) {
     // "If the flex container has only a single line (even if it's a
     // multi-line flex container) and has a definite cross size, the cross
     // size of the flex line is the flex container's inner cross size."
     // SOURCE: http://dev.w3.org/csswg/css-flexbox/#algo-line-break
     // NOTE: This means (by definition) that there's no packing space, which
     // means we don't need to be concerned with "align-conent" at all and we
     // can return early. This is handy, because this is the usual case (for
@@ -2609,57 +2646,72 @@ CrossAxisPositionTracker::
     numLines++;
   }
 
   // If packing space is negative, 'space-between' and 'stretch' behave like
   // 'flex-start', and 'space-around' behaves like 'center'. In those cases,
   // it's simplest to just pretend we have a different 'align-content' value
   // and share code.
   if (mPackingSpaceRemaining < 0) {
-    if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN ||
-        mAlignContent == NS_STYLE_ALIGN_CONTENT_STRETCH) {
-      mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START;
-    } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND) {
-      mAlignContent = NS_STYLE_ALIGN_CONTENT_CENTER;
+    if (mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN ||
+        mAlignContent == NS_STYLE_ALIGN_STRETCH) {
+      mAlignContent = NS_STYLE_ALIGN_FLEX_START;
+    } else if (mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND) {
+      mAlignContent = NS_STYLE_ALIGN_CENTER;
     }
   }
 
+  // Map 'start'/'end' to 'flex-start'/'flex-end'.
+  if (mAlignContent == NS_STYLE_ALIGN_START) {
+    mAlignContent = NS_STYLE_ALIGN_FLEX_START;
+  } else if (mAlignContent == NS_STYLE_ALIGN_END) {
+    mAlignContent = NS_STYLE_ALIGN_FLEX_END;
+  }
+
   // If our cross axis is (internally) reversed, swap the align-content
   // "flex-start" and "flex-end" behaviors:
   if (aAxisTracker.AreAxesInternallyReversed()) {
-    if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_START) {
-      mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_END;
-    } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_END) {
-      mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START;
+    if (mAlignContent == NS_STYLE_ALIGN_FLEX_START) {
+      mAlignContent = NS_STYLE_ALIGN_FLEX_END;
+    } else if (mAlignContent == NS_STYLE_ALIGN_FLEX_END) {
+      mAlignContent = NS_STYLE_ALIGN_FLEX_START;
     }
   }
 
   // Figure out how much space we'll set aside for packing spaces, and advance
   // past any leading packing-space.
   if (mPackingSpaceRemaining != 0) {
     switch (mAlignContent) {
-      case NS_STYLE_ALIGN_CONTENT_FLEX_START:
+      case NS_STYLE_JUSTIFY_LEFT:
+      case NS_STYLE_JUSTIFY_RIGHT:
+      case NS_STYLE_ALIGN_SELF_START:
+      case NS_STYLE_ALIGN_SELF_END:
+      case NS_STYLE_ALIGN_SPACE_EVENLY:
+      case NS_STYLE_ALIGN_BASELINE:
+      case NS_STYLE_ALIGN_LAST_BASELINE:
+        NS_WARNING("NYI: align-self:left/right/self-start/self-end/space-evenly/baseline/last-baseline");
+      case NS_STYLE_ALIGN_FLEX_START:
         // All packing space should go at the end --> nothing to do here.
         break;
-      case NS_STYLE_ALIGN_CONTENT_FLEX_END:
+      case NS_STYLE_ALIGN_FLEX_END:
         // All packing space goes at the beginning
         mPosition += mPackingSpaceRemaining;
         break;
-      case NS_STYLE_ALIGN_CONTENT_CENTER:
+      case NS_STYLE_ALIGN_CENTER:
         // Half the packing space goes at the beginning
         mPosition += mPackingSpaceRemaining / 2;
         break;
-      case NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN:
+      case NS_STYLE_ALIGN_SPACE_BETWEEN:
         MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                    "negative packing space should make us use 'flex-start' "
                    "instead of 'space-between'");
         // 1 packing space between each flex line, no packing space at ends.
         mNumPackingSpacesRemaining = numLines - 1;
         break;
-      case NS_STYLE_ALIGN_CONTENT_SPACE_AROUND: {
+      case NS_STYLE_ALIGN_SPACE_AROUND: {
         MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                    "negative packing space should make us use 'center' "
                    "instead of 'space-around'");
         // 1 packing space between each flex line, plus half a packing space
         // at beginning & end.  So our number of full packing-spaces is equal
         // to the number of flex lines.
         mNumPackingSpacesRemaining = numLines;
         // The edges (start/end) share one full packing space
@@ -2669,17 +2721,17 @@ CrossAxisPositionTracker::
         // ...and we'll use half of that right now, at the start
         mPosition += totalEdgePackingSpace / 2;
         // ...but we need to subtract all of it right away, so that we won't
         // hand out any of it to intermediate packing spaces.
         mPackingSpaceRemaining -= totalEdgePackingSpace;
         mNumPackingSpacesRemaining--;
         break;
       }
-      case NS_STYLE_ALIGN_CONTENT_STRETCH: {
+      case NS_STYLE_ALIGN_STRETCH: {
         // Split space equally between the lines:
         MOZ_ASSERT(mPackingSpaceRemaining > 0,
                    "negative packing space should make us use 'flex-start' "
                    "instead of 'stretch' (and we shouldn't bother with this "
                    "code if we have 0 packing space)");
 
         uint32_t numLinesLeft = numLines;
         for (FlexLine* line = aFirstLine; line; line = line->getNext()) {
@@ -2701,18 +2753,18 @@ CrossAxisPositionTracker::
     }
   }
 }
 
 void
 CrossAxisPositionTracker::TraversePackingSpace()
 {
   if (mNumPackingSpacesRemaining) {
-    MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_BETWEEN ||
-               mAlignContent == NS_STYLE_ALIGN_CONTENT_SPACE_AROUND,
+    MOZ_ASSERT(mAlignContent == NS_STYLE_ALIGN_SPACE_BETWEEN ||
+               mAlignContent == NS_STYLE_ALIGN_SPACE_AROUND,
                "mNumPackingSpacesRemaining only applies for "
                "space-between/space-around");
 
     MOZ_ASSERT(mPackingSpaceRemaining >= 0,
                "ran out of packing space earlier than we expected");
 
     // NOTE: This integer math will skew the distribution of remainder
     // app-units towards the end, which is fine.
@@ -2737,17 +2789,17 @@ FlexLine::ComputeCrossSizeAndBaseline(co
 {
   nscoord crossStartToFurthestBaseline = nscoord_MIN;
   nscoord crossEndToFurthestBaseline = nscoord_MIN;
   nscoord largestOuterCrossSize = 0;
   for (const FlexItem* item = mItems.getFirst(); item; item = item->getNext()) {
     nscoord curOuterCrossSize =
       item->GetOuterCrossSize(aAxisTracker.GetCrossAxis());
 
-    if (item->GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE &&
+    if (item->GetAlignSelf() == NS_STYLE_ALIGN_BASELINE &&
         item->GetNumAutoMarginsInAxis(aAxisTracker.GetCrossAxis()) == 0) {
       // FIXME: Once we support "writing-mode", we'll have to do baseline
       // alignment in vertical flex containers here (w/ horizontal cross-axes).
 
       // Find distance from our item's cross-start and cross-end margin-box
       // edges to its baseline.
       //
       // Here's a diagram of a flex-item that we might be doing this on.
@@ -2810,17 +2862,17 @@ FlexLine::ComputeCrossSizeAndBaseline(co
 void
 FlexItem::ResolveStretchedCrossSize(nscoord aLineCrossSize,
                                     const FlexboxAxisTracker& aAxisTracker)
 {
   AxisOrientationType crossAxis = aAxisTracker.GetCrossAxis();
   // We stretch IFF we are align-self:stretch, have no auto margins in
   // cross axis, and have cross-axis size property == "auto". If any of those
   // conditions don't hold up, we won't stretch.
-  if (mAlignSelf != NS_STYLE_ALIGN_ITEMS_STRETCH ||
+  if (mAlignSelf != NS_STYLE_ALIGN_STRETCH ||
       GetNumAutoMarginsInAxis(crossAxis) != 0 ||
       eStyleUnit_Auto != aAxisTracker.ComputedCrossSize(mFrame).GetUnit()) {
     return;
   }
 
   // If we've already been stretched, we can bail out early, too.
   // No need to redo the calculation.
   if (mIsStretched) {
@@ -2889,43 +2941,56 @@ SingleLineCrossAxisPositionTracker::
   // in the cross axis.
   if (aItem.GetNumAutoMarginsInAxis(mAxis)) {
     return;
   }
 
   uint8_t alignSelf = aItem.GetAlignSelf();
   // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any
   // auto-sized items (which we've already done).
-  if (alignSelf == NS_STYLE_ALIGN_ITEMS_STRETCH) {
-    alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START;
+  if (alignSelf == NS_STYLE_ALIGN_STRETCH) {
+    alignSelf = NS_STYLE_ALIGN_FLEX_START;
+  }
+
+  // Map 'start'/'end' to 'flex-start'/'flex-end'.
+  if (alignSelf == NS_STYLE_ALIGN_START) {
+    alignSelf = NS_STYLE_ALIGN_FLEX_START;
+  } else if (alignSelf == NS_STYLE_ALIGN_END) {
+    alignSelf = NS_STYLE_ALIGN_FLEX_END;
   }
 
   // If our cross axis is (internally) reversed, swap the align-self
   // "flex-start" and "flex-end" behaviors:
   if (aAxisTracker.AreAxesInternallyReversed()) {
-    if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_START) {
-      alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_END;
-    } else if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_END) {
-      alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START;
+    if (alignSelf == NS_STYLE_ALIGN_FLEX_START) {
+      alignSelf = NS_STYLE_ALIGN_FLEX_END;
+    } else if (alignSelf == NS_STYLE_ALIGN_FLEX_END) {
+      alignSelf = NS_STYLE_ALIGN_FLEX_START;
     }
   }
 
   switch (alignSelf) {
-    case NS_STYLE_ALIGN_ITEMS_FLEX_START:
+    case NS_STYLE_JUSTIFY_LEFT:
+    case NS_STYLE_JUSTIFY_RIGHT:
+    case NS_STYLE_ALIGN_SELF_START:
+    case NS_STYLE_ALIGN_SELF_END:
+    case NS_STYLE_ALIGN_LAST_BASELINE:
+      NS_WARNING("NYI: align-self:left/right/self-start/self-end/last-baseline");
+    case NS_STYLE_ALIGN_FLEX_START:
       // No space to skip over -- we're done.
       break;
-    case NS_STYLE_ALIGN_ITEMS_FLEX_END:
+    case NS_STYLE_ALIGN_FLEX_END:
       mPosition += aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis);
       break;
-    case NS_STYLE_ALIGN_ITEMS_CENTER:
+    case NS_STYLE_ALIGN_CENTER:
       // Note: If cross-size is odd, the "after" space will get the extra unit.
       mPosition +=
         (aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis)) / 2;
       break;
-    case NS_STYLE_ALIGN_ITEMS_BASELINE: {
+    case NS_STYLE_ALIGN_BASELINE: {
       // Normally, baseline-aligned items are collectively aligned with the
       // line's cross-start edge; however, if our cross axis is (internally)
       // reversed, we instead align them with the cross-end edge.
       AxisEdgeType baselineAlignEdge =
         aAxisTracker.AreAxesInternallyReversed() ?
         eAxisEdge_End : eAxisEdge_Start;
 
       nscoord itemBaselineOffset =
@@ -3439,17 +3504,17 @@ nsFlexContainerFrame::SizeItemInCrossAxi
     // for our cross size [width].)
     aItem.SetCrossSize(aChildReflowState.ComputedWidth());
     return;
   }
 
   MOZ_ASSERT(!aItem.HadMeasuringReflow(),
              "We shouldn't need more than one measuring reflow");
 
-  if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH) {
+  if (aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH) {
     // This item's got "align-self: stretch", so we probably imposed a
     // stretched computed height on it during its previous reflow. We're
     // not imposing that height for *this* measuring reflow, so we need to
     // tell it to treat this reflow as a vertical resize (regardless of
     // whether any of its ancestors are being resized).
     aChildReflowState.SetVResize(true);
   }
   nsHTMLReflowMetrics childDesiredSize(aChildReflowState);
@@ -3496,17 +3561,17 @@ nsFlexContainerFrame::SizeItemInCrossAxi
     // (normal case)
     aItem.SetCrossSize(childDesiredSize.Height() - crossAxisBorderPadding);
   }
 
   // If this is the first child, save its ascent, since it may be what
   // establishes the container's baseline. Also save the ascent if this child
   // needs to be baseline-aligned. (Else, we don't care about baseline/ascent.)
   if (aItem.Frame() == mFrames.FirstChild() ||
-      aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_BASELINE) {
+      aItem.GetAlignSelf() == NS_STYLE_ALIGN_BASELINE) {
     aItem.SetAscent(childDesiredSize.BlockStartAscent());
   }
 }
 
 void
 FlexLine::PositionItemsInCrossAxis(nscoord aLineStartPosition,
                                    const FlexboxAxisTracker& aAxisTracker)
 {
@@ -3731,17 +3796,18 @@ nsFlexContainerFrame::DoFlexLayout(nsPre
   const nscoord contentBoxCrossSize =
     ComputeCrossSize(aReflowState, aAxisTracker, sumLineCrossSizes,
                      aAvailableBSizeForContent, &isCrossSizeDefinite, aStatus);
 
   // Set up state for cross-axis alignment, at a high level (outside the
   // scope of a particular flex line)
   CrossAxisPositionTracker
     crossAxisPosnTracker(lines.getFirst(),
-                         aReflowState.mStylePosition->mAlignContent,
+                         aReflowState.mStylePosition->ComputedAlignContent(
+                           aReflowState.mStyleDisplay),
                          contentBoxCrossSize, isCrossSizeDefinite,
                          aAxisTracker);
 
   // Now that we know the cross size of each line (including
   // "align-content:stretch" adjustments, from the CrossAxisPositionTracker
   // constructor), we can create struts for any flex items with
   // "visibility: collapse" (and restart flex layout).
   if (aStruts.IsEmpty()) { // (Don't make struts if we already did)
@@ -3770,17 +3836,19 @@ nsFlexContainerFrame::DoFlexLayout(nsPre
           contentBoxCrossSize, aReflowState, aAxisTracker);
     }
   }
 
   for (FlexLine* line = lines.getFirst(); line; line = line->getNext()) {
 
     // Main-Axis Alignment - Flexbox spec section 9.5
     // ==============================================
-    line->PositionItemsInMainAxis(aReflowState.mStylePosition->mJustifyContent,
+    auto justifyContent =
+      aReflowState.mStylePosition->ComputedJustifyContent(aReflowState.mStyleDisplay);
+    line->PositionItemsInMainAxis(justifyContent,
                                   aContentBoxMainSize,
                                   aAxisTracker);
 
     // Cross-Axis Alignment - Flexbox spec section 9.6
     // ===============================================
     line->PositionItemsInCrossAxis(crossAxisPosnTracker.GetPosition(),
                                    aAxisTracker);
     crossAxisPosnTracker.TraverseLine(*line);
@@ -4010,17 +4078,17 @@ nsFlexContainerFrame::ReflowFlexItem(nsP
     didOverrideComputedWidth = true;
   } else {
     childReflowState.SetComputedHeight(aItem.GetMainSize());
     didOverrideComputedHeight = true;
   }
 
   // Override reflow state's computed cross-size, for stretched items.
   if (aItem.IsStretched()) {
-    MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_ITEMS_STRETCH,
+    MOZ_ASSERT(aItem.GetAlignSelf() == NS_STYLE_ALIGN_STRETCH,
                "stretched item w/o 'align-self: stretch'?");
     if (aAxisTracker.IsCrossAxisHorizontal()) {
       childReflowState.SetComputedWidth(aItem.GetCrossSize());
       didOverrideComputedWidth = true;
     } else {
       // If this item's height is stretched, it's a relative height.
       aItem.Frame()->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
       childReflowState.SetComputedHeight(aItem.GetCrossSize());
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4295,16 +4295,19 @@ nsFrame::ComputeSize(nsRenderingContext 
                      WritingMode aWM,
                      const LogicalSize& aCBSize,
                      nscoord aAvailableISize,
                      const LogicalSize& aMargin,
                      const LogicalSize& aBorder,
                      const LogicalSize& aPadding,
                      ComputeSizeFlags aFlags)
 {
+  MOZ_ASSERT(GetIntrinsicRatio() == nsSize(0,0),
+             "Please override this method and call "
+             "nsLayoutUtils::ComputeSizeWithIntrinsicDimensions instead.");
   LogicalSize result = ComputeAutoSize(aRenderingContext, aWM,
                                        aCBSize, aAvailableISize,
                                        aMargin, aBorder, aPadding,
                                        aFlags & ComputeSizeFlags::eShrinkWrap);
   LogicalSize boxSizingAdjust(aWM);
   const nsStylePosition *stylePos = StylePosition();
 
   switch (stylePos->mBoxSizing) {
@@ -4316,19 +4319,22 @@ nsFrame::ComputeSize(nsRenderingContext 
   }
   nscoord boxSizingToMarginEdgeISize =
     aMargin.ISize(aWM) + aBorder.ISize(aWM) + aPadding.ISize(aWM) -
     boxSizingAdjust.ISize(aWM);
 
   const nsStyleCoord* inlineStyleCoord = &stylePos->ISize(aWM);
   const nsStyleCoord* blockStyleCoord = &stylePos->BSize(aWM);
 
-  bool isFlexItem = IsFlexItem();
+  nsIAtom* parentFrameType = GetParent() ? GetParent()->GetType() : nullptr;
+  bool isGridItem = (parentFrameType == nsGkAtoms::gridContainerFrame &&
+                     !(GetStateBits() & NS_FRAME_OUT_OF_FLOW));
+  bool isFlexItem = (parentFrameType == nsGkAtoms::flexContainerFrame &&
+                     !(GetStateBits() & NS_FRAME_OUT_OF_FLOW));
   bool isInlineFlexItem = false;
- 
   if (isFlexItem) {
     // Flex items use their "flex-basis" property in place of their main-size
     // property (e.g. "width") for sizing purposes, *unless* they have
     // "flex-basis:auto", in which case they use their main-size property after
     // all.
     uint32_t flexDirection = GetParent()->StylePosition()->mFlexDirection;
     isInlineFlexItem =
       flexDirection == NS_STYLE_FLEX_DIRECTION_ROW ||
@@ -4360,39 +4366,45 @@ nsFrame::ComputeSize(nsRenderingContext 
 
   if (inlineStyleCoord->GetUnit() != eStyleUnit_Auto) {
     result.ISize(aWM) =
       nsLayoutUtils::ComputeISizeValue(aRenderingContext, this,
         aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize,
         *inlineStyleCoord);
   }
 
-  const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM);
-
   // Flex items ignore their min & max sizing properties in their
   // flex container's main-axis.  (Those properties get applied later in
   // the flexbox algorithm.)
+  const nsStyleCoord& maxISizeCoord = stylePos->MaxISize(aWM);
+  nscoord maxISize = NS_UNCONSTRAINEDSIZE;
   if (maxISizeCoord.GetUnit() != eStyleUnit_None &&
       !(isFlexItem && isInlineFlexItem)) {
-    nscoord maxISize =
+    maxISize =
       nsLayoutUtils::ComputeISizeValue(aRenderingContext, this,
         aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize,
         maxISizeCoord);
     result.ISize(aWM) = std::min(maxISize, result.ISize(aWM));
   }
 
   const nsStyleCoord& minISizeCoord = stylePos->MinISize(aWM);
-
   nscoord minISize;
   if (minISizeCoord.GetUnit() != eStyleUnit_Auto &&
       !(isFlexItem && isInlineFlexItem)) {
     minISize =
       nsLayoutUtils::ComputeISizeValue(aRenderingContext, this,
         aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM), boxSizingToMarginEdgeISize,
         minISizeCoord);
+  } else if (MOZ_UNLIKELY(isGridItem)) {
+    // This implements "Implied Minimum Size of Grid Items".
+    // https://drafts.csswg.org/css-grid/#min-size-auto
+    minISize = std::min(maxISize, GetMinISize(aRenderingContext));
+    if (inlineStyleCoord->IsCoordPercentCalcUnit()) {
+      minISize = std::min(minISize, result.ISize(aWM));
+    }
   } else {
     // Treat "min-width: auto" as 0.
     // NOTE: Technically, "auto" is supposed to behave like "min-content" on
     // flex items. However, we don't need to worry about that here, because
     // flex items' min-sizes are intentionally ignored until the flex
     // container explicitly considers them during space distribution.
     minISize = 0;
   }
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -425,22 +425,22 @@ void nsHTMLFramesetFrame::CalculateRowCo
                                           const nsFramesetSpec* aSpecs,
                                           nscoord*              aValues)
 {
   // aNumSpecs maximum value is NS_MAX_FRAMESET_SPEC_COUNT
   PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(int32_t));
 
   int32_t  fixedTotal = 0;
   int32_t  numFixed = 0;
-  nsAutoArrayPtr<int32_t> fixed(new int32_t[aNumSpecs]);
+  auto fixed = MakeUnique<int32_t[]>(aNumSpecs);
   int32_t  numPercent = 0;
-  nsAutoArrayPtr<int32_t> percent(new int32_t[aNumSpecs]);
+  auto percent = MakeUnique<int32_t[]>(aNumSpecs);
   int32_t  relativeSums = 0;
   int32_t  numRelative = 0;
-  nsAutoArrayPtr<int32_t> relative(new int32_t[aNumSpecs]);
+  auto relative = MakeUnique<int32_t[]>(aNumSpecs);
 
   if (MOZ_UNLIKELY(!fixed || !percent || !relative)) {
     return; // NS_ERROR_OUT_OF_MEMORY
   }
 
   int32_t i, j;
 
   // initialize the fixed, percent, relative indices, allocate the fixed sizes and zero the others
@@ -462,47 +462,47 @@ void nsHTMLFramesetFrame::CalculateRowCo
         numRelative++;
         relativeSums += aSpecs[i].mValue;
         break;
     }
   }
 
   // scale the fixed sizes if they total too much (or too little and there aren't any percent or relative)
   if ((fixedTotal > aSize) || ((fixedTotal < aSize) && (0 == numPercent) && (0 == numRelative))) {
-    Scale(aSize, numFixed, fixed, aNumSpecs, aValues);
+    Scale(aSize, numFixed, fixed.get(), aNumSpecs, aValues);
     return;
   }
 
   int32_t percentMax = aSize - fixedTotal;
   int32_t percentTotal = 0;
   // allocate the percentage sizes from what is left over from the fixed allocation
   for (i = 0; i < numPercent; i++) {
     j = percent[i];
     aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)aSize / 100.0f);
     percentTotal += aValues[j];
   }
 
   // scale the percent sizes if they total too much (or too little and there aren't any relative)
   if ((percentTotal > percentMax) || ((percentTotal < percentMax) && (0 == numRelative))) {
-    Scale(percentMax, numPercent, percent, aNumSpecs, aValues);
+    Scale(percentMax, numPercent, percent.get(), aNumSpecs, aValues);
     return;
   }
 
   int32_t relativeMax = percentMax - percentTotal;
   int32_t relativeTotal = 0;
   // allocate the relative sizes from what is left over from the percent allocation
   for (i = 0; i < numRelative; i++) {
     j = relative[i];
     aValues[j] = NSToCoordRound((float)aSpecs[j].mValue * (float)relativeMax / (float)relativeSums);
     relativeTotal += aValues[j];
   }
 
   // scale the relative sizes if they take up too much or too little
   if (relativeTotal != relativeMax) {
-    Scale(relativeMax, numRelative, relative, aNumSpecs, aValues);
+    Scale(relativeMax, numRelative, relative.get(), aNumSpecs, aValues);
   }
 }
 
 
 /**
   * Translate the rows/cols integer sizes into an array of specs for
   * each cell in the frameset.  Reverse of CalculateRowCol() behaviour.
   * This allows us to maintain the user size info through reflows.
@@ -847,38 +847,38 @@ nsHTMLFramesetFrame::Reflow(nsPresContex
     mDrag.UnSet();
     NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
     return;
   }
 
   CalculateRowCol(aPresContext, width, mNumCols, colSpecs, mColSizes.get());
   CalculateRowCol(aPresContext, height, mNumRows, rowSpecs, mRowSizes.get());
 
-  nsAutoArrayPtr<bool>  verBordersVis; // vertical borders visibility
-  nsAutoArrayPtr<nscolor> verBorderColors;
-  nsAutoArrayPtr<bool>  horBordersVis; // horizontal borders visibility
-  nsAutoArrayPtr<nscolor> horBorderColors;
+  UniquePtr<bool[]>  verBordersVis; // vertical borders visibility
+  UniquePtr<nscolor[]> verBorderColors;
+  UniquePtr<bool[]>  horBordersVis; // horizontal borders visibility
+  UniquePtr<nscolor[]> horBorderColors;
   nscolor                 borderColor = GetBorderColor();
   nsFrameborder           frameborder = GetFrameBorder();
 
   if (firstTime) {
     // Check for overflow in memory allocations using mNumCols and mNumRows
     // which have a maxium value of NS_MAX_FRAMESET_SPEC_COUNT.
     PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(bool));
     PR_STATIC_ASSERT(NS_MAX_FRAMESET_SPEC_COUNT < UINT_MAX / sizeof(nscolor));
 
-    verBordersVis = new bool[mNumCols];
-    verBorderColors = new nscolor[mNumCols];
+    verBordersVis = MakeUnique<bool[]>(mNumCols);
+    verBorderColors = MakeUnique<nscolor[]>(mNumCols);
     for (int verX  = 0; verX < mNumCols; verX++) {
       verBordersVis[verX] = false;
       verBorderColors[verX] = NO_COLOR;
     }
 
-    horBordersVis = new bool[mNumRows];
-    horBorderColors = new nscolor[mNumRows];
+    horBordersVis = MakeUnique<bool[]>(mNumRows);
+    horBorderColors = MakeUnique<nscolor[]>(mNumRows);
     for (int horX = 0; horX < mNumRows; horX++) {
       horBordersVis[horX] = false;
       horBorderColors[horX] = NO_COLOR;
     }
   }
 
   // reflow the children
   int32_t lastRow = 0;
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -582,16 +582,24 @@ struct MOZ_STACK_CLASS nsGridContainerFr
    */
   void CalculateSizes(GridReflowState&            aState,
                       nsTArray<GridItemInfo>&     aGridItems,
                       const TrackSizingFunctions& aFunctions,
                       nscoord                     aContentSize,
                       LineRange GridArea::*       aRange,
                       IntrinsicISizeType          aConstraint);
 
+  /**
+   * Apply 'align/justify-content', whichever is relevant for this axis.
+   * https://drafts.csswg.org/css-align-3/#propdef-align-content
+   */
+  void AlignJustifyContent(const nsHTMLReflowState& aReflowState,
+                           const LogicalSize& aContainerSize);
+
+
 #ifdef DEBUG
   void Dump() const
   {
     for (uint32_t i = 0, len = mSizes.Length(); i < len; ++i) {
       printf("  %d: ", i);
       mSizes[i].Dump();
       printf("\n");
     }
@@ -795,23 +803,28 @@ static bool
 IsNameWithStartSuffix(const nsString& aString, uint32_t* aIndex)
 {
   return IsNameWithSuffix(aString, NS_LITERAL_STRING("-start"), aIndex);
 }
 
 static nscoord
 GridLinePosition(uint32_t aLine, const nsTArray<TrackSize>& aTrackSizes)
 {
-  const uint32_t endIndex = aLine;
-  MOZ_ASSERT(endIndex <= aTrackSizes.Length(), "aTrackSizes is too small");
-  nscoord pos = 0;
-  for (uint32_t i = 0; i < endIndex; ++i) {
-    pos += aTrackSizes[i].mBase;
+  if (aTrackSizes.Length() == 0) {
+    // https://drafts.csswg.org/css-grid/#grid-definition
+    // "... the explicit grid still contains one grid line in each axis."
+    MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
+    return nscoord(0);
   }
-  return pos;
+  MOZ_ASSERT(aLine <= aTrackSizes.Length(), "aTrackSizes is too small");
+  if (aLine == aTrackSizes.Length()) {
+    const TrackSize& sz = aTrackSizes[aLine - 1];
+    return sz.mPosition + sz.mBase;
+  }
+  return aTrackSizes[aLine].mPosition;
 }
 
 /**
  * (XXX share this utility function with nsFlexContainerFrame at some point)
  *
  * Helper for BuildDisplayList, to implement this special-case for grid
  * items from the spec:
  *   The painting order of grid items is exactly the same as inline blocks,
@@ -824,16 +837,298 @@ GetDisplayFlagsForGridItem(nsIFrame* aFr
 {
   const nsStylePosition* pos = aFrame->StylePosition();
   if (pos->mZIndex.GetUnit() == eStyleUnit_Integer) {
     return nsIFrame::DISPLAY_CHILD_FORCE_STACKING_CONTEXT;
   }
   return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT;
 }
 
+static nscoord
+SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin,
+            LogicalAxis aAxis, nscoord aCBSize)
+{
+  nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM)
+                                            : aSize.ISize(aWM);
+  return aCBSize - (size + aMargin);
+}
+
+static bool
+AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis,
+                 bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS,
+                 const LogicalSize& aChildSize, LogicalSize* aContentSize,
+                 LogicalPoint* aPos)
+{
+  MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' "
+             "computed value for normal flow grid item");
+  MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT &&
+             aAlignment != NS_STYLE_ALIGN_RIGHT,
+             "caller should map that to the corresponding START/END");
+
+  // Map some alignment values to 'start' / 'end'.
+  switch (aAlignment) {
+    case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start
+      aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_START
+                                         : NS_STYLE_ALIGN_END;
+      break;
+    case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end
+      aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_END
+                                         : NS_STYLE_ALIGN_START;
+      break;
+    case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid
+      aAlignment = NS_STYLE_ALIGN_START;
+      break;
+    case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid
+      aAlignment = NS_STYLE_ALIGN_END;
+      break;
+  }
+
+  // XXX try to condense this code a bit by adding the necessary convenience
+  // methods? (bug 1209710)
+
+  // Get the item's margin corresponding to the container's start/end side.
+  const LogicalMargin margin = aRS.ComputedLogicalMargin();
+  WritingMode wm = aRS.GetWritingMode();
+  nscoord marginStart, marginEnd;
+  if (aAxis == eLogicalAxisBlock) {
+    if (MOZ_LIKELY(aSameSide)) {
+      marginStart = margin.BStart(wm);
+      marginEnd = margin.BEnd(wm);
+    } else {
+      marginStart = margin.BEnd(wm);
+      marginEnd = margin.BStart(wm);
+    }
+  } else {
+    if (MOZ_LIKELY(aSameSide)) {
+      marginStart = margin.IStart(wm);
+      marginEnd = margin.IEnd(wm);
+    } else {
+      marginStart = margin.IEnd(wm);
+      marginEnd = margin.IStart(wm);
+    }
+  }
+
+  // https://drafts.csswg.org/css-align-3/#overflow-values
+  // This implements <overflow-position> = 'safe'.
+  if (MOZ_UNLIKELY(aOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) {
+    nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd,
+                                aAxis, aCBSize);
+    // XXX we might want to include == 0 here as an optimization -
+    // I need to see what the baseline/last-baseline code looks like first.
+    if (space < 0) {
+      aAlignment = NS_STYLE_ALIGN_START;
+    }
+  }
+
+  // Set the position and size (aPos/aContentSize) for the requested alignment.
+  bool didResize = false;
+  nscoord offset = 0;
+  switch (aAlignment) {
+    case NS_STYLE_ALIGN_BASELINE:
+    case NS_STYLE_ALIGN_LAST_BASELINE:
+      NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX
+    case NS_STYLE_ALIGN_START:
+      offset = marginStart;
+      break;
+    case NS_STYLE_ALIGN_END: {
+      nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
+                                                : aChildSize.ISize(wm);
+      offset = aCBSize - (size + marginEnd);
+      break;
+    }
+    case NS_STYLE_ALIGN_CENTER:
+      offset = SpaceToFill(wm, aChildSize, marginStart + marginEnd,
+                           aAxis, aCBSize) / 2;
+      break;
+    case NS_STYLE_ALIGN_STRETCH: {
+      offset = marginStart;
+      const auto& styleMargin = aRS.mStyleMargin->mMargin;
+      if (aAxis == eLogicalAxisBlock
+             ? (aRS.mStylePosition->BSize(wm).GetUnit() == eStyleUnit_Auto &&
+                styleMargin.GetBStartUnit(wm) != eStyleUnit_Auto &&
+                styleMargin.GetBEndUnit(wm) != eStyleUnit_Auto)
+             : (aRS.mStylePosition->ISize(wm).GetUnit() == eStyleUnit_Auto &&
+                styleMargin.GetIStartUnit(wm) != eStyleUnit_Auto &&
+                styleMargin.GetIEndUnit(wm) != eStyleUnit_Auto)) {
+        nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
+                                                  : aChildSize.ISize(wm);
+        nscoord gap = aCBSize - (size + marginStart + marginEnd);
+        if (gap > 0) {
+          // Note: The ComputedMax* values are always content-box max values,
+          // even for box-sizing:border-box.
+          LogicalMargin bp = aRS.ComputedLogicalBorderPadding();
+          // XXX ApplySkipSides is probably not very useful here since we
+          // might not have created any next-in-flow yet.  Use the reflow status
+          // instead?  Do all fragments stretch? (bug 1144096).
+          bp.ApplySkipSides(aRS.frame->GetLogicalSkipSides());
+          nscoord bpInAxis = aAxis == eLogicalAxisBlock ? bp.BStartEnd(wm)
+                                                        : bp.IStartEnd(wm);
+          nscoord contentSize = size - bpInAxis;
+          NS_ASSERTION(contentSize >= 0, "huh?");
+          const nscoord unstretchedContentSize = contentSize;
+          contentSize += gap;
+          nscoord max = aAxis == eLogicalAxisBlock ? aRS.ComputedMaxBSize()
+                                                   : aRS.ComputedMaxISize();
+          if (MOZ_UNLIKELY(contentSize > max)) {
+            contentSize = max;
+            gap = contentSize - unstretchedContentSize;
+          }
+          // |gap| is now how much the content size is actually allowed to grow.
+          didResize = gap > 0;
+          if (didResize) {
+            (aAxis == eLogicalAxisBlock ? aContentSize->BSize(wm)
+                                        : aContentSize->ISize(wm)) = contentSize;
+            if (MOZ_UNLIKELY(!aSameSide)) {
+              offset += gap;
+            }
+          }
+        }
+      }
+      break;
+    }
+    default:
+      MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value");
+  }
+  if (offset != 0) {
+    nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm);
+    pos += MOZ_LIKELY(aSameSide) ? offset : -offset;
+  }
+  return didResize;
+}
+
+static bool
+SameSide(WritingMode aContainerWM, LogicalSide aContainerSide,
+         WritingMode aChildWM, LogicalSide aChildSide)
+{
+  MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) ==
+                 aChildWM.PhysicalAxis(GetAxis(aChildSide)));
+  return aContainerWM.PhysicalSide(aContainerSide) ==
+             aChildWM.PhysicalSide(aChildSide);
+}
+
+static Maybe<LogicalAxis>
+AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM,
+          const nsHTMLReflowState& aRS, const LogicalSize& aSize,
+          LogicalSize* aContentSize, LogicalPoint* aPos)
+{
+  Maybe<LogicalAxis> resizedAxis;
+  auto alignSelf = aAlignSelf;
+  bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE;
+  alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS;
+  MOZ_ASSERT(alignSelf != NS_STYLE_ALIGN_LEFT &&
+             alignSelf != NS_STYLE_ALIGN_RIGHT,
+             "Grid's 'align-self' axis is never parallel to the container's "
+             "inline axis, so these should've computed to 'start' already");
+  if (MOZ_UNLIKELY(alignSelf == NS_STYLE_ALIGN_AUTO)) {
+    // Happens in rare edge cases when 'position' was ignored by the frame
+    // constructor (and the style system computed 'auto' based on 'position').
+    alignSelf = NS_STYLE_ALIGN_STRETCH;
+  }
+  WritingMode childWM = aRS.GetWritingMode();
+  bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+  // |sameSide| is true if the container's start side in this axis is the same
+  // as the child's start side, in the child's parallel axis.
+  bool sameSide = SameSide(aCBWM, eLogicalSideBStart,
+                           childWM, isOrthogonal ? eLogicalSideIStart
+                                                 : eLogicalSideBStart);
+  LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
+  if (AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide,
+                       aCB.BSize(aCBWM), aRS, aSize, aContentSize, aPos)) {
+    resizedAxis.emplace(axis);
+  }
+  return resizedAxis;
+}
+
+static Maybe<LogicalAxis>
+JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM,
+            const nsHTMLReflowState& aRS, const LogicalSize& aSize,
+            LogicalSize* aContentSize, LogicalPoint* aPos)
+{
+  Maybe<LogicalAxis> resizedAxis;
+  auto justifySelf = aJustifySelf;
+  bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE;
+  justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS;
+  if (MOZ_UNLIKELY(justifySelf == NS_STYLE_ALIGN_AUTO)) {
+    // Happens in rare edge cases when 'position' was ignored by the frame
+    // constructor (and the style system computed 'auto' based on 'position').
+    justifySelf = NS_STYLE_ALIGN_STRETCH;
+  }
+  WritingMode childWM = aRS.GetWritingMode();
+  bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+  // |sameSide| is true if the container's start side in this axis is the same
+  // as the child's start side, in the child's parallel axis.
+  bool sameSide = SameSide(aCBWM, eLogicalSideIStart,
+                           childWM, isOrthogonal ? eLogicalSideBStart
+                                                 : eLogicalSideIStart);
+  // Grid's 'justify-self' axis is always parallel to the container's inline
+  // axis, so justify-self:left|right always applies.
+  switch (justifySelf) {
+    case NS_STYLE_JUSTIFY_LEFT:
+      justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START
+                                      : NS_STYLE_JUSTIFY_END;
+    break;
+    case NS_STYLE_JUSTIFY_RIGHT:
+      justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END
+                                      : NS_STYLE_JUSTIFY_START;
+    break;
+  }
+
+  LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
+  if (AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide,
+                       aCB.ISize(aCBWM), aRS, aSize, aContentSize, aPos)) {
+    resizedAxis.emplace(axis);
+  }
+  return resizedAxis;
+}
+
+static uint16_t
+GetAlignJustifyValue(uint16_t aAlignment, const WritingMode aWM,
+                     const bool aIsAlign, bool* aOverflowSafe)
+{
+  *aOverflowSafe = aAlignment & NS_STYLE_ALIGN_SAFE;
+  aAlignment &= (NS_STYLE_ALIGN_ALL_BITS & ~NS_STYLE_ALIGN_FLAG_BITS);
+
+  // Map some alignment values to 'start' / 'end'.
+  switch (aAlignment) {
+    case NS_STYLE_ALIGN_LEFT:
+    case NS_STYLE_ALIGN_RIGHT: {
+      MOZ_ASSERT(!aIsAlign, "Grid container's 'align-contents' axis is never "
+                 "parallel to its inline axis, so these should've computed to "
+                 "'start' already");
+      bool isStart = aWM.IsBidiLTR() == (aAlignment == NS_STYLE_ALIGN_LEFT);
+      return isStart ? NS_STYLE_ALIGN_START : NS_STYLE_ALIGN_END;
+    }
+    case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid
+      return NS_STYLE_ALIGN_START;
+    case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid
+      return NS_STYLE_ALIGN_END;
+  }
+  return aAlignment;
+}
+
+static uint16_t
+GetAlignJustifyFallbackIfAny(uint16_t aAlignment, const WritingMode aWM,
+                             const bool aIsAlign, bool* aOverflowSafe)
+{
+  uint16_t fallback = aAlignment >> NS_STYLE_ALIGN_ALL_SHIFT;
+  if (fallback) {
+    return GetAlignJustifyValue(fallback, aWM, aIsAlign, aOverflowSafe);
+  }
+  // https://drafts.csswg.org/css-align-3/#fallback-alignment
+  switch (aAlignment) {
+    case NS_STYLE_ALIGN_STRETCH:
+    case NS_STYLE_ALIGN_SPACE_BETWEEN:
+      return NS_STYLE_ALIGN_START;
+    case NS_STYLE_ALIGN_SPACE_AROUND:
+    case NS_STYLE_ALIGN_SPACE_EVENLY:
+      return NS_STYLE_ALIGN_CENTER;
+  }
+  return 0;
+}
+
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
 // =======================
 
 NS_QUERYFRAME_HEAD(nsGridContainerFrame)
   NS_QUERYFRAME_ENTRY(nsGridContainerFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
@@ -865,43 +1160,32 @@ nsGridContainerFrame::GridItemCB(nsIFram
   return *cb;
 }
 
 void
 nsGridContainerFrame::AddImplicitNamedAreas(
   const nsTArray<nsTArray<nsString>>& aLineNameLists)
 {
   // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
-  // XXX this just checks x-start .. x-end in one dimension and there's
-  // no other error checking.  A few wrong cases (maybe):
-  // (x-start x-end)
-  // (x-start) 0 (x-start) 0 (x-end)
-  // (x-end) 0 (x-start) 0 (x-end)
-  // (x-start) 0 (x-end) 0 (x-start) 0 (x-end)
+  // Note: recording these names for fast lookup later is just an optimization.
   const uint32_t len =
     std::min(aLineNameLists.Length(), size_t(nsStyleGridLine::kMaxLine));
   nsTHashtable<nsStringHashKey> currentStarts;
   ImplicitNamedAreas* areas = GetImplicitNamedAreas();
   for (uint32_t i = 0; i < len; ++i) {
-    const nsTArray<nsString>& names(aLineNameLists[i]);
-    const uint32_t jLen = names.Length();
-    for (uint32_t j = 0; j < jLen; ++j) {
-      const nsString& name = names[j];
+    for (const nsString& name : aLineNameLists[i]) {
       uint32_t index;
-      if (::IsNameWithStartSuffix(name, &index)) {
-        currentStarts.PutEntry(nsDependentSubstring(name, 0, index));
-      } else if (::IsNameWithEndSuffix(name, &index)) {
+      if (::IsNameWithStartSuffix(name, &index) ||
+          ::IsNameWithEndSuffix(name, &index)) {
         nsDependentSubstring area(name, 0, index);
-        if (currentStarts.Contains(area)) {
-          if (!areas) {
-            areas = new ImplicitNamedAreas;
-            Properties().Set(ImplicitNamedAreasProperty(), areas);
-          }
-          areas->PutEntry(area);
+        if (!areas) {
+          areas = new ImplicitNamedAreas;
+          Properties().Set(ImplicitNamedAreasProperty(), areas);
         }
+        areas->PutEntry(area);
       }
     }
   }
 }
 
 void
 nsGridContainerFrame::InitImplicitNamedAreas(const nsStylePosition* aStyle)
 {
@@ -951,18 +1235,16 @@ nsGridContainerFrame::ResolveLine(
         nsAutoString lineName(aLine.mLineName);
         if (aSide == eLineRangeSideStart) {
           lineName.AppendLiteral("-start");
           implicitLine = area ? area->*aAreaStart : 0;
         } else {
           lineName.AppendLiteral("-end");
           implicitLine = area ? area->*aAreaEnd : 0;
         }
-        // XXX must Implicit Named Areas have all four lines?
-        // http://dev.w3.org/csswg/css-grid/#implicit-named-areas
         line = ::FindNamedLine(lineName, &aNth, aFromIndex, implicitLine,
                                aLineNameList);
       }
     }
 
     if (line == 0) {
       // If mLineName ends in -start/-end, try the prefix as a named area.
       uint32_t implicitLine = 0;
@@ -1025,17 +1307,18 @@ nsGridContainerFrame::ResolveLineRangeHe
         // span <integer> / auto
         return LinePair(kAutoLine, aStart.mInteger);
       }
       // span <custom-ident> / span *
       // span <custom-ident> / auto
       return LinePair(kAutoLine, 1); // XXX subgrid explicit size instead of 1?
     }
 
-    auto end = ResolveLine(aEnd, aEnd.mInteger, 0, aLineNameList, aAreaStart,
+    uint32_t from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0;
+    auto end = ResolveLine(aEnd, aEnd.mInteger, from, aLineNameList, aAreaStart,
                            aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd,
                            aStyle);
     int32_t span = aStart.mInteger == 0 ? 1 : aStart.mInteger;
     if (end <= 1) {
       // The end is at or before the first explicit line, thus all lines before
       // it match <custom-ident> since they're implicit.
       int32_t start = std::max(end - span, nsStyleGridLine::kMinLine);
       return LinePair(start, end);
@@ -1058,43 +1341,46 @@ nsGridContainerFrame::ResolveLineRangeHe
         MOZ_ASSERT(aEnd.mInteger != 0);
         return LinePair(start, aEnd.mInteger);
       }
       // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
       // auto / span <custom-ident>
       return LinePair(start, 1); // XXX subgrid explicit size instead of 1?
     }
   } else {
-    start = ResolveLine(aStart, aStart.mInteger, 0, aLineNameList, aAreaStart,
-                        aAreaEnd, aExplicitGridEnd, eLineRangeSideStart,
-                        aStyle);
+    uint32_t from = aStart.mInteger < 0 ? aExplicitGridEnd : 0;
+    start = ResolveLine(aStart, aStart.mInteger, from, aLineNameList,
+                        aAreaStart, aAreaEnd, aExplicitGridEnd,
+                        eLineRangeSideStart, aStyle);
     if (aEnd.IsAuto()) {
       // A "definite line / auto" should resolve the auto to 'span 1'.
       // The error handling in ResolveLineRange will make that happen and also
       // clamp the end line correctly if we return "start / start".
       return LinePair(start, start);
     }
   }
 
-  uint32_t from = 0;
+  uint32_t from;
   int32_t nth = aEnd.mInteger == 0 ? 1 : aEnd.mInteger;
   if (aEnd.mHasSpan) {
     if (MOZ_UNLIKELY(start < 0)) {
       if (aEnd.mLineName.IsEmpty()) {
         return LinePair(start, start + nth);
       }
-      // Fall through and start searching from the start of the grid (from=0).
+      from = 0;
     } else {
       if (start >= int32_t(aExplicitGridEnd)) {
         // The start is at or after the last explicit line, thus all lines
         // after it match <custom-ident> since they're implicit.
         return LinePair(start, std::min(start + nth, nsStyleGridLine::kMaxLine));
       }
       from = start;
     }
+  } else {
+    from = aEnd.mInteger < 0 ? aExplicitGridEnd : 0;
   }
   auto end = ResolveLine(aEnd, nth, from, aLineNameList, aAreaStart,
                          aAreaEnd, aExplicitGridEnd, eLineRangeSideEnd, aStyle);
   if (start == int32_t(kAutoLine)) {
     // auto / definite line
     start = std::max(nsStyleGridLine::kMinLine, end - 1);
   }
   return LinePair(start, end);
@@ -1114,22 +1400,26 @@ nsGridContainerFrame::ResolveLineRange(
                                       aAreaEnd, aExplicitGridEnd, aStyle);
   MOZ_ASSERT(r.second != int32_t(kAutoLine));
 
   if (r.first == int32_t(kAutoLine)) {
     // r.second is a span, clamp it to kMaxLine - 1 so that the returned
     // range has a HypotheticalEnd <= kMaxLine.
     // http://dev.w3.org/csswg/css-grid/#overlarge-grids
     r.second = std::min(r.second, nsStyleGridLine::kMaxLine - 1);
-  } else if (r.second <= r.first) {
+  } else {
     // http://dev.w3.org/csswg/css-grid/#grid-placement-errors
-    if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) {
-      r.first = nsStyleGridLine::kMaxLine - 1;
+    if (r.first > r.second) {
+      Swap(r.first, r.second);
+    } else if (r.first == r.second) {
+      if (MOZ_UNLIKELY(r.first == nsStyleGridLine::kMaxLine)) {
+        r.first = nsStyleGridLine::kMaxLine - 1;
+      }
+      r.second = r.first + 1; // XXX subgrid explicit size instead of 1?
     }
-    r.second = r.first + 1; // XXX subgrid explicit size instead of 1?
   }
   return LineRange(r.first, r.second);
 }
 
 nsGridContainerFrame::GridArea
 nsGridContainerFrame::PlaceDefinite(nsIFrame* aChild,
                                     const nsStylePosition* aStyle)
 {
@@ -1643,40 +1933,32 @@ nsGridContainerFrame::Tracks::Initialize
   i += j;
   for (; i < mSizes.Length(); ++i) {
     mSizes[i].Initialize(percentageBasis,
                          aFunctions.mAutoMinSizing,
                          aFunctions.mAutoMaxSizing);
   }
 }
 
-static nscoord
-MinSize(nsIFrame* aChild, nsRenderingContext* aRC, WritingMode aCBWM,
-        LogicalAxis aAxis, nsLayoutUtils::IntrinsicISizeType aConstraint)
-{
-  PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
-  return nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild,
-                                                   aConstraint);
-}
-
 /**
  * Return the [min|max]-content contribution of aChild to its parent (i.e.
  * the child's margin-box) in aAxis.
  */
 static nscoord
 ContentContribution(nsIFrame*                         aChild,
                     const nsHTMLReflowState*          aReflowState,
                     nsRenderingContext*               aRC,
                     WritingMode                       aCBWM,
                     LogicalAxis                       aAxis,
-                    nsLayoutUtils::IntrinsicISizeType aConstraint)
+                    nsLayoutUtils::IntrinsicISizeType aConstraint,
+                    uint32_t                          aFlags = 0)
 {
   PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
   nscoord size = nsLayoutUtils::IntrinsicForAxis(axis, aRC, aChild, aConstraint,
-                   nsLayoutUtils::BAIL_IF_REFLOW_NEEDED);
+                   aFlags | nsLayoutUtils::BAIL_IF_REFLOW_NEEDED);
   if (size == NS_INTRINSIC_WIDTH_UNKNOWN) {
     // We need to reflow the child to find its BSize contribution.
     WritingMode wm = aChild->GetWritingMode();
     nsContainerFrame* parent = aChild->GetParent();
     nsPresContext* pc = aChild->PresContext();
     Maybe<nsHTMLReflowState> dummyParentState;
     const nsHTMLReflowState* rs = aReflowState;
     if (!aReflowState) {
@@ -1730,16 +2012,49 @@ MaxContentContribution(nsIFrame*        
                        nsRenderingContext*      aRC,
                        WritingMode              aCBWM,
                        LogicalAxis              aAxis)
 {
   return ContentContribution(aChild, aRS, aRC, aCBWM, aAxis,
                              nsLayoutUtils::PREF_ISIZE);
 }
 
+static nscoord
+MinSize(nsIFrame*                aChild,
+        const nsHTMLReflowState* aRS,
+        nsRenderingContext*      aRC,
+        WritingMode              aCBWM,
+        LogicalAxis              aAxis)
+{
+  PhysicalAxis axis(aCBWM.PhysicalAxis(aAxis));
+  const nsStylePosition* stylePos = aChild->StylePosition();
+  const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth
+                                                      : stylePos->mMinHeight;
+  // https://drafts.csswg.org/css-grid/#min-size-auto
+  // This calculates the min-content contribution from either a definite
+  // min-width (or min-height depending on aAxis), or the "specified /
+  // transferred size" for min-width:auto if overflow == visible (as min-width:0
+  // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
+  // (which results in always taking the "content size" part below).
+  nscoord sz =
+    nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, aChild,
+                                              nsLayoutUtils::MIN_ISIZE);
+  auto unit = style.GetUnit();
+  if (unit == eStyleUnit_Enumerated ||
+      (unit == eStyleUnit_Auto &&
+       aChild->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) {
+    // Now calculate the "content size" part and return whichever is smaller.
+    MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE);
+    sz = std::min(sz, ContentContribution(aChild, aRS, aRC, aCBWM, aAxis,
+                                          nsLayoutUtils::MIN_ISIZE,
+                                          nsLayoutUtils::MIN_INTRINSIC_ISIZE));
+  }
+  return sz;
+}
+
 void
 nsGridContainerFrame::Tracks::CalculateSizes(
   GridReflowState&            aState,
   nsTArray<GridItemInfo>&     aGridItems,
   const TrackSizingFunctions& aFunctions,
   nscoord                     aContentBoxSize,
   LineRange GridArea::*       aRange,
   IntrinsicISizeType          aConstraint)
@@ -1817,17 +2132,17 @@ nsGridContainerFrame::Tracks::ResolveInt
   Maybe<nscoord> minContentContribution;
   Maybe<nscoord> maxContentContribution;
   // min sizing
   TrackSize& sz = mSizes[aRange.mStart];
   WritingMode wm = aState.mWM;
   const nsHTMLReflowState* rs = aState.mReflowState;
   nsRenderingContext* rc = &aState.mRenderingContext;
   if (sz.mState & TrackSize::eAutoMinSizing) {
-    nscoord s = MinSize(aGridItem, rc, wm, mAxis, aConstraint);
+    nscoord s = MinSize(aGridItem, rs, rc, wm, mAxis);
     sz.mBase = std::max(sz.mBase, s);
   } else if ((sz.mState & TrackSize::eMinContentMinSizing) ||
              (aConstraint == nsLayoutUtils::MIN_ISIZE &&
               (sz.mState & TrackSize::eFlexMinSizing))) {
     nscoord s = MinContentContribution(aGridItem, rs, rc, wm, mAxis);
     minContentContribution.emplace(s);
     sz.mBase = std::max(sz.mBase, minContentContribution.value());
   } else if (sz.mState & TrackSize::eMaxContentMinSizing) {
@@ -1922,17 +2237,17 @@ nsGridContainerFrame::Tracks::ResolveInt
           stateBitsPerSpan.SetCapacity(len);
           for (uint32_t i = stateBitsPerSpan.Length(); i < len; ++i) {
             stateBitsPerSpan.AppendElement(TrackSize::StateBits(0));
           }
         }
         stateBitsPerSpan[span] |= state;
         nscoord minSize = 0;
         if (state & (flexMin | TrackSize::eIntrinsicMinSizing)) { // for 2.1
-          minSize = MinSize(child, rc, wm, mAxis, aConstraint);
+          minSize = MinSize(child, aState.mReflowState, rc, wm, mAxis);
         }
         nscoord minContent = 0;
         if (state & (flexMin | TrackSize::eMinOrMaxContentMinSizing | // for 2.2
                      TrackSize::eIntrinsicMaxSizing)) {               // for 2.5
           minContent = MinContentContribution(child, aState.mReflowState,
                                               rc, wm, mAxis);
         }
         nscoord maxContent = 0;
@@ -2251,51 +2566,176 @@ nsGridContainerFrame::Tracks::StretchFle
       if (flexLength > base) {
         base = flexLength;
       }
     }
   }
 }
 
 void
+nsGridContainerFrame::Tracks::AlignJustifyContent(
+  const nsHTMLReflowState& aReflowState,
+  const LogicalSize&     aContainerSize)
+{
+  if (mSizes.IsEmpty()) {
+    return;
+  }
+
+  const bool isAlign = mAxis == eLogicalAxisBlock;
+  auto stylePos = aReflowState.mStylePosition;
+  const auto valueAndFallback = isAlign ?
+    stylePos->ComputedAlignContent(aReflowState.mStyleDisplay) :
+    stylePos->ComputedJustifyContent(aReflowState.mStyleDisplay);
+  WritingMode wm = aReflowState.GetWritingMode();
+  bool overflowSafe;
+  auto alignment = ::GetAlignJustifyValue(valueAndFallback, wm, isAlign,
+                                          &overflowSafe);
+  if (alignment == NS_STYLE_ALIGN_AUTO) {
+    alignment = NS_STYLE_ALIGN_START;
+  }
+
+  // Compute the free space and count auto-sized tracks.
+  size_t numAutoTracks = 0;
+  nscoord space;
+  if (alignment != NS_STYLE_ALIGN_START) {
+    nscoord trackSizeSum = 0;
+    for (const TrackSize& sz : mSizes) {
+      trackSizeSum += sz.mBase;
+      if (sz.mState & TrackSize::eAutoMaxSizing) {
+        ++numAutoTracks;
+      }
+    }
+    nscoord cbSize = isAlign ? aContainerSize.BSize(wm)
+                             : aContainerSize.ISize(wm);
+    space = cbSize - trackSizeSum;
+    // Use the fallback value instead when applicable.
+    if (space < 0 ||
+        (alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) {
+      auto fallback = ::GetAlignJustifyFallbackIfAny(valueAndFallback, wm,
+                                                     isAlign, &overflowSafe);
+      if (fallback) {
+        alignment = fallback;
+      }
+    }
+    if (space == 0 || (space < 0 && overflowSafe)) {
+      // XXX check that this makes sense also for [last-]baseline (bug 1151204).
+      alignment = NS_STYLE_ALIGN_START;
+    }
+  }
+
+  // Optimize the cases where we just need to set each track's position.
+  nscoord pos = 0;
+  bool distribute = true;
+  switch (alignment) {
+    case NS_STYLE_ALIGN_BASELINE:
+    case NS_STYLE_ALIGN_LAST_BASELINE:
+      NS_WARNING("'NYI: baseline/last-baseline' (bug 1151204)"); // XXX
+    case NS_STYLE_ALIGN_START:
+      distribute = false;
+      break;
+    case NS_STYLE_ALIGN_END:
+      pos = space;
+      distribute = false;
+      break;
+    case NS_STYLE_ALIGN_CENTER:
+      pos = space / 2;
+      distribute = false;
+      break;
+    case NS_STYLE_ALIGN_STRETCH:
+      distribute = numAutoTracks != 0;
+      break;
+  }
+  if (!distribute) {
+    for (TrackSize& sz : mSizes) {
+      sz.mPosition = pos;
+      pos += sz.mBase;
+    }
+    return;
+  }
+
+  // Distribute free space to/between tracks and set their position.
+  MOZ_ASSERT(space > 0, "should've handled that on the fallback path above");
+  nscoord between, roundingError;
+  switch (alignment) {
+    case NS_STYLE_ALIGN_STRETCH: {
+      MOZ_ASSERT(numAutoTracks > 0, "we handled numAutoTracks == 0 above");
+      nscoord spacePerTrack;
+      roundingError = NSCoordDivRem(space, numAutoTracks, &spacePerTrack);
+      for (TrackSize& sz : mSizes) {
+#ifdef DEBUG
+        space += sz.mBase; // for the assert below: space + all mBase == pos
+#endif
+        sz.mPosition = pos;
+        if (!(sz.mState & TrackSize::eAutoMaxSizing)) {
+          pos += sz.mBase;
+          continue;
+        }
+        nscoord stretch = spacePerTrack;
+        if (roundingError) {
+          roundingError -= 1;
+          stretch += 1;
+        }
+        nscoord newBase = sz.mBase + stretch;
+        sz.mBase = newBase;
+        pos += newBase;
+      }
+      MOZ_ASSERT(pos == space && !roundingError,
+                 "we didn't distribute all space?");
+      return;
+    }
+    case NS_STYLE_ALIGN_SPACE_BETWEEN:
+      MOZ_ASSERT(mSizes.Length() > 1, "should've used a fallback above");
+      roundingError = NSCoordDivRem(space, mSizes.Length() - 1, &between);
+      break;
+    case NS_STYLE_ALIGN_SPACE_AROUND:
+      roundingError = NSCoordDivRem(space, mSizes.Length(), &between);
+      pos = between / 2;
+      break;
+    case NS_STYLE_ALIGN_SPACE_EVENLY:
+      roundingError = NSCoordDivRem(space, mSizes.Length() + 1, &between);
+      pos = between;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("unknown align-/justify-content value");
+  }
+  for (TrackSize& sz : mSizes) {
+    sz.mPosition = pos;
+    nscoord spacing = between;
+    if (roundingError) {
+      roundingError -= 1;
+      spacing += 1;
+    }
+    pos += sz.mBase + spacing;
+  }
+  MOZ_ASSERT(!roundingError, "we didn't distribute all space?");
+}
+
+void
 nsGridContainerFrame::LineRange::ToPositionAndLength(
   const nsTArray<TrackSize>& aTrackSizes, nscoord* aPos, nscoord* aLength) const
 {
   MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
              "expected a definite LineRange");
-  nscoord pos = 0;
-  const uint32_t start = mStart;
-  uint32_t i = 0;
-  for (; i < start; ++i) {
-    pos += aTrackSizes[i].mBase;
-  }
-  *aPos = pos;
-
-  nscoord length = 0;
-  const uint32_t end = mEnd;
-  MOZ_ASSERT(end <= aTrackSizes.Length(), "aTrackSizes isn't large enough");
-  for (; i < end; ++i) {
-    length += aTrackSizes[i].mBase;