Merge mozilla-central to inbound. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Tue, 15 Jan 2019 06:41:22 +0200
changeset 510964 ceabe45003322ae0bf3e7c9286b94d858780bfbc
parent 510963 10ec41de1192a83da845633041c94b1ea556d0e1 (current diff)
parent 510954 e49161da578400faf8e938324caff120b95f4863 (diff)
child 510965 cd696bc79dffd9b8f76e1fd924c441ad3d86cbdb
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -302,17 +302,18 @@ void AccessibleWrap::GetRoleDescription(
 
   nsCOMPtr<nsIStringBundle> bundle;
   rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get string bundle");
     return;
   }
 
-  if (aRole == roles::HEADING) {
+  if (aRole == roles::HEADING && aAttributes) {
+    // The heading level is an attribute, so we need that.
     nsString level;
     rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("level"), level);
     if (NS_SUCCEEDED(rv)) {
       const char16_t* formatString[] = {level.get()};
       rv = bundle->FormatStringFromName("headingLevel", formatString, 1,
                                         aRoleDescription);
       if (NS_SUCCEEDED(rv)) {
         return;
@@ -406,24 +407,28 @@ bool AccessibleWrap::WrapperRangeInfo(do
     *aMaxVal = MaxValue();
     *aStep = Step();
     return true;
   }
 
   return false;
 }
 
-mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle() {
+mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(bool aSmall) {
   nsAutoString name;
   Name(name);
   nsAutoString textValue;
   Value(textValue);
   nsAutoString nodeID;
   WrapperDOMNodeID(nodeID);
 
+  if (aSmall) {
+    return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID);
+  }
+
   double curValue = UnspecifiedNaN<double>();
   double minValue = UnspecifiedNaN<double>();
   double maxValue = UnspecifiedNaN<double>();
   double step = UnspecifiedNaN<double>();
   WrapperRangeInfo(&curValue, &minValue, &maxValue, &step);
 
   nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
 
@@ -508,120 +513,84 @@ mozilla::java::GeckoBundle::LocalRef Acc
     if (!IsNaN(aMaxVal)) {
       GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
     }
 
     GECKOBUNDLE_FINISH(rangeInfo);
     GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
   }
 
-  nsString inputTypeAttr;
-  nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputTypeAttr);
-  int32_t inputType = GetInputType(inputTypeAttr);
-  if (inputType) {
-    GECKOBUNDLE_PUT(nodeInfo, "inputType",
-                    java::sdk::Integer::ValueOf(inputType));
-  }
+  if (aAttributes) {
+    nsString inputTypeAttr;
+    nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType,
+                           inputTypeAttr);
+    int32_t inputType = GetInputType(inputTypeAttr);
+    if (inputType) {
+      GECKOBUNDLE_PUT(nodeInfo, "inputType",
+                      java::sdk::Integer::ValueOf(inputType));
+    }
 
-  nsString posinset;
-  nsresult rv =
-      aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"), posinset);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowIndex;
-    if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
-      GECKOBUNDLE_START(collectionItemInfo);
-      GECKOBUNDLE_PUT(collectionItemInfo, "rowIndex",
-                      java::sdk::Integer::ValueOf(rowIndex));
-      GECKOBUNDLE_PUT(collectionItemInfo, "columnIndex",
-                      java::sdk::Integer::ValueOf(0));
-      GECKOBUNDLE_PUT(collectionItemInfo, "rowSpan",
-                      java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_PUT(collectionItemInfo, "columnSpan",
-                      java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_FINISH(collectionItemInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
-    }
-  }
+    nsString posinset;
+    nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"),
+                                                 posinset);
+    if (NS_SUCCEEDED(rv)) {
+      int32_t rowIndex;
+      if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
+        GECKOBUNDLE_START(collectionItemInfo);
+        GECKOBUNDLE_PUT(collectionItemInfo, "rowIndex",
+                        java::sdk::Integer::ValueOf(rowIndex));
+        GECKOBUNDLE_PUT(collectionItemInfo, "columnIndex",
+                        java::sdk::Integer::ValueOf(0));
+        GECKOBUNDLE_PUT(collectionItemInfo, "rowSpan",
+                        java::sdk::Integer::ValueOf(1));
+        GECKOBUNDLE_PUT(collectionItemInfo, "columnSpan",
+                        java::sdk::Integer::ValueOf(1));
+        GECKOBUNDLE_FINISH(collectionItemInfo);
 
-  nsString colSize;
-  rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
-                                      colSize);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowCount;
-    if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
-      GECKOBUNDLE_START(collectionInfo);
-      GECKOBUNDLE_PUT(collectionInfo, "rowCount",
-                      java::sdk::Integer::ValueOf(rowCount));
-      GECKOBUNDLE_PUT(collectionInfo, "columnCount",
-                      java::sdk::Integer::ValueOf(1));
+        GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
+      }
+    }
+
+    nsString colSize;
+    rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
+                                        colSize);
+    if (NS_SUCCEEDED(rv)) {
+      int32_t rowCount;
+      if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
+        GECKOBUNDLE_START(collectionInfo);
+        GECKOBUNDLE_PUT(collectionInfo, "rowCount",
+                        java::sdk::Integer::ValueOf(rowCount));
+        GECKOBUNDLE_PUT(collectionInfo, "columnCount",
+                        java::sdk::Integer::ValueOf(1));
 
-      nsString unused;
-      rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
-                                          unused);
-      if (NS_SUCCEEDED(rv)) {
-        GECKOBUNDLE_PUT(collectionInfo, "isHierarchical",
-                        java::sdk::Boolean::TRUE());
-      }
+        nsString unused;
+        rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
+                                            unused);
+        if (NS_SUCCEEDED(rv)) {
+          GECKOBUNDLE_PUT(collectionInfo, "isHierarchical",
+                          java::sdk::Boolean::TRUE());
+        }
 
-      if (IsSelect()) {
-        int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
-        GECKOBUNDLE_PUT(collectionInfo, "selectionMode",
-                        java::sdk::Integer::ValueOf(selectionMode));
+        if (IsSelect()) {
+          int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
+          GECKOBUNDLE_PUT(collectionInfo, "selectionMode",
+                          java::sdk::Integer::ValueOf(selectionMode));
+        }
+
+        GECKOBUNDLE_FINISH(collectionInfo);
+        GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
       }
-      GECKOBUNDLE_FINISH(collectionInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
     }
   }
 
   auto childCount = ChildCount();
   nsTArray<int32_t> children(childCount);
   for (uint32_t i = 0; i < childCount; i++) {
     auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
     children.AppendElement(child->VirtualViewID());
   }
 
   GECKOBUNDLE_PUT(nodeInfo, "children",
                   jni::IntArray::New(children.Elements(), children.Length()));
   GECKOBUNDLE_FINISH(nodeInfo);
 
   return nodeInfo;
 }
-
-mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToSmallBundle() {
-  return ToSmallBundle(State(), Bounds(), ActionCount());
-}
-
-mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToSmallBundle(
-    const uint64_t aState, const nsIntRect& aBounds,
-    const uint8_t aActionCount) {
-  GECKOBUNDLE_START(nodeInfo);
-  GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
-
-  AccessibleWrap* parent = WrapperParent();
-  GECKOBUNDLE_PUT(
-      nodeInfo, "parentId",
-      java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
-
-  uint32_t flags = GetFlags(WrapperRole(), aState, aActionCount);
-  GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
-  GECKOBUNDLE_PUT(nodeInfo, "className",
-                  java::sdk::Integer::ValueOf(AndroidClass()));
-
-  const int32_t data[4] = {aBounds.x, aBounds.y, aBounds.x + aBounds.width,
-                           aBounds.y + aBounds.height};
-  GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
-
-  auto childCount = ChildCount();
-  nsTArray<int32_t> children(childCount);
-  for (uint32_t i = 0; i < childCount; ++i) {
-    auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
-    children.AppendElement(child->VirtualViewID());
-  }
-
-  GECKOBUNDLE_PUT(nodeInfo, "children",
-                  jni::IntArray::New(children.Elements(), children.Length()));
-
-  GECKOBUNDLE_FINISH(nodeInfo);
-
-  return nodeInfo;
-}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -28,30 +28,27 @@ class AccessibleWrap : public Accessible
   int32_t VirtualViewID() const { return mID; }
 
   virtual void SetTextContents(const nsAString& aText);
 
   virtual void GetTextContents(nsAString& aText);
 
   virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
 
-  mozilla::java::GeckoBundle::LocalRef ToBundle();
+  mozilla::java::GeckoBundle::LocalRef ToBundle(bool aSmall = false);
 
   mozilla::java::GeckoBundle::LocalRef ToBundle(
       const uint64_t aState, const nsIntRect& aBounds,
       const uint8_t aActionCount, const nsString& aName,
       const nsString& aTextValue, const nsString& aDOMNodeID,
-      const double& aCurVal, const double& aMinVal, const double& aMaxVal,
-      const double& aStep, nsIPersistentProperties* aAttributes);
-
-  mozilla::java::GeckoBundle::LocalRef ToSmallBundle(
-      const uint64_t aState, const nsIntRect& aBounds,
-      const uint8_t aActionCount);
-
-  mozilla::java::GeckoBundle::LocalRef ToSmallBundle();
+      const double& aCurVal = UnspecifiedNaN<double>(),
+      const double& aMinVal = UnspecifiedNaN<double>(),
+      const double& aMaxVal = UnspecifiedNaN<double>(),
+      const double& aStep = UnspecifiedNaN<double>(),
+      nsIPersistentProperties* aAttributes = nullptr);
 
   virtual void WrapperDOMNodeID(nsString& aDOMNodeID);
 
   int32_t AndroidClass() {
     return mID == kNoID ? java::SessionAccessibility::CLASSNAME_WEBVIEW
                         : GetAndroidClass(WrapperRole());
   }
 
--- a/accessible/android/DocAccessibleWrap.cpp
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -128,20 +128,28 @@ void DocAccessibleWrap::CacheViewportCal
   if (IPCAccessibilityActive()) {
     DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
     nsTArray<BatchData> cacheData(inViewAccs.Count());
     for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
       Accessible* accessible = iter.Data();
       auto uid = accessible->IsDoc() && accessible->AsDoc()->IPCDoc()
                      ? 0
                      : reinterpret_cast<uint64_t>(accessible->UniqueID());
+
+      nsAutoString name;
+      accessible->Name(name);
+      nsAutoString textValue;
+      accessible->Value(textValue);
+      nsAutoString nodeID;
+      static_cast<AccessibleWrap*>(accessible)->WrapperDOMNodeID(nodeID);
+
       cacheData.AppendElement(
           BatchData(accessible->Document()->IPCDoc(), uid, accessible->State(),
-                    accessible->Bounds(), accessible->ActionCount(), nsString(),
-                    nsString(), nsString(), UnspecifiedNaN<double>(),
+                    accessible->Bounds(), accessible->ActionCount(), name,
+                    textValue, nodeID, UnspecifiedNaN<double>(),
                     UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
                     UnspecifiedNaN<double>(), nsTArray<Attribute>()));
     }
 
     ipcDoc->SendBatch(eBatch_Viewport, cacheData);
   } else if (SessionAccessibility* sessionAcc =
                  SessionAccessibility::GetInstanceFor(docAcc)) {
     nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -328,20 +328,21 @@ void SessionAccessibility::ReplaceViewpo
     const nsTArray<AccessibleWrap*>& aAccessibles,
     const nsTArray<BatchData>& aData) {
   auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
   for (size_t i = 0; i < aAccessibles.Length(); i++) {
     AccessibleWrap* acc = aAccessibles.ElementAt(i);
     if (aData.Length() == aAccessibles.Length()) {
       const BatchData& data = aData.ElementAt(i);
       auto bundle =
-          acc->ToSmallBundle(data.State(), data.Bounds(), data.ActionCount());
+          acc->ToBundle(data.State(), data.Bounds(), data.ActionCount(),
+                        data.Name(), data.TextValue(), data.DOMNodeID());
       infos->SetElement(i, bundle);
     } else {
-      infos->SetElement(i, acc->ToSmallBundle());
+      infos->SetElement(i, acc->ToBundle(true));
     }
   }
 
   mSessionAccessibility->ReplaceViewportCache(infos);
 }
 
 void SessionAccessibility::ReplaceFocusPathCache(
     const nsTArray<AccessibleWrap*>& aAccessibles,
@@ -375,17 +376,18 @@ void SessionAccessibility::UpdateCachedB
     if (!acc) {
       MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
       continue;
     }
 
     if (aData.Length() == aAccessibles.Length()) {
       const BatchData& data = aData.ElementAt(i);
       auto bundle =
-          acc->ToSmallBundle(data.State(), data.Bounds(), data.ActionCount());
+          acc->ToBundle(data.State(), data.Bounds(), data.ActionCount(),
+                        data.Name(), data.TextValue(), data.DOMNodeID());
       infos->SetElement(i, bundle);
     } else {
-      infos->SetElement(i, acc->ToSmallBundle());
+      infos->SetElement(i, acc->ToBundle(true));
     }
   }
 
   mSessionAccessibility->UpdateCachedBounds(infos);
 }
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -406,16 +406,18 @@ mozilla::ipc::IPCResult DocAccessiblePar
   if (!target) {
     NS_ERROR("no proxy for event!");
     return IPC_OK();
   }
 
 #if defined(ANDROID)
   ProxyScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
                       aMaxScrollY);
+#else
+  ProxyEvent(target, aType);
 #endif
 
   if (!nsCoreUtils::AccEventObserversExist()) {
     return IPC_OK();
   }
 
   xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
   xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1169,16 +1169,28 @@ pref("services.sync.prefs.sync.addons.ig
 // source, and this would propagate automatically to other,
 // uncompromised Sync-connected devices.
 pref("services.sync.prefs.sync.browser.contentblocking.category", true);
 pref("services.sync.prefs.sync.browser.contentblocking.introCount", true);
 pref("services.sync.prefs.sync.browser.ctrlTab.recentlyUsedOrder", true);
 pref("services.sync.prefs.sync.browser.download.useDownloadDir", true);
 pref("services.sync.prefs.sync.browser.formfill.enable", true);
 pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.showSearch", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.feeds.topsites", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.topSitesRows", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.feeds.snippets", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.feeds.section.topstories", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.topstories.rows", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.feeds.section.highlights", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.highlights.includeVisited", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.highlights.includeBookmarks", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.highlights.includeDownloads", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.highlights.includePocket", true);
+pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.section.highlights.rows", true);
 pref("services.sync.prefs.sync.browser.newtabpage.enabled", true);
 pref("services.sync.prefs.sync.browser.newtabpage.pinned", true);
 pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.phishing.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.downloads.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.passwords.enabled", true);
 pref("services.sync.prefs.sync.browser.search.update", true);
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -487,17 +487,17 @@ window._gBrowser = {
     return this.selectedBrowser.textZoom;
   },
 
   get isSyntheticDocument() {
     return this.selectedBrowser.isSyntheticDocument;
   },
 
   set userTypedValue(val) {
-    return this.selectedBrowser.userTypedValue = val;
+    this.selectedBrowser.userTypedValue = val;
   },
 
   get userTypedValue() {
     return this.selectedBrowser.userTypedValue;
   },
 
   _setFindbarData() {
     // Ensure we know what the find bar key is in the content process:
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -5,16 +5,19 @@
 # set during early startup to have an impact as a canvas will be used by
 # startupRecorder.js
 prefs =
   # Skip migration work in BG__migrateUI for browser_startup.js since it isn't
   # representative of common startup.
   browser.migration.version=9999999
   browser.startup.record=true
   gfx.canvas.willReadFrequently.enable=true
+  # The form autofill framescript is only used in certain locales if this
+  # pref is set to 'detect', which is the default value on non-Nightly.
+  extensions.formautofill.available='on'
 support-files =
   head.js
 [browser_appmenu.js]
 skip-if = asan || debug || (os == 'win' && bits == 32) # Bug 1382809, bug 1369959, Win32 because of intermittent OOM failures
 [browser_preferences_usage.js]
 skip-if = !debug
 [browser_startup.js]
 [browser_startup_content.js]
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/* This test records which services, JS components, process scripts, and JS
- * modules are loaded when creating a new content process.
+/* This test records which services, JS components, frame scripts, process
+ * scripts, and JS modules are loaded when creating a new content process.
  *
  * If you made changes that cause this test to fail, it's likely because you
  * are loading more JS code during content process startup.
  *
  * If your code isn't strictly required to show a page, consider loading it
  * lazily. If you can't, consider delaying its load until after we have started
  * handling user events.
  */
@@ -63,16 +63,33 @@ const whitelist = {
     "resource://gre/modules/TelemetryController.jsm", // bug 1470339
     "resource://gre/modules/TelemetryUtils.jsm", // bug 1470339
 
     // Extensions
     "resource://gre/modules/ExtensionProcessScript.jsm",
     "resource://gre/modules/ExtensionUtils.jsm",
     "resource://gre/modules/MessageChannel.jsm",
   ]),
+  frameScripts: new Set([
+    // Test related
+    "resource://specialpowers/MozillaLogger.js",
+    "resource://specialpowers/specialpowersFrameScript.js",
+    "chrome://mochikit/content/shutdown-leaks-collector.js",
+    "chrome://mochikit/content/tests/SimpleTest/AsyncUtilsContent.js",
+    "chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js",
+
+    // Browser front-end
+    "chrome://global/content/browser-content.js",
+
+    // Forms
+    "chrome://formautofill/content/FormAutofillFrameScript.js",
+
+    // Extensions
+    "resource://gre/modules/addons/Content.js",
+  ]),
   processScripts: new Set([
     "chrome://global/content/process-content.js",
     "resource:///modules/ContentObservers.js",
     "data:,ChromeUtils.import('resource://gre/modules/ExtensionProcessScript.jsm')",
     "chrome://satchel/content/formSubmitListener.js",
     "resource://devtools/client/jsonview/converter-observer.js",
     "resource://gre/modules/WebRequestContent.js",
     "data:,new function() {\n      ChromeUtils.import(\"resource://formautofill/FormAutofillContent.jsm\");\n    }",
@@ -84,16 +101,17 @@ const whitelist = {
 // which are all required.
 const intermittently_loaded_whitelist = {
   components: new Set([
     "nsAsyncShutdown.js",
   ]),
   modules: new Set([
     "resource://gre/modules/sessionstore/Utils.jsm",
   ]),
+  frameScripts: new Set([]),
   processScripts: new Set([]),
 };
 
 const blacklist = {
   services: new Set([
     "@mozilla.org/base/telemetry-startup;1",
     "@mozilla.org/embedcomp/default-tooltiptextprovider;1",
     "@mozilla.org/push/Service;1",
@@ -135,16 +153,22 @@ add_task(async function() {
         }
       } catch (e) {}
     }
     sendAsyncMessage("Test:LoadedScripts", {components, modules, services});
   } + ")()", false);
 
   let loadedInfo = await promise;
 
+  // Gather loaded frame scripts.
+  loadedInfo.frameScripts = {};
+  for (let [uri] of Services.mm.getDelayedFrameScripts()) {
+    loadedInfo.frameScripts[uri] = "";
+  }
+
   // Gather loaded process scripts.
   loadedInfo.processScripts = {};
   for (let [uri] of Services.ppmm.getDelayedProcessScripts()) {
     loadedInfo.processScripts[uri] = "";
   }
 
   let loadedList = {};
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2815,16 +2815,19 @@ void EventStateManager::DecideGestureEve
   }    // ancestor chain
   aEvent->mDisplayPanFeedback = displayPanFeedback;
   aEvent->mPanDirection = panDirection;
 }
 
 #ifdef XP_MACOSX
 static bool NodeAllowsClickThrough(nsINode* aNode) {
   while (aNode) {
+    if (aNode->IsXULElement(nsGkAtoms::browser)) {
+      return false;
+    }
     if (aNode->IsXULElement()) {
       mozilla::dom::Element* element = aNode->AsElement();
       static Element::AttrValuesArray strings[] = {nsGkAtoms::always,
                                                    nsGkAtoms::never, nullptr};
       switch (element->FindAttrValueIn(
           kNameSpaceID_None, nsGkAtoms::clickthrough, strings, eCaseMatters)) {
         case 0:
           return true;
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -319,38 +319,40 @@ class MediaRecorder::Session : public Pr
       recorder->NotifyError(NS_ERROR_UNEXPECTED);
       return NS_OK;
     }
 
    private:
     RefPtr<Session> mSession;
   };
 
-  // Fire start event and set mimeType, run in main thread task.
-  class DispatchStartEventRunnable : public Runnable {
+  // Fire a named event, run in main thread task.
+  class DispatchEventRunnable : public Runnable {
    public:
-    explicit DispatchStartEventRunnable(Session* aSession)
-        : Runnable("dom::MediaRecorder::Session::DispatchStartEventRunnable"),
-          mSession(aSession) {}
+    explicit DispatchEventRunnable(Session* aSession,
+                                   const nsAString& aEventName)
+        : Runnable("dom::MediaRecorder::Session::DispatchEventRunnable"),
+          mSession(aSession),
+          mEventName(aEventName) {}
 
     NS_IMETHOD Run() override {
       LOG(LogLevel::Debug,
-          ("Session.DispatchStartEventRunnable s=(%p)", mSession.get()));
+          ("Session.DispatchEventRunnable s=(%p) e=(%s)", mSession.get(),
+           NS_ConvertUTF16toUTF8(mEventName).get()));
       MOZ_ASSERT(NS_IsMainThread());
 
       NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
-      RefPtr<MediaRecorder> recorder = mSession->mRecorder;
-
-      recorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
+      mSession->mRecorder->DispatchSimpleEvent(mEventName);
 
       return NS_OK;
     }
 
    private:
     RefPtr<Session> mSession;
+    nsString mEventName;
   };
 
   // Main thread task.
   // To delete RecordingSession object.
   class DestroyRunnable : public Runnable {
    public:
     explicit DestroyRunnable(Session* aSession)
         : Runnable("dom::MediaRecorder::Session::DestroyRunnable"),
@@ -586,28 +588,32 @@ class MediaRecorder::Session : public Pr
     LOG(LogLevel::Debug, ("Session.Pause"));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mEncoder) {
       return NS_ERROR_FAILURE;
     }
 
     mEncoder->Suspend(TimeStamp::Now());
+    NS_DispatchToMainThread(
+        new DispatchEventRunnable(this, NS_LITERAL_STRING("pause")));
     return NS_OK;
   }
 
   nsresult Resume() {
     LOG(LogLevel::Debug, ("Session.Resume"));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mEncoder) {
       return NS_ERROR_FAILURE;
     }
 
     mEncoder->Resume(TimeStamp::Now());
+    NS_DispatchToMainThread(
+        new DispatchEventRunnable(this, NS_LITERAL_STRING("resume")));
     return NS_OK;
   }
 
   nsresult RequestData() {
     LOG(LogLevel::Debug, ("Session.RequestData"));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (NS_FAILED(
@@ -978,17 +984,18 @@ class MediaRecorder::Session : public Pr
         mRunningState.unwrap() == RunningState::Stopped) {
       // We have already ended gracefully.
       return;
     }
 
     if (mRunningState.isOk() &&
         (mRunningState.unwrap() == RunningState::Idling ||
          mRunningState.unwrap() == RunningState::Starting)) {
-      NS_DispatchToMainThread(new DispatchStartEventRunnable(this));
+      NS_DispatchToMainThread(
+          new DispatchEventRunnable(this, NS_LITERAL_STRING("start")));
     }
 
     if (rv == NS_OK) {
       mRunningState = RunningState::Stopped;
     } else {
       mRunningState = Err(rv);
     }
 
@@ -1045,17 +1052,18 @@ class MediaRecorder::Session : public Pr
             // We set it to Running in the runnable since we can only assign
             // mRunningState on main thread. We set it before running the start
             // event runnable since that dispatches synchronously (and may cause
             // js calls to methods depending on mRunningState).
             self->mRunningState = RunningState::Running;
           }
           self->mMimeType = mime;
           self->mRecorder->SetMimeType(self->mMimeType);
-          auto startEvent = MakeRefPtr<DispatchStartEventRunnable>(self);
+          auto startEvent = MakeRefPtr<DispatchEventRunnable>(
+              self, NS_LITERAL_STRING("start"));
           startEvent->Run();
         }
       }
       return NS_OK;
     }));
   }
 
   void MediaEncoderDataAvailable() {
@@ -1318,58 +1326,63 @@ void MediaRecorder::Start(const Optional
   mStartTime = TimeStamp::Now();
   Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
 }
 
 void MediaRecorder::Stop(ErrorResult& aResult) {
   LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
   MediaRecorderReporter::RemoveMediaRecorder(this);
   if (mState == RecordingState::Inactive) {
-    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   mState = RecordingState::Inactive;
   MOZ_ASSERT(mSessions.Length() > 0);
   mSessions.LastElement()->Stop();
 }
 
 void MediaRecorder::Pause(ErrorResult& aResult) {
-  LOG(LogLevel::Debug, ("MediaRecorder.Pause"));
+  LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
   if (mState == RecordingState::Inactive) {
     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  if (mState == RecordingState::Paused) {
+    return;
+  }
+
   MOZ_ASSERT(mSessions.Length() > 0);
   nsresult rv = mSessions.LastElement()->Pause();
   if (NS_FAILED(rv)) {
     NotifyError(rv);
     return;
   }
 
   mState = RecordingState::Paused;
-  DispatchSimpleEvent(NS_LITERAL_STRING("pause"));
 }
 
 void MediaRecorder::Resume(ErrorResult& aResult) {
-  LOG(LogLevel::Debug, ("MediaRecorder.Resume"));
+  LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
   if (mState == RecordingState::Inactive) {
     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  if (mState == RecordingState::Recording) {
+    return;
+  }
+
   MOZ_ASSERT(mSessions.Length() > 0);
   nsresult rv = mSessions.LastElement()->Resume();
   if (NS_FAILED(rv)) {
     NotifyError(rv);
     return;
   }
 
   mState = RecordingState::Recording;
-  DispatchSimpleEvent(NS_LITERAL_STRING("resume"));
 }
 
 void MediaRecorder::RequestData(ErrorResult& aResult) {
   if (mState == RecordingState::Inactive) {
     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   MOZ_ASSERT(mSessions.Length() > 0);
@@ -1571,25 +1584,22 @@ nsresult MediaRecorder::CreateAndDispatc
 
 void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return;
   }
 
-  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
-  event->InitEvent(aStr, false, false);
-  event->SetTrusted(true);
-
-  IgnoredErrorResult res;
-  DispatchEvent(*event, res);
-  if (res.Failed()) {
+  rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr);
+  if (NS_FAILED(rv)) {
+    LOG(LogLevel::Error,
+        ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed  %p",
+         this));
     NS_ERROR("Failed to dispatch the event!!!");
-    return;
   }
 }
 
 void MediaRecorder::NotifyError(nsresult aRv) {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return;
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -570,36 +570,38 @@ static cdm::EncryptionScheme ConvertToCd
 
 static void InitInputBuffer(const CDMInputBuffer& aBuffer,
                             nsTArray<cdm::SubsampleEntry>& aSubSamples,
                             cdm::InputBuffer_2& aInputBuffer) {
   aInputBuffer.data = aBuffer.mData().get<uint8_t>();
   aInputBuffer.data_size = aBuffer.mData().Size<uint8_t>();
 
   if (aBuffer.mEncryptionScheme() > GMPEncryptionScheme::kGMPEncryptionNone) {
-    // Cbcs is not yet supported, so we expect only cenc if the buffer us
-    // encrypted
     MOZ_ASSERT(aBuffer.mEncryptionScheme() ==
-               GMPEncryptionScheme::kGMPEncryptionCenc);
+                   GMPEncryptionScheme::kGMPEncryptionCenc ||
+               aBuffer.mEncryptionScheme() ==
+                   GMPEncryptionScheme::kGMPEncryptionCbcs);
     aInputBuffer.key_id = aBuffer.mKeyId().Elements();
     aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
 
     aInputBuffer.iv = aBuffer.mIV().Elements();
     aInputBuffer.iv_size = aBuffer.mIV().Length();
 
     aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
     for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
       aSubSamples.AppendElement(cdm::SubsampleEntry{aBuffer.mClearBytes()[i],
                                                     aBuffer.mCipherBytes()[i]});
     }
     aInputBuffer.subsamples = aSubSamples.Elements();
     aInputBuffer.num_subsamples = aSubSamples.Length();
     aInputBuffer.encryption_scheme =
         ConvertToCdmEncryptionScheme(aBuffer.mEncryptionScheme());
   }
+  aInputBuffer.pattern.crypt_byte_block = aBuffer.mCryptByteBlock();
+  aInputBuffer.pattern.skip_byte_block = aBuffer.mSkipByteBlock();
   aInputBuffer.timestamp = aBuffer.mTimestamp();
 }
 
 bool ChromiumCDMChild::HasShmemOfSize(size_t aSize) const {
   for (const ipc::Shmem& shmem : mBuffers) {
     if (shmem.Size<uint8_t>() == aSize) {
       return true;
     }
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -259,25 +259,33 @@ bool ChromiumCDMParent::InitCDMInputBuff
     default:
       GMP_LOG(
           "InitCDMInputBuffer got unexpected encryption scheme with "
           "value of %" PRIu8 ". Treating as no encryption.",
           static_cast<uint8_t>(crypto.mCryptoScheme));
       MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
       break;
   }
+
+  const nsTArray<uint8_t>& iv =
+      encryptionScheme != GMPEncryptionScheme::kGMPEncryptionCbcs
+          ? crypto.mIV
+          : crypto.mConstantIV;
   aBuffer = gmp::CDMInputBuffer(
-      shmem, crypto.mKeyId, crypto.mIV, aSample->mTime.ToMicroseconds(),
+      shmem, crypto.mKeyId, iv, aSample->mTime.ToMicroseconds(),
       aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
-      crypto.mEncryptedSizes, encryptionScheme);
+      crypto.mEncryptedSizes, crypto.mCryptByteBlock, crypto.mSkipByteBlock,
+      encryptionScheme);
   MOZ_ASSERT(
       aBuffer.mEncryptionScheme() == GMPEncryptionScheme::kGMPEncryptionNone ||
           aBuffer.mEncryptionScheme() ==
-              GMPEncryptionScheme::kGMPEncryptionCenc,
-      "aBuffer should use either no encryption or cenc, other kinds are not "
+              GMPEncryptionScheme::kGMPEncryptionCenc ||
+          aBuffer.mEncryptionScheme() ==
+              GMPEncryptionScheme::kGMPEncryptionCbcs,
+      "aBuffer should use no encryption, cenc, or cbcs, other kinds are not "
       "yet supported");
   return true;
 }
 
 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
   GMP_LOG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32, aSizeInBytes);
   Shmem shmem;
   if (!AllocShmem(aSizeInBytes, Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
--- a/dom/media/gmp/GMPTypes.ipdlh
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -51,16 +51,18 @@ struct GMPVideoi420FrameData
 struct CDMInputBuffer {
   Shmem mData;
   uint8_t[] mKeyId;
   uint8_t[] mIV;
   int64_t mTimestamp;
   int64_t mDuration;
   uint16_t[] mClearBytes;
   uint32_t[] mCipherBytes;
+  uint8_t mCryptByteBlock;
+  uint8_t mSkipByteBlock;
   GMPEncryptionScheme mEncryptionScheme;
 };
 
 struct CDMVideoDecoderConfig {
   uint32_t mCodec;
   uint32_t mProfile;
   uint32_t mFormat;
   int32_t mImageWidth;
--- a/dom/media/test/test_mediarecorder_state_transition.html
+++ b/dom/media/test/test_mediarecorder_state_transition.html
@@ -11,17 +11,17 @@
 <script class="testbody" type="text/javascript">
 var manager = new MediaTestManager;
 
 // List of operation tests for media recorder objects to verify if running
 // these operations should result in an exception or not
 var operationTests = [
   {
     operations: ['stop'],
-    isValid: false
+    isValid: true
   },
   {
     operations: ['requestData'],
     isValid: false
   },
   {
     operations: ['pause'],
     isValid: false
@@ -65,17 +65,17 @@ var operationTests = [
     isValid: false
   },
   {
     operations: ['requestData', 'start'],
     isValid: false
   },
   {
     operations: ['stop', 'start'],
-    isValid: false
+    isValid: true
   },
   {
     operations: ['start', 'stop'],
     isValid: true
   },
   {
     operations: ['start', 'stop'],
     isValid: true,
@@ -151,17 +151,17 @@ var operationTests = [
     isValid: false
   },
   {
     operations: ['start', 'stop', 'requestData'],
     isValid: false
   },
   {
     operations: ['start', 'stop', 'stop'],
-    isValid: false
+    isValid: true
   },
   {
     operations: ['start', 'pause', 'resume', 'resume'],
     isValid: true
   },
   {
     operations: ['start', 'pause', 'resume', 'resume'],
     isValid: true,
--- a/gfx/ipc/GPUProcessHost.cpp
+++ b/gfx/ipc/GPUProcessHost.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GPUProcessHost.h"
 #include "chrome/common/process_watcher.h"
 #include "gfxPrefs.h"
 #include "mozilla/gfx/Logging.h"
 #include "nsITimer.h"
 #include "mozilla/Preferences.h"
+#include "VRGPUChild.h"
 
 namespace mozilla {
 namespace gfx {
 
 using namespace ipc;
 
 GPUProcessHost::GPUProcessHost(Listener* aListener)
     : GeckoChildProcessHost(GeckoProcessType_GPU),
@@ -145,16 +146,19 @@ void GPUProcessHost::Shutdown() {
 
   if (mGPUChild) {
     // OnChannelClosed uses this to check if the shutdown was expected or
     // unexpected.
     mShutdownRequested = true;
 
     // The channel might already be closed if we got here unexpectedly.
     if (!mChannelClosed) {
+      if (VRGPUChild::IsCreated()) {
+        VRGPUChild::Get()->Close();
+      }
       mGPUChild->SendShutdownVR();
       mGPUChild->Close();
     }
 
 #ifndef NS_FREE_PERMANENT_DATA
     // No need to communicate shutdown, the GPU process doesn't need to
     // communicate anything back.
     KillHard("NormalShutdown");
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -373,17 +373,17 @@ class gfxPrefs final {
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.autoactivate.enabled",           VRAutoActivateEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.controller_trigger_threshold",   VRControllerTriggerThreshold, float, 0.1f);
-  DECL_GFX_PREF(Once, "dom.vr.external.enabled",               VRExternalEnabled, bool, true);
+  DECL_GFX_PREF(Once, "dom.vr.external.enabled",               VRExternalEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.external.notdetected.timeout",   VRExternalNotDetectedTimeout, int32_t, 60000);
   DECL_GFX_PREF(Live, "dom.vr.external.quit.timeout",          VRExternalQuitTimeout, int32_t, 10000);
   DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.oculus.invisible.enabled",       VROculusInvisibleEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.oculus.present.timeout",         VROculusPresentTimeout, int32_t, 500);
   DECL_GFX_PREF(Live, "dom.vr.oculus.quit.timeout",            VROculusQuitTimeout, int32_t, 10000);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -73,17 +73,17 @@ VRManager::VRManager()
   MOZ_COUNT_CTOR(VRManager);
   MOZ_ASSERT(sVRManagerSingleton == nullptr);
 
   RefPtr<VRSystemManager> mgr;
 
 #if !defined(MOZ_WIDGET_ANDROID)
   // The VR Service accesses all hardware from a separate process
   // and replaces the other VRSystemManager when enabled.
-  if (!gfxPrefs::VRProcessEnabled()) {
+  if (!gfxPrefs::VRProcessEnabled() || !XRE_IsGPUProcess()) {
     VRServiceManager::Get().CreateService();
   }
   if (VRServiceManager::Get().IsServiceValid()) {
     mExternalManager =
         VRSystemManagerExternal::Create(VRServiceManager::Get().GetAPIShmem());
   }
   if (mExternalManager) {
     mManagers.AppendElement(mExternalManager);
@@ -122,30 +122,33 @@ void VRManager::Destroy() {
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Destroy();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
   if (VRServiceManager::Get().IsServiceValid()) {
     VRServiceManager::Get().Shutdown();
   }
 #endif
+  Shutdown();
   mInitialized = false;
 }
 
 void VRManager::Shutdown() {
   mVRDisplays.Clear();
   mVRControllers.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Shutdown();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
   if (VRServiceManager::Get().IsServiceValid()) {
     VRServiceManager::Get().Stop();
   }
-  if (gfxPrefs::VRProcessEnabled() && mVRServiceStarted) {
+  // XRE_IsGPUProcess() is helping us to check some platforms like
+  // Win 7 try which are not using GPU process but VR process is enabled.
+  if (XRE_IsGPUProcess() && gfxPrefs::VRProcessEnabled() && mVRServiceStarted) {
     RefPtr<Runnable> task = NS_NewRunnableFunction(
         "VRServiceManager::ShutdownVRProcess",
         []() -> void { VRServiceManager::Get().ShutdownVRProcess(); });
     NS_DispatchToMainThread(task.forget());
   }
 #endif
   mVRServiceStarted = false;
 }
@@ -430,23 +433,25 @@ void VRManager::EnumerateVRDisplays() {
    * and VR Process before enumeration.
    * We don't want to start this until we will
    * actualy enumerate, to avoid continuously
    * re-launching the thread/process when
    * no hardware is found or a VR software update
    * is in progress
    */
 #if !defined(MOZ_WIDGET_ANDROID)
-  if (gfxPrefs::VRProcessEnabled() && !mVRServiceStarted) {
-    VRServiceManager::Get().CreateVRProcess();
-    mVRServiceStarted = true;
-  } else if (!gfxPrefs::VRProcessEnabled()) {
-    if (VRServiceManager::Get().IsServiceValid()) {
-      VRServiceManager::Get().Start();
+  if (!mVRServiceStarted) {
+    if (XRE_IsGPUProcess() && gfxPrefs::VRProcessEnabled()) {
+      VRServiceManager::Get().CreateVRProcess();
       mVRServiceStarted = true;
+    } else {
+      if (VRServiceManager::Get().IsServiceValid()) {
+        VRServiceManager::Get().Start();
+        mVRServiceStarted = true;
+      }
     }
   }
 #endif
 
   /**
    * VRSystemManagers are inserted into mManagers in
    * a strict order of priority.  The managers for the
    * most device-specialized API's will have a chance
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -481,17 +481,18 @@ void VRSystemManagerExternal::OpenShmem(
   }
 
 #elif defined(XP_WIN)
   if (mShmemFile == NULL) {
     if (gfxPrefs::VRProcessEnabled()) {
       mShmemFile =
           CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
                              sizeof(VRExternalShmem), kShmemName);
-      MOZ_ASSERT(GetLastError() == 0);
+      MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS);
+      MOZ_ASSERT(mShmemFile);
     } else {
       mShmemFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, kShmemName);
     }
 
     if (mShmemFile == NULL) {
       // TODO - Implement logging
       CloseShmem();
       return;
--- a/gfx/vr/ipc/VRGPUChild.cpp
+++ b/gfx/vr/ipc/VRGPUChild.cpp
@@ -37,13 +37,24 @@ static StaticRefPtr<VRGPUChild> sVRGPUCh
 
 /* static */ VRGPUChild* VRGPUChild::Get() {
   MOZ_ASSERT(IsCreated(), "VRGPUChild haven't initialized yet.");
   return sVRGPUChildSingleton;
 }
 
 /*static*/ void VRGPUChild::Shutdown() {
   MOZ_ASSERT(NS_IsMainThread());
+  if (sVRGPUChildSingleton && !sVRGPUChildSingleton->IsClosed()) {
+    sVRGPUChildSingleton->Close();
+  }
   sVRGPUChildSingleton = nullptr;
 }
 
+void VRGPUChild::ActorDestroy(ActorDestroyReason aWhy) {
+  mClosed = true;
+}
+
+bool VRGPUChild::IsClosed() {
+  return mClosed;
+}
+
 }  // namespace gfx
 }  // namespace mozilla
--- a/gfx/vr/ipc/VRGPUChild.h
+++ b/gfx/vr/ipc/VRGPUChild.h
@@ -16,20 +16,25 @@ class VRGPUChild final : public PVRGPUCh
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUChild);
 
   static VRGPUChild* Get();
   static bool InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint);
   static bool IsCreated();
   static void Shutdown();
 
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  bool IsClosed();
+
  protected:
-  explicit VRGPUChild() {}
-  ~VRGPUChild() {}
+  explicit VRGPUChild() : mClosed(false) {}
+  ~VRGPUChild() = default;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(VRGPUChild);
+
+  bool mClosed;
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // GFX_VR_GPU_CHILD_H
\ No newline at end of file
--- a/gfx/vr/ipc/VRGPUParent.cpp
+++ b/gfx/vr/ipc/VRGPUParent.cpp
@@ -8,45 +8,51 @@
 
 #include "mozilla/ipc/ProcessChild.h"
 
 namespace mozilla {
 namespace gfx {
 
 using namespace ipc;
 
-VRGPUParent::VRGPUParent(ProcessId aChildProcessId) {
+VRGPUParent::VRGPUParent(ProcessId aChildProcessId)
+ : mClosed(false) {
   MOZ_COUNT_CTOR(VRGPUParent);
   MOZ_ASSERT(NS_IsMainThread());
 
   SetOtherProcessId(aChildProcessId);
 }
 
+VRGPUParent::~VRGPUParent() {
+  MOZ_COUNT_DTOR(VRGPUParent);
+}
+
 void VRGPUParent::ActorDestroy(ActorDestroyReason aWhy) {
 #if !defined(MOZ_WIDGET_ANDROID)
   if (mVRService) {
     mVRService->Stop();
     mVRService = nullptr;
   }
 #endif
 
+  mClosed = true;
   MessageLoop::current()->PostTask(
       NewRunnableMethod("gfx::VRGPUParent::DeferredDestroy", this,
                         &VRGPUParent::DeferredDestroy));
 }
 
 void VRGPUParent::DeferredDestroy() { mSelfRef = nullptr; }
 
 /* static */ RefPtr<VRGPUParent> VRGPUParent::CreateForGPU(
     Endpoint<PVRGPUParent>&& aEndpoint) {
   RefPtr<VRGPUParent> vcp = new VRGPUParent(aEndpoint.OtherPid());
   MessageLoop::current()->PostTask(NewRunnableMethod<Endpoint<PVRGPUParent>&&>(
       "gfx::VRGPUParent::Bind", vcp, &VRGPUParent::Bind, std::move(aEndpoint)));
 
-  return vcp;
+  return vcp.forget();
 }
 
 void VRGPUParent::Bind(Endpoint<PVRGPUParent>&& aEndpoint) {
   if (!aEndpoint.Bind(this)) {
     return;
   }
 
   mSelfRef = this;
@@ -69,10 +75,14 @@ mozilla::ipc::IPCResult VRGPUParent::Rec
     mVRService->Stop();
     mVRService = nullptr;
   }
 #endif
 
   return IPC_OK();
 }
 
+bool VRGPUParent::IsClosed() {
+  return mClosed;
+}
+
 }  // namespace gfx
 }  // namespace mozilla
\ No newline at end of file
--- a/gfx/vr/ipc/VRGPUParent.h
+++ b/gfx/vr/ipc/VRGPUParent.h
@@ -12,34 +12,37 @@
 
 namespace mozilla {
 namespace gfx {
 
 class VRGPUParent final : public PVRGPUParent {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUParent)
 
  public:
-  explicit VRGPUParent(ProcessId aChildProcessId);
-
+  static RefPtr<VRGPUParent> CreateForGPU(Endpoint<PVRGPUParent>&& aEndpoint);
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
-  static RefPtr<VRGPUParent> CreateForGPU(Endpoint<PVRGPUParent>&& aEndpoint);
+  bool IsClosed();
 
  protected:
-  ~VRGPUParent() = default;
 
   void Bind(Endpoint<PVRGPUParent>&& aEndpoint);
   virtual mozilla::ipc::IPCResult RecvStartVRService() override;
   virtual mozilla::ipc::IPCResult RecvStopVRService() override;
 
  private:
+  explicit VRGPUParent(ProcessId aChildProcessId);
+  ~VRGPUParent();
+
   void DeferredDestroy();
 
   RefPtr<VRGPUParent> mSelfRef;
 #if !defined(MOZ_WIDGET_ANDROID)
   RefPtr<VRService> mVRService;
 #endif
+  bool mClosed;
+
   DISALLOW_COPY_AND_ASSIGN(VRGPUParent);
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // GFX_VR_CONTENT_PARENT_H
--- a/gfx/vr/ipc/VRParent.cpp
+++ b/gfx/vr/ipc/VRParent.cpp
@@ -3,16 +3,17 @@
 /* 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 "VRParent.h"
 #include "VRGPUParent.h"
 #include "VRManager.h"
 #include "gfxConfig.h"
+#include "nsDebugImpl.h"
 
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/ipc/ProcessChild.h"
 
 #if defined(XP_WIN)
 #include "mozilla/gfx/DeviceManagerDx.h"
 #endif
 
@@ -91,16 +92,19 @@ mozilla::ipc::IPCResult VRParent::RecvOp
   return IPC_OK();
 }
 
 void VRParent::ActorDestroy(ActorDestroyReason aWhy) {
   if (AbnormalShutdown == aWhy) {
     NS_WARNING("Shutting down VR process early due to a crash!");
     ProcessChild::QuickExit();
   }
+  if (!mVRGPUParent->IsClosed()) {
+    mVRGPUParent->Close();
+  }
 
   mVRGPUParent = nullptr;
 #if defined(XP_WIN)
   DeviceManagerDx::Shutdown();
 #endif
   gfxVars::Shutdown();
   gfxConfig::Shutdown();
   gfxPrefs::DestroySingleton();
@@ -118,16 +122,18 @@ bool VRParent::Init(base::ProcessId aPar
     return false;
   }
 
   // Now it's safe to start IPC.
   if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
     return false;
   }
 
+  nsDebugImpl::SetMultiprocessMode("VR");
+
   // This must be checked before any IPDL message, which may hit sentinel
   // errors due to parent and content processes having different
   // versions.
   MessageChannel* channel = GetIPCChannel();
   if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) {
     // We need to quit this process if the buildID doesn't match the parent's.
     // This can occur when an update occurred in the background.
     ProcessChild::QuickExit();
--- a/gfx/vr/ipc/VRProcessChild.cpp
+++ b/gfx/vr/ipc/VRProcessChild.cpp
@@ -21,18 +21,16 @@ VRProcessChild::VRProcessChild(ProcessId
 VRProcessChild::~VRProcessChild() { sVRParent = nullptr; }
 
 /*static*/ VRParent* VRProcessChild::GetVRParent() {
   MOZ_ASSERT(sVRParent);
   return sVRParent;
 }
 
 bool VRProcessChild::Init(int aArgc, char* aArgv[]) {
-  BackgroundHangMonitor::Startup();
-
   char* parentBuildID = nullptr;
   for (int i = 1; i < aArgc; i++) {
     if (!aArgv[i]) {
       continue;
     }
     if (strcmp(aArgv[i], "-parentBuildID") == 0) {
       parentBuildID = aArgv[i + 1];
     }
--- a/gfx/vr/service/OculusSession.cpp
+++ b/gfx/vr/service/OculusSession.cpp
@@ -201,16 +201,20 @@ OculusSession::OculusSession()
       mInputLayout(nullptr),
       mRemainingVibrateTime{},
       mHapticPulseIntensity{},
       mIsPresenting(false) {}
 
 OculusSession::~OculusSession() { Shutdown(); }
 
 bool OculusSession::Initialize(mozilla::gfx::VRSystemState& aSystemState) {
+  if (!gfxPrefs::VREnabled() || !gfxPrefs::VROculusEnabled()) {
+    return false;
+  }
+
   if (!CreateD3DObjects()) {
     return false;
   }
   if (!CreateShaders()) {
     return false;
   }
   // Ideally, we should move LoadOvrLib() up to the first line to avoid
   // unnecessary D3D objects creation. But it will cause a WPT fail in Win 7
@@ -525,47 +529,55 @@ bool OculusSession::LoadOvrLib() {
     // Already loaded, early exit
     return true;
   }
 #if defined(_WIN32)
   nsTArray<nsString> libSearchPaths;
   nsString libName;
   nsString searchPath;
 
-  static const char dirSep = '\\';
-  static const int pathLen = 260;
-  searchPath.SetCapacity(pathLen);
-  int realLen =
-      ::GetSystemDirectoryW(char16ptr_t(searchPath.BeginWriting()), pathLen);
-  if (realLen != 0 && realLen < pathLen) {
-    searchPath.SetLength(realLen);
-    libSearchPaths.AppendElement(searchPath);
+  for (;;) {
+    UINT requiredLength = ::GetSystemDirectoryW(
+        char16ptr_t(searchPath.BeginWriting()), searchPath.Length());
+    if (!requiredLength) {
+      break;
+    }
+    if (requiredLength < searchPath.Length()) {
+      searchPath.Truncate(requiredLength);
+      libSearchPaths.AppendElement(searchPath);
+      break;
+    }
+    searchPath.SetLength(requiredLength);
   }
   libName.AppendPrintf("LibOVRRT%d_%d.dll", BUILD_BITS, OVR_PRODUCT_VERSION);
 
   // search the path/module dir
   libSearchPaths.InsertElementsAt(0, 1, EmptyString());
 
   // If the env var is present, we override libName
   if (_wgetenv(L"OVR_LIB_PATH")) {
     searchPath = _wgetenv(L"OVR_LIB_PATH");
     libSearchPaths.InsertElementsAt(0, 1, searchPath);
   }
 
   if (_wgetenv(L"OVR_LIB_NAME")) {
     libName = _wgetenv(L"OVR_LIB_NAME");
   }
 
+  if (libName.IsEmpty()) {
+    return false;
+  }
+
   for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) {
     nsString& libPath = libSearchPaths[i];
     nsString fullName;
     if (libPath.Length() == 0) {
       fullName.Assign(libName);
     } else {
-      fullName.AppendPrintf("%s%c%s", libPath.get(), dirSep, libName.get());
+      fullName.Assign(libPath + NS_LITERAL_STRING(u"\\") + libName);
     }
 
     mOvrLib = LoadLibraryWithFlags(fullName.get());
     if (mOvrLib) {
       break;
     }
   }
 #else
--- a/gfx/vr/service/VRServiceManager.cpp
+++ b/gfx/vr/service/VRServiceManager.cpp
@@ -35,17 +35,19 @@ void VRServiceManager::CreateVRProcess()
 
   NS_DispatchToMainThread(task.forget());
 }
 
 void VRServiceManager::ShutdownVRProcess() {
   if (VRGPUChild::IsCreated()) {
     VRGPUChild* vrGPUChild = VRGPUChild::Get();
     vrGPUChild->SendStopVRService();
-    vrGPUChild->Close();
+    if (!vrGPUChild->IsClosed()) {
+      vrGPUChild->Close();
+    }
     VRGPUChild::Shutdown();
   }
   if (gfxPrefs::VRProcessEnabled()) {
     // Using PGPU channel to tell the main process
     // to shutdown VR process.
     gfx::GPUParent* gpu = GPUParent::GetSingleton();
     MOZ_ASSERT(gpu);
     Unused << gpu->SendShutdownVRProcess();
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -10,17 +10,17 @@ use clip_scroll_tree::{ClipScrollTree, R
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
-use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind};
+use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
 use prim_store::image::ImageSource;
 use render_backend::FrameResources;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
@@ -578,17 +578,17 @@ impl AlphaBatchBuilder {
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
-        if prim_instance.bounding_rect.is_none() {
+        if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
         debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
         let transform_id = transforms
             .get_id(
@@ -596,25 +596,25 @@ impl AlphaBatchBuilder {
                 root_spatial_node_index,
                 ctx.clip_scroll_tree,
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
-        let bounding_rect = prim_instance.bounding_rect
-                                         .as_ref()
-                                         .expect("bug");
+        let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
+        let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
+
         let z_id = z_generator.next();
 
         // Get the clip task address for the global primitive, if one was set.
         let clip_task_address = get_clip_task_address(
             &ctx.scratch.clip_mask_instances,
-            prim_instance.clip_task_index,
+            prim_info.clip_task_index,
             0,
             render_tasks,
         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
         let prim_common_data = &ctx.resources.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
             prim_common_data.prim_size,
@@ -626,17 +626,17 @@ impl AlphaBatchBuilder {
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -686,27 +686,27 @@ impl AlphaBatchBuilder {
                         SegmentInstanceData {
                             textures: BatchTextures::color(cache_item.texture_id),
                             user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
                         }
                     );
                 }
 
                 let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::instanced(
                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
@@ -732,34 +732,34 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => {
                 let run = &ctx.prim_store.text_runs[run_index];
                 let subpx_dir = run.used_font.get_subpx_dir();
 
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
                 let prim_data = &ctx.resources.text_run_data_store[data_handle];
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let alpha_batch_list = &mut self.batch_lists.last_mut().unwrap().alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
 
@@ -886,27 +886,27 @@ impl AlphaBatchBuilder {
                         )
                     }
                 };
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
                 let blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     BlendMode::PremultipliedAlpha
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -938,28 +938,30 @@ impl AlphaBatchBuilder {
             }
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
                 let prim_cache_address = gpu_cache.get_address(&picture.gpu_location);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: picture.local_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 match picture.context_3d {
                     // Convert all children of the 3D hierarchy root into batches.
                     Picture3DContext::In { root_data: Some(ref list), .. } => {
                         for child in list {
                             let prim_instance = &picture.prim_list.prim_instances[child.anchor];
+                            let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
+
                             let pic_index = match prim_instance.kind {
                                 PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::NormalBorder { .. } |
                                 PrimitiveInstanceKind::ImageBorder { .. } |
                                 PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::YuvImage { .. } |
@@ -967,27 +969,28 @@ impl AlphaBatchBuilder {
                                 PrimitiveInstanceKind::LinearGradient { .. } |
                                 PrimitiveInstanceKind::RadialGradient { .. } |
                                 PrimitiveInstanceKind::Clear { .. } => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
+
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
                                 &ctx.scratch.clip_mask_instances,
-                                prim_instance.clip_task_index,
+                                prim_info.clip_task_index,
                                 0,
                                 render_tasks,
                             ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                             let prim_header = PrimitiveHeader {
                                 local_rect: pic.local_rect,
-                                local_clip_rect: prim_instance.combined_local_clip_rect,
+                                local_clip_rect: prim_info.combined_local_clip_rect,
                                 task_address,
                                 specific_prim_address: GpuCacheAddress::invalid(),
                                 clip_task_address,
                                 transform_id: child.transform_id,
                             };
 
                             let surface_index = pic
                                 .raster_config
@@ -1020,17 +1023,17 @@ impl AlphaBatchBuilder {
                             let instance = SplitCompositeInstance::new(
                                 prim_header_index,
                                 child.gpu_address,
                                 z_id,
                             );
 
                             self.current_batch_list().push_single_instance(
                                 key,
-                                &prim_instance.bounding_rect.as_ref().expect("bug"),
+                                &prim_info.clip_chain.pic_clip_rect,
                                 z_id,
                                 PrimitiveInstanceData::from(instance),
                             );
                         }
                     }
                     // Ignore the 3D pictures that are not in the root of preserve-3D
                     // hierarchy, since we process them with the root.
                     Picture3DContext::In { root_data: None, .. } => return,
@@ -1039,17 +1042,17 @@ impl AlphaBatchBuilder {
                 }
 
                 match picture.raster_config {
                     Some(ref raster_config) => {
                         match raster_config.composite_mode {
                             PictureCompositeMode::TileCache { .. } => {
                                 // Construct a local clip rect that ensures we only draw pixels where
                                 // the local bounds of the picture extend to within the edge tiles.
-                                let local_clip_rect = prim_instance
+                                let local_clip_rect = prim_info
                                     .combined_local_clip_rect
                                     .intersection(&picture.local_rect)
                                     .and_then(|rect| {
                                         rect.intersection(&picture.local_clip_rect)
                                     });
 
                                 if let Some(local_clip_rect) = local_clip_rect {
                                     // Step through each tile in the cache, and draw it with an image
@@ -1498,27 +1501,27 @@ impl AlphaBatchBuilder {
                 if cache_item.texture_id == TextureSource::Invalid {
                     return;
                 }
 
                 let textures = BatchTextures::color(cache_item.texture_id);
                 let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::shared(
                     BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
@@ -1544,30 +1547,30 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => {
                 let prim_data = &ctx.resources.prim_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index);
 
                 let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
                 let opacity = opacity.combine(prim_data.opacity);
 
                 let non_segmented_blend_mode = if !opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let batch_params = BrushBatchParameters::shared(
@@ -1582,17 +1585,17 @@ impl AlphaBatchBuilder {
                 } else {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -1607,17 +1610,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => {
                 let yuv_image_data = &ctx.resources.yuv_image_data_store[data_handle].kind;
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
 
@@ -1671,17 +1674,17 @@ impl AlphaBatchBuilder {
                         uv_rect_addresses[2],
                     ],
                     0,
                 );
 
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let non_segmented_blend_mode = if !prim_common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
@@ -1690,17 +1693,17 @@ impl AlphaBatchBuilder {
                 } else {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -1715,17 +1718,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
                 let image_data = &ctx.resources.image_data_store[data_handle].kind;
                 let common_data = &ctx.resources.image_data_store[data_handle].common;
                 let image_instance = &ctx.prim_store.images[image_instance_index];
                 let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index);
@@ -1764,17 +1767,17 @@ impl AlphaBatchBuilder {
                     }
 
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
                     let opacity = opacity.combine(common_data.opacity);
 
                     let non_segmented_blend_mode = if !opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1794,17 +1797,17 @@ impl AlphaBatchBuilder {
                     } else {
                         let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
                         let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                         (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                     };
 
                     let prim_header = PrimitiveHeader {
                         local_rect: prim_rect,
-                        local_clip_rect: prim_instance.combined_local_clip_rect,
+                        local_clip_rect: prim_info.combined_local_clip_rect,
                         task_address,
                         specific_prim_address: prim_cache_address,
                         clip_task_address,
                         transform_id,
                     };
 
                     let prim_header_index = prim_headers.push(
                         &prim_header,
@@ -1819,17 +1822,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     for tile in &image_instance.visible_tiles {
                         if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
@@ -1864,26 +1867,26 @@ impl AlphaBatchBuilder {
                 }
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.resources.linear_grad_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     clip_task_address,
                     transform_id,
                 };
 
                 if visible_tiles_range.is_empty() {
                     let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1918,17 +1921,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
 
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
@@ -1945,26 +1948,26 @@ impl AlphaBatchBuilder {
                 }
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.resources.radial_grad_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     clip_task_address,
                     transform_id,
                 };
 
                 if visible_tiles_range.is_empty() {
                     let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1999,17 +2002,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
 
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -457,16 +457,31 @@ pub struct ClipChainInstance {
     // If true, this clip chain requires allocation
     // of a clip mask.
     pub needs_mask: bool,
     // Combined clip rect in picture space (may
     // be more conservative that local_clip_rect).
     pub pic_clip_rect: PictureRect,
 }
 
+impl ClipChainInstance {
+    pub fn empty() -> Self {
+        ClipChainInstance {
+            clips_range: ClipNodeRange {
+                first: 0,
+                count: 0,
+            },
+            local_clip_rect: LayoutRect::zero(),
+            has_non_local_clips: false,
+            needs_mask: false,
+            pic_clip_rect: PictureRect::zero(),
+        }
+    }
+}
+
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_chain_nodes: Vec::new(),
             clip_node_instances: Vec::new(),
             clip_node_info: Vec::new(),
             clip_node_collectors: Vec::new(),
         }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -65,16 +65,30 @@ pub struct FrameBuilder {
     /// that can optionally be consumed by this frame builder.
     pending_retained_tiles: RetainedTiles,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 }
 
+pub struct FrameVisibilityContext<'a> {
+    pub clip_scroll_tree: &'a ClipScrollTree,
+    pub screen_world_rect: WorldRect,
+    pub device_pixel_scale: DevicePixelScale,
+    pub surfaces: &'a [SurfaceInfo],
+}
+
+pub struct FrameVisibilityState<'a> {
+    pub clip_store: &'a mut ClipStore,
+    pub resource_cache: &'a mut ResourceCache,
+    pub gpu_cache: &'a mut GpuCache,
+    pub scratch: &'a mut PrimitiveScratchBuffer,
+}
+
 pub struct FrameBuildingContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub max_local_clip: LayoutRect,
     pub debug_flags: DebugFlags,
@@ -193,16 +207,26 @@ impl FrameBuilder {
     /// a frame builder is replaced with a newly built scene.
     pub fn destroy(
         self,
         retained_tiles: &mut RetainedTiles,
     ) {
         self.prim_store.destroy(
             retained_tiles,
         );
+
+        // In general, the pending retained tiles are consumed by the frame
+        // builder the first time a frame is built after a new scene has
+        // arrived. However, if two scenes arrive in quick succession, the
+        // frame builder may not have had a chance to build a frame and
+        // consume the pending tiles. In this case, the pending tiles will
+        // be lost, causing a full invalidation of the entire screen. To
+        // avoid this, if there are still pending tiles, include them in
+        // the retained tiles passed to the next frame builder.
+        retained_tiles.tiles.extend(self.pending_retained_tiles.tiles);
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
@@ -290,16 +314,40 @@ impl FrameBuilder {
             resources,
             &self.clip_store,
             &pic_update_state.surfaces,
             gpu_cache,
             &mut retained_tiles,
             scratch,
         );
 
+        {
+            let visibility_context = FrameVisibilityContext {
+                device_pixel_scale,
+                clip_scroll_tree,
+                screen_world_rect,
+                surfaces: pic_update_state.surfaces,
+            };
+
+            let mut visibility_state = FrameVisibilityState {
+                resource_cache,
+                gpu_cache,
+                clip_store: &mut self.clip_store,
+                scratch,
+            };
+
+            self.prim_store.update_visibility(
+                self.root_pic_index,
+                ROOT_SURFACE_INDEX,
+                &visibility_context,
+                &mut visibility_state,
+                resources,
+            );
+        }
+
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             transforms: transform_palette,
             segment_builder: SegmentBuilder::new(),
@@ -331,17 +379,16 @@ impl FrameBuilder {
             scratch,
         );
 
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
             prim_list,
             pic_context,
             pic_state,
-            &mut frame_state,
         );
 
         let child_tasks = frame_state
             .surfaces[ROOT_SURFACE_INDEX.0]
             .take_render_tasks();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -4,32 +4,32 @@
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
 use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
 #[cfg(feature = "debug_renderer")]
 use api::{DebugFlags, DeviceVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode, ClipItem};
+use clip::{ClipStore, ClipChainId, ClipChainNode, ClipItem};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 #[cfg(feature = "debug_renderer")]
 use debug_colors;
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use intern::ItemUid;
 use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use plane_split::{Clipper, Polygon, Splitter};
 use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
 use prim_store::{get_raster_rects, CoordinateSpaceMapping, PrimitiveScratchBuffer};
-use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
+use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
 use print_tree::PrintTreePrinter;
 use render_backend::FrameResources;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::DocumentResources;
 use smallvec::SmallVec;
@@ -130,16 +130,19 @@ struct TileId(usize);
 pub struct Tile {
     /// The current world rect of thie tile.
     world_rect: WorldRect,
     /// The current local rect of this tile.
     pub local_rect: LayoutRect,
     /// The currently visible rect within this tile, updated per frame.
     /// If None, this tile is not currently visible.
     visible_rect: Option<WorldRect>,
+    /// The currently valid rect of the tile, used to invalidate
+    /// tiles that were only partially drawn.
+    valid_rect: WorldRect,
     /// Uniquely describes the content of this tile, in a way that can be
     /// (reasonably) efficiently hashed and compared.
     descriptor: TileDescriptor,
     /// Handle to the cached texture for this tile.
     pub handle: TextureCacheHandle,
     /// If true, this tile is marked valid, and the existing texture
     /// cache handle can be used. Tiles are invalidated during the
     /// build_dirty_regions method.
@@ -150,55 +153,62 @@ pub struct Tile {
     same_frames: usize,
     /// The tile id is stable between display lists and / or frames,
     /// if the tile is retained. Useful for debugging tile evictions.
     id: TileId,
     /// The set of transforms that affect primitives on this tile we
     /// care about. Stored as a set here, and then collected, sorted
     /// and converted to transform key values during post_update.
     transforms: FastHashSet<SpatialNodeIndex>,
+    /// A list of potentially important clips. We can't know if
+    /// they were important or can be discarded until we know the
+    /// tile cache bounding rect.
+    potential_clips: FastHashMap<RectangleKey, SpatialNodeIndex>,
 }
 
 impl Tile {
     /// Construct a new, invalid tile.
     fn new(
         id: TileId,
     ) -> Self {
         Tile {
             local_rect: LayoutRect::zero(),
             world_rect: WorldRect::zero(),
             visible_rect: None,
+            valid_rect: WorldRect::zero(),
             handle: TextureCacheHandle::invalid(),
             descriptor: TileDescriptor::new(),
             is_same_content: false,
             is_valid: false,
             same_frames: 0,
             transforms: FastHashSet::default(),
+            potential_clips: FastHashMap::default(),
             id,
         }
     }
 
     /// Clear the dependencies for a tile.
     fn clear(&mut self) {
         self.transforms.clear();
         self.descriptor.clear();
+        self.potential_clips.clear();
     }
 
     /// Update state related to whether a tile has the same
     /// content and is valid to use.
     fn update_validity(&mut self, tile_bounding_rect: &WorldRect) {
         // Check if the contents of the primitives, clips, and
         // other dependencies are the same.
         self.is_same_content &= self.descriptor.is_same_content();
 
         // The tile is only valid if:
         // - The content is the same *and*
-        // - The valid part of the tile is the same wrt to world clips.
+        // - The valid part of the tile includes the needed part.
         self.is_valid &= self.is_same_content;
-        self.is_valid &= self.descriptor.is_valid(&tile_bounding_rect);
+        self.is_valid &= self.valid_rect.contains_rect(tile_bounding_rect);
 
         // Update count of how many times this tile has had the same content.
         if !self.is_same_content {
             self.same_frames = 0;
         }
         self.same_frames += 1;
     }
 }
@@ -211,25 +221,16 @@ pub struct PrimitiveDescriptor {
     /// The origin in world space of this primitive.
     origin: WorldPoint,
     /// The first clip in the clip_uids array of clips that affect this tile.
     first_clip: u16,
     /// The number of clips that affect this primitive instance.
     clip_count: u16,
 }
 
-/// Defines the region of a primitive that exists on a tile.
-#[derive(Debug)]
-pub struct PrimitiveRegion {
-    /// The (prim relative) portion of on this tile.
-    prim_region: WorldRect,
-    /// Location within the tile.
-    tile_offset: WorldPoint,
-}
-
 /// Uniquely describes the content of this tile, in a way that can be
 /// (reasonably) efficiently hashed and compared.
 #[derive(Debug)]
 pub struct TileDescriptor {
     /// List of primitive instance unique identifiers. The uid is guaranteed
     /// to uniquely describe the content of the primitive template, while
     /// the other parameters describe the clip chain and instance params.
     prims: ComparableVec<PrimitiveDescriptor>,
@@ -245,107 +246,55 @@ pub struct TileDescriptor {
 
     /// List of image keys that this tile depends on.
     image_keys: ComparableVec<ImageKey>,
 
     /// The set of opacity bindings that this tile depends on.
     // TODO(gw): Ugh, get rid of all opacity binding support!
     opacity_bindings: ComparableVec<OpacityBinding>,
 
-    /// List of the required valid rectangles for each primitive.
-    needed_regions: Vec<PrimitiveRegion>,
-
-    /// List of the currently valid rectangles for each primitive.
-    current_regions: Vec<PrimitiveRegion>,
-
     /// List of the (quantized) transforms that we care about
     /// tracking for this tile.
     transforms: ComparableVec<TransformKey>,
 }
 
 impl TileDescriptor {
     fn new() -> Self {
         TileDescriptor {
             prims: ComparableVec::new(),
             clip_uids: ComparableVec::new(),
             clip_vertices: ComparableVec::new(),
             opacity_bindings: ComparableVec::new(),
             image_keys: ComparableVec::new(),
-            needed_regions: Vec::new(),
-            current_regions: Vec::new(),
             transforms: ComparableVec::new(),
         }
     }
 
     /// Clear the dependency information for a tile, when the dependencies
     /// are being rebuilt.
     fn clear(&mut self) {
         self.prims.reset();
         self.clip_uids.reset();
         self.clip_vertices.reset();
         self.opacity_bindings.reset();
         self.image_keys.reset();
-        self.needed_regions.clear();
         self.transforms.reset();
     }
 
     /// Return true if the content of the tile is the same
     /// as last frame. This doesn't check validity of the
     /// tile based on the currently valid regions.
     fn is_same_content(&self) -> bool {
         self.image_keys.is_valid() &&
         self.opacity_bindings.is_valid() &&
         self.clip_uids.is_valid() &&
         self.clip_vertices.is_valid() &&
         self.prims.is_valid() &&
         self.transforms.is_valid()
     }
-
-    /// Check if the tile is valid, given that the rest of the content is the same.
-    fn is_valid(&self, tile_bounding_rect: &WorldRect) -> bool {
-        // For a tile to be valid, it needs to ensure that the currently valid
-        // rect of each primitive encloses the required valid rect.
-        // TODO(gw): This is only needed for tiles that are partially rendered
-        //           (i.e. those clipped to edge of screen). We can make this much
-        //           faster by skipping this step for tiles that are not clipped!
-        // TODO(gw): For partial tiles that *do* need this test, we can probably
-        //           make it faster again by caching and checking the relative
-        //           transforms of primitives on this tile.
-        if self.needed_regions.len() == self.current_regions.len() {
-            for (needed, current) in self.needed_regions.iter().zip(self.current_regions.iter()) {
-                let needed_region = needed
-                    .prim_region
-                    .translate(&needed.tile_offset.to_vector())
-                    .intersection(tile_bounding_rect);
-
-                let needed_rect = match needed_region {
-                    Some(rect) => rect,
-                    None => continue,
-                };
-
-                let current_region = current
-                    .prim_region
-                    .translate(&current.tile_offset.to_vector())
-                    .intersection(tile_bounding_rect);
-
-                let current_rect = match current_region {
-                    Some(rect) => rect,
-                    None => return false,
-                };
-
-                if needed_rect != current_rect {
-                    return false;
-                }
-            }
-
-            true
-        } else {
-            false
-        }
-    }
 }
 
 /// Represents the dirty region of a tile cache picture.
 /// In future, we will want to support multiple dirty
 /// regions.
 #[derive(Debug)]
 pub struct DirtyRegion {
     pub dirty_world_rect: WorldRect,
@@ -784,16 +733,24 @@ impl TileCache {
         // Build the list of resources that this primitive has dependencies on.
         let mut opacity_bindings: SmallVec<[OpacityBinding; 4]> = SmallVec::new();
         let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
         let mut clip_vertices: SmallVec<[LayoutPoint; 8]> = SmallVec::new();
         let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
         let mut current_clip_chain_id = prim_instance.clip_chain_id;
         let mut clip_spatial_nodes = FastHashSet::default();
 
+        // TODO(gw): We only care about world clip rects that don't have the main
+        //           scroll root as an ancestor. It may be a worthwhile optimization
+        //           to check for these and skip them.
+        // TODO(gw): We could also trivially track and exclude the root iframe / content
+        //           clip chain id, since we know that will exist on every item but never
+        //           actually be relevant.
+        let mut world_clips: FastHashMap<RectangleKey, SpatialNodeIndex> = FastHashMap::default();
+
         // Some primitives can not be cached (e.g. external video images)
         let is_cacheable = prim_instance.is_cacheable(
             &resources,
             resource_cache,
         );
 
         // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
         // use it to calculate the local bounding rect for the tiles. If we include them
@@ -880,42 +837,53 @@ impl TileCache {
                     // Clips that are not in the root coordinate system are not axis-aligned,
                     // so we need to treat them as normal style clips with vertices.
                     if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
                         let local_rect = LayoutRect::new(
                             clip_chain_node.local_pos,
                             size,
                         );
 
-                        if let Some(clip_world_rect) = self.map_local_to_world.map(&local_rect) {
-                            // Even if this ends up getting clipped out by the current clip
-                            // stack, we want to ensure the primitive gets added to the tiles
-                            // below, to ensure invalidation isn't tripped up by the wrong
-                            // number of primitives that affect this tile.
-                            world_clip_rect = world_clip_rect
-                                .intersection(&clip_world_rect)
-                                .unwrap_or(WorldRect::zero());
+                        match self.map_local_to_world.map(&local_rect) {
+                            Some(clip_world_rect) => {
+                                // Even if this ends up getting clipped out by the current clip
+                                // stack, we want to ensure the primitive gets added to the tiles
+                                // below, to ensure invalidation isn't tripped up by the wrong
+                                // number of primitives that affect this tile.
+                                world_clip_rect = world_clip_rect
+                                    .intersection(&clip_world_rect)
+                                    .unwrap_or(WorldRect::zero());
+
+                                world_clips.insert(
+                                    clip_world_rect.into(),
+                                    clip_chain_node.spatial_node_index,
+                                );
+
+                                false
+                            }
+                            None => {
+                                true
+                            }
                         }
-
-                        false
                     } else {
                         true
                     }
                 }
                 ClipItem::Rectangle(_, ClipMode::ClipOut) |
                 ClipItem::RoundedRectangle(..) |
                 ClipItem::Image { .. } |
                 ClipItem::BoxShadow(..) => {
                     true
                 }
             };
 
+            clip_vertices.push(clip_chain_node.local_pos);
+            clip_chain_uids.push(clip_chain_node.handle.uid());
+
             if add_to_clip_deps {
-                clip_vertices.push(clip_chain_node.local_pos);
-                clip_chain_uids.push(clip_chain_node.handle.uid());
                 clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
             }
 
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         if include_clip_rect {
             self.world_bounding_rect = self.world_bounding_rect.union(&world_clip_rect);
@@ -934,27 +902,16 @@ impl TileCache {
                 let index = (y * self.tile_count.width + x) as usize;
                 let tile = &mut self.tiles[index];
 
                 // Work out the needed rect for the primitive on this tile.
                 // TODO(gw): We should be able to remove this for any tile that is not
                 //           a partially clipped tile, which would be a significant
                 //           optimization for the common case (non-clipped tiles).
 
-                // Get the required tile-local rect that this primitive occupies.
-                // Ensure that even if it's currently clipped out of this tile,
-                // we still insert a rect of zero size, so that the tile descriptor's
-                // needed rects array matches.
-                let prim_region = world_clip_rect.translate(&-world_rect.origin.to_vector());
-
-                tile.descriptor.needed_regions.push(PrimitiveRegion {
-                    prim_region,
-                    tile_offset: world_rect.origin - tile.world_rect.origin.to_vector(),
-                });
-
                 // Mark if the tile is cacheable at all.
                 tile.is_same_content &= is_cacheable;
 
                 // Include any image keys this tile depends on.
                 tile.descriptor.image_keys.extend_from_slice(&image_keys);
 
                 // // Include any opacity bindings this primitive depends on.
                 tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
@@ -968,16 +925,19 @@ impl TileCache {
                 });
                 tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
                 tile.descriptor.clip_vertices.extend_from_slice(&clip_vertices);
 
                 tile.transforms.insert(prim_instance.spatial_node_index);
                 for spatial_node_index in &clip_spatial_nodes {
                     tile.transforms.insert(*spatial_node_index);
                 }
+                for (world_rect, spatial_node_index) in &world_clips {
+                    tile.potential_clips.insert(world_rect.clone(), *spatial_node_index);
+                }
             }
         }
     }
 
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
@@ -1013,16 +973,27 @@ impl TileCache {
         );
 
         let local_clip_rect = map_surface_to_world
             .unmap(&self.world_bounding_rect)
             .expect("bug: unable to map local clip rect");
 
         // Step through each tile and invalidate if the dependencies have changed.
         for (i, tile) in self.tiles.iter_mut().enumerate() {
+            // Deal with any potential world clips. Check to see if they are
+            // outside the tile cache bounding rect. If they are, they're not
+            // relevant and we don't care if they move relative to the content
+            // itself. This avoids a lot of redundant invalidations.
+            for (clip_world_rect, spatial_node_index) in &tile.potential_clips {
+                let clip_world_rect = WorldRect::from(clip_world_rect.clone());
+                if !clip_world_rect.contains_rect(&self.world_bounding_rect) {
+                    tile.transforms.insert(*spatial_node_index);
+                }
+            }
+
             // Update tile transforms
             let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect();
             transform_spatial_nodes.sort();
             for spatial_node_index in transform_spatial_nodes {
                 let mapping: CoordinateSpaceMapping<LayoutPixel, PicturePixel> = CoordinateSpaceMapping::new(
                     self.spatial_node_index,
                     spatial_node_index,
                     frame_context.clip_scroll_tree,
@@ -1112,32 +1083,33 @@ impl TileCache {
                     );
 
                     let cache_item = resource_cache
                         .get_texture_cache_item(&tile.handle);
 
                     let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
                     let valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
 
+                    tile.valid_rect = visible_rect
+                        .intersection(&self.world_bounding_rect)
+                        .map(|rect| rect.translate(&-tile.world_rect.origin.to_vector()))
+                        .unwrap_or(WorldRect::zero());
+
                     // Store a blit operation to be done after drawing the
                     // frame in order to update the cached texture tile.
                     let dest_rect = (valid_rect * frame_context.device_pixel_scale).round().to_i32();
                     self.pending_blits.push(TileBlit {
                         target: cache_item,
                         src_offset: src_origin,
                         dest_offset: dest_rect.origin,
                         size: dest_rect.size,
                     });
 
                     // We can consider this tile valid now.
                     tile.is_valid = true;
-                    tile.descriptor.current_regions = mem::replace(
-                        &mut tile.descriptor.needed_regions,
-                        Vec::new(),
-                    );
                 }
             }
         }
 
         // Store the dirty region for drawing the main scene.
         self.dirty_region = if dirty_world_rect.is_empty() {
             None
         } else {
@@ -1672,17 +1644,17 @@ impl PicturePrimitive {
                 }
 
                 filter.is_visible()
             }
             _ => true,
         }
     }
 
-    fn is_visible(&self) -> bool {
+    pub fn is_visible(&self) -> bool {
         match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(ref filter)) => {
                 filter.is_visible()
             }
             _ => true,
         }
     }
 
@@ -1809,20 +1781,16 @@ impl PicturePrimitive {
 
                 (surface.raster_spatial_node_index, self.spatial_node_index, raster_config.surface_index)
             }
             None => {
                 (raster_spatial_node_index, surface_spatial_node_index, surface_index)
             }
         };
 
-        if self.raster_config.as_ref().map_or(false, |c| c.establishes_raster_root) {
-            frame_state.clip_store.push_raster_root(surface_spatial_node_index);
-        }
-
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             dirty_world_rect,
             frame_context.clip_scroll_tree,
         );
 
         let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
@@ -1897,55 +1865,49 @@ impl PicturePrimitive {
         Some((context, state, prim_list))
     }
 
     pub fn restore_context(
         &mut self,
         prim_list: PrimitiveList,
         context: PictureContext,
         state: PictureState,
-        frame_state: &mut FrameBuildingState,
-    ) -> Option<ClipNodeCollector> {
+    ) {
         self.prim_list = prim_list;
         self.state = Some((state, context));
-
-        if self.raster_config.as_ref().map_or(false, |c| c.establishes_raster_root) {
-            Some(frame_state.clip_store.pop_raster_root())
-        } else {
-            None
-        }
     }
 
     pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
         self.state.take().expect("bug: no state present!")
     }
 
     /// Add a primitive instance to the plane splitter. The function would generate
     /// an appropriate polygon, clip it against the frustum, and register with the
     /// given plane splitter.
     pub fn add_split_plane(
         splitter: &mut PlaneSplitter,
         transforms: &TransformPalette,
         prim_instance: &PrimitiveInstance,
         original_local_rect: LayoutRect,
+        combined_local_clip_rect: &LayoutRect,
         world_rect: WorldRect,
         plane_split_anchor: usize,
     ) -> bool {
         let transform = transforms
             .get_world_transform(prim_instance.spatial_node_index);
         let matrix = transform.cast();
 
         // Apply the local clip rect here, before splitting. This is
         // because the local clip rect can't be applied in the vertex
         // shader for split composites, since we are drawing polygons
         // rather that rectangles. The interpolation still works correctly
         // since we determine the UVs by doing a bilerp with a factor
         // from the original local rect.
         let local_rect = match original_local_rect
-            .intersection(&prim_instance.combined_local_clip_rect)
+            .intersection(combined_local_clip_rect)
         {
             Some(rect) => rect.cast(),
             None => return false,
         };
         let world_rect = world_rect.cast();
 
         match transform.transform_kind() {
             TransformedRectKind::AxisAligned => {
@@ -2293,17 +2255,16 @@ impl PicturePrimitive {
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
-        prim_local_rect: &LayoutRect,
         clipped_prim_bounding_rect: WorldRect,
         surface_index: SurfaceIndex,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
         let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
 
         if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
@@ -2329,17 +2290,17 @@ impl PicturePrimitive {
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             prim_instance.spatial_node_index,
             raster_spatial_node_index,
             pic_context.dirty_world_rect,
             frame_context.clip_scroll_tree,
         );
 
-        let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
+        let pic_rect = PictureRect::from_untyped(&self.local_rect.to_untyped());
 
         let (clipped, unclipped) = match get_raster_rects(
             pic_rect,
             &map_pic_to_raster,
             &map_raster_to_world,
             clipped_prim_bounding_rect,
             frame_context.device_pixel_scale,
         ) {
@@ -2563,24 +2524,24 @@ impl PicturePrimitive {
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  [brush specific data]
                     //  [segment_rect, segment data]
-                    let shadow_rect = prim_local_rect.translate(&offset);
+                    let shadow_rect = self.local_rect.translate(&offset);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
-                        prim_local_rect.size.width,
-                        prim_local_rect.size.height,
+                        self.local_rect.size.width,
+                        self.local_rect.size.height,
                         0.0,
                         0.0,
                     ]);
 
                     // segment rect / extra data
                     request.push(shadow_rect);
                     request.push([0.0, 0.0, 0.0, 0.0]);
                 }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1,33 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
 use api::{DeviceIntRect, DevicePixelScale, DeviceRect};
-use api::{FilterOp, ImageRendering, TileOffset, RepeatMode};
+use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, WorldPoint, WorldSize};
 use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
 use api::{PremultipliedColorF, PropertyBinding, Shadow};
 use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, AuHelpers};
-use api::LayoutPrimitiveInfo;
+use api::{LayoutPrimitiveInfo};
 #[cfg(feature = "debug_renderer")]
 use api::DevicePoint;
 use border::{get_max_scale_for_border, build_border_instances};
 use border::BorderSegmentCacheKey;
 use clip::{ClipStore};
-use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
-use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex, ROOT_SPATIAL_NODE_INDEX};
+use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 #[cfg(feature = "debug_renderer")]
 use debug_render::DebugItem;
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
-use frame_builder::PrimitiveContext;
+use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
 use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState, TileCacheUpdateState};
 use picture::{ClusterIndex, PrimitiveList, SurfaceIndex, SurfaceInfo, RetainedTiles, RasterConfig};
@@ -138,17 +138,17 @@ impl ops::Not for VisibleFace {
     fn not(self) -> Self {
         match self {
             VisibleFace::Front => VisibleFace::Back,
             VisibleFace::Back => VisibleFace::Front,
         }
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum CoordinateSpaceMapping<F, T> {
     Local,
     ScaleOffset(ScaleOffset),
     Transform(TypedTransform3D<f32, F, T>),
 }
 
 impl<F, T> CoordinateSpaceMapping<F, T> {
     pub fn new(
@@ -180,17 +180,17 @@ impl<F, T> CoordinateSpaceMapping<F, T> 
                 CoordinateSpaceMapping::Transform(
                     transform.with_source::<F>().with_destination::<T>()
                 )
             })
         }
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct SpaceMapper<F, T> {
     kind: CoordinateSpaceMapping<F, T>,
     pub ref_spatial_node_index: SpatialNodeIndex,
     pub current_target_spatial_node_index: SpatialNodeIndex,
     pub bounds: TypedRect<f32, T>,
 }
 
 impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
@@ -393,27 +393,47 @@ impl From<RectangleKey> for LayoutRect {
     fn from(key: RectangleKey) -> LayoutRect {
         LayoutRect {
             origin: LayoutPoint::new(key.x, key.y),
             size: LayoutSize::new(key.w, key.h),
         }
     }
 }
 
+impl From<RectangleKey> for WorldRect {
+    fn from(key: RectangleKey) -> WorldRect {
+        WorldRect {
+            origin: WorldPoint::new(key.x, key.y),
+            size: WorldSize::new(key.w, key.h),
+        }
+    }
+}
+
 impl From<LayoutRect> for RectangleKey {
     fn from(rect: LayoutRect) -> RectangleKey {
         RectangleKey {
             x: rect.origin.x,
             y: rect.origin.y,
             w: rect.size.width,
             h: rect.size.height,
         }
     }
 }
 
+impl From<WorldRect> for RectangleKey {
+    fn from(rect: WorldRect) -> RectangleKey {
+        RectangleKey {
+            x: rect.origin.x,
+            y: rect.origin.y,
+            w: rect.size.width,
+            h: rect.size.height,
+        }
+    }
+}
+
 /// A hashable SideOffset2D that can be used in primitive keys.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, MallocSizeOf, PartialEq)]
 pub struct SideOffsetsKey {
     pub top: f32,
     pub right: f32,
     pub bottom: f32,
@@ -1325,51 +1345,70 @@ pub enum PrimitiveInstanceKind {
     },
     /// Clear out a rect, used for special effects.
     Clear {
         /// Handle to the common interned data for this primitive.
         data_handle: PrimitiveDataHandle,
     },
 }
 
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct PrimitiveVisibilityIndex(pub u32);
+
+impl PrimitiveVisibilityIndex {
+    pub const INVALID: PrimitiveVisibilityIndex = PrimitiveVisibilityIndex(u32::MAX);
+}
+
+/// Information stored for a visible primitive about the visible
+/// rect and associated clip information.
+pub struct PrimitiveVisibility {
+    /// The clip chain instance that was built for this primitive.
+    pub clip_chain: ClipChainInstance,
+
+    /// The current world rect, clipped to screen / dirty rect boundaries.
+    // TODO(gw): This is only used by a small number of primitives.
+    //           It's probably faster to not store this and recalculate
+    //           on demand in those cases?
+    pub clipped_world_rect: WorldRect,
+
+    /// An index into the clip task instances array in the primitive
+    /// store. If this is ClipTaskIndex::INVALID, then the primitive
+    /// has no clip mask. Otherwise, it may store the offset of the
+    /// global clip mask task for this primitive, or the first of
+    /// a list of clip task ids (one per segment).
+    pub clip_task_index: ClipTaskIndex,
+
+    /// The current combined local clip for this primitive, from
+    /// the primitive local clip above and the current clip chain.
+    pub combined_local_clip_rect: LayoutRect,
+}
+
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
     /// the relevant information for the primitive
     /// can be found.
     pub kind: PrimitiveInstanceKind,
 
     /// Local space origin of this primitive. The size
     /// of the primitive is defined by the template.
     pub prim_origin: LayoutPoint,
 
-    /// The current combined local clip for this primitive, from
-    /// the primitive local clip above and the current clip chain.
-    pub combined_local_clip_rect: LayoutRect,
-
     #[cfg(debug_assertions)]
     pub id: PrimitiveDebugId,
 
     /// The last frame ID (of the `RenderTaskTree`) this primitive
     /// was prepared for rendering in.
     #[cfg(debug_assertions)]
     pub prepared_frame_id: FrameId,
 
-    /// The current minimal bounding rect of this primitive in picture space.
-    /// Includes the primitive rect, and any clipping rects from the same
-    /// coordinate system.
-    pub bounding_rect: Option<PictureRect>,
-
-    /// An index into the clip task instances array in the primitive
-    /// store. If this is ClipTaskIndex::INVALID, then the primitive
-    /// has no clip mask. Otherwise, it may store the offset of the
-    /// global clip mask task for this primitive, or the first of
-    /// a list of clip task ids (one per segment).
-    pub clip_task_index: ClipTaskIndex,
+    /// If this primitive is visible, an index into the instance
+    /// visibility scratch buffer. If not visible, INVALID.
+    pub visibility_info: PrimitiveVisibilityIndex,
 
     /// The cluster that this primitive belongs to. This is used
     /// for quickly culling out groups of primitives during the
     /// initial picture traversal pass.
     pub cluster_index: ClusterIndex,
 
     /// ID of the clip chain that this primitive is clipped by.
     pub clip_chain_id: ClipChainId,
@@ -1383,33 +1422,30 @@ impl PrimitiveInstance {
         prim_origin: LayoutPoint,
         kind: PrimitiveInstanceKind,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         PrimitiveInstance {
             prim_origin,
             kind,
-            combined_local_clip_rect: LayoutRect::zero(),
-            bounding_rect: None,
             #[cfg(debug_assertions)]
             prepared_frame_id: FrameId::INVALID,
             #[cfg(debug_assertions)]
             id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)),
-            clip_task_index: ClipTaskIndex::INVALID,
+            visibility_info: PrimitiveVisibilityIndex::INVALID,
             clip_chain_id,
             spatial_node_index,
             cluster_index: ClusterIndex::INVALID,
         }
     }
 
     // Reset any pre-frame state for this primitive.
     pub fn reset(&mut self) {
-        self.bounding_rect = None;
-        self.clip_task_index = ClipTaskIndex::INVALID;
+        self.visibility_info = PrimitiveVisibilityIndex::INVALID;
     }
 
     #[cfg(debug_assertions)]
     pub fn is_chased(&self) -> bool {
         PRIM_CHASE_ID.load(Ordering::SeqCst) == self.id.0
     }
 
     #[cfg(not(debug_assertions))]
@@ -1499,36 +1535,41 @@ pub struct PrimitiveScratchBuffer {
     /// that have opted into segment building. In future, this should be
     /// removed in favor of segment building during primitive interning.
     pub segment_instances: SegmentInstanceStorage,
 
     /// A list of visible tiles that tiled gradients use to store
     /// per-tile information.
     pub gradient_tiles: GradientTileStorage,
 
+    /// List of the visibility information for currently visible primitives.
+    pub prim_info: Vec<PrimitiveVisibility>,
+
     #[cfg(feature = "debug_renderer")]
     pub debug_items: Vec<DebugItem>,
 }
 
 impl PrimitiveScratchBuffer {
     pub fn new() -> Self {
         PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
             glyph_keys: GlyphKeyStorage::new(0),
             border_cache_handles: BorderHandleStorage::new(0),
             segments: SegmentStorage::new(0),
             segment_instances: SegmentInstanceStorage::new(0),
             gradient_tiles: GradientTileStorage::new(0),
             #[cfg(feature = "debug_renderer")]
             debug_items: Vec::new(),
+            prim_info: Vec::new(),
         }
     }
 
     pub fn recycle(&mut self) {
         recycle_vec(&mut self.clip_mask_instances);
+        recycle_vec(&mut self.prim_info);
         self.glyph_keys.recycle();
         self.border_cache_handles.recycle();
         self.segments.recycle();
         self.segment_instances.recycle();
         self.gradient_tiles.recycle();
         #[cfg(feature = "debug_renderer")]
         recycle_vec(&mut self.debug_items);
     }
@@ -1543,16 +1584,18 @@ impl PrimitiveScratchBuffer {
         self.border_cache_handles.clear();
 
         // TODO(gw): As in the previous code, the gradient tiles store GPU cache
         //           handles that are cleared (and thus invalidated + re-uploaded)
         //           every frame. This maintains the existing behavior, but we
         //           should fix this in the future to retain handles.
         self.gradient_tiles.clear();
 
+        self.prim_info.clear();
+
         #[cfg(feature = "debug_renderer")]
         self.debug_items.clear();
     }
 
     #[allow(dead_code)]
     #[cfg(feature = "debug_renderer")]
     pub fn push_debug_rect(
         &mut self,
@@ -1693,16 +1736,248 @@ impl PrimitiveStore {
                 children,
                 state,
                 frame_context,
                 gpu_cache,
             );
         }
     }
 
+    /// Update visibility pass - update each primitive visibility struct, and
+    /// build the clip chain instance if appropriate.
+    pub fn update_visibility(
+        &mut self,
+        pic_index: PictureIndex,
+        parent_surface_index: SurfaceIndex,
+        frame_context: &FrameVisibilityContext,
+        frame_state: &mut FrameVisibilityState,
+        resources: &mut FrameResources,
+    ) {
+        let (mut prim_list, surface_index, apply_local_clip_rect) = {
+            let pic = &mut self.pictures[pic_index.0];
+
+            let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty());
+            let surface_index = match pic.raster_config {
+                Some(ref raster_config) => raster_config.surface_index,
+                None => parent_surface_index,
+            };
+
+            (prim_list, surface_index, pic.apply_local_clip_rect)
+        };
+
+        let surface = &frame_context.surfaces[surface_index.0 as usize];
+
+        let mut map_local_to_surface = surface
+            .map_local_to_surface
+            .clone();
+
+        let map_surface_to_world = SpaceMapper::new_with_target(
+            ROOT_SPATIAL_NODE_INDEX,
+            surface.surface_spatial_node_index,
+            frame_context.screen_world_rect,
+            frame_context.clip_scroll_tree,
+        );
+
+        for prim_instance in &mut prim_list.prim_instances {
+            prim_instance.reset();
+
+            if prim_instance.is_chased() {
+                #[cfg(debug_assertions)]
+                println!("\tpreparing {:?} in {:?}",
+                    prim_instance.id, pic_index);
+            }
+
+            // Get the cluster and see if is visible
+            if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
+                continue;
+            }
+
+            let spatial_node = &frame_context
+                .clip_scroll_tree
+                .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
+
+            // TODO(gw): Although constructing these is cheap, they are often
+            //           the same for many consecutive primitives, so it may
+            //           be worth caching the most recent context.
+            let prim_context = PrimitiveContext::new(
+                spatial_node,
+                prim_instance.spatial_node_index,
+            );
+
+            map_local_to_surface.set_target_spatial_node(
+                prim_instance.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+
+            let (is_passthrough, prim_local_rect, prim_local_clip_rect, clip_node_collector) = match prim_instance.kind {
+                PrimitiveInstanceKind::Picture { pic_index, .. } => {
+                    if !self.pictures[pic_index.0].is_visible() {
+                        continue;
+                    }
+
+                    if let Some(ref raster_config) = self.pictures[pic_index.0].raster_config {
+                        if raster_config.establishes_raster_root {
+                            let surface = &frame_context.surfaces[raster_config.surface_index.0 as usize];
+                            frame_state.clip_store.push_raster_root(surface.surface_spatial_node_index);
+                        }
+                    }
+
+                    self.update_visibility(
+                        pic_index,
+                        surface_index,
+                        frame_context,
+                        frame_state,
+                        resources,
+                    );
+
+                    let pic = &self.pictures[pic_index.0];
+
+                    let clip_node_collector = pic.raster_config.as_ref().and_then(|rc| {
+                        if rc.establishes_raster_root {
+                            Some(frame_state.clip_store.pop_raster_root())
+                        } else {
+                            None
+                        }
+                    });
+
+                    (pic.raster_config.is_none(), pic.local_rect, LayoutRect::max_rect(), clip_node_collector)
+                }
+                _ => {
+                    let prim_data = &resources.as_common_data(&prim_instance);
+
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.prim_size,
+                    );
+                    let clip_rect = prim_data
+                        .prim_relative_clip_rect
+                        .translate(&LayoutVector2D::new(prim_instance.prim_origin.x, prim_instance.prim_origin.y));
+
+                    (false, prim_rect, clip_rect, None)
+                }
+            };
+
+            if is_passthrough {
+                let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
+
+                frame_state.scratch.prim_info.push(
+                    PrimitiveVisibility {
+                        clipped_world_rect: WorldRect::zero(),
+                        clip_chain: ClipChainInstance::empty(),
+                        clip_task_index: ClipTaskIndex::INVALID,
+                        combined_local_clip_rect: LayoutRect::zero(),
+                    }
+                );
+
+                prim_instance.visibility_info = vis_index;
+            } else {
+                if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
+                    if prim_instance.is_chased() {
+                        println!("\tculled for zero local rectangle");
+                    }
+                    continue;
+                }
+
+                // Inflate the local rect for this primitive by the inflation factor of
+                // the picture context. This ensures that even if the primitive itself
+                // is not visible, any effects from the blur radius will be correctly
+                // taken into account.
+                let inflation_factor = surface.inflation_factor;
+                let local_rect = prim_local_rect
+                    .inflate(inflation_factor, inflation_factor)
+                    .intersection(&prim_local_clip_rect);
+                let local_rect = match local_rect {
+                    Some(local_rect) => local_rect,
+                    None => {
+                        if prim_instance.is_chased() {
+                            println!("\tculled for being out of the local clip rectangle: {:?}",
+                                prim_local_clip_rect);
+                        }
+                        continue;
+                    }
+                };
+
+                let clip_chain = frame_state
+                    .clip_store
+                    .build_clip_chain_instance(
+                        prim_instance,
+                        local_rect,
+                        prim_local_clip_rect,
+                        prim_context.spatial_node_index,
+                        &map_local_to_surface,
+                        &map_surface_to_world,
+                        &frame_context.clip_scroll_tree,
+                        frame_state.gpu_cache,
+                        frame_state.resource_cache,
+                        frame_context.device_pixel_scale,
+                        &frame_context.screen_world_rect,
+                        clip_node_collector.as_ref(),
+                        &mut resources.clip_data_store,
+                    );
+
+                let clip_chain = match clip_chain {
+                    Some(clip_chain) => clip_chain,
+                    None => {
+                        if prim_instance.is_chased() {
+                            println!("\tunable to build the clip chain, skipping");
+                        }
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
+                        continue;
+                    }
+                };
+
+                if prim_instance.is_chased() {
+                    println!("\teffective clip chain from {:?} {}",
+                        clip_chain.clips_range,
+                        if apply_local_clip_rect { "(applied)" } else { "" },
+                    );
+                }
+
+                let combined_local_clip_rect = if apply_local_clip_rect {
+                    clip_chain.local_clip_rect
+                } else {
+                    prim_local_clip_rect
+                };
+
+                // Check if the clip bounding rect (in pic space) is visible on screen
+                // This includes both the prim bounding rect + local prim clip rect!
+                let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
+                    Some(world_rect) => world_rect,
+                    None => {
+                        continue;
+                    }
+                };
+
+                let clipped_world_rect = match world_rect.intersection(&frame_context.screen_world_rect) {
+                    Some(rect) => rect,
+                    None => {
+                        continue;
+                    }
+                };
+
+                let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
+
+                frame_state.scratch.prim_info.push(
+                    PrimitiveVisibility {
+                        clipped_world_rect,
+                        clip_chain,
+                        clip_task_index: ClipTaskIndex::INVALID,
+                        combined_local_clip_rect,
+                    }
+                );
+
+                prim_instance.visibility_info = vis_index;
+            }
+
+        }
+
+        // frame_state.pop_picture();
+        self.pictures[pic_index.0].prim_list = prim_list;
+    }
+
     /// Update any picture tile caches for a subset of the picture tree.
     /// This is often a no-op that exits very quickly, unless a new scene
     /// has arrived, or the relative transforms have changed.
     pub fn update_tile_cache(
         &mut self,
         pic_index: PictureIndex,
         state: &mut TileCacheUpdateState,
         frame_context: &FrameBuildingContext,
@@ -1963,17 +2238,17 @@ impl PrimitiveStore {
                 PrimitiveInstanceKind::LinearGradient { .. } |
                 PrimitiveInstanceKind::RadialGradient { .. } |
                 PrimitiveInstanceKind::Clear { .. } => {
                     None
                 }
             }
         };
 
-        let (is_passthrough, clip_node_collector) = match pic_info {
+        let is_passthrough = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
                 // Mark whether this picture has a complex coordinate system.
                 let is_passthrough = pic_context_for_children.is_passthrough;
 
                 self.prepare_primitives(
                     &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
@@ -1983,218 +2258,83 @@ impl PrimitiveStore {
                     scratch,
                 );
 
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
-                let clip_node_collector = self
-                    .pictures[pic_context_for_children.pic_index.0]
+                self.pictures[pic_context_for_children.pic_index.0]
                     .restore_context(
                         prim_list,
                         pic_context_for_children,
                         pic_state_for_children,
-                        frame_state,
                     );
 
-                (is_passthrough, clip_node_collector)
+                is_passthrough
             }
             None => {
-                (false, None)
-            }
-        };
-
-        let (prim_local_rect, prim_local_clip_rect) = match prim_instance.kind {
-            PrimitiveInstanceKind::Picture { pic_index, .. } => {
-                let pic = &self.pictures[pic_index.0];
-                (pic.local_rect, LayoutRect::max_rect())
-            }
-            _ => {
-                let prim_data = &resources.as_common_data(&prim_instance);
-
-                let prim_rect = LayoutRect::new(
-                    prim_instance.prim_origin,
-                    prim_data.prim_size,
-                );
-                let clip_rect = prim_data
-                    .prim_relative_clip_rect
-                    .translate(&LayoutVector2D::new(prim_instance.prim_origin.x, prim_instance.prim_origin.y));
-
-                (prim_rect, clip_rect)
+                false
             }
         };
 
-        // TODO(gw): Eventually we can move all the code handling below for
-        //           visibility and clip chain building to be done during the
-        //           update_prim_dependencies pass. That will mean that:
-        //           (a) We only do the work if the relative transforms change.
-        //           (b) Local clip rects can reduce the # of tile dependencies.
-
-        // TODO(gw): Having this declared outside is a hack / workaround. We
-        //           need it in pic.prepare_for_render below, but that code
-        //           path will only read it in the !is_passthrough case
-        //           below. This should be much tidier once we port this
-        //           traversal to work with a state stack like the initial
-        //           picture traversal now works.
-        let mut clipped_world_rect = WorldRect::zero();
-
-        if is_passthrough {
-            prim_instance.bounding_rect = Some(pic_state.map_local_to_pic.bounds);
-        } else {
-            if prim_local_rect.size.width <= 0.0 ||
-               prim_local_rect.size.height <= 0.0
-            {
-                if prim_instance.is_chased() {
-                    println!("\tculled for zero local rectangle");
-                }
-                return false;
-            }
-
-            // Inflate the local rect for this primitive by the inflation factor of
-            // the picture context. This ensures that even if the primitive itself
-            // is not visible, any effects from the blur radius will be correctly
-            // taken into account.
-            let inflation_factor = frame_state
-                .surfaces[pic_context.surface_index.0]
-                .inflation_factor;
-            let local_rect = prim_local_rect
-                .inflate(inflation_factor, inflation_factor)
-                .intersection(&prim_local_clip_rect);
-            let local_rect = match local_rect {
-                Some(local_rect) => local_rect,
-                None => {
-                    if prim_instance.is_chased() {
-                        println!("\tculled for being out of the local clip rectangle: {:?}",
-                            prim_local_clip_rect);
-                    }
-                    return false;
-                }
-            };
-
-            let clip_chain = frame_state
-                .clip_store
-                .build_clip_chain_instance(
-                    prim_instance,
-                    local_rect,
-                    prim_local_clip_rect,
-                    prim_context.spatial_node_index,
-                    &pic_state.map_local_to_pic,
-                    &pic_state.map_pic_to_world,
-                    &frame_context.clip_scroll_tree,
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_context.device_pixel_scale,
-                    &pic_context.dirty_world_rect,
-                    clip_node_collector.as_ref(),
-                    &mut resources.clip_data_store,
-                );
-
-            let clip_chain = match clip_chain {
-                Some(clip_chain) => clip_chain,
-                None => {
-                    if prim_instance.is_chased() {
-                        println!("\tunable to build the clip chain, skipping");
-                    }
-                    prim_instance.bounding_rect = None;
-                    return false;
-                }
-            };
-
-            if prim_instance.is_chased() {
-                println!("\teffective clip chain from {:?} {}",
-                    clip_chain.clips_range,
-                    if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
-                );
-            }
-
-            prim_instance.combined_local_clip_rect = if pic_context.apply_local_clip_rect {
-                clip_chain.local_clip_rect
-            } else {
-                prim_local_clip_rect
-            };
-
-            // Check if the clip bounding rect (in pic space) is visible on screen
-            // This includes both the prim bounding rect + local prim clip rect!
-            let world_rect = match pic_state
-                .map_pic_to_world
-                .map(&clip_chain.pic_clip_rect)
-            {
-                Some(world_rect) => world_rect,
-                None => {
-                    return false;
-                }
-            };
-
-            clipped_world_rect = match world_rect.intersection(&pic_context.dirty_world_rect) {
-                Some(rect) => rect,
-                None => {
-                    return false;
-                }
-            };
-
-            prim_instance.bounding_rect = Some(clip_chain.pic_clip_rect);
-
+        if !is_passthrough {
             prim_instance.update_clip_task(
-                prim_local_rect,
-                prim_local_clip_rect,
                 prim_context,
-                clipped_world_rect,
                 pic_context.raster_spatial_node_index,
-                &clip_chain,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
-                clip_node_collector.as_ref(),
                 self,
                 resources,
                 scratch,
             );
 
             if prim_instance.is_chased() {
-                println!("\tconsidered visible and ready with local rect {:?}", local_rect);
+                println!("\tconsidered visible and ready with local pos {:?}", prim_instance.prim_origin);
             }
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         pic_state.is_cacheable &= prim_instance.is_cacheable(
             &resources,
             frame_state.resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let pic = &mut self.pictures[pic_index.0];
+                let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
                 if pic.prepare_for_render(
                     pic_index,
                     prim_instance,
-                    &prim_local_rect,
-                    clipped_world_rect,
+                    prim_info.clipped_world_rect,
                     pic_context.surface_index,
                     frame_context,
                     frame_state,
                 ) {
                     if let Some(ref mut splitter) = pic_state.plane_splitter {
                         PicturePrimitive::add_split_plane(
                             splitter,
                             frame_state.transforms,
                             prim_instance,
-                            prim_local_rect,
+                            pic.local_rect,
+                            &prim_info.combined_local_clip_rect,
                             pic_context.dirty_world_rect,
                             plane_split_anchor,
                         );
                     }
                 } else {
-                    prim_instance.bounding_rect = None;
+                    prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                 }
 
                 if let Some(mut request) = frame_state.gpu_cache.request(&mut pic.gpu_location) {
                     request.push(PremultipliedColorF::WHITE);
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
                         -1.0,       // -ve means use prim rect for stretch size
                         0.0,
@@ -2210,17 +2350,16 @@ impl PrimitiveStore {
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
-                    prim_local_rect,
                     prim_context,
                     pic_context,
                     frame_context,
                     frame_state,
                     resources,
                     scratch,
                 );
             }
@@ -2235,26 +2374,17 @@ impl PrimitiveStore {
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         for (plane_split_anchor, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
-            prim_instance.reset();
-
-            if prim_instance.is_chased() {
-                #[cfg(debug_assertions)]
-                println!("\tpreparing {:?} in {:?}",
-                    prim_instance.id, pic_context.pipeline_id);
-            }
-
-            // Get the cluster and see if is visible
-            if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
+            if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
                 continue;
             }
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
 
             // TODO(gw): Although constructing these is cheap, they are often
@@ -2287,17 +2417,16 @@ impl PrimitiveStore {
     }
 
     /// Prepare an interned primitive for rendering, by requesting
     /// resources, render tasks etc. This is equivalent to the
     /// prepare_prim_for_render_inner call for old style primitives.
     fn prepare_interned_prim_for_render(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
-        prim_local_rect: LayoutRect,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let is_chased = prim_instance.is_chased();
@@ -2507,32 +2636,37 @@ impl PrimitiveStore {
 
                 if let Some(image_properties) = image_properties {
                     if let Some(tile_size) = image_properties.tiling {
                         let device_image_size = image_properties.descriptor.size;
 
                         // Tighten the clip rect because decomposing the repeated image can
                         // produce primitives that are partially covering the original image
                         // rect and we want to clip these extra parts out.
-                        let tight_clip_rect = prim_instance
+                        let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                        let prim_rect = LayoutRect::new(
+                            prim_instance.prim_origin,
+                            common_data.prim_size,
+                        );
+                        let tight_clip_rect = prim_info
                             .combined_local_clip_rect
-                            .intersection(&prim_local_rect).unwrap();
+                            .intersection(&prim_rect).unwrap();
 
                         let visible_rect = compute_conservative_visible_rect(
                             prim_context,
                             &pic_context.dirty_world_rect,
                             &tight_clip_rect
                         );
 
                         let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing);
 
                         let stride = image_data.stretch_size + image_data.tile_spacing;
 
                         let repetitions = ::image::repetitions(
-                            &prim_local_rect,
+                            &prim_rect,
                             &visible_rect,
                             stride,
                         );
 
                         let request = ImageRequest {
                             key: image_data.key,
                             rendering: image_data.image_rendering,
                             tile: None,
@@ -2577,36 +2711,42 @@ impl PrimitiveStore {
                         }
 
                         if image_instance.visible_tiles.is_empty() {
                             // At this point if we don't have tiles to show it means we could probably
                             // have done a better a job at culling during an earlier stage.
                             // Clearing the screen rect has the effect of "culling out" the primitive
                             // from the point of view of the batch builder, and ensures we don't hit
                             // assertions later on because we didn't request any image.
-                            prim_instance.bounding_rect = None;
+                            prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         }
                     }
                 }
 
                 write_segment(image_instance.segment_instance_index, frame_state, scratch, |request| {
                     image_data.write_prim_gpu_blocks(request);
                 });
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, .. } => {
                 let prim_data = &mut resources.linear_grad_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
                 if prim_data.tile_spacing != LayoutSize::zero() {
+                    let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.common.prim_size,
+                    );
+
                     *visible_tiles_range = decompose_repeated_primitive(
-                        &prim_instance.combined_local_clip_rect,
-                        &prim_local_rect,
+                        &prim_info.combined_local_clip_rect,
+                        &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
                         prim_context,
                         frame_state,
                         &pic_context.dirty_world_rect,
                         &mut scratch.gradient_tiles,
                         &mut |_, mut request| {
                             request.push([
@@ -2620,34 +2760,40 @@ impl PrimitiveStore {
                                 prim_data.stretch_size.width,
                                 prim_data.stretch_size.height,
                                 0.0,
                             ]);
                         }
                     );
 
                     if visible_tiles_range.is_empty() {
-                        prim_instance.bounding_rect = None;
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 // TODO(gw): Consider whether it's worth doing segment building
                 //           for gradient primitives.
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => {
                 let prim_data = &mut resources.radial_grad_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
                 if prim_data.tile_spacing != LayoutSize::zero() {
+                    let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.common.prim_size,
+                    );
+
                     *visible_tiles_range = decompose_repeated_primitive(
-                        &prim_instance.combined_local_clip_rect,
-                        &prim_local_rect,
+                        &prim_info.combined_local_clip_rect,
+                        &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
                         prim_context,
                         frame_state,
                         &pic_context.dirty_world_rect,
                         &mut scratch.gradient_tiles,
                         &mut |_, mut request| {
                             request.push([
@@ -2661,17 +2807,17 @@ impl PrimitiveStore {
                                 pack_as_float(prim_data.extend_mode as u32),
                                 prim_data.stretch_size.width,
                                 prim_data.stretch_size.height,
                             ]);
                         },
                     );
 
                     if visible_tiles_range.is_empty() {
-                        prim_instance.bounding_rect = None;
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 // TODO(gw): Consider whether it's worth doing segment building
                 //           for gradient primitives.
             }
             _ => {
                 unreachable!();
@@ -2932,24 +3078,30 @@ impl<'a> GpuDataRequest<'a> {
         }
 
         false
     }
 
 impl PrimitiveInstance {
     fn build_segments_if_needed(
         &mut self,
-        prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
         prim_clip_chain: &ClipChainInstance,
         frame_state: &mut FrameBuildingState,
         prim_store: &mut PrimitiveStore,
         resources: &FrameResources,
-        scratch: &mut PrimitiveScratchBuffer,
+        segments_store: &mut SegmentStorage,
+        segment_instances_store: &mut SegmentInstanceStorage,
     ) {
+        let prim_data = &resources.as_common_data(self);
+        let prim_local_rect = LayoutRect::new(
+            self.prim_origin,
+            prim_data.prim_size,
+        );
+
         let segment_instance_index = match self.kind {
             PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } |
             PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => {
                 segment_instance_index
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
                 let image_data = &resources.image_data_store[data_handle].kind;
                 let image_instance = &mut prim_store.images[image_instance_index];
@@ -2999,48 +3151,43 @@ impl PrimitiveInstance {
                         ),
                     );
                 });
             }
 
             if segments.is_empty() {
                 *segment_instance_index = SegmentInstanceIndex::UNUSED;
             } else {
-                let segments_range = scratch
-                    .segments
-                    .extend(segments);
+                let segments_range = segments_store.extend(segments);
 
                 let instance = SegmentedInstance {
                     segments_range,
                     gpu_cache_handle: GpuCacheHandle::new(),
                 };
 
-                *segment_instance_index = scratch
-                    .segment_instances
-                    .push(instance);
+                *segment_instance_index = segment_instances_store.push(instance);
             };
         }
     }
 
     fn update_clip_task_for_brush(
-        &mut self,
-        prim_origin: LayoutPoint,
+        &self,
+        prim_info: &mut PrimitiveVisibility,
         prim_local_clip_rect: LayoutRect,
         root_spatial_node_index: SpatialNodeIndex,
-        prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
-        prim_clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        clip_node_collector: Option<&ClipNodeCollector>,
         prim_store: &PrimitiveStore,
         resources: &mut FrameResources,
-        scratch: &mut PrimitiveScratchBuffer,
+        segments_store: &mut SegmentStorage,
+        segment_instances_store: &mut SegmentInstanceStorage,
+        clip_mask_instances: &mut Vec<ClipMaskKind>,
     ) -> bool {
         let segments = match self.kind {
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
@@ -3048,31 +3195,31 @@ impl PrimitiveInstance {
                 let segment_instance_index = prim_store
                     .images[image_instance_index]
                     .segment_instance_index;
 
                 if segment_instance_index == SegmentInstanceIndex::UNUSED {
                     return false;
                 }
 
-                let segment_instance = &scratch.segment_instances[segment_instance_index];
-
-                &scratch.segments[segment_instance.segments_range]
+                let segment_instance = &segment_instances_store[segment_instance_index];
+
+                &segments_store[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::YuvImage { segment_instance_index, .. } |
             PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => {
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
 
                 if segment_instance_index == SegmentInstanceIndex::UNUSED {
                     return false;
                 }
 
-                let segment_instance = &scratch.segment_instances[segment_instance_index];
-
-                &scratch.segments[segment_instance.segments_range]
+                let segment_instance = &segment_instances_store[segment_instance_index];
+
+                &segments_store[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
                 let border_data = &resources.image_border_data_store[data_handle].kind;
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
                 border_data.brush_segments.as_slice()
             }
@@ -3111,156 +3258,160 @@ impl PrimitiveInstance {
         // clip task instance location below.
         if segments.is_empty() {
             return true;
         }
 
         // Set where in the clip mask instances array the clip mask info
         // can be found for this primitive. Each segment will push the
         // clip mask information for itself in update_clip_task below.
-        self.clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
+        prim_info.clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
 
         // If we only built 1 segment, there is no point in re-running
         // the clip chain builder. Instead, just use the clip chain
         // instance that was built for the main primitive. This is a
         // significant optimization for the common case.
         if segments.len() == 1 {
             let clip_mask_kind = segments[0].update_clip_task(
-                Some(prim_clip_chain),
-                prim_bounding_rect,
+                Some(&prim_info.clip_chain),
+                prim_info.clipped_world_rect,
                 root_spatial_node_index,
                 pic_context.surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
                 &mut resources.clip_data_store,
             );
-            scratch.clip_mask_instances.push(clip_mask_kind);
+            clip_mask_instances.push(clip_mask_kind);
         } else {
             for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         self,
-                        segment.local_rect.translate(&LayoutVector2D::new(prim_origin.x, prim_origin.y)),
+                        segment.local_rect.translate(&LayoutVector2D::new(
+                            self.prim_origin.x,
+                            self.prim_origin.y,
+                        )),
                         prim_local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &pic_context.dirty_world_rect,
-                        clip_node_collector,
+                        None,
                         &mut resources.clip_data_store,
                     );
 
                 let clip_mask_kind = segment.update_clip_task(
                     segment_clip_chain.as_ref(),
-                    prim_bounding_rect,
+                    prim_info.clipped_world_rect,
                     root_spatial_node_index,
                     pic_context.surface_index,
                     pic_state,
                     frame_context,
                     frame_state,
                     &mut resources.clip_data_store,
                 );
-                scratch.clip_mask_instances.push(clip_mask_kind);
+                clip_mask_instances.push(clip_mask_kind);
             }
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
-        prim_local_rect: LayoutRect,
-        prim_local_clip_rect: LayoutRect,
         prim_context: &PrimitiveContext,
-        prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
-        clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        clip_node_collector: Option<&ClipNodeCollector>,
         prim_store: &mut PrimitiveStore,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
+        let prim_info = &mut scratch.prim_info[self.visibility_info.0 as usize];
+
         if self.is_chased() {
-            println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
+            println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect);
         }
 
+        let prim_clip_rect = resources
+            .as_common_data(self)
+            .prim_relative_clip_rect
+            .translate(&self.prim_origin.to_vector());
+
         self.build_segments_if_needed(
-            prim_local_rect,
-            prim_local_clip_rect,
-            clip_chain,
+            prim_clip_rect,
+            &prim_info.clip_chain,
             frame_state,
             prim_store,
             resources,
-            scratch,
+            &mut scratch.segments,
+            &mut scratch.segment_instances,
         );
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
-            prim_local_rect.origin,
-            prim_local_clip_rect,
+            prim_info,
+            prim_clip_rect,
             root_spatial_node_index,
-            prim_bounding_rect,
             prim_context,
-            &clip_chain,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
-            clip_node_collector,
             prim_store,
             resources,
-            scratch,
+            &mut scratch.segments,
+            &mut scratch.segment_instances,
+            &mut scratch.clip_mask_instances,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
-        if clip_chain.needs_mask {
+        if prim_info.clip_chain.needs_mask {
             if let Some((device_rect, _)) = get_raster_rects(
-                clip_chain.pic_clip_rect,
+                prim_info.clip_chain.pic_clip_rect,
                 &pic_state.map_pic_to_raster,
                 &pic_state.map_raster_to_world,
-                prim_bounding_rect,
+                prim_info.clipped_world_rect,
                 frame_context.device_pixel_scale,
             ) {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
-                    clip_chain.clips_range,
+                    prim_info.clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if self.is_chased() {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 // Set the global clip mask instance for this primitive.
                 let clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
                 scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
-                self.clip_task_index = clip_task_index;
+                prim_info.clip_task_index = clip_task_index;
                 frame_state.surfaces[pic_context.surface_index.0].tasks.push(clip_task_id);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
@@ -3361,15 +3512,15 @@ fn update_opacity_binding(
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveInstance>(), 112, "PrimitiveInstance size changed");
+    assert_eq!(mem::size_of::<PrimitiveInstance>(), 80, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 40, "PrimitiveInstanceKind size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplate>(), 56, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 20, "PrimitiveTemplateKind size changed");
     assert_eq!(mem::size_of::<PrimitiveKey>(), 36, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 5, "PrimitiveKeyKind size changed");
 }
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -3799,17 +3799,17 @@ static const JSFunctionSpec array_static
 
 const JSPropertySpec array_static_props[] = {
     JS_SELF_HOSTED_SYM_GET(species, "ArraySpecies", 0), JS_PS_END};
 
 static inline bool ArrayConstructorImpl(JSContext* cx, CallArgs& args,
                                         bool isConstructor) {
   RootedObject proto(cx);
   if (isConstructor) {
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Array, &proto)) {
       return false;
     }
   } else {
     // We're emulating |new Array(n)| with |std_Array(n)| in self-hosted JS,
     // and the proto should be %ArrayPrototype% regardless of the callee.
     proto = GlobalObject::getOrCreateArrayPrototype(cx, cx->global());
     if (!proto) {
       return false;
--- a/js/src/builtin/Boolean.cpp
+++ b/js/src/builtin/Boolean.cpp
@@ -100,17 +100,18 @@ static const JSFunctionSpec boolean_meth
 
 static bool Boolean(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   bool b = args.length() != 0 ? JS::ToBoolean(args[0]) : false;
 
   if (args.isConstructing()) {
     RootedObject proto(cx);
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Boolean,
+                                            &proto)) {
       return false;
     }
 
     JSObject* obj = BooleanObject::create(cx, b, proto);
     if (!obj) {
       return false;
     }
     args.rval().setObject(*obj);
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -132,17 +132,17 @@ bool DataViewObject::constructSameCompar
   cx->check(bufobj);
 
   uint32_t byteOffset, byteLength;
   if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength)) {
     return false;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DataView, &proto)) {
     return false;
   }
 
   Rooted<ArrayBufferObjectMaybeShared*> buffer(
       cx, &AsArrayBufferMaybeShared(bufobj));
   JSObject* obj =
       DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
   if (!obj) {
@@ -181,17 +181,17 @@ bool DataViewObject::constructWrapped(JS
   if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset,
                                   &byteLength)) {
     return false;
   }
 
   // Make sure to get the [[Prototype]] for the created view from this
   // compartment.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DataView, &proto)) {
     return false;
   }
 
   Rooted<GlobalObject*> global(cx, cx->realm()->maybeGlobal());
   if (!proto) {
     proto = GlobalObject::getOrCreateDataViewPrototype(cx, global);
     if (!proto) {
       return false;
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -646,17 +646,17 @@ void MapObject::finalize(FreeOp* fop, JS
 bool MapObject::construct(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "Map")) {
     return false;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Map, &proto)) {
     return false;
   }
 
   Rooted<MapObject*> obj(cx, MapObject::create(cx, proto));
   if (!obj) {
     return false;
   }
 
@@ -1265,17 +1265,17 @@ bool SetObject::isBuiltinAdd(HandleValue
 bool SetObject::construct(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "Set")) {
     return false;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Set, &proto)) {
     return false;
   }
 
   Rooted<SetObject*> obj(cx, SetObject::create(cx, proto));
   if (!obj) {
     return false;
   }
 
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2095,17 +2095,18 @@ static bool PromiseConstructor(JSContext
     }
   }
 
   if (needsWrapping) {
     if (!cx->compartment()->wrap(cx, &proto)) {
       return false;
     }
   } else {
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Promise,
+                                            &proto)) {
       return false;
     }
   }
   PromiseObject* promise =
       PromiseObject::create(cx, executor, proto, needsWrapping);
   if (!promise) {
     return false;
   }
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -95,16 +95,19 @@ class PromiseObject : public NativeObjec
                                bool needsWrapping = false);
 
   static PromiseObject* createSkippingExecutor(JSContext* cx);
 
   static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
   static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
 
   int32_t flags() { return getFixedSlot(PromiseSlot_Flags).toInt32(); }
+  void setHandled() {
+    setFixedSlot(PromiseSlot_Flags, Int32Value(flags() | PROMISE_FLAG_HANDLED));
+  }
   JS::PromiseState state() {
     int32_t flags = this->flags();
     if (!(flags & PROMISE_FLAG_RESOLVED)) {
       MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
       return JS::PromiseState::Pending;
     }
     if (flags & PROMISE_FLAG_FULFILLED) {
       return JS::PromiseState::Fulfilled;
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -478,17 +478,17 @@ bool js::regexp_construct(JSContext* cx,
       // If the RegExpShared is in another Zone, don't reuse it.
       if (cx->zone() != shared->zone()) {
         shared = nullptr;
       }
     }
 
     // Step 7.
     RootedObject proto(cx);
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RegExp, &proto)) {
       return false;
     }
 
     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
     if (!regexp) {
       return false;
     }
 
@@ -554,17 +554,17 @@ bool js::regexp_construct(JSContext* cx,
   } else {
     // Steps 6.a-b.
     P = patternValue;
     F = args.get(1);
   }
 
   // Step 7.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RegExp, &proto)) {
     return false;
   }
 
   Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
   if (!regexp) {
     return false;
   }
 
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -463,52 +463,32 @@ const Class TeeState::class_ = {"TeeStat
                              classOps, &cls::classSpec_};                   \
                                                                             \
   const Class cls::protoClass_ = {"object",                                 \
                                   JSCLASS_HAS_CACHED_PROTO(JSProto_##cls),  \
                                   JS_NULL_CLASS_OPS, &cls::classSpec_};
 
 /*** 3.2. Class ReadableStream **********************************************/
 
-/**
- * Characterizes the family of algorithms, (startAlgorithm, pullAlgorithm,
- * cancelAlgorithm), associated with a readable stream.
- *
- * See the comment on SetUpReadableStreamDefaultController().
- */
-enum class SourceAlgorithms {
-  Script,
-  Tee,
-};
-
-static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
-    JSContext* cx, Handle<ReadableStream*> stream, SourceAlgorithms algorithms,
-    HandleValue underlyingSource, HandleValue pullMethod,
-    HandleValue cancelMethod, double highWaterMark, HandleValue size);
-
-static MOZ_MUST_USE ReadableByteStreamController*
-CreateExternalReadableByteStreamController(
+static MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
     JSContext* cx, Handle<ReadableStream*> stream,
     JS::ReadableStreamUnderlyingSource* source);
 
 ReadableStream* ReadableStream::createExternalSourceStream(
     JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
     HandleObject proto /* = nullptr */) {
   Rooted<ReadableStream*> stream(cx, create(cx, proto));
   if (!stream) {
     return nullptr;
   }
 
-  Rooted<ReadableStreamController*> controller(cx);
-  controller = CreateExternalReadableByteStreamController(cx, stream, source);
-  if (!controller) {
+  if (!SetUpExternalReadableByteStreamController(cx, stream, source)) {
     return nullptr;
   }
 
-  stream->setController(controller);
   return stream;
 }
 
 static MOZ_MUST_USE bool MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
                                                            HandleValue size);
 
 static MOZ_MUST_USE bool ValidateAndNormalizeHighWaterMark(
     JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark);
@@ -546,17 +526,18 @@ bool ReadableStream::constructor(JSConte
     }
     strategy = ObjectValue(*emptyObj);
   }
 
   // Implicit in the spec: Set this to
   //     OrdinaryCreateFromConstructor(NewTarget, ...).
   // Step 1: Perform ! InitializeReadableStream(this).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ReadableStream,
+                                          &proto)) {
     return false;
   }
   Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx, proto));
   if (!stream) {
     return false;
   }
 
   // Step 2: Let size be ? GetV(strategy, "size").
@@ -580,21 +561,21 @@ bool ReadableStream::constructor(JSConte
 
   // Step 5: Let typeString be ? ToString(type).
   RootedString typeString(cx, ToString<CanGC>(cx, type));
   if (!typeString) {
     return false;
   }
 
   // Step 6: If typeString is "bytes",
-  int32_t cmp;
-  if (!CompareStrings(cx, typeString, cx->names().bytes, &cmp)) {
+  bool equal;
+  if (!EqualStrings(cx, typeString, cx->names().bytes, &equal)) {
     return false;
   }
-  if (cmp == 0) {
+  if (equal) {
     // The rest of step 6 is unimplemented, since we don't support
     // user-defined byte streams yet.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
     return false;
   }
 
   // Step 7: Otherwise, if type is undefined,
@@ -691,65 +672,72 @@ static MOZ_MUST_USE bool ReadableStream_
 
 static MOZ_MUST_USE ReadableStreamDefaultReader*
 CreateReadableStreamDefaultReader(
     JSContext* cx, Handle<ReadableStream*> unwrappedStream,
     ForAuthorCodeBool forAuthorCode = ForAuthorCodeBool::No,
     HandleObject proto = nullptr);
 
 /**
- * Streams spec, 3.2.5.3. getReader()
+ * Streams spec, 3.2.5.3. getReader({ mode } = {})
  */
 static bool ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
+  // Implicit in the spec: Argument defaults and destructuring.
+  RootedValue optionsVal(cx, args.get(0));
+  if (optionsVal.isUndefined()) {
+    JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+    if (!emptyObj) {
+      return false;
+    }
+    optionsVal.setObject(*emptyObj);
+  }
+  RootedValue modeVal(cx);
+  if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) {
+    return false;
+  }
+
   // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
   Rooted<ReadableStream*> unwrappedStream(
       cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "getReader"));
   if (!unwrappedStream) {
     return false;
   }
 
-  RootedObject reader(cx);
-
   // Step 2: If mode is undefined, return
   //         ? AcquireReadableStreamDefaultReader(this).
-  RootedValue modeVal(cx);
-  HandleValue optionsVal = args.get(0);
-  if (!optionsVal.isUndefined()) {
-    if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) {
-      return false;
-    }
-  }
-
+  RootedObject reader(cx);
   if (modeVal.isUndefined()) {
     reader = CreateReadableStreamDefaultReader(cx, unwrappedStream,
                                                ForAuthorCodeBool::Yes);
   } else {
     // Step 3: Set mode to ? ToString(mode) (implicit).
     RootedString mode(cx, ToString<CanGC>(cx, modeVal));
     if (!mode) {
       return false;
     }
 
     // Step 4: If mode is "byob",
     //         return ? AcquireReadableStreamBYOBReader(this).
-    int32_t notByob;
-    if (!CompareStrings(cx, mode, cx->names().byob, &notByob)) {
+    bool equal;
+    if (!EqualStrings(cx, mode, cx->names().byob, &equal)) {
       return false;
     }
-    if (notByob) {
-      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                JSMSG_READABLESTREAM_INVALID_READER_MODE);
-      // Step 5: Throw a RangeError exception.
+    if (equal) {
+      JS_ReportErrorNumberASCII(
+          cx, GetErrorMessage, nullptr,
+          JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
       return false;
     }
 
+    // Step 5: Throw a RangeError exception.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
+                              JSMSG_READABLESTREAM_INVALID_READER_MODE);
+    return false;
   }
 
   // Reordered second part of steps 2 and 4.
   if (!reader) {
     return false;
   }
   args.rval().setObject(*reader);
   return true;
@@ -817,16 +805,32 @@ CLASS_SPEC(ReadableStream, 0, SlotCount,
 
 // Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream )
 // Always inlined.
 
 // Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream )
 // Always inlined. See CreateReadableStreamDefaultReader.
 
 /**
+ * Characterizes the family of algorithms, (startAlgorithm, pullAlgorithm,
+ * cancelAlgorithm), associated with a readable stream.
+ *
+ * See the comment on SetUpReadableStreamDefaultController().
+ */
+enum class SourceAlgorithms {
+  Script,
+  Tee,
+};
+
+static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
+    JSContext* cx, Handle<ReadableStream*> stream, SourceAlgorithms algorithms,
+    HandleValue underlyingSource, HandleValue pullMethod,
+    HandleValue cancelMethod, double highWaterMark, HandleValue size);
+
+/**
  * Streams spec, 3.3.3. CreateReadableStream (
  *                          startAlgorithm, pullAlgorithm, cancelAlgorithm
  *                          [, highWaterMark [, sizeAlgorithm ] ] )
  *
  * The start/pull/cancelAlgorithm arguments are represented instead as four
  * arguments: sourceAlgorithms, underlyingSource, pullMethod, cancelMethod.
  * See the comment on SetUpReadableStreamDefaultController.
  */
@@ -836,33 +840,33 @@ MOZ_MUST_USE ReadableStream* CreateReada
     HandleValue cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
     HandleValue sizeAlgorithm = UndefinedHandleValue,
     HandleObject proto = nullptr) {
   cx->check(underlyingSource, sizeAlgorithm, proto);
   MOZ_ASSERT(sizeAlgorithm.isUndefined() || IsCallable(sizeAlgorithm));
 
   // Step 1: If highWaterMark was not passed, set it to 1 (implicit).
   // Step 2: If sizeAlgorithm was not passed, set it to an algorithm that
-  // returns 1 (implicit). Step 3: Assert: ! IsNonNegativeNumber(highWaterMark)
-  // is true.
+  //         returns 1 (implicit).
+  // Step 3: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
   MOZ_ASSERT(highWaterMark >= 0);
 
   // Step 4: Let stream be ObjectCreate(the original value of ReadableStream's
-  // prototype property). Step 5: Perform ! InitializeReadableStream(stream).
+  //         prototype property).
+  // Step 5: Perform ! InitializeReadableStream(stream).
   Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx, proto));
   if (!stream) {
     return nullptr;
   }
 
   // Step 6: Let controller be ObjectCreate(the original value of
   //         ReadableStreamDefaultController's prototype property).
   // Step 7: Perform ? SetUpReadableStreamDefaultController(stream,
   //         controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
   //         highWaterMark, sizeAlgorithm).
-
   if (!SetUpReadableStreamDefaultController(
           cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
           cancelMethod, highWaterMark, sizeAlgorithm)) {
     return nullptr;
   }
 
   // Step 8: Return stream.
   return stream;
@@ -897,17 +901,17 @@ MOZ_MUST_USE /* static */ ReadableStream
 
   // Step 3: Set stream.[[disturbed]] to false (done in step 1).
   MOZ_ASSERT(!stream->disturbed());
 
   return stream;
 }
 
 // Streams spec, 3.3.6. IsReadableStream ( x )
-// Using is<T> instead.
+// Using UnwrapAndTypeCheck templates instead.
 
 // Streams spec, 3.3.7. IsReadableStreamDisturbed ( stream )
 // Using stream->disturbed() instead.
 
 /**
  * Streams spec, 3.3.8. IsReadableStreamLocked ( stream )
  */
 bool ReadableStream::locked() const {
@@ -941,26 +945,25 @@ static bool TeeReaderReadHandler(JSConte
   Rooted<TeeState*> unwrappedTeeState(cx,
                                       UnwrapCalleeSlot<TeeState>(cx, args, 0));
   HandleValue resultVal = args.get(0);
 
   // Step i: Assert: Type(result) is Object.
   RootedObject result(cx, &resultVal.toObject());
 
   // Step ii: Let value be ? Get(result, "value").
+  // (This can fail only if `result` was nuked.)
   RootedValue value(cx);
-  if (!GetPropertyPure(cx, result, NameToId(cx->names().value),
-                       value.address())) {
+  if (!GetProperty(cx, result, result, cx->names().value, &value)) {
     return false;
   }
 
   // Step iii: Let done be ? Get(result, "done").
   RootedValue doneVal(cx);
-  if (!GetPropertyPure(cx, result, NameToId(cx->names().done),
-                       doneVal.address())) {
+  if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
     return false;
   }
 
   // Step iv: Assert: Type(done) is Boolean.
   bool done = doneVal.toBoolean();
 
   // Step v: If done is true and closedOrErrored is false,
   if (done && !unwrappedTeeState->closedOrErrored()) {
@@ -1184,17 +1187,17 @@ static MOZ_MUST_USE JSObject* ReadableSt
 static MOZ_MUST_USE bool ReadableStreamControllerError(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
     HandleValue e);
 
 /**
  * Streams spec, 3.3.9. step 18:
  * Upon rejection of reader.[[closedPromise]] with reason r,
  */
-static bool TeeReaderClosedHandler(JSContext* cx, unsigned argc, Value* vp) {
+static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args));
   HandleValue reason = args.get(0);
 
   // Step a: If closedOrErrored is false, then:
   if (!teeState->closedOrErrored()) {
     // Step a.iii: Set closedOrErrored to true.
     // Reordered to ensure that internal errors in the other steps don't
@@ -1295,17 +1298,18 @@ static MOZ_MUST_USE bool ReadableStreamT
   Rooted<ReadableStreamDefaultController*> branch2(cx);
   branch2 = &branch2Stream->controller()->as<ReadableStreamDefaultController>();
   branch2->setTeeBranch2();
   teeState->setBranch2(branch2);
 
   // Step 18: Upon rejection of reader.[[closedPromise]] with reason r, [...]
   RootedObject closedPromise(cx, reader->closedPromise());
 
-  RootedObject onRejected(cx, NewHandler(cx, TeeReaderClosedHandler, teeState));
+  RootedObject onRejected(cx,
+                          NewHandler(cx, TeeReaderErroredHandler, teeState));
   if (!onRejected) {
     return false;
   }
 
   if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) {
     return false;
   }
 
@@ -1315,34 +1319,40 @@ static MOZ_MUST_USE bool ReadableStreamT
 
 /*** 3.4. The interface between readable streams and controllers ************/
 
 inline static MOZ_MUST_USE bool AppendToListAtSlot(
     JSContext* cx, HandleNativeObject unwrappedContainer, uint32_t slot,
     HandleObject obj);
 
 /**
- * Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream, forAuthorCode
- * ) Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream, forAuthorCode )
+ * Streams spec, 3.4.1.
+ *      ReadableStreamAddReadIntoRequest ( stream, forAuthorCode )
+ * Streams spec, 3.4.2.
+ *      ReadableStreamAddReadRequest ( stream, forAuthorCode )
  *
  * Our implementation does not pass around forAuthorCode parameters in the same
  * places as the standard, but the effect is the same. See the comment on
  * `ReadableStreamReader::forAuthorCode()`.
  */
 static MOZ_MUST_USE JSObject* ReadableStreamAddReadOrReadIntoRequest(
     JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
-  // Step 1: Assert: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
-  // Skipped: handles both kinds of readers.
+  // Step 1: Assert: ! IsReadableStream{BYOB,Default}Reader(stream.[[reader]])
+  //         is true.
+  // (Only default readers exist so far.)
   Rooted<ReadableStreamReader*> unwrappedReader(
       cx, UnwrapReaderFromStream(cx, unwrappedStream));
   if (!unwrappedReader) {
     return nullptr;
   }
-
+  MOZ_ASSERT(unwrappedReader->is<ReadableStreamDefaultReader>());
+
+  // Step 2 of 3.4.1: Assert: stream.[[state]] is "readable" or "closed".
   // Step 2 of 3.4.2: Assert: stream.[[state]] is "readable".
+  MOZ_ASSERT(unwrappedStream->readable() || unwrappedStream->closed());
   MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(),
                 unwrappedStream->readable());
 
   // Step 3: Let promise be a new promise.
   RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx));
   if (!promise) {
     return nullptr;
   }
@@ -1416,17 +1426,17 @@ static MOZ_MUST_USE JSObject* ReadableSt
   Rooted<ReadableStreamController*> unwrappedController(
       cx, unwrappedStream->controller());
   RootedObject sourceCancelPromise(
       cx, ReadableStreamControllerCancelSteps(cx, unwrappedController, reason));
   if (!sourceCancelPromise) {
     return nullptr;
   }
 
-  // Step 6: Return the result of transforming sourceCancelPromise by a
+  // Step 6: Return the result of transforming sourceCancelPromise with a
   //         fulfillment handler that returns undefined.
   RootedAtom funName(cx, cx->names().empty);
   RootedFunction returnUndefined(
       cx, NewNativeFunction(cx, ReturnUndefined, 0, funName));
   if (!returnUndefined) {
     return nullptr;
   }
   return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined,
@@ -1491,17 +1501,16 @@ MOZ_MUST_USE bool ReadableStreamCloseInt
       }
     }
 
     // Step b: Set reader.[[readRequests]] to an empty List.
     unwrappedReader->clearRequests();
   }
 
   // Step 6: Resolve reader.[[closedPromise]] with undefined.
-  // Step 7: Return (implicit).
   RootedObject closedPromise(cx, unwrappedReader->closedPromise());
   if (!cx->compartment()->wrap(cx, &closedPromise)) {
     return false;
   }
   if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue)) {
     return false;
   }
 
@@ -1581,51 +1590,50 @@ MOZ_MUST_USE bool ReadableStreamErrorInt
 
   // Step 5: Let reader be stream.[[reader]].
   Rooted<ReadableStreamReader*> unwrappedReader(
       cx, UnwrapReaderFromStream(cx, unwrappedStream));
   if (!unwrappedReader) {
     return false;
   }
 
-  // Steps 7,8: (Identical in our implementation.)
-  // Step a: Repeat for each readRequest that is an element of
-  //         reader.[[readRequests]],
+  // Steps 7-8: (Identical in our implementation.)
+  // Step 7.a/8.b: Repeat for each read{Into}Request that is an element of
+  //               reader.[[read{Into}Requests]],
   Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
   RootedObject readRequest(cx);
   RootedValue val(cx);
   uint32_t len = unwrappedReadRequests->length();
   for (uint32_t i = 0; i < len; i++) {
-    // Step i: Reject readRequest.[[promise]] with e.
+    // Step i: Reject read{Into}Request.[[promise]] with e.
     val = unwrappedReadRequests->get(i);
     readRequest = &val.toObject();
 
     // Responses have to be created in the compartment from which the
     // error was triggered, which might not be the same as the one the
     // request was created in, so we have to wrap requests here.
     if (!cx->compartment()->wrap(cx, &readRequest)) {
       return false;
     }
 
     if (!RejectPromise(cx, readRequest, e)) {
       return false;
     }
   }
 
-  // Step b: Set reader.[[readRequests]] to a new empty List.
+  // Step 7.b/8.c: Set reader.[[read{Into}Requests]] to a new empty List.
   if (!SetNewList(cx, unwrappedReader, ReadableStreamReader::Slot_Requests)) {
     return false;
   }
 
   // Step 9: Reject reader.[[closedPromise]] with e.
   //
   // The closedPromise might have been created in another compartment.
-  // RejectPromise can deal with wrapped Promise objects, but has to be
-  // with all arguments in the current compartment, so we do need to wrap
-  // the Promise.
+  // RejectPromise can deal with wrapped Promise objects, but all its arguments
+  // must be same-compartment with cx, so we do need to wrap the Promise.
   RootedObject closedPromise(cx, unwrappedReader->closedPromise());
   if (!cx->compartment()->wrap(cx, &closedPromise)) {
     return false;
   }
   if (!RejectPromise(cx, closedPromise, e)) {
     return false;
   }
 
@@ -1661,21 +1669,21 @@ static MOZ_MUST_USE bool ReadableStreamF
 
   // Step 1: Let reader be stream.[[reader]].
   Rooted<ReadableStreamReader*> unwrappedReader(
       cx, UnwrapReaderFromStream(cx, unwrappedStream));
   if (!unwrappedReader) {
     return false;
   }
 
-  // Step 2: Let readIntoRequest be the first element of
-  //         reader.[[readIntoRequests]].
-  // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
-  //         all other elements downward (so that the second becomes the first,
-  //         and so on).
+  // Step 2: Let read{Into}Request be the first element of
+  //         reader.[[read{Into}Requests]].
+  // Step 3: Remove read{Into}Request from reader.[[read{Into}Requests]],
+  //         shifting all other elements downward (so that the second becomes
+  //         the first, and so on).
   Rooted<ListObject*> unwrappedReadIntoRequests(cx,
                                                 unwrappedReader->requests());
   RootedObject readIntoRequest(
       cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
   MOZ_ASSERT(readIntoRequest);
   if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
     return false;
   }
@@ -1695,17 +1703,17 @@ static MOZ_MUST_USE bool ReadableStreamF
 
 /**
  * Streams spec, 3.4.9. ReadableStreamGetNumReadIntoRequests ( stream )
  * Streams spec, 3.4.10. ReadableStreamGetNumReadRequests ( stream )
  * (Identical implementation.)
  */
 static uint32_t ReadableStreamGetNumReadRequests(ReadableStream* stream) {
   // Step 1: Return the number of elements in
-  //         stream.[[reader]].[[readRequests]].
+  //         stream.[[reader]].[[read{Into}Requests]].
   if (!stream->hasReader()) {
     return 0;
   }
 
   JS::AutoSuppressGCAnalysis nogc;
   ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
 
   // Reader is a dead wrapper, treat it as non-existent.
@@ -1722,25 +1730,24 @@ static uint32_t ReadableStreamGetNumRead
 static MOZ_MUST_USE bool ReadableStreamHasDefaultReader(
     JSContext* cx, Handle<ReadableStream*> unwrappedStream, bool* result) {
   // Step 1: Let reader be stream.[[reader]].
   // Step 2: If reader is undefined, return false.
   if (!unwrappedStream->hasReader()) {
     *result = false;
     return true;
   }
-
-  // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
-  // Step 4: Return true.
   Rooted<ReadableStreamReader*> unwrappedReader(
       cx, UnwrapReaderFromStream(cx, unwrappedStream));
   if (!unwrappedReader) {
     return false;
   }
 
+  // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
+  // Step 4: Return true.
   *result = unwrappedReader->is<ReadableStreamDefaultReader>();
   return true;
 }
 
 /*** 3.5. Class ReadableStreamDefaultReader *********************************/
 
 static MOZ_MUST_USE bool ReadableStreamReaderGenericInitialize(
     JSContext* cx, Handle<ReadableStreamReader*> reader,
@@ -1787,22 +1794,22 @@ bool ReadableStreamDefaultReader::constr
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
     return false;
   }
 
   // Implicit in the spec: Find the prototype object to use.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError
-  // exception.
+  //         exception.
   Rooted<ReadableStream*> unwrappedStream(
       cx, UnwrapAndTypeCheckArgument<ReadableStream>(
               cx, args, "ReadableStreamDefaultReader constructor", 0));
   if (!unwrappedStream) {
     return false;
   }
 
   RootedObject reader(
@@ -2023,33 +2030,39 @@ static MOZ_MUST_USE bool ReadableStreamR
   // Step 2 is moved to the end.
 
   // Step 3: If stream.[[state]] is "readable",
   RootedObject promise(cx);
   if (unwrappedStream->readable()) {
     // Step a: Set reader.[[closedPromise]] to a new promise.
     promise = PromiseObject::createSkippingExecutor(cx);
   } else if (unwrappedStream->closed()) {
-    // Step 4: Otherwise
-    // Step a: If stream.[[state]] is "closed",
-    // Step i: Set reader.[[closedPromise]] to a new promise resolved with
+    // Step 4: Otherwise, if stream.[[state]] is "closed",
+    // Step a: Set reader.[[closedPromise]] to a new promise resolved with
     //         undefined.
     promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
   } else {
-    // Step b: Otherwise,
-    // Step i: Assert: stream.[[state]] is "errored".
+    // Step 5: Otherwise,
+    // Step a: Assert: stream.[[state]] is "errored".
     MOZ_ASSERT(unwrappedStream->errored());
 
-    // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
-    //          stream.[[storedError]].
+    // Step b: Set reader.[[closedPromise]] to a promise rejected with
+    //         stream.[[storedError]].
     RootedValue storedError(cx, unwrappedStream->storedError());
     if (!cx->compartment()->wrap(cx, &storedError)) {
       return false;
     }
     promise = PromiseObject::unforgeableReject(cx, storedError);
+    if (!promise) {
+      return false;
+    }
+
+    // Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+    promise->as<PromiseObject>().setHandled();
+    cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
   }
 
   if (!promise) {
     return false;
   }
 
   reader->setClosedPromise(promise);
 
@@ -2107,91 +2120,96 @@ static MOZ_MUST_USE bool ReadableStreamR
   if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
     // Uncatchable error. Die immediately without resolving
     // reader.[[closedPromise]].
     return false;
   }
 
   // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
   //         reader.[[closedPromise]] with a TypeError exception.
+  Rooted<PromiseObject*> unwrappedClosedPromise(cx);
   if (unwrappedStream->readable()) {
-    Rooted<PromiseObject*> closedPromise(
-        cx, UnwrapInternalSlot<PromiseObject>(
-                cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise));
-    if (!closedPromise) {
+    unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>(
+        cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
+    if (!unwrappedClosedPromise) {
       return false;
     }
 
-    AutoRealm ar(cx, closedPromise);
+    AutoRealm ar(cx, unwrappedClosedPromise);
     if (!cx->compartment()->wrap(cx, &exn)) {
       return false;
     }
-    if (!PromiseObject::reject(cx, closedPromise, exn)) {
+    if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
       return false;
     }
   } else {
     // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise
     //         rejected with a TypeError exception.
     RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
     if (!closedPromise) {
       return false;
     }
+    unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
 
     AutoRealm ar(cx, unwrappedReader);
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
       return false;
     }
     unwrappedReader->setClosedPromise(closedPromise);
   }
 
-  // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+  // Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+  unwrappedClosedPromise->setHandled();
+  cx->runtime()->removeUnhandledRejectedPromise(cx, unwrappedClosedPromise);
+
+  // Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
   unwrappedStream->clearReader();
 
-  // Step 6: Set reader.[[ownerReadableStream]] to undefined.
+  // Step 7: Set reader.[[ownerReadableStream]] to undefined.
   unwrappedReader->clearStream();
 
   return true;
 }
 
 static MOZ_MUST_USE JSObject* ReadableStreamControllerPullSteps(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
 
 /**
- * Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader [,
- * forAuthorCode ] )
+ * Streams spec, 3.7.7.
+ *      ReadableStreamDefaultReaderRead ( reader [, forAuthorCode ] )
  */
 static MOZ_MUST_USE JSObject* ReadableStreamDefaultReaderRead(
     JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) {
   // Step 1: If forAuthorCode was not passed, set it to false (implicit).
 
   // Step 2: Let stream be reader.[[ownerReadableStream]].
   // Step 3: Assert: stream is not undefined.
   Rooted<ReadableStream*> unwrappedStream(
       cx, UnwrapStreamFromReader(cx, unwrappedReader));
   if (!unwrappedStream) {
     return nullptr;
   }
 
   // Step 4: Set stream.[[disturbed]] to true.
   unwrappedStream->setDisturbed();
 
-  // Step 5: If stream.[[state]] is "closed", return a new promise resolved with
+  // Step 5: If stream.[[state]] is "closed", return a promise resolved with
   //         ! ReadableStreamCreateReadResult(undefined, true, forAuthorCode).
   if (unwrappedStream->closed()) {
     RootedObject iterResult(
         cx, ReadableStreamCreateReadResult(cx, UndefinedHandleValue, true,
                                            unwrappedReader->forAuthorCode()));
     if (!iterResult) {
       return nullptr;
     }
     RootedValue iterResultVal(cx, ObjectValue(*iterResult));
     return PromiseObject::unforgeableResolve(cx, iterResultVal);
   }
 
-  // Step 6: If stream.[[state]] is "errored", return a new promise rejected
+  // Step 6: If stream.[[state]] is "errored", return a promise rejected
   //         with stream.[[storedError]].
   if (unwrappedStream->errored()) {
     RootedValue storedError(cx, unwrappedStream->storedError());
     if (!cx->compartment()->wrap(cx, &storedError)) {
       return nullptr;
     }
     return PromiseObject::unforgeableReject(cx, storedError);
   }
@@ -2206,61 +2224,60 @@ static MOZ_MUST_USE JSObject* ReadableSt
 }
 
 /*** 3.8. Class ReadableStreamDefaultController *****************************/
 
 inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
 
 /**
- * Streams spec, 3.8.3, step 11.a.
+ * Streams spec, 3.9.11. SetUpReadableStreamDefaultController, step 11
  * and
- * Streams spec, 3.10.3, step 16.a.
+ * Streams spec, 3.12.26. SetUpReadableByteStreamController, step 16:
+ *      Upon fulfillment of startPromise, [...]
  */
 static bool ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<ReadableStreamController*> controller(
       cx, TargetFromHandler<ReadableStreamController>(args));
 
-  // Step i: Set controller.[[started]] to true.
+  // Step a: Set controller.[[started]] to true.
   controller->setStarted();
 
-  // Step ii: Assert: controller.[[pulling]] is false.
+  // Step b: Assert: controller.[[pulling]] is false.
   MOZ_ASSERT(!controller->pulling());
 
-  // Step iii: Assert: controller.[[pullAgain]] is false.
+  // Step c: Assert: controller.[[pullAgain]] is false.
   MOZ_ASSERT(!controller->pullAgain());
 
-  // Step iv: Perform
-  //          ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
-  // or
-  // Step iv: Perform
-  //          ! ReadableByteStreamControllerCallPullIfNeeded((controller).
+  // Step d: Perform
+  //      ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
+  //      (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
   if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
     return false;
   }
   args.rval().setUndefined();
   return true;
 }
 
 /**
- * Streams spec, 3.9.11, step 12.a.
+ * Streams spec, 3.9.11. SetUpReadableStreamDefaultController, step 12
  * and
- * Streams spec, 3.12.26, step 17.a.
+ * Streams spec, 3.12.26. SetUpReadableByteStreamController, step 17:
+ *      Upon rejection of startPromise with reason r, [...]
  */
 static bool ControllerStartFailedHandler(JSContext* cx, unsigned argc,
                                          Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<ReadableStreamController*> controller(
       cx, TargetFromHandler<ReadableStreamController>(args));
 
-  // 3.9.11, Step 12.a: Perform
-  //      ! ReadableStreamDefaultControllerError(controller, r).
-  // 3.12.26, Step 17.a: Perform
-  //      ! ReadableByteStreamControllerError(controller, r).
+  // Step a: Perform
+  //      ! ReadableStreamDefaultControllerError(controller, r)
+  //      (or ReadableByteStreamControllerError(controller, r)).
   if (!ReadableStreamControllerError(cx, controller, args.get(0))) {
     return false;
   }
 
   args.rval().setUndefined();
   return true;
 }
 
@@ -2278,33 +2295,31 @@ bool ReadableStreamDefaultController::co
   return false;
 }
 
 static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
     ReadableStreamController* controller);
 
 /**
  * Streams spec, 3.8.4.1. get desiredSize
- * and
- * Streams spec, 3.10.4.2. get desiredSize
  */
 static bool ReadableStreamDefaultController_desiredSize(JSContext* cx,
                                                         unsigned argc,
                                                         Value* vp) {
   // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
   //         TypeError exception.
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<ReadableStreamController*> unwrappedController(
       cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(
               cx, args, "get desiredSize"));
   if (!unwrappedController) {
     return false;
   }
 
-  // Streams spec, 3.9.8. steps 1-4.
+  // 3.9.8. ReadableStreamDefaultControllerGetDesiredSize, steps 1-4.
   // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
   ReadableStream* unwrappedStream = unwrappedController->stream();
 
   // 3.9.8. Step 2: Let state be stream.[[state]].
   // 3.9.8. Step 3: If state is "errored", return null.
   if (unwrappedStream->errored()) {
     args.rval().setNull();
     return true;
@@ -2322,34 +2337,47 @@ static bool ReadableStreamDefaultControl
   return true;
 }
 
 static MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
     JSContext* cx,
     Handle<ReadableStreamDefaultController*> unwrappedController);
 
 /**
- * Unified implementation of step 2 of 3.8.4.2 and steps 2-3 of 3.10.4.3.
+ * Unified implementation of step 2 of 3.8.4.2 and 3.8.4.3,
+ * and steps 2-3 of 3.10.4.3.
  */
-static MOZ_MUST_USE bool VerifyControllerStateForClosing(
-    JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
-  // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+static MOZ_MUST_USE bool CheckReadableStreamControllerCanCloseOrEnqueue(
+    JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
+    const char* action) {
+  // 3.8.4.2. close(), step 2, and
+  // 3.8.4.3. enqueue(chunk), step 2:
+  //      If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false,
+  //      throw a TypeError exception.
+  // RSDCCanCloseOrEnqueue returns false in two cases: (1)
+  // controller.[[closeRequested]] is true; (2) the stream is not readable,
+  // i.e. already closed or errored. This amounts to exactly the same thing as
+  // 3.10.4.3 steps 2-3 below, and we want different error messages for the two
+  // cases anyway.
+
+  // 3.10.4.3. Step 2: If this.[[closeRequested]] is true, throw a TypeError
+  //                   exception.
   if (unwrappedController->closeRequested()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
+                              JSMSG_READABLESTREAMCONTROLLER_CLOSED, action);
     return false;
   }
 
-  // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
-  //         throw a TypeError exception.
+  // 3.10.4.3. Step 3: If this.[[controlledReadableByteStream]].[[state]] is
+  //                   not "readable", throw a TypeError exception.
   ReadableStream* unwrappedStream = unwrappedController->stream();
   if (!unwrappedStream->readable()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
-                              "close");
+                              action);
     return false;
   }
 
   return true;
 }
 
 /**
  * Streams spec, 3.8.4.2 close()
@@ -2361,25 +2389,28 @@ static bool ReadableStreamDefaultControl
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<ReadableStreamDefaultController*> unwrappedController(
       cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
                                                                   "close"));
   if (!unwrappedController) {
     return false;
   }
 
-  // Steps 2-3.
-  if (!VerifyControllerStateForClosing(cx, unwrappedController)) {
+  // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
+  //         false, throw a TypeError exception.
+  if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
+                                                      "close")) {
     return false;
   }
 
-  // Step 4: Perform ! ReadableStreamDefaultControllerClose(this).
+  // Step 3: Perform ! ReadableStreamDefaultControllerClose(this).
   if (!ReadableStreamDefaultControllerClose(cx, unwrappedController)) {
     return false;
   }
+
   args.rval().setUndefined();
   return true;
 }
 
 /**
  * Streams spec, 3.8.4.3. enqueue ( chunk )
  */
 static bool ReadableStreamDefaultController_enqueue(JSContext* cx,
@@ -2389,33 +2420,24 @@ static bool ReadableStreamDefaultControl
   CallArgs args = CallArgsFromVp(argc, vp);
   Rooted<ReadableStreamDefaultController*> unwrappedController(
       cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
                                                                   "enqueue"));
   if (!unwrappedController) {
     return false;
   }
 
-  // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
-  if (unwrappedController->closeRequested()) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+  // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
+  //         false, throw a TypeError exception.
+  if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
+                                                      "enqueue")) {
     return false;
   }
 
-  // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
-  //         throw a TypeError exception.
-  if (!unwrappedController->stream()->readable()) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
-                              "enqueue");
-    return false;
-  }
-
-  // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
+  // Step 3: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
   if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
                                               args.get(0))) {
     return false;
   }
   args.rval().setUndefined();
   return true;
 }
 
@@ -2569,48 +2591,49 @@ static MOZ_MUST_USE JSObject* ReadableSt
                              wrappedReason);
       }
       if (!cx->compartment()->wrap(cx, &result)) {
         result = nullptr;
       }
     }
   }
 
-  // Step 3 of 3.8.5.1, step 4 of 3.10.5.1: Perform
-  //      ! ReadableByteStreamControllerClearAlgorithms(this).
+  // Step 3 (or 4): Perform
+  //      ! ReadableStreamDefaultControllerClearAlgorithms(this)
+  //      (or ReadableByteStreamControllerClearAlgorithms(this)).
   ReadableStreamControllerClearAlgorithms(unwrappedController);
 
-  // Step 4 of 3.8.5.1, step 5 of 3.10.5.1: Return result.
+  // Step 4 (or 5): Return result.
   return result;
 }
 
 inline static MOZ_MUST_USE bool DequeueValue(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
     MutableHandleValue chunk);
 
 /**
- * Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]](
- * forAuthorCode )
+ * Streams spec, 3.8.5.2.
+ *     ReadableStreamDefaultController [[PullSteps]]( forAuthorCode )
  */
 static JSObject* ReadableStreamDefaultControllerPullSteps(
     JSContext* cx,
     Handle<ReadableStreamDefaultController*> unwrappedController) {
   // Step 1: Let stream be this.[[controlledReadableStream]].
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
   // Step 2: If this.[[queue]] is not empty,
   Rooted<ListObject*> unwrappedQueue(cx);
   RootedValue val(
       cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue));
   if (val.isObject()) {
     unwrappedQueue = &val.toObject().as<ListObject>();
   }
 
   if (unwrappedQueue && unwrappedQueue->length() != 0) {
-    // Step a: Let chunk be ! DequeueValue(this.[[queue]]).
+    // Step a: Let chunk be ! DequeueValue(this).
     RootedValue chunk(cx);
     if (!DequeueValue(cx, unwrappedController, &chunk)) {
       return nullptr;
     }
 
     // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
     //         perform ! ReadableStreamClose(stream).
     if (unwrappedController->closeRequested() &&
@@ -2665,38 +2688,39 @@ static JSObject* ReadableStreamDefaultCo
 
 /*** 3.9. Readable stream default controller abstract operations ************/
 
 // Streams spec, 3.9.1. IsReadableStreamDefaultController ( x )
 // Implemented via is<ReadableStreamDefaultController>()
 
 /**
  * Streams spec, 3.9.2 and 3.12.3. step 7:
- * Upon fulfillment of pullPromise,
+ *      Upon fulfillment of pullPromise, [...]
  */
 static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
-  Rooted<ReadableStreamController*> controller(
+  Rooted<ReadableStreamController*> unwrappedController(
       cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
-  if (!controller) {
+  if (!unwrappedController) {
     return false;
   }
 
-  bool pullAgain = controller->pullAgain();
+  bool pullAgain = unwrappedController->pullAgain();
 
   // Step a: Set controller.[[pulling]] to false.
   // Step b.i: Set controller.[[pullAgain]] to false.
-  controller->clearPullFlags();
+  unwrappedController->clearPullFlags();
 
   // Step b: If controller.[[pullAgain]] is true,
   if (pullAgain) {
     // Step ii: Perform
-    //          ! ReadableByteStreamControllerCallPullIfNeeded(controller).
-    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
+    //          ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
+    //          (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
+    if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
       return false;
     }
   }
 
   args.rval().setUndefined();
   return true;
 }
 
@@ -2828,79 +2852,91 @@ inline static MOZ_MUST_USE bool Readable
         return false;
       }
     }
   }
   if (!pullPromise) {
     return false;
   }
 
+  // Step 7: Upon fulfillment of pullPromise, [...]
+  // Step 8. Upon rejection of pullPromise with reason e, [...]
   RootedObject onPullFulfilled(
       cx, NewHandler(cx, ControllerPullHandler, wrappedController));
   if (!onPullFulfilled) {
     return false;
   }
-
   RootedObject onPullRejected(
       cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController));
   if (!onPullRejected) {
     return false;
   }
-
   return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled,
                                  onPullRejected);
-
-  // Steps 7-8 implemented in functions above.
 }
 
 /**
  * Streams spec, 3.9.3.
  *      ReadableStreamDefaultControllerShouldCallPull ( controller )
  * Streams spec, 3.12.25.
  *      ReadableByteStreamControllerShouldCallPull ( controller )
  */
 static bool ReadableStreamControllerShouldCallPull(
     ReadableStreamController* unwrappedController) {
-  // Step 1: Let stream be controller.[[controlledReadableStream]].
+  // Step 1: Let stream be controller.[[controlledReadableStream]]
+  //         (or [[controlledReadableByteStream]]).
   ReadableStream* unwrappedStream = unwrappedController->stream();
 
-  // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored",
-  //         return false.
-  // or, equivalently
-  // Step 2: If stream.[[state]] is not "readable", return false.
+  // 3.9.3. Step 2:
+  //      If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
+  //      is false, return false.
+  // This turns out to be the same as 3.12.25 steps 2-3.
+
+  // 3.12.25 Step 2: If stream.[[state]] is not "readable", return false.
   if (!unwrappedStream->readable()) {
     return false;
   }
 
-  // Step 3: If controller.[[closeRequested]] is true, return false.
+  // 3.12.25 Step 3: If controller.[[closeRequested]] is true, return false.
   if (unwrappedController->closeRequested()) {
     return false;
   }
 
-  // Step 4: If controller.[[started]] is false, return false.
+  // Step 3 (or 4):
+  //      If controller.[[started]] is false, return false.
   if (!unwrappedController->started()) {
     return false;
   }
 
-  // Step 5: If ! IsReadableStreamLocked(stream) is true and
+  // 3.9.3.
+  // Step 4: If ! IsReadableStreamLocked(stream) is true and
+  //      ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
+  //
+  // 3.12.25.
+  // Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and
   //         ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
-  // Steps 5-6 of 3.12.24 are equivalent in our implementation.
+  // Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and
+  //         ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
+  //
+  // All of these amount to the same thing in this implementation:
   if (unwrappedStream->locked() &&
       ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
     return true;
   }
 
-  // Step 6: Let desiredSize be
-  //         ReadableStreamDefaultControllerGetDesiredSize(controller).
+  // Step 5 (or 7):
+  //      Let desiredSize be
+  //      ! ReadableStreamDefaultControllerGetDesiredSize(controller).
+  //      (ReadableByteStreamControllerGetDesiredSize in 3.12.25.)
   double desiredSize =
       ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
 
-  // Step 7: If desiredSize > 0, return true.
-  // Step 8: Return false.
-  // Steps 7-8 of 3.12.24 are equivalent in our implementation.
+  // Step 6 (or 8): Assert: desiredSize is not null (implicit).
+  // Step 7 (or 9): If desiredSize > 0, return true.
+  // Step 8 (or 10): Return false.
   return desiredSize > 0;
 }
 
 /**
  * Streams spec, 3.9.4.
  *      ReadableStreamDefaultControllerClearAlgorithms ( controller )
  * and 3.12.4.
  *      ReadableByteStreamControllerClearAlgorithms ( controller )
@@ -2925,23 +2961,23 @@ static void ReadableStreamControllerClea
  * Streams spec, 3.9.5. ReadableStreamDefaultControllerClose ( controller )
  */
 static MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
     JSContext* cx,
     Handle<ReadableStreamDefaultController*> unwrappedController) {
   // Step 1: Let stream be controller.[[controlledReadableStream]].
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
-  // Step 2: Assert: controller.[[closeRequested]] is false.
+  // Step 2: Assert:
+  //         ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
+  //         is true.
   MOZ_ASSERT(!unwrappedController->closeRequested());
-
-  // Step 3: Assert: stream.[[state]] is "readable".
   MOZ_ASSERT(unwrappedStream->readable());
 
-  // Step 4: Set controller.[[closeRequested]] to true.
+  // Step 3: Set controller.[[closeRequested]] to true.
   unwrappedController->setCloseRequested();
 
   // Step 5: If controller.[[queue]] is empty, perform
   //         ! ReadableStreamClose(stream).
   Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
   if (unwrappedQueue->length() == 0) {
     return ReadableStreamCloseInternal(cx, unwrappedStream);
   }
@@ -2960,18 +2996,21 @@ static MOZ_MUST_USE bool EnqueueValueWit
 static MOZ_MUST_USE bool ReadableStreamDefaultControllerEnqueue(
     JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
     HandleValue chunk) {
   AssertSameCompartment(cx, chunk);
 
   // Step 1: Let stream be controller.[[controlledReadableStream]].
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
-  // Step 2: Assert: controller.[[closeRequested]] is false.
+  // Step 2: Assert:
+  //      ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is
+  //      true.
   MOZ_ASSERT(!unwrappedController->closeRequested());
+  MOZ_ASSERT(unwrappedStream->readable());
 
   // Step 3: If ! IsReadableStreamLocked(stream) is true and
   //         ! ReadableStreamGetNumReadRequests(stream) > 0, perform
   //         ! ReadableStreamFulfillReadRequest(stream, chunk, false).
   if (unwrappedStream->locked() &&
       ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
     if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
                                                     false)) {
@@ -2994,20 +3033,20 @@ static MOZ_MUST_USE bool ReadableStreamD
     }
 
     // Step d: Let enqueueResult be
     //         EnqueueValueWithSize(controller, chunk, chunkSize).
     if (success) {
       success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
     }
 
+    // Step b: If result is an abrupt completion,
+    // and
+    // Step e: If enqueueResult is an abrupt completion,
     if (!success) {
-      // Step b: If result is an abrupt completion,
-      // and
-      // Step e: If enqueueResult is an abrupt completion,
       RootedValue exn(cx);
       if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
         // Uncatchable error. Die immediately without erroring the
         // stream.
         return false;
       }
 
       // Step b.i: Perform ! ReadableStreamDefaultControllerError(
@@ -3021,36 +3060,36 @@ static MOZ_MUST_USE bool ReadableStreamD
       // Step b.ii: Return result.
       // Step e.ii: Return enqueueResult.
       // (I.e., propagate the exception.)
       cx->setPendingException(exn);
       return false;
     }
   }
 
-  // Step 6: Perform
+  // Step 5: Perform
   //         ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
-  // Step 7: Return.
   return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
 }
 
 static MOZ_MUST_USE bool ReadableByteStreamControllerClearPendingPullIntos(
     JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController);
 
 /**
  * Streams spec, 3.9.7. ReadableStreamDefaultControllerError ( controller, e )
  * Streams spec, 3.12.11. ReadableByteStreamControllerError ( controller, e )
  */
 static MOZ_MUST_USE bool ReadableStreamControllerError(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
     HandleValue e) {
   MOZ_ASSERT(!cx->isExceptionPending());
   AssertSameCompartment(cx, e);
 
-  // Step 1: Let stream be controller.[[controlledReadableStream]].
+  // Step 1: Let stream be controller.[[controlledReadableStream]]
+  //         (or controller.[[controlledReadableByteStream]]).
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
   // Step 2: If stream.[[state]] is not "readable", return.
   if (!unwrappedStream->readable()) {
     return true;
   }
 
   // Step 3 of 3.12.10:
@@ -3200,23 +3239,21 @@ static MOZ_MUST_USE bool SetUpReadableSt
 
   // Step 11: Upon fulfillment of startPromise, [...]
   // Step 12: Upon rejection of startPromise with reason r, [...]
   RootedObject onStartFulfilled(
       cx, NewHandler(cx, ControllerStartHandler, controller));
   if (!onStartFulfilled) {
     return false;
   }
-
   RootedObject onStartRejected(
       cx, NewHandler(cx, ControllerStartFailedHandler, controller));
   if (!onStartRejected) {
     return false;
   }
-
   if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
                                onStartRejected)) {
     return false;
   }
 
   return true;
 }
 
@@ -3264,17 +3301,16 @@ SetUpReadableStreamDefaultControllerFrom
           cx, underlyingSource, "ReadableStream source.cancel method",
           cx->names().cancel, &cancelMethod)) {
     return false;
   }
 
   // Step 6. Perform ? SetUpReadableStreamDefaultController(stream,
   //             controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
   //             highWaterMark, sizeAlgorithm).
-
   return SetUpReadableStreamDefaultController(
       cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod,
       highWaterMark, sizeAlgorithm);
 }
 
 /*** 3.10. Class ReadableByteStreamController *******************************/
 
 #if 0  // disable user-defined byte streams
@@ -3406,92 +3442,105 @@ bool ReadableByteStreamController::const
   // Step 1: Throw a TypeError exception.
   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                             JSMSG_BOGUS_CONSTRUCTOR,
                             "ReadableByteStreamController");
   return false;
 }
 
 /**
- * Version of the ReadableByteStreamConstructor that's specialized for
- * handling external, embedding-provided, underlying sources.
+ * Version of SetUpReadableByteStreamController that's specialized for handling
+ * external, embedding-provided, underlying sources.
  */
-static MOZ_MUST_USE ReadableByteStreamController*
-CreateExternalReadableByteStreamController(
+static MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
     JSContext* cx, Handle<ReadableStream*> stream,
     JS::ReadableStreamUnderlyingSource* source) {
+  // Done elsewhere in the standard: Create the controller object.
   Rooted<ReadableByteStreamController*> controller(
       cx, NewBuiltinClassInstance<ReadableByteStreamController>(cx));
   if (!controller) {
-    return nullptr;
-  }
-
-  // Step 3: Set this.[[controlledReadableStream]] to stream.
+    return false;
+  }
+
+  // Step 1: Assert: stream.[[readableStreamController]] is undefined.
+  MOZ_ASSERT(!stream->hasController());
+
+  // Step 2: If autoAllocateChunkSize is not undefined, [...]
+  // (It's treated as undefined.)
+
+  // Step 3: Set controller.[[controlledReadableByteStream]] to stream.
   controller->setStream(stream);
 
-  // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
-  controller->setExternalSource(source);
-
-  // Step 5: Set this.[[pullAgain]] and this.[[pulling]] to false (implicit).
+  // Step 4: Set controller.[[pullAgain]] and controller.[[pulling]] to false.
+  controller->setFlags(0);
   MOZ_ASSERT(!controller->pullAgain());
   MOZ_ASSERT(!controller->pulling());
 
-  // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
-  // Omitted.
-
-  // Step 7: Perform ! ResetQueue(this).
+  // Step 5: Perform
+  //         ! ReadableByteStreamControllerClearPendingPullIntos(controller).
+  // Omitted. This step is apparently redundant; see
+  // <https://github.com/whatwg/streams/issues/975>.
+
+  // Step 6: Perform ! ResetQueue(this).
   controller->setQueueTotalSize(0);
 
-  // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
-  // Step 9: Set this.[[strategyHWM]] to
+  // Step 7: Set controller.[[closeRequested]] and controller.[[started]] to
+  //         false (implicit).
+  MOZ_ASSERT(!controller->closeRequested());
+  MOZ_ASSERT(!controller->started());
+
+  // Step 8: Set controller.[[strategyHWM]] to
   //         ? ValidateAndNormalizeHighWaterMark(highWaterMark).
   controller->setStrategyHWM(0);
 
-  // Step 10: Let autoAllocateChunkSize be
-  //          ? GetV(underlyingByteSource, "autoAllocateChunkSize").
-  // Step 11: If autoAllocateChunkSize is not undefined,
-  // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
-  // Omitted.
-
-  // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
+  // Step 9: Set controller.[[pullAlgorithm]] to pullAlgorithm.
+  // Step 10: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
+  // (These algorithms are given by source's virtual methods.)
+  controller->setExternalSource(source);
+
+  // Step 11: Set controller.[[autoAllocateChunkSize]] to
+  //          autoAllocateChunkSize (implicit).
+  MOZ_ASSERT(controller->autoAllocateChunkSize().isUndefined());
+
+  // Step 12: Set this.[[pendingPullIntos]] to a new empty List.
   if (!SetNewList(cx, controller,
                   ReadableByteStreamController::Slot_PendingPullIntos)) {
-    return nullptr;
-  }
-
-  // Step 14: Let controller be this (implicit).
-  // Step 15: Let startResult be
-  //          ? InvokeOrNoop(underlyingSource, "start", « this »).
-  // Omitted.
-
-  // Step 16: Let startPromise be a promise resolved with startResult:
+    return false;
+  }
+
+  // Step 13: Set stream.[[readableStreamController]] to controller.
+  stream->setController(controller);
+
+  // Step 14: Let startResult be the result of performing startAlgorithm.
+  // (For external sources, this algorithm does nothing and returns undefined.)
+  // Step 15: Let startPromise be a promise resolved with startResult.
   RootedObject startPromise(
       cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
   if (!startPromise) {
-    return nullptr;
-  }
-
+    return false;
+  }
+
+  // Step 16: Upon fulfillment of startPromise, [...]
+  // Step 17: Upon rejection of startPromise with reason r, [...]
   RootedObject onStartFulfilled(
       cx, NewHandler(cx, ControllerStartHandler, controller));
   if (!onStartFulfilled) {
-    return nullptr;
-  }
-
+    return false;
+  }
   RootedObject onStartRejected(
       cx, NewHandler(cx, ControllerStartFailedHandler, controller));
   if (!onStartRejected) {
-    return nullptr;
-  }
-
+    return false;
+  }
   if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
                                onStartRejected)) {
-    return nullptr;
-  }
-
-  return controller;
+    return false;
+  }
+
+  return true;
 }
 
 static const JSPropertySpec ReadableByteStreamController_properties[] = {
     JS_PS_END};
 
 static const JSFunctionSpec ReadableByteStreamController_methods[] = {
     JS_FS_END};
 
@@ -3535,17 +3584,17 @@ CLASS_SPEC(ReadableByteStreamController,
 static MOZ_MUST_USE bool ReadableByteStreamControllerHandleQueueDrain(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
 
 /**
  * Streams spec, 3.10.5.2. [[PullSteps]] ( forAuthorCode )
  */
 static MOZ_MUST_USE JSObject* ReadableByteStreamControllerPullSteps(
     JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
-  // Step 1: Let stream be this.[[controlledReadableStream]].
+  // Step 1: Let stream be this.[[controlledReadableByteStream]].
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
   // Step 2: Assert: ! ReadableStreamHasDefaultReader(stream) is true.
 #ifdef DEBUG
   bool result;
   if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &result)) {
     return nullptr;
   }
@@ -3673,17 +3722,18 @@ static MOZ_MUST_USE JSObject* ReadableBy
     }
 
     RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());
 
     // Step 5.c: Let pullIntoDescriptor be
     //           Record {[[buffer]]: buffer.[[Value]],
     //                   [[byteOffset]]: 0,
     //                   [[byteLength]]: autoAllocateChunkSize,
-    //                   [[bytesFilled]]: 0, [[elementSize]]: 1,
+    //                   [[bytesFilled]]: 0,
+    //                   [[elementSize]]: 1,
     //                   [[ctor]]: %Uint8Array%,
     //                   [[readerType]]: `"default"`}.
     RootedObject pullIntoDescriptor(
         cx, PullIntoDescriptor::create(cx, buffer, 0, autoAllocateChunkSize, 0,
                                        1, nullptr, ReaderType_Default));
     if (!pullIntoDescriptor) {
       return PromiseRejectedWithPendingError(cx);
     }
@@ -3693,17 +3743,17 @@ static MOZ_MUST_USE JSObject* ReadableBy
     if (!AppendToListAtSlot(cx, unwrappedController,
                             ReadableByteStreamController::Slot_PendingPullIntos,
                             pullIntoDescriptor)) {
       return nullptr;
     }
   }
 
   // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream,
-  // forAuthorCode).
+  //                                                       forAuthorCode).
   RootedObject promise(
       cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
   if (!promise) {
     return nullptr;
   }
 
   // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
   if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
@@ -3768,17 +3818,17 @@ static MOZ_MUST_USE bool ReadableByteStr
                     ReadableByteStreamController::Slot_PendingPullIntos);
 }
 
 /**
  * Streams spec, 3.12.6. ReadableByteStreamControllerClose ( controller )
  */
 static MOZ_MUST_USE bool ReadableByteStreamControllerClose(
     JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
-  // Step 1: Let stream be controller.[[controlledReadableStream]].
+  // Step 1: Let stream be controller.[[controlledReadableByteStream]].
   Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
   // Step 2: Assert: controller.[[closeRequested]] is false.
   MOZ_ASSERT(!unwrappedController->closeRequested());
 
   // Step 3: Assert: stream.[[state]] is "readable".
   MOZ_ASSERT(unwrappedStream->readable());
 
@@ -3930,17 +3980,18 @@ bool js::ByteLengthQueuingStrategy::cons
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "ByteLengthQueuingStrategy")) {
     return false;
   }
 
   // Implicit in the spec: Create the new strategy object.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(
+          cx, args, JSProto_ByteLengthQueuingStrategy, &proto)) {
     return false;
   }
   RootedObject strategy(
       cx, NewObjectWithClassProto<ByteLengthQueuingStrategy>(cx, proto));
   if (!strategy) {
     return false;
   }
 
@@ -3989,17 +4040,18 @@ bool js::CountQueuingStrategy::construct
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "CountQueuingStrategy")) {
     return false;
   }
 
   // Implicit in the spec: Create the new strategy object.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(
+          cx, args, JSProto_CountQueuingStrategy, &proto)) {
     return false;
   }
   Rooted<CountQueuingStrategy*> strategy(
       cx, NewObjectWithClassProto<CountQueuingStrategy>(cx, proto));
   if (!strategy) {
     return false;
   }
 
@@ -4065,29 +4117,27 @@ inline static MOZ_MUST_USE bool DequeueV
   MOZ_ASSERT(unwrappedPair);
 
   // Step 5: Set container.[[queueTotalSize]] to
   //         container.[[queueTotalSize]] − pair.[[size]].
   // Step 6: If container.[[queueTotalSize]] < 0, set
   //         container.[[queueTotalSize]] to 0.
   //         (This can occur due to rounding errors.)
   double totalSize = unwrappedContainer->queueTotalSize();
-
   totalSize -= unwrappedPair->size();
   if (totalSize < 0) {
     totalSize = 0;
   }
   unwrappedContainer->setQueueTotalSize(totalSize);
 
+  // Step 7: Return pair.[[value]].
   RootedValue val(cx, unwrappedPair->value());
   if (!cx->compartment()->wrap(cx, &val)) {
     return false;
   }
-
-  // Step 7: Return pair.[[value]].
   chunk.set(val);
   return true;
 }
 
 /**
  * Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
  */
 static MOZ_MUST_USE bool EnqueueValueWithSize(
@@ -4136,19 +4186,16 @@ static MOZ_MUST_USE bool EnqueueValueWit
   unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() +
                                         size);
 
   return true;
 }
 
 /**
  * Streams spec, 6.2.4. ResetQueue ( container ) nothrow
- *
- * Note: can operate on unwrapped container instances from another
- * compartment.
  */
 inline static MOZ_MUST_USE bool ResetQueue(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer) {
   // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
   //         slots (implicit).
   // Step 2: Set container.[[queue]] to a new empty List.
   if (!SetNewList(cx, unwrappedContainer, StreamController::Slot_Queue)) {
     return false;
@@ -4234,37 +4281,41 @@ static MOZ_MUST_USE bool CreateAlgorithm
 
   // Step 7: Return an algorithm which returns a promise resolved with
   //         undefined (implicit).
   return true;
 }
 
 /**
  * Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
+ * As it happens, all callers pass exactly one argument.
  */
 inline static MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, HandleValue O,
                                              HandlePropertyName P,
                                              HandleValue arg,
                                              MutableHandleValue rval) {
   cx->check(O, P, arg);
 
-  // Step 1: Assert: P is a valid property key (omitted).
-  // Step 2: If args was not passed, let args be a new empty List (omitted).
-  // Step 3: Let method be ? GetV(O, P).
+  // Step 1: Assert: O is not undefined.
+  MOZ_ASSERT(!O.isUndefined());
+
+  // Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
+  // Step 3: Assert: args is a List (implicit).
+  // Step 4: Let method be ? GetV(O, P).
   RootedValue method(cx);
   if (!GetProperty(cx, O, P, &method)) {
     return false;
   }
 
-  // Step 4: If method is undefined, return.
+  // Step 5: If method is undefined, return.
   if (method.isUndefined()) {
     return true;
   }
 
-  // Step 5: Return ? Call(method, O, args).
+  // Step 6: Return ? Call(method, O, args).
   return Call(cx, method, O, arg, rval);
 }
 
 /**
  * Streams spec, 6.3.5. PromiseCall ( F, V, args )
  * As it happens, all callers pass exactly one argument.
  */
 static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
@@ -4653,22 +4704,26 @@ JS_PUBLIC_API bool JS::ReadableStreamUpd
     RootedValue chunk(cx, ObjectValue(*transferredView));
     if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
                                                     false)) {
       return false;
     }
 
     unwrappedController->setQueueTotalSize(availableData - bytesWritten);
   } else {
-    // Step b: Otherwise,
-    // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
+    // Step 9: Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true,
+    //         [...]
+    // (Omitted. BYOB readers are not implemented.)
+
+    // Step 10: Otherwise,
+    // Step a: Assert: ! IsReadableStreamLocked(stream) is false.
     MOZ_ASSERT(!unwrappedStream->locked());
 
-    // Step ii: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(
-    //              controller, transferredBuffer, byteOffset, byteLength).
+    // Step b: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(
+    //         controller, transferredBuffer, byteOffset, byteLength).
     // (Not needed for external underlying sources.)
   }
 
   return true;
 }
 
 JS_PUBLIC_API bool JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj,
                                          MutableHandleObject branch1Obj,
@@ -4686,17 +4741,16 @@ JS_PUBLIC_API bool JS::ReadableStreamTee
   Rooted<ReadableStream*> branch2Stream(cx);
   if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1Stream,
                          &branch2Stream)) {
     return false;
   }
 
   branch1Obj.set(branch1Stream);
   branch2Obj.set(branch2Stream);
-
   return true;
 }
 
 JS_PUBLIC_API bool JS::ReadableStreamGetDesiredSize(JSContext* cx,
                                                     JSObject* streamObj,
                                                     bool* hasValue,
                                                     double* value) {
   ReadableStream* unwrappedStream =
@@ -4730,17 +4784,18 @@ JS_PUBLIC_API bool JS::ReadableStreamClo
   Rooted<ReadableStream*> unwrappedStream(
       cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
   if (!unwrappedStream) {
     return false;
   }
 
   Rooted<ReadableStreamController*> unwrappedControllerObj(
       cx, unwrappedStream->controller());
-  if (!VerifyControllerStateForClosing(cx, unwrappedControllerObj)) {
+  if (!CheckReadableStreamControllerCanCloseOrEnqueue(
+          cx, unwrappedControllerObj, "close")) {
     return false;
   }
 
   if (unwrappedControllerObj->is<ReadableStreamDefaultController>()) {
     Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
     unwrappedController =
         &unwrappedControllerObj->as<ReadableStreamDefaultController>();
     return ReadableStreamDefaultControllerClose(cx, unwrappedController);
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -308,19 +308,18 @@ class ReadableStreamController : public 
     static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2,
                   "External underling sources are stored as PrivateValues, "
                   "so they must have even addresses");
     MOZ_ASSERT(hasExternalSource());
     return static_cast<JS::ReadableStreamUnderlyingSource*>(
         underlyingSource().toPrivate());
   }
   void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
-    MOZ_ASSERT(getFixedSlot(Slot_Flags).isUndefined());
     setUnderlyingSource(JS::PrivateValue(underlyingSource));
-    setFlags(Flag_ExternalSource);
+    addFlags(Flag_ExternalSource);
   }
   double strategyHWM() const {
     return getFixedSlot(Slot_StrategyHWM).toNumber();
   }
   void setStrategyHWM(double highWaterMark) {
     setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark));
   }
   uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -3421,17 +3421,17 @@ bool js::StringConstructor(JSContext* cx
       return false;
     }
   } else {
     str = cx->runtime()->emptyString;
   }
 
   if (args.isConstructing()) {
     RootedObject proto(cx);
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_String, &proto)) {
       return false;
     }
 
     StringObject* strobj = StringObject::create(cx, str, proto);
     if (!strobj) {
       return false;
     }
     args.rval().setObject(*strobj);
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -222,17 +222,17 @@ JS_PUBLIC_API bool JS::SetWeakMapEntry(J
   CallArgs args = CallArgsFromVp(argc, vp);
 
   // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1.
   if (!ThrowIfNotConstructing(cx, args, "WeakMap")) {
     return false;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WeakMap, &proto)) {
     return false;
   }
 
   RootedObject obj(cx, NewObjectWithClassProto<WeakMapObject>(cx, proto));
   if (!obj) {
     return false;
   }
 
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -167,17 +167,17 @@ bool WeakSetObject::construct(JSContext*
   // Based on our "Set" implementation instead of the more general ES6 steps.
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!ThrowIfNotConstructing(cx, args, "WeakSet")) {
     return false;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WeakSet, &proto)) {
     return false;
   }
 
   Rooted<WeakSetObject*> obj(cx, WeakSetObject::create(cx, proto));
   if (!obj) {
     return false;
   }
 
--- a/js/src/builtin/intl/Collator.cpp
+++ b/js/src/builtin/intl/Collator.cpp
@@ -76,17 +76,17 @@ static const JSPropertySpec collator_pro
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool Collator(JSContext* cx, const CallArgs& args) {
   // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
   // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   if (!proto) {
     proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
     if (!proto) {
       return false;
     }
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -87,17 +87,17 @@ static const JSPropertySpec dateTimeForm
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct,
                            DateTimeFormatOptions dtfOptions) {
   // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
   // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   if (!proto) {
     proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
     if (!proto) {
       return false;
     }
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -86,17 +86,17 @@ static const JSPropertySpec numberFormat
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
   // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
   // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   if (!proto) {
     proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
     if (!proto) {
       return false;
     }
--- a/js/src/builtin/intl/PluralRules.cpp
+++ b/js/src/builtin/intl/PluralRules.cpp
@@ -72,17 +72,17 @@ static bool PluralRules(JSContext* cx, u
 
   // Step 1.
   if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules")) {
     return false;
   }
 
   // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   if (!proto) {
     proto = GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global());
     if (!proto) {
       return false;
     }
--- a/js/src/builtin/intl/RelativeTimeFormat.cpp
+++ b/js/src/builtin/intl/RelativeTimeFormat.cpp
@@ -78,17 +78,17 @@ static bool RelativeTimeFormat(JSContext
 
   // Step 1.
   if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat")) {
     return false;
   }
 
   // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
     return false;
   }
 
   if (!proto) {
     proto =
         GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global());
     if (!proto) {
       return false;
--- a/js/src/gdb/mozilla/unwind.py
+++ b/js/src/gdb/mozilla/unwind.py
@@ -115,25 +115,24 @@ class UnwinderTypeCache(object):
             # Strip off "js::jit::".
             name = field.name[9:]
             enumval = long(field.enumval)
             self.d[name] = enumval
             self.frame_enum_names[enumval] = name
             class_type = gdb.lookup_type('js::jit::' + SizeOfFramePrefix[name])
             self.frame_class_types[enumval] = class_type.pointer()
 
-# gdb doesn't have a direct way to tell us if a given address is
-# claimed by some shared library or the executable.  See
-# https://sourceware.org/bugzilla/show_bug.cgi?id=19288
-# In the interest of not requiring a patched gdb, instead we read
-# /proc/.../maps.  This only works locally, but maybe could work
-# remotely using "remote get".  FIXME.
-
 
 def parse_proc_maps():
+    # gdb doesn't have a direct way to tell us if a given address is
+    # claimed by some shared library or the executable.  See
+    # https://sourceware.org/bugzilla/show_bug.cgi?id=19288
+    # In the interest of not requiring a patched gdb, instead we read
+    # /proc/.../maps.  This only works locally, but maybe could work
+    # remotely using "remote get".  FIXME.
     mapfile = '/proc/' + str(gdb.selected_inferior().pid) + '/maps'
     # Note we only examine executable mappings here.
     matcher = re.compile("^([a-fA-F0-9]+)-([a-fA-F0-9]+)\s+..x.\s+\S+\s+\S+\s+\S*(.*)$")
     mappings = []
     with open(mapfile, "r") as inp:
         for line in inp:
             match = matcher.match(line)
             if not match:
@@ -143,36 +142,36 @@ def parse_proc_maps():
             end = match.group(2)
             name = match.group(3).strip()
             if name is '' or (name.startswith('[') and name is not '[vdso]'):
                 # Skip entries not corresponding to a file.
                 continue
             mappings.append((long(start, 16), long(end, 16)))
     return mappings
 
-# A symbol/value pair as expected from gdb frame decorators.
-
 
 class FrameSymbol(object):
+    "A symbol/value pair as expected from gdb frame decorators."
+
     def __init__(self, sym, val):
         self.sym = sym
         self.val = val
 
     def symbol(self):
         return self.sym
 
     def value(self):
         return self.val
 
-# This represents a single JIT frame for the purposes of display.
-# That is, the frame filter creates instances of this when it sees a
-# JIT frame in the stack.
-
 
 class JitFrameDecorator(FrameDecorator):
+    """This represents a single JIT frame for the purposes of display.
+    That is, the frame filter creates instances of this when it sees a
+    JIT frame in the stack."""
+
     def __init__(self, base, info, cache):
         super(JitFrameDecorator, self).__init__(base)
         self.info = info
         self.cache = cache
 
     def _decode_jitframe(self, this_frame):
         calleetoken = long(this_frame['calleeToken_'])
         tag = calleetoken & 3
@@ -253,20 +252,20 @@ class JitFrameDecorator(FrameDecorator):
             # anything better to do.
             if i == 0:
                 name = 'this'
             else:
                 name = 'arg%d' % i
             result.append(FrameSymbol(name, args_ptr[i]))
         return result
 
-# A frame filter for SpiderMonkey.
-
 
 class SpiderMonkeyFrameFilter(object):
+    "A frame filter for SpiderMonkey."
+
     # |state_holder| is either None, or an instance of
     # SpiderMonkeyUnwinder.  If the latter, then this class will
     # reference the |unwinder_state| attribute to find the current
     # unwinder state.
     def __init__(self, cache, state_holder):
         self.name = "SpiderMonkey"
         self.enabled = True
         self.priority = 100
@@ -280,41 +279,41 @@ class SpiderMonkeyFrameFilter(object):
         info = self.state_holder.unwinder_state.get_frame(base)
         if info is None:
             return frame
         return JitFrameDecorator(frame, info, self.cache)
 
     def filter(self, frame_iter):
         return imap(self.maybe_wrap_frame, frame_iter)
 
-# A frame id class, as specified by the gdb unwinder API.
-
 
 class SpiderMonkeyFrameId(object):
+    "A frame id class, as specified by the gdb unwinder API."
+
     def __init__(self, sp, pc):
         self.sp = sp
         self.pc = pc
 
-# This holds all the state needed during a given unwind.  Each time a
-# new unwind is done, a new instance of this class is created.  It
-# keeps track of all the state needed to unwind JIT frames.  Note that
-# this class is not directly instantiated.
-#
-# This is a base class, and must be specialized for each target
-# architecture, both because we need to use arch-specific register
-# names, and because entry frame unwinding is arch-specific.
-# See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info
-# about the register name issue.
-#
-# Each subclass must define SP_REGISTER, PC_REGISTER, and
-# SENTINEL_REGISTER (see x64UnwinderState for info); and implement
-# unwind_entry_frame_registers.
-
 
 class UnwinderState(object):
+    """This holds all the state needed during a given unwind.  Each time a
+    new unwind is done, a new instance of this class is created.  It
+    keeps track of all the state needed to unwind JIT frames.  Note that
+    this class is not directly instantiated.
+
+    This is a base class, and must be specialized for each target
+    architecture, both because we need to use arch-specific register
+    names, and because entry frame unwinding is arch-specific.
+    See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info
+    about the register name issue.
+
+    Each subclass must define SP_REGISTER, PC_REGISTER, and
+    SENTINEL_REGISTER (see x64UnwinderState for info); and implement
+    unwind_entry_frame_registers."""
+
     def __init__(self, typecache):
         self.next_sp = None
         self.next_type = None
         self.activation = None
         # An unwinder instance is specific to a thread.  Record the
         # selected thread for later verification.
         self.thread = gdb.selected_thread()
         self.frame_map = {}
@@ -500,20 +499,20 @@ class UnwinderState(object):
             if self.next_type == self.typecache.CppToJSJit:
                 return self.unwind_entry_frame(pc, pending_frame)
             return self.unwind_ordinary(pc, pending_frame)
         # Maybe we've found an exit frame.  FIXME I currently don't
         # know how to identify these precisely, so we'll just hope for
         # the time being.
         return self.unwind_exit_frame(pc, pending_frame)
 
-# The UnwinderState subclass for x86-64.
-
 
 class x64UnwinderState(UnwinderState):
+    "The UnwinderState subclass for x86-64."
+
     SP_REGISTER = 'rsp'
     PC_REGISTER = 'rip'
 
     # A register unique to this architecture, that is also likely to
     # have been saved in any frame.  The best thing to use here is
     # some arch-specific name for PC or SP.
     SENTINEL_REGISTER = 'rip'
 
@@ -529,22 +528,22 @@ class x64UnwinderState(UnwinderState):
         sp = sp + 1
         for reg in self.PUSHED_REGS:
             data = sp.dereference()
             sp = sp + 1
             unwind_info.add_saved_register(reg, data)
             if reg is "rbp":
                 unwind_info.add_saved_register(self.SP_REGISTER, sp)
 
-# The unwinder object.  This provides the "user interface" to the JIT
-# unwinder, and also handles constructing or destroying UnwinderState
-# objects as needed.
-
 
 class SpiderMonkeyUnwinder(Unwinder):
+    """The unwinder object.  This provides the "user interface" to the JIT
+    unwinder, and also handles constructing or destroying UnwinderState
+    objects as needed."""
+
     # A list of all the possible unwinders.  See |self.make_unwinder|.
     UNWINDERS = [x64UnwinderState]
 
     def __init__(self, typecache):
         super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey")
         self.typecache = typecache
         self.unwinder_state = None
 
@@ -596,21 +595,21 @@ class SpiderMonkeyUnwinder(Unwinder):
             self.unwinder_state = self.make_unwinder(pending_frame)
         if not self.unwinder_state:
             return None
         return self.unwinder_state.unwind(pending_frame)
 
     def invalidate_unwinder_state(self, *args, **kwargs):
         self.unwinder_state = None
 
-# Register the unwinder and frame filter with |objfile|.  If |objfile|
-# is None, register them globally.
-
 
 def register_unwinder(objfile):
+    """Register the unwinder and frame filter with |objfile|.  If |objfile|
+    is None, register them globally."""
+
     type_cache = UnwinderTypeCache()
     unwinder = None
     # This currently only works on Linux, due to parse_proc_maps.
     if _have_unwinder and platform.system() == "Linux":
         unwinder = SpiderMonkeyUnwinder(type_cache)
         gdb.unwinder.register_unwinder(objfile, unwinder, replace=True)
     # We unconditionally register the frame filter, because at some
     # point we'll add interpreter frame filtering.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/stream/reader-closedPromise-handled-2.js
@@ -0,0 +1,33 @@
+// Releasing a reader should not result in a promise being tracked as
+// unhandled.
+
+function test(readable) {
+  // Create an errored stream.
+  let controller;
+  let stream = new ReadableStream({
+    start(c) {
+      controller = c;
+    }
+  });
+  drainJobQueue();
+
+  // Track promises.
+  let status = new Map;
+  setPromiseRejectionTrackerCallback((p, x) => { status.set(p, x); });
+
+  // Per Streams spec 3.7.5 step 5, this creates a rejected promise
+  // (reader.closed) but marks it as handled.
+  let reader = stream.getReader();
+  if (!readable) {
+    controller.close();
+  }
+  reader.releaseLock();
+
+  // Check that the promise's status is not 0 (unhandled);
+  // it may be either 1 (handled) or undefined (never tracked).
+  let result = status.get(reader.closed);
+  assertEq(result === 1 || result === undefined, true);
+}
+
+test(true);
+test(false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/stream/reader-closedPromise-handled.js
@@ -0,0 +1,23 @@
+// Creating a reader from an errored stream should not result in a promise
+// being tracked as unhandled.
+
+// Create an errored stream.
+let stream = new ReadableStream({
+  start(controller) {
+    controller.error(new Error("splines insufficiently reticulated"));
+  }
+});
+drainJobQueue();
+
+// Track promises.
+let status = new Map;
+setPromiseRejectionTrackerCallback((p, x) => { status.set(p, x); });
+
+// Per Streams spec 3.7.4 step 5.c, this creates a rejected promise
+// (reader.closed) but marks it as handled.
+let reader = stream.getReader();
+
+// Check that the promise's status is not 0 (unhandled);
+// it may be either 1 (handled) or undefined (never tracked).
+let result = status.get(reader.closed);
+assertEq(result === 1 || result === undefined, true);
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -3131,17 +3131,17 @@ static const JSFunctionSpec date_methods
     JS_FN(js_valueOf_str, date_valueOf, 0, 0),
     JS_SYM_FN(toPrimitive, date_toPrimitive, 1, JSPROP_READONLY),
     JS_FS_END};
 
 static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
   MOZ_ASSERT(args.isConstructing());
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Date, &proto)) {
     return false;
   }
 
   JSObject* obj = NewDateObjectMsec(cx, t, proto);
   if (!obj) {
     return false;
   }
 
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -383,75 +383,74 @@ JS_PUBLIC_API uint64_t JS::ExceptionTime
   }
 
   return obj->as<ErrorObject>().timeWarpTarget();
 }
 
 bool Error(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
+  // ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
+  // called as functions, without operator new.  But as we do not give
+  // each constructor a distinct JSClass, we must get the exception type
+  // ourselves.
+  JSExnType exnType =
+      JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
+
+  JSProtoKey protoKey =
+      JSCLASS_CACHED_PROTO_KEY(&ErrorObject::classes[exnType]);
+
   // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
     return false;
   }
 
-  /* Compute the error message, if any. */
+  // Compute the error message, if any.
   RootedString message(cx, nullptr);
   if (args.hasDefined(0)) {
     message = ToString<CanGC>(cx, args[0]);
     if (!message) {
       return false;
     }
   }
 
-  /* Find the scripted caller, but only ones we're allowed to know about. */
+  // Find the scripted caller, but only ones we're allowed to know about.
   NonBuiltinFrameIter iter(cx, cx->realm()->principals());
 
-  /* Set the 'fileName' property. */
   RootedString fileName(cx);
   if (args.length() > 1) {
     fileName = ToString<CanGC>(cx, args[1]);
   } else {
     fileName = cx->runtime()->emptyString;
     if (!iter.done()) {
       if (const char* cfilename = iter.filename()) {
         fileName = JS_NewStringCopyZ(cx, cfilename);
       }
     }
   }
   if (!fileName) {
     return false;
   }
 
-  /* Set the 'lineNumber' property. */
   uint32_t lineNumber, columnNumber = 0;
   if (args.length() > 2) {
     if (!ToUint32(cx, args[2], &lineNumber)) {
       return false;
     }
   } else {
     lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
     columnNumber = FixupColumnForDisplay(columnNumber);
   }
 
   RootedObject stack(cx);
   if (!CaptureStack(cx, &stack)) {
     return false;
   }
 
-  /*
-   * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
-   * called as functions, without operator new.  But as we do not give
-   * each constructor a distinct JSClass, we must get the exception type
-   * ourselves.
-   */
-  JSExnType exnType =
-      JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
-
   RootedObject obj(cx,
                    ErrorObject::create(cx, exnType, stack, fileName, lineNumber,
                                        columnNumber, nullptr, message, proto));
   if (!obj) {
     return false;
   }
 
   args.rval().setObject(*obj);
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -558,17 +558,17 @@ static bool Number(JSContext* cx, unsign
       args.rval().set(args[0]);
     } else {
       args.rval().setInt32(0);
     }
     return true;
   }
 
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Number, &proto)) {
     return false;
   }
 
   double d = args.length() > 0 ? args[0].toNumber() : 0;
   JSObject* obj = NumberObject::create(cx, d, proto);
   if (!obj) {
     return false;
   }
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -261,22 +261,22 @@ skip script test262/built-ins/Proxy/revo
 skip script test262/built-ins/Promise/all/resolve-element-function-name.js
 skip script test262/built-ins/Promise/executor-function-name.js
 skip script test262/built-ins/Promise/reject-function-name.js
 skip script test262/built-ins/Promise/resolve-function-name.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=944846
 skip script test262/built-ins/Number/prototype/toExponential/return-values.js
 
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1225839
+skip script test262/built-ins/Function/internals/Call/class-ctor-realm.js
+
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1288457
 skip script test262/built-ins/Function/internals/Construct/base-ctor-revoked-proxy-realm.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1225839
-skip script test262/built-ins/Function/internals/Call/class-ctor-realm.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1297179
 skip script test262/built-ins/Proxy/apply/arguments-realm.js
 skip script test262/built-ins/Proxy/apply/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/construct/arguments-realm.js
 skip script test262/built-ins/Proxy/construct/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/defineProperty/desc-realm.js
 skip script test262/built-ins/Proxy/defineProperty/null-handler-realm.js
 skip script test262/built-ins/Proxy/defineProperty/targetdesc-configurable-desc-not-configurable-realm.js
@@ -293,61 +293,25 @@ skip script test262/built-ins/Proxy/getP
 skip script test262/built-ins/Proxy/has/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/isExtensible/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/ownKeys/return-not-list-object-throws-realm.js
 skip script test262/built-ins/Proxy/ownKeys/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/preventExtensions/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/set/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/setPrototypeOf/trap-is-not-callable-realm.js
 
-# Erros thrown from wrong realm, similar to 1225839, 1288457, and 1297179.
+# Errors thrown from wrong realm, similar to 1225839, 1288457, and 1297179.
 skip script test262/built-ins/Array/length/define-own-prop-length-overflow-realm.js
 skip script test262/built-ins/Function/internals/Construct/derived-return-val-realm.js
 skip script test262/built-ins/Function/internals/Construct/derived-this-uninitialized-realm.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317416
 skip script test262/language/expressions/super/realm.js
-skip script test262/built-ins/Array/proto-from-ctor-realm.js
-skip script test262/built-ins/ArrayBuffer/proto-from-ctor-realm.js
-skip script test262/built-ins/Boolean/proto-from-ctor-realm.js
-skip script test262/built-ins/DataView/proto-from-ctor-realm.js
-skip script test262/built-ins/DataView/proto-from-ctor-realm-sab.js
-skip script test262/built-ins/Date/proto-from-ctor-realm-one.js
-skip script test262/built-ins/Date/proto-from-ctor-realm-two.js
-skip script test262/built-ins/Date/proto-from-ctor-realm-zero.js
-skip script test262/built-ins/Error/proto-from-ctor-realm.js
+skip script test262/built-ins/GeneratorFunction/proto-from-ctor-realm.js
 skip script test262/built-ins/Function/prototype/bind/proto-from-ctor-realm.js
-skip script test262/built-ins/Function/proto-from-ctor-realm.js
-skip script test262/built-ins/GeneratorFunction/proto-from-ctor-realm.js
-skip script test262/built-ins/Map/proto-from-ctor-realm.js
-skip script test262/built-ins/Number/proto-from-ctor-realm.js
-skip script test262/built-ins/Object/proto-from-ctor.js
-skip script test262/built-ins/Promise/proto-from-ctor-realm.js
-skip script test262/built-ins/RegExp/proto-from-ctor-realm.js
-skip script test262/built-ins/Set/proto-from-ctor-realm.js
-skip script test262/built-ins/SharedArrayBuffer/proto-from-ctor-realm.js
-skip script test262/built-ins/String/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/buffer-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/buffer-arg/proto-from-ctor-realm-sab.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/length-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/no-args/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/object-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors-bigint/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/buffer-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/buffer-arg/proto-from-ctor-realm-sab.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/length-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/no-args/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/object-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/proto-from-ctor-realm.js
-skip script test262/built-ins/TypedArrayConstructors/ctors/typedarray-arg/same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
-skip script test262/built-ins/WeakMap/proto-from-ctor-realm.js
-skip script test262/built-ins/WeakSet/proto-from-ctor-realm.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317395
 skip script test262/built-ins/ArrayBuffer/prototype/byteLength/detached-buffer.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317378
 skip script test262/language/statements/do-while/cptn-abrupt-empty.js
 skip script test262/language/statements/do-while/cptn-normal.js
 skip script test262/language/statements/for-in/cptn-decl-abrupt-empty.js
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -150,16 +150,22 @@ def parse_args():
                           help='Run tests to check output for different jit-flags')
     op.add_option_group(harness_og)
 
     input_og = OptionGroup(op, "Inputs", "Change what tests are run.")
     input_og.add_option('-f', '--file', dest='test_file', action='append',
                         help='Get tests from the given file.')
     input_og.add_option('-x', '--exclude-file', action='append',
                         help='Exclude tests from the given file.')
+    input_og.add_option('--wpt', dest='wpt',
+                        type='choice',
+                        choices=['enabled', 'disabled', 'if-running-everything'],
+                        default='if-running-everything',
+                        help="Enable or disable shell web-platform-tests "
+                        "(default: enable if no test paths are specified).")
     input_og.add_option('--include', action='append', dest='requested_paths', default=[],
                         help='Include the given test file or directory.')
     input_og.add_option('--exclude', action='append', dest='excluded_paths', default=[],
                         help='Exclude the given test file or directory.')
     input_og.add_option('-d', '--exclude-random', dest='random',
                         action='store_false',
                         help='Exclude tests marked as "random."')
     input_og.add_option('--run-skipped', action='store_true',
@@ -429,17 +435,21 @@ def load_tests(options, requested_paths,
         xul_tester = manifest.XULInfoTester(xul_info, options.js_shell)
 
     test_dir = dirname(abspath(__file__))
     path_options = PathOptions(test_dir, requested_paths, excluded_paths)
     test_count = manifest.count_tests(test_dir, path_options)
     test_gen = manifest.load_reftests(test_dir, path_options, xul_tester)
 
     # WPT tests are already run in the browser in their own harness.
-    if not options.make_manifests:
+    wpt_enabled = (options.wpt == 'enabled' or
+                   (options.wpt == 'if-running-everything' and
+                    len(requested_paths) == 0 and
+                    not options.make_manifests))
+    if wpt_enabled:
         wpt_tests = load_wpt_tests(xul_tester,
                                    requested_paths,
                                    excluded_paths)
         test_count += len(wpt_tests)
         test_gen = chain(test_gen, wpt_tests)
 
     if options.test_reflect_stringify is not None:
         def trs_gen(tests):
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -414,17 +414,18 @@ bool ArrayBufferObject::class_constructo
   uint64_t byteLength;
   if (!ToIndex(cx, args.get(0), &byteLength)) {
     return false;
   }
 
   // Step 3 (Inlined 24.1.1.1 AllocateArrayBuffer).
   // 24.1.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ArrayBuffer,
+                                          &proto)) {
     return false;
   }
 
   // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
   // Refuse to allocate too large buffers, currently limited to ~2 GiB.
   if (byteLength > INT32_MAX) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_BAD_ARRAY_LENGTH);
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -116,22 +116,22 @@ class GlobalObject : public NativeObject
    * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and
    * we won't expose GlobalObject, so just assert that the two values are
    * synchronized.
    */
   static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                 "global object slot counts are inconsistent");
 
   static unsigned constructorSlot(JSProtoKey key) {
-    MOZ_ASSERT(key <= JSProto_LIMIT);
+    MOZ_ASSERT(key < JSProto_LIMIT);
     return APPLICATION_SLOTS + key;
   }
 
   static unsigned prototypeSlot(JSProtoKey key) {
-    MOZ_ASSERT(key <= JSProto_LIMIT);
+    MOZ_ASSERT(key < JSProto_LIMIT);
     return APPLICATION_SLOTS + JSProto_LIMIT + key;
   }
 
  public:
   LexicalEnvironmentObject& lexicalEnvironment() const;
   GlobalScope& emptyGlobalScope() const;
 
   void setOriginalEval(JSObject* evalobj) {
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -352,17 +352,17 @@ class MOZ_STACK_CLASS TryNoteIter {
       if (tn_->kind == JSTRY_FOR_OF_ITERCLOSE) {
         do {
           ++tn_;
           MOZ_ASSERT(tn_ != tnEnd_);
           MOZ_ASSERT_IF(pcInRange(), tn_->kind != JSTRY_FOR_OF_ITERCLOSE);
         } while (!(pcInRange() && tn_->kind == JSTRY_FOR_OF));
 
         // Advance to trynote following the enclosing for-of.
-        ++tn_;
+        continue;
       }
 
       /*
        * We have a note that covers the exception pc but we must check
        * whether the interpreter has already executed the corresponding
        * handler. This is possible when the executed bytecode implements
        * break or return from inside a for-in loop.
        *
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1994,16 +1994,17 @@ static bool CreateDynamicFunction(JSCont
   SourceOwnership ownership = stableChars.maybeGiveOwnershipToCaller()
                                   ? SourceOwnership::TakeOwnership
                                   : SourceOwnership::Borrowed;
   SourceText<char16_t> srcBuf;
   if (!srcBuf.init(cx, chars.begin().get(), chars.length(), ownership)) {
     return false;
   }
 
+  JSProtoKey protoKey = JSProto_Null;
   if (isAsync) {
     if (isGenerator) {
       if (!CompileStandaloneAsyncGenerator(cx, &fun, options, srcBuf,
                                            parameterListEnd)) {
         return false;
       }
     } else {
       if (!CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf,
@@ -2017,22 +2018,23 @@ static bool CreateDynamicFunction(JSCont
                                       parameterListEnd)) {
         return false;
       }
     } else {
       if (!CompileStandaloneFunction(cx, &fun, options, srcBuf,
                                      parameterListEnd)) {
         return false;
       }
+      protoKey = JSProto_Function;
     }
   }
 
   // Steps 6, 29.
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
     return false;
   }
 
   if (isAsync) {
     // Create the async function wrapper.
     JSObject* wrapped;
     if (isGenerator) {
       wrapped = proto ? WrapAsyncGeneratorWithProto(cx, fun, proto)
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -1021,17 +1021,18 @@ bool js::NewObjectScriptedCall(JSContext
 
   pobj.set(obj);
   return true;
 }
 
 JSObject* js::CreateThis(JSContext* cx, const Class* newclasp,
                          HandleObject callee) {
   RootedObject proto(cx);
-  if (!GetPrototypeFromConstructor(cx, callee, &proto)) {
+  if (!GetPrototypeFromConstructor(
+          cx, callee, JSCLASS_CACHED_PROTO_KEY(newclasp), &proto)) {
     return nullptr;
   }
   gc::AllocKind kind = NewObjectGCKind(newclasp);
   return NewObjectWithClassProto(cx, newclasp, proto, kind);
 }
 
 static inline JSObject* CreateThisForFunctionWithGroup(JSContext* cx,
                                                        HandleObjectGroup group,
@@ -1153,30 +1154,63 @@ JSObject* js::CreateThisForFunctionWithP
     }
     TypeScript::SetThis(cx, script, TypeSet::ObjectType(res));
   }
 
   return res;
 }
 
 bool js::GetPrototypeFromConstructor(JSContext* cx, HandleObject newTarget,
+                                     JSProtoKey intrinsicDefaultProto,
                                      MutableHandleObject proto) {
   RootedValue protov(cx);
   if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protov)) {
     return false;
   }
-  proto.set(protov.isObject() ? &protov.toObject() : nullptr);
+  if (protov.isObject()) {
+    proto.set(&protov.toObject());
+  } else if (newTarget->is<JSFunction>() &&
+             newTarget->as<JSFunction>().realm() == cx->realm()) {
+    // Steps 4.a-b fetch the builtin prototype of the current realm, which we
+    // represent as nullptr.
+    proto.set(nullptr);
+  } else if (intrinsicDefaultProto == JSProto_Null) {
+    // Bug 1317416. The caller did not pass a reasonable JSProtoKey, so let the
+    // caller select a prototype object. Most likely they will choose one from
+    // the wrong realm.
+    proto.set(nullptr);
+  } else {
+    // Step 4.a: Let realm be ? GetFunctionRealm(constructor);
+    JSObject* unwrappedConstructor = CheckedUnwrap(newTarget);
+    if (!unwrappedConstructor) {
+      ReportAccessDenied(cx);
+      return false;
+    }
+
+    // Step 4.b: Set proto to realm's intrinsic object named
+    //           intrinsicDefaultProto.
+    {
+      AutoRealm ar(cx, unwrappedConstructor);
+      proto.set(GlobalObject::getOrCreatePrototype(cx, intrinsicDefaultProto));
+    }
+    if (!proto) {
+      return false;
+    }
+    if (!cx->compartment()->wrap(cx, proto)) {
+      return false;
+    }
+  }
   return true;
 }
 
 JSObject* js::CreateThisForFunction(JSContext* cx, HandleObject callee,
                                     HandleObject newTarget,
                                     NewObjectKind newKind) {
   RootedObject proto(cx);
-  if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) {
+  if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) {
     return nullptr;
   }
 
   JSObject* obj =
       CreateThisForFunctionWithProto(cx, callee, newTarget, proto, newKind);
 
   if (obj && newKind == SingletonObject) {
     RootedPlainObject nobj(cx, &obj->as<PlainObject>());
@@ -3674,16 +3708,20 @@ static void DumpProperty(const NativeObj
 
   if (shape.isDataProperty()) {
     out.printf(" slot %d", shape.maybeSlot());
   }
 
   out.printf(")\n");
 }
 
+bool JSObject::hasSameRealmAs(JSContext* cx) const {
+  return nonCCWRealm() == cx->realm();
+}
+
 bool JSObject::uninlinedIsProxy() const { return is<ProxyObject>(); }
 
 bool JSObject::uninlinedNonProxyIsExtensible() const {
   return nonProxyIsExtensible();
 }
 
 void JSObject::dump(js::GenericPrinter& out) const {
   const JSObject* obj = this;
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -430,16 +430,17 @@ class JSObject : public js::gc::Cell {
   // Cross-compartment wrappers are not associated with a single realm/global,
   // so these methods assert the object is not a CCW.
   inline js::GlobalObject& nonCCWGlobal() const;
 
   JS::Realm* nonCCWRealm() const {
     MOZ_ASSERT(!js::UninlinedIsCrossCompartmentWrapper(this));
     return group_->realm();
   }
+  bool hasSameRealmAs(JSContext* cx) const;
 
   // Returns the object's realm even if the object is a CCW (be careful, in
   // this case the realm is not very meaningful because wrappers are shared by
   // all realms in the compartment).
   JS::Realm* maybeCCWRealm() const { return group_->realm(); }
 
   /*
    * ES5 meta-object properties and operations.
@@ -788,32 +789,54 @@ inline gc::InitialHeap GetInitialHeap(Ne
 bool NewObjectWithTaggedProtoIsCachable(JSContext* cx,
                                         Handle<TaggedProto> proto,
                                         NewObjectKind newKind,
                                         const Class* clasp);
 
 // ES6 9.1.15 GetPrototypeFromConstructor.
 extern bool GetPrototypeFromConstructor(JSContext* cx,
                                         js::HandleObject newTarget,
+                                        JSProtoKey intrinsicDefaultProto,
                                         js::MutableHandleObject proto);
 
+// https://tc39.github.io/ecma262/#sec-getprototypefromconstructor
+//
+// Determine which [[Prototype]] to use when creating a new object using a
+// builtin constructor.
+//
+// This sets `proto` to `nullptr` to mean "the builtin prototype object for
+// this type in the current realm", the common case.
+//
+// We could set it to `cx->global()->getOrCreatePrototype(protoKey)`, but
+// nullptr gets a fast path in e.g. js::NewObjectWithClassProtoCommon.
+//
+// intrinsicDefaultProto can be JSProto_Null if there's no appropriate
+// JSProtoKey enum; but we then select the wrong prototype object in a
+// multi-realm corner case (see bug 1515167).
 MOZ_ALWAYS_INLINE bool GetPrototypeFromBuiltinConstructor(
-    JSContext* cx, const CallArgs& args, js::MutableHandleObject proto) {
-  // When proto is set to nullptr, the caller is expected to select the
-  // correct default built-in prototype for this constructor.
+    JSContext* cx, const CallArgs& args, JSProtoKey intrinsicDefaultProto,
+    js::MutableHandleObject proto) {
+  // We can skip the "prototype" lookup in the two common cases:
+  // 1.  Builtin constructor called without `new`, as in `obj = Object();`.
+  // 2.  Builtin constructor called with `new`, as in `obj = new Object();`.
+  //
+  // Cases that can't take the fast path include `new MySubclassOfObject()`,
+  // `new otherGlobal.Object()`, and `Reflect.construct(Object, [], Date)`.
   if (!args.isConstructing() ||
       &args.newTarget().toObject() == &args.callee()) {
+    MOZ_ASSERT(args.callee().hasSameRealmAs(cx));
     proto.set(nullptr);
     return true;
   }
 
   // We're calling this constructor from a derived class, retrieve the
   // actual prototype from newTarget.
   RootedObject newTarget(cx, &args.newTarget().toObject());
-  return GetPrototypeFromConstructor(cx, newTarget, proto);
+  return GetPrototypeFromConstructor(cx, newTarget, intrinsicDefaultProto,
+                                     proto);
 }
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern JSObject* CreateThisForFunctionWithProto(
     JSContext* cx, js::HandleObject callee, HandleObject newTarget,
     HandleObject proto, NewObjectKind newKind = GenericObject);
 
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -193,17 +193,18 @@ bool SharedArrayBufferObject::class_cons
   uint64_t byteLength;
   if (!ToIndex(cx, args.get(0), &byteLength)) {
     return false;
   }
 
   // Step 3 (Inlined 24.2.1.1 AllocateSharedArrayBuffer).
   // 24.2.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
   RootedObject proto(cx);
-  if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SharedArrayBuffer,
+                                          &proto)) {
     return false;
   }
 
   // 24.2.1.1, step 3 (Inlined 6.2.7.2 CreateSharedByteDataBlock, step 2).
   // Refuse to allocate too large buffers, currently limited to ~2 GiB.
   if (byteLength > INT32_MAX) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_SHARED_ARRAY_BAD_LENGTH);
--- a/js/src/vm/TypedArrayObject-inl.h
+++ b/js/src/vm/TypedArrayObject-inl.h
@@ -116,48 +116,57 @@ inline To ConvertNumber(From src) {
   return To(src);
 }
 
 template <typename NativeType>
 struct TypeIDOfType;
 template <>
 struct TypeIDOfType<int8_t> {
   static const Scalar::Type id = Scalar::Int8;
+  static const JSProtoKey protoKey = JSProto_Int8Array;
 };
 template <>
 struct TypeIDOfType<uint8_t> {
   static const Scalar::Type id = Scalar::Uint8;
+  static const JSProtoKey protoKey = JSProto_Uint8Array;
 };
 template <>
 struct TypeIDOfType<int16_t> {
   static const Scalar::Type id = Scalar::Int16;
+  static const JSProtoKey protoKey = JSProto_Int16Array;
 };
 template <>
 struct TypeIDOfType<uint16_t> {
   static const Scalar::Type id = Scalar::Uint16;
+  static const JSProtoKey protoKey = JSProto_Uint16Array;
 };
 template <>
 struct TypeIDOfType<int32_t> {
   static const Scalar::Type id = Scalar::Int32;
+  static const JSProtoKey protoKey = JSProto_Int32Array;
 };
 template <>
 struct TypeIDOfType<uint32_t> {
   static const Scalar::Type id = Scalar::Uint32;
+  static const JSProtoKey protoKey = JSProto_Uint32Array;
 };
 template <>
 struct TypeIDOfType<float> {
   static const Scalar::Type id = Scalar::Float32;
+  static const JSProtoKey protoKey = JSProto_Float32Array;
 };
 template <>
 struct TypeIDOfType<double> {
   static const Scalar::Type id = Scalar::Float64;
+  static const JSProtoKey protoKey = JSProto_Float64Array;
 };
 template <>
 struct TypeIDOfType<uint8_clamped> {
   static const Scalar::Type id = Scalar::Uint8Clamped;
+  static const JSProtoKey protoKey = JSProto_Uint8ClampedArray;
 };
 
 class SharedOps {
  public:
   template <typename T>
   static T load(SharedMem<T*> addr) {
     return js::jit::AtomicOperations::loadSafeWhenRacy(addr);
   }
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -280,16 +280,20 @@ enum class CreateSingleton { No, Yes };
 template <typename NativeType>
 class TypedArrayObjectTemplate : public TypedArrayObject {
   friend class TypedArrayObject;
 
  public:
   static constexpr Scalar::Type ArrayTypeID() {
     return TypeIDOfType<NativeType>::id;
   }
+  static constexpr JSProtoKey protoKey() {
+    return TypeIDOfType<NativeType>::protoKey;
+  }
+
   static constexpr bool ArrayTypeIsUnsigned() {
     return TypeIsUnsigned<NativeType>();
   }
   static constexpr bool ArrayTypeIsFloatingPoint() {
     return TypeIsFloatingPoint<NativeType>();
   }
 
   static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType);
@@ -591,29 +595,29 @@ class TypedArrayObjectTemplate : public 
       uint64_t len;
       if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) {
         return nullptr;
       }
 
       // 22.2.4.1, step 3 and 22.2.4.2, step 5.
       // 22.2.4.2.1 AllocateTypedArray, step 1.
       RootedObject proto(cx);
-      if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+      if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
         return nullptr;
       }
 
       return fromLength(cx, len, proto);
     }
 
     RootedObject dataObj(cx, &args[0].toObject());
 
     // 22.2.4.{3,4,5}, step 4.
     // 22.2.4.2.1 AllocateTypedArray, step 1.
     RootedObject proto(cx);
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
       return nullptr;
     }
 
     // 22.2.4.3 TypedArray ( typedArray )
     // 22.2.4.4 TypedArray ( object )
     if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) {
       return fromArray(cx, dataObj, proto);
     }
@@ -952,30 +956,19 @@ template <typename T>
       GlobalObject::getOrCreateArrayBufferConstructor(cx, cx->global());
   if (!arrayBufferCtor) {
     return false;
   }
 
   // As an optimization, skip the "prototype" lookup for %ArrayBuffer%.
   if (ctor != arrayBufferCtor) {
     // 9.1.13 OrdinaryCreateFromConstructor, steps 1-2.
-    if (!GetPrototypeFromConstructor(cx, ctor, &proto)) {
+    if (!GetPrototypeFromConstructor(cx, ctor, JSProto_ArrayBuffer, &proto)) {
       return false;
     }
-
-    JSObject* arrayBufferProto =
-        GlobalObject::getOrCreateArrayBufferPrototype(cx, cx->global());
-    if (!arrayBufferProto) {
-      return false;
-    }
-
-    // Reset |proto| if it's the default %ArrayBufferPrototype%.
-    if (proto == arrayBufferProto) {
-      proto = nullptr;
-    }
   }
 
   // 24.1.1.1 steps 1 (remaining part), 2-6.
   if (!maybeCreateArrayBuffer(cx, count, proto, buffer)) {
     return false;
   }
 
   return true;
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2552,17 +2552,16 @@ static void WrapSeparatorTransform(nsDis
                                    nsDisplayItem** aSeparator) {
   if (aNonParticipants->IsEmpty()) {
     return;
   }
 
   nsDisplayTransform* item = MakeDisplayItem<nsDisplayTransform>(
       aBuilder, aFrame, aNonParticipants, aBuilder->GetVisibleRect(),
       Matrix4x4(), aIndex);
-  item->SetNoExtendContext();
 
   if (*aSeparator == nullptr) {
     *aSeparator = item;
   }
 
   aParticipants->AppendToTop(item);
 }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7179,17 +7179,16 @@ nsDisplayTransform::nsDisplayTransform(
     ComputeTransformFunction aTransformGetter, uint32_t aIndex)
     : nsDisplayHitTestInfoItem(aBuilder, aFrame),
       mStoredList(aBuilder, aFrame, aList),
       mTransformGetter(aTransformGetter),
       mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
       mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
       mChildrenBuildingRect(aChildrenBuildingRect),
       mIndex(aIndex),
-      mNoExtendContext(false),
       mIsTransformSeparator(false),
       mTransformPreserves3DInited(false),
       mAllowAsyncAnimation(false) {
   MOZ_COUNT_CTOR(nsDisplayTransform);
   MOZ_ASSERT(aFrame, "Must have a frame!");
   Init(aBuilder);
 }
 
@@ -7243,17 +7242,16 @@ nsDisplayTransform::nsDisplayTransform(n
                                        bool aAllowAsyncAnimation)
     : nsDisplayHitTestInfoItem(aBuilder, aFrame),
       mStoredList(aBuilder, aFrame, aList),
       mTransformGetter(nullptr),
       mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
       mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
       mChildrenBuildingRect(aChildrenBuildingRect),
       mIndex(aIndex),
-      mNoExtendContext(false),
       mIsTransformSeparator(false),
       mTransformPreserves3DInited(false),
       mAllowAsyncAnimation(aAllowAsyncAnimation) {
   MOZ_COUNT_CTOR(nsDisplayTransform);
   MOZ_ASSERT(aFrame, "Must have a frame!");
   SetReferenceFrameToAncestor(aBuilder);
   Init(aBuilder);
   UpdateBoundsFor3D(aBuilder);
@@ -7267,17 +7265,16 @@ nsDisplayTransform::nsDisplayTransform(n
     : nsDisplayHitTestInfoItem(aBuilder, aFrame),
       mStoredList(aBuilder, aFrame, aList),
       mTransform(Some(aTransform)),
       mTransformGetter(nullptr),
       mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot),
       mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot),
       mChildrenBuildingRect(aChildrenBuildingRect),
       mIndex(aIndex),
-      mNoExtendContext(false),
       mIsTransformSeparator(true),
       mTransformPreserves3DInited(false),
       mAllowAsyncAnimation(false) {
   MOZ_COUNT_CTOR(nsDisplayTransform);
   MOZ_ASSERT(aFrame, "Must have a frame!");
   Init(aBuilder);
   UpdateBoundsFor3D(aBuilder);
 }
@@ -7868,23 +7865,25 @@ bool nsDisplayTransform::CreateWebRender
     deferredTransformItem = Some(this);
   }
 
   // If it looks like we're animated, we should rasterize in local space
   // (disabling subpixel-aa and global pixel snapping)
   bool animated =
       ActiveLayerTracker::IsStyleMaybeAnimated(Frame(), eCSSProperty_transform);
 
+  bool preserve3D = mFrame->Extend3DContext() && !mIsTransformSeparator;
+
   StackingContextHelper sc(
       aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, filters,
       LayoutDeviceRect(position, LayoutDeviceSize()), &newTransformMatrix,
       animationsId ? &prop : nullptr, nullptr, transformForSC, nullptr,
       gfx::CompositionOp::OP_OVER, !BackfaceIsHidden(),
-      mFrame->Extend3DContext() && !mNoExtendContext, deferredTransformItem,
-      wr::WrStackingContextClip::None(), animated);
+      preserve3D, deferredTransformItem, wr::WrStackingContextClip::None(),
+      animated);
 
   return mStoredList.CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
                                              aDisplayListBuilder);
 }
 
 bool nsDisplayTransform::UpdateScrollData(
     mozilla::layers::WebRenderScrollData* aData,
     mozilla::layers::WebRenderLayerScrollData* aLayerData) {
@@ -7927,18 +7926,18 @@ already_AddRefed<Layer> nsDisplayTransfo
           aBuilder, aManager, mFrame, this, mStoredList.GetChildren(),
           aContainerParameters, &newTransformMatrix, flags);
 
   if (!container) {
     return nullptr;
   }
 
   // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all
-  // flags, so we never need to explicitely unset this flag.
-  if (mFrame->Extend3DContext() && !mNoExtendContext) {
+  // flags, so we never need to explicitly unset this flag.
+  if (mFrame->Extend3DContext() && !mIsTransformSeparator) {
     container->SetContentFlags(container->GetContentFlags() |
                                Layer::CONTENT_EXTEND_3D_CONTEXT);
   } else {
     container->SetContentFlags(container->GetContentFlags() &
                                ~Layer::CONTENT_EXTEND_3D_CONTEXT);
   }
 
   if (mAllowAsyncAnimation) {
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -6705,20 +6705,16 @@ class nsDisplayTransform : public nsDisp
       nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect);
   bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
 
   bool MayBeAnimated(nsDisplayListBuilder* aBuilder,
                      bool aEnforceMinimumSize = true) const;
 
   void WriteDebugInfo(std::stringstream& aStream) override;
 
-  // Force the layer created for this item not to extend 3D context.
-  // See nsIFrame::BuildDisplayListForStackingContext()
-  void SetNoExtendContext() { mNoExtendContext = true; }
-
   void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
     MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() ||
                IsTransformSeparator());
     // Updating is not going through to child 3D context.
     ComputeBounds(aBuilder);
   }
 
   /**
@@ -6789,25 +6785,23 @@ class nsDisplayTransform : public nsDisp
   ComputeTransformFunction mTransformGetter;
   RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForChildren;
   RefPtr<AnimatedGeometryRoot> mAnimatedGeometryRootForScrollMetadata;
   nsRect mChildrenBuildingRect;
   uint32_t mIndex;
   mutable nsRect mBounds;
   // True for mBounds is valid.
   mutable bool mHasBounds;
-  // Be forced not to extend 3D context.  Since we don't create a
-  // transform item, a container layer, for every frames in a
-  // preserves3d context, the transform items of a child preserves3d
-  // context may extend the parent context not intented if the root of
-  // the child preserves3d context doesn't create a transform item.
-  // With this flags, we force the item not extending 3D context.
-  bool mNoExtendContext;
   // This item is a separator between 3D rendering contexts, and
   // mTransform have been presetted by the constructor.
+  // This also forces us not to extend the 3D context.  Since we don't create a
+  // transform item, a container layer, for every frame in a preserves3d
+  // context, the transform items of a child preserves3d context may extend the
+  // parent context unintendedly if the root of the child preserves3d context
+  // doesn't create a transform item.
   bool mIsTransformSeparator;
   // True if mTransformPreserves3D have been initialized.
   bool mTransformPreserves3DInited;
   // True if async animation of the transform is allowed.
   bool mAllowAsyncAnimation;
   // True if this nsDisplayTransform should get flattened
   bool mShouldFlatten;
 };
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -58,24 +58,26 @@ import android.content.SharedPreferences
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.os.StrictMode;
 import android.provider.ContactsContract;
 import android.support.annotation.NonNull;
 import android.support.annotation.WorkerThread;
 import android.support.design.widget.Snackbar;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
@@ -131,16 +133,17 @@ public abstract class GeckoApp extends G
     public static final String ACTION_LAUNCH_SETTINGS      = "org.mozilla.gecko.SETTINGS";
     public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
     public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
     public static final String ACTION_SWITCH_TAB           = "org.mozilla.gecko.SWITCH_TAB";
     public static final String ACTION_SHUTDOWN             = "org.mozilla.gecko.SHUTDOWN";
 
     public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
 
+    private static final String GECKOVIEW_STATE_BUNDLE     = "geckoViewState";
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
     public static final String PREFS_FLASH_USAGE           = "playFlashCount";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
     public static final String PREFS_CRASHED_COUNT         = "crashedCount";
     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
@@ -653,16 +656,26 @@ public abstract class GeckoApp extends G
 
             // Make sure we are not bloating the Bundle which can result in TransactionTooLargeException
             if (getBundleSizeInBytes(outState) > MAX_BUNDLE_SIZE_BYTES) {
                 outState.remove(SAVED_STATE_PRIVATE_SESSION);
             }
         }
 
         outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
+
+        // There are situations where the saved instance state will be cleared (e.g. user swipes
+        // away activity in the task switcher), but Gecko will actually remain alive (because
+        // another activity or service of ours is still running in this process). The saved state is
+        // the only way we can reconnect to our previous GeckoView session and all the user's open
+        // tabs, so we need to keep a copy of the state ourselves.
+        SparseArray<Parcelable> geckoViewState = new SparseArray<>();
+        mLayerView.saveHierarchyState(geckoViewState);
+        outState.putSparseParcelableArray(GECKOVIEW_STATE_BUNDLE, geckoViewState);
+        getGeckoApplication().setSavedState(geckoViewState);
     }
 
     public void addTab(int flags) { }
 
     public void addTab() { }
 
     public void addPrivateTab() { }
 
@@ -691,17 +704,17 @@ public abstract class GeckoApp extends G
             // know that mHealthRecorder will exist. That doesn't stop us being
             // paranoid.
             // This method is cheap, so don't spawn a new runnable.
             final HealthRecorder rec = mHealthRecorder;
             if (rec != null) {
               rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
             }
 
-            ((GeckoApplication) getApplicationContext()).onDelayedStartup();
+            getGeckoApplication().onDelayedStartup();
 
             // Reset the crash loop counter if we remain alive for at least half a minute.
             ThreadUtils.postDelayedToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
                     getSharedPreferences().edit().putInt(PREFS_CRASHED_COUNT, 0).apply();
                 }
             }, STARTUP_PHASE_DURATION_MS);
@@ -971,16 +984,21 @@ public abstract class GeckoApp extends G
     /**
      * Called when the activity is first created.
      *
      * Here we initialize all of our profile settings, Firefox Health Report,
      * and other one-shot constructions.
      **/
     @Override
     public void onCreate(Bundle savedInstanceState) {
+        // Within onCreate(), we might inject a different savedInstanceState for testing, but this
+        // won't influence what the OS will do with regards to calling onSaveInstanceState().
+        // Therefore, record whether we were passed some data or not.
+        final boolean receivedSavedInstanceState = (savedInstanceState != null);
+
         // Enable Android Strict Mode for developers' local builds (the "default" channel).
         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
             enableStrictMode();
         }
 
         final boolean corruptAPK = GeckoThread.isState(GeckoThread.State.CORRUPT_APK);
         boolean supported = HardwareUtils.isSupportedSystem();
         if (supported) {
@@ -1101,29 +1119,35 @@ public abstract class GeckoApp extends G
 
         setContentView(getLayout());
 
         // Set up Gecko layout.
         mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
         mLayerView = (GeckoView) findViewById(R.id.layer_view);
+        // Disable automatic state staving - we require some special handling that we need to do
+        // ourselves.
+        mLayerView.setSaveFromParentEnabled(false);
 
         final GeckoSession session = new GeckoSession(
                 new GeckoSessionSettings.Builder()
                         .chromeUri("chrome://browser/content/browser.xul")
                         .build());
         session.setContentDelegate(this);
 
         // If the view already has a session, we need to ensure it is closed.
         if (mLayerView.getSession() != null) {
             mLayerView.getSession().close();
         }
         mLayerView.setSession(session, GeckoApplication.getRuntime());
         mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+        if (mIsRestoringActivity && !receivedSavedInstanceState) {
+            restoreGeckoViewState(getGeckoApplication().getSavedState());
+        }
 
         getAppEventDispatcher().registerGeckoThreadListener(this,
             "Locale:Set",
             "PrivateBrowsing:Data",
             null);
 
         getAppEventDispatcher().registerUiThreadListener(this,
             "Contact:Add",
@@ -1309,16 +1333,36 @@ public abstract class GeckoApp extends G
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         mWasFirstTabShownAfterActivityUnhidden = false; // onStart indicates we were hidden.
     }
 
     @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        final SparseArray<Parcelable> stateToRestore =
+                savedInstanceState.getSparseParcelableArray(GECKOVIEW_STATE_BUNDLE);
+        restoreGeckoViewState(stateToRestore);
+    }
+
+    /**
+     * Restores the given state into our GeckoView and clears any state we might have kept locally
+     * within our process, as it has now become obsolete.
+     */
+    private void restoreGeckoViewState(final SparseArray<Parcelable> state) {
+        if (state != null) {
+            mLayerView.restoreHierarchyState(state);
+        }
+        getGeckoApplication().setSavedState(null);
+    }
+
+    @Override
     protected void onStop() {
         super.onStop();
         // Overriding here is not necessary, but we do this so we don't
         // forget to add the abort if we override this method later.
         if (mIsAbortingAppLaunch) {
             return;
         }
     }
@@ -2529,16 +2573,20 @@ public abstract class GeckoApp extends G
 
     protected void recordStartupActionTelemetry(final String passedURL, final String action) {
     }
 
     public GeckoView getGeckoView() {
         return mLayerView;
     }
 
+    protected GeckoApplication getGeckoApplication() {
+        return (GeckoApplication) getApplicationContext();
+    }
+
     @Override
     public boolean setRequestedOrientationForCurrentActivity(int requestedActivityInfoOrientation) {
         // We want to support the Screen Orientation API, and it always makes sense to lock the
         // orientation of a browser Activity, so we support locking.
         if (getRequestedOrientation() == requestedActivityInfoOrientation) {
             return false;
         }
         setRequestedOrientation(requestedActivityInfoOrientation);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -11,26 +11,28 @@ import android.app.Service;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
 import android.provider.MediaStore;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.support.multidex.MultiDex;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.squareup.leakcanary.LeakCanary;
 import com.squareup.leakcanary.RefWatcher;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.LocalBrowserDB;
@@ -76,16 +78,20 @@ public class GeckoApplication extends Ap
     private static final String MEDIA_DECODING_PROCESS_CRASH = "MEDIA_DECODING_PROCESS_CRASH";
 
     private boolean mInBackground;
     private boolean mPausedGecko;
     private boolean mIsInitialResume;
 
     private LightweightTheme mLightweightTheme;
 
+    // GeckoApp *must* keep its GeckoView state around for as long as our app process (and
+    // therefore Gecko) keeps running, even if Android clears the normal savedInstanceState.
+    private SparseArray<Parcelable> mSavedState;
+
     private RefWatcher mRefWatcher;
 
     private final EventListener mListener = new EventListener();
 
     private static String sSessionUUID = null;
 
     public GeckoApplication() {
         super();
@@ -636,16 +642,24 @@ public class GeckoApplication extends Ap
     public LightweightTheme getLightweightTheme() {
         return mLightweightTheme;
     }
 
     public void prepareLightweightTheme() {
         mLightweightTheme = new LightweightTheme(this);
     }
 
+    /* package */ void setSavedState(SparseArray<Parcelable> savedState) {
+        mSavedState = savedState;
+    }
+
+    /* package */ SparseArray<Parcelable> getSavedState() {
+        return mSavedState;
+    }
+
     public static void createShortcut() {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         if (selectedTab != null) {
             createShortcut(selectedTab.getTitle(), selectedTab.getURL());
         }
     }
 
     // Creates a homescreen shortcut for a web page.
--- a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java
@@ -196,16 +196,18 @@ public class UpdateServiceHelper {
             .replace("%BUILD_TARGET%", "Android_" + AppConstants.MOZ_APP_ABI + pkgSpecial)
             .replace("%LOCALE%", locale)
             .replace("%CHANNEL%", AppConstants.MOZ_UPDATE_CHANNEL)
             .replace("%OS_VERSION%", Build.VERSION.RELEASE)
             .replace("%DISTRIBUTION%", "default")
             .replace("%DISTRIBUTION_VERSION%", "default")
             .replace("%MOZ_VERSION%", AppConstants.MOZILLA_VERSION);
 
+        Log.i(LOGTAG, "AUS Url is: " + url);
+
         try {
             return new URI(url);
         } catch (java.net.URISyntaxException e) {
             Log.e(LOGTAG, "Failed to create update url: ", e);
             return null;
         }
     }
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -88,16 +88,17 @@ class AccessibilityTest : BaseSessionTes
                         AccessibilityNodeInfo::class.java.getMethod(
                                 "getChildId", Int::class.java).invoke(this, index) as Long
                     else
                         (AccessibilityNodeInfo::class.java.getMethod("getChildNodeIds")
                                 .invoke(this) as SparseLongArray).get(index))
 
     private interface EventDelegate {
         fun onAccessibilityFocused(event: AccessibilityEvent) { }
+        fun onAccessibilityFocusCleared(event: AccessibilityEvent) { }
         fun onClicked(event: AccessibilityEvent) { }
         fun onFocused(event: AccessibilityEvent) { }
         fun onSelected(event: AccessibilityEvent) { }
         fun onScrolled(event: AccessibilityEvent) { }
         fun onTextSelectionChanged(event: AccessibilityEvent) { }
         fun onTextChanged(event: AccessibilityEvent) { }
         fun onTextTraversal(event: AccessibilityEvent) { }
         fun onWinContentChanged(event: AccessibilityEvent) { }
@@ -120,16 +121,17 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.addExternalDelegateUntilTestEnd(
             EventDelegate::class,
         { newDelegate -> (view.parent as View).setAccessibilityDelegate(object : View.AccessibilityDelegate() {
             override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean {
                 when (event.eventType) {
                     AccessibilityEvent.TYPE_VIEW_FOCUSED -> newDelegate.onFocused(event)
                     AccessibilityEvent.TYPE_VIEW_CLICKED -> newDelegate.onClicked(event)
                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
+                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED -> newDelegate.onAccessibilityFocusCleared(event)
                     AccessibilityEvent.TYPE_VIEW_SELECTED -> newDelegate.onSelected(event)
                     AccessibilityEvent.TYPE_VIEW_SCROLLED -> newDelegate.onScrolled(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> newDelegate.onTextSelectionChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
                     AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
                     AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
                     else -> {}
@@ -187,34 +189,48 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Label accessibility focused", node.className.toString(),
                         equalTo("android.view.View"))
                 assertThat("Text node should not be focusable", node.isFocusable, equalTo(false))
+                assertThat("Text node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
                 assertThat("Text node should not be clickable", node.isClickable, equalTo(false))
             }
         })
 
         provider.performAction(nodeId,
             AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Editbox accessibility focused", node.className.toString(),
                         equalTo("android.widget.EditText"))
                 assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
+                assertThat("Entry node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
                 assertThat("Entry node should be clickable", node.isClickable, equalTo(true))
             }
         })
+
+        provider.performAction(nodeId,
+                AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onAccessibilityFocusCleared(event: AccessibilityEvent) {
+                assertThat("Accessibility focused node is now cleared", getSourceId(event), equalTo(nodeId))
+                val node = createNodeInfo(nodeId)
+                assertThat("Entry node should node be a11y focused", node.isAccessibilityFocused, equalTo(false))
+            }
+        })
     }
 
     @Test fun testTextEntryNode() {
         sessionRule.session.loadString("<input aria-label='Name' value='Tobias'>", "text/html")
         waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -113,20 +113,16 @@ public class SessionAccessibility {
 
     /* package */ final class NodeProvider extends AccessibilityNodeProvider {
         @Override
         public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
             AccessibilityNodeInfo node = null;
             if (mAttached) {
                 node = mSession.getSettings().getFullAccessibilityTree() ?
                         getNodeFromGecko(virtualDescendantId) : getNodeFromCache(virtualDescendantId);
-                if (node != null) {
-                    node.setAccessibilityFocused(mAccessibilityFocusedNode == virtualDescendantId);
-                    node.setFocused(mFocusedNode == virtualDescendantId);
-                }
             }
 
             if (node == null) {
                 Log.w(LOGTAG, "Failed to retrieve accessible node virtualDescendantId=" +
                         virtualDescendantId + " mAttached=" + mAttached);
                 node = AccessibilityNodeInfo.obtain(mView, View.NO_ID);
                 if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
                     // When running junit tests we don't have a display
@@ -138,16 +134,19 @@ public class SessionAccessibility {
             return node;
         }
 
         @Override
         public boolean performAction(final int virtualViewId, int action, Bundle arguments) {
             final GeckoBundle data;
 
             switch (action) {
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, virtualViewId, CLASSNAME_VIEW, null);
+                return true;
             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
                     if (virtualViewId == View.NO_ID) {
                         sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, View.NO_ID, CLASSNAME_WEBVIEW, null);
                     } else {
                         final GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
                         final int flags = nodeInfo != null ? nodeInfo.getInt("flags") : 0;
                         if ((flags & FLAG_FOCUSED) != 0) {
                             mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityCursorToFocused", null);
@@ -291,17 +290,18 @@ public class SessionAccessibility {
             }
         }
 
         private void populateNodeFromBundle(final AccessibilityNodeInfo node, final GeckoBundle nodeInfo, final boolean fromCache) {
             if (mView == null || nodeInfo == null) {
                 return;
             }
 
-            boolean isRoot = nodeInfo.getInt("id") == View.NO_ID;
+            final int id = nodeInfo.getInt("id");
+            boolean isRoot = id == View.NO_ID;
             if (isRoot) {
                 if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
                     // When running junit tests we don't have a display
                     mView.onInitializeAccessibilityNodeInfo(node);
                 }
                 node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
                 node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
             } else {
@@ -316,18 +316,16 @@ public class SessionAccessibility {
 
             if (nodeInfo.containsKey("text")) {
                 node.setText(nodeInfo.getString("text"));
             }
 
             // Add actions
             node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
             node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-            node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
-            node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
             node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
             node.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
             node.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             if ((flags & FLAG_CLICKABLE) != 0) {
                 node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
@@ -343,16 +341,24 @@ public class SessionAccessibility {
             node.setLongClickable((flags & FLAG_LONG_CLICKABLE) != 0);
             node.setPassword((flags & FLAG_PASSWORD) != 0);
             node.setScrollable((flags & FLAG_SCROLLABLE) != 0);
             node.setSelected((flags & FLAG_SELECTED) != 0);
             node.setVisibleToUser((flags & FLAG_VISIBLE_TO_USER) != 0);
             // Other boolean properties to consider later:
             // setHeading, setImportantForAccessibility, setScreenReaderFocusable, setShowingHintText, setDismissable
 
+            if (mAccessibilityFocusedNode == id) {
+                node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                node.setAccessibilityFocused(true);
+            } else {
+                node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+            node.setFocused(mFocusedNode == id);
+
             // Bounds
             int[] b = nodeInfo.getIntArray("bounds");
             if (b != null) {
                 final Rect screenBounds = new Rect(b[0], b[1], b[2], b[3]);
                 node.setBoundsInScreen(screenBounds);
 
                 final Matrix matrix = new Matrix();
                 mSession.getClientToScreenMatrix(matrix);
@@ -709,16 +715,21 @@ public class SessionAccessibility {
                 if (cachedBundle != null && eventData != null && eventData.containsKey("selected")) {
                     if (eventData.getInt("selected") != 0) {
                         cachedBundle.putInt("flags", cachedBundle.getInt("flags") | FLAG_SELECTED);
                     } else {
                         cachedBundle.putInt("flags", cachedBundle.getInt("flags") & ~FLAG_SELECTED);
                     }
                 }
                 break;
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+                if (mAccessibilityFocusedNode == sourceId) {
+                    mAccessibilityFocusedNode = 0;
+                }
+                break;
             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
                 mAccessibilityFocusedNode = sourceId;
                 break;
             case AccessibilityEvent.TYPE_VIEW_FOCUSED:
                 mFocusedNode = sourceId;
                 break;
         }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5329,17 +5329,21 @@ pref("dom.vr.enabled", false);
 // being worn.  This can result in WebVR content taking over the headset
 // when the user is using it outside the browser or inadvertent start of
 // presentation due to the high sensitivity of the proximity sensor in some
 // headsets, so it is off by default.
 pref("dom.vr.autoactivate.enabled", false);
 // The threshold value of trigger inputs for VR controllers
 pref("dom.vr.controller_trigger_threshold", "0.1");
 // Enable external XR API integrations
+#if defined(XP_WIN) && defined(NIGHTLY_BUILD)
+pref("dom.vr.external.enabled", true);
+#else
 pref("dom.vr.external.enabled", false);
+#endif
 // Minimum number of milliseconds the browser will wait before attempting
 // to re-start the VR service after an enumeration returned no devices.
 pref("dom.vr.external.notdetected.timeout", 60000);
 // Minimum number of milliseconds the browser will wait before attempting
 // to re-start the VR service after a VR API (eg, OpenVR or Oculus)
 // requests that we shutdown and unload its libraries.
 // To ensure that we don't interfere with VR runtime software auto-updates,
 // we will not attempt to re-load the service until this timeout has elapsed.
@@ -5418,18 +5422,18 @@ pref("dom.vr.inactive.timeout", 5000);
 // Oculus Rift on SDK 0.8 or greater.
 pref("dom.vr.poseprediction.enabled", true);
 // Starting VR presentation is only allowed within a user gesture or event such
 // as VRDisplayActivate triggered by the system.  dom.vr.require-gesture allows
 // this requirement to be disabled for special cases such as during automated
 // tests or in a headless kiosk system.
 pref("dom.vr.require-gesture", true);
 // Enable a separate process for VR module.
-#if defined(XP_WIN)
-pref("dom.vr.process.enabled", false);
+#if defined(XP_WIN) && defined(NIGHTLY_BUILD)
+pref("dom.vr.process.enabled", true);
 #endif
 // Puppet device, used for simulating VR hardware within tests and dev tools
 pref("dom.vr.puppet.enabled", false);
 // Allow displaying the result of vr submitframe (0: disable, 1: store the
 // result as a base64 image, 2: show it on the screen).
 pref("dom.vr.puppet.submitframe", 0);
 // path to OSVR DLLs
 pref("gfx.vr.osvr.utilLibPath", "");
--- a/netwerk/test/mochitests/test_accept_header.html
+++ b/netwerk/test/mochitests/test_accept_header.html
@@ -2,34 +2,34 @@
 <html>
 <head>
   <title>Accept header</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <script>
+SimpleTest.requestCompleteLog();
 
 // All the requests are sent to test_accept_header.sjs which will return
 // different content based on the queryString. When the queryString is 'get',
 // test_accept_header.sjs returns a JSON object with the latest request and its
 // accept header value.
 
 function test_last_request_and_continue(query, expected) {
   fetch("test_accept_header.sjs?get").then(r => r.json()).then(json => {
     is(json.type, query, "Expected: " + query);
     is(json.accept, expected, "Accept header: " + expected);
     next();
   });
 }
 
 function test_iframe() {
   let observer = new PerformanceObserver(function(list, obj) {
-    obj.disconnect();
-
+    list.getEntries().forEach(entry => info(entry.name));
     list.getEntries().forEach(entry => {
       if (entry.name.endsWith("test_accept_header.sjs?iframe")) {
         obj.disconnect();
         test_last_request_and_continue("iframe", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
       }
     });
   });
 
@@ -46,18 +46,17 @@ function test_image() {
   i.onload = function() {
     // Fetch spec says we should have: "image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"
     test_last_request_and_continue("image", "image/webp,*/*");
   }
 }
 
 function test_style() {
   let observer = new PerformanceObserver(function(list, obj) {
-    obj.disconnect();
-
+    list.getEntries().forEach(entry => info(entry.name));
     list.getEntries().forEach(entry => {
       if (entry.name.endsWith("test_accept_header.sjs?style")) {
         obj.disconnect();
         test_last_request_and_continue("style", "text/css,*/*;q=0.1");
       }
     });
   });
 
--- a/netwerk/test/mochitests/test_accept_header.sjs
+++ b/netwerk/test/mochitests/test_accept_header.sjs
@@ -1,13 +1,14 @@
 function handleRequest(request, response) {
   response.setStatusLine(request.httpVersion, "200", "OK");
+  dump(`test_accept_header ${request.path}?${request.queryString}\n`);
 
   if (request.queryString == "worker") {
-    response.setHeader("Content-Type", "application/json", false);
+    response.setHeader("Content-Type", "text/javascript", false);
     response.write("postMessage(42)");
 
     setState("data", JSON.stringify({type: "worker", accept: request.getHeader("Accept") }));
     return;
   }
 
   if (request.queryString == "image") {
     // A 1x1 PNG image.
@@ -34,15 +35,15 @@ function handleRequest(request, response
     response.setHeader("Content-Type", "text/html", false);
     response.write("<h1>Hello world!</h1>");
 
     setState("data", JSON.stringify({type: "iframe", accept: request.getHeader("Accept") }));
     return;
   }
 
   if (request.queryString == "get") {
-    response.setHeader("Content-Type", "text/javascript", false);
+    response.setHeader("Content-Type", "application/json", false);
     response.write(getState("data"));
 
     setState("data", "");
     return;
   }
 }
--- a/python/mach/docs/usage.rst
+++ b/python/mach/docs/usage.rst
@@ -39,30 +39,50 @@ following are valid:
     $ ./mach help try fuzzy
     $ ./mach try -h
     $ ./mach try fuzzy --help
 
 
 Auto Completion
 ---------------
 
-A `bash completion`_ script is bundled with mach. To enable it with ``bash``,
-add the following to your ``~/.bashrc``, ``~/.bash_profile`` or equivalent:
+A `bash completion`_ script is bundled with mach, it can be used with either ``bash`` or ``zsh``.
+
+Bash
+~~~~
+
+Add the following to your ``~/.bashrc``, ``~/.bash_profile`` or equivalent:
 
 .. code-block:: shell
 
     source <srcdir>/python/mach/bash-completion.sh
 
-This script can also be used with ``zsh``. Add this to your ``~/.zshrc`` or
-equivalent:
+.. tip::
+
+    Windows users using the default shell bundled with mozilla-build should source the completion
+    script from ``~/.bash_profile`` (it may need to be created first).
+
+Zsh
+~~~
+
+Add this to your ``~/.zshrc`` or equivalent:
 
 .. code-block:: shell
 
-    autoload bashcompinit
-    bashcompinit
+    autoload -U bashcompinit && bashcompinit
+    source <srcdir>/python/mach/bash-completion.sh
+
+The ``compinit`` function also needs to be loaded, but if using a framework (like ``oh-my-zsh``),
+this will often be done for you. So if you see ``command not found: compdef``, you'll need to modify
+the above instructions to:
+
+.. code-block:: shell
+
+    autoload -U compinit && compinit
+    autoload -U bashcompinit && bashcompinit
     source <srcdir>/python/mach/bash-completion.sh
 
 Don't forget to substitute ``<srcdir>`` with the path to your checkout.
 
 
 User Settings
 -------------
 
--- a/python/mach/mach/commands/commandinfo.py
+++ b/python/mach/mach/commands/commandinfo.py
@@ -67,24 +67,39 @@ class BuiltinCommands(object):
         is_help = 'help' in args
         command = None
         for i, arg in enumerate(args):
             if arg in all_commands:
                 command = arg
                 args = args[i+1:]
                 break
 
+        # If no command is typed yet, just offer the commands.
         if not command:
             print("\n".join(all_commands))
             return
 
         handler = self.context.commands.command_handlers[command]
+        # If a subcommand was typed, update the handler.
         for arg in args:
             if arg in handler.subcommand_handlers:
                 handler = handler.subcommand_handlers[arg]
                 break
 
-        parser = handler.parser
         targets = sorted(handler.subcommand_handlers.keys())
-        if not is_help:
-            targets.append('help')
-            targets.extend(chain(*[action.option_strings for action in parser._actions]))
+        if is_help:
+            print("\n".join(targets))
+            return
+
+        targets.append('help')
+
+        # The 'option_strings' are of the form [('-f', '--foo'), ('-b', '--bar'), ...].
+        option_strings = [item[0] for item in handler.arguments]
+        # Filter out positional arguments (we don't want to complete their metavar).
+        option_strings = [opt for opt in option_strings if opt[0].startswith('-')]
+        targets.extend(chain(*option_strings))
+
+        # If the command uses its own ArgumentParser, extract options from there as well.
+        if handler.parser:
+            targets.extend(chain(*[action.option_strings
+                                   for action in handler.parser._actions]))
+
         print("\n".join(targets))
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -294,16 +294,40 @@ raptor-tp6-8-chrome:
         by-test-platform:
             linux64.*: 3
             default: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6-8
             - --app=chrome
 
+raptor-tp6-9-firefox:
+    description: "Raptor tp6-9 on Firefox"
+    try-name: raptor-tp6-9-firefox
+    treeherder-symbol: Rap(tp6-9)
+    run-on-projects: ['try', 'mozilla-central']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6-9
+
+raptor-tp6-9-chrome:
+    description: "Raptor tp6-9 on Chrome"
+    try-name: raptor-tp6-9-chrome
+    treeherder-symbol: Rap-C(tp6-9)
+    run-on-projects: ['try', 'mozilla-central']
+    tier:
+        by-test-platform:
+            linux64.*: 3
+            default: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6-9
+            - --app=chrome
+
 raptor-tp6-10-firefox:
     description: "Raptor tp6-10 on Firefox"
     try-name: raptor-tp6-10-firefox
     treeherder-symbol: Rap(tp6-10)
     run-on-projects: ['try', 'mozilla-central']
     tier: 2
     mozharness:
         extra-options:
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -83,16 +83,17 @@ raptor-firefox:
     - raptor-tp6-1-firefox
     - raptor-tp6-2-firefox
     - raptor-tp6-3-firefox
     - raptor-tp6-4-firefox
     - raptor-tp6-5-firefox
     - raptor-tp6-6-firefox
     - raptor-tp6-7-firefox
     - raptor-tp6-8-firefox
+    - raptor-tp6-9-firefox
     - raptor-tp6-10-firefox
     - raptor-speedometer-firefox
     - raptor-stylebench-firefox
     - raptor-motionmark-htmlsuite-firefox
     - raptor-motionmark-animometer-firefox
     - raptor-webaudio-firefox
     - raptor-sunspider-firefox
     - raptor-wasm-godot-firefox
@@ -116,16 +117,17 @@ raptor-chrome:
     - raptor-tp6-1-chrome
     - raptor-tp6-2-chrome
     - raptor-tp6-3-chrome
     - raptor-tp6-4-chrome
     - raptor-tp6-5-chrome
     - raptor-tp6-6-chrome
     - raptor-tp6-7-chrome
     - raptor-tp6-8-chrome
+    - raptor-tp6-9-firefox
     - raptor-tp6-10-chrome
     - raptor-speedometer-chrome
     - raptor-stylebench-chrome
     - raptor-motionmark-htmlsuite-chrome
     - raptor-motionmark-animometer-chrome
     - raptor-webaudio-chrome
     - raptor-sunspider-chrome
     - raptor-wasm-godot-chrome
--- a/testing/mozbase/mozleak/mozleak/leaklog.py
+++ b/testing/mozbase/mozleak/mozleak/leaklog.py
@@ -159,17 +159,17 @@ def process_leak_log(leak_log_file, leak
     log.info("leakcheck | Processing log file %s%s" %
              (leakLogFile, (" for scope %s" % scope) if scope is not None else ""))
 
     leakThresholds = leak_thresholds or {}
     ignoreMissingLeaks = ignore_missing_leaks or []
 
     # This list is based on kGeckoProcessTypeString. ipdlunittest processes likely
     # are not going to produce leak logs we will ever see.
-    knownProcessTypes = ["default", "plugin", "tab", "gmplugin", "gpu", "rdd"]
+    knownProcessTypes = ["default", "plugin", "tab", "gmplugin", "gpu", "rdd", "vr"]
 
     for processType in knownProcessTypes:
         log.info("TEST-INFO | leakcheck | %s process: leak threshold set at %d bytes"
                  % (processType, leakThresholds.get(processType, 0)))
 
     for processType in leakThresholds:
         if processType not in knownProcessTypes:
             log.error("TEST-UNEXPECTED-FAIL | leakcheck | "
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-pinterest.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 11627712,
+    "visibility": "public",
+    "digest": "a0dd4ccb99bac02a38b3c67baa08296c3e894837aba1c1b0750c592db6ff962ab0c61f557ffb45c2ea77e95345954a0786bf764448d090cc6cf76679d1a8a616",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-tp6-pinterest.zip",
+    "unpack": true
+  }
+]
\ No newline at end of file
--- a/testing/raptor/raptor/raptor.ini
+++ b/testing/raptor/raptor/raptor.ini
@@ -2,16 +2,17 @@
 [include:tests/raptor-tp6-1.ini]
 [include:tests/raptor-tp6-2.ini]
 [include:tests/raptor-tp6-3.ini]
 [include:tests/raptor-tp6-4.ini]
 [include:tests/raptor-tp6-5.ini]
 [include:tests/raptor-tp6-6.ini]
 [include:tests/raptor-tp6-7.ini]
 [include:tests/raptor-tp6-8.ini]
+[include:tests/raptor-tp6-9.ini]
 [include:tests/raptor-tp6-10.ini]
 
 # raptor benchmark tests
 [include:tests/raptor-assorted-dom.ini]
 [include:tests/raptor-motionmark-animometer.ini]
 [include:tests/raptor-motionmark-htmlsuite.ini]
 [include:tests/raptor-speedometer.ini]
 [include:tests/raptor-stylebench.ini]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/tests/raptor-tp6-9.ini
@@ -0,0 +1,34 @@
+# 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/.
+
+# raptor tp6-9
+
+[DEFAULT]
+type =  pageload
+playback = mitmproxy
+playback_binary_manifest = mitmproxy-rel-bin-{platform}.manifest
+python3_win_manifest = python3{x64}.manifest
+page_cycles = 25
+unit = ms
+lower_is_better = true
+alert_threshold = 2.0
+# TTI/TTFI can take a while on some pages, and requires at least 5 seconds
+# beyond typical pageload time
+page_timeout = 30000
+gecko_profile_interval = 1
+gecko_profile_entries = 2000000
+
+[raptor-tp6-pinterest-firefox]
+apps = firefox
+test_url = https://pinterest.com/
+playback_pageset_manifest = mitmproxy-recordings-raptor-pinterest.manifest
+playback_recordings = pinterest.mp
+measure = fnbpaint, dcf, ttfi, loadtime
+
+[raptor-tp6-pinterest-chrome]
+apps = chrome
+test_url = https://pinterest.com/
+playback_pageset_manifest = mitmproxy-recordings-raptor-pinterest.manifest
+playback_recordings = pinterest.mp
+measure = fcp, loadtime
\ No newline at end of file
--- a/testing/raptor/webext/raptor/manifest.json
+++ b/testing/raptor/webext/raptor/manifest.json
@@ -20,16 +20,17 @@
                   "*://*.ebay.com/*",
                   "*://*.facebook.com/*",
                   "*://*.google.com/*",
                   "*://*.imdb.com/*",
                   "*://*.imgur.com/*",
                   "*://*.instagram.com/*",
                   "*://*.microsoft.com/*",
                   "*://*.paypal.com/*",
+                  "*://*.pinterest.com/*",
                   "*://*.reddit.com/*",
                   "*://*.twitter.com/*",
                   "*://*.vice.com/*",
                   "*://*.wikia.com/*",
                   "*://*.wikipedia.org/*",
                   "*://*.yahoo.com/*",
                   "*://*.youtube.com/*",
                   "*://*.yandex.ru/*"
--- a/testing/web-platform/meta/css/css-fonts/font-display/__dir__.ini
+++ b/testing/web-platform/meta/css/css-fonts/font-display/__dir__.ini
@@ -1,1 +1,2 @@
+lsan-allowed: [Alloc, AllocateProtoAndIfaceCache, EntrySlotOrCreate, Realloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::alloc, alloc_system::platform::_$LT$impl$u20$core..alloc..GlobalAlloc$u20$for$u20$alloc_system..System$GT$::realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceMainThread::CreateNavigationTimingEntry, mozilla::net::nsStandardURL::TemplatedMutator]
 leak-threshold: [tab:358400]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/mediacapture-record/MediaRecorder-pause-resume.html.ini
@@ -0,0 +1,4 @@
+[MediaRecorder-pause-resume.html]
+  [MediaRecorder handles pause() and resume() calls appropriately in state and events]
+    expected: FAIL
+
--- a/testing/web-platform/meta/mediacapture-record/MediaRecorder-stop.html.ini
+++ b/testing/web-platform/meta/mediacapture-record/MediaRecorder-stop.html.ini
@@ -1,8 +1,8 @@
 [MediaRecorder-stop.html]
   expected: TIMEOUT
   [MediaRecorder will stop recording and fire a stop event when stop() is called]
-    expected: FAIL
+    expected: NOTRUN
 
   [MediaRecorder will stop recording and fire a stop event when all tracks are ended]
     expected: TIMEOUT
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-pause-resume.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<html>
+<head>
+    <title>MediaRecorder Pause and Resume</title>
+    <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="200" height="200">
+</canvas>
+<script>
+    function createVideoStream() {
+        let canvas = document.getElementById("canvas");
+        canvas.getContext('2d');
+        return canvas.captureStream();
+    }
+
+    function recordEvents(target, events) {
+        let arr = [];
+        for (let ev of events) {
+            target.addEventListener(ev, _ => arr.push(ev));
+        }
+        return arr;
+    }
+
+    promise_test(async () => {
+        let video = createVideoStream();
+        let recorder = new MediaRecorder(video);
+        let events = recordEvents(recorder,
+            ["start", "stop", "dataavailable", "pause", "resume", "error"]);
+
+        recorder.start();
+        assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully");
+
+        recorder.pause();
+        assert_equals(recorder.state, "paused", "MediaRecorder should be paused immediately following pause()");
+
+        // A second call to pause should be idempotent
+        recorder.pause();
+        assert_equals(recorder.state, "paused", "MediaRecorder should be paused immediately following pause()");
+
+        let event = await new Promise(r => recorder.onpause = r);
+        assert_equals(event.type, "pause", "the event type should be pause");
+        assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
+
+        recorder.resume();
+        assert_equals(recorder.state, "recording", "MediaRecorder state should be recording immediately following resume() call");
+
+        // A second call to resume should be idempotent
+        recorder.resume();
+        assert_equals(recorder.state, "recording", "MediaRecorder state should be recording immediately following resume() call");
+
+        event = await new Promise(r => recorder.onresume = r);
+        assert_equals(event.type, "resume", "the event type should be resume");
+        assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
+
+        recorder.stop();
+        await new Promise(r => recorder.onstop = r);
+
+        assert_array_equals(events, ["start", "pause", "resume", "dataavailable", "stop"],
+            "Should have gotten expected events");
+    }, "MediaRecorder handles pause() and resume() calls appropriately in state and events");
+</script>
+</body>
+</html>
--- a/testing/web-platform/tests/mediacapture-record/MediaRecorder-stop.html
+++ b/testing/web-platform/tests/mediacapture-record/MediaRecorder-stop.html
@@ -11,40 +11,73 @@
 </canvas>
 <script>
     function createVideoStream() {
         let canvas = document.getElementById("canvas");
         canvas.getContext('2d');
         return canvas.captureStream();
     }
 
-    async_test(t => {
+    function recordEvents(target, events) {
+        let arr = [];
+        for (let ev of events) {
+            target.addEventListener(ev, _ => arr.push(ev));
+        }
+        return arr;
+    }
+
+    promise_test(async t => {
         let video = createVideoStream();
         let recorder = new MediaRecorder(video);
-        recorder.onstop = t.step_func(errorEvent => {
-            assert_equals(errorEvent.type, 'stop', 'the error type should be stop');
-            assert_true(errorEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
-            assert_equals(recorder.state, "inactive", "MediaRecorder has been stopped when all tracks are ended");
-            t.done();
-        });
+        let events = recordEvents(recorder,
+            ["start", "stop", "dataavailable", "pause", "resume", "error"]);
         assert_equals(video.getVideoTracks().length, 1, "video mediastream starts with one track");
         recorder.start();
         assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully");
+
         video.getVideoTracks()[0].stop();
+        assert_equals(recorder.state, "recording", "MediaRecorder state should be recording immediately following last track ending");
+        let event = await new Promise(r => recorder.onstop = r);
+
+        assert_equals(event.type, "stop", "the event type should be stop");
+        assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
+        assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event");
+
+        assert_array_equals(events, ["start", "dataavailable", "stop"],
+            "Should have gotten expected events");
+
+        recorder.stop();
+        await Promise.race([
+            new Promise((_, reject) => recorder.onstop =
+                _ => reject(new Error("stop() is idempotent"))),
+            new Promise(r => t.step_timeout(r, 0))
+        ]);
     }, "MediaRecorder will stop recording and fire a stop event when all tracks are ended");
 
-    async_test(t => {
+    promise_test(async t => {
         let video = createVideoStream();
         let recorder = new MediaRecorder(video);
-        recorder.onstop = t.step_func(errorEvent => {
-            assert_equals(errorEvent.type, 'stop', 'the error type should be stop');
-            assert_true(errorEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
-            assert_equals(recorder.state, "inactive", "MediaRecorder has been stopped when stop() is called");
-            t.done();
-        });
+        let events = recordEvents(recorder,
+            ["start", "stop", "dataavailable", "pause", "resume", "error"]);
         recorder.start();
         assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully");
+
         recorder.stop();
-        assert_equals(recorder.state, "recording", "State should remain the same until stop event is fired");
+        assert_equals(recorder.state, "inactive", "MediaRecorder state should be inactive immediately following stop() call");
+
+        let event = await new Promise (r => recorder.onstop = r);
+        assert_equals(event.type, "stop", "the event type should be stop");
+        assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
+        assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event");
+
+        assert_array_equals(events, ["start", "dataavailable", "stop"],
+            "Should have gotten expected events");
+
+        recorder.stop();
+        await Promise.race([
+            new Promise((_, reject) => recorder.onstop =
+                _ => reject(new Error("stop() is idempotent"))),
+            new Promise(r => t.step_timeout(r, 0))
+        ]);
     }, "MediaRecorder will stop recording and fire a stop event when stop() is called");
 </script>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/toolkit/content/moz.build
+++ b/toolkit/content/moz.build
@@ -43,16 +43,19 @@ with Files('aboutwebrtc/*'):
     BUG_COMPONENT = ('Core', 'WebRTC')
 
 with Files('gmp-sources/*'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 with Files('tests/browser/browser_audio*'):
     BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
 
+with Files('tests/browser/browser_autoplay*'):
+    BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
+
 with Files('tests/browser/*block*'):
     BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
 
 with Files('tests/browser/*silent*'):
     BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
 
 with Files('tests/browser/*1170531*'):
     BUG_COMPONENT = ('Firefox', 'Menus')
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -372,57 +372,62 @@ class MozBrowser extends MozElementMixin
   }
 
   get dateTimePicker() {
     return document.getElementById(this.getAttribute("datetimepicker"));
   }
 
   set docShellIsActive(val) {
     if (this.isRemoteBrowser) {
-      this.frameLoader.tabParent.docShellIsActive = val;
-      return val;
+      let { frameLoader } = this;
+      if (frameLoader && frameLoader.tabParent) {
+        frameLoader.tabParent.docShellIsActive = val;
+      }
+    } else if (this.docShell) {
+      this.docShell.isActive = val;
     }
-    if (this.docShell)
-      return this.docShell.isActive = val;
-    return false;
   }
 
   get docShellIsActive() {
     if (this.isRemoteBrowser) {
-      return this.frameLoader.tabParent.docShellIsActive;
+      let { frameLoader } = this;
+      if (frameLoader && frameLoader.tabParent) {
+        return frameLoader.tabParent.docShellIsActive;
+      }
+      return false;
     }
     return this.docShell && this.docShell.isActive;
   }
 
   set renderLayers(val) {
     if (this.isRemoteBrowser) {
       let { frameLoader } = this;
       if (frameLoader && frameLoader.tabParent) {
-        return frameLoader.tabParent.renderLayers = val;
+        frameLoader.tabParent.renderLayers = val;
       }
-      return false;
+    } else {
+      this.docShellIsActive = val;
     }
-    return this.docShellIsActive = val;
   }
 
   get renderLayers() {
     if (this.isRemoteBrowser) {
       let { frameLoader } = this;
       if (frameLoader && frameLoader.tabParent) {
         return frameLoader.tabParent.renderLayers;
       }
       return false;
     }
     return this.docShellIsActive;
   }
 
   get hasLayers() {
     if (this.isRemoteBrowser) {
       let { frameLoader } = this;
-      if (frameLoader.tabParent) {
+      if (frameLoader && frameLoader.tabParent) {
         return frameLoader.tabParent.hasLayers;
       }
       return false;
     }
 
     return this.docShellIsActive;
   }
 
@@ -722,17 +727,16 @@ class MozBrowser extends MozElementMixin
     }
 
     return this.docShell.securityUI;
   }
 
   set userTypedValue(val) {
     this.urlbarChangeTracker.userTyped();
     this._userTypedValue = val;
-    return val;
   }
 
   get userTypedValue() {
     return this._userTypedValue;
   }
 
   get dontPromptAndDontUnload() {
     return 1;
--- a/tools/lint/codespell.yml
+++ b/tools/lint/codespell.yml
@@ -19,16 +19,17 @@ codespell:
         - layout/tools/layout-debug/ui/locale/en-US/
         - mobile/android/base/locales/en-US/
         - mobile/android/branding/
         - mobile/android/docs/
         - mobile/android/locales/en-US/
         - mobile/locales/en-US/
         - netwerk/locales/en-US/
         - python/docs/
+        - python/mach/docs/
         - python/mozlint/
         - python/safety/
         - services/sync/locales/en-US/
         - taskcluster/docs/
         - testing/mozbase/docs/
         - toolkit/components/extensions/docs/
         - toolkit/components/telemetry/docs/
         - toolkit/crashreporter/docs/
--- a/widget/tests/test_bug596600.xul
+++ b/widget/tests/test_bug596600.xul
@@ -18,47 +18,44 @@
 </body>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const NSMouseMoved = 5;
 
-var gLeftWindow, gRightWindow, gIFrame;
+var gLeftWindow, gRightWindow, gBrowserElement;
 var gExpectedEvents = [];
 
 function moveMouseTo(x, y, andThen) {
   var utils = gLeftWindow.windowUtils;
   utils.sendNativeMouseEvent(x, y, NSMouseMoved, 0, gLeftWindow.documentElement);
   SimpleTest.executeSoon(andThen);
 }
 
 function openWindows() {
   gLeftWindow = open('empty_window.xul', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200');
   SimpleTest.waitForFocus(function () {
     gRightWindow = open('empty_window.xul', '', 'chrome,screenX=300,screenY=50,width=200,height=200');
-    SimpleTest.waitForFocus(attachIFrameToRightWindow, gRightWindow);
+    SimpleTest.waitForFocus(attachBrowserToLeftWindow, gRightWindow);
   }, gLeftWindow);
 }
 
-function attachIFrameToRightWindow() {
-  gIFrame = gLeftWindow.document.createElementNS(XUL_NS, "iframe");
-  gIFrame.setAttribute("type", "content");
-  gIFrame.setAttribute("clickthrough", "never");
-  gIFrame.setAttribute("src", "file_bug596600.html");
-  gIFrame.style.width = "100px";
-  gIFrame.style.height = "100px";
-  gIFrame.style.margin = "50px";
-  gLeftWindow.document.documentElement.appendChild(gIFrame);
-  gIFrame.addEventListener("load", function (e) {
-    gIFrame.removeEventListener("load", arguments.callee, true);
+function attachBrowserToLeftWindow() {
+  gBrowserElement = gLeftWindow.document.createXULElement("browser");
+  gBrowserElement.setAttribute("type", "content");
+  gBrowserElement.setAttribute("src", "file_bug596600.html");
+  gBrowserElement.style.width = "100px";
+  gBrowserElement.style.height = "100px";
+  gBrowserElement.style.margin = "50px";
+  gLeftWindow.document.documentElement.appendChild(gBrowserElement);
+  gBrowserElement.addEventListener("load", function (e) {
     test1();
-  }, true);
-
+  }, { capture: true, once: true });
 }
 
 function test1() {
   // gRightWindow is active, gLeftWindow is inactive.
   moveMouseTo(0, 0, function () {
     var expectMouseOver = false, expectMouseOut = false;
     function mouseOverListener(e) {
       ok(expectMouseOver, "Got expected mouseover at " + e.screenX + ", " + e.screenY);
@@ -71,46 +68,46 @@ function test1() {
     gLeftWindow.addEventListener("mouseover", mouseOverListener, false);
     gLeftWindow.addEventListener("mouseout", mouseOutListener, false);
 
     // Move into the left window
     expectMouseOver = true;
     moveMouseTo(80, 80, function () {
       ok(!expectMouseOver, "Should have got mouseover event");
 
-      // Move over the iframe, which has clickthrough="never".
+      // Move over the browser
       expectMouseOut = true;
       moveMouseTo(150, 150, function () {
         ok (!expectMouseOut, "Should have got mouseout event");
         gLeftWindow.removeEventListener("mouseover", mouseOverListener, false);
         gLeftWindow.removeEventListener("mouseout", mouseOutListener, false);
         test2();
       });
     });
   });
 }
 
 function test2() {
-  // Make the iframe cover the whole window.
-  gIFrame.style.margin = "0";
-  gIFrame.style.width = gIFrame.style.height = "200px";
+  // Make the browser cover the whole window.
+  gBrowserElement.style.margin = "0";
+  gBrowserElement.style.width = gBrowserElement.style.height = "200px";
 
-  // Add a box to the iframe at the left edge.
-  var doc = gIFrame.contentDocument;
+  // Add a box to the browser at the left edge.
+  var doc = gBrowserElement.contentDocument;
   var box = doc.createElement("div");
   box.setAttribute("id", "box");
   box.style.position = "absolute";
   box.style.left = "0";
   box.style.top = "50px";
   box.style.width = "100px";
   box.style.height = "100px";
   box.style.backgroundColor = "green";
   doc.body.appendChild(box);
 
-  ok(!box.matches(":hover"), "Box shouldn't be hovered (since the mouse isn't over it and since it's in a non-clickthrough iframe in a background window)");
+  ok(!box.matches(":hover"), "Box shouldn't be hovered (since the mouse isn't over it and since it's in a non-clickthrough browser in a background window)");
 
   // A function to waitForFocus and then wait for synthetic mouse
   // events to happen.  Note that those happen off the refresh driver,
   // and happen after animation frame requests.
   function changeFocusAndAwaitSyntheticMouse(callback, winToFocus,
                                              elementToWatchForMouseEventOn) {
      function mouseWatcher() {
        elementToWatchForMouseEventOn.removeEventListener("mouseover",
@@ -129,32 +126,32 @@ function test2() {
                                                     false);
      // Just pass a dummy function to waitForFocus; the mouseout/over listener
      // will actually handle things for us.
      SimpleTest.waitForFocus(function() {}, winToFocus);
   }
 
   // Move the mouse over the box.
   moveMouseTo(100, 150, function () {
-    ok(!box.matches(":hover"), "Box shouldn't be hovered (since it's in a non-clickthrough iframe in a background window)");
+    ok(!box.matches(":hover"), "Box shouldn't be hovered (since it's in a non-clickthrough browser in a background window)");
     // Activate the left window.
     changeFocusAndAwaitSyntheticMouse(function () {
-      ok(gIFrame.matches(":hover"), "iframe should be hovered");
+      ok(gBrowserElement.matches(":hover"), "browser should be hovered");
       ok(box.matches(":hover"), "Box should be hovered");
       // De-activate the window (by activating the right window).
       changeFocusAndAwaitSyntheticMouse(function () {
-        ok(!gIFrame.matches(":hover"), "iframe shouldn't be hovered");
+        ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
         ok(!box.matches(":hover"), "Box shouldn't be hovered");
         // Re-activate it.
         changeFocusAndAwaitSyntheticMouse(function () {
-          ok(gIFrame.matches(":hover"), "iframe should be hovered");
+          ok(gBrowserElement.matches(":hover"), "browser should be hovered");
           ok(box.matches(":hover"), "Box should be hovered");
-          // Unhover box and iframe by moving the mouse outside the window.
+          // Unhover box and browser by moving the mouse outside the window.
           moveMouseTo(0, 150, function () {
-            ok(!gIFrame.matches(":hover"), "iframe shouldn't be hovered");
+            ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
             ok(!box.matches(":hover"), "box shouldn't be hovered");
             finalize();
           });
         }, gLeftWindow, box);
       }, gRightWindow, box);
     }, gLeftWindow, box);
   });
 }