Merge inbound to mozilla-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Fri, 25 May 2018 20:51:27 +0300
changeset 419896 94d7f0e1c4d0390450028972cfeb65e0550b9892
parent 419843 e1db820796b22bb3cbdafad7e6aabab4cc6a6ab2 (current diff)
parent 419895 50a6c79860000396a9d1378687e150310ae96eb4 (diff)
child 419913 35e794382641da25bccff16af3552b876daaa45a
child 419989 58bb8b7eaaa14db1d80feb9649a1d9b737f1fbd9
push id34052
push userccoroiu@mozilla.com
push dateFri, 25 May 2018 17:52:14 +0000
treeherdermozilla-central@94d7f0e1c4d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.0a1
first release with
nightly linux32
94d7f0e1c4d0 / 62.0a1 / 20180525220044 / files
nightly linux64
94d7f0e1c4d0 / 62.0a1 / 20180525220044 / files
nightly mac
94d7f0e1c4d0 / 62.0a1 / 20180525220044 / files
nightly win32
94d7f0e1c4d0 / 62.0a1 / 20180525220044 / files
nightly win64
94d7f0e1c4d0 / 62.0a1 / 20180525220044 / 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 inbound to mozilla-central a=merge
dom/base/test/test_bug704320.html
--- a/accessible/generic/ARIAGridAccessible.cpp
+++ b/accessible/generic/ARIAGridAccessible.cpp
@@ -35,16 +35,31 @@ ARIAGridAccessible::
 
 role
 ARIAGridAccessible::NativeRole() const
 {
   a11y::role r = GetAccService()->MarkupRole(mContent);
   return r != roles::NOTHING ? r : roles::TABLE;
 }
 
+already_AddRefed<nsIPersistentProperties>
+ARIAGridAccessible::NativeAttributes()
+{
+  nsCOMPtr<nsIPersistentProperties> attributes =
+    AccessibleWrap::NativeAttributes();
+
+  if (IsProbablyLayoutTable()) {
+    nsAutoString unused;
+    attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
+                                  NS_LITERAL_STRING("true"), unused);
+  }
+
+  return attributes.forget();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Table
 
 uint32_t
 ARIAGridAccessible::ColCount() const
 {
   AccIterator rowIter(this, filters::GetRow);
   Accessible* row = rowIter.Next();
--- a/accessible/generic/ARIAGridAccessible.h
+++ b/accessible/generic/ARIAGridAccessible.h
@@ -21,16 +21,17 @@ class ARIAGridAccessible : public Access
 {
 public:
   ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
   NS_INLINE_DECL_REFCOUNTING_INHERITED(ARIAGridAccessible, AccessibleWrap)
 
   // Accessible
   virtual a11y::role NativeRole() const override;
+  virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
   virtual TableAccessible* AsTable() override { return this; }
 
   // TableAccessible
   virtual uint32_t ColCount() const override;
   virtual uint32_t RowCount() override;
   virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
   virtual bool IsColSelected(uint32_t aColIdx) override;
   virtual bool IsRowSelected(uint32_t aRowIdx) override;
new file mode 100644
--- /dev/null
+++ b/accessible/generic/TableAccessible.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TableAccessible.h"
+
+#include "Accessible-inl.h"
+
+#include "nsTableCellFrame.h"
+#include "nsTableWrapperFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+bool
+TableAccessible::IsProbablyLayoutTable()
+{
+  // Implement a heuristic to determine if table is most likely used for layout.
+
+  // XXX do we want to look for rowspan or colspan, especialy that span all but
+  // a couple cells  at the beginning or end of a row/col, and especially when
+  // they occur at the edge of a table?
+
+  // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
+  // This will allow release trunk builds to be used by testers to refine
+  // the algorithm. Integrate it into Logging.
+  // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
+#ifdef SHOW_LAYOUT_HEURISTIC
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
+  { \
+    mLayoutHeuristic = isLayout ? \
+      NS_LITERAL_STRING("layout table: " heuristic) : \
+      NS_LITERAL_STRING("data table: " heuristic); \
+    return isLayout; \
+  }
+#else
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
+#endif
+
+  Accessible* thisacc = AsAccessible();
+
+  // Need to see all elements while document is being edited.
+  if (thisacc->Document()->State() & states::EDITABLE) {
+    RETURN_LAYOUT_ANSWER(false, "In editable document");
+  }
+
+  // Check to see if an ARIA role overrides the role from native markup,
+  // but for which we still expose table semantics (treegrid, for example).
+  if (thisacc->HasARIARole()) {
+    RETURN_LAYOUT_ANSWER(false, "Has role attribute");
+  }
+
+  dom::Element* el = thisacc->Elm();
+  if (el->IsMathMLElement(nsGkAtoms::mtable_)) {
+    RETURN_LAYOUT_ANSWER(false, "MathML matrix");
+  }
+
+  MOZ_ASSERT(el->IsHTMLElement(nsGkAtoms::table),
+             "Table should not be built by CSS display:table style");
+
+  // Check if datatable attribute has "0" value.
+  if (el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
+                      NS_LITERAL_STRING("0"), eCaseMatters)) {
+    RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
+  }
+
+  // Check for legitimate data table attributes.
+  nsAutoString summary;
+  if (el->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
+      !summary.IsEmpty()) {
+    RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
+  }
+
+  // Check for legitimate data table elements.
+  Accessible* caption = thisacc->FirstChild();
+  if (caption && caption->IsHTMLCaption() && caption->HasChildren()) {
+    RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
+  }
+
+  for (nsIContent* childElm = el->GetFirstChild(); childElm;
+       childElm = childElm->GetNextSibling()) {
+    if (!childElm->IsHTMLElement())
+      continue;
+
+    if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col,
+                                      nsGkAtoms::colgroup,
+                                      nsGkAtoms::tfoot,
+                                      nsGkAtoms::thead)) {
+      RETURN_LAYOUT_ANSWER(false,
+                           "Has col, colgroup, tfoot or thead -- legitimate table structures");
+    }
+
+    if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
+      for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
+           rowElm = rowElm->GetNextSibling()) {
+        if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
+          for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
+               cellElm = cellElm->GetNextSibling()) {
+            if (cellElm->IsHTMLElement()) {
+
+              if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
+                RETURN_LAYOUT_ANSWER(false,
+                                     "Has th -- legitimate table structures");
+              }
+
+              if (cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
+                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
+                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
+                RETURN_LAYOUT_ANSWER(false,
+                                     "Has headers, scope, or abbr attribute -- legitimate table structures");
+              }
+
+              Accessible* cell = thisacc->Document()->GetAccessible(cellElm);
+              if (cell && cell->ChildCount() == 1 &&
+                  cell->FirstChild()->IsAbbreviation()) {
+                RETURN_LAYOUT_ANSWER(false,
+                                     "has abbr -- legitimate table structures");
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Check for nested tables.
+  nsCOMPtr<nsIHTMLCollection> nestedTables =
+    el->GetElementsByTagName(NS_LITERAL_STRING("table"));
+  if (nestedTables->Length() > 0) {
+    RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
+  }
+
+  // If only 1 column or only 1 row, it's for layout.
+  auto colCount = ColCount();
+  if (colCount <= 1) {
+    RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
+  }
+  auto rowCount = RowCount();
+  if (rowCount <=1) {
+    RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
+  }
+
+  // Check for many columns.
+  if (colCount >= 5) {
+    RETURN_LAYOUT_ANSWER(false, ">=5 columns");
+  }
+
+  // Now we know there are 2-4 columns and 2 or more rows. Check to see if
+  // there are visible borders on the cells.
+  // XXX currently, we just check the first cell -- do we really need to do more?
+  nsTableWrapperFrame* tableFrame = do_QueryFrame(el->GetPrimaryFrame());
+  if (!tableFrame) {
+    RETURN_LAYOUT_ANSWER(false, "table with no frame!");
+  }
+
+  nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
+  if (!cellFrame) {
+    RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
+  }
+
+  nsMargin border;
+  cellFrame->GetXULBorder(border);
+  if (border.top && border.bottom && border.left && border.right) {
+    RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
+  }
+
+  // Rules for non-bordered tables with 2-4 columns and 2+ rows from here on
+  // forward.
+
+  // Check for styled background color across rows (alternating background
+  // color is a common feature for data tables).
+  auto childCount = thisacc->ChildCount();
+  nscolor rowColor = 0;
+  nscolor prevRowColor;
+  for (auto childIdx = 0U; childIdx < childCount; childIdx++) {
+    Accessible* child = thisacc->GetChildAt(childIdx);
+    if (child->IsHTMLTableRow()) {
+      prevRowColor = rowColor;
+      nsIFrame* rowFrame = child->GetFrame();
+      MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
+      if (!rowFrame) {
+        RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
+      }
+
+      rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
+
+      if (childIdx > 0 && prevRowColor != rowColor) {
+        RETURN_LAYOUT_ANSWER(
+          false, "2 styles of row background color, non-bordered"
+        );
+      }
+    }
+  }
+
+  // Check for many rows.
+  const uint32_t kMaxLayoutRows = 20;
+  if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
+    RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
+  }
+
+  // Check for very wide table.
+  nsIFrame* documentFrame = thisacc->Document()->GetFrame();
+  nsSize documentSize = documentFrame->GetSize();
+  if (documentSize.width > 0) {
+    nsSize tableSize = thisacc->GetFrame()->GetSize();
+    int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
+    if (percentageOfDocWidth > 95) {
+      // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
+      // Probably for layout
+      RETURN_LAYOUT_ANSWER(
+        true, "<= 4 columns, table width is 95% of document width"
+      );
+    }
+  }
+
+  // Two column rules.
+  if (rowCount * colCount <= 10) {
+    RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
+  }
+
+  static const nsLiteralString tags[] = {
+    NS_LITERAL_STRING("embed"),
+    NS_LITERAL_STRING("object"),
+    NS_LITERAL_STRING("iframe")
+  };
+  for (auto& tag : tags) {
+    nsCOMPtr<nsIHTMLCollection> descendants = el->GetElementsByTagName(tag);
+    if (descendants->Length() > 0) {
+      RETURN_LAYOUT_ANSWER(
+        true, "Has no borders, and has iframe, object or embed, typical of advertisements"
+      );
+    }
+  }
+
+  RETURN_LAYOUT_ANSWER(
+    false, "No layout factor strong enough, so will guess data"
+  );
+}
--- a/accessible/generic/TableAccessible.h
+++ b/accessible/generic/TableAccessible.h
@@ -168,17 +168,17 @@ public:
   /**
    * Unselect the given row leaving other selected rows selected.
    */
   virtual void UnselectRow(uint32_t aRowIdx) {}
 
   /**
    * Return true if the table is probably for layout.
    */
-  virtual bool IsProbablyLayoutTable() { return false; }
+  virtual bool IsProbablyLayoutTable();
 
   /**
    * Convert the table to an Accessible*.
    */
   virtual Accessible* AsAccessible() = 0;
 };
 
 } // namespace a11y
--- a/accessible/generic/moz.build
+++ b/accessible/generic/moz.build
@@ -16,16 +16,17 @@ UNIFIED_SOURCES += [
     'ARIAGridAccessible.cpp',
     'BaseAccessibles.cpp',
     'DocAccessible.cpp',
     'FormControlAccessible.cpp',
     'HyperTextAccessible.cpp',
     'ImageAccessible.cpp',
     'OuterDocAccessible.cpp',
     'RootAccessible.cpp',
+    'TableAccessible.cpp',
     'TableCellAccessible.cpp',
     'TextLeafAccessible.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/html',
     '/accessible/xpcom',
--- a/accessible/html/HTMLTableAccessible.cpp
+++ b/accessible/html/HTMLTableAccessible.cpp
@@ -855,254 +855,16 @@ HTMLTableAccessible::Description(nsStrin
   if (aDescription.IsEmpty()) {
     bool isProbablyForLayout = IsProbablyLayoutTable();
     aDescription = mLayoutHeuristic;
   }
   printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
 #endif
 }
 
-bool
-HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty)
-{
-  nsCOMPtr<nsIHTMLCollection> elements =
-    mContent->AsElement()->GetElementsByTagName(aTagName);
-
-  Element* foundItem = elements->Item(0);
-  if (!foundItem)
-    return false;
-
-  if (aAllowEmpty)
-    return true;
-
-  // Make sure that the item we found has contents and either has multiple
-  // children or the found item is not a whitespace-only text node.
-  if (foundItem->GetChildCount() > 1)
-    return true; // Treat multiple child nodes as non-empty
-
-  nsIContent *innerItemContent = foundItem->GetFirstChild();
-  if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
-    return true;
-
-  // If we found more than one node then return true not depending on
-  // aAllowEmpty flag.
-  // XXX it might be dummy but bug 501375 where we changed this addresses
-  // performance problems only. Note, currently 'aAllowEmpty' flag is used for
-  // caption element only. On another hand we create accessible object for
-  // the first entry of caption element (see
-  // HTMLTableAccessible::InsertChildAt).
-  return !!elements->Item(1);
-}
-
-bool
-HTMLTableAccessible::IsProbablyLayoutTable()
-{
-  // Implement a heuristic to determine if table is most likely used for layout
-  // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells
-  // at the beginning or end of a row/col, and especially when they occur at the edge of a table?
-  // XXX expose this info via object attributes to AT-SPI
-
-  // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
-  // This will allow release trunk builds to be used by testers to refine the algorithm
-  // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
-#ifdef SHOW_LAYOUT_HEURISTIC
-#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
-  { \
-    mLayoutHeuristic = isLayout ? \
-      NS_LITERAL_STRING("layout table: " heuristic) : \
-      NS_LITERAL_STRING("data table: " heuristic); \
-    return isLayout; \
-  }
-#else
-#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
-#endif
-
-  DocAccessible* docAccessible = Document();
-  if (docAccessible) {
-    uint64_t docState = docAccessible->State();
-    if (docState & states::EDITABLE) {  // Need to see all elements while document is being edited
-      RETURN_LAYOUT_ANSWER(false, "In editable document");
-    }
-  }
-
-  // Check to see if an ARIA role overrides the role from native markup,
-  // but for which we still expose table semantics (treegrid, for example).
-  if (Role() != roles::TABLE)
-    RETURN_LAYOUT_ANSWER(false, "Has role attribute");
-
-  if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
-    // Role attribute is present, but overridden roles have already been dealt with.
-    // Only landmarks and other roles that don't override the role from native
-    // markup are left to deal with here.
-    RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
-  }
-
-  NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table),
-    "table should not be built by CSS display:table style");
-
-  // Check if datatable attribute has "0" value.
-  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
-                            NS_LITERAL_STRING("0"), eCaseMatters)) {
-    RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
-  }
-
-  // Check for legitimate data table attributes.
-  nsAutoString summary;
-  if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
-      !summary.IsEmpty())
-    RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
-
-  // Check for legitimate data table elements.
-  Accessible* caption = FirstChild();
-  if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
-    RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
-
-  for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
-       childElm = childElm->GetNextSibling()) {
-    if (!childElm->IsHTMLElement())
-      continue;
-
-    if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col,
-                                      nsGkAtoms::colgroup,
-                                      nsGkAtoms::tfoot,
-                                      nsGkAtoms::thead)) {
-      RETURN_LAYOUT_ANSWER(false,
-                           "Has col, colgroup, tfoot or thead -- legitimate table structures");
-    }
-
-    if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
-      for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
-           rowElm = rowElm->GetNextSibling()) {
-        if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
-          for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
-               cellElm = cellElm->GetNextSibling()) {
-            if (cellElm->IsHTMLElement()) {
-
-              if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
-                RETURN_LAYOUT_ANSWER(false,
-                                     "Has th -- legitimate table structures");
-              }
-
-              if (cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
-                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
-                  cellElm->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
-                RETURN_LAYOUT_ANSWER(false,
-                                     "Has headers, scope, or abbr attribute -- legitimate table structures");
-              }
-
-              Accessible* cell = mDoc->GetAccessible(cellElm);
-              if (cell && cell->ChildCount() == 1 &&
-                  cell->FirstChild()->IsAbbreviation()) {
-                RETURN_LAYOUT_ANSWER(false,
-                                     "has abbr -- legitimate table structures");
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  if (HasDescendant(NS_LITERAL_STRING("table"))) {
-    RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
-  }
-
-  // If only 1 column or only 1 row, it's for layout
-  uint32_t colCount = ColCount();
-  if (colCount <=1) {
-    RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
-  }
-  uint32_t rowCount = RowCount();
-  if (rowCount <=1) {
-    RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
-  }
-
-  // Check for many columns
-  if (colCount >= 5) {
-    RETURN_LAYOUT_ANSWER(false, ">=5 columns");
-  }
-
-  // Now we know there are 2-4 columns and 2 or more rows
-  // Check to see if there are visible borders on the cells
-  // XXX currently, we just check the first cell -- do we really need to do more?
-  nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
-  if (!tableFrame)
-    RETURN_LAYOUT_ANSWER(false, "table with no frame!");
-
-  nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
-  if (!cellFrame)
-    RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
-
-  nsMargin border;
-  cellFrame->GetXULBorder(border);
-  if (border.top && border.bottom && border.left && border.right) {
-    RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
-  }
-
-  /**
-   * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward
-   */
-
-  // Check for styled background color across rows (alternating background
-  // color is a common feature for data tables).
-  uint32_t childCount = ChildCount();
-  nscolor rowColor = 0;
-  nscolor prevRowColor;
-  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
-    Accessible* child = GetChildAt(childIdx);
-    if (child->Role() == roles::ROW) {
-      prevRowColor = rowColor;
-      nsIFrame* rowFrame = child->GetFrame();
-      MOZ_ASSERT(rowFrame, "Table hierarchy got screwed up");
-      if (!rowFrame) {
-        RETURN_LAYOUT_ANSWER(false, "Unexpected table hierarchy");
-      }
-
-      rowColor = rowFrame->StyleBackground()->BackgroundColor(rowFrame);
-
-      if (childIdx > 0 && prevRowColor != rowColor)
-        RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered");
-    }
-  }
-
-  // Check for many rows
-  const uint32_t kMaxLayoutRows = 20;
-  if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
-    RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
-  }
-
-  // Check for very wide table.
-  nsIFrame* documentFrame = Document()->GetFrame();
-  nsSize documentSize = documentFrame->GetSize();
-  if (documentSize.width > 0) {
-    nsSize tableSize = GetFrame()->GetSize();
-    int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
-    if (percentageOfDocWidth > 95) {
-      // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
-      // Probably for layout
-      RETURN_LAYOUT_ANSWER(true,
-                           "<= 4 columns, table width is 95% of document width");
-    }
-  }
-
-  // Two column rules
-  if (rowCount * colCount <= 10) {
-    RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
-  }
-
-  if (HasDescendant(NS_LITERAL_STRING("embed")) ||
-      HasDescendant(NS_LITERAL_STRING("object")) ||
-      HasDescendant(NS_LITERAL_STRING("iframe"))) {
-    RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, or iframe, typical of advertisements");
-  }
-
-  RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data");
-}
-
-
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLCaptionAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 Relation
 HTMLCaptionAccessible::RelationByType(RelationType aType) const
 {
   Relation rel = HyperTextAccessible::RelationByType(aType);
--- a/accessible/html/HTMLTableAccessible.h
+++ b/accessible/html/HTMLTableAccessible.h
@@ -151,17 +151,16 @@ public:
   virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
   virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
   virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
   virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
   virtual void SelectCol(uint32_t aColIdx) override;
   virtual void SelectRow(uint32_t aRowIdx) override;
   virtual void UnselectCol(uint32_t aColIdx) override;
   virtual void UnselectRow(uint32_t aRowIdx) override;
-  virtual bool IsProbablyLayoutTable() override;
   virtual Accessible* AsAccessible() override { return this; }
 
   // Accessible
   virtual TableAccessible* AsTable() override { return this; }
   virtual void Description(nsString& aDescription) override;
   virtual a11y::role NativeRole() const override;
   virtual uint64_t NativeState() const override;
   virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
@@ -194,25 +193,16 @@ protected:
    * @param  aIsOuter  [in] indicates whether all rows or column excepting
    *                    the given one should be unselected or the given one
    *                    should be unselected only
    */
   nsresult RemoveRowsOrColumnsFromSelection(int32_t aIndex,
                                             TableSelection aTarget,
                                             bool aIsOuter);
 
-  /**
-   * Return true if table has an element with the given tag name.
-   *
-   * @param  aTagName     [in] tag name of searched element
-   * @param  aAllowEmpty  [in, optional] points if found element can be empty
-   *                       or contain whitespace text only.
-   */
-  bool HasDescendant(const nsAString& aTagName, bool aAllowEmpty = true);
-
 #ifdef SHOW_LAYOUT_HEURISTIC
   nsString mLayoutHeuristic;
 #endif
 };
 
 /**
  * HTML caption accessible (html:caption).
  */
--- a/accessible/tests/mochitest/table/test_layoutguess.html
+++ b/accessible/tests/mochitest/table/test_layoutguess.html
@@ -9,116 +9,124 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../attributes.js"></script>
 
   <script type="application/javascript">
+    function isLayoutTable(id) {
+      testAttrs(id, { "layout-guess": "true" }, true);
+    }
+    function isDataTable(id) {
+      testAbsentAttrs(id, { "layout-guess": "true" });
+    }
+
     function doTest() {
-      // Attribute we're looking for
-      var attr = {
-        "layout-guess": "true"
-      };
-
       // table with role of grid
-      testAbsentAttrs("table1", attr);
+      isDataTable("table1");
       // table with role of grid and datatable="0"
-      testAbsentAttrs("table1.1", attr);
+      isDataTable("table1.1");
 
       // table with landmark role
-      testAbsentAttrs("table2", attr);
+      isDataTable("table2");
 
       // table with summary
-      testAbsentAttrs("table3", attr);
+      isDataTable("table3");
 
       // table with caption
-      testAbsentAttrs("table4", attr);
+      isDataTable("table4");
 
       // layout table with empty caption
-      testAttrs("table4.2", attr, true);
+      isLayoutTable("table4.2");
 
       // table with thead element
-      testAbsentAttrs("table5", attr);
+      isDataTable("table5");
 
       // table with tfoot element
-      testAbsentAttrs("table5.1", attr);
+      isDataTable("table5.1");
 
       // table with colgroup or col elements
-      testAbsentAttrs("table5.2", attr);
-      testAbsentAttrs("table5.3", attr);
+      isDataTable("table5.2");
+      isDataTable("table5.3");
 
       // table with th element
-      testAbsentAttrs("table6", attr);
+      isDataTable("table6");
 
       // table with headers attribute
-      testAbsentAttrs("table6.2", attr);
+      isDataTable("table6.2");
 
       // table with scope attribute
-      testAbsentAttrs("table6.2.2", attr);
+      isDataTable("table6.2.2");
 
       // table with abbr attribute
-      testAbsentAttrs("table6.2.3", attr);
+      isDataTable("table6.2.3");
 
       // table with abbr element
-      testAbsentAttrs("table6.3", attr);
+      isDataTable("table6.3");
 
       // table with abbr element having empty text node
-      testAbsentAttrs("table6.4", attr);
+      isDataTable("table6.4");
 
       // table with abbr element and non-empty text node
-      testAttrs("table6.5", attr, true);
+      isLayoutTable("table6.5");
 
       // layout table with nested table
-      testAttrs("table9", attr, true);
+      isLayoutTable("table9");
 
       // layout table with 1 column
-      testAttrs("table10", attr, true);
+      isLayoutTable("table10");
 
       // layout table with 1 row
-      testAttrs("table11", attr, true);
+      isLayoutTable("table11");
 
       // table with 5 columns
-      testAbsentAttrs("table12", attr);
+      isDataTable("table12");
 
       // table with a bordered cell
-      testAbsentAttrs("table13", attr);
+      isDataTable("table13");
 
       // table with alternating row background colors
-      testAbsentAttrs("table14", attr);
+      isDataTable("table14");
 
       // table with 3 columns and 21 rows
-      testAbsentAttrs("table15", attr);
+      isDataTable("table15");
 
       // layout table that has a 100% width
-      testAttrs("table16", attr, true);
+      isLayoutTable("table16");
 
       // layout table that has a 95% width in pixels
-      testAttrs("table17", attr, true);
+      isLayoutTable("table17");
 
       // layout table with less than 10 columns
-      testAttrs("table18", attr, true);
+      isLayoutTable("table18");
 
       // layout table with embedded iframe
-      testAttrs("table19", attr, true);
+      isLayoutTable("table19");
 
       // tree grid, no layout table
-      testAbsentAttrs("table20", attr);
+      isDataTable("table20");
 
       // layout table containing nested data table (having data structures)
-      testAttrs("table21", attr, true);
-      testAttrs("table21.2", attr, true);
-      testAttrs("table21.3", attr, true);
-      testAttrs("table21.4", attr, true);
-      testAttrs("table21.5", attr, true);
-      testAttrs("table21.6", attr, true);
+      isLayoutTable("table21");
+      isLayoutTable("table21.2");
+      isLayoutTable("table21.3");
+      isLayoutTable("table21.4");
+      isLayoutTable("table21.5");
+      isLayoutTable("table21.6");
 
       // layout table having datatable="0" attribute and containing data table structure (tfoot element)
-      testAttrs("table22", attr, true);
+      isLayoutTable("table22");
+
+      // layout display:block table with 1 column
+      isLayoutTable("displayblock_table1");
+
+      // matrix
+      isDataTable("mtable1");
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -496,10 +504,37 @@
   <table id="table22" datatable="0">
     <tfoot>
       <tr>
         <td>Cell1</td><td>cell2</td>
       </tr>
     </tfoot>
   </table>
 
+  <!-- display:block table -->
+  <table id="displayblock_table1" style="display:block">
+    <tr><td>Row1</td></tr>
+    <tr><td>Row2</td></tr>
+  </table>
+
+  <!-- MathML matrix -->
+  <math>
+    <mtable id="mtable1">
+      <mtr>
+        <mtd>
+          <mn>1</mn>
+        </mtd>
+        <mtd>
+          <mn>0</mn>
+        </mtd>
+      </mtr>
+      <mtr>
+        <mtd>
+          <mn>0</mn>
+        </mtd>
+        <mtd>
+          <mn>1</mn>
+        </mtd>
+      </mtr>
+    </mtable>
+  </math>
 </body>
 </html>
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -208,31 +208,27 @@ OptionsPanel.prototype = {
   setupToolsList: function() {
     let defaultToolsBox = this.panelDoc.getElementById("default-tools-box");
     let additionalToolsBox = this.panelDoc.getElementById(
       "additional-tools-box");
     let toolsNotSupportedLabel = this.panelDoc.getElementById(
       "tools-not-supported-label");
     let atleastOneToolNotSupported = false;
 
-    const toolbox = this.toolbox;
-
     // Signal tool registering/unregistering globally (for the tools registered
     // globally) and per toolbox (for the tools registered to a single toolbox).
     // This event handler expect this to be binded to the related checkbox element.
-    let onCheckboxClick = function(tool) {
+    let onCheckboxClick = function(telemetry, tool) {
       // Set the kill switch pref boolean to true
       Services.prefs.setBoolPref(tool.visibilityswitch, this.checked);
 
       if (!tool.isWebExtension) {
         gDevTools.emit(this.checked ? "tool-registered" : "tool-unregistered", tool.id);
         // Record which tools were registered and unregistered.
-        this.telemetry.keyedScalarSet("devtools.tool.registered",
-                                      tool.id,
-                                      this.checked);
+        telemetry.keyedScalarSet("devtools.tool.registered", tool.id, this.checked);
       }
     };
 
     let createToolCheckbox = (tool) => {
       let checkboxLabel = this.panelDoc.createElement("label");
       let checkboxInput = this.panelDoc.createElement("input");
       checkboxInput.setAttribute("type", "checkbox");
       checkboxInput.setAttribute("id", tool.id);
@@ -248,17 +244,18 @@ OptionsPanel.prototype = {
         checkboxInput.setAttribute("data-unsupported", "true");
         checkboxInput.setAttribute("disabled", "true");
       }
 
       if (InfallibleGetBoolPref(tool.visibilityswitch)) {
         checkboxInput.setAttribute("checked", "true");
       }
 
-      checkboxInput.addEventListener("change", onCheckboxClick.bind(checkboxInput, tool));
+      checkboxInput.addEventListener("change",
+        onCheckboxClick.bind(checkboxInput, this.telemetry, tool));
 
       checkboxLabel.appendChild(checkboxInput);
       checkboxLabel.appendChild(checkboxSpanLabel);
       return checkboxLabel;
     };
 
     // Clean up any existent default tools content.
     for (let label of defaultToolsBox.querySelectorAll("label")) {
@@ -282,17 +279,17 @@ OptionsPanel.prototype = {
     // Populating the additional tools list.
     let atleastOneAddon = false;
     for (let tool of gDevTools.getAdditionalTools()) {
       atleastOneAddon = true;
       additionalToolsBox.appendChild(createToolCheckbox(tool));
     }
 
     // Populating the additional tools that came from the installed WebExtension add-ons.
-    for (let {uuid, name, pref} of toolbox.listWebExtensions()) {
+    for (let {uuid, name, pref} of this.toolbox.listWebExtensions()) {
       atleastOneAddon = true;
 
       additionalToolsBox.appendChild(createToolCheckbox({
         isWebExtension: true,
 
         // Use the preference as the unified webextensions tool id.
         id: `webext-${uuid}`,
         tooltip: name,
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -31,25 +31,20 @@ pref("devtools.command-button-noautohide
 
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
 pref("devtools.inspector.remote", false);
 
-#if defined(NIGHTLY_BUILD)
 // Show the 3 pane onboarding tooltip in the inspector
 pref("devtools.inspector.show-three-pane-tooltip", true);
 // Enable the 3 pane mode in the inspector
 pref("devtools.inspector.three-pane-enabled", true);
-#else
-pref("devtools.inspector.show-three-pane-tooltip", false);
-pref("devtools.inspector.three-pane-enabled", false);
-#endif
 
 // Collapse pseudo-elements by default in the rule-view
 pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -22,16 +22,17 @@ class Telemetry {
   constructor() {
     // Bind pretty much all functions so that callers do not need to.
     this.msSystemNow = this.msSystemNow.bind(this);
     this.getHistogramById = this.getHistogramById.bind(this);
     this.getKeyedHistogramById = this.getKeyedHistogramById.bind(this);
     this.scalarSet = this.scalarSet.bind(this);
     this.scalarAdd = this.scalarAdd.bind(this);
     this.keyedScalarAdd = this.keyedScalarAdd.bind(this);
+    this.keyedScalarSet = this.keyedScalarSet.bind(this);
     this.recordEvent = this.recordEvent.bind(this);
     this.setEventRecordingEnabled = this.setEventRecordingEnabled.bind(this);
     this.preparePendingEvent = this.preparePendingEvent.bind(this);
     this.addEventProperty = this.addEventProperty.bind(this);
     this.toolOpened = this.toolOpened.bind(this);
     this.toolClosed = this.toolClosed.bind(this);
   }
 
@@ -242,16 +243,48 @@ class Telemetry {
     } catch (e) {
       dump(`Warning: An attempt was made to write to the ${scalarId} ` +
            `scalar, which is not defined in Scalars.yaml\n` +
            `CALLER: ${getCaller()}`);
     }
   }
 
   /**
+   * Log a value to a keyed scalar.
+   *
+   * @param  {String} scalarId
+   *         Scalar in which the data is to be stored.
+   * @param  {String} key
+   *         The key within the  scalar.
+   * @param  value
+   *         Value to store.
+   */
+  keyedScalarSet(scalarId, key, value) {
+    if (!scalarId) {
+      return;
+    }
+
+    try {
+      if (isNaN(value) && typeof value !== "boolean") {
+        dump(`Warning: An attempt was made to write a non-numeric and ` +
+             `non-boolean value ${value} to the ${scalarId} scalar. Only ` +
+             `numeric and boolean values are allowed.\n` +
+             `CALLER: ${getCaller()}`);
+
+        return;
+      }
+      Services.telemetry.keyedScalarSet(scalarId, key, value);
+    } catch (e) {
+      dump(`Warning: An attempt was made to write to the ${scalarId} ` +
+           `scalar, which is not defined in Scalars.yaml\n` +
+           `CALLER: ${getCaller()}`);
+    }
+  }
+
+  /**
    * Log a value to a keyed count scalar.
    *
    * @param  {String} scalarId
    *         Scalar in which the data is to be stored.
    * @param  {String} key
    *         The key within the  scalar.
    * @param  value
    *         Value to store.
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2034,17 +2034,17 @@ Element::UnbindFromTree(bool aDeep, bool
     shadowRoot->SetIsComposedDocParticipant(false);
   }
 
   MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits));
   MOZ_ASSERT(!document || document->GetServoRestyleRoot() != this);
 }
 
 nsDOMCSSAttributeDeclaration*
-Element::GetSMILOverrideStyle()
+Element::SMILOverrideStyle()
 {
   Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots();
 
   if (!slots->mSMILOverrideStyle) {
     slots->mSMILOverrideStyle = new nsDOMCSSAttributeDeclaration(this, true);
   }
 
   return slots->mSMILOverrideStyle;
@@ -4484,17 +4484,17 @@ NoteDirtyElement(Element* aElement, uint
       return;
     }
 
     // If the parent is styled but is display:none, we're done.
     //
     // We can't check for a frame here, since <frame> elements inside <frameset>
     // still need to generate a frame, even if they're display: none. :(
     //
-    // The servo traversal doesn't keep style data under display: none subtrees, 
+    // The servo traversal doesn't keep style data under display: none subtrees,
     // so in order for it to not need to cleanup each time anything happens in a
     // display: none subtree, we keep it clean.
     //
     // Also, we can't be much more smarter about using the parent's frame in
     // order to avoid work here, because since the style system keeps style data
     // in, e.g., subtrees under a leaf frame, missing restyles and such in there
     // has observable behavior via getComputedStyle, for example.
     if (Servo_Element_IsDisplayNone(parent->AsElement())) {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -356,17 +356,17 @@ public:
   /**
    * Get the SMIL override style for this element. This is a style declaration
    * that is applied *after* the inline style, and it can be used e.g. to store
    * animated style values.
    *
    * Note: This method is analogous to the 'GetStyle' method in
    * nsGenericHTMLElement and nsStyledElement.
    */
-  nsDOMCSSAttributeDeclaration* GetSMILOverrideStyle();
+  nsDOMCSSAttributeDeclaration* SMILOverrideStyle();
 
   /**
    * Returns if the element is labelable as per HTML specification.
    */
   virtual bool IsLabelable() const;
 
   /**
    * Returns if the element is interactive content as per HTML specification.
--- a/dom/base/nsIImageLoadingContent.idl
+++ b/dom/base/nsIImageLoadingContent.idl
@@ -109,21 +109,16 @@ interface nsIImageLoadingContent : imgIN
    * security policies enforced.
    *
    * @param aContentDecision the decision returned from nsIContentPolicy
    *                         (any of the types REJECT_*)
    */
   [notxpcom, nostdcall] void setBlockedRequest(in int16_t aContentDecision);
 
   /**
-   * @return true if the current request's size is available.
-   */
-  [noscript, notxpcom] boolean currentRequestHasSize();
-
-  /**
    * Used to notify the image loading content node that a frame has been
    * created.
    */
   [notxpcom] void frameCreated(in nsIFrame aFrame);
 
   /**
    * Used to notify the image loading content node that a frame has been
    * destroyed.
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -130,33 +130,29 @@ nsImageLoadingContent::~nsImageLoadingCo
 /*
  * imgINotificationObserver impl
  */
 NS_IMETHODIMP
 nsImageLoadingContent::Notify(imgIRequest* aRequest,
                               int32_t aType,
                               const nsIntRect* aData)
 {
+  MOZ_ASSERT(aRequest, "no request?");
+  MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+             "Forgot to cancel a previous request?");
+
   if (aType == imgINotificationObserver::IS_ANIMATED) {
     return OnImageIsAnimated(aRequest);
   }
 
   if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
     OnUnlockedDraw();
     return NS_OK;
   }
 
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    // We should definitely have a request here
-    MOZ_ASSERT(aRequest, "no request?");
-
-    MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest,
-               "Unknown request");
-  }
-
   {
     // Calling Notify on observers can modify the list of observers so make
     // a local copy.
     AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
     for (ImageObserver* observer = &mObserverList, *next; observer;
          observer = next) {
       next = observer->mNext;
       if (observer->mObserver) {
@@ -624,22 +620,16 @@ nsImageLoadingContent::GetRequest(int32_
   NS_ENSURE_ARG_POINTER(aRequest);
 
   ErrorResult result;
   *aRequest = GetRequest(aRequestType, result).take();
 
   return result.StealNSResult();
 }
 
-NS_IMETHODIMP_(bool)
-nsImageLoadingContent::CurrentRequestHasSize()
-{
-  return HaveSize(mCurrentRequest);
-}
-
 NS_IMETHODIMP_(void)
 nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
 {
   NS_ASSERTION(aFrame, "aFrame is null");
 
   TrackImage(mCurrentRequest, aFrame);
   TrackImage(mPendingRequest, aFrame);
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -516,17 +516,18 @@ skip-if = toolkit == 'android' #bug 6870
 [test_bug693615.html]
 [test_bug693875.html]
 [test_bug694754.xhtml]
 [test_bug696301-1.html]
 [test_bug696301-2.html]
 [test_bug698381.html]
 [test_bug698384.html]
 [test_bug704063.html]
-[test_bug704320.html]
+[test_bug704320-1.html]
+[test_bug704320-2.html]
 [test_bug704320_policyset.html]
 [test_bug704320_policyset2.html]
 [test_bug704320_preload.html]
 [test_bug707142.html]
 [test_bug708620.html]
 [test_bug711047.html]
 [test_bug711180.html]
 [test_bug719533.html]
rename from dom/base/test/test_bug704320.html
rename to dom/base/test/test_bug704320-1.html
--- a/dom/base/test/test_bug704320.html
+++ b/dom/base/test/test_bug704320-1.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+This Test is split into two for Bug 1453396
 -->
 <head>
   <meta charset="utf-8">
-  <title>Test for Bug 704320</title>
+  <title>Test for Bug 704320-Part1</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="referrerHelper.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
 <script type="application/javascript">
 
 //generates URLs to test
 var generateURLArray = (function(from, to){
@@ -25,32 +26,29 @@ var generateURLArray = (function(from, t
     from + baseURL + from + schemeTo + to + '&policy=origin-when-cross-origin',
     from + baseURL + from + schemeTo + to + '&policy=same-origin',
     from + baseURL + from + schemeTo + to + '&policy=strict-origin',
     from + baseURL + from + schemeTo + to + '&policy=strict-origin-when-cross-origin',
   ];
 });
 
 let testIframeUrls = [generateURLArray('http', 'http'),
-                      generateURLArray('https', 'https'),
-                      generateURLArray('http', 'https'),
-                      generateURLArray('https', 'http')];
+                      generateURLArray('https', 'https')];
 
 SimpleTest.waitForExplicitFinish();
 let advance = function(testName) {
   testsGenerator[testName].next();
 };
 
-let testNames = ['testframeone', 'testframetwo', 'testframethree',
-                 'testframefour'];
+let testNames = ['testframeone', 'testframetwo'];
 let isTestFinished = 0;
 
 function checkTestsCompleted() {
   isTestFinished++;
-  if (isTestFinished == 4) {
+  if (isTestFinished == 2) {
     SimpleTest.finish();
   }
 }
 let testsGenerator = {};
 SimpleTest.requestLongerTimeout(4);
 /**
  * This is the main test routine -- serialized by use of a generator.
  * It performs all tests in sequence using four iframes.
@@ -77,20 +75,16 @@ function startTests(testName, testIframe
 for (i = 0; i < testIframeUrls.length; i++) {
   startTests(testNames[i], testIframeUrls[i]);
 }
 
 </script>
 </head>
 
 <body onload="testsGenerator[testNames[0]].next();
-              testsGenerator[testNames[1]].next();
-              testsGenerator[testNames[2]].next();
-              testsGenerator[testNames[3]].next();">
+              testsGenerator[testNames[1]].next();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP/HTTPS to HTTPS/HTTP</a>
 <p id="display"></p>
 <pre id="content">
 </pre>
   <iframe id="testframeone"></iframe>
   <iframe id="testframetwo"></iframe>
-  <iframe id="testframethree"></iframe>
-  <iframe id="testframefour"></iframe>
 </body>
copy from dom/base/test/test_bug704320.html
copy to dom/base/test/test_bug704320-2.html
--- a/dom/base/test/test_bug704320.html
+++ b/dom/base/test/test_bug704320-2.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+This Test is split into two for Bug 1453396
 -->
 <head>
   <meta charset="utf-8">
-  <title>Test for Bug 704320</title>
+  <title>Test for Bug 704320-Part2</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="referrerHelper.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
 <script type="application/javascript">
 
 //generates URLs to test
 var generateURLArray = (function(from, to){
@@ -24,33 +25,30 @@ var generateURLArray = (function(from, t
     from + baseURL + from + schemeTo + to + '&policy=origin',
     from + baseURL + from + schemeTo + to + '&policy=origin-when-cross-origin',
     from + baseURL + from + schemeTo + to + '&policy=same-origin',
     from + baseURL + from + schemeTo + to + '&policy=strict-origin',
     from + baseURL + from + schemeTo + to + '&policy=strict-origin-when-cross-origin',
   ];
 });
 
-let testIframeUrls = [generateURLArray('http', 'http'),
-                      generateURLArray('https', 'https'),
-                      generateURLArray('http', 'https'),
+let testIframeUrls = [generateURLArray('http', 'https'),
                       generateURLArray('https', 'http')];
 
 SimpleTest.waitForExplicitFinish();
 let advance = function(testName) {
   testsGenerator[testName].next();
 };
 
-let testNames = ['testframeone', 'testframetwo', 'testframethree',
-                 'testframefour'];
+let testNames = ['testframeone', 'testframetwo'];
 let isTestFinished = 0;
 
 function checkTestsCompleted() {
   isTestFinished++;
-  if (isTestFinished == 4) {
+  if (isTestFinished == 2) {
     SimpleTest.finish();
   }
 }
 let testsGenerator = {};
 SimpleTest.requestLongerTimeout(4);
 /**
  * This is the main test routine -- serialized by use of a generator.
  * It performs all tests in sequence using four iframes.
@@ -77,20 +75,16 @@ function startTests(testName, testIframe
 for (i = 0; i < testIframeUrls.length; i++) {
   startTests(testNames[i], testIframeUrls[i]);
 }
 
 </script>
 </head>
 
 <body onload="testsGenerator[testNames[0]].next();
-              testsGenerator[testNames[1]].next();
-              testsGenerator[testNames[2]].next();
-              testsGenerator[testNames[3]].next();">
+              testsGenerator[testNames[1]].next();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP/HTTPS to HTTPS/HTTP</a>
 <p id="display"></p>
 <pre id="content">
 </pre>
   <iframe id="testframeone"></iframe>
   <iframe id="testframetwo"></iframe>
-  <iframe id="testframethree"></iframe>
-  <iframe id="testframefour"></iframe>
 </body>
--- a/dom/file/nsHostObjectProtocolHandler.cpp
+++ b/dom/file/nsHostObjectProtocolHandler.cpp
@@ -476,17 +476,19 @@ public:
 
   NS_IMETHOD
   Notify(nsITimer* aTimer) override
   {
     RevokeURI(mBroadcastToOtherProcesses);
     return NS_OK;
   }
 
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
   using nsINamed::GetName;
+#endif
 
   // nsIAsyncShutdownBlocker interface
 
   NS_IMETHOD
   GetName(nsAString& aName) override
   {
     aName.AssignLiteral("ReleasingTimerHolder for blobURL: ");
     aName.Append(NS_ConvertUTF8toUTF16(mURI));
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/dom/HTMLImageElementBinding.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsMappedAttributes.h"
 #include "nsSize.h"
 #include "nsDocument.h"
 #include "nsIDocument.h"
+#include "nsImageFrame.h"
 #include "nsIScriptContext.h"
 #include "nsIURL.h"
 #include "nsIIOService.h"
 #include "nsIServiceManager.h"
 #include "nsContentUtils.h"
 #include "nsContainerFrame.h"
 #include "nsNodeInfoManager.h"
 #include "mozilla/MouseEvents.h"
@@ -944,68 +945,93 @@ HTMLImageElement::InResponsiveMode()
   // will happen from the microtask, and we should behave responsively in the
   // interim
   return mResponsiveSelector ||
          mPendingImageLoadTask ||
          HaveSrcsetOrInPicture();
 }
 
 bool
-HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource, double aSelectedDensity)
+HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource)
 {
   // If there was no selected source previously, we don't want to short-circuit the load.
   // Similarly for if there is no newly selected source.
   if (!mLastSelectedSource || !aSelectedSource) {
     return false;
   }
   bool equal = false;
-  return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) && equal &&
-      aSelectedDensity == mCurrentDensity;
+  return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) && equal;
 }
 
 nsresult
 HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad)
 {
-  nsresult rv = NS_ERROR_FAILURE;
+  double currentDensity = 1.0; // default to 1.0 for the src attribute case
+
+  // Helper to update state when only density may have changed (i.e., the source
+  // to load hasn't changed, and we don't do any request at all). We need (apart
+  // from updating our internal state) to tell the image frame because its
+  // intrinsic size may have changed.
+  //
+  // In the case we actually trigger a new load, that load will trigger a call
+  // to nsImageFrame::NotifyNewCurrentRequest, which takes care of that for us.
+  auto UpdateDensityOnly = [&]() -> void {
+    if (mCurrentDensity == currentDensity) {
+      return;
+    }
+    mCurrentDensity = currentDensity;
+    if (nsImageFrame* f = do_QueryFrame(GetPrimaryFrame())) {
+      f->ResponsiveContentDensityChanged();
+    }
+  };
 
   if (aForce) {
-    // In responsive mode we generally want to re-run the full
-    // selection algorithm whenever starting a new load, per
-    // spec. This also causes us to re-resolve the URI as appropriate.
-    if (!UpdateResponsiveSource() && !aAlwaysLoad) {
+    // In responsive mode we generally want to re-run the full selection
+    // algorithm whenever starting a new load, per spec.
+    //
+    // This also causes us to re-resolve the URI as appropriate.
+    const bool sourceChanged = UpdateResponsiveSource();
+
+    if (mResponsiveSelector) {
+      currentDensity = mResponsiveSelector->GetSelectedImageDensity();
+    }
+
+    if (!sourceChanged && !aAlwaysLoad) {
+      UpdateDensityOnly();
       return NS_OK;
     }
+  } else if (mResponsiveSelector) {
+    currentDensity = mResponsiveSelector->GetSelectedImageDensity();
   }
 
+  nsresult rv = NS_ERROR_FAILURE;
   nsCOMPtr<nsIURI> selectedSource;
-  double currentDensity = 1.0; // default to 1.0 for the src attribute case
   if (mResponsiveSelector) {
     nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
     nsCOMPtr<nsIPrincipal> triggeringPrincipal = mResponsiveSelector->GetSelectedImageTriggeringPrincipal();
     selectedSource = url;
-    currentDensity = mResponsiveSelector->GetSelectedImageDensity();
-    if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
+    if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) {
+      UpdateDensityOnly();
       return NS_OK;
     }
     if (url) {
       rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset,
                      triggeringPrincipal);
     }
   } else {
     nsAutoString src;
     if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
       CancelImageRequests(aNotify);
       rv = NS_OK;
     } else {
-      nsIDocument* doc = GetOurOwnerDoc();
-      if (doc) {
-        StringToURI(src, doc, getter_AddRefs(selectedSource));
-        if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
-          return NS_OK;
-        }
+      nsIDocument* doc = OwnerDoc();
+      StringToURI(src, doc, getter_AddRefs(selectedSource));
+      if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) {
+        UpdateDensityOnly();
+        return NS_OK;
       }
 
       // If we have a srcset attribute or are in a <picture> element,
       // we always use the Imageset load type, even if we parsed no
       // valid responsive sources from either, per spec.
       rv = LoadImage(src, aForce, aNotify,
                      HaveSrcsetOrInPicture() ? eImageLoadType_Imageset
                                              : eImageLoadType_Normal,
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -38,16 +38,21 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLImageElement,
                                            nsGenericHTMLElement)
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual bool Draggable() const override;
 
+  ResponsiveImageSelector* GetResponsiveImageSelector()
+  {
+    return mResponsiveSelector.get();
+  }
+
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
 
   // EventTarget
   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
 
   NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLImageElement, img)
 
@@ -320,18 +325,18 @@ protected:
   // True if we have a srcset attribute or a <picture> parent, regardless of if
   // any valid responsive sources were parsed from either.
   bool HaveSrcsetOrInPicture();
 
   // True if we are using the newer image loading algorithm. This will be the
   // only mode after Bug 1076583
   bool InResponsiveMode();
 
-  // True if the given URL and density equal the last URL and density that was loaded by this element.
-  bool SelectedSourceMatchesLast(nsIURI* aSelectedSource, double aSelectedDensity);
+  // True if the given URL equals the last URL that was loaded by this element.
+  bool SelectedSourceMatchesLast(nsIURI* aSelectedSource);
 
   // Resolve and load the current mResponsiveSelector (responsive mode) or src
   // attr image.
   nsresult LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad);
 
   // True if this string represents a type we would support on <source type>
   static bool SupportedPictureSourceType(const nsAString& aType);
 
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -897,16 +897,20 @@ nsPluginInstanceOwner::RequestCommitOrCa
     widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION);
   }
   return true;
 }
 
 bool
 nsPluginInstanceOwner::EnableIME(bool aEnable)
 {
+  if (NS_WARN_IF(!mPluginFrame)) {
+    return false;
+  }
+
   nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset();
   if (!widget) {
     widget = GetRootWidgetForPluginFrame(mPluginFrame);
     if (NS_WARN_IF(!widget)) {
       return false;
     }
   }
 
--- a/dom/smil/nsSMILCSSProperty.cpp
+++ b/dom/smil/nsSMILCSSProperty.cpp
@@ -102,27 +102,24 @@ nsSMILCSSProperty::ValueFromString(const
   }
   return NS_OK;
 }
 
 nsresult
 nsSMILCSSProperty::SetAnimValue(const nsSMILValue& aValue)
 {
   NS_ENSURE_TRUE(IsPropertyAnimatable(mPropID), NS_ERROR_FAILURE);
-  return mElement->GetSMILOverrideStyle()->SetSMILValue(mPropID, aValue);
+  return mElement->SMILOverrideStyle()->SetSMILValue(mPropID, aValue);
 }
 
 void
 nsSMILCSSProperty::ClearAnimValue()
 {
   // Put empty string in override style for our property
-  nsDOMCSSAttributeDeclaration* overrideDecl = mElement->GetSMILOverrideStyle();
-  if (overrideDecl) {
-    overrideDecl->SetPropertyValue(mPropID, EmptyString(), nullptr);
-  }
+  mElement->SMILOverrideStyle()->SetPropertyValue(mPropID, EmptyString(), nullptr);
 }
 
 // Based on http://www.w3.org/TR/SVG/propidx.html
 // static
 bool
 nsSMILCSSProperty::IsPropertyAnimatable(nsCSSPropertyID aPropID)
 {
   // Bug 1353918: Drop this check
--- a/gfx/skia/skia/src/core/SkScan_Path.cpp
+++ b/gfx/skia/skia/src/core/SkScan_Path.cpp
@@ -559,17 +559,22 @@ static bool clip_to_limit(const SkRegion
     }
     reduced->op(orig, limitR, SkRegion::kIntersect_Op);
     return true;
 }
 
 // Bias used for conservative rounding of float rects to int rects, to nudge the irects a little
 // larger, so we don't "think" a path's bounds are inside a clip, when (due to numeric drift in
 // the scan-converter) we might walk beyond the predicted limits.
-static const double kConservativeRoundBias = 0.5 + 0.5 / SK_FDot6One;
+//
+// This value has been determined trial and error: pick the smallest value (after the 0.5) that
+// fixes any problematic cases (e.g. crbug.com/844457)
+// NOTE: cubics appear to be the main reason for needing this slop. If we could (perhaps) have a
+// more accurate walker for cubics, we may be able to reduce this fudge factor.
+static const double kConservativeRoundBias = 0.5 + 1.5 / SK_FDot6One;
 
 /**
  *  Round the value down. This is used to round the top and left of a rectangle,
  *  and corresponds to the way the scan converter treats the top and left edges.
  *  It has a slight bias to make the "rounded" int smaller than a normal round, to create a more
  *  conservative int-bounds (larger) from a float rect.
  */
 static inline int round_down_to_int(SkScalar x) {
--- a/image/SurfacePipe.h
+++ b/image/SurfacePipe.h
@@ -24,16 +24,17 @@
 
 #include <stdint.h>
 
 #include "nsDebug.h"
 
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Variant.h"
 #include "mozilla/gfx/2D.h"
 
 namespace mozilla {
 namespace image {
 
@@ -170,16 +171,53 @@ public:
   {
     Maybe<WriteState> result;
     while (!(result = DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc)))) { }
 
     return *result;
   }
 
   /**
+   * Write pixels to the surface by calling a lambda which may write as many
+   * pixels as there is remaining to complete the row. It is not completely
+   * memory safe as it trusts the underlying decoder not to overrun the given
+   * buffer, however it is an acceptable tradeoff for performance.
+   *
+   * Writing continues until every pixel in the surface has been written to
+   * (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState
+   * which WritePixelBlocks() will return to the caller.
+   *
+   * The template parameter PixelType must be uint8_t (for paletted surfaces) or
+   * uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
+   * size passed to ConfigureFilter().
+   *
+   * XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
+   * which means we can remove the PixelType template parameter from this
+   * method.
+   *
+   * @param aFunc A lambda that functions as a generator, yielding at most the
+   *              maximum number of pixels requested. The lambda must accept a
+   *              pointer argument to the first pixel to write, a maximum
+   *              number of pixels to write as part of the block, and return a
+   *              NextPixel<PixelType> value.
+   *
+   * @return A WriteState value indicating the lambda generator's state.
+   *         WritePixelBlocks() itself will return WriteState::FINISHED if
+   *         writing has finished, regardless of the lambda's internal state.
+   */
+  template <typename PixelType, typename Func>
+  WriteState WritePixelBlocks(Func aFunc)
+  {
+    Maybe<WriteState> result;
+    while (!(result = DoWritePixelBlockToRow<PixelType>(Forward<Func>(aFunc)))) { }
+
+    return *result;
+  }
+
+  /**
    * A variant of WritePixels() that writes a single row of pixels to the
    * surface one at a time by repeatedly calling a lambda that yields pixels.
    * WritePixelsToRow() is completely memory safe.
    *
    * Writing continues until every pixel in the row has been written to. If the
    * surface is complete at that pointer, WriteState::FINISHED is returned;
    * otherwise, WritePixelsToRow() returns WriteState::NEED_MORE_DATA. The
    * lambda can terminate writing early by returning a WriteState itself, which
@@ -444,16 +482,60 @@ protected:
     mPixelSize = aPixelSize;
 
     ResetToFirstRow();
   }
 
 private:
 
   /**
+   * An internal method used to implement WritePixelBlocks. This method writes
+   * up to the number of pixels necessary to complete the row and returns Some()
+   * if we either finished the entire surface or the lambda returned a
+   * WriteState indicating that we should return to the caller. If the row was
+   * successfully written without either of those things happening, it returns
+   * Nothing(), allowing WritePixelBlocks() to iterate to fill as many rows as
+   * possible.
+   */
+  template <typename PixelType, typename Func>
+  Maybe<WriteState> DoWritePixelBlockToRow(Func aFunc)
+  {
+    MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
+    MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
+    MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
+
+    if (IsSurfaceFinished()) {
+      return Some(WriteState::FINISHED);  // We're already done.
+    }
+
+    PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
+    int32_t remainder = mInputSize.width - mCol;
+    int32_t written;
+    Maybe<WriteState> result;
+    Tie(written, result) = aFunc(&rowPtr[mCol], remainder);
+    if (written == remainder) {
+      MOZ_ASSERT(result.isNothing());
+      mCol = mInputSize.width;
+      AdvanceRow();  // We've finished the row.
+      return IsSurfaceFinished() ? Some(WriteState::FINISHED)
+                                 : Nothing();
+    }
+
+    MOZ_ASSERT(written >= 0 && written < remainder);
+    MOZ_ASSERT(result.isSome());
+
+    mCol += written;
+    if (*result == WriteState::FINISHED) {
+      ZeroOutRestOfSurface<PixelType>();
+    }
+
+    return result;
+  }
+
+  /**
    * An internal method used to implement both WritePixels() and
    * WritePixelsToRow(). Those methods differ only in their behavior after a row
    * is successfully written - WritePixels() continues to write another row,
    * while WritePixelsToRow() returns to the caller. This method writes a single
    * row and returns Some() if we either finished the entire surface or the
    * lambda returned a WriteState indicating that we should return to the
    * caller. If the row was successfully written without either of those things
    * happening, it returns Nothing(), allowing WritePixels() and
@@ -554,16 +636,30 @@ public:
   template <typename PixelType, typename Func>
   WriteState WritePixels(Func aFunc)
   {
     MOZ_ASSERT(mHead, "Use before configured!");
     return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
   }
 
   /**
+   * A variant of WritePixels() that writes up to a single row of pixels to the
+   * surface in blocks by repeatedly calling a lambda that yields up to the
+   * requested number of pixels.
+   *
+   * @see SurfaceFilter::WritePixelBlocks() for the canonical documentation.
+   */
+  template <typename PixelType, typename Func>
+  WriteState WritePixelBlocks(Func aFunc)
+  {
+    MOZ_ASSERT(mHead, "Use before configured!");
+    return mHead->WritePixelBlocks<PixelType>(Forward<Func>(aFunc));
+  }
+
+  /**
    * A variant of WritePixels() that writes a single row of pixels to the
    * surface one at a time by repeatedly calling a lambda that yields pixels.
    * WritePixelsToRow() is completely memory safe.
    *
    * @see SurfaceFilter::WritePixelsToRow() for the canonical documentation.
    */
   template <typename PixelType, typename Func>
   WriteState WritePixelsToRow(Func aFunc)
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -287,130 +287,143 @@ nsGIFDecoder2::ColormapIndexToPixel(uint
 template <>
 uint8_t
 nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
 {
   return aIndex & mColorMask;
 }
 
 template <typename PixelSize>
-NextPixel<PixelSize>
-nsGIFDecoder2::YieldPixel(const uint8_t* aData,
-                          size_t aLength,
-                          size_t* aBytesReadOut)
+Tuple<int32_t, Maybe<WriteState>>
+nsGIFDecoder2::YieldPixels(const uint8_t* aData,
+                           size_t aLength,
+                           size_t* aBytesReadOut,
+                           PixelSize* aPixelBlock,
+                           int32_t aBlockSize)
 {
   MOZ_ASSERT(aData);
   MOZ_ASSERT(aBytesReadOut);
   MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack);
 
   // Advance to the next byte we should read.
   const uint8_t* data = aData + *aBytesReadOut;
 
-  // If we don't have any decoded data to yield, try to read some input and
-  // produce some.
-  if (mGIFStruct.stackp == mGIFStruct.stack) {
-    while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
-      // Feed the next byte into the decoder's 32-bit input buffer.
-      mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
-      mGIFStruct.bits += 8;
-      data += 1;
-      *aBytesReadOut += 1;
-    }
+  int32_t written = 0;
+  while (aBlockSize > written) {
+    // If we don't have any decoded data to yield, try to read some input and
+    // produce some.
+    if (mGIFStruct.stackp == mGIFStruct.stack) {
+      while (mGIFStruct.bits < mGIFStruct.codesize && *aBytesReadOut < aLength) {
+        // Feed the next byte into the decoder's 32-bit input buffer.
+        mGIFStruct.datum += int32_t(*data) << mGIFStruct.bits;
+        mGIFStruct.bits += 8;
+        data += 1;
+        *aBytesReadOut += 1;
+      }
 
-    if (mGIFStruct.bits < mGIFStruct.codesize) {
-      return AsVariant(WriteState::NEED_MORE_DATA);
-    }
-
-    // Get the leading variable-length symbol from the data stream.
-    int code = mGIFStruct.datum & mGIFStruct.codemask;
-    mGIFStruct.datum >>= mGIFStruct.codesize;
-    mGIFStruct.bits -= mGIFStruct.codesize;
+      if (mGIFStruct.bits < mGIFStruct.codesize) {
+        return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
+      }
 
-    const int clearCode = ClearCode();
+      // Get the leading variable-length symbol from the data stream.
+      int code = mGIFStruct.datum & mGIFStruct.codemask;
+      mGIFStruct.datum >>= mGIFStruct.codesize;
+      mGIFStruct.bits -= mGIFStruct.codesize;
 
-    // Reset the dictionary to its original state, if requested
-    if (code == clearCode) {
-      mGIFStruct.codesize = mGIFStruct.datasize + 1;
-      mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
-      mGIFStruct.avail = clearCode + 2;
-      mGIFStruct.oldcode = -1;
-      return AsVariant(WriteState::NEED_MORE_DATA);
-    }
+      const int clearCode = ClearCode();
 
-    // Check for explicit end-of-stream code. It should only appear after all
-    // image data, but if that was the case we wouldn't be in this function, so
-    // this is always an error condition.
-    if (code == (clearCode + 1)) {
-      return AsVariant(WriteState::FAILURE);
-    }
+      // Reset the dictionary to its original state, if requested
+      if (code == clearCode) {
+        mGIFStruct.codesize = mGIFStruct.datasize + 1;
+        mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
+        mGIFStruct.avail = clearCode + 2;
+        mGIFStruct.oldcode = -1;
+        return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
+      }
 
-    if (mGIFStruct.oldcode == -1) {
-      if (code >= MAX_BITS) {
-        return AsVariant(WriteState::FAILURE);  // The code's too big; something's wrong.
+      // Check for explicit end-of-stream code. It should only appear after all
+      // image data, but if that was the case we wouldn't be in this function, so
+      // this is always an error condition.
+      if (code == (clearCode + 1)) {
+        return MakeTuple(written, Some(WriteState::FAILURE));
       }
 
-      mGIFStruct.firstchar = mGIFStruct.oldcode = code;
+      if (mGIFStruct.oldcode == -1) {
+        if (code >= MAX_BITS) {
+          // The code's too big; something's wrong.
+          return MakeTuple(written, Some(WriteState::FAILURE));
+        }
+
+        mGIFStruct.firstchar = mGIFStruct.oldcode = code;
+
+        // Yield a pixel at the appropriate index in the colormap.
+        mGIFStruct.pixels_remaining--;
+        aPixelBlock[written++] =
+          ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]);
+        continue;
+      }
+
+      int incode = code;
+      if (code >= mGIFStruct.avail) {
+        *mGIFStruct.stackp++ = mGIFStruct.firstchar;
+        code = mGIFStruct.oldcode;
+
+        if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+          // Stack overflow; something's wrong.
+          return MakeTuple(written, Some(WriteState::FAILURE));
+        }
+      }
 
-      // Yield a pixel at the appropriate index in the colormap.
-      mGIFStruct.pixels_remaining--;
-      return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]));
+      while (code >= clearCode) {
+        if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
+          return MakeTuple(written, Some(WriteState::FAILURE));
+        }
+
+        *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
+        code = mGIFStruct.prefix[code];
+
+        if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+          // Stack overflow; something's wrong.
+          return MakeTuple(written, Some(WriteState::FAILURE));
+        }
+      }
+
+      *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
+
+      // Define a new codeword in the dictionary.
+      if (mGIFStruct.avail < 4096) {
+        mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
+        mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
+        mGIFStruct.avail++;
+
+        // If we've used up all the codewords of a given length increase the
+        // length of codewords by one bit, but don't exceed the specified maximum
+        // codeword size of 12 bits.
+        if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
+            (mGIFStruct.avail < 4096)) {
+          mGIFStruct.codesize++;
+          mGIFStruct.codemask += mGIFStruct.avail;
+        }
+      }
+
+      mGIFStruct.oldcode = incode;
     }
 
-    int incode = code;
-    if (code >= mGIFStruct.avail) {
-      *mGIFStruct.stackp++ = mGIFStruct.firstchar;
-      code = mGIFStruct.oldcode;
-
-      if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
-        return AsVariant(WriteState::FAILURE);  // Stack overflow; something's wrong.
-      }
-    }
-
-    while (code >= clearCode) {
-      if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
-        return AsVariant(WriteState::FAILURE);
-      }
-
-      *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
-      code = mGIFStruct.prefix[code];
-
-      if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
-        return AsVariant(WriteState::FAILURE);  // Stack overflow; something's wrong.
-      }
+    if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
+      MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
+      return MakeTuple(written, Some(WriteState::FAILURE));
     }
 
-    *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
-
-    // Define a new codeword in the dictionary.
-    if (mGIFStruct.avail < 4096) {
-      mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
-      mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
-      mGIFStruct.avail++;
-
-      // If we've used up all the codewords of a given length increase the
-      // length of codewords by one bit, but don't exceed the specified maximum
-      // codeword size of 12 bits.
-      if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
-          (mGIFStruct.avail < 4096)) {
-        mGIFStruct.codesize++;
-        mGIFStruct.codemask += mGIFStruct.avail;
-      }
-    }
-
-    mGIFStruct.oldcode = incode;
+    // Yield a pixel at the appropriate index in the colormap.
+    mGIFStruct.pixels_remaining--;
+    aPixelBlock[written++]
+      = ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp);
   }
 
-  if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
-    MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
-    return AsVariant(WriteState::FAILURE);
-  }
-
-  // Yield a pixel at the appropriate index in the colormap.
-  mGIFStruct.pixels_remaining--;
-  return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp));
+  return MakeTuple(written, Maybe<WriteState>());
 }
 
 /// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
 /// And apply any LCMS transformation.
 static void
 ConvertColormap(uint32_t* aColormap, uint32_t aColors)
 {
   // Apply CMS transformation if enabled and available
@@ -1027,18 +1040,22 @@ nsGIFDecoder2::ReadLZWData(const char* a
   const uint8_t* data = reinterpret_cast<const uint8_t*>(aData);
   size_t length = aLength;
 
   while (mGIFStruct.pixels_remaining > 0 &&
          (length > 0 || mGIFStruct.bits >= mGIFStruct.codesize)) {
     size_t bytesRead = 0;
 
     auto result = mGIFStruct.images_decoded == 0
-      ? mPipe.WritePixels<uint32_t>([&]{ return YieldPixel<uint32_t>(data, length, &bytesRead); })
-      : mPipe.WritePixels<uint8_t>([&]{ return YieldPixel<uint8_t>(data, length, &bytesRead); });
+      ? mPipe.WritePixelBlocks<uint32_t>([&](uint32_t* aPixelBlock, int32_t aBlockSize) {
+          return YieldPixels<uint32_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
+        })
+      : mPipe.WritePixelBlocks<uint8_t>([&](uint8_t* aPixelBlock, int32_t aBlockSize) {
+          return YieldPixels<uint8_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
+        });
 
     if (MOZ_UNLIKELY(bytesRead > length)) {
       MOZ_ASSERT_UNREACHABLE("Overread?");
       bytesRead = length;
     }
 
     // Advance our position in the input based upon what YieldPixel() consumed.
     data += bytesRead;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -60,18 +60,22 @@ private:
   /// Called when we finish decoding the entire image.
   void      FlushImageData();
 
   /// Transforms a palette index into a pixel.
   template <typename PixelSize> PixelSize
   ColormapIndexToPixel(uint8_t aIndex);
 
   /// A generator function that performs LZW decompression and yields pixels.
-  template <typename PixelSize> NextPixel<PixelSize>
-  YieldPixel(const uint8_t* aData, size_t aLength, size_t* aBytesReadOut);
+  template <typename PixelSize> Tuple<int32_t, Maybe<WriteState>>
+  YieldPixels(const uint8_t* aData,
+              size_t aLength,
+              size_t* aBytesReadOut,
+              PixelSize* aPixelBlock,
+              int32_t aBlockSize);
 
   /// Checks if we have transparency, either because the header indicates that
   /// there's alpha, or because the frame rect doesn't cover the entire image.
   bool CheckForTransparency(const gfx::IntRect& aFrameRect);
 
   // @return the clear code used for LZW decompression.
   int ClearCode() const {
     MOZ_ASSERT(mGIFStruct.datasize <= MAX_LZW_BITS);
--- a/image/imgTools.cpp
+++ b/image/imgTools.cpp
@@ -300,70 +300,81 @@ imgTools::DecodeImageAsync(nsIInputStrea
 /**
  * This takes a DataSourceSurface rather than a SourceSurface because some
  * of the callers have a DataSourceSurface and we don't want to call
  * GetDataSurface on such surfaces since that may incure a conversion to
  * SurfaceType::DATA which we don't need.
  */
 static nsresult
 EncodeImageData(DataSourceSurface* aDataSurface,
+                DataSourceSurface::ScopedMap& aMap,
                 const nsACString& aMimeType,
                 const nsAString& aOutputOptions,
                 nsIInputStream** aStream)
 {
-  MOZ_ASSERT(aDataSurface->GetFormat() ==  SurfaceFormat::B8G8R8A8,
-             "We're assuming B8G8R8A8");
+  MOZ_ASSERT(aDataSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+             aDataSurface->GetFormat() == SurfaceFormat::B8G8R8X8,
+             "We're assuming B8G8R8A8/X8");
 
   // Get an image encoder for the media type
   nsAutoCString encoderCID(
     NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
 
   nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
-  DataSourceSurface::MappedSurface map;
-  if (!aDataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
-    return NS_ERROR_FAILURE;
-  }
-
   IntSize size = aDataSurface->GetSize();
-  uint32_t dataLength = map.mStride * size.height;
+  uint32_t dataLength = aMap.GetStride() * size.height;
 
   // Encode the bitmap
-  nsresult rv = encoder->InitFromData(map.mData,
+  nsresult rv = encoder->InitFromData(aMap.GetData(),
                                       dataLength,
                                       size.width,
                                       size.height,
-                                      map.mStride,
+                                      aMap.GetStride(),
                                       imgIEncoder::INPUT_FORMAT_HOSTARGB,
                                       aOutputOptions);
-  aDataSurface->Unmap();
   NS_ENSURE_SUCCESS(rv, rv);
 
   encoder.forget(aStream);
   return NS_OK;
 }
 
+static nsresult
+EncodeImageData(DataSourceSurface* aDataSurface,
+                const nsACString& aMimeType,
+                const nsAString& aOutputOptions,
+                nsIInputStream** aStream)
+{
+  DataSourceSurface::ScopedMap map(aDataSurface, DataSourceSurface::READ);
+  if (!map.IsMapped()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return EncodeImageData(aDataSurface, map, aMimeType, aOutputOptions, aStream);
+}
+
 NS_IMETHODIMP
 imgTools::EncodeImage(imgIContainer* aContainer,
                       const nsACString& aMimeType,
                       const nsAString& aOutputOptions,
                       nsIInputStream** aStream)
 {
   // Use frame 0 from the image container.
   RefPtr<SourceSurface> frame =
     aContainer->GetFrame(imgIContainer::FRAME_FIRST,
                          imgIContainer::FLAG_SYNC_DECODE);
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   RefPtr<DataSourceSurface> dataSurface;
 
-  if (frame->GetFormat() == SurfaceFormat::B8G8R8A8) {
+  if (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+      frame->GetFormat() == SurfaceFormat::B8G8R8X8) {
     dataSurface = frame->GetDataSurface();
   } else {
     // Convert format to SurfaceFormat::B8G8R8A8
     dataSurface = gfxUtils::
       CopySurfaceToDataSourceSurfaceWithFormat(frame,
                                                SurfaceFormat::B8G8R8A8);
   }
 
@@ -402,48 +413,57 @@ imgTools::EncodeScaledImage(imgIContaine
   // Use frame 0 from the image container.
   RefPtr<SourceSurface> frame =
     aContainer->GetFrameAtSize(scaledSize,
                                imgIContainer::FRAME_FIRST,
                                imgIContainer::FLAG_HIGH_QUALITY_SCALING |
                                imgIContainer::FLAG_SYNC_DECODE);
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
+  // If the given surface is the right size/format, we can encode it directly.
+  if (scaledSize == frame->GetSize() &&
+      (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+       frame->GetFormat() == SurfaceFormat::B8G8R8X8)) {
+    RefPtr<DataSourceSurface> dataSurface = frame->GetDataSurface();
+    if (dataSurface) {
+      return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
+    }
+  }
+
+  // Otherwise we need to scale it using a draw target.
   RefPtr<DataSourceSurface> dataSurface =
     Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
   if (NS_WARN_IF(!dataSurface)) {
     return NS_ERROR_FAILURE;
   }
 
-  DataSourceSurface::MappedSurface map;
-  if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
+  DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+  if (!map.IsMapped()) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<DrawTarget> dt =
-    Factory::CreateDrawTargetForData(BackendType::CAIRO,
-                                     map.mData,
+    Factory::CreateDrawTargetForData(BackendType::SKIA,
+                                     map.GetData(),
                                      dataSurface->GetSize(),
-                                     map.mStride,
+                                     map.GetStride(),
                                      SurfaceFormat::B8G8R8A8);
   if (!dt) {
     gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   IntSize frameSize = frame->GetSize();
   dt->DrawSurface(frame,
                   Rect(0, 0, scaledSize.width, scaledSize.height),
                   Rect(0, 0, frameSize.width, frameSize.height),
                   DrawSurfaceOptions(),
                   DrawOptions(1.0f, CompositionOp::OP_SOURCE));
 
-  dataSurface->Unmap();
-
-  return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
+  return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
 }
 
 NS_IMETHODIMP
 imgTools::EncodeCroppedImage(imgIContainer* aContainer,
                              const nsACString& aMimeType,
                              int32_t aOffsetX,
                              int32_t aOffsetY,
                              int32_t aWidth,
@@ -487,39 +507,37 @@ imgTools::EncodeCroppedImage(imgIContain
   RefPtr<DataSourceSurface> dataSurface =
     Factory::CreateDataSourceSurface(IntSize(aWidth, aHeight),
                                      SurfaceFormat::B8G8R8A8,
                                      /* aZero = */ true);
   if (NS_WARN_IF(!dataSurface)) {
     return NS_ERROR_FAILURE;
   }
 
-  DataSourceSurface::MappedSurface map;
-  if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
+  DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+  if (!map.IsMapped()) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<DrawTarget> dt =
-    Factory::CreateDrawTargetForData(BackendType::CAIRO,
-                                     map.mData,
+    Factory::CreateDrawTargetForData(BackendType::SKIA,
+                                     map.GetData(),
                                      dataSurface->GetSize(),
-                                     map.mStride,
+                                     map.GetStride(),
                                      SurfaceFormat::B8G8R8A8);
   if (!dt) {
     gfxWarning() <<
       "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
     return NS_ERROR_OUT_OF_MEMORY;
   }
   dt->CopySurface(frame,
                   IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
                   IntPoint(0, 0));
 
-  dataSurface->Unmap();
-
-  return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
+  return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
 }
 
 NS_IMETHODIMP
 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
                                  imgINotificationObserver** aObserver)
 {
   NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
   return NS_OK;
--- a/image/test/gtest/TestSurfacePipeIntegration.cpp
+++ b/image/test/gtest/TestSurfacePipeIntegration.cpp
@@ -196,16 +196,32 @@ TEST_F(ImageSurfacePipeIntegration, Surf
       result = pipe.WriteBuffer(buffer, 0, 100);
       ++count;
     }
     EXPECT_EQ(WriteState::FINISHED, result);
     EXPECT_EQ(100u, count);
     CheckSurfacePipeMethodResults(&pipe, decoder);
   }
 
+  // Test that WritePixelBlocks() gets passed through to the underlying pipeline.
+  {
+    uint32_t count = 0;
+    WriteState result = pipe.WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                            int32_t aLength) {
+      ++count;
+      EXPECT_EQ(int32_t(100), aLength);
+      memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t));
+      return MakeTuple(int32_t(100), Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(100u, count);
+    CheckSurfacePipeMethodResults(&pipe, decoder);
+  }
+
   // Test that WriteEmptyRow() gets passed through to the underlying pipeline.
   {
     uint32_t count = 0;
     WriteState result = WriteState::NEED_MORE_DATA;
     while (result == WriteState::NEED_MORE_DATA) {
       result = pipe.WriteEmptyRow();
       ++count;
     }
@@ -276,16 +292,32 @@ TEST_F(ImageSurfacePipeIntegration, Pale
       result = pipe.WriteBuffer(buffer, 0, 100);
       ++count;
     }
     EXPECT_EQ(WriteState::FINISHED, result);
     EXPECT_EQ(100u, count);
     CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
   }
 
+  // Test that WritePixelBlocks() gets passed through to the underlying pipeline.
+  {
+    uint32_t count = 0;
+    WriteState result = pipe.WritePixelBlocks<uint8_t>([&](uint8_t* aBlockStart,
+                                                           int32_t aLength) {
+      ++count;
+      EXPECT_EQ(int32_t(100), aLength);
+      memcpy(aBlockStart, buffer, 100 * sizeof(uint8_t));
+      return MakeTuple(int32_t(100), Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(100u, count);
+    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
+  }
+
   // Test that WriteEmptyRow() gets passed through to the underlying pipeline.
   {
     uint32_t count = 0;
     WriteState result = WriteState::NEED_MORE_DATA;
     while (result == WriteState::NEED_MORE_DATA) {
       result = pipe.WriteEmptyRow();
       ++count;
     }
--- a/image/test/gtest/TestSurfaceSink.cpp
+++ b/image/test/gtest/TestSurfaceSink.cpp
@@ -597,16 +597,164 @@ TEST(ImageSurfaceSink, SurfaceSinkWriteU
                                                          uint32_t aLength) {
         EXPECT_EQ(100u, aLength );
         memcpy(aRow + 50, buffer, 50 * sizeof(uint32_t));
       });
     });
   });
 }
 
+TEST(ImageSurfaceSink, SurfaceSinkWritePixelBlocks)
+{
+  WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
+    // Create a green buffer the same size as one row of the surface (which is 100x100),
+    // containing 60 pixels of green in the middle and 20 transparent pixels on
+    // either side.
+    uint32_t buffer[100];
+    for (int i = 0; i < 100; ++i) {
+      buffer[i] = 20 <= i && i < 80 ? BGRAColor::Green().AsPixel()
+                                    : BGRAColor::Transparent().AsPixel();
+    }
+
+    uint32_t count = 0;
+    WriteState result = aSink->WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                              int32_t aLength) {
+      ++count;
+      EXPECT_EQ(int32_t(100), aLength);
+      memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t));
+      return MakeTuple(int32_t(100), Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(100u, count);
+
+    AssertCorrectPipelineFinalState(aSink,
+                                    IntRect(0, 0, 100, 100),
+                                    IntRect(0, 0, 100, 100));
+
+    // Check that the generated image is correct.
+    CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100));
+
+    // Attempt to write more and make sure that nothing gets written.
+    count = 0;
+    result = aSink->WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                   int32_t aLength) {
+      count++;
+      for (int32_t i = 0; i < aLength; ++i) {
+        aBlockStart[i] = BGRAColor::Red().AsPixel();
+      }
+      return MakeTuple(aLength, Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(0u, count);
+    EXPECT_TRUE(aSink->IsSurfaceFinished());
+
+    // Check that the generated image is still correct.
+    CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100));
+  });
+}
+
+TEST(ImageSurfaceSink, SurfaceSinkWritePixelBlocksPartialRow)
+{
+  WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
+    // Create a green buffer the same size as one row of the surface (which is 100x100),
+    // containing 60 pixels of green in the middle and 20 transparent pixels on
+    // either side.
+    uint32_t buffer[100];
+    for (int i = 0; i < 100; ++i) {
+      buffer[i] = 20 <= i && i < 80 ? BGRAColor::Green().AsPixel()
+                                    : BGRAColor::Transparent().AsPixel();
+    }
+
+    // Write the first 99 rows of our 100x100 surface and verify that even
+    // though our lambda will yield pixels forever, only one row is written per
+    // call to WritePixelsToRow().
+    for (int row = 0; row < 99; ++row) {
+      for (int32_t written = 0; written < 100; ) {
+        WriteState result = aSink->WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                                  int32_t aLength) {
+          // When we write the final block of pixels, it will request we start
+          // another row. We should abort at that point.
+          if (aLength == int32_t(100) && written == int32_t(100)) {
+            return MakeTuple(int32_t(0), Some(WriteState::NEED_MORE_DATA));
+          }
+
+          // It should always request enough data to fill the row. So it should
+          // request 100, 75, 50, and finally 25 pixels.
+          EXPECT_EQ(int32_t(100) - written, aLength);
+
+          // Only write one quarter of the pixels for the row.
+          memcpy(aBlockStart, &buffer[written], 25 * sizeof(uint32_t));
+          written += 25;
+
+          // We've written the last pixels remaining for the row.
+          if (written == int32_t(100)) {
+            return MakeTuple(int32_t(25), Maybe<WriteState>());
+          }
+
+          // We've written another quarter of the row but not yet all of it.
+          return MakeTuple(int32_t(25), Some(WriteState::NEED_MORE_DATA));
+        });
+
+        EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
+      }
+
+      EXPECT_FALSE(aSink->IsSurfaceFinished());
+
+      Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
+      EXPECT_TRUE(invalidRect.isSome());
+      EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
+      EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect);
+
+      CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, row + 1));
+    }
+
+    // Write the final line, which should finish the surface.
+    uint32_t count = 0;
+    WriteState result = aSink->WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                              int32_t aLength) {
+      ++count;
+      EXPECT_EQ(int32_t(100), aLength);
+      memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t));
+      return MakeTuple(int32_t(100), Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(1u, count);
+
+    // Note that the final invalid rect we expect here is only the last row;
+    // that's because we called TakeInvalidRect() repeatedly in the loop above.
+    AssertCorrectPipelineFinalState(aSink,
+                                    IntRect(0, 99, 100, 1),
+                                    IntRect(0, 99, 100, 1));
+
+    // Check that the generated image is correct.
+    CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100));
+
+    // Attempt to write more and make sure that nothing gets written.
+    count = 0;
+    result = aSink->WritePixelBlocks<uint32_t>([&](uint32_t* aBlockStart,
+                                                   int32_t aLength) {
+      count++;
+      for (int32_t i = 0; i < aLength; ++i) {
+        aBlockStart[i] = BGRAColor::Red().AsPixel();
+      }
+      return MakeTuple(aLength, Maybe<WriteState>());
+    });
+
+    EXPECT_EQ(WriteState::FINISHED, result);
+    EXPECT_EQ(0u, count);
+    EXPECT_TRUE(aSink->IsSurfaceFinished());
+
+    // Check that the generated image is still correct.
+    CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100));
+  });
+}
+
 TEST(ImageSurfaceSink, SurfaceSinkProgressivePasses)
 {
   WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
     {
       // Fill the image with a first pass of red.
       uint32_t count = 0;
       auto result = aSink->WritePixels<uint32_t>([&]() {
         ++count;
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -145,17 +145,17 @@ class Builder {
 
       protected:
         // The Builder to which this trusted thing belongs.
         Builder& owner;
 
         // A rooted reference to our value.
         PersistentRooted<T> value;
 
-        BuiltThing(JSContext* cx, Builder& owner_, T value_ = GCPolicy<T>::initial())
+        BuiltThing(JSContext* cx, Builder& owner_, T value_ = SafelyInitialized<T>())
           : owner(owner_), value(cx, value_)
         {
             owner.assertBuilt(value_);
         }
 
         // Forward some things from our owner, for convenience.
         js::Debugger* debugger() const { return owner.debugger; }
         JSObject* debuggerObject() const { return owner.debuggerObject; }
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -8,19 +8,16 @@
 
 // A GCPolicy controls how the GC interacts with both direct pointers to GC
 // things (e.g. JSObject* or JSString*), tagged and/or optional pointers to GC
 // things (e.g.  Value or jsid), and C++ container types (e.g.
 // JSPropertyDescriptor or GCHashMap).
 //
 // The GCPolicy provides at a minimum:
 //
-//   static T initial()
-//       - Construct and return an empty T.
-//
 //   static void trace(JSTracer, T* tp, const char* name)
 //       - Trace the edge |*tp|, calling the edge |name|. Containers like
 //         GCHashMap and GCHashSet use this method to trace their children.
 //
 //   static bool needsSweep(T* tp)
 //       - Return true if |*tp| is about to be finalized. Otherwise, update the
 //         edge for moving GC, and return false. Containers like GCHashMap and
 //         GCHashSet use this method to decide when to remove an entry: if this
@@ -67,20 +64,16 @@
 
 namespace JS {
 
 // Defines a policy for container types with non-GC, i.e. C storage. This
 // policy dispatches to the underlying struct for GC interactions.
 template <typename T>
 struct StructGCPolicy
 {
-    static T initial() {
-        return T();
-    }
-
     static void trace(JSTracer* trc, T* tp, const char* name) {
         tp->trace(trc);
     }
 
     static void sweep(T* tp) {
         return tp->sweep();
     }
 
@@ -97,28 +90,26 @@ struct StructGCPolicy
 // Most C++ structures that contain a default constructor, a trace function and
 // a sweep function will work out of the box with Rooted, Handle, GCVector,
 // and GCHash{Set,Map}.
 template <typename T> struct GCPolicy : public StructGCPolicy<T> {};
 
 // This policy ignores any GC interaction, e.g. for non-GC types.
 template <typename T>
 struct IgnoreGCPolicy {
-    static T initial() { return T(); }
     static void trace(JSTracer* trc, T* t, const char* name) {}
     static bool needsSweep(T* v) { return false; }
     static bool isValid(const T& v) { return true; }
 };
 template <> struct GCPolicy<uint32_t> : public IgnoreGCPolicy<uint32_t> {};
 template <> struct GCPolicy<uint64_t> : public IgnoreGCPolicy<uint64_t> {};
 
 template <typename T>
 struct GCPointerPolicy
 {
-    static T initial() { return nullptr; }
     static void trace(JSTracer* trc, T* vp, const char* name) {
         if (*vp)
             js::UnsafeTraceManuallyBarrieredEdge(trc, vp, name);
     }
     static bool needsSweep(T* vp) {
         if (*vp)
             return js::gc::IsAboutToBeFinalizedUnbarriered(vp);
         return false;
@@ -135,17 +126,16 @@ template <> struct GCPolicy<JSAtom*> : p
 template <> struct GCPolicy<JSFunction*> : public GCPointerPolicy<JSFunction*> {};
 template <> struct GCPolicy<JSObject*> : public GCPointerPolicy<JSObject*> {};
 template <> struct GCPolicy<JSScript*> : public GCPointerPolicy<JSScript*> {};
 template <> struct GCPolicy<JSString*> : public GCPointerPolicy<JSString*> {};
 
 template <typename T>
 struct NonGCPointerPolicy
 {
-    static T initial() { return nullptr; }
     static void trace(JSTracer* trc, T* vp, const char* name) {
         if (*vp)
             (*vp)->trace(trc);
     }
     static bool needsSweep(T* vp) {
         if (*vp)
             return (*vp)->needsSweep();
         return false;
@@ -165,17 +155,16 @@ struct GCPolicy<JS::Heap<T>>
         return *thingp && js::gc::EdgeNeedsSweep(thingp);
     }
 };
 
 // GCPolicy<UniquePtr<T>> forwards the contained pointer to GCPolicy<T>.
 template <typename T, typename D>
 struct GCPolicy<mozilla::UniquePtr<T, D>>
 {
-    static mozilla::UniquePtr<T,D> initial() { return mozilla::UniquePtr<T,D>(); }
     static void trace(JSTracer* trc, mozilla::UniquePtr<T,D>* tp, const char* name) {
         if (tp->get())
             GCPolicy<T>::trace(trc, tp->get(), name);
     }
     static bool needsSweep(mozilla::UniquePtr<T,D>* tp) {
         if (tp->get())
             return GCPolicy<T>::needsSweep(tp->get());
         return false;
@@ -187,17 +176,16 @@ struct GCPolicy<mozilla::UniquePtr<T, D>
     }
 };
 
 // GCPolicy<Maybe<T>> forwards tracing/sweeping to GCPolicy<T*> if
 // when the Maybe<T> is full.
 template <typename T>
 struct GCPolicy<mozilla::Maybe<T>>
 {
-    static mozilla::Maybe<T> initial() { return mozilla::Maybe<T>(); }
     static void trace(JSTracer* trc, mozilla::Maybe<T>* tp, const char* name) {
         if (tp->isSome())
             GCPolicy<T>::trace(trc, tp->ptr(), name);
     }
     static bool needsSweep(mozilla::Maybe<T>* tp) {
         if (tp->isSome())
             return GCPolicy<T>::needsSweep(tp->ptr());
         return false;
--- a/js/public/GCVariant.h
+++ b/js/public/GCVariant.h
@@ -107,19 +107,16 @@ struct GCVariantImplementation<T, Ts...>
 
 } // namespace detail
 
 template <typename... Ts>
 struct GCPolicy<mozilla::Variant<Ts...>>
 {
     using Impl = detail::GCVariantImplementation<Ts...>;
 
-    // Variants do not provide initial(). They do not have a default initial
-    // value and one must be provided.
-
     static void trace(JSTracer* trc, mozilla::Variant<Ts...>* v, const char* name) {
         Impl::trace(trc, v, name);
     }
 
     static bool isValid(const mozilla::Variant<Ts...>& v) {
         return v.match(IsValidMatcher());
     }
 
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -171,17 +171,16 @@ extern JS_PUBLIC_DATA(const jsid) JSID_E
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_VOIDHANDLE;
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_EMPTYHANDLE;
 
 namespace JS {
 
 template <>
 struct GCPolicy<jsid>
 {
-    static jsid initial() { return JSID_VOID; }
     static void trace(JSTracer* trc, jsid* idp, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, idp, name);
     }
     static bool isValid(jsid id) {
         return !JSID_IS_GCTHING(id) || js::gc::IsCellPointerValid(JSID_TO_GCTHING(id).asCell());
     }
 };
 
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -195,16 +195,50 @@ struct PersistentRootedMarker;
 namespace JS {
 
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next);
 JS_FRIEND_API(void) HeapStringPostBarrier(JSString** objp, JSString* prev, JSString* next);
 
+/**
+ * Create a safely-initialized |T|, suitable for use as a default value in
+ * situations requiring a safe but arbitrary |T| value.
+ */
+template<typename T>
+inline T
+SafelyInitialized()
+{
+    // This function wants to presume that |T()| -- which value-initializes a
+    // |T| per C++11 [expr.type.conv]p2 -- will produce a safely-initialized,
+    // safely-usable T that it can return.
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_UNIX) && !defined(__clang__))
+
+    // That presumption holds for pointers, where value initialization produces
+    // a null pointer.
+    constexpr bool IsPointer = std::is_pointer<T>::value;
+
+    // For classes and unions we *assume* that if |T|'s default constructor is
+    // non-trivial it'll initialize correctly. (This is unideal, but C++
+    // doesn't offer a type trait indicating whether a class's constructor is
+    // user-defined, which better approximates our desired semantics.)
+    constexpr bool IsNonTriviallyDefaultConstructibleClassOrUnion =
+        (std::is_class<T>::value || std::is_union<T>::value) &&
+        !std::is_trivially_default_constructible<T>::value;
+
+    static_assert(IsPointer || IsNonTriviallyDefaultConstructibleClassOrUnion,
+                  "T() must evaluate to a safely-initialized T");
+
+#endif
+
+    return T();
+}
+
 #ifdef JS_DEBUG
 /**
  * For generational GC, assert that an object is in the tenured generation as
  * opposed to being in the nursery.
  */
 extern JS_FRIEND_API(void)
 AssertGCThingMustBeTenured(JSObject* obj);
 extern JS_FRIEND_API(void)
@@ -242,30 +276,30 @@ class MOZ_NON_MEMMOVABLE Heap : public j
     static_assert(js::IsHeapConstructibleType<T>::value,
                   "Type T must be a public GC pointer type");
   public:
     using ElementType = T;
 
     Heap() {
         static_assert(sizeof(T) == sizeof(Heap<T>),
                       "Heap<T> must be binary compatible with T.");
-        init(GCPolicy<T>::initial());
+        init(SafelyInitialized<T>());
     }
     explicit Heap(const T& p) { init(p); }
 
     /*
      * For Heap, move semantics are equivalent to copy semantics. In C++, a
      * copy constructor taking const-ref is the way to get a single function
      * that will be used for both lvalue and rvalue copies, so we can simply
      * omit the rvalue variant.
      */
     explicit Heap(const Heap<T>& p) { init(p.ptr); }
 
     ~Heap() {
-        post(ptr, GCPolicy<T>::initial());
+        post(ptr, SafelyInitialized<T>());
     }
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(Heap, T);
 
     const T* address() const { return &ptr; }
 
     void exposeToActiveJS() const {
@@ -286,17 +320,17 @@ class MOZ_NON_MEMMOVABLE Heap : public j
     }
     explicit operator bool() {
         return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr));
     }
 
   private:
     void init(const T& newPtr) {
         ptr = newPtr;
-        post(GCPolicy<T>::initial(), ptr);
+        post(SafelyInitialized<T>(), ptr);
     }
 
     void set(const T& newPtr) {
         T tmp = ptr;
         ptr = newPtr;
         post(tmp, ptr);
     }
 
@@ -945,17 +979,17 @@ class MOZ_RAII Rooted : public js::Roote
         return rootLists(RootingContext::get(cx));
     }
 
   public:
     using ElementType = T;
 
     template <typename RootingContext>
     explicit Rooted(const RootingContext& cx)
-      : ptr(GCPolicy<T>::initial())
+      : ptr(SafelyInitialized<T>())
     {
         registerWithRootLists(rootLists(cx));
     }
 
     template <typename RootingContext, typename S>
     Rooted(const RootingContext& cx, S&& initial)
       : ptr(mozilla::Forward<S>(initial))
     {
@@ -1214,26 +1248,26 @@ class PersistentRooted : public js::Root
         MOZ_ASSERT(!initialized());
         JS::RootKind kind = JS::MapTypeToRootKind<T>::kind;
         AddPersistentRoot(rt, kind, reinterpret_cast<JS::PersistentRooted<void*>*>(this));
     }
 
   public:
     using ElementType = T;
 
-    PersistentRooted() : ptr(GCPolicy<T>::initial()) {}
+    PersistentRooted() : ptr(SafelyInitialized<T>()) {}
 
     explicit PersistentRooted(RootingContext* cx)
-      : ptr(GCPolicy<T>::initial())
+      : ptr(SafelyInitialized<T>())
     {
         registerWithRootLists(cx);
     }
 
     explicit PersistentRooted(JSContext* cx)
-      : ptr(GCPolicy<T>::initial())
+      : ptr(SafelyInitialized<T>())
     {
         registerWithRootLists(RootingContext::get(cx));
     }
 
     template <typename U>
     PersistentRooted(RootingContext* cx, U&& initial)
       : ptr(mozilla::Forward<U>(initial))
     {
@@ -1243,17 +1277,17 @@ class PersistentRooted : public js::Root
     template <typename U>
     PersistentRooted(JSContext* cx, U&& initial)
       : ptr(mozilla::Forward<U>(initial))
     {
         registerWithRootLists(RootingContext::get(cx));
     }
 
     explicit PersistentRooted(JSRuntime* rt)
-      : ptr(GCPolicy<T>::initial())
+      : ptr(SafelyInitialized<T>())
     {
         registerWithRootLists(rt);
     }
 
     template <typename U>
     PersistentRooted(JSRuntime* rt, U&& initial)
       : ptr(mozilla::Forward<U>(initial))
     {
@@ -1275,28 +1309,28 @@ class PersistentRooted : public js::Root
         const_cast<PersistentRooted&>(rhs).setNext(this);
     }
 
     bool initialized() {
         return ListBase::isInList();
     }
 
     void init(JSContext* cx) {
-        init(cx, GCPolicy<T>::initial());
+        init(cx, SafelyInitialized<T>());
     }
 
     template <typename U>
     void init(JSContext* cx, U&& initial) {
         ptr = mozilla::Forward<U>(initial);
         registerWithRootLists(RootingContext::get(cx));
     }
 
     void reset() {
         if (initialized()) {
-            set(GCPolicy<T>::initial());
+            set(SafelyInitialized<T>());
             ListBase::remove();
         }
     }
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(PersistentRooted, T);
     DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr);
 
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1239,17 +1239,16 @@ SameType(const Value& lhs, const Value& 
 /************************************************************************/
 
 namespace JS {
 JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep, const Value& prev, const Value& next);
 
 template <>
 struct GCPolicy<JS::Value>
 {
-    static Value initial() { return UndefinedValue(); }
     static void trace(JSTracer* trc, Value* v, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, v, name);
     }
     static bool isTenured(const Value& thing) {
         return !thing.isGCThing() || !IsInsideNursery(thing.toGCThing());
     }
     static bool isValid(const Value& value) {
         return !value.isGCThing() || js::gc::IsCellPointerValid(value.toGCThing());
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -3697,17 +3697,18 @@ CreateArrayPrototype(JSContext* cx, JSPr
     }
 
     /*
      * The default 'new' group of Array.prototype is required by type inference
      * to have unknown properties, to simplify handling of e.g. heterogenous
      * arrays in JSON and script literals and allows setDenseArrayElement to
      * be used without updating the indexed type set for such default arrays.
      */
-    if (!JSObject::setNewGroupUnknown(cx, &ArrayObject::class_, arrayProto))
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    if (!JSObject::setNewGroupUnknown(cx, realm, &ArrayObject::class_, arrayProto))
         return nullptr;
 
     return arrayProto;
 }
 
 static bool
 array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
 {
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -371,17 +371,17 @@ MapIteratorObject::next(Handle<MapIterat
 /* static */ JSObject*
 MapIteratorObject::createResultPair(JSContext* cx)
 {
     RootedArrayObject resultPairObj(cx, NewDenseFullyAllocatedArray(cx, 2, nullptr, TenuredObject));
     if (!resultPairObj)
         return nullptr;
 
     Rooted<TaggedProto> proto(cx, resultPairObj->taggedProto());
-    ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultPairObj->getClass(), proto);
+    ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, resultPairObj->getClass(), proto);
     if (!group)
         return nullptr;
     resultPairObj->setGroup(group);
 
     resultPairObj->setDenseInitializedLength(2);
     resultPairObj->initDenseElement(0, NullValue());
     resultPairObj->initDenseElement(1, NullValue());
 
@@ -1199,17 +1199,17 @@ SetIteratorObject::next(Handle<SetIterat
 /* static */ JSObject*
 SetIteratorObject::createResult(JSContext* cx)
 {
     RootedArrayObject resultObj(cx, NewDenseFullyAllocatedArray(cx, 1, nullptr, TenuredObject));
     if (!resultObj)
         return nullptr;
 
     Rooted<TaggedProto> proto(cx, resultObj->taggedProto());
-    ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultObj->getClass(), proto);
+    ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, resultObj->getClass(), proto);
     if (!group)
         return nullptr;
     resultObj->setGroup(group);
 
     resultObj->setDenseInitializedLength(1);
     resultObj->initDenseElement(0, NullValue());
 
     // See comments in SetIteratorObject::next.
--- a/js/src/builtin/Module.js
+++ b/js/src/builtin/Module.js
@@ -345,18 +345,18 @@ function InnerModuleInstantiation(module
         module.status === MODULE_STATUS_INSTANTIATED ||
         module.status === MODULE_STATUS_EVALUATED ||
         module.status === MODULE_STATUS_EVALUATED_ERROR)
     {
         return index;
     }
 
     // Step 3
-    assert(module.status === MODULE_STATUS_UNINSTANTIATED,
-          "Bad module status in ModuleDeclarationInstantiation");
+    if (module.status !== MODULE_STATUS_UNINSTANTIATED)
+        ThrowInternalError(JSMSG_BAD_MODULE_STATUS);
 
     // Steps 4
     ModuleSetStatus(module, MODULE_STATUS_INSTANTIATING);
 
     // Step 5-7
     UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index);
     UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index);
     index++;
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -2018,17 +2018,18 @@ CreateObjectPrototype(JSContext* cx, JSP
                "should have been able to make a fresh Object.prototype's "
                "[[Prototype]] immutable");
 
     /*
      * The default 'new' type of Object.prototype is required by type inference
      * to have unknown properties, to simplify handling of e.g. heterogenous
      * objects in JSON and script literals.
      */
-    if (!JSObject::setNewGroupUnknown(cx, &PlainObject::class_, objectProto))
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    if (!JSObject::setNewGroupUnknown(cx, realm, &PlainObject::class_, objectProto))
         return nullptr;
 
     return objectProto;
 }
 
 static bool
 FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
 {
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -43,17 +43,17 @@ js::CreateRegExpMatchResult(JSContext* c
      * Array contents:
      *  0:              matched string
      *  1..pairCount-1: paren matches
      *  input:          input string
      *  index:          start index for the match
      */
 
     /* Get the templateObject that defines the shape and type of the output object */
-    JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
+    JSObject* templateObject = cx->realm()->regExps.getOrCreateMatchResultTemplateObject(cx);
     if (!templateObject)
         return false;
 
     size_t numPairs = matches.length();
     MOZ_ASSERT(numPairs > 0);
 
     /* Step 17. */
     RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject));
@@ -1563,17 +1563,17 @@ js::RegExpPrototypeOptimizableRaw(JSCont
 {
     AutoUnsafeCallWithABI unsafe;
     AutoAssertNoPendingException aanpe(cx);
     if (!proto->isNative())
         return false;
 
     NativeObject* nproto = static_cast<NativeObject*>(proto);
 
-    Shape* shape = cx->compartment()->regExps.getOptimizableRegExpPrototypeShape();
+    Shape* shape = cx->realm()->regExps.getOptimizableRegExpPrototypeShape();
     if (shape == nproto->lastProperty())
         return true;
 
     JSFunction* flagsGetter;
     if (!GetOwnGetterPure(cx, proto, NameToId(cx->names().flags), &flagsGetter))
         return false;
 
     if (!flagsGetter)
@@ -1630,17 +1630,17 @@ js::RegExpPrototypeOptimizableRaw(JSCont
     if (!has)
         return false;
 
     if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has))
         return false;
     if (!has)
         return false;
 
-    cx->compartment()->regExps.setOptimizableRegExpPrototypeShape(nproto->lastProperty());
+    cx->realm()->regExps.setOptimizableRegExpPrototypeShape(nproto->lastProperty());
     return true;
 }
 
 bool
 js::RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp)
 {
     // This can only be called from self-hosted code.
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -1654,30 +1654,30 @@ js::RegExpInstanceOptimizable(JSContext*
 bool
 js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto)
 {
     AutoUnsafeCallWithABI unsafe;
     AutoAssertNoPendingException aanpe(cx);
 
     RegExpObject* rx = &obj->as<RegExpObject>();
 
-    Shape* shape = cx->compartment()->regExps.getOptimizableRegExpInstanceShape();
+    Shape* shape = cx->realm()->regExps.getOptimizableRegExpInstanceShape();
     if (shape == rx->lastProperty())
         return true;
 
     if (!rx->hasStaticPrototype())
         return false;
 
     if (rx->staticPrototype() != proto)
         return false;
 
     if (!RegExpObject::isInitialShape(rx))
         return false;
 
-    cx->compartment()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty());
+    cx->realm()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty());
     return true;
 }
 
 /*
  * Pattern match the script to check if it is is indexing into a particular
  * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
  * such cases, which are used by javascript packers (particularly the popular
  * Dean Edwards packer) to efficiently encode large scripts. We only handle the
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -4076,17 +4076,17 @@ BuildFlatMatchArray(JSContext* cx, Handl
                     MutableHandleValue rval)
 {
     if (match < 0) {
         rval.setNull();
         return true;
     }
 
     /* Get the templateObject that defines the shape and type of the output object */
-    JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
+    JSObject* templateObject = cx->realm()->regExps.getOrCreateMatchResultTemplateObject(cx);
     if (!templateObject)
         return false;
 
     RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, 1, templateObject));
     if (!arr)
         return false;
 
     /* Store a Value for each pair. */
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1266,40 +1266,39 @@ SetSavedStacksRNGState(JSContext* cx, un
         return false;
 
     int32_t seed;
     if (!ToInt32(cx, args[0], &seed))
         return false;
 
     // Either one or the other of the seed arguments must be non-zero;
     // make this true no matter what value 'seed' has.
-    cx->compartment()->savedStacks().setRNGState(seed, (seed + 1) * 33);
+    cx->realm()->savedStacks().setRNGState(seed, (seed + 1) * 33);
     return true;
 }
 
 static bool
 GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setNumber(cx->compartment()->savedStacks().count());
+    args.rval().setNumber(cx->realm()->savedStacks().count());
     return true;
 }
 
 static bool
 ClearSavedFrames(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    js::SavedStacks& savedStacks = cx->compartment()->savedStacks();
+    js::SavedStacks& savedStacks = cx->realm()->savedStacks();
     if (savedStacks.initialized())
         savedStacks.clear();
 
-    for (ActivationIterator iter(cx); !iter.done(); ++iter) {
+    for (ActivationIterator iter(cx); !iter.done(); ++iter)
         iter->clearLiveSavedFrameCache();
-    }
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 SaveStack(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -404,17 +404,17 @@ class WriteBarrieredBase : public Barrie
  * manually implemented when using this class. GCPtr and HeapPtr should be used
  * in all cases that do not require explicit low-level control of moving
  * behavior, e.g. for HashMap keys.
  */
 template <class T>
 class PreBarriered : public WriteBarrieredBase<T>
 {
   public:
-    PreBarriered() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    PreBarriered() : WriteBarrieredBase<T>(JS::SafelyInitialized<T>()) {}
     /*
      * Allow implicit construction for use in generic contexts, such as
      * DebuggerWeakMap::markKeys.
      */
     MOZ_IMPLICIT PreBarriered(const T& v) : WriteBarrieredBase<T>(v) {}
     explicit PreBarriered(const PreBarriered<T>& v) : WriteBarrieredBase<T>(v.value) {}
     ~PreBarriered() { this->pre(); }
 
@@ -448,43 +448,43 @@ class PreBarriered : public WriteBarrier
  * The post-barriers implemented by this class are faster than those
  * implemented by js::HeapPtr<T> or JS::Heap<T> at the cost of not
  * automatically handling deletion or movement.
  */
 template <class T>
 class GCPtr : public WriteBarrieredBase<T>
 {
   public:
-    GCPtr() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    GCPtr() : WriteBarrieredBase<T>(JS::SafelyInitialized<T>()) {}
     explicit GCPtr(const T& v) : WriteBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), v);
+        this->post(JS::SafelyInitialized<T>(), v);
     }
     explicit GCPtr(const GCPtr<T>& v) : WriteBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), v);
+        this->post(JS::SafelyInitialized<T>(), v);
     }
 #ifdef DEBUG
     ~GCPtr() {
         // No barriers are necessary as this only happens when we are sweeping
         // or when after GCManagedDeletePolicy has triggered the barriers for us
         // and cleared the pointer.
         //
         // If you get a crash here, you may need to make the containing object
         // use GCManagedDeletePolicy and use JS::DeletePolicy to destroy it.
         //
         // Note that when sweeping the wrapped pointer may already have been
         // freed by this point.
-        MOZ_ASSERT(CurrentThreadIsGCSweeping() || this->value == JS::GCPolicy<T>::initial());
+        MOZ_ASSERT(CurrentThreadIsGCSweeping() || this->value == JS::SafelyInitialized<T>());
         Poison(this, JS_FREED_HEAP_PTR_PATTERN, sizeof(*this), MemCheckKind::MakeNoAccess);
     }
 #endif
 
     void init(const T& v) {
         CheckTargetIsNotGray(v);
         this->value = v;
-        this->post(JS::GCPolicy<T>::initial(), v);
+        this->post(JS::SafelyInitialized<T>(), v);
     }
 
     DECLARE_POINTER_ASSIGN_OPS(GCPtr, T);
 
   private:
     void set(const T& v) {
         CheckTargetIsNotGray(v);
         this->pre();
@@ -524,42 +524,42 @@ class GCPtr : public WriteBarrieredBase<
  * However, since the GC itself is moving those values, it takes care of its
  * internal pointers to those pointers itself. HeapPtr is only necessary
  * when the relocation would otherwise occur without the GC's knowledge.
  */
 template <class T>
 class HeapPtr : public WriteBarrieredBase<T>
 {
   public:
-    HeapPtr() : WriteBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    HeapPtr() : WriteBarrieredBase<T>(JS::SafelyInitialized<T>()) {}
 
     // Implicitly adding barriers is a reasonable default.
     MOZ_IMPLICIT HeapPtr(const T& v) : WriteBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), this->value);
+        this->post(JS::SafelyInitialized<T>(), this->value);
     }
 
     /*
      * For HeapPtr, move semantics are equivalent to copy semantics. In
      * C++, a copy constructor taking const-ref is the way to get a single
      * function that will be used for both lvalue and rvalue copies, so we can
      * simply omit the rvalue variant.
      */
     MOZ_IMPLICIT HeapPtr(const HeapPtr<T>& v) : WriteBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), this->value);
+        this->post(JS::SafelyInitialized<T>(), this->value);
     }
 
     ~HeapPtr() {
         this->pre();
-        this->post(this->value, JS::GCPolicy<T>::initial());
+        this->post(this->value, JS::SafelyInitialized<T>());
     }
 
     void init(const T& v) {
         CheckTargetIsNotGray(v);
         this->value = v;
-        this->post(JS::GCPolicy<T>::initial(), this->value);
+        this->post(JS::SafelyInitialized<T>(), this->value);
     }
 
     DECLARE_POINTER_ASSIGN_OPS(HeapPtr, T);
 
     /* Make this friend so it can access pre() and post(). */
     template <class T1, class T2>
     friend inline void
     BarrieredSetPair(Zone* zone,
@@ -606,39 +606,39 @@ class ReadBarrieredBase : public Barrier
 template <typename T>
 class ReadBarriered : public ReadBarrieredBase<T>,
                       public WrappedPtrOperations<T, ReadBarriered<T>>
 {
   protected:
     using ReadBarrieredBase<T>::value;
 
   public:
-    ReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    ReadBarriered() : ReadBarrieredBase<T>(JS::SafelyInitialized<T>()) {}
 
     // It is okay to add barriers implicitly.
     MOZ_IMPLICIT ReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), v);
+        this->post(JS::SafelyInitialized<T>(), v);
     }
 
     // The copy constructor creates a new weak edge but the wrapped pointer does
     // not escape, so no read barrier is necessary.
     explicit ReadBarriered(const ReadBarriered& v) : ReadBarrieredBase<T>(v) {
-        this->post(JS::GCPolicy<T>::initial(), v.unbarrieredGet());
+        this->post(JS::SafelyInitialized<T>(), v.unbarrieredGet());
     }
 
     // Move retains the lifetime status of the source edge, so does not fire
     // the read barrier of the defunct edge.
     ReadBarriered(ReadBarriered&& v)
       : ReadBarrieredBase<T>(mozilla::Move(v))
     {
-        this->post(JS::GCPolicy<T>::initial(), v.value);
+        this->post(JS::SafelyInitialized<T>(), v.value);
     }
 
     ~ReadBarriered() {
-        this->post(this->value, JS::GCPolicy<T>::initial());
+        this->post(this->value, JS::SafelyInitialized<T>());
     }
 
     ReadBarriered& operator=(const ReadBarriered& v) {
         CheckTargetIsNotGray(v.value);
         T prior = this->value;
         this->value = v.value;
         this->post(prior, v.value);
         return *this;
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -2572,17 +2572,17 @@ GCRuntime::sweepZoneAfterCompacting(Zone
     zone->sweepWeakMaps();
     for (auto* cache : zone->weakCaches())
         cache->sweep();
 
     if (jit::JitZone* jitZone = zone->jitZone())
         jitZone->sweep();
 
     for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
-        r->objectGroups.sweep();
+        r->sweepObjectGroups();
         r->sweepRegExps();
         r->sweepSavedStacks();
         r->sweepVarNames();
         r->sweepGlobalObject();
         r->sweepSelfHostingScriptSource();
         r->sweepDebugEnvironments();
         r->sweepJitRealm();
         r->sweepObjectRealm();
@@ -4248,18 +4248,19 @@ GCRuntime::prepareZonesForCollection(JS:
     }
 
     // Discard JIT code more aggressively if the process is approaching its
     // executable code limit.
     bool canAllocateMoreCode = jit::CanLikelyAllocateMoreExecutableMemory();
 
     for (RealmsIter r(rt, WithAtoms); !r.done(); r.next()) {
         r->unmark();
-        r->scheduledForDestruction = false;
-        r->maybeAlive = r->shouldTraceGlobal() || !r->zone()->isGCScheduled();
+        JSCompartment* comp = JS::GetCompartmentForRealm(r);
+        comp->scheduledForDestruction = false;
+        comp->maybeAlive = r->shouldTraceGlobal() || !r->zone()->isGCScheduled();
         if (shouldPreserveJITCode(r, currentTime, reason, canAllocateMoreCode))
             r->zone()->setPreservingCode(true);
     }
 
     if (!cleanUpEverything && canAllocateMoreCode) {
         jit::JitActivationIterator activation(rt->mainContextFromOwnThread());
         if (!activation.done())
             activation->compartment()->zone()->setPreservingCode(true);
@@ -5419,18 +5420,18 @@ SweepCCWrappers(GCParallelTask* task)
     for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next())
         c->sweepCrossCompartmentWrappers();
 }
 
 static void
 SweepObjectGroups(GCParallelTask* task)
 {
     JSRuntime* runtime = task->runtime();
-    for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next())
-        c->objectGroups.sweep();
+    for (SweepGroupRealmsIter r(runtime); !r.done(); r.next())
+        r->sweepObjectGroups();
 }
 
 static void
 SweepMisc(GCParallelTask* task)
 {
     JSRuntime* runtime = task->runtime();
     for (SweepGroupRealmsIter r(runtime); !r.done(); r.next()) {
         r->sweepGlobalObject();
@@ -5527,18 +5528,18 @@ GCRuntime::sweepDebuggerOnMainThread(Fre
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_COMPARTMENTS);
 
     // Sweep debug environment information. This performs lookups in the Zone's
     // unique IDs table and so must not happen in parallel with sweeping that
     // table.
     {
         gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_MISC);
-        for (SweepGroupCompartmentsIter c(rt); !c.done(); c.next())
-            c->sweepDebugEnvironments();
+        for (SweepGroupRealmsIter r(rt); !r.done(); r.next())
+            r->sweepDebugEnvironments();
     }
 
     // Sweep breakpoints. This is done here to be with the other debug sweeping,
     // although note that it can cause JIT code to be patched.
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_BREAKPOINT);
         for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next())
             zone->sweepBreakpoints(fop);
@@ -7948,26 +7949,26 @@ js::NewCompartment(JSContext* cx, JSPrin
         const JSPrincipals* trusted = rt->trustedPrincipals();
         bool isSystem = principals && principals == trusted;
         if (!zone->init(isSystem)) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
     }
 
-    ScopedJSDeletePtr<Realm> compartment(cx->new_<Realm>(zone, options));
-    if (!compartment || !compartment->init(cx))
+    ScopedJSDeletePtr<Realm> realm(cx->new_<Realm>(zone, options));
+    if (!realm || !realm->init(cx))
         return nullptr;
 
     // Set up the principals.
-    JS_SetCompartmentPrincipals(compartment, principals);
+    JS_SetCompartmentPrincipals(JS::GetCompartmentForRealm(realm), principals);
 
     AutoLockGC lock(rt);
 
-    if (!zone->compartments().append(compartment.get())) {
+    if (!zone->compartments().append(JS::GetCompartmentForRealm(realm.get()))) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     if (zoneHolder) {
         if (!rt->gc.zones().append(zone)) {
             ReportOutOfMemory(cx);
             return nullptr;
@@ -7977,17 +7978,17 @@ js::NewCompartment(JSContext* cx, JSPrin
         if (zoneSpec == JS::SystemZone) {
             MOZ_RELEASE_ASSERT(!rt->gc.systemZone);
             rt->gc.systemZone = zone;
             zone->isSystem = true;
         }
     }
 
     zoneHolder.forget();
-    return compartment.forget();
+    return JS::GetCompartmentForRealm(realm.forget());
 }
 
 void
 gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
 {
     JSRuntime* rt = source->runtimeFromMainThread();
     rt->gc.mergeCompartments(source, target);
 
@@ -8477,22 +8478,22 @@ js::gc::CheckHashTablesAfterMovingGC(JSR
         JS::AutoCheckCannotGC nogc;
         for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next()) {
             if (ShapeTable* table = baseShape->maybeTable(nogc))
                 table->checkAfterMovingGC();
         }
     }
 
     for (RealmsIter r(rt, SkipAtoms); !r.done(); r.next()) {
-        r->objectGroups.checkTablesAfterMovingGC();
+        r->checkObjectGroupTablesAfterMovingGC();
         r->dtoaCache.checkCacheAfterMovingGC();
         JS::GetCompartmentForRealm(r)->checkWrapperMapAfterMovingGC();
         r->checkScriptMapsAfterMovingGC();
-        if (r->debugEnvs)
-            r->debugEnvs->checkHashTablesAfterMovingGC();
+        if (r->debugEnvs())
+            r->debugEnvs()->checkHashTablesAfterMovingGC();
     }
 }
 #endif
 
 JS_PUBLIC_API(void)
 JS::PrepareZoneForGC(Zone* zone)
 {
     zone->scheduleGC();
--- a/js/src/gc/NurseryAwareHashMap.h
+++ b/js/src/gc/NurseryAwareHashMap.h
@@ -18,17 +18,17 @@ namespace js {
 namespace detail {
 // This class only handles the incremental case and does not deal with nursery
 // pointers. The only users should be for NurseryAwareHashMap; it is defined
 // externally because we need a GCPolicy for its use in the contained map.
 template <typename T>
 class UnsafeBareReadBarriered : public ReadBarrieredBase<T>
 {
   public:
-    UnsafeBareReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    UnsafeBareReadBarriered() : ReadBarrieredBase<T>(JS::SafelyInitialized<T>()) {}
     MOZ_IMPLICIT UnsafeBareReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {}
     explicit UnsafeBareReadBarriered(const UnsafeBareReadBarriered& v) : ReadBarrieredBase<T>(v) {}
     UnsafeBareReadBarriered(UnsafeBareReadBarriered&& v)
       : ReadBarrieredBase<T>(mozilla::Move(v))
     {}
 
     UnsafeBareReadBarriered& operator=(const UnsafeBareReadBarriered& v) {
         this->value = v.value;
@@ -37,17 +37,17 @@ class UnsafeBareReadBarriered : public R
 
     UnsafeBareReadBarriered& operator=(const T& v) {
         this->value = v;
         return *this;
     }
 
     const T get() const {
         if (!InternalBarrierMethods<T>::isMarkable(this->value))
-            return JS::GCPolicy<T>::initial();
+            return JS::SafelyInitialized<T>();
         this->read();
         return this->value;
     }
 
     explicit operator bool() const {
         return bool(this->value);
     }
 
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -116,17 +116,16 @@ class JitCode;
     FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D)
 
 namespace js {
 
 // Define the GCPolicy for all internal pointers.
 template <typename T>
 struct InternalGCPointerPolicy {
     using Type = typename mozilla::RemovePointer<T>::Type;
-    static T initial() { return nullptr; }
     static void preBarrier(T v) {
         if (v)
             Type::writeBarrierPre(v);
     }
     static void postBarrier(T* vp, T prev, T next) {
         if (*vp)
             Type::writeBarrierPost(vp, prev, next);
     }
--- a/js/src/gc/Rooting.h
+++ b/js/src/gc/Rooting.h
@@ -84,17 +84,17 @@ typedef JS::GCVector<JSString*>     Stri
 /** Interface substitute for Rooted<T> which does not root the variable's memory. */
 template <typename T>
 class MOZ_RAII FakeRooted : public RootedBase<T, FakeRooted<T>>
 {
   public:
     using ElementType = T;
 
     template <typename CX>
-    explicit FakeRooted(CX* cx) : ptr(JS::GCPolicy<T>::initial()) {}
+    explicit FakeRooted(CX* cx) : ptr(JS::SafelyInitialized<T>()) {}
 
     template <typename CX>
     FakeRooted(CX* cx, T initial) : ptr(initial) {}
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(FakeRooted, T);
     DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr);
     DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr);
--- a/js/src/jit-test/tests/coverage/bug1214548.js
+++ b/js/src/jit-test/tests/coverage/bug1214548.js
@@ -1,10 +1,10 @@
 if (!('oomTest' in this))
     quit();
 
 oomTest(() => {
-  var g = newGlobal();
+  var g = newGlobal({sameZoneAs: this});
   g.eval("\
     function f(){}; \
     getLcovInfo(); \
   ");
 });
--- a/js/src/jit-test/tests/debug/bug-1248162.js
+++ b/js/src/jit-test/tests/debug/bug-1248162.js
@@ -5,10 +5,10 @@ if (typeof oomTest !== "function")
 
 // Adapted from randomly chosen test: js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
 for (var i = 0; i < 9; ++i) {
   var dbg = new Debugger;
   dbg.onNewGlobalObject = function() {};
 }
 // jsfunfuzz-generated
 oomTest(function() {
-  newGlobal();
+  newGlobal({sameZoneAs: this});
 })
--- a/js/src/jit-test/tests/debug/bug-1260725.js
+++ b/js/src/jit-test/tests/debug/bug-1260725.js
@@ -3,10 +3,10 @@
 if (!('oomTest' in this))
   throw new Error("out of memory");
 
 var dbg = new Debugger;
 dbg.onNewGlobalObject = function(global) {
   dbg.memory.takeCensus({});
 };
 oomTest(function() {
-  newGlobal({})
+  newGlobal({sameZoneAs: this})
 });
--- a/js/src/jit-test/tests/debug/bug1251919.js
+++ b/js/src/jit-test/tests/debug/bug1251919.js
@@ -4,10 +4,10 @@ if (!('oomTest' in this))
     throw new Error("out of memory");
 
 // jsfunfuzz-generated
 fullcompartmentchecks(true);
 // Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug-1248162.js
 var dbg = new Debugger;
 dbg.onNewGlobalObject = function() {};
 oomTest(function() {
-    newGlobal();
+    newGlobal({sameZoneAs: this});
 })
--- a/js/src/jit-test/tests/debug/bug1254123.js
+++ b/js/src/jit-test/tests/debug/bug1254123.js
@@ -7,11 +7,11 @@ evaluate(`
 function ERROR(msg) {
   throw new Error("boom");
 }
 for (var i = 0; i < 9; ++ i) {
   var dbg = new Debugger;
   dbg.onNewGlobalObject = ERROR;
 }
 oomTest(function() {
-  newGlobal();
+  newGlobal({sameZoneAs: this});
 })
 `);
--- a/js/src/jit-test/tests/debug/bug1254190.js
+++ b/js/src/jit-test/tests/debug/bug1254190.js
@@ -5,11 +5,11 @@ if (!('oomTest' in this))
 
 var g = newGlobal();
 var dbg = new Debugger(g);
 dbg.onNewScript = function (s) {
   log += dbg.findScripts({ source: s.source }).length;
 }
 log = "";
 oomTest(() => {
-    var static  = newGlobal();
+    var static  = newGlobal({sameZoneAs: this});
     g.eval("(function() {})()");
 });
--- a/js/src/jit-test/tests/debug/bug1254578.js
+++ b/js/src/jit-test/tests/debug/bug1254578.js
@@ -13,11 +13,10 @@ g.eval("(" + function() {
             debuggeeGlobal.log += s;
     };
 } + ")();");
 var dbg = new Debugger;
 dbg.onNewGlobalObject = function(global) {
     get.seen = true;
 };
 oomTest(function() {
-    newGlobal({
-    })
+    newGlobal({sameZoneAs: this})
 });
--- a/js/src/jit-test/tests/debug/bug1264961.js
+++ b/js/src/jit-test/tests/debug/bug1264961.js
@@ -3,17 +3,17 @@
 if (!('oomTest' in this))
   quit();
 
 loadFile(`
   var o = {}
   var global = this;
   var p = new Proxy(o, {
     "deleteProperty": function (await , key) {
-      var g = newGlobal();
+      var g = newGlobal({sameZoneAs: this});
       g.parent = global;
       g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
     }
   })
   for (var i=0; i<100; i++);
   assertEq(delete p.foo, true);
 `);
 function loadFile(lfVarx) {
--- a/js/src/jit-test/tests/debug/bug1272908.js
+++ b/js/src/jit-test/tests/debug/bug1272908.js
@@ -9,11 +9,11 @@ g.eval("(" + function() {
 } + ")()");
 // Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug1254123.js
 function ERROR(msg) {
     throw new Error("boom");
 }
 var dbg = new Debugger;
 dbg.onNewGlobalObject = ERROR;
 oomTest(function() {
-    newGlobal();
+    newGlobal({sameZoneAs: this});
 })
 
--- a/js/src/jit-test/tests/debug/bug1370905.js
+++ b/js/src/jit-test/tests/debug/bug1370905.js
@@ -1,20 +1,18 @@
 // |jit-test| allow-oom
 
 if (!('oomTest' in this))
     quit();
 
-var source = `
-    var global = newGlobal();
+function x() {
+    var global = newGlobal({sameZoneAs: this});
     global.eval('function f() { debugger; }');
     var debug = new Debugger(global);
     var foo;
     debug.onDebuggerStatement = function(frame) {
         foo = frame.arguments[0];
         return null;
     };
     global.eval('f(0)');
-`;
-function test() {
-    oomTest(new Function(source), false);
 }
-test();
+
+oomTest(x, false);
--- a/js/src/jit-test/tests/gc/bug-1226896.js
+++ b/js/src/jit-test/tests/gc/bug-1226896.js
@@ -1,9 +1,9 @@
 // |jit-test| --ion-pgo=on
 
 if (!('oomTest' in this))
    quit();
 
 oomTest(() => {
-    var g = newGlobal();
+    var g = newGlobal({sameZoneAs: this});
     g.eval("(function() {})()");
 });
--- a/js/src/jit-test/tests/gc/bug-1292564.js
+++ b/js/src/jit-test/tests/gc/bug-1292564.js
@@ -1,12 +1,12 @@
 // |jit-test| allow-oom
 
 if (!('oomTest' in this))
     quit();
 
 oomTest(() => {
-    let global = newGlobal();
+    let global = newGlobal({sameZoneAs: this});
     Debugger(global).onDebuggerStatement = function (frame) {
         frame.eval("f")
     }
     global.eval("debugger")
 }, false);
--- a/js/src/jit-test/tests/gc/bug-1303015.js
+++ b/js/src/jit-test/tests/gc/bug-1303015.js
@@ -1,13 +1,13 @@
 if (!('oomTest' in this))
     quit();
 
 var x = ``.split();
 oomTest(function() {
-    var lfGlobal = newGlobal();
+    var lfGlobal = newGlobal({sameZoneAs: this});
     for (lfLocal in this) {
         if (!(lfLocal in lfGlobal)) {
                 lfGlobal[lfLocal] = this[lfLocal];
         }
     }
 });
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug-1463371.js
@@ -0,0 +1,10 @@
+// |jit-test| error: Error
+
+var g = newGlobal();
+g.eval(`
+  setModuleResolveHook(function(module, specifier) { return module; });
+`);
+let m = parseModule(`
+  import {} from './foo.js';
+`);
+m.declarationInstantiation();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/bug-1463373.js
@@ -0,0 +1,11 @@
+// |jit-test| error: InternalError
+
+let m = parseModule(`
+  let c = parseModule(\`
+    import "a";
+  \`);
+  c.declarationInstantiation();
+`);
+setModuleResolveHook(function(module, specifier) { return m; });
+m.declarationInstantiation();
+m.evaluation();
--- a/js/src/jit-test/tests/wasm/globals.js
+++ b/js/src/jit-test/tests/wasm/globals.js
@@ -345,66 +345,66 @@ wasmAssert(`(module
 
 // WebAssembly.Global experiment
 
 if (typeof WebAssembly.Global === "function") {
 
     const Global = WebAssembly.Global;
 
     // These types should work:
-    assertEq(new Global({type: "i32"}) instanceof Global, true);
-    assertEq(new Global({type: "f32"}) instanceof Global, true);
-    assertEq(new Global({type: "f64"}) instanceof Global, true);
+    assertEq(new Global({value: "i32"}) instanceof Global, true);
+    assertEq(new Global({value: "f32"}) instanceof Global, true);
+    assertEq(new Global({value: "f64"}) instanceof Global, true);
 
     // These types should not work:
-    assertErrorMessage(() => new Global({type: "i64"}),   TypeError, /bad type for a WebAssembly.Global/);
-    assertErrorMessage(() => new Global({}),              TypeError, /bad type for a WebAssembly.Global/);
-    assertErrorMessage(() => new Global({type: "fnord"}), TypeError, /bad type for a WebAssembly.Global/);
-    assertErrorMessage(() => new Global(),                TypeError, /Global requires more than 0 arguments/);
+    assertErrorMessage(() => new Global({value: "i64"}),   TypeError, /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new Global({}),               TypeError, /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new Global({value: "fnord"}), TypeError, /bad type for a WebAssembly.Global/);
+    assertErrorMessage(() => new Global(),                 TypeError, /Global requires more than 0 arguments/);
 
     // Coercion of init value; ".value" accessor
-    assertEq((new Global({type: "i32"}, 3.14)).value, 3);
-    assertEq((new Global({type: "f32"}, { valueOf: () => 33.5 })).value, 33.5);
-    assertEq((new Global({type: "f64"}, "3.25")).value, 3.25);
+    assertEq((new Global({value: "i32"}, 3.14)).value, 3);
+    assertEq((new Global({value: "f32"}, { valueOf: () => 33.5 })).value, 33.5);
+    assertEq((new Global({value: "f64"}, "3.25")).value, 3.25);
 
     // Nothing special about NaN, it coerces just fine
-    assertEq((new Global({type: "i32"}, NaN)).value, 0);
+    assertEq((new Global({value: "i32"}, NaN)).value, 0);
 
     // The default init value is zero.
-    assertEq((new Global({type: "i32"})).value, 0);
-    assertEq((new Global({type: "f32"})).value, 0);
-    assertEq((new Global({type: "f64"})).value, 0);
+    assertEq((new Global({value: "i32"})).value, 0);
+    assertEq((new Global({value: "f32"})).value, 0);
+    assertEq((new Global({value: "f64"})).value, 0);
 
     {
         // "value" is enumerable
-        let x = new Global({type: "i32"});
+        let x = new Global({value: "i32"});
         let s = "";
         for ( let i in x )
             s = s + i + ",";
         assertEq(s, "value,");
     }
 
     // "value" is defined on the prototype, not on the object
     assertEq("value" in Global.prototype, true);
 
     // Can't set the value of an immutable global
-    assertErrorMessage(() => (new Global({type: "i32"})).value = 10,
+    assertErrorMessage(() => (new Global({value: "i32"})).value = 10,
                        TypeError,
                        /can't set value of immutable global/);
 
     {
         // Can set the value of a mutable global
-        let g = new Global({type: "i32", mutable: true}, 37);
+        let g = new Global({value: "i32", mutable: true}, 37);
         g.value = 10;
         assertEq(g.value, 10);
     }
 
     {
         // Misc internal conversions
-        let g = new Global({type: "i32"}, 42);
+        let g = new Global({value: "i32"}, 42);
 
         // valueOf
         assertEq(g - 5, 37);
 
         // @@toStringTag
         assertEq(g.toString(), "[object WebAssembly.Global]");
     }
 
@@ -508,26 +508,26 @@ if (typeof WebAssembly.Global === "funct
     {
         const mutErr = /imported global mutability mismatch/;
         const i64Err = /cannot pass i64 to or from JS/;
 
         let m1 = new Module(wasmTextToBinary(`(module
                                                (import "m" "g" (global i32)))`));
 
         // Mutable Global matched to immutable import
-        let gm = new Global({type: "i32", mutable: true}, 42);
+        let gm = new Global({value: "i32", mutable: true}, 42);
         assertErrorMessage(() => new Instance(m1, {m: {g: gm}}),
                            LinkError,
                            mutErr);
 
         let m2 = new Module(wasmTextToBinary(`(module
                                                (import "m" "g" (global (mut i32))))`));
 
         // Immutable Global matched to mutable import
-        let gi = new Global({type: "i32", mutable: false}, 42);
+        let gi = new Global({value: "i32", mutable: false}, 42);
         assertErrorMessage(() => new Instance(m2, {m: {g: gi}}),
                            LinkError,
                            mutErr);
 
         // Constant value is the same as immutable Global
         assertErrorMessage(() => new Instance(m2, {m: {g: 42}}),
                            LinkError,
                            mutErr);
--- a/js/src/jit-test/tests/wasm/regress/oom-eval.js
+++ b/js/src/jit-test/tests/wasm/regress/oom-eval.js
@@ -1,12 +1,12 @@
 // |jit-test| slow; allow-oom
 
 if (typeof oomTest !== 'function' || !wasmIsSupported()) {
     print('Missing oomTest or wasm support in wasm/regress/oom-eval');
     quit();
 }
 
 function foo() {
-  var g = newGlobal();
+  var g = newGlobal({sameZoneAs: this});
   g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`);
 }
 oomTest(foo);
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -78,18 +78,18 @@ BaselineFrame::trace(JSTracer* trc, cons
         // Clear dead block-scoped locals.
         while (nfixed > nlivefixed)
             unaliasedLocal(--nfixed).setUndefined();
 
         // Trace live locals.
         TraceLocals(this, trc, 0, nlivefixed);
     }
 
-    if (script->compartment()->debugEnvs)
-        script->compartment()->debugEnvs->traceLiveFrame(trc, this);
+    if (auto* debugEnvs = script->realm()->debugEnvs())
+        debugEnvs->traceLiveFrame(trc, this);
 }
 
 bool
 BaselineFrame::isNonGlobalEvalFrame() const
 {
     return isEvalFrame() && script()->enclosingScope()->as<EvalScope>().isNonGlobal();
 }
 
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -4401,17 +4401,17 @@ CallIRGenerator::tryAttachStringSplit()
 
     // Just for now: if they're both atoms, then do not optimize using
     // CacheIR and allow the legacy "ConstStringSplit" BaselineIC optimization
     // to proceed.
     if (args_[0].toString()->isAtom() && args_[1].toString()->isAtom())
         return false;
 
     // Get the object group to use for this location.
-    RootedObjectGroup group(cx_, ObjectGroupCompartment::getStringSplitStringGroup(cx_));
+    RootedObjectGroup group(cx_, ObjectGroupRealm::getStringSplitStringGroup(cx_));
     if (!group)
         return false;
 
     AutoAssertNoPendingException aanpe(cx_);
     Int32OperandId argcId(writer.setInputOperandId(0));
 
     // Ensure argc == 1.
     writer.guardSpecificInt32Immediate(argcId, 2);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1925,17 +1925,17 @@ JitRealm::generateRegExpMatcherStub(JSCo
     Register temp3 = regs.takeAny();
 
     Register maybeTemp4 = InvalidReg;
     if (!regs.empty()) {
         // There are not enough registers on x86.
         maybeTemp4 = regs.takeAny();
     }
 
-    ArrayObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
+    ArrayObject* templateObject = cx->realm()->regExps.getOrCreateMatchResultTemplateObject(cx);
     if (!templateObject)
         return nullptr;
 
     // The template object should have enough space for the maximum number of
     // pairs this stub can handle.
     MOZ_ASSERT(ObjectElements::VALUES_PER_HEADER + RegExpObject::MaxPairCount ==
                gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()));
 
@@ -2538,17 +2538,17 @@ CodeGenerator::visitRegExpPrototypeOptim
     Register temp = ToRegister(ins->temp());
 
     OutOfLineRegExpPrototypeOptimizable* ool = new(alloc()) OutOfLineRegExpPrototypeOptimizable(ins);
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfRealm()), temp);
     size_t offset = Realm::offsetOfRegExps() +
-                    RegExpCompartment::offsetOfOptimizableRegExpPrototypeShape();
+                    RegExpRealm::offsetOfOptimizableRegExpPrototypeShape();
     masm.loadPtr(Address(temp, offset), temp);
 
     masm.branchTestObjShapeUnsafe(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
@@ -2598,17 +2598,17 @@ CodeGenerator::visitRegExpInstanceOptimi
     Register temp = ToRegister(ins->temp());
 
     OutOfLineRegExpInstanceOptimizable* ool = new(alloc()) OutOfLineRegExpInstanceOptimizable(ins);
     addOutOfLineCode(ool, ins->mir());
 
     masm.loadJSContext(temp);
     masm.loadPtr(Address(temp, JSContext::offsetOfRealm()), temp);
     size_t offset = Realm::offsetOfRegExps() +
-                    RegExpCompartment::offsetOfOptimizableRegExpInstanceShape();
+                    RegExpRealm::offsetOfOptimizableRegExpInstanceShape();
     masm.loadPtr(Address(temp, offset), temp);
 
     masm.branchTestObjShapeUnsafe(Assembler::NotEqual, object, temp, ool->entry());
     masm.move32(Imm32(0x1), output);
 
     masm.bind(ool->rejoin());
 }
 
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1765,17 +1765,17 @@ IonBuilder::inlineStringSplitString(Call
         return InliningStatus_NotInlined;
 
     IonBuilder::InliningStatus resultConstStringSplit;
     MOZ_TRY_VAR(resultConstStringSplit, inlineConstantStringSplitString(callInfo));
     if (resultConstStringSplit != InliningStatus_NotInlined)
         return resultConstStringSplit;
 
     JSContext* cx = TlsContext.get();
-    ObjectGroup* group = ObjectGroupCompartment::getStringSplitStringGroup(cx);
+    ObjectGroup* group = ObjectGroupRealm::getStringSplitStringGroup(cx);
     if (!group)
         return InliningStatus_NotInlined;
     AutoSweepObjectGroup sweep(group);
     if (group->maybePreliminaryObjects(sweep))
         return InliningStatus_NotInlined;
 
     TypeSet::ObjectKey* retKey = TypeSet::ObjectKey::get(group);
     if (retKey->unknownProperties())
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1124,17 +1124,17 @@ MStringSplit::writeRecoverData(CompactBu
 RStringSplit::RStringSplit(CompactBufferReader& reader)
 {}
 
 bool
 RStringSplit::recover(JSContext* cx, SnapshotIterator& iter) const
 {
     RootedString str(cx, iter.read().toString());
     RootedString sep(cx, iter.read().toString());
-    RootedObjectGroup group(cx, ObjectGroupCompartment::getStringSplitStringGroup(cx));
+    RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
     if (!group) {
         return false;
     }
     RootedValue result(cx);
 
     JSObject* res = str_split_string(cx, group, str, sep, INT32_MAX);
     if (!res)
         return false;
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -366,16 +366,17 @@ MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG,     2
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_WASMLINKERROR, "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_IMP_MAX,        1, JSEXN_WASMLINKERROR, "imported {0} with incompatible maximum size")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_REQD,    0, JSEXN_WASMLINKERROR, "imported unshared memory but shared required")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_BANNED,  0, JSEXN_WASMLINKERROR, "imported shared memory but unshared required")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_WASMLINKERROR, "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_BAD_I64_LINK,       0, JSEXN_WASMLINKERROR, "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_NO_SHMEM_LINK,      0, JSEXN_WASMLINKERROR, "shared memory is disabled")
 MSG_DEF(JSMSG_WASM_BAD_MUT_LINK,       0, JSEXN_WASMLINKERROR, "imported global mutability mismatch")
+MSG_DEF(JSMSG_WASM_BAD_TYPE_LINK,      0, JSEXN_WASMLINKERROR, "imported global type mismatch")
 MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL,   0, JSEXN_WASMRUNTIMEERROR, "indirect call to null")
 MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG,   0, JSEXN_WASMRUNTIMEERROR, "indirect call signature mismatch")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_WASMRUNTIMEERROR, "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_WASMRUNTIMEERROR, "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_WASMRUNTIMEERROR, "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_WASMRUNTIMEERROR, "integer divide by zero")
 MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS,      0, JSEXN_WASMRUNTIMEERROR, "index out of bounds")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -677,17 +677,17 @@ JS_SetExternalStringSizeofCallback(JSCon
 JS_PUBLIC_API(JSCompartment*)
 JS::EnterRealm(JSContext* cx, JSObject* target)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
 
     Realm* oldRealm = cx->realm();
     cx->enterRealmOf(target);
-    return JS::GetRealmForCompartment(oldRealm);
+    return JS::GetCompartmentForRealm(oldRealm);
 }
 
 JS_PUBLIC_API(void)
 JS::LeaveRealm(JSContext* cx, JS::Realm* oldRealm)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     cx->leaveRealm(oldRealm);
@@ -7791,40 +7791,39 @@ JS::FirstSubsumedFrame::FirstSubsumedFra
 { }
 
 JS_PUBLIC_API(bool)
 JS::CaptureCurrentStack(JSContext* cx, JS::MutableHandleObject stackp,
                         JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
-
-    JSCompartment* compartment = cx->compartment();
+    MOZ_RELEASE_ASSERT(cx->realm());
+
+    Realm* realm = cx->realm();
     Rooted<SavedFrame*> frame(cx);
-    if (!compartment->savedStacks().saveCurrentStack(cx, &frame, mozilla::Move(capture)))
+    if (!realm->savedStacks().saveCurrentStack(cx, &frame, mozilla::Move(capture)))
         return false;
     stackp.set(frame.get());
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS::CopyAsyncStack(JSContext* cx, JS::HandleObject asyncStack,
                    JS::HandleString asyncCause, JS::MutableHandleObject stackp,
                    const Maybe<size_t>& maxFrameCount)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     js::AssertObjectIsSavedFrameOrWrapper(cx, asyncStack);
-    JSCompartment* compartment = cx->compartment();
+    Realm* realm = cx->realm();
     Rooted<SavedFrame*> frame(cx);
-    if (!compartment->savedStacks().copyAsyncStack(cx, asyncStack, asyncCause,
-                                                   &frame, maxFrameCount))
+    if (!realm->savedStacks().copyAsyncStack(cx, asyncStack, asyncCause, &frame, maxFrameCount))
         return false;
     stackp.set(frame.get());
     return true;
 }
 
 JS_PUBLIC_API(Zone*)
 JS::GetObjectZone(JSObject* obj)
 {
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -442,17 +442,17 @@ js::TransparentObjectWrapper(JSContext* 
 }
 
 ErrorCopier::~ErrorCopier()
 {
     JSContext* cx = ar->context();
 
     // The provenance of Debugger.DebuggeeWouldRun is the topmost locking
     // debugger compartment; it should not be copied around.
-    if (ar->origin() != cx->compartment() &&
+    if (JS::GetCompartmentForRealm(ar->origin()) != cx->compartment() &&
         cx->isExceptionPending() &&
         !cx->isThrowingDebuggeeWouldRun())
     {
         RootedValue exc(cx);
         if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is<ErrorObject>()) {
             cx->clearPendingException();
             ar.reset();
             Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -167,16 +167,25 @@ PR_UnloadLibrary(PRLibrary* dll)
 
 enum JSShellExitCode {
     EXITCODE_RUNTIME_ERROR      = 3,
     EXITCODE_FILE_NOT_FOUND     = 4,
     EXITCODE_OUT_OF_MEMORY      = 5,
     EXITCODE_TIMEOUT            = 6
 };
 
+// Define use of application-specific slots on the shell's global object.
+enum GlobalAppSlot
+{
+    GlobalAppSlotModuleResolveHook,
+    GlobalAppSlotCount
+};
+static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
+              "Too many applications slots defined for shell global");
+
 /*
  * Note: This limit should match the stack limit set by the browser in
  *       js/xpconnect/src/XPCJSContext.cpp
  */
 #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
 static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
 #else
 static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
@@ -605,18 +614,17 @@ ShellContext::ShellContext(JSContext* cx
     lastWarning(cx, NullValue()),
     promiseRejectionTrackerCallback(cx, NullValue()),
     watchdogLock(mutexid::ShellContextWatchdog),
     exitCode(0),
     quitting(false),
     readLineBufPos(0),
     errFilePtr(nullptr),
     outFilePtr(nullptr),
-    offThreadMonitor(mutexid::ShellOffThreadState),
-    moduleResolveHook(cx)
+    offThreadMonitor(mutexid::ShellOffThreadState)
 {}
 
 ShellContext::~ShellContext()
 {
     MOZ_ASSERT(offThreadJobs.empty());
 }
 
 ShellContext*
@@ -4274,38 +4282,40 @@ SetModuleResolveHook(JSContext* cx, unsi
     }
 
     if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
         const char* typeName = InformalValueTypeName(args[0]);
         JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
         return false;
     }
 
-    ShellContext* sc = GetShellContext(cx);
-    sc->moduleResolveHook = &args[0].toObject().as<JSFunction>();
+    Handle<GlobalObject*> global = cx->global();
+    global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);
 
     args.rval().setUndefined();
     return true;
 }
 
 static JSObject*
 CallModuleResolveHook(JSContext* cx, HandleObject module, HandleString specifier)
 {
-    ShellContext* sc = GetShellContext(cx);
-    if (!sc->moduleResolveHook) {
+    Handle<GlobalObject*> global = cx->global();
+    RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleResolveHook));
+    if (hookValue.isUndefined()) {
         JS_ReportErrorASCII(cx, "Module resolve hook not set");
         return nullptr;
     }
+    MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
 
     JS::AutoValueArray<2> args(cx);
     args[0].setObject(*module);
     args[1].setString(specifier);
 
     RootedValue result(cx);
-    if (!JS_CallFunction(cx, nullptr, sc->moduleResolveHook, args, &result))
+    if (!JS_CallFunctionValue(cx, nullptr, hookValue, args, &result))
         return nullptr;
 
     if (!result.isObject() || !result.toObject().is<ModuleObject>()) {
          JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
          return nullptr;
     }
 
     return &result.toObject();
--- a/js/src/shell/jsshell.h
+++ b/js/src/shell/jsshell.h
@@ -175,18 +175,16 @@ struct ShellContext
     UniquePtr<ProfilingStack> geckoProfilingStack;
 
     JS::UniqueChars moduleLoadPath;
     UniquePtr<MarkBitObservers> markObservers;
 
     // Off-thread parse state.
     js::Monitor offThreadMonitor;
     Vector<OffThreadJob*, 0, SystemAllocPolicy> offThreadJobs;
-
-    JS::PersistentRootedFunction moduleResolveHook;
 };
 
 extern ShellContext*
 GetShellContext(JSContext* cx);
 
 } /* namespace shell */
 } /* namespace js */
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2737,19 +2737,18 @@ Debugger::ensureExecutionObservabilityOf
     MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
     if (frame.isDebuggee())
         return true;
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
-Debugger::ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp)
-{
-    Realm* realm = JS::GetRealmForCompartment(comp);
+Debugger::ensureExecutionObservabilityOfRealm(JSContext* cx, Realm* realm)
+{
     if (realm->debuggerObservesAllExecution())
         return true;
     ExecutionObservableRealms obs(cx);
     if (!obs.init() || !obs.add(realm))
         return false;
     realm->updateDebuggerObservesAllExecution();
     return updateExecutionObservability(cx, obs, Observing);
 }
@@ -3741,17 +3740,17 @@ Debugger::addDebuggee(JSContext* cx, uns
 /* static */ bool
 Debugger::addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
     for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
         for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
             if (r == dbg->object->realm() || r->creationOptions().invisibleToDebugger())
                 continue;
-            r->scheduledForDestruction = false;
+            JS::GetCompartmentForRealm(r)->scheduledForDestruction = false;
             GlobalObject* global = r->maybeGlobal();
             if (global) {
                 Rooted<GlobalObject*> rg(cx, global);
                 if (!dbg->addDebuggeeGlobal(cx, rg))
                     return false;
             }
         }
     }
@@ -4073,17 +4072,17 @@ Debugger::addDebuggeeGlobal(JSContext* c
     });
 
     // (6)
     AutoRestoreRealmDebugMode debugModeGuard(debuggeeRealm);
     debuggeeRealm->setIsDebuggee();
     debuggeeRealm->updateDebuggerObservesAsmJS();
     debuggeeRealm->updateDebuggerObservesBinarySource();
     debuggeeRealm->updateDebuggerObservesCoverage();
-    if (observesAllExecution() && !ensureExecutionObservabilityOfCompartment(cx, debuggeeRealm))
+    if (observesAllExecution() && !ensureExecutionObservabilityOfRealm(cx, debuggeeRealm))
         return false;
 
     globalDebuggersGuard.release();
     debuggeesGuard.release();
     zoneDebuggersGuard.release();
     debuggeeZonesGuard.release();
     allocationsTrackingGuard.release();
     debugModeGuard.release();
@@ -4958,17 +4957,17 @@ Debugger::findAllGlobals(JSContext* cx, 
         // Accumulate the list of globals before wrapping them, because
         // wrapping can GC and collect realms from under us, while iterating.
         JS::AutoCheckCannotGC nogc;
 
         for (RealmsIter r(cx->runtime(), SkipAtoms); !r.done(); r.next()) {
             if (r->creationOptions().invisibleToDebugger())
                 continue;
 
-            r->scheduledForDestruction = false;
+            JS::GetCompartmentForRealm(r)->scheduledForDestruction = false;
 
             GlobalObject* global = r->maybeGlobal();
 
             if (cx->runtime()->isSelfHostingGlobal(global))
                 continue;
 
             if (global) {
                 /*
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -749,18 +749,18 @@ class Debugger : private mozilla::Linked
     // execution.
     IsObserving observesCoverage() const;
 
     IsObserving observesBinarySource() const;
 
   private:
     static MOZ_MUST_USE bool ensureExecutionObservabilityOfFrame(JSContext* cx,
                                                                  AbstractFramePtr frame);
-    static MOZ_MUST_USE bool ensureExecutionObservabilityOfCompartment(JSContext* cx,
-                                                                       JSCompartment* comp);
+    static MOZ_MUST_USE bool ensureExecutionObservabilityOfRealm(JSContext* cx,
+                                                                 JS::Realm* realm);
 
     static bool hookObservesAllExecution(Hook which);
 
     MOZ_MUST_USE bool updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing);
     MOZ_MUST_USE bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing);
     void updateObservesAsmJSOnDebuggees(IsObserving observing);
     void updateObservesBinarySourceDebuggees(IsObserving observing);
 
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -303,20 +303,20 @@ DebuggerMemory::setAllocationSamplingPro
         return false;
     }
 
     Debugger* dbg = memory->getDebugger();
     if (dbg->allocationSamplingProbability != probability) {
         dbg->allocationSamplingProbability = probability;
 
         // If this is a change any debuggees would observe, have all debuggee
-        // compartments recompute their sampling probabilities.
+        // realms recompute their sampling probabilities.
         if (dbg->enabled && dbg->trackingAllocationSites) {
             for (auto r = dbg->debuggees.all(); !r.empty(); r.popFront())
-                r.front()->compartment()->chooseAllocationSamplingProbability();
+                r.front()->realm()->chooseAllocationSamplingProbability();
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -140,17 +140,19 @@ CallObject::create(JSContext* cx, Handle
 
 CallObject*
 CallObject::createSingleton(JSContext* cx, HandleShape shape)
 {
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
-    RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr)));
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, realm, &class_,
+                                                                TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     JSObject* obj;
     JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::TenuredHeap, shape, group));
 
     MOZ_ASSERT(obj->isSingleton(),
                "group created inline above must be a singleton");
@@ -2309,17 +2311,17 @@ JSObject::is<js::DebugEnvironmentProxy>(
 }
 
 const char DebugEnvironmentProxyHandler::family = 0;
 const DebugEnvironmentProxyHandler DebugEnvironmentProxyHandler::singleton;
 
 /* static */ DebugEnvironmentProxy*
 DebugEnvironmentProxy::create(JSContext* cx, EnvironmentObject& env, HandleObject enclosing)
 {
-    MOZ_ASSERT(env.compartment() == cx->compartment());
+    MOZ_ASSERT(env.realm() == cx->realm());
     MOZ_ASSERT(!enclosing->is<EnvironmentObject>());
 
     RootedValue priv(cx, ObjectValue(env));
     JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton, priv,
                                    nullptr /* proto */);
     if (!obj)
         return nullptr;
 
@@ -2509,95 +2511,95 @@ DebugEnvironments::checkHashTablesAfterM
  */
 static bool
 CanUseDebugEnvironmentMaps(JSContext* cx)
 {
     return cx->realm()->isDebuggee();
 }
 
 DebugEnvironments*
-DebugEnvironments::ensureCompartmentData(JSContext* cx)
+DebugEnvironments::ensureRealmData(JSContext* cx)
 {
-    JSCompartment* c = cx->compartment();
-    if (c->debugEnvs)
-        return c->debugEnvs.get();
+    Realm* realm = cx->realm();
+    if (auto* debugEnvs = realm->debugEnvs())
+        return debugEnvs;
 
     auto debugEnvs = cx->make_unique<DebugEnvironments>(cx, cx->zone());
     if (!debugEnvs || !debugEnvs->init()) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    c->debugEnvs = Move(debugEnvs);
-    return c->debugEnvs.get();
+    realm->debugEnvsRef() = Move(debugEnvs);
+    return realm->debugEnvs();
 }
 
 /* static */ DebugEnvironmentProxy*
 DebugEnvironments::hasDebugEnvironment(JSContext* cx, EnvironmentObject& env)
 {
-    DebugEnvironments* envs = env.compartment()->debugEnvs.get();
+    DebugEnvironments* envs = env.realm()->debugEnvs();
     if (!envs)
         return nullptr;
 
     if (JSObject* obj = envs->proxiedEnvs.lookup(&env)) {
         MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
         return &obj->as<DebugEnvironmentProxy>();
     }
 
     return nullptr;
 }
 
 /* static */ bool
 DebugEnvironments::addDebugEnvironment(JSContext* cx, Handle<EnvironmentObject*> env,
                                        Handle<DebugEnvironmentProxy*> debugEnv)
 {
-    MOZ_ASSERT(cx->compartment() == env->compartment());
-    MOZ_ASSERT(cx->compartment() == debugEnv->compartment());
+    MOZ_ASSERT(cx->realm() == env->realm());
+    MOZ_ASSERT(cx->realm() == debugEnv->realm());
 
     if (!CanUseDebugEnvironmentMaps(cx))
         return true;
 
-    DebugEnvironments* envs = ensureCompartmentData(cx);
+    DebugEnvironments* envs = ensureRealmData(cx);
     if (!envs)
         return false;
 
     return envs->proxiedEnvs.add(cx, env, debugEnv);
 }
 
 /* static */ DebugEnvironmentProxy*
 DebugEnvironments::hasDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
 {
     MOZ_ASSERT(!ei.hasSyntacticEnvironment());
 
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return nullptr;
 
     if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
         MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
         return p->value();
     }
     return nullptr;
 }
 
 /* static */ bool
 DebugEnvironments::addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei,
                                        Handle<DebugEnvironmentProxy*> debugEnv)
 {
     MOZ_ASSERT(!ei.hasSyntacticEnvironment());
-    MOZ_ASSERT(cx->compartment() == debugEnv->compartment());
+    MOZ_ASSERT(cx->realm() == debugEnv->realm());
     // Generators should always have environments.
     MOZ_ASSERT_IF(ei.scope().is<FunctionScope>(),
                   !ei.scope().as<FunctionScope>().canonicalFunction()->isGenerator() &&
                   !ei.scope().as<FunctionScope>().canonicalFunction()->isAsync());
 
     if (!CanUseDebugEnvironmentMaps(cx))
         return true;
 
-    DebugEnvironments* envs = ensureCompartmentData(cx);
+    DebugEnvironments* envs = ensureRealmData(cx);
     if (!envs)
         return false;
 
     MissingEnvironmentKey key(ei);
     MOZ_ASSERT(!envs->missingEnvs.has(key));
     if (!envs->missingEnvs.put(key, ReadBarriered<DebugEnvironmentProxy*>(debugEnv))) {
         ReportOutOfMemory(cx);
         return false;
@@ -2712,17 +2714,17 @@ DebugEnvironments::takeFrameSnapshot(JSC
     debugEnv->initSnapshot(*snapshot);
 }
 
 /* static */ void
 DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame)
 {
     assertSameCompartment(cx, frame);
 
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return;
 
     Rooted<DebugEnvironmentProxy*> debugEnv(cx, nullptr);
 
     FunctionScope* funScope = &frame.script()->bodyScope()->as<FunctionScope>();
     if (funScope->hasEnvironment()) {
         MOZ_ASSERT(frame.callee()->needsCallObject());
@@ -2754,29 +2756,29 @@ DebugEnvironments::onPopCall(JSContext* 
         DebugEnvironments::takeFrameSnapshot(cx, debugEnv, frame);
 }
 
 void
 DebugEnvironments::onPopLexical(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
 {
     assertSameCompartment(cx, frame);
 
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return;
 
     EnvironmentIter ei(cx, frame, pc);
     onPopLexical(cx, ei);
 }
 
 template <typename Environment, typename Scope>
 void
 DebugEnvironments::onPopGeneric(JSContext* cx, const EnvironmentIter& ei)
 {
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return;
 
     MOZ_ASSERT(ei.withinInitialFrame());
     MOZ_ASSERT(ei.scope().is<Scope>());
 
     Rooted<Environment*> env(cx);
     if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
@@ -2802,17 +2804,17 @@ DebugEnvironments::onPopLexical(JSContex
     onPopGeneric<LexicalEnvironmentObject, LexicalScope>(cx, ei);
 }
 
 void
 DebugEnvironments::onPopVar(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
 {
     assertSameCompartment(cx, frame);
 
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return;
 
     EnvironmentIter ei(cx, frame, pc);
     onPopVar(cx, ei);
 }
 
 void
@@ -2822,24 +2824,25 @@ DebugEnvironments::onPopVar(JSContext* c
         onPopGeneric<VarEnvironmentObject, EvalScope>(cx, ei);
     else
         onPopGeneric<VarEnvironmentObject, VarScope>(cx, ei);
 }
 
 void
 DebugEnvironments::onPopWith(AbstractFramePtr frame)
 {
-    if (DebugEnvironments* envs = frame.compartment()->debugEnvs.get())
+    Realm* realm = JS::GetRealmForCompartment(frame.compartment());
+    if (DebugEnvironments* envs = realm->debugEnvs())
         envs->liveEnvs.remove(&frame.environmentChain()->as<WithEnvironmentObject>());
 }
 
 void
-DebugEnvironments::onCompartmentUnsetIsDebuggee(JSCompartment* c)
+DebugEnvironments::onRealmUnsetIsDebuggee(Realm* realm)
 {
-    if (DebugEnvironments* envs = c->debugEnvs.get()) {
+    if (DebugEnvironments* envs = realm->debugEnvs()) {
         envs->proxiedEnvs.clear();
         envs->missingEnvs.clear();
         envs->liveEnvs.clear();
     }
 }
 
 bool
 DebugEnvironments::updateLiveEnvironments(JSContext* cx)
@@ -2859,17 +2862,17 @@ DebugEnvironments::updateLiveEnvironment
      * fp, simply popping fp effectively clears the flag for us, at exactly
      * the time when execution resumes fp->prev().
      */
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (!i.hasUsableAbstractFramePtr())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
-        if (frame.environmentChain()->compartment() != cx->compartment())
+        if (frame.environmentChain()->realm() != cx->realm())
             continue;
 
         if (frame.isFunctionFrame()) {
             if (frame.callee()->isGenerator() || frame.callee()->isAsync())
                 continue;
         }
 
         if (!frame.isDebuggee())
@@ -2877,18 +2880,18 @@ DebugEnvironments::updateLiveEnvironment
 
         RootedObject env(cx);
         RootedScope scope(cx);
         if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope))
             return false;
 
         for (EnvironmentIter ei(cx, env, scope, frame); ei.withinInitialFrame(); ei++) {
             if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
-                MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
-                DebugEnvironments* envs = ensureCompartmentData(cx);
+                MOZ_ASSERT(ei.environment().realm() == cx->realm());
+                DebugEnvironments* envs = ensureRealmData(cx);
                 if (!envs)
                     return false;
                 if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
                     return false;
             }
         }
 
         if (frame.prevUpToDate())
@@ -2898,17 +2901,17 @@ DebugEnvironments::updateLiveEnvironment
     }
 
     return true;
 }
 
 LiveEnvironmentVal*
 DebugEnvironments::hasLiveEnvironment(EnvironmentObject& env)
 {
-    DebugEnvironments* envs = env.compartment()->debugEnvs.get();
+    DebugEnvironments* envs = env.realm()->debugEnvs();
     if (!envs)
         return nullptr;
 
     if (LiveEnvironmentMap::Ptr p = envs->liveEnvs.lookup(&env))
         return &p->value();
 
     return nullptr;
 }
@@ -2927,27 +2930,27 @@ DebugEnvironments::unsetPrevUpToDateUnti
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (!i.hasUsableAbstractFramePtr())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame == until)
             return;
 
-        if (frame.environmentChain()->compartment() != cx->compartment())
+        if (frame.environmentChain()->realm() != cx->realm())
             continue;
 
         frame.unsetPrevUpToDate();
     }
 }
 
 /* static */ void
 DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
 {
-    DebugEnvironments* envs = cx->compartment()->debugEnvs.get();
+    DebugEnvironments* envs = cx->realm()->debugEnvs();
     if (!envs)
         return;
 
     for (MissingEnvironmentMap::Enum e(envs->missingEnvs); !e.empty(); e.popFront()) {
         MissingEnvironmentKey key = e.front().key();
         if (key.frame() == from) {
             key.updateFrame(to);
             e.rekeyFront(key);
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -947,17 +947,17 @@ class DebugEnvironmentProxy : public Pro
     bool isFunctionEnvironmentWithThis();
 
     // Does this debug environment not have a real counterpart or was never
     // live (and thus does not have a synthesized EnvironmentObject or a
     // snapshot)?
     bool isOptimizedOut() const;
 };
 
-/* Maintains per-compartment debug environment bookkeeping information. */
+/* Maintains per-realm debug environment bookkeeping information. */
 class DebugEnvironments
 {
     Zone* zone_;
 
     /* The map from (non-debug) environments to debug environments. */
     ObjectWeakMap proxiedEnvs;
 
     /*
@@ -988,17 +988,17 @@ class DebugEnvironments
     DebugEnvironments(JSContext* cx, Zone* zone);
     ~DebugEnvironments();
 
     Zone* zone() const { return zone_; }
 
   private:
     bool init();
 
-    static DebugEnvironments* ensureCompartmentData(JSContext* cx);
+    static DebugEnvironments* ensureRealmData(JSContext* cx);
 
     template <typename Environment, typename Scope>
     static void onPopGeneric(JSContext* cx, const EnvironmentIter& ei);
 
   public:
     void trace(JSTracer* trc);
     void sweep();
     void finish();
@@ -1039,17 +1039,17 @@ class DebugEnvironments
     // In debug-mode, these must be called whenever exiting a scope that might
     // have stack-allocated locals.
     static void onPopCall(JSContext* cx, AbstractFramePtr frame);
     static void onPopVar(JSContext* cx, const EnvironmentIter& ei);
     static void onPopVar(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc);
     static void onPopLexical(JSContext* cx, const EnvironmentIter& ei);
     static void onPopLexical(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc);
     static void onPopWith(AbstractFramePtr frame);
-    static void onCompartmentUnsetIsDebuggee(JSCompartment* c);
+    static void onRealmUnsetIsDebuggee(Realm* realm);
 };
 
 }  /* namespace js */
 
 template <>
 inline bool
 JSObject::is<js::EnvironmentObject>() const
 {
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -341,20 +341,20 @@ GlobalObject::resolveOffThreadConstructo
         return false;
 
     if (key == JSProto_Object &&
         !JSObject::setFlags(cx, placeholder, BaseShape::IMMUTABLE_PROTOTYPE))
     {
         return false;
     }
 
-    if ((key == JSProto_Object || key == JSProto_Function || key == JSProto_Array) &&
-        !JSObject::setNewGroupUnknown(cx, placeholder->getClass(), placeholder))
-    {
-        return false;
+    if (key == JSProto_Object || key == JSProto_Function || key == JSProto_Array) {
+        ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+        if (!JSObject::setNewGroupUnknown(cx, realm, placeholder->getClass(), placeholder))
+            return false;
     }
 
     global->setPrototype(key, ObjectValue(*placeholder));
     global->setConstructor(key, MagicValue(JS_OFF_THREAD_CONSTRUCTOR));
     return true;
 }
 
 /* static */ JSObject*
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1655,21 +1655,21 @@ class ReservedRooted : public RootedBase
     Rooted<T>* savedRoot;
 
   public:
     ReservedRooted(Rooted<T>* root, const T& ptr) : savedRoot(root) {
         *root = ptr;
     }
 
     explicit ReservedRooted(Rooted<T>* root) : savedRoot(root) {
-        *root = JS::GCPolicy<T>::initial();
+        *root = JS::SafelyInitialized<T>();
     }
 
     ~ReservedRooted() {
-        *savedRoot = JS::GCPolicy<T>::initial();
+        *savedRoot = JS::SafelyInitialized<T>();
     }
 
     void set(const T& p) const { *savedRoot = p; }
     operator Handle<T>() { return *savedRoot; }
     operator Rooted<T>&() { return *savedRoot; }
     MutableHandle<T> operator&() { return &*savedRoot; }
 
     DECLARE_NONPOINTER_ACCESSOR_METHODS(savedRoot->get())
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -966,18 +966,18 @@ Realm::getOrCreateIterResultTemplateObje
 
     // Create template plain object
     RootedNativeObject templateObject(cx, NewBuiltinClassInstance<PlainObject>(cx, TenuredObject));
     if (!templateObject)
         return iterResultTemplate_; // = nullptr
 
     // Create a new group for the template.
     Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
-    RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(),
-                                                                  proto));
+    RootedObjectGroup group(cx, ObjectGroupRealm::makeGroup(cx, templateObject->getClass(),
+                                                            proto));
     if (!group)
         return iterResultTemplate_; // = nullptr
     templateObject->setGroup(group);
 
     // Set dummy `value` property
     if (!NativeDefineDataProperty(cx, templateObject, cx->names().value, UndefinedHandleValue,
                                   JSPROP_ENUMERATE))
     {
--- a/js/src/vm/JSCompartment.cpp
+++ b/js/src/vm/JSCompartment.cpp
@@ -38,20 +38,17 @@
 using namespace js;
 using namespace js::gc;
 using namespace js::jit;
 
 using mozilla::PodArrayZero;
 
 JSCompartment::JSCompartment(Zone* zone)
   : zone_(zone),
-    runtime_(zone->runtimeFromAnyThread()),
-    data(nullptr),
-    regExps(),
-    gcIncomingGrayPointers(nullptr)
+    runtime_(zone->runtimeFromAnyThread())
 {
     runtime_->numCompartments++;
 }
 
 ObjectRealm::ObjectRealm(JS::Zone* zone)
   : innerViews(zone)
 {}
 
@@ -75,27 +72,27 @@ Realm::Realm(JS::Zone* zone, const JS::R
 }
 
 Realm::~Realm()
 {
     // Write the code coverage information in a file.
     JSRuntime* rt = runtimeFromMainThread();
     if (rt->lcovOutput().isEnabled())
         rt->lcovOutput().writeLCovResult(lcovOutput);
+
+#ifdef DEBUG
+    // Avoid assertion destroying the unboxed layouts list if the embedding
+    // leaked GC things.
+    if (!runtime_->gc.shutdownCollectedEverything())
+        objectGroups_.unboxedLayouts.clear();
+#endif
 }
 
 JSCompartment::~JSCompartment()
 {
-#ifdef DEBUG
-    // Avoid assertion destroying the unboxed layouts list if the embedding
-    // leaked GC things.
-    if (!runtime_->gc.shutdownCollectedEverything())
-        unboxedLayouts.clear();
-#endif
-
     runtime_->numCompartments--;
 }
 
 bool
 JSCompartment::init(JSContext* maybecx)
 {
     if (!crossCompartmentWrappers.init(0)) {
         if (maybecx)
@@ -705,18 +702,18 @@ Realm::traceRoots(JSTracer* trc, js::gc:
     }
 
     // Nothing below here needs to be treated as a root if we aren't marking
     // this zone for a collection.
     if (traceOrMark == js::gc::GCRuntime::MarkRuntime && !zone()->isCollectingFromAnyThread())
         return;
 
     /* Mark debug scopes, if present */
-    if (debugEnvs)
-        debugEnvs->trace(trc);
+    if (debugEnvs_)
+        debugEnvs_->trace(trc);
 
     objects_.trace(trc);
 
     // If code coverage is only enabled with the Debugger or the LCovOutput,
     // then the following comment holds.
     //
     // The scriptCountsMap maps JSScript weak-pointers to ScriptCounts
     // structures. It uses a HashMap instead of a WeakMap, so that we can keep
@@ -752,18 +749,18 @@ ObjectRealm::finishRoots()
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->clear();
 }
 
 void
 Realm::finishRoots()
 {
-    if (debugEnvs)
-        debugEnvs->finish();
+    if (debugEnvs_)
+        debugEnvs_->finish();
 
     objects_.finishRoots();
 
     clearScriptCounts();
     clearScriptNames();
 }
 
 void
@@ -787,17 +784,17 @@ JSCompartment::sweepAfterMinorGC(JSTrace
 {
     crossCompartmentWrappers.sweepAfterMinorGC(trc);
 
     Realm* realm = JS::GetRealmForCompartment(this);
     realm->sweepAfterMinorGC();
 }
 
 void
-JSCompartment::sweepSavedStacks()
+Realm::sweepSavedStacks()
 {
     savedStacks_.sweep();
 }
 
 void
 Realm::sweepGlobalObject()
 {
     if (global_ && IsAboutToBeFinalized(&global_))
@@ -817,31 +814,31 @@ Realm::sweepSelfHostingScriptSource()
 void
 Realm::sweepJitRealm()
 {
     if (jitRealm_)
         jitRealm_->sweep(this);
 }
 
 void
-JSCompartment::sweepRegExps()
+Realm::sweepRegExps()
 {
     /*
      * JIT code increments activeWarmUpCounter for any RegExpShared used by jit
      * code for the lifetime of the JIT script. Thus, we must perform
      * sweeping after clearing jit code.
      */
     regExps.sweep();
 }
 
 void
-JSCompartment::sweepDebugEnvironments()
+Realm::sweepDebugEnvironments()
 {
-    if (debugEnvs)
-        debugEnvs->sweep();
+    if (debugEnvs_)
+        debugEnvs_->sweep();
 }
 
 void
 ObjectRealm::sweepNativeIterators()
 {
     /* Sweep list of native iterators. */
     NativeIterator* ni = enumerators->next();
     while (ni != enumerators) {
@@ -928,26 +925,31 @@ JSCompartment::fixupCrossCompartmentWrap
         comp->sweepCrossCompartmentWrappers();
         // Trace the wrappers in the map to update their cross-compartment edges
         // to wrapped values in other compartments that may have been moved.
         comp->traceOutgoingCrossCompartmentWrappers(trc);
     }
 }
 
 void
+Realm::fixupAfterMovingGC()
+{
+    purge();
+    fixupGlobal();
+    objectGroups_.fixupTablesAfterMovingGC();
+    fixupScriptMapsAfterMovingGC();
+}
+
+void
 JSCompartment::fixupAfterMovingGC()
 {
     MOZ_ASSERT(zone()->isGCCompacting());
 
     Realm* realm = JS::GetRealmForCompartment(this);
-
-    realm->purge();
-    realm->fixupGlobal();
-    objectGroups.fixupTablesAfterMovingGC();
-    realm->fixupScriptMapsAfterMovingGC();
+    realm->fixupAfterMovingGC();
 
     // Sweep the wrapper map to update values (wrapper objects) in this
     // compartment that may have been moved.
     sweepCrossCompartmentWrappers();
 }
 
 void
 Realm::fixupGlobal()
@@ -1030,34 +1032,34 @@ Realm::checkScriptMapsAfterMovingGC()
 }
 #endif
 
 void
 Realm::purge()
 {
     dtoaCache.purge();
     newProxyCache.purge();
-    objectGroups.purge();
+    objectGroups_.purge();
     objects_.iteratorCache.clearAndShrink();
     arraySpeciesLookup.purge();
 }
 
 void
 Realm::clearTables()
 {
     global_.set(nullptr);
 
     // No scripts should have run in this realm. This is used when merging
     // a realm that has been used off thread into another realm and zone.
     JS::GetCompartmentForRealm(this)->assertNoCrossCompartmentWrappers();
     MOZ_ASSERT(!jitRealm_);
-    MOZ_ASSERT(!debugEnvs);
+    MOZ_ASSERT(!debugEnvs_);
     MOZ_ASSERT(objects_.enumerators->next() == objects_.enumerators);
 
-    objectGroups.clearTables();
+    objectGroups_.clearTables();
     if (savedStacks_.initialized())
         savedStacks_.clear();
     if (varNames_.initialized())
         varNames_.clear();
 }
 
 void
 Realm::setAllocationMetadataBuilder(const js::AllocationMetadataBuilder* builder)
@@ -1081,17 +1083,17 @@ Realm::forgetAllocationMetadataBuilder()
 
     allocationMetadataBuilder_ = nullptr;
 }
 
 void
 Realm::setNewObjectMetadata(JSContext* cx, HandleObject obj)
 {
     MOZ_ASSERT(obj->realm() == this);
-    assertSameCompartment(cx, this, obj);
+    assertSameCompartment(cx, JS::GetCompartmentForRealm(this), obj);
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (JSObject* metadata = allocationMetadataBuilder_->build(cx, obj, oomUnsafe)) {
         MOZ_ASSERT(metadata->realm() == obj->realm());
         assertSameCompartment(cx, metadata);
 
         if (!objects_.objectMetadataTable) {
             auto table = cx->make_unique<ObjectWeakMap>(cx);
@@ -1234,17 +1236,17 @@ Realm::updateDebuggerObservesFlag(unsign
     debugModeBits_ &= ~flag;
 }
 
 void
 Realm::unsetIsDebuggee()
 {
     if (isDebuggee()) {
         debugModeBits_ &= ~DebuggerObservesMask;
-        DebugEnvironments::onCompartmentUnsetIsDebuggee(this);
+        DebugEnvironments::onRealmUnsetIsDebuggee(this);
     }
 }
 
 void
 Realm::updateDebuggerObservesCoverage()
 {
     bool previousState = debuggerObservesCoverage();
     updateDebuggerObservesFlag(DebuggerObservesCoverage);
@@ -1366,19 +1368,19 @@ Realm::addSizeOfIncludingThis(mozilla::M
                               size_t* jitRealm,
                               size_t* privateData,
                               size_t* scriptCountsMapArg)
 {
     // This is temporary until Realm and JSCompartment are completely separated.
     JSCompartment::addSizeOfExcludingThis(mallocSizeOf, crossCompartmentWrappersArg);
 
     *realmObject += mallocSizeOf(this);
-    objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
-                                        tiArrayTypeTables, tiObjectTypeTables,
-                                        realmTables);
+    objectGroups_.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
+                                         tiArrayTypeTables, tiObjectTypeTables,
+                                         realmTables);
     wasm.addSizeOfExcludingThis(mallocSizeOf, realmTables);
 
     objects_.addSizeOfExcludingThis(mallocSizeOf,
                                     innerViewsArg,
                                     lazyArrayBuffersArg,
                                     objectMetadataTablesArg,
                                     nonSyntacticLexicalEnvironmentsArg);
 
--- a/js/src/vm/JSCompartment.h
+++ b/js/src/vm/JSCompartment.h
@@ -552,81 +552,64 @@ class WeakMapBase;
 
 struct JSCompartment
 {
   protected:
     JS::Zone*                    zone_;
     JSRuntime*                   runtime_;
 
   private:
-    friend struct JSRuntime;
-    friend struct JSContext;
+    js::WrapperMap crossCompartmentWrappers;
 
   public:
+    /*
+     * During GC, stores the head of a list of incoming pointers from gray cells.
+     *
+     * The objects in the list are either cross-compartment wrappers, or
+     * debugger wrapper objects.  The list link is either in the second extra
+     * slot for the former, or a special slot for the latter.
+     */
+    JSObject* gcIncomingGrayPointers = nullptr;
+
+    void* data = nullptr;
+
+    // These flags help us to discover if a compartment that shouldn't be alive
+    // manages to outlive a GC. Note that these flags have to be on the
+    // compartment, not the realm, because same-compartment realms can have
+    // cross-realm pointers without wrappers.
+    bool scheduledForDestruction = false;
+    bool maybeAlive = true;
+
     JS::Zone* zone() { return zone_; }
     const JS::Zone* zone() const { return zone_; }
 
     JSRuntime* runtimeFromMainThread() const {
         MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
         return runtime_;
     }
 
     // Note: Unrestricted access to the zone's runtime from an arbitrary
     // thread can easily lead to races. Use this method very carefully.
     JSRuntime* runtimeFromAnyThread() const {
         return runtime_;
     }
 
-  public:
-    void*                        data;
-
-  protected:
-    js::SavedStacks              savedStacks_;
-
-  private:
-    js::WrapperMap               crossCompartmentWrappers;
-
-  public:
     void assertNoCrossCompartmentWrappers() {
         MOZ_ASSERT(crossCompartmentWrappers.empty());
     }
 
-  public:
-    js::RegExpCompartment        regExps;
-
-    // Recompute the probability with which this compartment should record
-    // profiling data (stack traces, allocations log, etc.) about each
-    // allocation. We consult the probabilities requested by the Debugger
-    // instances observing us, if any.
-    void chooseAllocationSamplingProbability() { savedStacks_.chooseSamplingProbability(this); }
-
   protected:
     void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* crossCompartmentWrappersArg);
 
   public:
-    // Object group tables and other state in the compartment.
-    js::ObjectGroupCompartment   objectGroups;
-
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkWrapperMapAfterMovingGC();
 #endif
 
-    // All unboxed layouts in the compartment.
-    mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
-
-    /*
-     * During GC, stores the head of a list of incoming pointers from gray cells.
-     *
-     * The objects in the list are either cross-compartment wrappers, or
-     * debugger wrapper objects.  The list link is either in the second extra
-     * slot for the former, or a special slot for the latter.
-     */
-    JSObject*                    gcIncomingGrayPointers;
-
   private:
     bool getNonWrapperObjectForCurrentCompartment(JSContext* cx, js::MutableHandleObject obj);
     bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing, js::MutableHandleObject obj);
 
   protected:
     explicit JSCompartment(JS::Zone* zone);
     ~JSCompartment();
 
@@ -684,40 +667,21 @@ struct JSCompartment
      * when compacting to update cross-compartment pointers.
      */
     void traceOutgoingCrossCompartmentWrappers(JSTracer* trc);
     static void traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc);
 
     void sweepAfterMinorGC(JSTracer* trc);
 
     void sweepCrossCompartmentWrappers();
-    void sweepSavedStacks();
-    void sweepRegExps();
-    void sweepDebugEnvironments();
 
     static void fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc);
     void fixupAfterMovingGC();
 
-    js::SavedStacks& savedStacks() { return savedStacks_; }
-
     void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
-
-    static size_t offsetOfRegExps() {
-        return offsetof(JSCompartment, regExps);
-    }
-
-    /* Bookkeeping information for debug scope objects. */
-    js::UniquePtr<js::DebugEnvironments> debugEnvs;
-
-    // These flags help us to discover if a compartment that shouldn't be alive
-    // manages to outlive a GC. Note that these flags have to be on the
-    // compartment, not the realm, because same-compartment realms can have
-    // cross-realm pointers without wrappers.
-    bool scheduledForDestruction = false;
-    bool maybeAlive = true;
 };
 
 namespace js {
 
 // ObjectRealm stores various tables and other state associated with particular
 // objects in a realm. To make sure the correct ObjectRealm is used for an
 // object, use of the ObjectRealm::get(obj) static method is required.
 class ObjectRealm
@@ -777,28 +741,34 @@ class ObjectRealm
 
     js::LexicalEnvironmentObject*
     getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx, js::HandleObject enclosing);
     js::LexicalEnvironmentObject* getNonSyntacticLexicalEnvironment(JSObject* enclosing) const;
 };
 
 } // namespace js
 
-class JS::Realm : public JSCompartment
+class JS::Realm : private JSCompartment
 {
     const JS::RealmCreationOptions creationOptions_;
     JS::RealmBehaviors behaviors_;
 
     friend struct ::JSContext;
     js::ReadBarrieredGlobalObject global_;
 
     // Note: this is private to enforce use of ObjectRealm::get(obj).
     js::ObjectRealm objects_;
     friend js::ObjectRealm& js::ObjectRealm::get(const JSObject*);
 
+    // Object group tables and other state in the realm. This is private to
+    // enforce use of ObjectGroupRealm::get(group)/getForNewObject(cx).
+    js::ObjectGroupRealm objectGroups_;
+    friend js::ObjectGroupRealm& js::ObjectGroupRealm::get(js::ObjectGroup* group);
+    friend js::ObjectGroupRealm& js::ObjectGroupRealm::getForNewObject(JSContext* cx);
+
     // The global environment record's [[VarNames]] list that contains all
     // names declared using FunctionDeclaration, GeneratorDeclaration, and
     // VariableDeclaration declarations in global code in this realm.
     // Names are only removed from this list by a |delete IdentifierReference|
     // that successfully removes that global property.
     using VarNamesSet = JS::GCHashSet<JSAtom*,
                                       js::DefaultHasher<JSAtom*>,
                                       js::SystemAllocPolicy>;
@@ -812,16 +782,21 @@ class JS::Realm : public JSCompartment
 
     // Random number generator for randomHashCodeScrambler().
     mozilla::non_crypto::XorShift128PlusRNG randomKeyGenerator_;
 
     JSPrincipals* principals_ = nullptr;
 
     js::UniquePtr<js::jit::JitRealm> jitRealm_;
 
+    // Bookkeeping information for debug scope objects.
+    js::UniquePtr<js::DebugEnvironments> debugEnvs_;
+
+    js::SavedStacks savedStacks_;
+
     // Used by memory reporters and invalid otherwise.
     JS::RealmStats* realmStats_ = nullptr;
 
     const js::AllocationMetadataBuilder* allocationMetadataBuilder_ = nullptr;
     void* realmPrivate_ = nullptr;
 
     // This pointer is controlled by the embedder. If it is non-null, and if
     // cx->enableAccessValidation is true, then we assert that *validAccessPtr
@@ -858,16 +833,18 @@ class JS::Realm : public JSCompartment
   public:
     // WebAssembly state for the realm.
     js::wasm::Realm wasm;
 
     // Aggregated output used to collect JSScript hit counts when code coverage
     // is enabled.
     js::coverage::LCovRealm lcovOutput;
 
+    js::RegExpRealm regExps;
+
     js::DtoaCache dtoaCache;
     js::NewProxyCache newProxyCache;
     js::ArraySpeciesLookup arraySpeciesLookup;
 
     js::PerformanceGroupHolder performanceMonitoring;
 
     js::UniquePtr<js::ScriptCountsMap> scriptCountsMap;
     js::UniquePtr<js::ScriptNameMap> scriptNameMap;
@@ -922,16 +899,34 @@ class JS::Realm : public JSCompartment
                                 size_t* crossCompartmentWrappers,
                                 size_t* savedStacksSet,
                                 size_t* varNamesSet,
                                 size_t* nonSyntacticLexicalScopes,
                                 size_t* jitRealm,
                                 size_t* privateData,
                                 size_t* scriptCountsMapArg);
 
+    JS::Zone* zone() {
+        return zone_;
+    }
+    const JS::Zone* zone() const {
+        return zone_;
+    }
+
+    JSRuntime* runtimeFromMainThread() const {
+        MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
+        return runtime_;
+    }
+
+    // Note: Unrestricted access to the runtime from an arbitrary thread
+    // can easily lead to races. Use this method very carefully.
+    JSRuntime* runtimeFromAnyThread() const {
+        return runtime_;
+    }
+
     const JS::RealmCreationOptions& creationOptions() const { return creationOptions_; }
     JS::RealmBehaviors& behaviors() { return behaviors_; }
     const JS::RealmBehaviors& behaviors() const { return behaviors_; }
 
     /* Whether to preserve JIT code on non-shrinking GCs. */
     bool preserveJitCode() { return creationOptions_.preserveJitCode(); }
 
     bool isAtomsRealm() const {
@@ -982,28 +977,38 @@ class JS::Realm : public JSCompartment
      */
     void traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark);
     /*
      * This method clears out tables of roots in preparation for the final GC.
      */
     void finishRoots();
 
     void sweepAfterMinorGC();
+    void sweepDebugEnvironments();
     void sweepObjectRealm();
+    void sweepRegExps();
     void sweepSelfHostingScriptSource();
     void sweepTemplateObjects();
 
+    void sweepObjectGroups() {
+        objectGroups_.sweep();
+    }
+
     void clearScriptCounts();
     void clearScriptNames();
 
     void purge();
 
+    void fixupAfterMovingGC();
     void fixupScriptMapsAfterMovingGC();
 
 #ifdef JSGC_HASH_TABLE_CHECKS
+    void checkObjectGroupTablesAfterMovingGC() {
+        objectGroups_.checkTablesAfterMovingGC();
+    }
     void checkScriptMapsAfterMovingGC();
 #endif
 
     // Add a name to [[VarNames]].  Reports OOM on failure.
     MOZ_MUST_USE bool addToVarNames(JSContext* cx, JS::Handle<JSAtom*> name);
     void sweepVarNames();
 
     void removeFromVarNames(JS::Handle<JSAtom*> name) {
@@ -1261,16 +1266,41 @@ class JS::Realm : public JSCompartment
     }
 
     bool ensureJitRealmExists(JSContext* cx);
     void sweepJitRealm();
 
     js::jit::JitRealm* jitRealm() {
         return jitRealm_.get();
     }
+
+    js::DebugEnvironments* debugEnvs() {
+        return debugEnvs_.get();
+    }
+    js::UniquePtr<js::DebugEnvironments>& debugEnvsRef() {
+        return debugEnvs_;
+    }
+
+    js::SavedStacks& savedStacks() {
+        return savedStacks_;
+    }
+
+    // Recompute the probability with which this realm should record
+    // profiling data (stack traces, allocations log, etc.) about each
+    // allocation. We consult the probabilities requested by the Debugger
+    // instances observing us, if any.
+    void chooseAllocationSamplingProbability() {
+        savedStacks_.chooseSamplingProbability(this);
+    }
+
+    void sweepSavedStacks();
+
+    static constexpr size_t offsetOfRegExps() {
+        return offsetof(JS::Realm, regExps);
+    }
 };
 
 namespace js {
 
 // We only set the maybeAlive flag for objects and scripts. It's assumed that,
 // if a compartment is alive, then it will have at least some live object or
 // script it in. Even if we get this wrong, the worst that will happen is that
 // scheduledForDestruction will be set on the compartment, which will cause
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -161,17 +161,16 @@ class CompartmentChecker
     void check(JSScript* script) {
         MOZ_ASSERT(JS::CellIsNotGray(script));
         if (script)
             check(script->compartment());
     }
 
     void check(InterpreterFrame* fp);
     void check(AbstractFramePtr frame);
-    void check(SavedStacks* stacks);
 
     void check(Handle<PropertyDescriptor> desc) {
         check(desc.object());
         if (desc.hasGetterObject())
             check(desc.getterObject());
         if (desc.hasSetterObject())
             check(desc.setterObject());
         check(desc.value());
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -885,17 +885,18 @@ CreateFunctionPrototype(JSContext* cx, J
 
     protoGroup->setInterpretedFunction(functionProto);
 
     /*
      * The default 'new' group of Function.prototype is required by type
      * inference to have unknown properties, to simplify handling of e.g.
      * NewFunctionClone.
      */
-    if (!JSObject::setNewGroupUnknown(cx, &JSFunction::class_, functionProto))
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    if (!JSObject::setNewGroupUnknown(cx, realm, &JSFunction::class_, functionProto))
         return nullptr;
 
     return functionProto;
 }
 
 static const ClassOps JSFunctionClassOps = {
     nullptr,                 /* addProperty */
     nullptr,                 /* delProperty */
--- a/js/src/vm/JSObject-inl.h
+++ b/js/src/vm/JSObject-inl.h
@@ -153,17 +153,18 @@ js::NativeObject::updateDictionaryListPo
         shape()->listp = shapePtr();
 }
 
 /* static */ inline bool
 JSObject::setSingleton(JSContext* cx, js::HandleObject obj)
 {
     MOZ_ASSERT(!IsInsideNursery(obj));
 
-    js::ObjectGroup* group = js::ObjectGroup::lazySingletonGroup(cx, obj->getClass(),
+    js::ObjectGroupRealm& realm = js::ObjectGroupRealm::get(obj->group_);
+    js::ObjectGroup* group = js::ObjectGroup::lazySingletonGroup(cx, realm, obj->getClass(),
                                                                  obj->taggedProto());
     if (!group)
         return false;
 
     obj->group_ = group;
     return true;
 }
 
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -2103,17 +2103,17 @@ SetClassAndProto(JSContext* cx, HandleOb
     RootedObjectGroup oldGroup(cx, obj->group());
 
     ObjectGroup* newGroup;
     if (oldGroup->maybeInterpretedFunction()) {
         // We're changing the group/proto of a scripted function. Create a new
         // group so we can keep track of the interpreted function for Ion
         // inlining.
         MOZ_ASSERT(obj->is<JSFunction>());
-        newGroup = ObjectGroupCompartment::makeGroup(cx, &JSFunction::class_, proto);
+        newGroup = ObjectGroupRealm::makeGroup(cx, &JSFunction::class_, proto);
         if (!newGroup)
             return false;
         newGroup->setInterpretedFunction(oldGroup->maybeInterpretedFunction());
     } else {
         newGroup = ObjectGroup::defaultNewGroup(cx, clasp, proto);
         if (!newGroup)
             return false;
     }
@@ -2139,17 +2139,18 @@ SetClassAndProto(JSContext* cx, HandleOb
 
 /* static */ bool
 JSObject::changeToSingleton(JSContext* cx, HandleObject obj)
 {
     MOZ_ASSERT(!obj->isSingleton());
 
     MarkObjectGroupUnknownProperties(cx, obj->group());
 
-    ObjectGroup* group = ObjectGroup::lazySingletonGroup(cx, obj->getClass(),
+    ObjectGroupRealm& realm = ObjectGroupRealm::get(obj->group());
+    ObjectGroup* group = ObjectGroup::lazySingletonGroup(cx, realm, obj->getClass(),
                                                          obj->taggedProto());
     if (!group)
         return false;
 
     obj->group_ = group;
     return true;
 }
 
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -385,17 +385,18 @@ class JSObject : public js::gc::Cell
         return setFlags(cx, obj, js::BaseShape::ITERATED_SINGLETON);
     }
 
     /*
      * Mark an object as requiring its default 'new' type to have unknown
      * properties.
      */
     inline bool isNewGroupUnknown() const;
-    static bool setNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj);
+    static bool setNewGroupUnknown(JSContext* cx, js::ObjectGroupRealm& realm,
+                                   const js::Class* clasp, JS::HandleObject obj);
 
     /* Set a new prototype for an object with a singleton type. */
     static bool splicePrototype(JSContext* cx, js::HandleObject obj, const js::Class* clasp,
                                 js::Handle<js::TaggedProto> proto);
 
     /*
      * For bootstrapping, whether to splice a prototype for Function.prototype
      * or the global object.
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -317,54 +317,55 @@ JSObject::makeLazyGroup(JSContext* cx, H
 
     if (obj->isNative() && obj->as<NativeObject>().isIndexed())
         initialFlags |= OBJECT_FLAG_SPARSE_INDEXES;
 
     if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX)
         initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW;
 
     Rooted<TaggedProto> proto(cx, obj->taggedProto());
-    ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, obj->getClass(), proto,
-                                                           initialFlags);
+    ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, obj->getClass(), proto,
+                                                     initialFlags);
     if (!group)
         return nullptr;
 
     AutoEnterAnalysis enter(cx);
 
     /* Fill in the type according to the state of this object. */
 
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted())
         group->setInterpretedFunction(&obj->as<JSFunction>());
 
     obj->group_ = group;
 
     return group;
 }
 
 /* static */ bool
-JSObject::setNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj)
+JSObject::setNewGroupUnknown(JSContext* cx, ObjectGroupRealm& realm, const js::Class* clasp,
+                             JS::HandleObject obj)
 {
-    ObjectGroup::setDefaultNewGroupUnknown(cx, clasp, obj);
+    ObjectGroup::setDefaultNewGroupUnknown(cx, realm, clasp, obj);
     return JSObject::setFlags(cx, obj, BaseShape::NEW_GROUP_UNKNOWN);
 }
 
 /////////////////////////////////////////////////////////////////////
-// ObjectGroupCompartment NewTable
+// ObjectGroupRealm NewTable
 /////////////////////////////////////////////////////////////////////
 
 /*
- * Entries for the per-compartment set of groups which are the default
+ * Entries for the per-realm set of groups which are the default
  * types to use for some prototype. An optional associated object is used which
  * allows multiple groups to be created with the same prototype. The
  * associated object may be a function (for types constructed with 'new') or a
  * type descriptor (for typed objects). These entries are also used for the set
- * of lazy groups in the compartment, which use a null associated object
- * (though there are only a few of these per compartment).
+ * of lazy groups in the realm, which use a null associated object
+ * (though there are only a few of these per realm).
  */
-struct ObjectGroupCompartment::NewEntry
+struct ObjectGroupRealm::NewEntry
 {
     ReadBarrieredObjectGroup group;
 
     // Note: This pointer is only used for equality and does not need a read barrier.
     JSObject* associated;
 
     NewEntry(ObjectGroup* group, JSObject* associated)
       : group(group), associated(associated)
@@ -400,17 +401,17 @@ struct ObjectGroupCompartment::NewEntry
     }
 
     static inline HashNumber hash(const Lookup& lookup) {
         HashNumber hash = MovableCellHasher<TaggedProto>::hash(lookup.proto);
         hash = mozilla::AddToHash(hash, MovableCellHasher<JSObject*>::hash(lookup.associated));
         return mozilla::AddToHash(hash, mozilla::HashGeneric(lookup.clasp));
     }
 
-    static inline bool match(const ObjectGroupCompartment::NewEntry& key, const Lookup& lookup) {
+    static inline bool match(const ObjectGroupRealm::NewEntry& key, const Lookup& lookup) {
         if (lookup.clasp && key.group.unbarrieredGet()->clasp() != lookup.clasp)
             return false;
 
         TaggedProto proto = key.group.unbarrieredGet()->proto();
         if (!MovableCellHasher<TaggedProto>::match(proto, lookup.proto))
             return false;
 
         return MovableCellHasher<JSObject*>::match(key.associated, lookup.associated);
@@ -425,39 +426,51 @@ struct ObjectGroupCompartment::NewEntry
 
     bool operator==(const NewEntry& other) const {
         return group == other.group && associated == other.associated;
     }
 };
 
 namespace js {
 template <>
-struct FallibleHashMethods<ObjectGroupCompartment::NewEntry>
+struct FallibleHashMethods<ObjectGroupRealm::NewEntry>
 {
     template <typename Lookup> static bool hasHash(Lookup&& l) {
-        return ObjectGroupCompartment::NewEntry::hasHash(mozilla::Forward<Lookup>(l));
+        return ObjectGroupRealm::NewEntry::hasHash(mozilla::Forward<Lookup>(l));
     }
     template <typename Lookup> static bool ensureHash(Lookup&& l) {
-        return ObjectGroupCompartment::NewEntry::ensureHash(mozilla::Forward<Lookup>(l));
+        return ObjectGroupRealm::NewEntry::ensureHash(mozilla::Forward<Lookup>(l));
     }
 };
 } // namespace js
 
-class ObjectGroupCompartment::NewTable : public JS::WeakCache<js::GCHashSet<NewEntry, NewEntry,
+class ObjectGroupRealm::NewTable : public JS::WeakCache<js::GCHashSet<NewEntry, NewEntry,
                                                                             SystemAllocPolicy>>
 {
     using Table = js::GCHashSet<NewEntry, NewEntry, SystemAllocPolicy>;
     using Base = JS::WeakCache<Table>;
 
   public:
     explicit NewTable(Zone* zone) : Base(zone) {}
 };
 
+/* static*/ ObjectGroupRealm&
+ObjectGroupRealm::get(ObjectGroup* group)
+{
+    return group->realm()->objectGroups_;
+}
+
+/* static*/ ObjectGroupRealm&
+ObjectGroupRealm::getForNewObject(JSContext* cx)
+{
+    return cx->realm()->objectGroups_;
+}
+
 MOZ_ALWAYS_INLINE ObjectGroup*
-ObjectGroupCompartment::DefaultNewGroupCache::lookup(const Class* clasp, TaggedProto proto,
+ObjectGroupRealm::DefaultNewGroupCache::lookup(const Class* clasp, TaggedProto proto,
                                                      JSObject* associated)
 {
     if (group_ &&
         associated_ == associated &&
         group_->proto() == proto &&
         (!clasp || group_->clasp() == clasp))
     {
         return group_;
@@ -498,27 +511,27 @@ ObjectGroup::defaultNewGroup(JSContext* 
         } else {
             associated = nullptr;
         }
 
         if (!associated)
             clasp = &PlainObject::class_;
     }
 
-    ObjectGroupCompartment& groups = cx->compartment()->objectGroups;
+    ObjectGroupRealm& groups = ObjectGroupRealm::getForNewObject(cx);
 
     if (ObjectGroup* group = groups.defaultNewGroupCache.lookup(clasp, proto, associated))
         return group;
 
     AutoEnterAnalysis enter(cx);
 
-    ObjectGroupCompartment::NewTable*& table = groups.defaultNewTable;
+    ObjectGroupRealm::NewTable*& table = groups.defaultNewTable;
 
     if (!table) {
-        table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
+        table = cx->new_<ObjectGroupRealm::NewTable>(cx->zone());
         if (!table || !table->init()) {
             js_delete(table);
             table = nullptr;
             ReportOutOfMemory(cx);
             return nullptr;
         }
     }
 
@@ -540,39 +553,39 @@ ObjectGroup::defaultNewGroup(JSContext* 
             if (protoObj->hasUncacheableProto()) {
                 HandleNativeObject nobj = protoObj.as<NativeObject>();
                 if (!NativeObject::clearFlag(cx, nobj, BaseShape::UNCACHEABLE_PROTO))
                     return nullptr;
             }
         }
     }
 
-    ObjectGroupCompartment::NewTable::AddPtr p =
-        table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, associated));
+    ObjectGroupRealm::NewTable::AddPtr p =
+        table->lookupForAdd(ObjectGroupRealm::NewEntry::Lookup(clasp, proto, associated));
     if (p) {
         ObjectGroup* group = p->group;
         MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
         MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
                               group->clasp() == &UnboxedPlainObject::class_);
         MOZ_ASSERT(group->proto() == proto);
         groups.defaultNewGroupCache.put(group, associated);
         return group;
     }
 
     ObjectGroupFlags initialFlags = 0;
     if (proto.isDynamic() || (proto.isObject() && proto.toObject()->isNewGroupUnknown()))
         initialFlags = OBJECT_FLAG_DYNAMIC_MASK;
 
     Rooted<TaggedProto> protoRoot(cx, proto);
-    ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, clasp ? clasp : &PlainObject::class_,
-                                                           protoRoot, initialFlags);
+    ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, clasp ? clasp : &PlainObject::class_,
+                                                     protoRoot, initialFlags);
     if (!group)
         return nullptr;
 
-    if (!table->add(p, ObjectGroupCompartment::NewEntry(group, associated))) {
+    if (!table->add(p, ObjectGroupRealm::NewEntry(group, associated))) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     if (associated) {
         if (associated->is<JSFunction>()) {
             if (!TypeNewScript::make(cx, group, &associated->as<JSFunction>()))
                 return nullptr;
@@ -600,80 +613,82 @@ ObjectGroup::defaultNewGroup(JSContext* 
         AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type());
     }
 
     groups.defaultNewGroupCache.put(group, associated);
     return group;
 }
 
 /* static */ ObjectGroup*
-ObjectGroup::lazySingletonGroup(JSContext* cx, const Class* clasp, TaggedProto proto)
+ObjectGroup::lazySingletonGroup(JSContext* cx, ObjectGroupRealm& realm, const Class* clasp,
+                                TaggedProto proto)
 {
     MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
 
-    ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.lazyTable;
+    ObjectGroupRealm::NewTable*& table = realm.lazyTable;
 
     if (!table) {
-        table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
+        table = cx->new_<ObjectGroupRealm::NewTable>(cx->zone());
         if (!table || !table->init()) {
             ReportOutOfMemory(cx);
             js_delete(table);
             table = nullptr;
             return nullptr;
         }
     }
 
-    ObjectGroupCompartment::NewTable::AddPtr p =
-        table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, nullptr));
+    ObjectGroupRealm::NewTable::AddPtr p =
+        table->lookupForAdd(ObjectGroupRealm::NewEntry::Lookup(clasp, proto, nullptr));
     if (p) {
         ObjectGroup* group = p->group;
         MOZ_ASSERT(group->lazy());
 
         return group;
     }
 
     AutoEnterAnalysis enter(cx);
 
     Rooted<TaggedProto> protoRoot(cx, proto);
     ObjectGroup* group =
-        ObjectGroupCompartment::makeGroup(cx, clasp, protoRoot,
-                                          OBJECT_FLAG_SINGLETON | OBJECT_FLAG_LAZY_SINGLETON);
+        ObjectGroupRealm::makeGroup(cx, clasp, protoRoot,
+                                    OBJECT_FLAG_SINGLETON | OBJECT_FLAG_LAZY_SINGLETON);
     if (!group)
         return nullptr;
 
-    if (!table->add(p, ObjectGroupCompartment::NewEntry(group, nullptr))) {
+    if (!table->add(p, ObjectGroupRealm::NewEntry(group, nullptr))) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     return group;
 }
 
 /* static */ void
-ObjectGroup::setDefaultNewGroupUnknown(JSContext* cx, const Class* clasp, HandleObject obj)
+ObjectGroup::setDefaultNewGroupUnknown(JSContext* cx, ObjectGroupRealm& realm, const Class* clasp,
+                                       HandleObject obj)
 {
     // If the object already has a new group, mark that group as unknown.
-    ObjectGroupCompartment::NewTable* table = cx->compartment()->objectGroups.defaultNewTable;
+    ObjectGroupRealm::NewTable* table = realm.defaultNewTable;
     if (table) {
         Rooted<TaggedProto> taggedProto(cx, TaggedProto(obj));
-        auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, taggedProto, nullptr);
+        auto lookup = ObjectGroupRealm::NewEntry::Lookup(clasp, taggedProto, nullptr);
         auto p = table->lookup(lookup);
         if (p)
             MarkObjectGroupUnknownProperties(cx, p->group);
     }
 }
 
 #ifdef DEBUG
 /* static */ bool
 ObjectGroup::hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group)
 {
-    ObjectGroupCompartment::NewTable* table = proto->compartment()->objectGroups.defaultNewTable;
+    ObjectGroupRealm::NewTable* table = ObjectGroupRealm::get(group).defaultNewTable;
 
     if (table) {
-        auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr);
+        auto lookup = ObjectGroupRealm::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr);
         auto p = table->lookup(lookup);
         return p && p->group == group;
     }
     return false;
 }
 #endif /* DEBUG */
 
 inline const Class*
@@ -730,20 +745,20 @@ ObjectGroup::defaultNewGroup(JSContext* 
         proto = GlobalObject::getOrCreatePrototype(cx, key);
         if (!proto)
             return nullptr;
     }
     return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto));
 }
 
 /////////////////////////////////////////////////////////////////////
-// ObjectGroupCompartment ArrayObjectTable
+// ObjectGroupRealm ArrayObjectTable
 /////////////////////////////////////////////////////////////////////
 
-struct ObjectGroupCompartment::ArrayObjectKey : public DefaultHasher<ArrayObjectKey>
+struct ObjectGroupRealm::ArrayObjectKey : public DefaultHasher<ArrayObjectKey>
 {
     TypeSet::Type type;
 
     ArrayObjectKey()
       : type(TypeSet::UndefinedType())
     {}
 
     explicit ArrayObjectKey(TypeSet::Type type)
@@ -828,47 +843,47 @@ ObjectGroup::newArrayObject(JSContext* c
                 } else {
                     elementType = TypeSet::UnknownType();
                     break;
                 }
             }
         }
     }
 
-    ObjectGroupCompartment::ArrayObjectTable*& table =
-        cx->compartment()->objectGroups.arrayObjectTable;
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    ObjectGroupRealm::ArrayObjectTable*& table = realm.arrayObjectTable;
 
     if (!table) {
-        table = cx->new_<ObjectGroupCompartment::ArrayObjectTable>();
+        table = cx->new_<ObjectGroupRealm::ArrayObjectTable>();
         if (!table || !table->init()) {
             ReportOutOfMemory(cx);
             js_delete(table);
             table = nullptr;
             return nullptr;
         }
     }
 
-    ObjectGroupCompartment::ArrayObjectKey key(elementType);
-    DependentAddPtr<ObjectGroupCompartment::ArrayObjectTable> p(cx, *table, key);
+    ObjectGroupRealm::ArrayObjectKey key(elementType);
+    DependentAddPtr<ObjectGroupRealm::ArrayObjectTable> p(cx, *table, key);
 
     RootedObjectGroup group(cx);
     if (p) {
         group = p->value();
     } else {
         JSObject* proto = GlobalObject::getOrCreateArrayPrototype(cx, cx->global());
         if (!proto)
             return nullptr;
         Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto));
-        group = ObjectGroupCompartment::makeGroup(cx, &ArrayObject::class_, taggedProto);
+        group = ObjectGroupRealm::makeGroup(cx, &ArrayObject::class_, taggedProto);
         if (!group)
             return nullptr;
 
         AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
 
-        if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
+        if (!p.add(cx, *table, ObjectGroupRealm::ArrayObjectKey(elementType), group))
             return nullptr;
     }
 
     // The type of the elements being added will already be reflected in type
     // information.
     ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
     return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
 }
@@ -1036,20 +1051,20 @@ js::CombinePlainObjectPropertyTypes(JSCo
             }
         }
     }
 
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////
-// ObjectGroupCompartment PlainObjectTable
+// ObjectGroupRealm PlainObjectTable
 /////////////////////////////////////////////////////////////////////
 
-struct ObjectGroupCompartment::PlainObjectKey
+struct ObjectGroupRealm::PlainObjectKey
 {
     jsid* properties;
     uint32_t nproperties;
 
     struct Lookup {
         IdValuePair* properties;
         uint32_t nproperties;
 
@@ -1077,17 +1092,17 @@ struct ObjectGroupCompartment::PlainObje
         for (unsigned i = 0; i < nproperties; i++) {
             if (gc::IsAboutToBeFinalizedUnbarriered(&properties[i]))
                 return true;
         }
         return false;
     }
 };
 
-struct ObjectGroupCompartment::PlainObjectEntry
+struct ObjectGroupRealm::PlainObjectEntry
 {
     ReadBarrieredObjectGroup group;
     ReadBarrieredShape shape;
     TypeSet::Type* types;
 
     bool needsSweep(unsigned nproperties) {
         if (IsAboutToBeFinalized(&group))
             return true;
@@ -1151,43 +1166,43 @@ js::NewPlainObjectWithProperties(JSConte
 /* static */ JSObject*
 ObjectGroup::newPlainObject(JSContext* cx, IdValuePair* properties, size_t nproperties,
                             NewObjectKind newKind)
 {
     // Watch for simple cases where we don't try to reuse plain object groups.
     if (newKind == SingletonObject || nproperties == 0 || nproperties >= PropertyTree::MAX_HEIGHT)
         return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
 
-    ObjectGroupCompartment::PlainObjectTable*& table =
-        cx->compartment()->objectGroups.plainObjectTable;
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    ObjectGroupRealm::PlainObjectTable*& table = realm.plainObjectTable;
 
     if (!table) {
-        table = cx->new_<ObjectGroupCompartment::PlainObjectTable>();
+        table = cx->new_<ObjectGroupRealm::PlainObjectTable>();
         if (!table || !table->init()) {
             ReportOutOfMemory(cx);
             js_delete(table);
             table = nullptr;
             return nullptr;
         }
     }
 
-    ObjectGroupCompartment::PlainObjectKey::Lookup lookup(properties, nproperties);
-    ObjectGroupCompartment::PlainObjectTable::Ptr p = table->lookup(lookup);
+    ObjectGroupRealm::PlainObjectKey::Lookup lookup(properties, nproperties);
+    ObjectGroupRealm::PlainObjectTable::Ptr p = table->lookup(lookup);
 
     if (!p) {
         if (!CanShareObjectGroup(properties, nproperties))
             return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
 
         JSObject* proto = GlobalObject::getOrCreatePrototype(cx, JSProto_Object);
         if (!proto)
             return nullptr;
 
         Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
-        RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_,
-                                                                      tagged));
+        RootedObjectGroup group(cx, ObjectGroupRealm::makeGroup(cx, &PlainObject::class_,
+                                                                tagged));
         if (!group)
             return nullptr;
 
         gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
         RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group,
                                                                   allocKind, TenuredObject));
         if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties))
             return nullptr;
@@ -1229,27 +1244,27 @@ ObjectGroup::newPlainObject(JSContext* c
         }
 
         for (size_t i = 0; i < nproperties; i++) {
             ids[i] = properties[i].id;
             types[i] = GetValueTypeForTable(obj->getSlot(i));
             AddTypePropertyId(cx, group, nullptr, IdToTypeId(ids[i]), types[i]);
         }
 
-        ObjectGroupCompartment::PlainObjectKey key;
+        ObjectGroupRealm::PlainObjectKey key;
         key.properties = ids;
         key.nproperties = nproperties;
-        MOZ_ASSERT(ObjectGroupCompartment::PlainObjectKey::match(key, lookup));
+        MOZ_ASSERT(ObjectGroupRealm::PlainObjectKey::match(key, lookup));
 
-        ObjectGroupCompartment::PlainObjectEntry entry;
+        ObjectGroupRealm::PlainObjectEntry entry;
         entry.group.set(group);
         entry.shape.set(obj->lastProperty());
         entry.types = types;
 
-        ObjectGroupCompartment::PlainObjectTable::AddPtr np = table->lookupForAdd(lookup);
+        ObjectGroupRealm::PlainObjectTable::AddPtr np = table->lookupForAdd(lookup);
         if (!table->add(np, key, entry)) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         ids.forget();
         types.forget();
 
@@ -1318,20 +1333,20 @@ ObjectGroup::newPlainObject(JSContext* c
         group->maybePreliminaryObjects(*sweep)->registerNewObject(obj);
         group->maybePreliminaryObjects(*sweep)->maybeAnalyze(cx, group);
     }
 
     return obj;
 }
 
 /////////////////////////////////////////////////////////////////////
-// ObjectGroupCompartment AllocationSiteTable
+// ObjectGroupRealm AllocationSiteTable
 /////////////////////////////////////////////////////////////////////
 
-struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> {
+struct ObjectGroupRealm::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> {
     ReadBarrieredScript script;
 
     uint32_t offset : 24;
     JSProtoKey kind : 8;
 
     ReadBarrieredObject proto;
 
     static const uint32_t OFFSET_LIMIT = (1 << 23);
@@ -1389,17 +1404,17 @@ struct ObjectGroupCompartment::Allocatio
     bool operator==(const AllocationSiteKey& other) const {
         return script == other.script &&
                offset == other.offset &&
                kind == other.kind &&
                proto == other.proto;
     }
 };
 
-class ObjectGroupCompartment::AllocationSiteTable
+class ObjectGroupRealm::AllocationSiteTable
   : public JS::WeakCache<js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
                                        AllocationSiteKey, SystemAllocPolicy>>
 {
     using Table = js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
                                 AllocationSiteKey, SystemAllocPolicy>;
     using Base = JS::WeakCache<Table>;
 
   public:
@@ -1407,58 +1422,59 @@ class ObjectGroupCompartment::Allocation
 };
 
 /* static */ ObjectGroup*
 ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* pc,
                                  JSProtoKey kind, HandleObject protoArg /* = nullptr */)
 {
     MOZ_ASSERT(!useSingletonForAllocationSite(scriptArg, pc, kind));
     MOZ_ASSERT_IF(protoArg, kind == JSProto_Array);
+    MOZ_ASSERT(cx->realm() == scriptArg->realm());
 
     uint32_t offset = scriptArg->pcToOffset(pc);
 
-    if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT) {
+    if (offset >= ObjectGroupRealm::AllocationSiteKey::OFFSET_LIMIT) {
         if (protoArg)
             return defaultNewGroup(cx, GetClassForProtoKey(kind), TaggedProto(protoArg));
         return defaultNewGroup(cx, kind);
     }
 
-    ObjectGroupCompartment::AllocationSiteTable*& table =
-        cx->compartment()->objectGroups.allocationSiteTable;
+    ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
+    ObjectGroupRealm::AllocationSiteTable*& table = realm.allocationSiteTable;
 
     if (!table) {
-        table = cx->new_<ObjectGroupCompartment::AllocationSiteTable>(cx->zone());
+        table = cx->new_<ObjectGroupRealm::AllocationSiteTable>(cx->zone());
         if (!table || !table->init()) {
             ReportOutOfMemory(cx);
             js_delete(table);
             table = nullptr;
             return nullptr;
         }
     }
 
     RootedScript script(cx, scriptArg);
     JSObject* proto = protoArg;
     if (!proto && kind != JSProto_Null) {
         proto = GlobalObject::getOrCreatePrototype(cx, kind);
         if (!proto)
             return nullptr;
     }
 
-    Rooted<ObjectGroupCompartment::AllocationSiteKey> key(cx,
-        ObjectGroupCompartment::AllocationSiteKey(script, offset, kind, proto));
+    Rooted<ObjectGroupRealm::AllocationSiteKey> key(cx,
+        ObjectGroupRealm::AllocationSiteKey(script, offset, kind, proto));
 
-    ObjectGroupCompartment::AllocationSiteTable::AddPtr p = table->lookupForAdd(key);
+    ObjectGroupRealm::AllocationSiteTable::AddPtr p = table->lookupForAdd(key);
     if (p)
         return p->value();
 
     AutoEnterAnalysis enter(cx);
 
     Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
-    ObjectGroup* res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged,
-                                                         OBJECT_FLAG_FROM_ALLOCATION_SITE);
+    ObjectGroup* res = ObjectGroupRealm::makeGroup(cx, GetClassForProtoKey(kind), tagged,
+                                                   OBJECT_FLAG_FROM_ALLOCATION_SITE);
     if (!res)
         return nullptr;
 
     if (JSOp(*pc) == JSOP_NEWOBJECT) {
         // Keep track of the preliminary objects with this group, so we can try
         // to use an unboxed layout for the object once some are allocated.
         Shape* shape = script->getObject(pc)->as<PlainObject>().lastProperty();
         if (!shape->isEmptyShape()) {
@@ -1475,19 +1491,21 @@ ObjectGroup::allocationSiteGroup(JSConte
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     return res;
 }
 
 void
-ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
-                                                   JSProtoKey kind, ObjectGroup* group)
+ObjectGroupRealm::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
+                                             JSProtoKey kind, ObjectGroup* group)
 {
+    MOZ_ASSERT(script->realm() == group->realm());
+
     AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull());
 
     AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key);
     MOZ_RELEASE_ASSERT(p);
     allocationSiteTable->remove(p);
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!allocationSiteTable->putNew(key, group))
@@ -1585,63 +1603,63 @@ ObjectGroup::getCopyOnWriteObject(JSScri
 
 /* static */ bool
 ObjectGroup::findAllocationSite(JSContext* cx, ObjectGroup* group,
                                 JSScript** script, uint32_t* offset)
 {
     *script = nullptr;
     *offset = 0;
 
-    const ObjectGroupCompartment::AllocationSiteTable* table =
-        cx->compartment()->objectGroups.allocationSiteTable;
+    ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
+    const ObjectGroupRealm::AllocationSiteTable* table = realm.allocationSiteTable;
 
     if (!table)
         return false;
 
-    for (ObjectGroupCompartment::AllocationSiteTable::Range r = table->all();
+    for (ObjectGroupRealm::AllocationSiteTable::Range r = table->all();
          !r.empty();
          r.popFront())
     {
         if (group == r.front().value()) {
             *script = r.front().key().script;
             *offset = r.front().key().offset;
             return true;
         }
     }
 
     return false;
 }
 
 /////////////////////////////////////////////////////////////////////
-// ObjectGroupCompartment
+// ObjectGroupRealm
 /////////////////////////////////////////////////////////////////////
 
-ObjectGroupCompartment::~ObjectGroupCompartment()
+ObjectGroupRealm::~ObjectGroupRealm()
 {
     js_delete(defaultNewTable);
     js_delete(lazyTable);
     js_delete(arrayObjectTable);
     js_delete(plainObjectTable);
     js_delete(allocationSiteTable);
     stringSplitStringGroup = nullptr;
 }
 
 void
-ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
+ObjectGroupRealm::removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
                                               JSObject* associated)
 {
     auto p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated));
     MOZ_RELEASE_ASSERT(p);
 
     defaultNewTable->remove(p);
     defaultNewGroupCache.purge();
 }
 
 void
-ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
+ObjectGroupRealm::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
                                                JSObject* associated, ObjectGroup* group)
 {
     NewEntry::Lookup lookup(clasp, proto, associated);
 
     auto p = defaultNewTable->lookup(lookup);
     MOZ_RELEASE_ASSERT(p);
     defaultNewTable->remove(p);
     defaultNewGroupCache.purge();
@@ -1649,35 +1667,35 @@ ObjectGroupCompartment::replaceDefaultNe
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!defaultNewTable->putNew(lookup, NewEntry(group, associated)))
             oomUnsafe.crash("Inconsistent object table");
     }
 }
 
 /* static */
 ObjectGroup*
-ObjectGroupCompartment::makeGroup(JSContext* cx, const Class* clasp,
-                                  Handle<TaggedProto> proto,
-                                  ObjectGroupFlags initialFlags /* = 0 */)
+ObjectGroupRealm::makeGroup(JSContext* cx, const Class* clasp,
+                            Handle<TaggedProto> proto,
+                            ObjectGroupFlags initialFlags /* = 0 */)
 {
     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
 
     ObjectGroup* group = Allocate<ObjectGroup>(cx);
     if (!group)
         return nullptr;
     new(group) ObjectGroup(clasp, proto, cx->realm(), initialFlags);
 
     return group;
 }
 
 /* static */
 ObjectGroup*
-ObjectGroupCompartment::getStringSplitStringGroup(JSContext* cx)
+ObjectGroupRealm::getStringSplitStringGroup(JSContext* cx)
 {
-    ObjectGroupCompartment& groups = cx->compartment()->objectGroups;
+    ObjectGroupRealm& groups = ObjectGroupRealm::getForNewObject(cx);
 
     ObjectGroup* group = groups.stringSplitStringGroup.get();
     if (group) {
         return group;
     }
 
     // The following code is a specialized version of the code
     // for ObjectGroup::allocationSiteGroup().
@@ -1693,21 +1711,21 @@ ObjectGroupCompartment::getStringSplitSt
     if (!group)
         return nullptr;
 
     groups.stringSplitStringGroup.set(group);
     return group;
 }
 
 void
-ObjectGroupCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
+ObjectGroupRealm::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                                size_t* allocationSiteTables,
                                                size_t* arrayObjectGroupTables,
                                                size_t* plainObjectGroupTables,
-                                               size_t* compartmentTables)
+                                               size_t* realmTables)
 {
     if (allocationSiteTable)
         *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf);
 
     if (arrayObjectTable)
         *arrayObjectGroupTables += arrayObjectTable->sizeOfIncludingThis(mallocSizeOf);
 
     if (plainObjectTable) {
@@ -1721,24 +1739,24 @@ ObjectGroupCompartment::addSizeOfExcludi
             const PlainObjectEntry& value = e.front().value();
 
             /* key.ids and values.types have the same length. */
             *plainObjectGroupTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
         }
     }
 
     if (defaultNewTable)
-        *compartmentTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf);
+        *realmTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf);
 
     if (lazyTable)
-        *compartmentTables += lazyTable->sizeOfIncludingThis(mallocSizeOf);
+        *realmTables += lazyTable->sizeOfIncludingThis(mallocSizeOf);
 }
 
 void
-ObjectGroupCompartment::clearTables()
+ObjectGroupRealm::clearTables()
 {
     if (allocationSiteTable && allocationSiteTable->initialized())
         allocationSiteTable->clear();
     if (arrayObjectTable && arrayObjectTable->initialized())
         arrayObjectTable->clear();
     if (plainObjectTable && plainObjectTable->initialized()) {
         for (PlainObjectTable::Enum e(*plainObjectTable); !e.empty(); e.popFront()) {
             const PlainObjectKey& key = e.front().key();
@@ -1751,28 +1769,28 @@ ObjectGroupCompartment::clearTables()
     if (defaultNewTable && defaultNewTable->initialized())
         defaultNewTable->clear();
     if (lazyTable && lazyTable->initialized())
         lazyTable->clear();
     defaultNewGroupCache.purge();
 }
 
 /* static */ bool
-ObjectGroupCompartment::PlainObjectTableSweepPolicy::needsSweep(PlainObjectKey* key,
-                                                                PlainObjectEntry* entry)
+ObjectGroupRealm::PlainObjectTableSweepPolicy::needsSweep(PlainObjectKey* key,
+                                                          PlainObjectEntry* entry)
 {
     if (!(JS::GCPolicy<PlainObjectKey>::needsSweep(key) || entry->needsSweep(key->nproperties)))
         return false;
     js_free(key->properties);
     js_free(entry->types);
     return true;
 }
 
 void
-ObjectGroupCompartment::sweep()
+ObjectGroupRealm::sweep()
 {
     /*
      * Iterate through the array/object group tables and remove all entries
      * referencing collected data. These tables only hold weak references.
      */
 
     if (arrayObjectTable)
         arrayObjectTable->sweep();
@@ -1781,17 +1799,17 @@ ObjectGroupCompartment::sweep()
     if (stringSplitStringGroup) {
         if (JS::GCPolicy<ReadBarrieredObjectGroup>::needsSweep(&stringSplitStringGroup)) {
             stringSplitStringGroup = nullptr;
         }
     }
 }
 
 void
-ObjectGroupCompartment::fixupNewTableAfterMovingGC(NewTable* table)
+ObjectGroupRealm::fixupNewTableAfterMovingGC(NewTable* table)
 {
     /*
      * Each entry's hash depends on the object's prototype and we can't tell
      * whether that has been moved or not in sweepNewObjectGroupTable().
      */
     if (table && table->initialized()) {
         for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
             NewEntry& entry = e.mutableFront();
@@ -1812,17 +1830,17 @@ ObjectGroupCompartment::fixupNewTableAft
                 entry.associated = Forwarded(entry.associated);
         }
     }
 }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void
-ObjectGroupCompartment::checkNewTableAfterMovingGC(NewTable* table)
+ObjectGroupRealm::checkNewTableAfterMovingGC(NewTable* table)
 {
     /*
      * Assert that nothing points into the nursery or needs to be relocated, and
      * that the hash table entries are discoverable.
      */
     if (!table || !table->initialized())
         return;
 
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -24,16 +24,17 @@ class TypeDescr;
 class UnboxedLayout;
 
 class PreliminaryObjectArrayWithTemplate;
 class TypeNewScript;
 class HeapTypeSet;
 class AutoClearTypeInferenceStateOnOOM;
 class AutoSweepObjectGroup;
 class CompilerConstraintList;
+class ObjectGroupRealm;
 
 namespace gc {
 void MergeCompartments(JSCompartment* source, JSCompartment* target);
 } // namespace gc
 
 /*
  * The NewObjectKind allows an allocation site to specify the type properties
  * and lifetime requirements that must be fixed at allocation time.
@@ -522,31 +523,32 @@ class ObjectGroup : public gc::TenuredCe
     static bool useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecode* pc);
 
     // Whether to make a singleton object at an allocation site.
     static bool useSingletonForAllocationSite(JSScript* script, jsbytecode* pc,
                                               JSProtoKey key);
     static bool useSingletonForAllocationSite(JSScript* script, jsbytecode* pc,
                                               const Class* clasp);
 
-    // Static accessors for ObjectGroupCompartment NewTable.
+    // Static accessors for ObjectGroupRealm NewTable.
 
     static ObjectGroup* defaultNewGroup(JSContext* cx, const Class* clasp,
                                         TaggedProto proto,
                                         JSObject* associated = nullptr);
-    static ObjectGroup* lazySingletonGroup(JSContext* cx, const Class* clasp,
-                                           TaggedProto proto);
+    static ObjectGroup* lazySingletonGroup(JSContext* cx, ObjectGroupRealm& realm,
+                                           const Class* clasp, TaggedProto proto);
 
-    static void setDefaultNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj);
+    static void setDefaultNewGroupUnknown(JSContext* cx, ObjectGroupRealm& realm,
+                                          const js::Class* clasp, JS::HandleObject obj);
 
 #ifdef DEBUG
     static bool hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group);
 #endif
 
-    // Static accessors for ObjectGroupCompartment ArrayObjectTable and PlainObjectTable.
+    // Static accessors for ObjectGroupRealm ArrayObjectTable and PlainObjectTable.
 
     enum class NewArrayKind {
         Normal,       // Specialize array group based on its element type.
         CopyOnWrite,  // Make an array with copy-on-write elements.
         UnknownIndex  // Make an array with an unknown element type.
     };
 
     // Create an ArrayObject with the specified elements and a group specialized
@@ -556,17 +558,17 @@ class ObjectGroup : public gc::TenuredCe
                                        NewArrayKind arrayKind = NewArrayKind::Normal);
 
     // Create a PlainObject or UnboxedPlainObject with the specified properties
     // and a group specialized for those properties.
     static JSObject* newPlainObject(JSContext* cx,
                                     IdValuePair* properties, size_t nproperties,
                                     NewObjectKind newKind);
 
-    // Static accessors for ObjectGroupCompartment AllocationSiteTable.
+    // Static accessors for ObjectGroupRealm AllocationSiteTable.
 
     // Get a non-singleton group to use for objects created at the specified
     // allocation site.
     static ObjectGroup* allocationSiteGroup(JSContext* cx, JSScript* script, jsbytecode* pc,
                                             JSProtoKey key, HandleObject proto = nullptr);
 
     // Get a non-singleton group to use for objects created in a JSNative call.
     static ObjectGroup* callingAllocationSiteGroup(JSContext* cx, JSProtoKey key,
@@ -584,18 +586,18 @@ class ObjectGroup : public gc::TenuredCe
     // Returns false if not found.
     static bool findAllocationSite(JSContext* cx, ObjectGroup* group,
                                    JSScript** script, uint32_t* offset);
 
   private:
     static ObjectGroup* defaultNewGroup(JSContext* cx, JSProtoKey key);
 };
 
-// Structure used to manage the groups in a compartment.
-class ObjectGroupCompartment
+// Structure used to manage the groups in a realm.
+class ObjectGroupRealm
 {
   private:
     class NewTable;
 
     struct ArrayObjectKey;
     using ArrayObjectTable = js::GCRekeyableHashMap<ArrayObjectKey,
                                                     ReadBarrieredObjectGroup,
                                                     ArrayObjectKey,
@@ -610,17 +612,17 @@ class ObjectGroupCompartment
                                            PlainObjectEntry,
                                            PlainObjectKey,
                                            SystemAllocPolicy,
                                            PlainObjectTableSweepPolicy>;
 
     class AllocationSiteTable;
 
   private:
-    // Set of default 'new' or lazy groups in the compartment.
+    // Set of default 'new' or lazy groups in the realm.
     NewTable* defaultNewTable = nullptr;
     NewTable* lazyTable = nullptr;
 
     // This cache is purged on GC.
     class DefaultNewGroupCache
     {
         ObjectGroup* group_;
         JSObject* associated_;
@@ -650,36 +652,46 @@ class ObjectGroupCompartment
     // objects which have the same shape and property types will also share a
     // group. We don't try to collate arrays or objects with type mismatches.
     ArrayObjectTable* arrayObjectTable = nullptr;
     PlainObjectTable* plainObjectTable = nullptr;
 
     // Table for referencing types of objects keyed to an allocation site.
     AllocationSiteTable* allocationSiteTable = nullptr;
 
-    // A single per-compartment ObjectGroup for all calls to StringSplitString.
+    // A single per-realm ObjectGroup for all calls to StringSplitString.
     // StringSplitString is always called from self-hosted code, and conceptually
     // the return object for a string.split(string) operation should have a
     // unified type.  Having a global group for this also allows us to remove
     // the hash-table lookup that would be required if we allocated this group
     // on the basis of call-site pc.
     ReadBarrieredObjectGroup stringSplitStringGroup = {};
 
+  public:
+    // All unboxed layouts in the realm.
+    mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
+
     // END OF PROPERTIES
 
   private:
     friend class ObjectGroup;
 
     struct AllocationSiteKey;
 
   public:
     struct NewEntry;
 
-    ObjectGroupCompartment() = default;
-    ~ObjectGroupCompartment();
+    ObjectGroupRealm() = default;
+    ~ObjectGroupRealm();
+
+    ObjectGroupRealm(ObjectGroupRealm&) = delete;
+    void operator=(ObjectGroupRealm&) = delete;
+
+    static ObjectGroupRealm& get(ObjectGroup* group);
+    static ObjectGroupRealm& getForNewObject(JSContext* cx);
 
     void replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
                                     JSProtoKey kind, ObjectGroup* group);
 
     void removeDefaultNewGroup(const Class* clasp, TaggedProto proto, JSObject* associated);
     void replaceDefaultNewGroup(const Class* clasp, TaggedProto proto, JSObject* associated,
                                 ObjectGroup* group);
 
@@ -688,17 +700,17 @@ class ObjectGroupCompartment
                                   ObjectGroupFlags initialFlags = 0);
 
     static ObjectGroup* getStringSplitStringGroup(JSContext* cx);
 
     void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* allocationSiteTables,
                                 size_t* arrayGroupTables,
                                 size_t* plainObjectGroupTables,
-                                size_t* compartmentTables);
+                                size_t* realmTables);
 
     void clearTables();
 
     void sweep();
 
     void purge() {
         defaultNewGroupCache.purge();
     }
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -59,18 +59,19 @@ ProxyObject::New(JSContext* cx, const Ba
     /*
      * Eagerly mark properties unknown for proxies, so we don't try to track
      * their properties and so that we don't need to walk the compartment if
      * their prototype changes later.  But don't do this for DOM proxies,
      * because we want to be able to keep track of them in typesets in useful
      * ways.
      */
     if (proto.isObject() && !options.singleton() && !clasp->isDOMClass()) {
+        ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx);
         RootedObject protoObj(cx, proto.toObject());
-        if (!JSObject::setNewGroupUnknown(cx, clasp, protoObj))
+        if (!JSObject::setNewGroupUnknown(cx, realm, clasp, protoObj))
             return nullptr;
     }
 
     // Ensure that the wrapper has the same lifetime assumptions as the
     // wrappee. Prefer to allocate in the nursery, when possible.
     NewObjectKind newKind = NurseryAllocatedProxy;
     if (options.singleton()) {
         MOZ_ASSERT(priv.isNull() || (priv.isGCThing() && priv.toGCThing()->isTenured()));
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -1203,38 +1203,38 @@ RegExpShared::sizeOfExcludingThis(mozill
 
     n += tables.sizeOfExcludingThis(mallocSizeOf);
     for (size_t i = 0; i < tables.length(); i++)
         n += mallocSizeOf(tables[i].get());
 
     return n;
 }
 
-/* RegExpCompartment */
+/* RegExpRealm */
 
-RegExpCompartment::RegExpCompartment()
+RegExpRealm::RegExpRealm()
   : matchResultTemplateObject_(nullptr),
     optimizableRegExpPrototypeShape_(nullptr),
     optimizableRegExpInstanceShape_(nullptr)
 {}
 
 ArrayObject*
-RegExpCompartment::createMatchResultTemplateObject(JSContext* cx)
+RegExpRealm::createMatchResultTemplateObject(JSContext* cx)
 {
     MOZ_ASSERT(!matchResultTemplateObject_);
 
     /* Create template array object */
     RootedArrayObject templateObject(cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount,
                                                                   nullptr, TenuredObject));
     if (!templateObject)
         return matchResultTemplateObject_; // = nullptr
 
     // Create a new group for the template.
     Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
-    ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(), proto);
+    ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, templateObject->getClass(), proto);
     if (!group)
         return matchResultTemplateObject_; // = nullptr
     templateObject->setGroup(group);
 
     /* Set dummy index property */
     RootedValue index(cx, Int32Value(0));
     if (!NativeDefineDataProperty(cx, templateObject, cx->names().index, index, JSPROP_ENUMERATE))
         return matchResultTemplateObject_; // = nullptr
@@ -1269,17 +1269,17 @@ RegExpZone::init()
 {
     if (!set_.init(0))
         return false;
 
     return true;
 }
 
 void
-RegExpCompartment::sweep()
+RegExpRealm::sweep()
 {
     if (matchResultTemplateObject_ &&
         IsAboutToBeFinalized(&matchResultTemplateObject_))
     {
         matchResultTemplateObject_.set(nullptr);
     }
 
     if (optimizableRegExpPrototypeShape_ &&
--- a/js/src/vm/RegExpShared.h
+++ b/js/src/vm/RegExpShared.h
@@ -23,17 +23,17 @@
 #include "js/UbiNode.h"
 #include "js/Vector.h"
 #include "vm/ArrayObject.h"
 #include "vm/JSAtom.h"
 
 namespace js {
 
 class ArrayObject;
-class RegExpCompartment;
+class RegExpRealm;
 class RegExpShared;
 class RegExpStatics;
 class VectorMatchPairs;
 
 using RootedRegExpShared = JS::Rooted<RegExpShared*>;
 using HandleRegExpShared = JS::Handle<RegExpShared*>;
 using MutableHandleRegExpShared = JS::MutableHandle<RegExpShared*>;
 
@@ -283,17 +283,17 @@ class RegExpZone
 
 #ifdef DEBUG
     void clear() { set_.clear(); }
 #endif
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 };
 
-class RegExpCompartment
+class RegExpRealm
 {
     /*
      * This is the template object where the result of re.exec() is based on,
      * if there is a result. This is used in CreateRegExpMatchResult to set
      * the input/index properties faster.
      */
     ReadBarriered<ArrayObject*> matchResultTemplateObject_;
 
@@ -316,17 +316,17 @@ class RegExpCompartment
      *   * lastProperty is lastIndex
      *   * prototype is RegExp.prototype
      */
     ReadBarriered<Shape*> optimizableRegExpInstanceShape_;
 
     ArrayObject* createMatchResultTemplateObject(JSContext* cx);
 
   public:
-    explicit RegExpCompartment();
+    explicit RegExpRealm();
 
     void sweep();
 
     /* Get or create template object used to base the result of .exec() on. */
     ArrayObject* getOrCreateMatchResultTemplateObject(JSContext* cx) {
         if (matchResultTemplateObject_)
             return matchResultTemplateObject_;
         return createMatchResultTemplateObject(cx);
@@ -341,20 +341,20 @@ class RegExpCompartment
     Shape* getOptimizableRegExpInstanceShape() {
         return optimizableRegExpInstanceShape_;
     }
     void setOptimizableRegExpInstanceShape(Shape* shape) {
         optimizableRegExpInstanceShape_ = shape;
     }
 
     static size_t offsetOfOptimizableRegExpPrototypeShape() {
-        return offsetof(RegExpCompartment, optimizableRegExpPrototypeShape_);
+        return offsetof(RegExpRealm, optimizableRegExpPrototypeShape_);
     }
     static size_t offsetOfOptimizableRegExpInstanceShape() {
-        return offsetof(RegExpCompartment, optimizableRegExpInstanceShape_);
+        return offsetof(RegExpRealm, optimizableRegExpInstanceShape_);
     }
 };
 
 } /* namespace js */
 
 namespace JS {
 namespace ubi {
 
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -220,17 +220,17 @@ JSRuntime::init(JSContext* cx, uint32_t 
         return false;
 
     JS::RealmOptions options;
     ScopedJSDeletePtr<Realm> atomsRealm(js_new<Realm>(atomsZone.get(), options));
     if (!atomsRealm || !atomsRealm->init(nullptr))
         return false;
 
     gc.atomsZone = atomsZone.get();
-    if (!atomsZone->compartments().append(atomsRealm.get()))
+    if (!atomsZone->compartments().append(JS::GetCompartmentForRealm(atomsRealm.get())))
         return false;
 
     atomsRealm->setIsSystem(true);
     atomsRealm->setIsAtomsRealm();
 
     atomsZone.forget();
     this->atomsRealm_ = atomsRealm.forget();
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -96,40 +96,40 @@ LiveSavedFrameCache::insert(JSContext* c
 
 void
 LiveSavedFrameCache::find(JSContext* cx, FramePtr& framePtr, const jsbytecode* pc,
                           MutableHandleSavedFrame frame) const
 {
     MOZ_ASSERT(initialized());
     MOZ_ASSERT(framePtr.hasCachedSavedFrame());
 
-    // If we flushed the cache due to a compartment mismatch, then we shouldn't
+    // If we flushed the cache due to a realm mismatch, then we shouldn't
     // expect to find any frames in the cache.
     if (frames->empty()) {
         frame.set(nullptr);
         return;
     }
 
-    // All our SavedFrames should be in the same compartment. If the last
-    // entry's SavedFrame's compartment doesn't match cx's, flush the cache.
-    if (frames->back().savedFrame->compartment() != cx->compartment()) {
+    // All our SavedFrames should be in the same realm. If the last
+    // entry's SavedFrame's realm doesn't match cx's, flush the cache.
+    if (frames->back().savedFrame->realm() != cx->realm()) {
 #ifdef DEBUG
-        // Check that they are, indeed, all in the same compartment.
-        auto compartment = frames->back().savedFrame->compartment();
+        // Check that they are, indeed, all in the same realm.
+        auto compartment = frames->back().savedFrame->realm();
         for (const auto& f : (*frames))
-            MOZ_ASSERT(compartment == f.savedFrame->compartment());
+            MOZ_ASSERT(compartment == f.savedFrame->realm());
 #endif
         frames->clear();
         frame.set(nullptr);
         return;
     }
 
     Key key(framePtr);
     while (key != frames->back().key) {
-        MOZ_ASSERT(frames->back().savedFrame->compartment() == cx->compartment());
+        MOZ_ASSERT(frames->back().savedFrame->realm() == cx->realm());
 
         // We know that the cache does contain an entry for frameIter's frame,
         // since its bit is set. That entry must be below this one in the stack,
         // so frames->back() must correspond to a frame younger than
         // frameIter's. If frameIter is the youngest frame with its bit set,
         // then its entry is the youngest that is valid, and we can pop this
         // entry. Even if frameIter is not the youngest frame with its bit set,
         // since we're going to push new cache entries for all frames younger
@@ -554,17 +554,17 @@ SavedFrame::initFromLookup(JSContext* cx
 SavedFrame::create(JSContext* cx)
 {
     RootedGlobalObject global(cx, cx->global());
     assertSameCompartment(cx, global);
 
     // Ensure that we don't try to capture the stack again in the
     // `SavedStacksMetadataBuilder` for this new SavedFrame object, and
     // accidentally cause O(n^2) behavior.
-    SavedStacks::AutoReentrancyGuard guard(cx->compartment()->savedStacks());
+    SavedStacks::AutoReentrancyGuard guard(cx->realm()->savedStacks());
 
     RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
     if (!proto)
         return nullptr;
     assertSameCompartment(cx, proto);
 
     return NewObjectWithGivenProto<SavedFrame>(cx, proto, TenuredObject);
 }
@@ -824,17 +824,17 @@ UnwrapSavedFrame(JSContext* cx, HandleOb
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep,
                     SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     {
         AutoMaybeEnterFrameRealm ar(cx, savedFrame);
         bool skippedAsync;
         js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
         if (!frame) {
             sourcep.set(cx->runtime()->emptyString);
             return SavedFrameResult::AccessDenied;
@@ -847,17 +847,17 @@ GetSavedFrameSource(JSContext* cx, Handl
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep,
                   SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
     MOZ_ASSERT(linep);
 
     AutoMaybeEnterFrameRealm ar(cx, savedFrame);
     bool skippedAsync;
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *linep = 0;
         return SavedFrameResult::AccessDenied;
@@ -867,17 +867,17 @@ GetSavedFrameLine(JSContext* cx, HandleO
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp,
                     SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
     MOZ_ASSERT(columnp);
 
     AutoMaybeEnterFrameRealm ar(cx, savedFrame);
     bool skippedAsync;
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *columnp = 0;
         return SavedFrameResult::AccessDenied;
@@ -887,17 +887,17 @@ GetSavedFrameColumn(JSContext* cx, Handl
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep,
                                  SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     {
         AutoMaybeEnterFrameRealm ar(cx, savedFrame);
         bool skippedAsync;
         js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
         if (!frame) {
             namep.set(nullptr);
             return SavedFrameResult::AccessDenied;
@@ -910,17 +910,17 @@ GetSavedFrameFunctionDisplayName(JSConte
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep,
                         SavedFrameSelfHosted unused_ /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     {
         AutoMaybeEnterFrameRealm ar(cx, savedFrame);
         bool skippedAsync;
         // This function is always called with self-hosted frames excluded by
         // GetValueIfNotCached in dom/bindings/Exceptions.cpp. However, we want
         // to include them because our Promise implementation causes us to have
         // the async cause on a self-hosted frame. So we just ignore the
@@ -941,17 +941,17 @@ GetSavedFrameAsyncCause(JSContext* cx, H
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp,
                          SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     AutoMaybeEnterFrameRealm ar(cx, savedFrame);
     bool skippedAsync;
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         asyncParentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
@@ -974,17 +974,17 @@ GetSavedFrameAsyncParent(JSContext* cx, 
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp,
                     SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     AutoMaybeEnterFrameRealm ar(cx, savedFrame);
     bool skippedAsync;
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         parentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
@@ -1080,17 +1080,17 @@ FormatV8StackFrame(JSContext* cx, js::St
 }
 
 JS_PUBLIC_API(bool)
 BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
                  size_t indent, js::StackFormat format)
 {
     js::AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    MOZ_RELEASE_ASSERT(cx->compartment());
+    MOZ_RELEASE_ASSERT(cx->realm());
 
     js::StringBuffer sb(cx);
 
     if (format == js::StackFormat::Default)
         format = cx->runtime()->stackFormat();
     MOZ_ASSERT(format != js::StackFormat::Default);
 
     // Enter a new block to constrain the scope of possibly entering the stack's
@@ -1274,18 +1274,18 @@ SavedStacks::init()
            pcLocationMap.init();
 }
 
 bool
 SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame,
                               JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */)
 {
     MOZ_ASSERT(initialized());
-    MOZ_RELEASE_ASSERT(cx->compartment());
-    assertSameCompartment(cx, this);
+    MOZ_RELEASE_ASSERT(cx->realm());
+    MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
 
     if (creatingSavedFrame ||
         cx->isExceptionPending() ||
         !cx->global() ||
         !cx->global()->isStandardClassResolved(JSProto_Object))
     {
         frame.set(nullptr);
         return true;
@@ -1296,18 +1296,18 @@ SavedStacks::saveCurrentStack(JSContext*
 }
 
 bool
 SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
                             MutableHandleSavedFrame adoptedStack,
                             const Maybe<size_t>& maxFrameCount)
 {
     MOZ_ASSERT(initialized());
-    MOZ_RELEASE_ASSERT(cx->compartment());
-    assertSameCompartment(cx, this);
+    MOZ_RELEASE_ASSERT(cx->realm());
+    MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
 
     RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
     if (!asyncCauseAtom)
         return false;
 
     RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack));
     MOZ_RELEASE_ASSERT(asyncStackObj);
     MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj));
@@ -1470,17 +1470,17 @@ SavedStacks::insertFrames(JSContext* cx,
                 break;
         }
 
         // We'll be pushing this frame onto stackChain. Gather the information
         // needed to construct the SavedFrame::Lookup.
         Rooted<LocationValue> location(cx);
         {
             AutoRealmUnchecked ar(cx, iter.compartment());
-            if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location))
+            if (!cx->realm()->savedStacks().getLocation(cx, iter, &location))
                 return false;
         }
 
         RootedAtom displayAtom(cx, iter.maybeFunctionDisplayAtom());
 
         auto principals = JS::GetRealmForCompartment(iter.compartment())->principals();
         MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc());
 
@@ -1613,22 +1613,22 @@ SavedStacks::adoptAsyncStack(JSContext* 
         }
 
         currentSavedFrame = currentSavedFrame->getParent();
     }
 
     // Attach the asyncCause to the youngest frame.
     stackChain[0]->asyncCause = asyncCause;
 
-    // If we walked the entire stack, and it's in cx's compartment, we don't
+    // If we walked the entire stack, and it's in cx's realm, we don't
     // need to rebuild the full chain again using the lookup objects - we can
     // just use the existing chain. Only the asyncCause on the youngest frame
     // needs to be changed.
     if (currentSavedFrame == nullptr &&
-        asyncStack->compartment() == cx->compartment())
+        asyncStack->realm() == cx->realm())
     {
         SavedFrame::HandleLookup lookup = stackChain[0];
         lookup->parent = asyncStack->getParent();
         asyncStack.set(getOrCreateSavedFrame(cx, lookup));
         return !!asyncStack;
     }
 
     // If we captured the maximum number of frames and the caller requested no
@@ -1743,17 +1743,18 @@ SavedStacks::createFrameFromLookup(JSCon
 bool
 SavedStacks::getLocation(JSContext* cx, const FrameIter& iter,
                          MutableHandle<LocationValue> locationp)
 {
     // We should only ever be caching location values for scripts in this
     // compartment. Otherwise, we would get dead cross-compartment scripts in
     // the cache because our compartment's sweep method isn't called when their
     // compartment gets collected.
-    assertSameCompartment(cx, this, iter.compartment());
+    MOZ_DIAGNOSTIC_ASSERT(&cx->realm()->savedStacks() == this);
+    assertSameCompartment(cx, iter.compartment());
 
     // When we have a |JSScript| for this frame, use a potentially memoized
     // location from our PCLocationMap and copy it into |locationp|. When we do
     // not have a |JSScript| for this frame (wasm frames), we take a slow path
     // that doesn't employ memoization, and update |locationp|'s slots directly.
 
     if (iter.isWasm()) {
         // Only asm.js has a displayURL.
@@ -1801,19 +1802,18 @@ SavedStacks::getLocation(JSContext* cx, 
         }
     }
 
     locationp.set(p->value());
     return true;
 }
 
 void
-SavedStacks::chooseSamplingProbability(JSCompartment* compartment)
+SavedStacks::chooseSamplingProbability(Realm* realm)
 {
-    Realm* realm = JS::GetRealmForCompartment(compartment);
     GlobalObject* global = realm->maybeGlobal();
     if (!global)
         return;
 
     GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
     if (!dbgs || dbgs->empty())
         return;
 
@@ -1845,45 +1845,33 @@ SavedStacks::chooseSamplingProbability(J
 }
 
 JSObject*
 SavedStacks::MetadataBuilder::build(JSContext* cx, HandleObject target,
                                     AutoEnterOOMUnsafeRegion& oomUnsafe) const
 {
     RootedObject obj(cx, target);
 
-    SavedStacks& stacks = cx->compartment()->savedStacks();
+    SavedStacks& stacks = cx->realm()->savedStacks();
     if (!stacks.bernoulli.trial())
         return nullptr;
 
     RootedSavedFrame frame(cx);
     if (!stacks.saveCurrentStack(cx, &frame))
         oomUnsafe.crash("SavedStacksMetadataBuilder");
 
     if (!Debugger::onLogAllocationSite(cx, obj, frame, mozilla::TimeStamp::Now()))
         oomUnsafe.crash("SavedStacksMetadataBuilder");
 
     MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>());
     return frame;
 }
 
 const SavedStacks::MetadataBuilder SavedStacks::metadataBuilder;
 
-#ifdef JS_CRASH_DIAGNOSTICS
-void
-CompartmentChecker::check(SavedStacks* stacks)
-{
-    if (&compartment->savedStacks() != stacks) {
-        printf("*** Compartment SavedStacks mismatch: %p vs. %p\n",
-               (void*) &compartment->savedStacks(), stacks);
-        MOZ_CRASH();
-    }
-}
-#endif /* JS_CRASH_DIAGNOSTICS */
-
 /* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsSystem;
 /* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsNotSystem;
 
 UTF8CharsZ
 BuildUTF8StackString(JSContext* cx, HandleObject stack)
 {
     RootedString stackStr(cx);
     if (!JS::BuildStackString(cx, stack, &stackStr))
@@ -1996,17 +1984,17 @@ ConstructSavedFrameStackSlow(JSContext* 
 
         ubiFrame = ubiFrame.get().parent();
     }
 
     js::RootedSavedFrame parentFrame(cx);
     for (size_t i = stackChain->length(); i != 0; i--) {
         SavedFrame::HandleLookup lookup = stackChain[i-1];
         lookup->parent = parentFrame;
-        parentFrame = cx->compartment()->savedStacks().getOrCreateSavedFrame(cx, lookup);
+        parentFrame = cx->realm()->savedStacks().getOrCreateSavedFrame(cx, lookup);
         if (!parentFrame)
             return false;
     }
 
     outSavedFrameStack.set(parentFrame);
     return true;
 }
 
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -169,17 +169,17 @@ class SavedStacks {
     MOZ_MUST_USE bool copyAsyncStack(JSContext* cx, HandleObject asyncStack,
                                      HandleString asyncCause,
                                      MutableHandleSavedFrame adoptedStack,
                                      const mozilla::Maybe<size_t>& maxFrameCount);
     void sweep();
     void trace(JSTracer* trc);
     uint32_t count();
     void clear();
-    void chooseSamplingProbability(JSCompartment*);
+    void chooseSamplingProbability(JS::Realm* realm);
 
     // Set the sampling random number generator's state to |state0| and
     // |state1|. One or the other must be non-zero. See the comments for
     // mozilla::non_crypto::XorShift128PlusRNG::setState for details.
     void setRNGState(uint64_t state0, uint64_t state1) { bernoulli.setRandomState(state0, state1); }
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1759,17 +1759,17 @@ bool
 js::intrinsic_StringSplitString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
     RootedString string(cx, args[0].toString());
     RootedString sep(cx, args[1].toString());
 
-    RootedObjectGroup group(cx, ObjectGroupCompartment::getStringSplitStringGroup(cx));
+    RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
     if (!group)
         return false;
 
     JSObject* aobj = str_split_string(cx, group, string, sep, INT32_MAX);
     if (!aobj)
         return false;
 
     args.rval().setObject(*aobj);
@@ -1785,17 +1785,17 @@ intrinsic_StringSplitStringLimit(JSConte
     RootedString string(cx, args[0].toString());
     RootedString sep(cx, args[1].toString());
 
     // args[2] should be already in UInt32 range, but it could be double typed,
     // because of Ion optimization.
     uint32_t limit = uint32_t(args[2].toNumber());
     MOZ_ASSERT(limit > 0, "Zero limit case is already handled in self-hosted code.");
 
-    RootedObjectGroup group(cx, ObjectGroupCompartment::getStringSplitStringGroup(cx));
+    RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
     if (!group)
         return false;
 
     JSObject* aobj = str_split_string(cx, group, string, sep, limit);
     if (!aobj)
         return false;
 
     args.rval().setObject(*aobj);
@@ -2804,17 +2804,17 @@ JSRuntime::createSelfHostingGlobal(JSCon
         JS_GlobalObjectTraceHook
     };
 
     static const Class shgClass = {
         "self-hosting-global", JSCLASS_GLOBAL_FLAGS,
         &shgClassOps
     };
 
-    AutoRealmUnchecked ar(cx, realm);
+    AutoRealmUnchecked ar(cx, compartment);
     Rooted<GlobalObject*> shg(cx, GlobalObject::createInternal(cx, &shgClass));
     if (!shg)
         return nullptr;
 
     cx->runtime()->selfHostingGlobal_ = shg;
     realm->setIsSelfHostingRealm();
     realm->setIsSystem(true);
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -380,18 +380,18 @@ InterpreterFrame::trace(JSTracer* trc, V
         // Clear dead block-scoped locals.
         while (nfixed > nlivefixed)
             unaliasedLocal(--nfixed).setUndefined();
 
         // Trace live locals.
         traceValues(trc, 0, nlivefixed);
     }
 
-    if (script->compartment()->debugEnvs)
-        script->compartment()->debugEnvs->traceLiveFrame(trc, this);
+    if (auto* debugEnvs = script->realm()->debugEnvs())
+        debugEnvs->traceLiveFrame(trc, this);
 }
 
 void
 InterpreterFrame::traceValues(JSTracer* trc, unsigned start, unsigned end)
 {
     if (start < end)
         TraceRootRange(trc, end - start, slots() + start, "vm_stack");
 }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3016,17 +3016,17 @@ ObjectGroup::detachNewScript(bool writeB
     // analyzed, remove it from the newObjectGroups table so that it will not be
     // produced by calling 'new' on the associated function anymore.
     // The TypeNewScript is not actually destroyed.
     AutoSweepObjectGroup sweep(this);
     TypeNewScript* newScript = anyNewScript(sweep);
     MOZ_ASSERT(newScript);
 
     if (newScript->analyzed()) {
-        ObjectGroupCompartment& objectGroups = newScript->function()->compartment()->objectGroups;
+        ObjectGroupRealm& objectGroups = ObjectGroupRealm::get(this);
         TaggedProto proto = this->proto();
         if (proto.isObject() && IsForwarded(proto.toObject()))
             proto = TaggedProto(Forwarded(proto.toObject()));
         JSObject* associated = MaybeForwarded(newScript->function());
         if (replacement) {
             AutoSweepObjectGroup sweepReplacement(replacement);
             MOZ_ASSERT(replacement->newScript(sweepReplacement)->function() == newScript->function());
             objectGroups.replaceDefaultNewGroup(nullptr, proto, associated, replacement);
@@ -3471,18 +3471,18 @@ JSFunction::setTypeForScriptedFunction(J
                                        bool singleton /* = false */)
 {
     if (singleton) {
         if (!setSingleton(cx, fun))
             return false;
     } else {
         RootedObject funProto(cx, fun->staticPrototype());
         Rooted<TaggedProto> taggedProto(cx, TaggedProto(funProto));
-        ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, &JSFunction::class_,
-                                                               taggedProto);
+        ObjectGroup* group = ObjectGroupRealm::makeGroup(cx, &JSFunction::class_,
+                                                         taggedProto);
         if (!group)
             return false;
 
         fun->setGroup(group);
         group->setInterpretedFunction(fun);
     }
 
     return true;
@@ -3800,16 +3800,17 @@ TypeNewScript::maybeAnalyze(JSContext* c
 
     // Make sure there aren't dead references in preliminaryObjects. This can
     // clear out the new script information on OOM.
     AutoSweepObjectGroup sweep(group);
     if (!group->newScript(sweep))
         return true;
 
     MOZ_ASSERT(this == group->newScript(sweep));
+    MOZ_ASSERT(cx->realm() == group->realm());
 
     if (regenerate)
         *regenerate = false;
 
     if (analyzed()) {
         // The analyses have already been performed.
         return true;
     }
@@ -3989,26 +3990,26 @@ TypeNewScript::maybeAnalyze(JSContext* c
     // existing group to represent fully initialized objects with all
     // definite properties in the prefix shape, and make a new group to
     // represent partially initialized objects.
     MOZ_ASSERT(prefixShape->slotSpan() > templateObject()->slotSpan());
 
     ObjectGroupFlags initialFlags = group->flags(sweep) & OBJECT_FLAG_DYNAMIC_MASK;
 
     Rooted<TaggedProto> protoRoot(cx, group->proto());
-    ObjectGroup* initialGroup = ObjectGroupCompartment::makeGroup(cx, group->clasp(), protoRoot,
-                                                                  initialFlags);
+    ObjectGroup* initialGroup = ObjectGroupRealm::makeGroup(cx, group->clasp(), protoRoot,
+                                                            initialFlags);
     if (!initialGroup)
         return false;
 
     initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty());
     group->addDefiniteProperties(cx, prefixShape);
 
-    cx->compartment()->objectGroups.replaceDefaultNewGroup(nullptr, group->proto(), function(),
-                                                           initialGroup);
+    ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
+    realm.replaceDefaultNewGroup(nullptr, group->proto(), function(), initialGroup);
 
     templateObject()->setGroup(initialGroup);
 
     // Transfer this TypeNewScript from the fully initialized group to the
     // partially initialized group.
     group->detachNewScript();
     initialGroup->setNewScript(this);
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -548,16 +548,18 @@ MakeReplacementTemplateObject(JSContext*
     }
 
     return obj;
 }
 
 /* static */ bool
 UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
 {
+    MOZ_ASSERT(cx->realm() == group->realm());
+
     AutoEnterAnalysis enter(cx);
 
     AutoSweepObjectGroup sweep(group);
     UnboxedLayout& layout = group->unboxedLayout(sweep);
     Rooted<TaggedProto> proto(cx, group->proto());
 
     MOZ_ASSERT(!layout.nativeGroup());
 
@@ -565,17 +567,17 @@ UnboxedLayout::makeNativeGroup(JSContext
 
     // Immediately clear any new script on the group. This is done by replacing
     // the existing new script with one for a replacement default new group.
     // This is done so that the size of the replacment group's objects is the
     // same as that for the unboxed group, so that we do not see polymorphic
     // slot accesses later on for sites that see converted objects from this
     // group and objects that were allocated using the replacement new group.
     if (layout.newScript()) {
-        replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+        replacementGroup = ObjectGroupRealm::makeGroup(cx, &PlainObject::class_, proto);
         if (!replacementGroup)
             return false;
 
         PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout);
         if (!templateObject)
             return false;
 
         TypeNewScript* replacementNewScript =
@@ -590,25 +592,25 @@ UnboxedLayout::makeNativeGroup(JSContext
     }
 
     // Similarly, if this group is keyed to an allocation site, replace its
     // entry with a new group that has no unboxed layout.
     if (layout.allocationScript()) {
         RootedScript script(cx, layout.allocationScript());
         jsbytecode* pc = layout.allocationPc();
 
-        replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+        replacementGroup = ObjectGroupRealm::makeGroup(cx, &PlainObject::class_, proto);
         if (!replacementGroup)
             return false;
 
         PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
         replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
 
-        cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object,
-                                                                   replacementGroup);
+        ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
+        realm.replaceAllocationSiteGroup(script, pc, JSProto_Object, replacementGroup);
 
         // Clear any baseline information at this opcode which might use the old group.
         if (script->hasBaselineScript()) {
             jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
             jit::ICFallbackStub* fallback = entry.fallbackStub();
             for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++)
                 iter.unlink(cx);
             if (fallback->isNewObject_Fallback())
@@ -631,18 +633,18 @@ UnboxedLayout::makeNativeGroup(JSContext
         Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name),
                                                 i, JSPROP_ENUMERATE));
         shape = cx->zone()->propertyTree().getChild(cx, shape, child);
         if (!shape)
             return false;
     }
 
     ObjectGroup* nativeGroup =
-        ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
-                                          group->flags(sweep) & OBJECT_FLAG_DYNAMIC_MASK);
+        ObjectGroupRealm::makeGroup(cx, &PlainObject::class_, proto,
+                                    group->flags(sweep) & OBJECT_FLAG_DYNAMIC_MASK);
     if (!nativeGroup)
         return false;
 
     // No sense propagating if we don't know what we started with.
     AutoSweepObjectGroup sweepNative(nativeGroup);
     if (!group->unknownProperties(sweep)) {
         for (size_t i = 0; i < layout.properties().length(); i++) {
             const UnboxedLayout::Property& property = layout.properties()[i];
@@ -1194,17 +1196,17 @@ CombinePlainObjectProperties(PlainObject
         if (!CombineUnboxedTypes(val, &existing))
             return false;
     }
 
     return true;
 }
 
 static size_t
-ComputePlainObjectLayout(JSContext* cx, Shape* templateShape,
+ComputePlainObjectLayout(JSContext* cx, ObjectGroupRealm& realm, Shape* templateShape,
                          UnboxedLayout::PropertyVector& properties)
 {
     // Fill in the names for all the object's properties.
     for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
         size_t slot = r.front().slot();
         MOZ_ASSERT(!properties[slot].name);
         properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
     }
@@ -1213,17 +1215,17 @@ ComputePlainObjectLayout(JSContext* cx, 
     uint32_t offset = 0;
 
     // Search for an existing unboxed layout which is a subset of this one.
     // If there are multiple such layouts, use the largest one. If we're able
     // to find such a layout, use the same property offsets for the shared
     // properties, which will allow us to generate better code if the objects
     // have a subtype/supertype relation and are accessed at common sites.
     UnboxedLayout* bestExisting = nullptr;
-    for (UnboxedLayout* existing : cx->compartment()->unboxedLayouts) {
+    for (UnboxedLayout* existing : realm.unboxedLayouts) {
         if (PropertiesAreSuperset(properties, existing)) {
             if (!bestExisting ||
                 existing->properties().length() > bestExisting->properties().length())
             {
                 bestExisting = existing;
             }
         }
     }
@@ -1385,33 +1387,34 @@ js::TryConvertToUnboxedLayout(JSContext*
     // names, and not indexes.
     for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
         jsid id = r.front().propid();
         uint32_t dummy;
         if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
             return true;
     }
 
-    layoutSize = ComputePlainObjectLayout(cx, templateShape, properties);
+    ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
+    layoutSize = ComputePlainObjectLayout(cx, realm, templateShape, properties);
 
     // The entire object must be allocatable inline.
     if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE)
         return true;
 
     UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
     MOZ_ASSERT(!layout);
     layout = group->zone()->make_unique<UnboxedLayout>(group->zone());
     if (!layout)
         return false;
 
     if (!layout->initProperties(properties, layoutSize))
         return false;
 
     // The unboxedLayouts list only tracks layouts for plain objects.
-    cx->compartment()->unboxedLayouts.insertFront(layout.get());
+    realm.unboxedLayouts.insertFront(layout.get());
 
     if (!SetLayoutTraceList(cx, layout.get()))
         return false;
 
     // We've determined that all the preliminary objects can use the new layout
     // just constructed, so convert the existing group to use the unboxed class,
     // and update the preliminary objects to use the new layout. Do the
     // fallible stuff first before modifying any objects.
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -249,16 +249,20 @@ GetImports(JSContext* cx,
 #if defined(ENABLE_WASM_GLOBAL) && defined(EARLY_BETA_OR_EARLIER)
             if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
                 RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
 
                 if (obj->isMutable() != global.isMutable()) {
                     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
                     return false;
                 }
+                if (obj->type() != global.type()) {
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TYPE_LINK);
+                    return false;
+                }
 
                 if (globalObjs.length() <= index && !globalObjs.resize(index + 1)) {
                     ReportOutOfMemory(cx);
                     return false;
                 }
                 globalObjs[index] = obj;
                 val = obj->val();
             } else
@@ -2162,17 +2166,17 @@ WasmGlobalObject::construct(JSContext* c
     if (!args.get(0).isObject()) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "global");
         return false;
     }
 
     RootedObject obj(cx, &args[0].toObject());
 
     RootedValue typeVal(cx);
-    if (!JS_GetProperty(cx, obj, "type", &typeVal))
+    if (!JS_GetProperty(cx, obj, "value", &typeVal))
         return false;
 
     RootedString typeStr(cx, ToString(cx, typeVal));
     if (!typeStr)
         return false;
 
     RootedLinearString typeLinearStr(cx, typeStr->ensureLinear(cx));
     if (!typeLinearStr)
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -13,16 +13,18 @@
 #include "gfxUtils.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Unused.h"
 
 #include "nsCOMPtr.h"
 #include "nsFontMetrics.h"
 #include "nsIImageLoadingContent.h"
 #include "nsString.h"
@@ -207,17 +209,17 @@ nsImageFrame::DestroyFrom(nsIFrame* aDes
     if (imageLoader) {
       // Notify our image loading content that we are going away so it can
       // deregister with our refresh driver.
       imageLoader->FrameDestroyed(this);
 
       imageLoader->RemoveNativeObserver(mListener);
     }
 
-    reinterpret_cast<nsImageListener*>(mListener.get())->SetFrame(nullptr);
+    mListener->SetFrame(nullptr);
   }
 
   mListener = nullptr;
 
   // If we were displaying an icon, take ourselves off the list
   if (mDisplayingIcon)
     gIconLoad->RemoveIconObserver(this);
 
@@ -256,62 +258,84 @@ void
 nsImageFrame::Init(nsIContent*       aContent,
                    nsContainerFrame* aParent,
                    nsIFrame*         aPrevInFlow)
 {
   nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
 
   mListener = new nsImageListener(this);
 
+  if (!gIconLoad)
+    LoadIcons(PresContext());
+
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
   if (!imageLoader) {
     MOZ_CRASH("Why do we have an nsImageFrame here at all?");
   }
 
   imageLoader->AddNativeObserver(mListener);
 
-  nsPresContext *aPresContext = PresContext();
-
-  if (!gIconLoad)
-    LoadIcons(aPresContext);
-
-  // We have a PresContext now, so we need to notify the image content node
-  // that it can register images.
+  // We have a PresContext now, so we need to notify the image content node that
+  // it can register images.
   imageLoader->FrameCreated(this);
 
-  // Give image loads associated with an image frame a small priority boost!
-  nsCOMPtr<imgIRequest> currentRequest;
-  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                          getter_AddRefs(currentRequest));
-
-  if (currentRequest) {
+  // Give image loads associated with an image frame a small priority boost.
+  if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
     uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
 
     // Increase load priority further if intrinsic size might be important for layout.
     if (!HaveSpecifiedSize(StylePosition())) {
       categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
     }
 
     currentRequest->BoostPriority(categoryToBoostPriority);
   }
 }
 
+static void
+ScaleIntrinsicSizeForDensity(nsIContent& aContent, nsSize& aSize)
+{
+  auto* image = HTMLImageElement::FromNode(aContent);
+  if (!image) {
+    return;
+  }
+
+  ResponsiveImageSelector* selector = image->GetResponsiveImageSelector();
+  if (!selector) {
+    return;
+  }
+
+  double density = selector->GetSelectedImageDensity();
+  MOZ_ASSERT(density > 0.0);
+  if (density == 1.0) {
+    return;
+  }
+
+  if (aSize.width != -1) {
+    aSize.width = NSToCoordRound(double(aSize.width) / density);
+  }
+  if (aSize.height != -1) {
+    aSize.height = NSToCoordRound(double(aSize.height) / density);
+  }
+}
+
 bool
 nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage)
 {
   MOZ_ASSERT(aImage, "null image");
   if (!aImage)
     return false;
 
   IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
   mIntrinsicSize = IntrinsicSize();
 
   // Set intrinsic size to match aImage's reported intrinsic width & height.
   nsSize intrinsicSize;
   if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) {
+    ScaleIntrinsicSizeForDensity(*mContent, intrinsicSize);
     // If the image has no intrinsic width, intrinsicSize.width will be -1, and
     // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None.
     // Otherwise we use intrinsicSize.width. Height works the same way.
     if (intrinsicSize.width != -1)
       mIntrinsicSize.width.SetCoordValue(intrinsicSize.width);
     if (intrinsicSize.height != -1)
       mIntrinsicSize.height.SetCoordValue(intrinsicSize.height);
   } else {
@@ -579,22 +603,18 @@ nsImageFrame::OnSizeAvailable(imgIReques
   }
 
   MarkNeedsDisplayItemRebuild();
 
   if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) {
     // Now we need to reflow if we have an unconstrained size and have
     // already gotten the initial reflow
     if (!(mState & IMAGE_SIZECONSTRAINED)) {
-      nsIPresShell *presShell = presContext->GetPresShell();
-      NS_ASSERTION(presShell, "No PresShell.");
-      if (presShell) {
-        presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+      PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
                                     NS_FRAME_IS_DIRTY);
-      }
     } else {
       // We've already gotten the initial reflow, and our size hasn't changed,
       // so we're ready to request a decode.
       MaybeDecodeForPredictedSize();
     }
 
     mPrevImage = nullptr;
   }
@@ -660,33 +680,41 @@ nsImageFrame::InvalidateSelf(const nsInt
                     aLayerInvalidRect,
                     aFrameInvalidRect);
   }
 }
 
 nsresult
 nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
 {
-  // Check what request type we're dealing with
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  NS_ASSERTION(imageLoader, "Who's notifying us??");
-  int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST;
-  imageLoader->GetRequestType(aRequest, &loadType);
-  if (loadType != nsIImageLoadingContent::CURRENT_REQUEST &&
-      loadType != nsIImageLoadingContent::PENDING_REQUEST) {
-    return NS_ERROR_FAILURE;
-  }
-
   NotifyNewCurrentRequest(aRequest, aStatus);
   return NS_OK;
 }
 
 void
-nsImageFrame::NotifyNewCurrentRequest(imgIRequest *aRequest,
-                                      nsresult aStatus)
+nsImageFrame::ResponsiveContentDensityChanged()
+{
+  if (!(mState & IMAGE_GOTINITIALREFLOW)) {
+    return;
+  }
+
+  if (!mImage) {
+    return;
+  }
+
+  if (!UpdateIntrinsicSize(mImage) && !UpdateIntrinsicRatio(mImage)) {
+    return;
+  }
+
+  PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+                                NS_FRAME_IS_DIRTY);
+}
+
+void
+nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest, nsresult aStatus)
 {
   nsCOMPtr<imgIContainer> image;
   aRequest->GetImage(getter_AddRefs(image));
   NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?");
 
   // May have to switch sizes here!
   bool intrinsicSizeChanged = true;
   if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) {
@@ -703,21 +731,18 @@ nsImageFrame::NotifyNewCurrentRequest(im
     mIntrinsicSize.width.SetCoordValue(0);
     mIntrinsicSize.height.SetCoordValue(0);
     mIntrinsicRatio.SizeTo(0, 0);
   }
 
   if (mState & IMAGE_GOTINITIALREFLOW) { // do nothing if we haven't gotten the initial reflow yet
     if (intrinsicSizeChanged) {
       if (!(mState & IMAGE_SIZECONSTRAINED)) {
-        nsIPresShell *presShell = PresContext()->GetPresShell();
-        if (presShell) {
-          presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+        PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
                                       NS_FRAME_IS_DIRTY);
-        }
       } else {
         // We've already gotten the initial reflow, and our size hasn't changed,
         // so we're ready to request a decode.
         MaybeDecodeForPredictedSize();
       }
 
       mPrevImage = nullptr;
     }
@@ -808,36 +833,32 @@ nsImageFrame::EnsureIntrinsicSizeAndRati
     if (mImage) {
       UpdateIntrinsicSize(mImage);
       UpdateIntrinsicRatio(mImage);
     } else {
       // image request is null or image size not known, probably an
       // invalid image specified
       if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
         bool imageInvalid = false;
+
         // check for broken images. valid null images (eg. img src="") are
         // not considered broken because they have no image requests
-        nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-        if (imageLoader) {
-          nsCOMPtr<imgIRequest> currentRequest;
-          imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                                  getter_AddRefs(currentRequest));
-          if (currentRequest) {
-            uint32_t imageStatus;
-            imageInvalid =
-              NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
-              (imageStatus & imgIRequest::STATUS_ERROR);
-          } else {
-            // check if images are user-disabled (or blocked for other
-            // reasons)
-            int16_t imageBlockingStatus;
-            imageLoader->GetImageBlockingStatus(&imageBlockingStatus);
-            imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
-          }
+        if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+          uint32_t imageStatus;
+          imageInvalid =
+            NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
+            (imageStatus & imgIRequest::STATUS_ERROR);
+        } else if (nsCOMPtr<nsIImageLoadingContent> loader = do_QueryInterface(mContent)) {
+          // check if images are user-disabled (or blocked for other
+          // reasons)
+          int16_t imageBlockingStatus;
+          loader->GetImageBlockingStatus(&imageBlockingStatus);
+          imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
         }
+
         // invalid image specified. make the image big enough for the "broken" icon
         if (imageInvalid) {
           nscoord edgeLengthToUse =
             nsPresContext::CSSPixelsToAppUnits(
               ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
           mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
           mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
           mIntrinsicRatio.SizeTo(1, 1);
@@ -854,56 +875,18 @@ nsImageFrame::ComputeSize(gfxContext *aR
                           const LogicalSize& aCBSize,
                           nscoord aAvailableISize,
                           const LogicalSize& aMargin,
                           const LogicalSize& aBorder,
                           const LogicalSize& aPadding,
                           ComputeSizeFlags aFlags)
 {
   EnsureIntrinsicSizeAndRatio();
-
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  NS_ASSERTION(imageLoader, "No content node??");
-  mozilla::IntrinsicSize intrinsicSize(mIntrinsicSize);
-
-  // XXX(seth): We may sometimes find ourselves in the situation where we have
-  // mImage, but imageLoader's current request does not have a size yet.
-  // This can happen when we load an image speculatively from cache, it fails
-  // to validate, and the new image load hasn't fired SIZE_AVAILABLE yet. In
-  // this situation we should always use mIntrinsicSize, because
-  // GetNaturalWidth/Height will return 0, so we check CurrentRequestHasSize()
-  // below. See bug 1019840. We will fix this in bug 1141395.
-
-  // Content may override our default dimensions. This is termed as overriding
-  // the intrinsic size by the spec, but all other consumers of mIntrinsic*
-  // values are being used to refer to the real/true size of the image data.
-  if (imageLoader && imageLoader->CurrentRequestHasSize() && mImage &&
-      intrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
-      intrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
-    uint32_t width;
-    uint32_t height;
-    if (NS_SUCCEEDED(imageLoader->GetNaturalWidth(&width)) &&
-        NS_SUCCEEDED(imageLoader->GetNaturalHeight(&height))) {
-      nscoord appWidth = nsPresContext::CSSPixelsToAppUnits((int32_t)width);
-      nscoord appHeight = nsPresContext::CSSPixelsToAppUnits((int32_t)height);
-      // If this image is rotated, we'll need to transpose the natural
-      // width/height.
-      bool coordFlip;
-      if (StyleVisibility()->mImageOrientation.IsFromImage()) {
-        coordFlip = mImage->GetOrientation().SwapsWidthAndHeight();
-      } else {
-        coordFlip = StyleVisibility()->mImageOrientation.SwapsWidthAndHeight();
-      }
-      intrinsicSize.width.SetCoordValue(coordFlip ? appHeight : appWidth);
-      intrinsicSize.height.SetCoordValue(coordFlip ? appWidth : appHeight);
-    }
-  }
-
   return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
-                                            intrinsicSize, mIntrinsicRatio,
+                                            mIntrinsicSize, mIntrinsicRatio,
                                             aCBSize, aMargin, aBorder, aPadding,
                                             aFlags);
 }
 
 // XXXdholbert This function's clients should probably just be calling
 // GetContentRectRelativeToSelf() directly.
 nsRect
 nsImageFrame::GetInnerArea() const
@@ -1017,26 +1000,20 @@ nsImageFrame::Reflow(nsPresContext*     
     aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top;
     aMetrics.Height() = std::max(0, aMetrics.Height());
   }
 
 
   // we have to split images if we are:
   //  in Paginated mode, we need to have a constrained height, and have a height larger than our available height
   uint32_t loadStatus = imgIRequest::STATUS_NONE;
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  NS_ASSERTION(imageLoader, "No content node??");
-  if (imageLoader) {
-    nsCOMPtr<imgIRequest> currentRequest;
-    imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                            getter_AddRefs(currentRequest));
-    if (currentRequest) {
-      currentRequest->GetImageStatus(&loadStatus);
-    }
+  if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+    currentRequest->GetImageStatus(&loadStatus);
   }
+
   if (aPresContext->IsPaginated() &&
       ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & IMAGE_SIZECONSTRAINED)) &&
       NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
       aMetrics.Height() > aReflowInput.AvailableHeight()) {
     // our desired height was greater than 0, so to avoid infinite
     // splitting, use 1 pixel as the min
     aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), aReflowInput.AvailableHeight());
     aStatus.SetIncomplete();
@@ -1805,16 +1782,30 @@ nsImageFrame::PaintImage(gfxContext& aRe
     mPrevImage = aImage;
   } else if (result == ImgDrawResult::BAD_IMAGE) {
     mPrevImage = nullptr;
   }
 
   return result;
 }
 
+already_AddRefed<imgIRequest>
+nsImageFrame::GetCurrentRequest() const
+{
+  nsCOMPtr<imgIRequest> request;
+
+  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+  MOZ_ASSERT(imageLoader);
+
+  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                          getter_AddRefs(request));
+
+  return request.forget();
+}
+
 void
 nsImageFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
     return;
 
   DisplayBorderBackgroundOutline(aBuilder, aLists);
@@ -1822,28 +1813,21 @@ nsImageFrame::BuildDisplayList(nsDisplay
   uint32_t clipFlags =
     nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
     0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
 
   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
     clip(aBuilder, this, clipFlags);
 
   if (mComputedSize.width != 0 && mComputedSize.height != 0) {
-    nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-    NS_ASSERTION(imageLoader, "Not an image loading content?");
-
-    nsCOMPtr<imgIRequest> currentRequest;
-    if (imageLoader) {
-      imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                              getter_AddRefs(currentRequest));
-    }
-
     EventStates contentState = mContent->AsElement()->State();
     bool imageOK = IMAGE_OK(contentState, true);
 
+    nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+
     // XXX(seth): The SizeIsAvailable check here should not be necessary - the
     // intention is that a non-null mImage means we have a size, but there is
     // currently some code that violates this invariant.
     if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) {
       // No image yet, or image load failed. Draw the alt-text and an icon
       // indicating the status
       aLists.Content()->AppendToTop(
         MakeDisplayItem<nsDisplayAltFeedback>(aBuilder, this));
@@ -2170,28 +2154,22 @@ nsImageFrame::GetFrameName(nsAString& aR
 
 void
 nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
 {
   nsCString str;
   ListGeneric(str, aPrefix, aFlags);
 
   // output the img src url
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  if (imageLoader) {
-    nsCOMPtr<imgIRequest> currentRequest;
-    imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                            getter_AddRefs(currentRequest));
-    if (currentRequest) {
-      nsCOMPtr<nsIURI> uri;
-      currentRequest->GetURI(getter_AddRefs(uri));
-      nsAutoCString uristr;
-      uri->GetAsciiSpec(uristr);
-      str += nsPrintfCString(" [src=%s]", uristr.get());
-    }
+  if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+    nsCOMPtr<nsIURI> uri;
+    currentRequest->GetURI(getter_AddRefs(uri));
+    nsAutoCString uristr;
+    uri->GetAsciiSpec(uristr);
+    str += nsPrintfCString(" [src=%s]", uristr.get());
   }
   fprintf_stderr(out, "%s\n", str.get());
 }
 #endif
 
 nsIFrame::LogicalSides
 nsImageFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
 {
@@ -2442,24 +2420,22 @@ nsImageFrame::IconLoad::Notify(imgIReque
     frame->InvalidateFrame();
   }
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
 
-nsImageListener::nsImageListener(nsImageFrame *aFrame) :
-  mFrame(aFrame)
+nsImageListener::nsImageListener(nsImageFrame* aFrame)
+  : mFrame(aFrame)
 {
 }
 
-nsImageListener::~nsImageListener()
-{
-}
+nsImageListener::~nsImageListener() = default;
 
 NS_IMETHODIMP
 nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
   return mFrame->Notify(aRequest, aType, aData);
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -100,16 +100,18 @@ public:
                              nsIFrame::Cursor& aCursor) override;
   virtual nsresult AttributeChanged(int32_t aNameSpaceID,
                                     nsAtom* aAttribute,
                                     int32_t aModType) override;
 
   void OnVisibilityChange(Visibility aNewVisibility,
                           const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override;
 
+  void ResponsiveContentDensityChanged();
+
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
     return nsAtomicContainerFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
@@ -133,16 +135,17 @@ public:
   static void ReleaseGlobals() {
     if (gIconLoad) {
       gIconLoad->Shutdown();
       gIconLoad = nullptr;
     }
     NS_IF_RELEASE(sIOService);
   }
 
+  already_AddRefed<imgIRequest> GetCurrentRequest() const;
   nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
 
   /**
    * Function to test whether aContent, which has aComputedStyle as its style,
    * should get an image frame.  Note that this method is only used by the
    * frame constructor; it's only here because it uses gIconLoad for now.
    */
   static bool ShouldCreateImageFrameFor(mozilla::dom::Element* aElement,
@@ -323,17 +326,17 @@ private:
    * @param aFrameInvalidRect The area to invalidate in frame space. If null, the
    *                          entire frame will be invalidated.
    */
   void InvalidateSelf(const nsIntRect* aLayerInvalidRect,
                       const nsRect* aFrameInvalidRect);
 
   RefPtr<nsImageMap> mImageMap;
 
-  nsCOMPtr<imgINotificationObserver> mListener;
+  RefPtr<nsImageListener> mListener;
 
   nsCOMPtr<imgIContainer> mImage;
   nsCOMPtr<imgIContainer> mPrevImage;
   nsSize mComputedSize;
   mozilla::IntrinsicSize mIntrinsicSize;
   nsSize mIntrinsicRatio;
 
   bool mDisplayingIcon;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/image/image-srcset-isize-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html reftest-zoom="2">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.image-container {
+  display: inline;
+}
+.content-container {
+  display: inline-block;
+}
+.flex-container {
+  align-items: center;
+  display: flex;
+}
+</style>
+<div class="flex-container">
+  <div class="image-container">
+    <img src="200.png" width="100">
+  </div>
+  <div class="content-container">
+    Should see me right by the side of the image.
+  </div>
+</div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/image/image-srcset-isize.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html reftest-zoom="2" class="reftest-wait">
+<title>CSS Test: srcset intrinsic size isn't confused</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<style>
+.image-container {
+  display: inline;
+}
+.content-container {
+  display: inline-block;
+}
+.flex-container {
+  align-items: center;
+  display: flex;
+}
+</style>
+<script>
+  // reftest-zoom is only applied at onload, so ensure the source-selection
+  // has happened after that
+  function clearWait() {
+    document.documentElement.classList.remove("reftest-wait");
+  }
+  window.addEventListener("load", function() {
+    setTimeout(function() {
+      var img = document.querySelector("img");
+      img.onload = clearWait;
+      img.onerror = clearWait;
+      img.src = img.src;
+    }, 0);
+  });
+</script>
+<div class="flex-container">
+  <div class="image-container">
+    <img srcset="50.png 0.5x, 100.png 1x, 200.png 2x, 300.png 3x, 400.png">
+  </div>
+  <div class="content-container">
+    Should see me right by the side of the image.
+  </div>
+</div>
+</html>
--- a/layout/reftests/image/reftest.list
+++ b/layout/reftests/image/reftest.list
@@ -113,16 +113,17 @@ fuzzy(1,1) == image-orientation-backgrou
 == image-srcset-basic-selection-width-0.5x.html image-srcset-basic-selection-width-0.5x-ref.html
 == image-srcset-basic-selection-width-10x.html image-srcset-basic-selection-width-10x-ref.html
 == image-srcset-basic-selection-width-2x.html image-srcset-basic-selection-width-2x-ref.html
 == image-srcset-basic-selection-width-1x.html image-srcset-basic-selection-width-1x-ref.html
 == image-srcset-default-2x.html image-srcset-default-2x-ref.html
 == image-srcset-default-1x.html image-srcset-default-1x-ref.html
 == image-srcset-default-src-2x.html image-srcset-default-src-2x-ref.html
 == image-srcset-default-src-1x.html image-srcset-default-src-1x-ref.html
+== image-srcset-isize.html image-srcset-isize-ref.html
 == image-srcset-orientation-2x.html image-srcset-orientation-2x-ref.html
 == image-srcset-orientation-1x.html image-srcset-orientation-1x-ref.html
 == image-srcset-svg-3x.html image-srcset-svg-3x-ref.html
 == image-srcset-svg-2x.html image-srcset-svg-2x-ref.html
 == image-srcset-svg-1x.html image-srcset-svg-1x-ref.html
 == image-srcset-svg-default-2x.html image-srcset-svg-default-2x-ref.html
 == image-srcset-svg-default-1x.html image-srcset-svg-default-1x-ref.html
 
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -110,17 +110,17 @@ ThreadSafeGetDefaultFontHelper(const nsP
     AutoReadLock guard(*sServoFFILock);
     retval = aPresContext->GetDefaultFont(aGenericId, aLanguage, &needsCache);
   }
   if (!needsCache) {
     return retval;
   }
   {
     AutoWriteLock guard(*sServoFFILock);
-  retval = aPresContext->GetDefaultFont(aGenericId, aLanguage, nullptr);
+    retval = aPresContext->GetDefaultFont(aGenericId, aLanguage, nullptr);
   }
   return retval;
 }
 
 void
 AssertIsMainThreadOrServoLangFontPrefsCacheLocked()
 {
   MOZ_ASSERT(NS_IsMainThread() || sServoFFILock->LockedForWritingByCurrentThread());
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -193,23 +193,27 @@ union PrefValue {
     }
     *this = aNewValue;
   }
 
   void Clear(PrefType aType)
   {
     if (aType == PrefType::String) {
       free(const_cast<char*>(mStringVal));
-      mStringVal = nullptr;
     }
-  }
-
-  void Replace(PrefType aOldType, PrefType aNewType, PrefValue aNewValue)
+
+    // Zero the entire value (regardless of type) via mStringVal.
+    mStringVal = nullptr;
+  }
+
+  void Replace(bool aHasValue, PrefType aOldType, PrefType aNewType, PrefValue aNewValue)
   {
-    Clear(aOldType);
+    if (aHasValue) {
+      Clear(aOldType);
+    }
     Init(aNewType, aNewValue);
   }
 
   void ToDomPrefValue(PrefType aType, dom::PrefValue* aDomValue)
   {
     switch (aType) {
       case PrefType::String:
         *aDomValue = nsDependentCString(mStringVal);
@@ -592,32 +596,32 @@ public:
 
     const dom::MaybePrefValue& defaultValue = aDomPref.defaultValue();
     bool defaultValueChanged = false;
     if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) {
       PrefValue value;
       PrefType type = value.FromDomPrefValue(defaultValue.get_PrefValue());
       if (!ValueMatches(PrefValueKind::Default, type, value)) {
         // Type() is PrefType::None if it's a newly added pref. This is ok.
-        mDefaultValue.Replace(Type(), type, value);
+        mDefaultValue.Replace(mHasDefaultValue, Type(), type, value);
         SetType(type);
         mHasDefaultValue = true;
         defaultValueChanged = true;
       }
     }
     // Note: we never clear a default value.
 
     const dom::MaybePrefValue& userValue = aDomPref.userValue();
     bool userValueChanged = false;
     if (userValue.type() == dom::MaybePrefValue::TPrefValue) {
       PrefValue value;
       PrefType type = value.FromDomPrefValue(userValue.get_PrefValue());
       if (!ValueMatches(PrefValueKind::User, type, value)) {
         // Type() is PrefType::None if it's a newly added pref. This is ok.
-        mUserValue.Replace(Type(), type, value);
+        mUserValue.Replace(mHasUserValue, Type(), type, value);
         SetType(type);
         mHasUserValue = true;
         userValueChanged = true;
       }
     } else if (mHasUserValue) {
       ClearUserValue();
       userValueChanged = true;
     }
@@ -662,21 +666,17 @@ private:
            (aKind == PrefValueKind::Default
               ? mHasDefaultValue && mDefaultValue.Equals(aType, aValue)
               : mHasUserValue && mUserValue.Equals(aType, aValue));
   }
 
 public:
   void ClearUserValue()
   {
-    if (Type() == PrefType::String) {
-      free(const_cast<char*>(mUserValue.mStringVal));
-      mUserValue.mStringVal = nullptr;
-    }
-
+    mUserValue.Clear(Type());
     mHasUserValue = false;
     mHasChangedSinceInit = true;
   }
 
   nsresult SetDefaultValue(PrefType aType,
                            PrefValue aValue,
                            bool aIsSticky,
                            bool aIsLocked,
@@ -690,17 +690,17 @@ public:
 
     // Should we set the default value? Only if the pref is not locked, and
     // doing so would change the default value.
     if (!IsLocked()) {
       if (aIsLocked) {
         SetIsLocked(true);
       }
       if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
-        mDefaultValue.Replace(Type(), aType, aValue);
+        mDefaultValue.Replace(mHasDefaultValue, Type(), aType, aValue);
         mHasDefaultValue = true;
         if (!aFromInit) {
           mHasChangedSinceInit = true;
         }
         if (aIsSticky) {
           mIsSticky = true;
         }
         if (!mHasUserValue) {
@@ -734,17 +734,17 @@ public:
         if (!IsLocked()) {
           *aValueChanged = true;
         }
       }
 
       // Otherwise, should we set the user value? Only if doing so would
       // change the user value.
     } else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
-      mUserValue.Replace(Type(), aType, aValue);
+      mUserValue.Replace(mHasUserValue, Type(), aType, aValue);
       SetType(aType); // needed because we may have changed the type
       mHasUserValue = true;
       if (!aFromInit) {
         mHasChangedSinceInit = true;
       }
       if (!IsLocked()) {
         *aValueChanged = true;
       }
--- a/servo/components/style/values/computed/font.rs
+++ b/servo/components/style/values/computed/font.rs
@@ -469,17 +469,17 @@ impl SingleFontFamily {
 
         match family.mType {
             FontFamilyType::eFamily_sans_serif => SingleFontFamily::Generic(atom!("sans-serif")),
             FontFamilyType::eFamily_serif => SingleFontFamily::Generic(atom!("serif")),
             FontFamilyType::eFamily_monospace => SingleFontFamily::Generic(atom!("monospace")),
             FontFamilyType::eFamily_cursive => SingleFontFamily::Generic(atom!("cursive")),
             FontFamilyType::eFamily_fantasy => SingleFontFamily::Generic(atom!("fantasy")),
             FontFamilyType::eFamily_moz_fixed => {
-                SingleFontFamily::Generic(Atom::from("-moz-fixed"))
+                SingleFontFamily::Generic(atom!("-moz-fixed"))
             },
             FontFamilyType::eFamily_named => {
                 let name = Atom::from(&*family.mName);
                 SingleFontFamily::FamilyName(FamilyName {
                     name,
                     syntax: FamilyNameSyntax::Identifiers,
                 })
             },
--- a/testing/mozbase/mozlog/mozlog/formatters/machformatter.py
+++ b/testing/mozbase/mozlog/mozlog/formatters/machformatter.py
@@ -238,17 +238,17 @@ class MachFormatter(base.BaseFormatter):
 
         if data["min_expected"] != data["max_expected"]:
             expected = "%i to %i" % (data["min_expected"],
                                      data["max_expected"])
         else:
             expected = "%i" % data["min_expected"]
 
         action = self.term.red("ASSERT")
-        return "%s: Assertion count %i, expected %i assertions\n" % (
+        return "%s: Assertion count %i, expected %s assertions\n" % (
                 action, data["count"], expected)
 
     def process_output(self, data):
         rv = []
 
         pid = data['process']
         if pid.isdigit():
             pid = 'pid:%s' % pid
--- a/testing/web-platform/mach_commands.py
+++ b/testing/web-platform/mach_commands.py
@@ -353,17 +353,18 @@ class MachCommands(MachCommandBase):
     def run_wpt(self, **params):
         return self.run_web_platform_tests(**params)
 
     @Command("web-platform-tests-update",
              category="testing",
              parser=create_parser_update)
     def update_web_platform_tests(self, **params):
         self.setup()
-        self.virtualenv_manager.install_pip_package('html5lib==0.99')
+        self.virtualenv_manager.install_pip_package('html5lib==1.0.1')
+        self.virtualenv_manager.install_pip_package('ujson')
         self.virtualenv_manager.install_pip_package('requests')
         wpt_updater = self._spawn(WebPlatformTestsUpdater)
         return wpt_updater.run_update(**params)
 
     @Command("wpt-update",
              category="testing",
              parser=create_parser_update)
     def update_wpt(self, **params):
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -182144,16 +182144,28 @@
       [
        "/html/semantics/embedded-content/the-img-element/document-base-url-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html": [
+    [
+     "/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html",
+     [
+      [
+       "/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/semantics/embedded-content/the-video-element/video_content_image.htm": [
     [
      "/html/semantics/embedded-content/the-video-element/video_content_image.htm",
      [
       [
        "/html/semantics/embedded-content/the-video-element/video_content-ref.htm",
        "=="
       ]
@@ -286133,16 +286145,21 @@
      {}
     ]
    ],
    "html/semantics/embedded-content/the-img-element/resources/cat.jpg": [
     [
      {}
     ]
    ],
+   "html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html": [
     [
      {}
     ]
    ],
    "html/semantics/embedded-content/the-img-element/srcset/common.js": [
     [
      {}
@@ -577595,16 +577612,24 @@
   "html/semantics/embedded-content/the-img-element/resources/cat.jpg": [
    "d8bdb2208a32d2200afb173368c38826fede8476",
    "support"
   ],
   "html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute.html": [
    "22040d8543a29c1e4f1708017096a5f9de478549",
    "testharness"
   ],
+  "html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html": [
+   "d2a087e2d55f7d0a2de3a0008c149932151a69f0",
+   "support"
+  ],
+  "html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html": [
+   "0256768b16722bf4a8e6e1fb41b48e321509610f",
+   "reftest"
+  ],
   "html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html": [
    "47e56d828d8c366a95d0ea77571a1dbcaaca3164",
    "support"
   ],
   "html/semantics/embedded-content/the-img-element/srcset/common.js": [
    "1928faeb6e177e80b56d049ff81439512538fc36",
    "support"
   ],
--- a/testing/web-platform/meta/css/CSS2/ui/outline-applies-to-005.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-applies-to-005.xht.ini
@@ -1,3 +1,4 @@
 [outline-applies-to-005.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-applies-to-006.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-applies-to-006.xht.ini
@@ -1,3 +1,4 @@
 [outline-applies-to-006.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-color-applies-to-005.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-color-applies-to-005.xht.ini
@@ -1,3 +1,4 @@
 [outline-color-applies-to-005.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-color-applies-to-006.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-color-applies-to-006.xht.ini
@@ -1,3 +1,4 @@
 [outline-color-applies-to-006.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-style-applies-to-005.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-style-applies-to-005.xht.ini
@@ -1,3 +1,4 @@
 [outline-style-applies-to-005.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-style-applies-to-006.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-style-applies-to-006.xht.ini
@@ -1,3 +1,4 @@
 [outline-style-applies-to-006.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-width-applies-to-005.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-width-applies-to-005.xht.ini
@@ -1,3 +1,4 @@
 [outline-width-applies-to-005.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/css/CSS2/ui/outline-width-applies-to-006.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/ui/outline-width-applies-to-006.xht.ini
@@ -1,3 +1,4 @@
 [outline-width-applies-to-006.xht]
   expected:
     if webrender: FAIL
+  max-asserts: 2
--- a/testing/web-platform/meta/custom-elements/pseudo-class-defined.html.ini
+++ b/testing/web-platform/meta/custom-elements/pseudo-class-defined.html.ini
@@ -1,9 +1,10 @@
 [pseudo-class-defined.html]
+  max-asserts: 4
   [<div> should be :defined]
     expected: FAIL
 
   [createElement("div") should be :defined]
     expected: FAIL
 
   [createElementNS("http://www.w3.org/1999/xhtml", "div") should be :defined]
     expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/custom-elements/upgrading.html.ini
@@ -0,0 +1,2 @@
+[upgrading.html]
+  max-asserts: 4
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/domparsing/insert_adjacent_html-xhtml.xhtml.ini
@@ -0,0 +1,2 @@
+[insert_adjacent_html-xhtml.xhtml]
+  max-asserts: 2
--- a/testing/web-platform/meta/fetch/api/request/request-consume-empty.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-consume-empty.html.ini
@@ -1,9 +1,10 @@
 [request-consume-empty.html]
+  max-asserts: 2
   [Consume request's body as text]
     expected: FAIL
 
   [Consume request's body as blob]
     expected: FAIL
 
   [Consume request's body as arrayBuffer]
     expected: FAIL
--- a/testing/web-platform/meta/fetch/api/response/response-consume-empty.html.ini
+++ b/testing/web-platform/meta/fetch/api/response/response-consume-empty.html.ini
@@ -1,9 +1,10 @@
 [response-consume-empty.html]
+  max-asserts: 2
   [Consume response's body as text]
     expected: FAIL
 
   [Consume response's body as blob]
     expected: FAIL
 
   [Consume response's body as arrayBuffer]
     expected: FAIL
--- a/testing/web-platform/meta/html/browsers/browsing-the-web/unloading-documents/004.html.ini
+++ b/testing/web-platform/meta/html/browsers/browsing-the-web/unloading-documents/004.html.ini
@@ -1,4 +1,5 @@
 [004.html]
+  max-asserts: 2
   [document.open in beforeunload with button]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/dom/documents/dom-tree-accessors/document.title-09.html.ini
@@ -0,0 +1,2 @@
+[document.title-09.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/dom/reflection-forms.html.ini
+++ b/testing/web-platform/meta/html/dom/reflection-forms.html.ini
@@ -1,10 +1,11 @@
 [reflection-forms.html]
   prefs: [dom.forms.inputmode:true]
+  max-asserts: 3
   [form.tabIndex: setAttribute() to object "3" followed by getAttribute()]
     expected: FAIL
 
   [form.tabIndex: setAttribute() to object "3" followed by IDL get]
     expected: FAIL
 
   [form.method: setAttribute() to "dialog" followed by IDL get]
     expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/dom/reflection-tabular.html.ini
@@ -0,0 +1,2 @@
+[reflection-tabular.html]
+  max-asserts: 7
--- a/testing/web-platform/meta/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference.html.ini
@@ -1,9 +1,10 @@
 [hash-name-reference.html]
+  max-asserts: 52
   [HTML (standards) IMG usemap="#hash-name"]
     expected: FAIL
 
   [HTML (standards) OBJECT usemap="#hash-name"]
     expected: FAIL
 
   [HTML (standards) IMG usemap="#hash-id"]
     expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html.ini
@@ -0,0 +1,2 @@
+[constructor.html]
+  max-asserts: 103
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/pause-move-to-other-document.html.ini
@@ -0,0 +1,2 @@
+[pause-move-to-other-document.html]
+  max-asserts: 103
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html.ini
@@ -0,0 +1,2 @@
+[seek-to-currentTime.html]
+  max-asserts: 102
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/__dir__.ini
@@ -0,0 +1,1 @@
+max-asserts: 3
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-button-element/button-type.html.ini
@@ -0,0 +1,2 @@
+[button-type.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-button-element/button-validationmessage.html.ini
@@ -0,0 +1,2 @@
+[button-validationmessage.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-button-element/button-validity.html.ini
@@ -0,0 +1,2 @@
+[button-validity.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-button-element/button-willvalidate.html.ini
@@ -0,0 +1,2 @@
+[button-willvalidate.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/__dir__.ini
@@ -0,0 +1,1 @@
+max-asserts: 2
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-action-reflection-with-base-url.html.ini
@@ -0,0 +1,2 @@
+[form-action-reflection-with-base-url.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-action-reflection.html.ini
@@ -0,0 +1,2 @@
+[form-action-reflection.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-form-element/form-autocomplete.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-autocomplete.html.ini
@@ -1,10 +1,11 @@
 [form-autocomplete.html]
   prefs: [dom.forms.autocomplete.formautofill:true]
+  max-asserts: 3
   [form autocomplete attribute missing]
     expected: FAIL
 
   [form autocomplete attribute on]
     expected: FAIL
 
   [form autocomplete attribute off]
     expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-checkvalidity.html.ini
@@ -0,0 +1,2 @@
+[form-checkvalidity.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-form-element/form-elements-filter.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-elements-filter.html.ini
@@ -1,2 +1,3 @@
 [form-elements-filter.html]
   prefs: [dom.webcomponents.shadowdom.enabled:true]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-elements-matches.html.ini
@@ -0,0 +1,2 @@
+[form-elements-matches.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-form-element/form-elements-nameditem-01.html.ini
@@ -0,0 +1,2 @@
+[form-elements-nameditem-01.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/__dir__.ini
@@ -0,0 +1,2 @@
+max-asserts: 2
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-stepup.html.ini
@@ -0,0 +1,2 @@
+[input-stepup.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-type-button.html.ini
@@ -0,0 +1,2 @@
+[input-type-button.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-type-checkbox.html.ini
@@ -0,0 +1,2 @@
+[input-type-checkbox.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-validationmessage.html.ini
@@ -0,0 +1,2 @@
+[input-validationmessage.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-validity.html.ini
@@ -0,0 +1,2 @@
+[input-validity.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-input-element/input-value-invalidstateerr.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-value-invalidstateerr.html.ini
@@ -1,4 +1,5 @@
 [input-value-invalidstateerr.html]
+  max-asserts: 3
   [Forms]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-valueasdate-invalidstateerr.html.ini
@@ -0,0 +1,2 @@
+[input-valueasdate-invalidstateerr.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-valueasnumber-invalidstateerr.html.ini
@@ -0,0 +1,2 @@
+[input-valueasnumber-invalidstateerr.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-input-element/input-width.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-width.html.ini
@@ -1,4 +1,5 @@
 [input-width.html]
+  max-asserts: 3
   [Forms]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/input-willvalidate.html.ini
@@ -0,0 +1,2 @@
+[input-willvalidate.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/maxlength.html.ini
@@ -0,0 +1,2 @@
+[maxlength.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/minlength.html.ini
@@ -0,0 +1,2 @@
+[minlength.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-input-element/month.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/month.html.ini
@@ -1,9 +1,10 @@
 [month.html]
+  max-asserts: 3
   [The value attribute, if specified and not empty, must have a value that is a valid month string]
     expected: FAIL
 
   [The min attribute, if specified, must have a value that is a valid month string.]
     expected: FAIL
 
   [The max attribute, if specified, must have a value that is a valid month string]
     expected: FAIL
--- a/testing/web-platform/meta/html/semantics/forms/the-input-element/number.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/number.html.ini
@@ -1,7 +1,8 @@
 [number.html]
+  max-asserts: 2
   [value >= Number.MAX_VALUE]
     expected: FAIL
 
   [value = +1]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/pattern_attribute.html.ini
@@ -0,0 +1,2 @@
+[pattern_attribute.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/radio-groupname-case.html.ini
@@ -0,0 +1,2 @@
+[radio-groupname-case.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/reset.html.ini
@@ -0,0 +1,2 @@
+[reset.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-input-element/search_input.html.ini
@@ -0,0 +1,2 @@
+[search_input.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini
@@ -1,2 +1,3 @@
 [label-attributes.html]
   prefs: [dom.webcomponents.shadowdom.enabled:true]
+  max-asserts: 8
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html.ini
@@ -0,0 +1,2 @@
+[proxy-click-to-associated-element.html]
+  max-asserts: 22
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-meter-element/meter.html.ini
@@ -0,0 +1,2 @@
+[meter.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-add.html.ini
@@ -0,0 +1,2 @@
+[common-HTMLOptionsCollection-add.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/common-HTMLOptionsCollection-namedItem.html.ini
@@ -1,9 +1,10 @@
 [common-HTMLOptionsCollection-namedItem.html]
+  max-asserts: 3
   [return an HTMLOptionsCollection in correct order for repeated 'id' value]
     expected: FAIL
 
   [return an HTMLOptionsCollection in correct order for repeated 'name' value]
     expected: FAIL
 
   [return an HTMLOptionsCollection in correct order for repeated mixed value]
     expected: FAIL
--- a/testing/web-platform/meta/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/common-HTMLOptionsCollection.html.ini
@@ -1,4 +1,5 @@
 [common-HTMLOptionsCollection.html]
+  max-asserts: 3
   [Changing the length adds new nodes; The number of new nodes = new length minus old length]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/select-ask-for-reset.html.ini
@@ -0,0 +1,2 @@
+[select-ask-for-reset.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/select-multiple.html.ini
@@ -0,0 +1,2 @@
+[select-multiple.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/html/semantics/forms/the-select-element/select-named-getter.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/select-named-getter.html.ini
@@ -1,4 +1,5 @@
 [select-named-getter.html]
+  max-asserts: 3
   [Empty string name]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/select-selectedOptions.html.ini
@@ -0,0 +1,2 @@
+[select-selectedOptions.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-select-element/selected-index.html.ini
@@ -0,0 +1,2 @@
+[selected-index.html]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/forms/the-textarea-element/value-defaultValue-textContent-xhtml.xhtml.ini
@@ -0,0 +1,2 @@
+[value-defaultValue-textContent-xhtml.xhtml]
+  max-asserts: 3
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/grouping-content/the-ol-element/reversed-1b.html.ini
@@ -0,0 +1,2 @@
+[reversed-1b.html]
+  max-asserts: 2
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/semantics/grouping-content/the-ul-element/grouping-ul.html.ini
@@ -0,0 +1,2 @@
+[grouping-ul.html]
+  max-asserts: 3
--- a/testing/web-platform/meta/mimesniff/mime-types/parsing.any.js.ini
+++ b/testing/web-platform/meta/mimesniff/mime-types/parsing.any.js.ini
@@ -1,9 +1,10 @@
 [parsing.any.worker.html]
+  max-asserts: 2469
   [TEXT/HTML;CHARSET=GBK (Blob/File)]
     expected: FAIL
 
   [TEXT/HTML;CHARSET=GBK (Request/Response)]
     expected: FAIL
 
   [text/html;charset=gbk( (Blob/File)]
     expected: FAIL
@@ -3695,16 +3696,17 @@
   [/ (Blob/File)]
     expected: FAIL
 
   [/ (Request/Response)]
     expected: FAIL
 
 
 [parsing.any.html]
+  max-asserts: 2469
   [TEXT/HTML;CHARSET=GBK (Blob/File)]
     expected: FAIL
 
   [TEXT/HTML;CHARSET=GBK (Request/Response)]
     expected: FAIL
 
   [text/html;charset=gbk( (Blob/File)]
     expected: FAIL
--- a/testing/web-platform/meta/quirks/percentage-height-calculation.html.ini
+++ b/testing/web-platform/meta/quirks/percentage-height-calculation.html.ini
@@ -1,9 +1,10 @@
 [percentage-height-calculation.html]
+  max-asserts: 15
   [The percentage height calculation quirk, html { margin:10px } body { display:inline } #test { height:100% }<div id=test></div>]
     expected: FAIL
 
   [The percentage height calculation quirk, <html xmlns="{html}"><head><style>#test { height:100% }</style></head><body><body><div id="test"/></body></body></html>]
     expected: FAIL
 
   [The percentage height calculation quirk, <html><head xmlns="{html}"><style>#test { height:100% }</style></head><body xmlns="{html}"><div id="test"/></body></html>]
     expected: FAIL
--- a/testing/web-platform/meta/shadow-dom/leaktests/window-frames.html.ini
+++ b/testing/web-platform/meta/shadow-dom/leaktests/window-frames.html.ini
@@ -1,4 +1,5 @@
 [window-frames.html]
+  max-asserts: 4
   [window.frames should not leak frames in Shadow DOM.]
     expected: FAIL
 
--- a/testing/web-platform/meta/streams/readable-streams/tee.dedicatedworker.html.ini
+++ b/testing/web-platform/meta/streams/readable-streams/tee.dedicatedworker.html.ini
@@ -1,9 +1,9 @@
 [tee.dedicatedworker.html]
   expected:
-    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
--- a/testing/web-platform/meta/streams/readable-streams/tee.html.ini
+++ b/testing/web-platform/meta/streams/readable-streams/tee.html.ini
@@ -1,9 +1,10 @@
 [tee.html]
   expected:
     if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
     if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
     if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
     if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
     if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
     if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
--- a/testing/web-platform/meta/streams/readable-streams/tee.serviceworker.https.html.ini
+++ b/testing/web-platform/meta/streams/readable-streams/tee.serviceworker.https.html.ini
@@ -1,9 +1,9 @@
 [tee.serviceworker.https.html]
   expected:
-    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
--- a/testing/web-platform/meta/streams/readable-streams/tee.sharedworker.html.ini
+++ b/testing/web-platform/meta/streams/readable-streams/tee.sharedworker.html.ini
@@ -1,9 +1,9 @@
 [tee.sharedworker.html]
   expected:
-    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
+    if debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/xhr/abort-event-abort.htm.ini
@@ -0,0 +1,2 @@
+[abort-event-abort.htm]
+  max-asserts: 2
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/xhr/send-data-unexpected-tostring.htm.ini
@@ -0,0 +1,2 @@
+[send-data-unexpected-tostring.htm]
+  max-asserts: 2
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>Test reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<iframe width="500" height="500" srcdoc='<!doctype html><img alt="FAIL" srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="10px">'></iframe>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Image intrinsic size specified via sizes attribute reacts properly to media changes</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="sizes-dynamic-001-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<script>
+function frameLoaded(frame) {
+  frame.width = "500";
+  let img = frame.contentDocument.querySelector('img');
+
+  // Trigger the viewport resize, which will trigger the image load task.
+  img.offsetWidth;
+
+  // Wait for the image load task to run.
+  setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+}
+</script>
+<iframe onload="frameLoaded(this)" width="200" height="500" srcdoc='<!doctype html><img srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="(min-width: 400px) 10px, 20px">'></iframe>
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/chrome.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/chrome.py
@@ -28,17 +28,18 @@ def browser_kwargs(test_type, run_info_d
             "webdriver_args": kwargs.get("webdriver_args")}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data,
+                                           **kwargs)
     executor_kwargs["close_after_done"] = True
     capabilities = dict(DesiredCapabilities.CHROME.items())
     capabilities.setdefault("chromeOptions", {})["prefs"] = {
         "profile": {
             "default_content_setting_values": {
                 "popups": 1
             }
         }
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/chrome_android.py
@@ -38,18 +38,18 @@ def executor_kwargs(test_type, server_co
     from selenium.webdriver import DesiredCapabilities
 
     # Use extend() to modify the global list in place.
     _wptserve_ports.update(set(
         server_config['ports']['http'] + server_config['ports']['https'] +
         server_config['ports']['ws'] + server_config['ports']['wss']
     ))
 
-    executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+    executor_kwargs = base_executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+                                           **kwargs)
     executor_kwargs["close_after_done"] = True
     capabilities = dict(DesiredCapabilities.CHROME.items())
     capabilities["chromeOptions"] = {}
     # required to start on mobile
     capabilities["chromeOptions"]["androidPackage"] = "com.google.android.apps.chrome"
 
     for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]:
         if kwargs[kwarg] is not None:
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/edge.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/edge.py
@@ -33,17 +33,17 @@ def browser_kwargs(test_type, run_info_d
                                                          run_info_data,
                                                          **kwargs)}
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
     executor_kwargs["close_after_done"] = True
     executor_kwargs["timeout_multiplier"] = get_timeout_multiplier(test_type,
                                                                    run_info_data,
                                                                    **kwargs)
     executor_kwargs["capabilities"] = dict(DesiredCapabilities.EDGE.items())
     return executor_kwargs
 
 def env_extras(**kwargs):
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -82,21 +82,23 @@ def browser_kwargs(test_type, run_info_d
             "stylo_threads": kwargs["stylo_threads"],
             "chaos_mode_flags": kwargs["chaos_mode_flags"],
             "config": kwargs["config"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data,
+                                           **kwargs)
     executor_kwargs["close_after_done"] = test_type != "reftest"
     executor_kwargs["timeout_multiplier"] = get_timeout_multiplier(test_type,
                                                                    run_info_data,
                                                                    **kwargs)
+    executor_kwargs["e10s"] = run_info_data["e10s"]
     capabilities = {}
     if test_type == "reftest":
         executor_kwargs["reftest_internal"] = kwargs["reftest_internal"]
         executor_kwargs["reftest_screenshot"] = kwargs["reftest_screenshot"]
     if test_type == "wdspec":
         options = {}
         if kwargs["binary"]:
             options["binary"] = kwargs["binary"]
@@ -105,16 +107,17 @@ def executor_kwargs(test_type, server_co
         options["prefs"] = {
             "network.dns.localDomains": ",".join(server_config.domains_set)
         }
         capabilities["moz:firefoxOptions"] = options
     if kwargs["certutil_binary"] is None:
         capabilities["acceptInsecureCerts"] = True
     if capabilities:
         executor_kwargs["capabilities"] = capabilities
+    executor_kwargs["debug"] = run_info_data["debug"]
     return executor_kwargs
 
 
 def env_extras(**kwargs):
     return []
 
 
 def env_options():
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/ie.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/ie.py
@@ -28,17 +28,17 @@ def executor_kwargs(test_type, server_co
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     options = {}
     options["requireWindowFocus"] = True
     capabilities = {}
     capabilities["se:ieOptions"] = options
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
     executor_kwargs["close_after_done"] = True
     executor_kwargs["capabilities"] = capabilities
     return executor_kwargs
 
 def env_extras(**kwargs):
     return []
 
 def env_options():
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/opera.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/opera.py
@@ -28,17 +28,17 @@ def browser_kwargs(test_type, run_info_d
             "webdriver_args": kwargs.get("webdriver_args")}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
     executor_kwargs["close_after_done"] = True
     capabilities = dict(DesiredCapabilities.OPERA.items())
     capabilities.setdefault("operaOptions", {})["prefs"] = {
         "profile": {
             "default_content_setting_values": {
                 "popups": 1
             }
         }
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/safari.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/safari.py
@@ -25,17 +25,17 @@ def browser_kwargs(test_type, run_info_d
             "webdriver_args": kwargs.get("webdriver_args")}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
     executor_kwargs["close_after_done"] = True
     executor_kwargs["capabilities"] = dict(DesiredCapabilities.SAFARI.items())
     if kwargs["binary"] is not None:
         raise ValueError("Safari doesn't support setting executable location")
 
     return executor_kwargs
 
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/sauce.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/sauce.py
@@ -96,17 +96,17 @@ def browser_kwargs(test_type, run_info_d
     sauce_config = get_sauce_config(**kwargs)
 
     return {"sauce_config": sauce_config}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
 
     executor_kwargs["capabilities"] = get_capabilities(**kwargs)
 
     return executor_kwargs
 
 
 def env_extras(**kwargs):
     return [SauceConnect(**kwargs)]
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/servo.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/servo.py
@@ -35,17 +35,17 @@ def browser_kwargs(test_type, run_info_d
         "user_stylesheets": kwargs.get("user_stylesheets"),
         "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
     }
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     rv = base_executor_kwargs(test_type, server_config,
-                              cache_manager, **kwargs)
+                              cache_manager, run_info_data, **kwargs)
     rv["pause_after_test"] = kwargs["pause_after_test"]
     if test_type == "wdspec":
         rv["capabilities"] = {}
     return rv
 
 
 def env_extras(**kwargs):
     return []
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/servodriver.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/servodriver.py
@@ -39,17 +39,17 @@ def browser_kwargs(test_type, run_info_d
         "binary": kwargs["binary"],
         "debug_info": kwargs["debug_info"],
         "user_stylesheets": kwargs.get("user_stylesheets"),
     }
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data, **kwargs):
     rv = base_executor_kwargs(test_type, server_config,
-                              cache_manager, **kwargs)
+                              cache_manager, run_info_data, **kwargs)
     return rv
 
 
 def env_extras(**kwargs):
     return []
 
 
 def env_options():
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/webkit.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/webkit.py
@@ -42,17 +42,17 @@ def capabilities_for_port(webkit_port, b
         return capabilities
 
     return {}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     executor_kwargs = base_executor_kwargs(test_type, server_config,
-                                           cache_manager, **kwargs)
+                                           cache_manager, run_info_data, **kwargs)
     executor_kwargs["close_after_done"] = True
     capabilities = capabilities_for_port(kwargs["webkit_port"],
                                          kwargs["binary"],
                                          kwargs.get("binary_args", []))
     executor_kwargs["capabilities"] = capabilities
     return executor_kwargs
 
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/base.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/base.py
@@ -12,17 +12,18 @@ from protocol import Protocol, BaseProto
 
 here = os.path.split(__file__)[0]
 
 # Extra timeout to use after internal test timeout at which the harness
 # should force a timeout
 extra_timeout = 5  # seconds
 
 
-def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
+def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+                    **kwargs):
     timeout_multiplier = kwargs["timeout_multiplier"]
     if timeout_multiplier is None:
         timeout_multiplier = 1
 
     executor_kwargs = {"server_config": server_config,
                        "timeout_multiplier": timeout_multiplier,
                        "debug_info": kwargs["debug_info"]}
 
@@ -56,33 +57,33 @@ class TestharnessResultConverter(object)
                      1: "ERROR",
                      2: "TIMEOUT"}
 
     test_codes = {0: "PASS",
                   1: "FAIL",
                   2: "TIMEOUT",
                   3: "NOTRUN"}
 
-    def __call__(self, test, result):
+    def __call__(self, test, result, extra=None):
         """Convert a JSON result into a (TestResult, [SubtestResult]) tuple"""
         result_url, status, message, stack, subtest_results = result
         assert result_url == test.url, ("Got results from %s, expected %s" %
-                                      (result_url, test.url))
-        harness_result = test.result_cls(self.harness_codes[status], message)
+                                        (result_url, test.url))
+        harness_result = test.result_cls(self.harness_codes[status], message, extra=extra)
         return (harness_result,
                 [test.subtest_result_cls(st_name, self.test_codes[st_status], st_message, st_stack)
                  for st_name, st_status, st_message, st_stack in subtest_results])
 
 
 testharness_result_converter = TestharnessResultConverter()
 
 
 def reftest_result_converter(self, test, result):
     return (test.result_cls(result["status"], result["message"],
-                            extra=result.get("extra")), [])
+                            extra=result.get("extra", {})), [])
 
 
 def pytest_result_converter(self, test, data):
     harness_data, subtest_data = data
 
     if subtest_data is None:
         subtest_data = []
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -20,17 +20,18 @@ from .base import (CallbackHandler,
                    TestharnessExecutor,
                    WdspecExecutor,
                    WdspecRun,
                    WebDriverProtocol,
                    extra_timeout,
                    testharness_result_converter,
                    reftest_result_converter,
                    strip_server)
-from .protocol import (BaseProtocolPart,
+from .protocol import (AssertsProtocolPart,
+                       BaseProtocolPart,
                        TestharnessProtocolPart,
                        PrefsProtocolPart,
                        Protocol,
                        StorageProtocolPart,
                        SelectorProtocolPart,
                        ClickProtocolPart,
                        SendKeysProtocolPart,
                        TestDriverProtocolPart)
@@ -288,38 +289,84 @@ class MarionetteStorageProtocolPart(Stor
             let qms = Components.classes["@mozilla.org/dom/quota-manager-service;1"]
                                 .getService(Components.interfaces.nsIQuotaManagerService);
             qms.clearStoragesForPrincipal(principal, "default", true);
             """ % url
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
 
 
+class MarionetteAssertsProtocolPart(AssertsProtocolPart):
+    def setup(self):
+        self.assert_count = {"chrome": 0, "content": 0}
+        self.chrome_assert_count = 0
+        self.marionette = self.parent.marionette
+
+    def get(self):
+        script = """
+        debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
+        if (debug.isDebugBuild) {
+          return debug.assertionCount;
+        }
+        return 0;
+        """
+
+        def get_count(context, **kwargs):
+            try:
+                context_count = self.marionette.execute_script(script, **kwargs)
+                if context_count:
+                    self.parent.logger.info("Got %s assert count %s" % (context, context_count))
+                    test_count = context_count - self.assert_count[context]
+                    self.assert_count[context] = context_count
+                    return test_count
+            except errors.NoSuchWindowException:
+                # If the window was already closed
+                self.parent.logger.warning("Failed to get assertion count; window was closed")
+            except (errors.MarionetteException, socket.error):
+                # This usually happens if the process crashed
+                pass
+
+        counts = []
+        with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
+            counts.append(get_count("chrome"))
+        if self.parent.e10s:
+            counts.append(get_count("content", sandbox="system"))
+
+        counts = [item for item in counts if item is not None]
+
+        if not counts:
+            return None
+
+        return sum(counts)
+
+
 class MarionetteSelectorProtocolPart(SelectorProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def elements_by_selector(self, selector):
         return self.marionette.find_elements("css selector", selector)
 
 
 class MarionetteClickProtocolPart(ClickProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def element(self, element):
         return element.click()
 
+
 class MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def send_keys(self, element, keys):
         return element.send_keys(keys)
 
+
 class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def send_message(self, message_type, status, message=None):
         obj = {
             "type": "testdriver-%s" % str(message_type),
             "status": str(status)
@@ -332,27 +379,29 @@ class MarionetteTestDriverProtocolPart(T
 class MarionetteProtocol(Protocol):
     implements = [MarionetteBaseProtocolPart,
                   MarionetteTestharnessProtocolPart,
                   MarionettePrefsProtocolPart,
                   MarionetteStorageProtocolPart,
                   MarionetteSelectorProtocolPart,
                   MarionetteClickProtocolPart,
                   MarionetteSendKeysProtocolPart,
-                  MarionetteTestDriverProtocolPart]
+                  MarionetteTestDriverProtocolPart,
+                  MarionetteAssertsProtocolPart]
 
-    def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
+    def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True):
         do_delayed_imports()
 
         super(MarionetteProtocol, self).__init__(executor, browser)
         self.marionette = None
         self.marionette_port = browser.marionette_port
         self.capabilities = capabilities
         self.timeout_multiplier = timeout_multiplier
         self.runner_handle = None
+        self.e10s = e10s
 
     def connect(self):
         self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
         startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
         self.marionette = marionette.Marionette(host='localhost',
                                                 port=self.marionette_port,
                                                 socket_timeout=None,
                                                 startup_timeout=startup_timeout)
@@ -432,25 +481,28 @@ class ExecuteAsyncScriptRun(object):
                 # We just want it to never time out, really, but marionette doesn't
                 # make that possible. It also seems to time out immediately if the
                 # timeout is set too high. This works at least.
                 self.protocol.base.set_timeout(2**28 - 1)
         except IOError:
             self.logger.error("Lost marionette connection before starting test")
             return Stop
 
-        executor = threading.Thread(target = self._run)
-        executor.start()
-
         if timeout is not None:
             wait_timeout = timeout + 2 * extra_timeout
         else:
             wait_timeout = None
 
-        flag = self.result_flag.wait(wait_timeout)
+        timer = threading.Timer(wait_timeout, self._timeout)
+        timer.start()
+
+        self._run()
+
+        self.result_flag.wait()
+        timer.cancel()
 
         if self.result == (None, None):
             self.logger.debug("Timed out waiting for a result")
             self.result = False, ("EXTERNAL-TIMEOUT", None)
         elif self.result[1] is None:
             # We didn't get any data back from the test, so check if the
             # browser is still responsive
             if self.protocol.is_alive:
@@ -475,33 +527,37 @@ class ExecuteAsyncScriptRun(object):
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
             self.logger.warning(traceback.format_exc())
             self.result = False, ("INTERNAL-ERROR", e)
         finally:
             self.result_flag.set()
 
+    def _timeout(self):
+        self.result = False, ("EXTERNAL-TIMEOUT", None)
+        self.result_flag.set()
+
 
 class MarionetteTestharnessExecutor(TestharnessExecutor):
     supports_testdriver = True
 
     def __init__(self, browser, server_config, timeout_multiplier=1,
                  close_after_done=True, debug_info=None, capabilities=None,
-                 **kwargs):
+                 debug=False, **kwargs):
         """Marionette-based executor for testharness.js tests"""
         TestharnessExecutor.__init__(self, browser, server_config,
                                      timeout_multiplier=timeout_multiplier,
                                      debug_info=debug_info)
-
-        self.protocol = MarionetteProtocol(self, browser, capabilities, timeout_multiplier)
+        self.protocol = MarionetteProtocol(self, browser, capabilities, timeout_multiplier, kwargs["e10s"])
         self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
         self.script_resume = open(os.path.join(here, "testharness_webdriver_resume.js")).read()
         self.close_after_done = close_after_done
         self.window_id = str(uuid.uuid4())
+        self.debug = debug
 
         self.original_pref_values = {}
 
         if marionette is None:
             do_delayed_imports()
 
     def is_alive(self):
         return self.protocol.is_alive
@@ -516,20 +572,33 @@ class MarionetteTestharnessExecutor(Test
         timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
                    else None)
 
         success, data = ExecuteAsyncScriptRun(self.logger,
                                               self.do_testharness,
                                               self.protocol,
                                               self.test_url(test),
                                               timeout).run()
+        # The format of data depends on whether the test ran to completion or not
+        # For asserts we only care about the fact that if it didn't complete, the
+        # status is in the first field.
+        status = None
+        if not success:
+            status = data[0]
+
+        extra = None
+        if self.debug and (success or status not in ("CRASH", "INTERNAL-ERROR")):
+            assertion_count = self.protocol.asserts.get()
+            if assertion_count is not None:
+                extra = {"assertion_count": assertion_count}
+
         if success:
-            return self.convert_result(test, data)
+            return self.convert_result(test, data, extra=extra)
 
-        return (test.result_cls(*data), [])
+        return (test.result_cls(extra=extra, *data), [])
 
     def do_testharness(self, protocol, url, timeout):
         protocol.base.execute_script("if (window.win) {window.win.close()}")
         parent_window = protocol.testharness.close_old_windows(protocol)
 
         if timeout is not None:
             timeout_ms = str(timeout * 1000)
         else:
@@ -560,36 +629,37 @@ class MarionetteTestharnessExecutor(Test
         return rv
 
 
 class MarionetteRefTestExecutor(RefTestExecutor):
     def __init__(self, browser, server_config, timeout_multiplier=1,
                  screenshot_cache=None, close_after_done=True,
                  debug_info=None, reftest_internal=False,
                  reftest_screenshot="unexpected",
-                 group_metadata=None, capabilities=None, **kwargs):
+                 group_metadata=None, capabilities=None, debug=False, **kwargs):
         """Marionette-based executor for reftests"""
         RefTestExecutor.__init__(self,
                                  browser,
                                  server_config,
                                  screenshot_cache=screenshot_cache,
                                  timeout_multiplier=timeout_multiplier,
                                  debug_info=debug_info)
         self.protocol = MarionetteProtocol(self, browser, capabilities,
-                                           timeout_multiplier)
+                                           timeout_multiplier, kwargs["e10s"])
         self.implementation = (InternalRefTestImplementation
                                if reftest_internal
                                else RefTestImplementation)(self)
         self.implementation_kwargs = ({"screenshot": reftest_screenshot} if
                                       reftest_internal else {})
 
         self.close_after_done = close_after_done
         self.has_window = False
         self.original_pref_values = {}
         self.group_metadata = group_metadata
+        self.debug = debug
 
         with open(os.path.join(here, "reftest.js")) as f:
             self.script = f.read()
         with open(os.path.join(here, "reftest-wait_marionette.js")) as f:
             self.wait_script = f.read()
 
     def setup(self, runner):
         super(self.__class__, self).setup(runner)
@@ -620,16 +690,23 @@ class MarionetteRefTestExecutor(RefTestE
                 self.has_window = False
 
             if not self.has_window:
                 self.protocol.base.execute_script(self.script)
                 self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
                 self.has_window = True
 
         result = self.implementation.run_test(test)
+
+        if self.debug:
+            assertion_count = self.protocol.asserts.get()
+            if "extra" not in result:
+                result["extra"] = {}
+            result["extra"]["assertion_count"] = assertion_count
+
         return self.convert_result(test, result)
 
     def screenshot(self, test, viewport_size, dpi):
         # https://github.com/w3c/wptrunner/issues/166
         assert viewport_size is None
         assert dpi is None
 
         timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/protocol.py
@@ -285,8 +285,21 @@ class TestDriverProtocolPart(ProtocolPar
     def send_message(self, message_type, status, message=None):
         """Send a testdriver message to the browser.
 
         :param str message_type: The kind of the message.
         :param str status: Either "failure" or "success" depending on whether the
                            previous command succeeded.
         :param str message: Additional data to add to the message."""
         pass
+
+
+class AssertsProtocolPart(ProtocolPart):
+    """ProtocolPart that implements the functionality required to get a count of non-fatal
+    assertions triggered"""
+    __metaclass__ = ABCMeta
+
+    name = "asserts"
+
+    @abstractmethod
+    def get(self):
+        """Get a count of assertions since the last browser start"""
+        pass
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/formatters.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/formatters.py
@@ -44,16 +44,28 @@ class WptreportFormatter(BaseFormatter):
         }
         test["subtests"].append(subtest)
 
         return subtest
 
     def test_status(self, data):
         subtest = self.create_subtest(data)
         subtest["status"] = data["status"]
+        if "expected" in data:
+            subtest["expected"] = data["expected"]
         if "message" in data:
             subtest["message"] = data["message"]
 
     def test_end(self, data):
         test = self.find_or_create_test(data)
         test["status"] = data["status"]
+        if "expected" in data:
+            test["expected"] = data["expected"]
         if "message" in data:
             test["message"] = data["message"]
+
+    def assertion_count(self, data):
+        test = self.find_or_create_test(data)
+        test["asserts"] = {
+            "count": data["count"],
+            "min": data["min_expected"],
+            "max": data["max_expected"]
+        }
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
@@ -28,16 +28,24 @@ def data_cls_getter(output_node, visited
 def bool_prop(name, node):
     """Boolean property"""
     try:
         return node.get(name)
     except KeyError:
         return None
 
 
+def int_prop(name, node):
+    """Boolean property"""
+    try:
+        return int(node.get(name))
+    except KeyError:
+        return None
+
+
 def tags(node):
     """Set of tags that have been applied to the test"""
     try:
         value = node.get("tags")
         if isinstance(value, (str, unicode)):
             return {value}
         return set(value)
     except KeyError:
@@ -111,16 +119,24 @@ class ExpectedManifest(ManifestItem):
     def restart_after(self):
         return bool_prop("restart-after", self)
 
     @property
     def leaks(self):
         return bool_prop("leaks", self)
 
     @property
+    def min_assertion_count(self):
+        return int_prop("min-asserts", self)
+
+    @property
+    def max_assertion_count(self):
+        return int_prop("max-asserts", self)
+
+    @property
     def tags(self):
         return tags(self)
 
     @property
     def prefs(self):
         return prefs(self)
 
 
@@ -133,16 +149,24 @@ class DirectoryManifest(ManifestItem):
     def restart_after(self):
         return bool_prop("restart-after", self)
 
     @property
     def leaks(self):
         return bool_prop("leaks", self)
 
     @property
+    def min_assertion_count(self):
+        return int_prop("min-asserts", self)
+
+    @property
+    def max_assertion_count(self):
+        return int_prop("max-asserts", self)
+
+    @property
     def tags(self):
         return tags(self)
 
     @property
     def prefs(self):
         return prefs(self)
 
 
@@ -182,16 +206,24 @@ class TestNode(ManifestItem):
     def restart_after(self):
         return bool_prop("restart-after", self)
 
     @property
     def leaks(self):
         return bool_prop("leaks", self)
 
     @property
+    def min_assertion_count(self):
+        return int_prop("min-asserts", self)
+
+    @property
+    def max_assertion_count(self):
+        return int_prop("max-asserts", self)
+
+    @property
     def tags(self):
         return tags(self)
 
     @property
     def prefs(self):
         return prefs(self)
 
     def append(self, node):
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestupdate.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestupdate.py
@@ -1,8 +1,9 @@
+import itertools
 import os
 import urlparse
 from collections import namedtuple, defaultdict
 
 from wptmanifest.node import (DataNode, ConditionalNode, BinaryExpressionNode,
                               BinaryOperatorNode, VariableNode, StringNode, NumberNode,
                               UnaryExpressionNode, UnaryOperatorNode, KeyValueNode)
 from wptmanifest.backends import conditional
@@ -30,17 +31,17 @@ is updated with the changes, and the res
 """
 
 
 class ConditionError(Exception):
     def __init__(self, cond=None):
         self.cond = cond
 
 
-Result = namedtuple("Result", ["run_info", "status"])
+Value = namedtuple("Value", ["run_info", "value"])
 
 
 def data_cls_getter(output_node, visited_node):
     # visited_node is intentionally unused
     if output_node is None:
         return ExpectedManifest
     elif isinstance(output_node, ExpectedManifest):
         return TestNode
@@ -108,32 +109,33 @@ class ExpectedManifest(ManifestItem):
 
 class TestNode(ManifestItem):
     def __init__(self, node):
         """Tree node associated with a particular test in a manifest
 
         :param node: AST node associated with the test"""
 
         ManifestItem.__init__(self, node)
-        self.updated_expected = []
-        self.new_expected = []
-        self.new_disabled = False
         self.subtests = {}
-        self.default_status = None
         self._from_file = True
+        self.update_properties = {
+            "expected": ExpectedUpdate(self),
+            "max-asserts": MaxAssertsUpdate(self),
+            "min-asserts": MinAssertsUpdate(self)
+        }
 
     @classmethod
     def create(cls, test_id):
         """Create a TestNode corresponding to a given test
 
         :param test_type: The type of the test
         :param test_id: The id of the test"""
 
         url = test_id
-        name = url.split("/")[-1]
+        name = url.rsplit("/", 1)[1]
         node = DataNode(name)
         self = cls(node)
 
         self._from_file = False
         return self
 
     @property
     def is_empty(self):
@@ -163,140 +165,47 @@ class TestNode(ManifestItem):
 
     def set_result(self, run_info, result):
         """Set the result of the test in a particular run
 
         :param run_info: Dictionary of run_info parameters corresponding
                          to this run
         :param result: Status of the test in this run"""
 
-        if self.default_status is not None:
-            assert self.default_status == result.default_expected
-        else:
-            self.default_status = result.default_expected
-
-        # Add this result to the list of results satisfying
-        # any condition in the list of updated results it matches
-        for (cond, values) in self.updated_expected:
-            if cond(run_info):
-                values.append(Result(run_info, result.status))
-                if result.status != cond.value:
-                    self.root.modified = True
-                break
-        else:
-            # We didn't find a previous value for this
-            self.new_expected.append(Result(run_info, result.status))
-            self.root.modified = True
-
-    def coalesce_expected(self, stability=None):
-        """Update the underlying manifest AST for this test based on all the
-        added results.
-
-        This will update existing conditionals if they got the same result in
-        all matching runs in the updated results, will delete existing conditionals
-        that get more than one different result in the updated run, and add new
-        conditionals for anything that doesn't match an existing conditional.
-
-        Conditionals not matched by any added result are not changed.
-
-        When `stability` is not None, disable any test that shows multiple
-        unexpected results for the same set of parameters.
-        """
-
-        try:
-            unconditional_status = self.get("expected")
-        except KeyError:
-            unconditional_status = self.default_status
+        self.update_properties["expected"].set(run_info, result)
 
-        for conditional_value, results in self.updated_expected:
-            if not results:
-                # The conditional didn't match anything in these runs so leave it alone
-                pass
-            elif all(results[0].status == result.status for result in results):
-                # All the new values for this conditional matched, so update the node
-                result = results[0]
-                if (result.status == unconditional_status and
-                    conditional_value.condition_node is not None):
-                    if "expected" in self:
-                        self.remove_value("expected", conditional_value)
-                else:
-                    conditional_value.value = result.status
-            elif conditional_value.condition_node is not None:
-                # Blow away the existing condition and rebuild from scratch
-                # This isn't sure to work if we have a conditional later that matches
-                # these values too, but we can hope, verify that we get the results
-                # we expect, and if not let a human sort it out
-                self.remove_value("expected", conditional_value)
-                self.new_expected.extend(results)
-            elif conditional_value.condition_node is None:
-                self.new_expected.extend(result for result in results
-                                         if result.status != unconditional_status)
-
-        # It is an invariant that nothing in new_expected matches an existing
-        # condition except for the default condition
+    def set_asserts(self, run_info, count):
+        """Set the assert count of a test
 
-        if self.new_expected:
-            if all(self.new_expected[0].status == result.status
-                   for result in self.new_expected) and not self.updated_expected:
-                status = self.new_expected[0].status
-                if status != self.default_status:
-                    self.set("expected", status, condition=None)
-            else:
-                try:
-                    conditionals = group_conditionals(
-                        self.new_expected,
-                        property_order=self.root.property_order,
-                        boolean_properties=self.root.boolean_properties)
-                except ConditionError as e:
-                    if stability is not None:
-                        self.set("disabled", stability or "unstable", e.cond.children[0])
-                        self.new_disabled = True
-                    else:
-                        print "Conflicting test results for %s, cannot update" % self.root.test_path
-                    return
-                for conditional_node, status in conditionals:
-                    if status != unconditional_status:
-                        self.set("expected", status, condition=conditional_node.children[0])
-
-        if ("expected" in self._data and
-            len(self._data["expected"]) > 0 and
-            self._data["expected"][-1].condition_node is None and
-            self._data["expected"][-1].value == self.default_status):
-
-            self.remove_value("expected", self._data["expected"][-1])
-
-        if ("expected" in self._data and
-            len(self._data["expected"]) == 0):
-            for child in self.node.children:
-                if (isinstance(child, KeyValueNode) and
-                    child.data == "expected"):
-                    child.remove()
-                    break
+        """
+        self.update_properties["min-asserts"].set(run_info, count)
+        self.update_properties["max-asserts"].set(run_info, count)
 
     def _add_key_value(self, node, values):
         ManifestItem._add_key_value(self, node, values)
-        if node.data == "expected":
-            self.updated_expected = []
+        if node.data in self.update_properties:
+            new_updated = []
+            self.update_properties[node.data].updated = new_updated
             for value in values:
-                self.updated_expected.append((value, []))
+                new_updated.append((value, []))
 
-    def clear_expected(self):
+    def clear(self, key):
         """Clear all the expected data for this test and all of its subtests"""
 
-        self.updated_expected = []
-        if "expected" in self._data:
+        self.updated = []
+        if key in self._data:
             for child in self.node.children:
                 if (isinstance(child, KeyValueNode) and
-                    child.data == "expected"):
+                    child.data == key):
                     child.remove()
-                    del self._data["expected"]
+                    del self._data[key]
                     break
 
         for subtest in self.subtests.itervalues():
-            subtest.clear_expected()
+            subtest.clear(key)
 
     def append(self, node):
         child = ManifestItem.append(self, node)
         self.subtests[child.name] = child
 
     def get_subtest(self, name):
         """Return a SubtestNode corresponding to a particular subtest of
         the current test, creating a new one if no subtest with that name
@@ -306,16 +215,20 @@ class TestNode(ManifestItem):
 
         if name in self.subtests:
             return self.subtests[name]
         else:
             subtest = SubtestNode.create(name)
             self.append(subtest)
             return subtest
 
+    def coalesce_properties(self, stability):
+        for prop_update in self.update_properties.itervalues():
+            prop_update.coalesce(stability)
+
 
 class SubtestNode(TestNode):
     def __init__(self, node):
         assert isinstance(node, DataNode)
         TestNode.__init__(self, node)
 
     @classmethod
     def create(cls, name):
@@ -325,31 +238,262 @@ class SubtestNode(TestNode):
 
     @property
     def is_empty(self):
         if self._data:
             return False
         return True
 
 
+class PropertyUpdate(object):
+    property_name = None
+    cls_default_value = None
+    value_type = None
+
+    def __init__(self, node):
+        self.node = node
+        self.updated = []
+        self.new = []
+        self.default_value = self.cls_default_value
+
+    def set(self, run_info, in_value):
+        self.check_default(in_value)
+        value = self.get_value(in_value)
+
+        # Add this result to the list of results satisfying
+        # any condition in the list of updated results it matches
+        for (cond, values) in self.updated:
+            if cond(run_info):
+                values.append(Value(run_info, value))
+                if value != cond.value_as(self.value_type):
+                    self.node.root.modified = True
+                break
+        else:
+            # We didn't find a previous value for this
+            self.new.append(Value(run_info, value))
+            self.node.root.modified = True
+
+    def check_default(self, result):
+        return
+
+    def get_value(self, in_value):
+        return in_value
+
+    def coalesce(self, stability=None):
+        """Update the underlying manifest AST for this test based on all the
+        added results.
+
+        This will update existing conditionals if they got the same result in
+        all matching runs in the updated results, will delete existing conditionals
+        that get more than one different result in the updated run, and add new
+        conditionals for anything that doesn't match an existing conditional.
+
+        Conditionals not matched by any added result are not changed.
+
+        When `stability` is not None, disable any test that shows multiple
+        unexpected results for the same set of parameters.
+        """
+
+        try:
+            unconditional_value = self.node.get(self.property_name)
+            if self.value_type:
+                unconditional_value = self.value_type(unconditional_value)
+        except KeyError:
+            unconditional_value = self.default_value
+
+        for conditional_value, results in self.updated:
+            if not results:
+                # The conditional didn't match anything in these runs so leave it alone
+                pass
+            elif all(results[0].value == result.value for result in results):
+                # All the new values for this conditional matched, so update the node
+                result = results[0]
+                if (result.value == unconditional_value and
+                    conditional_value.condition_node is not None):
+                    if self.property_name in self.node:
+                        self.node.remove_value(self.property_name, conditional_value)
+                else:
+                    conditional_value.value = self.update_value(conditional_value.value_as(self.value_type),
+                                                                result.value)
+            elif conditional_value.condition_node is not None:
+                # Blow away the existing condition and rebuild from scratch
+                # This isn't sure to work if we have a conditional later that matches
+                # these values too, but we can hope, verify that we get the results
+                # we expect, and if not let a human sort it out
+                self.node.remove_value(self.property_name, conditional_value)
+                self.new.extend(results)
+            elif conditional_value.condition_node is None:
+                self.new.extend(result for result in results
+                                if result.value != unconditional_value)
+
+        # It is an invariant that nothing in new matches an existing
+        # condition except for the default condition
+        if self.new:
+            update_default, new_default_value = self.update_default()
+            if update_default:
+                if new_default_value != self.default_value:
+                    self.node.set(self.property_name, self.update_value(None, new_default_value), condition=None)
+            else:
+                self.add_new(unconditional_value)
+
+        # Remove cases where the value matches the default
+        if (self.property_name in self.node._data and
+            len(self.node._data[self.property_name]) > 0 and
+            self.node._data[self.property_name][-1].condition_node is None and
+            self.node._data[self.property_name][-1].value_as(self.value_type) == self.default_value):
+
+            self.node.remove_value(self.property_name, self.node._data[self.property_name][-1])
+
+        # Remove empty properties
+        if (self.property_name in self.node._data and len(self.node._data[self.property_name]) == 0):
+            for child in self.node.children:
+                if (isinstance(child, KeyValueNode) and child.data == self.property_name):
+                    child.remove()
+                    break
+
+    def update_default(self):
+        """Get the updated default value for the property (i.e. the one chosen when no conditions match).
+
+        :returns: (update, new_default_value) where updated is a bool indicating whether the property
+                  should be updated, and new_default_value is the value to set if it should."""
+        raise NotImplementedError
+
+    def add_new(self, unconditional_value):
+        """Add new conditional values for the property.
+
+        Subclasses need not implement this if they only ever update the default value."""
+        raise NotImplementedError
+
+    def update_value(self, old_value, new_value):
+        """Get a value to set on the property, given its previous value and the new value from logs.
+
+        By default this just returns the new value, but overriding is useful in cases
+        where we want the new value to be some function of both old and new e.g. max(old_value, new_value)"""
+        return new_value
+
+
+class ExpectedUpdate(PropertyUpdate):
+    property_name = "expected"
+
+    def check_default(self, result):
+        if self.default_value is not None:
+            assert self.default_value == result.default_expected
+        else:
+            self.default_value = result.default_expected
+
+    def get_value(self, in_value):
+        return in_value.status
+
+    def update_default(self):
+        update_default = all(self.new[0].value == result.value
+                             for result in self.new) and not self.updated
+        new_value = self.new[0].value
+        return update_default, new_value<