merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 04 Nov 2015 11:59:46 +0100
changeset 271123 6077f51254c69a1e14e1b61acba4af451bf1783e
parent 271122 cd5fab20f6bd128847ef8131c9aeed63cde019fc (current diff)
parent 271023 287b4aa0d8653968e74930ce8cc2a61251f4170d (diff)
child 271124 8868503916baa3e2eec135e5b9b140717e402171
child 271258 6e5296397487dc014b07a9cc9dfc7b938e2b4f17
child 271271 d2906d28d9ed99ee4b2b93cc22bef718f3e81de4
push id67553
push usercbook@mozilla.com
push dateWed, 04 Nov 2015 11:31:24 +0000
treeherdermozilla-inbound@8868503916ba [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
6077f51254c6 / 45.0a1 / 20151104030437 / files
nightly linux64
6077f51254c6 / 45.0a1 / 20151104030437 / files
nightly mac
6077f51254c6 / 45.0a1 / 20151104030437 / files
nightly win32
6077f51254c6 / 45.0a1 / 20151104030437 / files
nightly win64
6077f51254c6 / 45.0a1 / 20151104030437 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
layout/reftests/css-grid/grid-abspos-items-011-ref.html
layout/reftests/css-grid/grid-abspos-items-011.html
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- 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/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/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/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/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;
-  }
-  *aLength = length;
+  MOZ_ASSERT(mStart < mEnd);
+  nscoord startPos = aTrackSizes[mStart].mPosition;
+  const TrackSize& sz = aTrackSizes[mEnd - 1];
+  *aPos = startPos;
+  *aLength = (sz.mPosition + sz.mBase) - startPos;
 }
 
 nscoord
 nsGridContainerFrame::LineRange::ToLength(
   const nsTArray<TrackSize>& aTrackSizes) const
 {
   MOZ_ASSERT(mStart != kAutoLine && mEnd != kAutoLine,
              "expected a definite LineRange");
-  nscoord length = 0;
-  const uint32_t end = mEnd;
-  MOZ_ASSERT(end <= aTrackSizes.Length(), "aTrackSizes isn't large enough");
-  for (uint32_t i = mStart; i < end; ++i) {
-    length += aTrackSizes[i].mBase;
-  }
-  return length;
+  MOZ_ASSERT(mStart < mEnd);
+  nscoord startPos = aTrackSizes[mStart].mPosition;
+  const TrackSize& sz = aTrackSizes[mEnd - 1];
+  return (sz.mPosition + sz.mBase) - startPos;
 }
 
 void
 nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos(
   const nsTArray<TrackSize>& aTrackSizes, nscoord aGridOrigin,
   nscoord* aPos, nscoord* aLength) const
 {
   // kAutoLine for abspos children contributes the corresponding edge
@@ -2362,62 +2802,91 @@ nsGridContainerFrame::ReflowChildren(Gri
   MOZ_ASSERT(aState.mReflowState);
 
   WritingMode wm = aState.mReflowState->GetWritingMode();
   const LogicalPoint gridOrigin(aContentArea.Origin(wm));
   const nsSize containerSize =
     (aContentArea.Size(wm) +
      aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm);
   nsPresContext* pc = PresContext();
+  nsStyleContext* containerSC = StyleContext();
   for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
     nsIFrame* child = *aState.mIter;
     const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame;
     LogicalRect cb(wm);
     if (MOZ_LIKELY(isGridItem)) {
       MOZ_ASSERT(mGridItems[aState.mIter.GridItemIndex()].mFrame == child,
                  "iterator out of sync with mGridItems");
       GridArea& area = mGridItems[aState.mIter.GridItemIndex()].mArea;
       MOZ_ASSERT(area.IsDefinite());
       cb = ContainingBlockFor(aState, area);
       cb += gridOrigin;
     } else {
       cb = aContentArea;
     }
     WritingMode childWM = child->GetWritingMode();
     LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm);
-    nsHTMLReflowState childRS(pc, *aState.mReflowState, child, childCBSize);
-    const LogicalMargin margin = childRS.ComputedLogicalMargin();
-    if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) {
-      // XXX the start of an align-self:stretch impl.  Needs min-/max-bsize
-      // clamping though, and check the prop value is actually 'stretch'!
-      LogicalMargin bp = childRS.ComputedLogicalBorderPadding();
-      bp.ApplySkipSides(child->GetLogicalSkipSides());
-      nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) -
-                        margin.BStartEnd(childWM);
-      childRS.SetComputedBSize(std::max(bSize, 0));
-    }
+    LogicalSize percentBasis(childCBSize);
+    // XXX temporary workaround to avoid being INCOMPLETE until we have
+    // support for fragmentation (bug 1144096)
+    childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
+
+    Maybe<nsHTMLReflowState> childRS; // Maybe<> so we can reuse the space
+    childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis);
     // We need the width of the child before we can correctly convert
     // the writing-mode of its origin, so we reflow at (0, 0) using a dummy
     // containerSize, and then pass the correct position to FinishReflowChild.
-    nsHTMLReflowMetrics childSize(childRS);
+    Maybe<nsHTMLReflowMetrics> childSize; // Maybe<> so we can reuse the space
+    childSize.emplace(*childRS);
     nsReflowStatus childStatus;
     const nsSize dummyContainerSize;
-    ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM),
+    ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM),
                 dummyContainerSize, 0, childStatus);
     LogicalPoint childPos =
       cb.Origin(wm).ConvertTo(childWM, wm,
-                              containerSize - childSize.PhysicalSize() -
-                              margin.Size(childWM).GetPhysicalSize(childWM));
-    childPos.I(childWM) += margin.IStart(childWM);
-    childPos.B(childWM) += margin.BStart(childWM);
-    childRS.ApplyRelativePositioning(&childPos, containerSize);
-    FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos,
+                              containerSize - childSize->PhysicalSize());
+    // Apply align/justify-self and reflow again if that affects the size.
+    if (isGridItem) {
+      LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild()
+      LogicalSize newContentSize(childWM);
+      auto align = childRS->mStylePosition->ComputedAlignSelf(
+                     childRS->mStyleDisplay, containerSC);
+      Maybe<LogicalAxis> alignResize =
+        AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos);
+      auto justify = childRS->mStylePosition->ComputedJustifySelf(
+                       childRS->mStyleDisplay, containerSC);
+      Maybe<LogicalAxis> justifyResize =
+        JustifySelf(justify, cb, wm, *childRS, oldSize, &newContentSize, &childPos);
+      if (alignResize || justifyResize) {
+        FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM,
+                          LogicalPoint(childWM), containerSize,
+                          NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW);
+        childSize.reset(); // In reverse declaration order since it runs
+        childRS.reset();   // destructors.
+        childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis);
+        if ((alignResize && alignResize.value() == eLogicalAxisBlock) ||
+            (justifyResize && justifyResize.value() == eLogicalAxisBlock)) {
+          childRS->SetComputedBSize(newContentSize.BSize(childWM));
+          childRS->SetBResize(true);
+        }
+        if ((alignResize && alignResize.value() == eLogicalAxisInline) ||
+            (justifyResize && justifyResize.value() == eLogicalAxisInline)) {
+          childRS->SetComputedISize(newContentSize.ISize(childWM));
+          childRS->SetIResize(true);
+        }
+        childSize.emplace(*childRS);
+        ReflowChild(child, pc, *childSize, *childRS, childWM,
+                    LogicalPoint(childWM), dummyContainerSize, 0, childStatus);
+      }
+    }
+    childRS->ApplyRelativePositioning(&childPos, containerSize);
+    FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, childPos,
                       containerSize, 0);
     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
-    // XXX deal with 'childStatus' not being COMPLETE
+    // XXX deal with 'childStatus' not being COMPLETE (bug 1144096)
   }
 
   if (IsAbsoluteContainer()) {
     nsFrameList children(GetChildList(GetAbsoluteListID()));
     if (!children.IsEmpty()) {
       LogicalMargin pad(aState.mReflowState->ComputedLogicalPadding());
       pad.ApplySkipSides(GetLogicalSkipSides(aState.mReflowState));
       // 'gridOrigin' is the origin of the grid (the start of the first track),
@@ -2501,16 +2970,21 @@ nsGridContainerFrame::Reflow(nsPresConte
   bSize = std::max(bSize - GetConsumedBSize(), 0);
   LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
                           bSize + bp.BStartEnd(wm));
   aDesiredSize.SetSize(wm, desiredSize);
   aDesiredSize.SetOverflowAreasToDesiredBounds();
 
   LogicalRect contentArea(wm, bp.IStart(wm), bp.BStart(wm),
                           computedISize, bSize);
+
+  // Apply 'align/justify-content' to the grid.
+  gridReflowState.mCols.AlignJustifyContent(aReflowState, contentArea.Size(wm));
+  gridReflowState.mRows.AlignJustifyContent(aReflowState, contentArea.Size(wm));
+
   gridReflowState.mIter.Reset(GridItemCSSOrderIterator::eIncludeAll);
   ReflowChildren(gridReflowState, contentArea, aDesiredSize, aStatus);
 
   FinishAndStoreOverflow(&aDesiredSize);
   aStatus = NS_FRAME_COMPLETE;
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
 }
 
@@ -2527,18 +3001,21 @@ nsGridContainerFrame::IntrinsicISize(nsR
     return 0;
   }
   state.mCols.Initialize(state.mColFunctions, mGridColEnd,
                          NS_UNCONSTRAINEDSIZE);
   state.mIter.Reset();
   state.mCols.CalculateSizes(state, mGridItems, state.mColFunctions,
                              NS_UNCONSTRAINEDSIZE, &GridArea::mCols,
                              aConstraint);
-  TranslatedLineRange allTracks(0, mGridColEnd);
-  return allTracks.ToLength(state.mCols.mSizes);
+  nscoord length = 0;
+  for (const TrackSize& sz : state.mCols.mSizes) {
+    length += sz.mBase;
+  }
+  return length;
 }
 
 nscoord
 nsGridContainerFrame::GetMinISize(nsRenderingContext* aRC)
 {
   DISPLAY_MIN_WIDTH(this, mCachedMinISize);
   if (mCachedMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
     mCachedMinISize = IntrinsicISize(aRC, nsLayoutUtils::MIN_ISIZE);
--- a/layout/generic/nsGridContainerFrame.h
+++ b/layout/generic/nsGridContainerFrame.h
@@ -77,16 +77,17 @@ public:
       eFrozen =                0x100,
       eSkipGrowUnlimited1 =    0x200,
       eSkipGrowUnlimited2 =    0x400,
       eSkipGrowUnlimited = eSkipGrowUnlimited1 | eSkipGrowUnlimited2,
     };
 
     nscoord mBase;
     nscoord mLimit;
+    nscoord mPosition;  // zero until we apply 'align/justify-content'
     StateBits mState;
   };
 
   // @see nsAbsoluteContainingBlock::Reflow about this magic number
   static const nscoord VERY_LIKELY_A_GRID_CONTAINER = -123456789;
 
   NS_DECLARE_FRAME_PROPERTY(GridItemContainingBlockRect, DeleteValue<nsRect>)
 
@@ -227,25 +228,24 @@ protected:
     {
       MOZ_ASSERT(aStart < aEnd && aEnd <= kTranslatedMaxLine);
       mStart = aStart;
       mEnd = aEnd;
     }
   };
 
   /**
-   * Return aLine if it's inside the aMin..aMax range (inclusive), otherwise
-   * return kAutoLine.  If the range is empty (aMin == aMax, i.e. there are
-   * no tracks in the grid) then aLine is outside.
+   * Return aLine if it's inside the aMin..aMax range (inclusive),
+   * otherwise return kAutoLine.
    */
   static int32_t
   AutoIfOutside(int32_t aLine, int32_t aMin, int32_t aMax)
   {
     MOZ_ASSERT(aMin <= aMax);
-    if (aLine < aMin || aLine > aMax || aMin == aMax) {
+    if (aLine < aMin || aLine > aMax) {
       return kAutoLine;
     }
     return aLine;
   }
 
   /**
    * A GridArea is the area in the grid for a grid item.
    * The area is represented by two LineRanges, both of which can be auto
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -13,17 +13,16 @@
 #include "nsFrame.h"
 #include "nsIContent.h"
 #include "nsGkAtoms.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsFontMetrics.h"
 #include "nsBlockFrame.h"
 #include "nsLineBox.h"
-#include "nsFlexContainerFrame.h"
 #include "nsImageFrame.h"
 #include "nsTableFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsIPercentBSizeObserver.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsFontInflationData.h"
 #include "StickyScrollContainer.h"
@@ -615,17 +614,17 @@ nsHTMLReflowState::InitResizeFlags(nsPre
       (mFlags.mSpecialBSizeReflow ||
        (frame->FirstInFlow()->GetStateBits() &
          NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) &&
       (frame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
     // Need to set the bit on the cell so that
     // mCBReflowState->IsBResize() is set correctly below when
     // reflowing descendant.
     SetBResize(true);
-  } else if (mCBReflowState && !nsLayoutUtils::IsNonWrapperBlock(frame)) {
+  } else if (mCBReflowState && frame->IsBlockWrapper()) {
     // XXX Is this problematic for relatively positioned inlines acting
     // as containing block for absolutely positioned elements?
     // Possibly; in that case we should at least be checking
     // NS_SUBTREE_DIRTY, I'd think.
     SetBResize(mCBReflowState->IsBResize());
   } else if (ComputedBSize() == NS_AUTOHEIGHT) {
     if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() &&
         mCBReflowState) {
@@ -2032,40 +2031,29 @@ IsSideCaption(nsIFrame* aFrame, const ns
   if (aStyleDisplay->mDisplay != NS_STYLE_DISPLAY_TABLE_CAPTION) {
     return false;
   }
   uint8_t captionSide = aFrame->StyleTableBorder()->mCaptionSide;
   return captionSide == NS_STYLE_CAPTION_SIDE_LEFT ||
          captionSide == NS_STYLE_CAPTION_SIDE_RIGHT;
 }
 
-static nsFlexContainerFrame*
-GetFlexContainer(nsIFrame* aFrame)
-{
-  nsIFrame* parent = aFrame->GetParent();
-  if (!parent ||
-      parent->GetType() != nsGkAtoms::flexContainerFrame) {
-    return nullptr;
-  }
-
-  return static_cast<nsFlexContainerFrame*>(parent);
-}
-
-// Flex items resolve block-axis percentage margin & padding against the flex
-// container's block-size (which is the containing block block-size).
+// Flex/grid items resolve block-axis percentage margin & padding against the
+// containing block block-size (also for abs/fixed-pos child frames).
 // For everything else: the CSS21 spec requires that margin and padding
 // percentage values are calculated with respect to the inline-size of the
 // containing block, even for margin & padding in the block axis.
 static LogicalSize
 OffsetPercentBasis(const nsIFrame*    aFrame,
                    WritingMode        aWM,
                    const LogicalSize& aContainingBlockSize)
 {
   LogicalSize offsetPercentBasis = aContainingBlockSize;
-  if (!aFrame->IsFlexOrGridItem()) {
+  if (MOZ_LIKELY(!aFrame->GetParent() ||
+                 !aFrame->GetParent()->IsFlexOrGridContainer())) {
     offsetPercentBasis.BSize(aWM) = offsetPercentBasis.ISize(aWM);
   } else if (offsetPercentBasis.BSize(aWM) == NS_AUTOHEIGHT) {
     offsetPercentBasis.BSize(aWM) = 0;
   }
 
   return offsetPercentBasis;
 }
 
@@ -2301,18 +2289,19 @@ nsHTMLReflowState::InitConstraints(nsPre
            (aFrameType == nsGkAtoms::scrollFrame &&
             frame->GetContentInsertionFrame()->GetType() == nsGkAtoms::legendFrame) ||
            (mCBReflowState &&
             mCBReflowState->GetWritingMode().IsOrthogonalTo(mWritingMode)))) {
         computeSizeFlags =
           ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap);
       }
 
-      const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame);
-      if (flexContainerFrame) {
+      nsIFrame* parent = frame->GetParent();
+      nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr;
+      if (parentFrameType == nsGkAtoms::flexContainerFrame) {
         computeSizeFlags =
           ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap);
 
         // If we're inside of a flex container that needs to measure our
         // auto height, pass that information along to ComputeSize().
         if (mFlags.mIsFlexContainerMeasuringHeight) {
           computeSizeFlags =
             ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eUseAutoHeight);
@@ -2338,21 +2327,23 @@ nsHTMLReflowState::InitConstraints(nsPre
                            computeSizeFlags);
 
       ComputedISize() = size.ISize(wm);
       ComputedBSize() = size.BSize(wm);
       NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size");
       NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE ||
                    ComputedBSize() >= 0, "Bogus block-size");
 
-      // Exclude inline tables and flex items from the block margin calculations
+      // Exclude inline tables, side captions, flex and grid items from block
+      // margin calculations.
       if (isBlock &&
           !IsSideCaption(frame, mStyleDisplay, cbwm) &&
           mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE &&
-          !flexContainerFrame) {
+          parentFrameType != nsGkAtoms::flexContainerFrame &&
+          parentFrameType != nsGkAtoms::gridContainerFrame) {
         CalculateBlockSideMargins(aFrameType);
       }
     }
   }
 }
 
 static void
 UpdateProp(FrameProperties& aProps,
--- a/layout/generic/nsHTMLReflowState.h
+++ b/layout/generic/nsHTMLReflowState.h
@@ -647,19 +647,19 @@ public:
    * state are copied from the parent's reflow state. The remainder is computed.
    *
    * @param aPresContext Must be equal to aFrame->PresContext().
    * @param aParentReflowState A reference to an nsHTMLReflowState object that
    *        is to be the parent of this object.
    * @param aFrame The frame for whose reflow state is being constructed.
    * @param aAvailableSpace See comments for availableHeight and availableWidth
    *        members.
-   * @param aContainingBlockSize An optional size, in app units, that
-   *        is used by absolute positioning code to override default containing
-   *        block sizes.
+   * @param aContainingBlockSize An optional size, in app units, specifying
+   *        the containing block size to use instead of the default which is
+   *        to use the aAvailableSpace.
    * @param aFlags A set of flags used for additional boolean parameters (see
    *        below).
    */
   nsHTMLReflowState(nsPresContext*              aPresContext,
                     const nsHTMLReflowState&    aParentReflowState,
                     nsIFrame*                   aFrame,
                     const mozilla::LogicalSize& aAvailableSpace,
                     const mozilla::LogicalSize* aContainingBlockSize = nullptr,
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2948,16 +2948,17 @@ NS_PTR_TO_INT32(frame->Properties().Get(
   /**
    * Is this a flex item? (i.e. a non-abs-pos child of a flex container)
    */
   inline bool IsFlexItem() const;
   /**
    * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid container)
    */
   inline bool IsFlexOrGridItem() const;
+  inline bool IsFlexOrGridContainer() const;
 
   /**
    * @return true if this frame is used as a table caption.
    */
   inline bool IsTableCaption() const;
 
   inline bool IsBlockInside() const;
   inline bool IsBlockOutside() const;
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -15,25 +15,29 @@ bool
 nsIFrame::IsFlexItem() const
 {
   return GetParent() &&
     GetParent()->GetType() == nsGkAtoms::flexContainerFrame &&
     !(GetStateBits() & NS_FRAME_OUT_OF_FLOW);
 }
 
 bool
+nsIFrame::IsFlexOrGridContainer() const
+{
+  nsIAtom* t = GetType();
+  return t == nsGkAtoms::flexContainerFrame ||
+         t == nsGkAtoms::gridContainerFrame;
+}
+
+bool
 nsIFrame::IsFlexOrGridItem() const
 {
-  if (GetParent()) {
-    nsIAtom* t = GetParent()->GetType();
-    return (t == nsGkAtoms::flexContainerFrame ||
-            t == nsGkAtoms::gridContainerFrame) &&
-      !(GetStateBits() & NS_FRAME_OUT_OF_FLOW);
-  }
-  return false;
+  return !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+         GetParent() &&
+         GetParent()->IsFlexOrGridContainer();
 }
 
 bool
 nsIFrame::IsTableCaption() const
 {
   return StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_TABLE_CAPTION &&
     GetParent()->StyleContext()->GetPseudo() == nsCSSAnonBoxes::tableOuter;
 }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5402,18 +5402,18 @@ nsTextFrame::DrawSelectionDecorations(gf
           relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
         } else if (!weDefineSelectionUnderline) {
           // There is no underline style definition.
           return;
         }
         // If underline color is defined and that doesn't depend on the
         // foreground color, we should use the color directly.
         if (aRangeStyle.IsUnderlineColorDefined() &&
-            aRangeStyle.IsForegroundColorDefined() &&
-            aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor) {
+            (!aRangeStyle.IsForegroundColorDefined() ||
+             aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
           color = aRangeStyle.mUnderlineColor;
         }
         // If foreground color or background color is defined, the both colors
         // are computed by GetSelectionTextColors().  Then, we should use its
         // foreground color always.  The color should have sufficient contrast
         // with the background color.
         else if (aRangeStyle.IsForegroundColorDefined() ||
                  aRangeStyle.IsBackgroundColorDefined()) {
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -383,17 +383,19 @@ RenderFrameParent::BuildLayer(nsDisplayL
   if (IsTempLayerManager(aManager) ||
       (mContainer && mContainer->Manager() != aManager)) {
     // This can happen if aManager is a "temporary" manager, or if the
     // widget's layer manager changed out from under us.  We need to
     // FIXME handle the former case somehow, probably with an API to
     // draw a manager's subtree.  The latter is bad bad bad, but the the
     // MOZ_ASSERT() above will flag it.  Returning nullptr here will just
     // cause the shadow subtree not to be rendered.
-    NS_WARNING("Remote iframe not rendered");
+    if (!aContainerParameters.mForEventsOnly) {
+      NS_WARNING("Remote iframe not rendered");
+    }
     return nullptr;
   }
 
   uint64_t id = GetLayerTreeId();
   if (!id) {
     return nullptr;
   }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: auto;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible fieldset below:
+<div>
+  <fieldset id="f">This is fieldset</fieldset>
+</div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: 0;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible fieldset below:
+<div>
+  <fieldset id="f">This is fieldset</fieldset>
+</div>
+<script>
+h = document.body.clientHeight;
+f.style.height = "auto";
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-2-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: auto;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible button below:
+<div>
+  <button id="f">This is button</button>
+</div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: 0;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible button below:
+<div>
+  <button id="f">This is button</button>
+</div>
+<script>
+h = document.body.clientHeight;
+f.style.height = "auto";
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-3-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: auto;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible table below:
+<div>
+  <table id="f">This is a table</table>
+</div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-3.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: 0;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible table below:
+<div>
+  <table id="f">This is a table</table>
+</div>
+<script>
+h = document.body.clientHeight;
+f.style.height = "auto";
+</script>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-4-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: auto;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible select below:
+<div>
+  <select id="f"><option>This is a select</option></select>
+</div>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1209994-4.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+div {
+  height: 240px;
+}
+#f {
+  height: 0;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+</style>
+There should be a visible select below:
+<div>
+  <select id="f"><option>This is a select</option></select>
+</div>
+<script>
+h = document.body.clientHeight;
+f.style.height = "auto";
+</script>
+
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1932,8 +1932,12 @@ skip-if(B2G||Mulet) == 1150021-1.xul 115
 fuzzy(1,74) fuzzy-if(gtkWidget,6,79) == 1174332-1.html 1174332-1-ref.html
 == 1179078-1.html 1179078-1-ref.html
 == 1179288-1.html 1179288-1-ref.html
 == 1190635-1.html 1190635-1-ref.html
 == 1202512-1.html 1202512-1-ref.html
 == 1202512-2.html 1202512-2-ref.html
 != 1207326-1.html about:blank
 == 1209603-1.html 1209603-1-ref.html
+== 1209994-1.html 1209994-1-ref.html
+== 1209994-2.html 1209994-2-ref.html
+== 1209994-3.html 1209994-3-ref.html
+== 1209994-4.html 1209994-4-ref.html
--- a/layout/reftests/css-display/display-contents-acid.html
+++ b/layout/reftests/css-display/display-contents-acid.html
@@ -27,17 +27,17 @@
 .colg    { display:table-column-group; }
 .flex    { display:flex; }
 .iflex   { display:inline-flex; }
 .li      { display:list-item; }
 .ib      { display:inline-block; }
 .inline  { display:inline; }
 .columns  { -moz-columns:2; columns:2; height:4em; }
 
-.contents { display:contents; }
+.contents { display:contents; align-items:inherit; justify-items:inherit; }
 
 .c1 { color:lime; }
 .c2 { background:blue; color:pink; }
 .c3 { color:teal; }
 .c4 { color:green; }
 .c5 { color:silver; }
 .c6 { color:cyan; }
 .c7 { color:magenta; }
--- a/layout/reftests/css-grid/grid-abspos-items-001-ref.html
+++ b/layout/reftests/css-grid/grid-abspos-items-001-ref.html
@@ -25,16 +25,17 @@ body,html { color:black; background:whit
 }
 .zero-size { width:0; height:0; }
 .auto-size { width:auto; height:0px; }
 
 .a {
   position: absolute;
   left: 13px; top: 31px;
   height: 12px; width: 44px;
+  background: blue;
 }
 
 .abs {
   position: absolute;
   opacity: 0.7;
   width: 21px;
   height: 15px;
 }
@@ -42,17 +43,17 @@ body,html { color:black; background:whit
   left: 1px; top: 3px; bottom: 1px;
   width: 28px; height: auto;
 }
 .c {
   right: 5px; bottom: 1px;
   width: 112px; height: 82px;
 }
 .d {
-  left: 1px; top: 27px;
+  left: 1px; top: 20px;
   width: 5px; height: 1px;
 }
 .e {
   right: 5px; bottom: 1px;
   width: 112px; height: 51px;
 }
 .f {
   right: 5px; top: 3px;
@@ -77,74 +78,74 @@ span {
 }
   </style>
 </head>
 <body>
 
 <div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid zero-size">
 <span class="b abs" style="width:12px">b</span>
 </div>
@@ -157,27 +158,27 @@ span {
 <span class="i abs">i</span>
 </div>
 
 <div class="grid" style="height:7px">
 <span class="j abs">j</span>
 </div>
 
 <div class="grid" style="width:43px; height:53px">
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">a</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">b</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">c</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">d</span>
+<span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span>
+<span class="abs" style="right:5px; top:3px; height:11px; width:42px;">b</span>
+<span class="abs" style="left:1px; bottom:1px; height:58px; width:5px;">c</span>
+<span class="abs" style="right:5px; bottom:1px; height:58px; width:42px;">d</span>
 </div>
 
 <div class="grid" style="width:43px; height:28px; border-width:0;">
-<span class="abs" style="right:48px; top:3px; height:22px; width:12px;"></span>
+<span class="abs" style="right:48px; top:3px; height:11px; width:12px;"></span>
 </div>
 <div class="grid" style="width:43px; height:28px; border-width:0;">
-<span class="abs" style="left:1px; bottom:57px; height:22px; width:12px;"></span>
+<span class="abs" style="left:1px; bottom:57px; height:22px; width:5px;"></span>
 </div>
 <div class="grid" style="width:43px; height:28px; border-width:0;">
 <span class="abs" style="right:48px; bottom:85px; height:22px; width:12px;"></span>
 </div>
 
 </div>
 
 </body>
--- a/layout/reftests/css-grid/grid-abspos-items-001.html
+++ b/layout/reftests/css-grid/grid-abspos-items-001.html
@@ -24,16 +24,17 @@ body,html { color:black; background:whit
  height: 60px;
 }
 .zero-size { width:0; height:0; }
 .auto-size { width:auto; height:auto; }
 
 .a {
   grid-column: 1 / 3; 
   grid-row:    3 / 5;
+  background: blue;
 }
 
 .abs {
   position: absolute;
   top:3px; left:1px; right:5px; bottom:1px;
   opacity: 0.7;
 }
 .b {
@@ -80,74 +81,74 @@ span {
 }
   </style>
 </head>
 <body>
 
 <div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <div><span class="b abs">b</span></div>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <x><span class="c abs">c</span></x>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <div><span class="e abs">e</span></div>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <x><span class="g abs">g</span></x>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid zero-size">
 <span class="b abs">b</span>
 </div>
--- a/layout/reftests/css-grid/grid-abspos-items-002-ref.html
+++ b/layout/reftests/css-grid/grid-abspos-items-002-ref.html
@@ -26,16 +26,17 @@ body,html { color:black; background:whit
 }
 .zero-size { width:0; height:0; }
 .auto-size { width:auto; height:0px; }
 
 .a {
   position: absolute;
   left: 13px; top: 31px;
   height: 12px; width: 44px;
+  background: blue;
 }
 
 .abs {
   position: absolute;
   opacity: 0.7;
   width: 21px;
   height: 15px;
 }
@@ -43,17 +44,17 @@ body,html { color:black; background:whit
   left: 1px; top: 3px; bottom: 1px;
   width: 28px; height: auto;
 }
 .c {
   right: 5px; bottom: 1px;
   width: 112px; height: 82px;
 }
 .d {
-  left: 1px; top: 27px;
+  left: 1px; top: 20px;
   width: 5px; height: 1px;
 }
 .e {
   right: 5px; bottom: 1px;
   width: 112px; height: 51px;
 }
 .f {
   right: 5px; top: 3px;
@@ -78,74 +79,74 @@ span {
 }
   </style>
 </head>
 <body>
 
 <div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid zero-size">
 <span class="b abs" style="width:12px">b</span>
 </div>
@@ -158,18 +159,18 @@ span {
 <span class="i abs">i</span>
 </div>
 
 <div class="grid" style="height:7px">
 <span class="j abs">j</span>
 </div>
 
 <div class="grid" style="width:43px; height:53px">
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">a</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">b</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">c</span>
-<span class="abs" style="width:auto;height:auto; top:3px; left:1px; bottom:1px; right:5px">d</span>
+<span class="abs" style="left:1px; top:3px; height:11px; width:5px;">a</span>
+<span class="abs" style="right:5px; top:3px; height:11px; width:42px;">b</span>
+<span class="abs" style="left:1px; bottom:1px; height:58px; width:5px;">c</span>
+<span class="abs" style="right:5px; bottom:1px; height:58px; width:42px;">d</span>
 </div>
 
 </div>
 
 </body>
 </html>
--- a/layout/reftests/css-grid/grid-abspos-items-002.html
+++ b/layout/reftests/css-grid/grid-abspos-items-002.html
@@ -24,16 +24,17 @@ body,html { color:black; background:whit
  height: 60px;
 }
 .zero-size { width:0; height:0; }
 .auto-size { width:auto; height:auto; }
 
 .a {
   grid-column: 1 / 3; 
   grid-row:    3 / 5;
+  background: blue;
 }
 
 .abs {
   position: fixed;
   top:3px; left:1px; right:5px; bottom:1px;
   opacity: 0.7;
 }
 .b {
@@ -80,74 +81,74 @@ span {
 }
   </style>
 </head>
 <body>
 
 <div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="b abs">b</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="c abs">c</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="e abs">e</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="g abs">g</span>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <div><span class="b abs">b</span></div>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <x><span class="c abs">c</span></x>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="d abs">d</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <div><span class="e abs">e</span></div>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <span class="f abs">f</span>
 </div>
 
 <div class="grid">
-<span class="a">a</span>
+<span class="a"></span>
 <x><span class="g abs">g</span></x>
 </div>
 
 </div><div style="float:left">
 
 <div class="grid zero-size">
 <span class="b abs">b</span>
 </div>
--- a/layout/reftests/css-grid/grid-abspos-items-011-ref.html
+++ b/layout/reftests/css-grid/grid-abspos-items-011-ref.html
@@ -1,27 +1,31 @@
 <!DOCTYPE HTML>
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html><head>
   <meta charset="utf-8">
   <title>CSS Grid Test: abs pos areas in empty grid</title>
-  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1215099">
   <style type="text/css">
 
 div {
   display: block;
   position: relative;
   float: left;
   width: 20px;
   height: 20px;
   background: red;
 }
+x div {
+  padding: 4px;
+  background: white;
+}
 
 span {
   position: absolute;
   top:0;left:0;bottom:0;right:0;
   background: lime;
 }
 
 </style>
@@ -29,26 +33,50 @@ span {
 <body>
 
 There should be no red areas.
 <br clear="all">
 
 <div><span class="cs"></span></div>
 <div><span class="ce"></span></div>
 <div><span class="rs"></span></div>
-<div><span class="rs"></span></div>
+<div><span class="re"></span></div>
 
 <div><span class="cs ce"></span></div>
 <div><span class="cs rs"></span></div>
 <div><span class="cs re"></span></div>
 <div><span class="ce rs"></span></div>
 <div><span class="ce re"></span></div>
 <div><span class="rs re"></span></div>
 
 <div><span class="cs ce rs"></span></div>
 <div><span class="cs ce re"></span></div>
 <div><span class="rs re cs"></span></div>
 <div><span class="rs re ce"></span></div>
 
 <div><span class="cs ce rs re"></span></div>
 
+<br clear="all">
+<br clear="all">
+
+<x>
+<div><span class="cs" style="left:4px"></span></div>
+<div><span class="ce" style="right:auto;width:4px"></span></div>
+<div><span class="rs" style="top:4px"></span></div>
+<div><span class="re" style="bottom:auto;height:4px"></span></div>
+
+<div><span class="cs ce" style="left:4px;"></span></div>
+<div><span class="cs rs" style="left:4px;top:4px"></span></div>
+<div><span class="cs re" style="left:4px;bottom:auto;height:4px"></span></div>
+<div><span class="ce rs" style="right:auto;width:4px;top:4px"></span></div>
+<div><span class="ce re" style="right:auto;width:4px;bottom:auto;height:4px"></span></div>
+<div><span class="rs re" style="top:4px"></span></div>
+
+<div><span class="cs ce rs" style="left:4px;top:4px"></span></div>
+<div><span class="cs ce re" style="left:4px;bottom:auto;height:4px"></span></div>
+<div><span class="rs re cs" style="left:4px;top:4px"></span></div>
+<div><span class="rs re ce" style="top:4px;right:auto;width:4px"></span></div>
+
+<div><span class="cs ce rs re" style="left:4px;top:4px"></span></div>
+</x>
+
 </body>
 </html>
--- a/layout/reftests/css-grid/grid-abspos-items-011.html
+++ b/layout/reftests/css-grid/grid-abspos-items-011.html
@@ -1,61 +1,97 @@
 <!DOCTYPE HTML>
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html><head>
   <meta charset="utf-8">
   <title>CSS Grid Test: abs pos areas in empty grid</title>
-  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1215099">
   <link rel="help" href="http://dev.w3.org/csswg/css-grid/#abspos-items">
   <link rel="match" href="grid-abspos-items-011-ref.html">
   <style type="text/css">
 
 div {
   display: grid;
   position: relative;
   float: left;
   width: 20px;
   height: 20px;
   background: red;
 }
+div.green {
+  background: lime;
+}
+x div {
+  padding: 4px;
+  background: white;
+}
 
 span {
   position: absolute;
   top:0;left:0;bottom:0;right:0;
   background: lime;
 }
+span.red {
+  background: red;
+}
 
 .cs { grid-column-start: 1; }
 .ce { grid-column-end: 1; }
 .rs { grid-row-start: 1; }
 .re { grid-row-end: 1; }
 
 </style>
 </head>
 <body>
 
 There should be no red areas.
 <br clear="all">
 
 <div><span class="cs"></span></div>
+<div class="green"><span class="ce red"></span></div>
+<div><span class="rs"></span></div>
+<div class="green"><span class="re red"></span></div>
+
+<div><span class="cs ce"></span></div>
+<div><span class="cs rs"></span></div>
+<div class="green"><span class="cs re red"></span></div>
+<div class="green"><span class="ce rs red"></span></div>
+<div class="green"><span class="ce re red"></span></div>
+<div><span class="rs re"></span></div>
+
+<div><span class="cs ce rs"></span></div>
+<div class="green"><span class="cs ce re red"></span></div>
+<div><span class="rs re cs"></span></div>
+<div class="green"><span class="rs re ce red"></span></div>
+
+<div><span class="cs ce rs re"></span></div>
+
+<br clear="all">
+<br clear="all">
+
+<!-- the same combinations in a grid container with padding -->
+
+<x>
+<div><span class="cs"></span></div>
 <div><span class="ce"></span></div>
 <div><span class="rs"></span></div>
-<div><span class="rs"></span></div>
+<div><span class="re"></span></div>
 
 <div><span class="cs ce"></span></div>
 <div><span class="cs rs"></span></div>
 <div><span class="cs re"></span></div>
 <div><span class="ce rs"></span></div>
 <div><span class="ce re"></span></div>
 <div><span class="rs re"></span></div>
 
 <div><span class="cs ce rs"></span></div>
 <div><span class="cs ce re"></span></div>
 <div><span class="rs re cs"></span></div>
 <div><span class="rs re ce"></span></div>
 
 <div><span class="cs ce rs re"></span></div>
+</x>
 
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-align-content-001-ref.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: align-content</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151214">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 11px 7px 5px / 3px 5px 7px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 40px;
+  height: 40px;
+}
+
+item1,item2,item3 {
+  display: block;
+  position: relative;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.aend *, .aflexend * { offset-block-start:25px; }
+.acenter * { offset-block-start:12.5px;  }
+
+
+.aspace-between item2 { offset-block-start:12.5px; }
+.aspace-between item3 { offset-block-start:25px; }
+
+.aspace-around item1 { offset-block-start:4.1666px; }
+.aspace-around item2 { offset-block-start:12.5px; }
+.aspace-around item3 { offset-block-start:20.8333px; }
+
+.aspace-evenly item1 { offset-block-start:6.25px; }
+.aspace-evenly item2 { offset-block-start:12.5px; }
+.aspace-evenly item3 { offset-block-start:18.75px; }
+
+.astretch2 { grid-template-rows: 1fr 5px 7px; }
+.astretch3 { grid-template-rows: 15.5px 17.5px 7px; }
+.astretch4 { grid-template-rows: 11.33333px 13.33333px 15.33333px; }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-align-content-001.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: align-content</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151214">
+  <link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content">
+  <link rel="match" href="grid-align-content-001-ref.html">
+  <style type="text/css">
+html,body {
+    color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+separator { clear:both; display:block; height:6px; }
+
+.grid {
+  display: grid;
+  float: left;
+  position: relative;
+  border: 1px solid;
+  border-block-start: 2px solid blue;
+  grid-template: 11px 7px 5px / 3px 5px 7px;
+  padding: 1px 1px 3px 2px;
+  margin-right: 4px;
+  width: 40px;
+  height: 40px;
+}
+
+item1,item2,item3 {
+  display: block;
+  background: grey;
+  justify-self: stretch;
+  align-self: stretch;
+}
+
+item1 { grid-area: 1 / 1; }
+item2 { grid-area: 2 / 2; }
+item3 { grid-area: 3 / 3; }
+
+.hl  { writing-mode: horizontal-tb; direction:ltr; }
+.hr  { writing-mode: horizontal-tb; direction:rtl; }
+.vl  { writing-mode: vertical-lr; }
+.vr  { writing-mode: vertical-rl; }
+.vlr { writing-mode: vertical-lr; direction:rtl; }
+.vrl { writing-mode: vertical-rl; direction:ltr; }
+
+.astart { align-content:start; }
+.aend { align-content:end; }
+.aflexstart { align-content:flex-start; }
+.aflexend { align-content:flex-end; }
+.acenter { align-content:center; }
+.aleft { align-content:left; }
+.aright { align-content:right; }
+
+.aspace-between{ align-content:space-between; }
+.aspace-around { align-content:space-around;  }
+.aspace-evenly { align-content:space-evenly;  }
+
+.astretch1, .astretch2, .astretch3, .astretch4 { align-content:stretch; }
+.astretch2 { grid-template-rows: minmax(3px,auto) 5px 7px; }
+.astretch3 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) 7px; }
+.astretch4 { grid-template-rows: minmax(3px,auto) minmax(5px,auto) minmax(7px,auto); }
+
+</style>
+</head>
+<body>
+
+<script>
+var gridwm = [ "hl", "hr", "vl", "vr", "vlr", "vrl" ];
+var test = [ "start", "end", "flexstart", "flexend", "center", "left", "right",
+             "space-between", "space-around", "space-evenly",
+             "stretch1", "stretch2", "stretch3", "stretch4" ];
+for (var k = 0; k < test.length; ++k) {
+  for (var i = 0; i < gridwm.length; ++i) {
+      var div = document.createElement("div");
+      div.className = "grid a" + test[k] + " " + gridwm[i];
+      div.appendChild(document.createElement("item1"));
+      div.appendChild(document.createElement("item2"));
+      div.appendChild(document.createElement("item3"));
+      document.body.appendChild(div)
+  }
+    document.body.appendChild(document.createElement("separator"));
+}
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001-ref.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: Testing 'auto' min-sizing with definite min-width/height</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176775">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:12px; padding:0; margin:0; }
+pre { clear:both; }
+	
+.grid {
+  display: grid;
+  border: 1px dashed silver;
+  grid-template-columns: minmax(0,min-content);
+  grid-template-rows: minmax(0,min-content) minmax(0,min-content);
+  float: left;
+  margin-bottom:20px;
+  align-items: start;
+  justify-items: start;
+}
+.v { 
+  writing-mode: vertical-lr;
+  -webkit-writing-mode: vertical-lr;
+}
+.h { 
+  writing-mode: horizontal-tb;
+  -webkit-writing-mode: horizontal-tb;
+}
+.bb {
+  box-sizing: border-box;
+}
+
+span { 
+  display: block;
+  background: lime;
+  margin: 7px 13px 50% 20%;
+  padding:1px 3px 10% 30%;
+}
+
+span.v {
+  height: 30px;
+  width: 10px;
+  border-left: 1px solid;
+  border-top: 3px solid;
+}
+
+span.h {
+  width: 30px;
+  height: 10px;
+  border-left: 3px solid;
+  border-top: 1px solid;
+}
+
+b40 {
+  display: block;
+  width: 40px;
+  height: 40px;
+  border: 1px solid pink;
+  z-index: 1; position:relative; 
+}
+
+w {
+  position:absolute;
+  background: lime;
+  border-width: 1px 0 0 3px;
+  border-style: solid;
+  height:45px;
+  width:48px;
+  left:-3px; top:-1px;
+}
+.v > w {
+  border-width: 3px 0 0 1px;
+  left:-1px; top:-3px;
+  height:53px;
+  width:61px;
+}
+.v .h > w {
+  height:48px;
+  width:45px;
+}
+.v.r > w {
+  height:47px;
+  width:54px;
+}
+.v .v > w {
+  height:41px;
+  width:45px;
+}
+
+.r { position:relative; }
+
+  </style>
+</head>
+<body>
+
+<pre>horizontal container, horizontal item</pre>
+<div class="grid"><span class="h"><x></x></span></div>
+<div class="grid"><span class="h bb"><x></x></span></div>
+<div class="grid t3"><span class="h"><x></x></span><span class="h"><x></x></span></div>
+<div class="grid"><span class="h r"><w></w><b40></b40></span></div>
+<pre>horizontal container, vertical item</pre>
+<div class="grid"><span class="v"><x></x></span></div>
+<div class="grid"><span class="v bb"><x></x></span></div>
+<div class="grid t7"><span class="v"><x></x></span><span class="v"><x></x></span></div>
+<div class="grid"><span class="v r"><w></w><b40></b40></span></div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-grid/grid-auto-min-sizing-definite-001.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Grid Test: 'auto' min-sizing with definite min-width/height</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176775">
+  <link rel="help" href="https://drafts.csswg.org/css-grid/#min-size-auto">
+  <link rel="match" href="grid-auto-min-sizing-definite-001-ref.html">
+  <style type="text/css">
+body,html { color:black; background:white; font-size:12px; padding:0; margin:0; }
+pre { clear:both; }