Merge mozilla-inbound to mozilla-central. a=merge
authorAndreea Pavel <apavel@mozilla.com>
Fri, 21 Sep 2018 00:54:47 +0300
changeset 437537 5347c7e4811a1a4d80bcee4119cead9192fdff69
parent 437476 4d3cd0ab72776e5940619b2589cf14e3b29c4f5c (current diff)
parent 437536 bbc165233051fb57331cf92b2c223faa333b02bf (diff)
child 437554 b7b64e0bf4b12ec4e2f5af957e19b7a2905061ef
child 437577 89bae1ff9c78cd6132988913167ef085380d6d39
push id34684
push userapavel@mozilla.com
push dateThu, 20 Sep 2018 21:55:17 +0000
treeherdermozilla-central@5347c7e4811a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
5347c7e4811a / 64.0a1 / 20180920220102 / files
nightly linux64
5347c7e4811a / 64.0a1 / 20180920220102 / files
nightly mac
5347c7e4811a / 64.0a1 / 20180920220102 / files
nightly win32
5347c7e4811a / 64.0a1 / 20180920220102 / files
nightly win64
5347c7e4811a / 64.0a1 / 20180920220102 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
browser/app/profile/firefox.js
dom/base/nsDocument.cpp
dom/base/nsGlobalWindowOuter.cpp
dom/indexedDB/test/test_third_party.html
dom/serviceworkers/test/test_third_party_iframes.html
dom/tests/mochitest/bugs/test_bug1171215.html
dom/tests/mochitest/general/storagePermissionsUtils.js
dom/tests/mochitest/localstorage/test_localStorageCookieSettings.html
dom/workers/RuntimeService.cpp
dom/workers/test/test_sharedWorker_thirdparty.html
extensions/cookie/test/file_testcommon.js
extensions/cookie/test/unit/test_cookies_thirdparty.js
modules/libpref/init/StaticPrefList.h
modules/libpref/moz.build
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/background-fetch/dangling-markup.https.window.js.ini
testing/web-platform/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-002.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-003.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-004.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-003.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-004.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-002.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-002.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-003.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-004.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-003.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-004.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/goal-parameter.htm.ini
testing/web-platform/meta/navigation-timing/nav2_test_document_open.html.ini
testing/web-platform/meta/referrer-policy/css-integration/external-import-stylesheet.html.ini
testing/web-platform/meta/referrer-policy/css-integration/external-stylesheet.html.ini
testing/web-platform/meta/referrer-policy/css-integration/internal-import-stylesheet.html.ini
testing/web-platform/meta/referrer-policy/css-integration/processing-instruction.html.ini
testing/web-platform/tests/background-fetch/dangling-markup.https.window.js
testing/web-platform/tests/html/semantics/scripting-1/the-script-element/goal-parameter.htm
toolkit/components/antitracking/AntiTrackingCommon.cpp
toolkit/components/antitracking/AntiTrackingCommon.h
toolkit/components/antitracking/test/browser/browser.ini
toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
toolkit/components/antitracking/test/browser/browser_imageCache1.js
toolkit/components/antitracking/test/browser/browser_imageCache2.js
toolkit/components/antitracking/test/browser/browser_imageCache3.js
toolkit/components/antitracking/test/browser/browser_imageCache4.js
toolkit/components/antitracking/test/browser/browser_imageCache5.js
toolkit/components/antitracking/test/browser/browser_imageCache6.js
toolkit/components/antitracking/test/browser/browser_imageCache7.js
toolkit/components/antitracking/test/browser/browser_onBeforeRequestNotificationForTrackingResources.js
toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
toolkit/components/antitracking/test/browser/browser_script.js
toolkit/components/antitracking/test/browser/browser_subResources.js
toolkit/components/antitracking/test/browser/head.js
toolkit/components/antitracking/test/browser/imageCacheWorker.js
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -362,17 +362,20 @@ private:
   template<class T>
   class nsCOMPtrHashKey : public PLDHashEntryHdr
   {
   public:
     typedef T* KeyType;
     typedef const T* KeyTypePointer;
 
     explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
-    explicit nsCOMPtrHashKey(const nsPtrHashKey<T> &aToCopy) : mKey(aToCopy.mKey) {}
+    nsCOMPtrHashKey(nsCOMPtrHashKey<T>&& aOther)
+      : PLDHashEntryHdr(std::move(aOther))
+      , mKey(std::move(aOther.mKey))
+    {}
     ~nsCOMPtrHashKey() { }
 
     KeyType GetKey() const { return mKey; }
     bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
 
     static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
     static PLDHashNumber HashKey(KeyTypePointer aKey)
       { return NS_PTR_TO_INT32(aKey) >> 2; }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1494,31 +1494,23 @@ pref("toolkit.telemetry.hybridContent.en
 pref("browser.ping-centre.telemetry", true);
 pref("browser.ping-centre.log", false);
 pref("browser.ping-centre.staging.endpoint", "https://onyx_tiles.stage.mozaws.net/v3/links/ping-centre");
 pref("browser.ping-centre.production.endpoint", "https://tiles.services.mozilla.com/v3/links/ping-centre");
 
 // Enable GMP support in the addon manager.
 pref("media.gmp-provider.enabled", true);
 
-// Enable the new Content Blocking UI only on Nightly.
-#ifdef NIGHTLY_BUILD
-pref("browser.contentblocking.ui.enabled", true);
-#else
-pref("browser.contentblocking.ui.enabled", false);
-#endif
-
 pref("browser.contentblocking.global-toggle.enabled", true);
 
 // Define a set of default features for the Content Blocking UI
 pref("browser.contentblocking.fastblock.ui.enabled", true);
 pref("browser.contentblocking.fastblock.control-center.ui.enabled", true);
 pref("browser.contentblocking.trackingprotection.ui.enabled", true);
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
-pref("browser.contentblocking.rejecttrackers.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.ui.recommended", true);
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended", true);
 pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", true);
 
 // Enable the Report Breakage UI on Nightly and Beta but not on Release yet.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.contentblocking.reportBreakage.enabled", true);
--- a/devtools/client/inspector/flexbox/flexbox.js
+++ b/devtools/client/inspector/flexbox/flexbox.js
@@ -25,16 +25,17 @@ class FlexboxInspector {
     this.document = window.document;
     this.inspector = inspector;
     this.selection = inspector.selection;
     this.store = inspector.store;
     this.walker = inspector.walker;
 
     this.onHighlighterShown = this.onHighlighterShown.bind(this);
     this.onHighlighterHidden = this.onHighlighterHidden.bind(this);
+    this.onNavigate = this.onNavigate.bind(this);
     this.onReflow = throttle(this.onReflow, 500, this);
     this.onSetFlexboxOverlayColor = this.onSetFlexboxOverlayColor.bind(this);
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
     this.onToggleFlexboxHighlighter = this.onToggleFlexboxHighlighter.bind(this);
     this.onToggleFlexItemShown = this.onToggleFlexItemShown.bind(this);
     this.onUpdatePanel = this.onUpdatePanel.bind(this);
 
     this.init();
@@ -83,21 +84,23 @@ class FlexboxInspector {
   destroy() {
     if (this._highlighters) {
       this.highlighters.off("flexbox-highlighter-hidden", this.onHighlighterHidden);
       this.highlighters.off("flexbox-highlighter-shown", this.onHighlighterShown);
     }
 
     this.selection.off("new-node-front", this.onUpdatePanel);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
-    this.inspector.off("new-root", this.onUpdatePanel);
+    this.inspector.off("new-root", this.onNavigate);
 
     this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
 
+    this._customHostColors = null;
     this._highlighters = null;
+    this._overlayColor = null;
     this.document = null;
     this.hasGetCurrentFlexbox = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.selection = null;
     this.store = null;
     this.walker = null;
   }
@@ -106,22 +109,50 @@ class FlexboxInspector {
     return {
       onSetFlexboxOverlayColor: this.onSetFlexboxOverlayColor,
       onToggleFlexboxHighlighter: this.onToggleFlexboxHighlighter,
       onToggleFlexItemShown: this.onToggleFlexItemShown,
     };
   }
 
   /**
+   * Returns the custom overlay color for the current host or the default flexbox color.
+   *
+   * @return {String} overlay color.
+   */
+  async getOverlayColor() {
+    if (this._overlayColor) {
+      return this._overlayColor;
+    }
+
+    // Cache the overlay color for the current host to avoid repeatably parsing the host
+    // and fetching the custom color from async storage.
+    const customColors = await this.getCustomHostColors();
+    const currentUrl = this.inspector.target.url;
+    // Get the hostname, if there is no hostname, fall back on protocol
+    // ex: `data:` uri, and `about:` pages
+    const hostName = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
+    this._overlayColor = customColors[hostName] ? customColors[hostName] : FLEXBOX_COLOR;
+    return this._overlayColor;
+  }
+
+  /**
    * Returns an object containing the custom flexbox colors for different hosts.
    *
    * @return {Object} that maps a host name to a custom flexbox color for a given host.
    */
-  async getCustomFlexboxColors() {
-    return await asyncStorage.getItem("flexboxInspectorHostColors") || {};
+  async getCustomHostColors() {
+    if (this._customHostColors) {
+      return this._customHostColors;
+    }
+
+    // Cache the custom host colors to avoid refetching from async storage.
+    this._customHostColors = await asyncStorage.getItem("flexboxInspectorHostColors")
+      || {};
+    return this._customHostColors;
   }
 
   /**
    * Returns true if the layout panel is visible, and false otherwise.
    */
   isPanelVisible() {
     return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
            this.inspector.toolbox.currentToolId === "inspector" &&
@@ -170,16 +201,25 @@ class FlexboxInspector {
     const { flexbox } = this.store.getState();
 
     if (flexbox.nodeFront === nodeFront && flexbox.highlighted !== highlighted) {
       this.store.dispatch(updateFlexboxHighlighted(highlighted));
     }
   }
 
   /**
+   * Handler for the "new-root" event fired by the inspector. Clears the cached overlay
+   * color for the flexbox highlighter and updates the panel.
+   */
+  onNavigate() {
+    this._overlayColor = null;
+    this.onUpdatePanel();
+  }
+
+  /**
    * Handler for the "reflow" event fired by the inspector's reflow tracker. On reflows,
    * updates the flexbox panel because the shape of the flexbox on the page may have
    * changed.
    */
   async onReflow() {
     if (!this.isPanelVisible() ||
         !this.store ||
         !this.selection.nodeFront ||
@@ -226,37 +266,37 @@ class FlexboxInspector {
 
     if (flexbox.highlighted) {
       this.highlighters.showFlexboxHighlighter(flexbox.nodeFront);
     }
 
     const currentUrl = this.inspector.target.url;
     // Get the hostname, if there is no hostname, fall back on protocol
     // ex: `data:` uri, and `about:` pages
-    const hostname = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
-    const customFlexboxColors = await this.getCustomFlexboxColors();
-
-    customFlexboxColors[hostname] = color;
-    await asyncStorage.setItem("flexboxInspectorHostColors", customFlexboxColors);
+    const hostName = parseURL(currentUrl).hostName || parseURL(currentUrl).protocol;
+    const customColors = await this.getCustomHostColors();
+    customColors[hostName] = color;
+    this._customHostColors = customColors;
+    await asyncStorage.setItem("flexboxInspectorHostColors", customColors);
   }
 
   /**
    * Handler for the inspector sidebar "select" event. Updates the flexbox panel if it
    * is visible.
    */
   onSidebarSelect() {
     if (!this.isPanelVisible()) {
       this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
-      this.inspector.off("new-root", this.onUpdatePanel);
+      this.inspector.off("new-root", this.onNavigate);
       this.selection.off("new-node-front", this.onUpdatePanel);
       return;
     }
 
     this.inspector.reflowTracker.trackReflows(this, this.onReflow);
-    this.inspector.on("new-root", this.onUpdatePanel);
+    this.inspector.on("new-root", this.onNavigate);
     this.selection.on("new-node-front", this.onUpdatePanel);
 
     this.update();
   }
 
   /**
    * Handler for a change in the input checkboxes in the FlexboxItem component.
    * Toggles on/off the flexbox highlighter for the provided flex container element.
@@ -366,22 +406,17 @@ class FlexboxInspector {
           flexItemSizing: flexItemFront.flexItemSizing,
           nodeFront: itemNodeFront,
           properties: flexItemFront.properties,
         });
       }
 
       const highlighted = this._highlighters &&
         containerNodeFront == this.highlighters.flexboxHighlighterShown;
-      const currentUrl = this.inspector.target.url;
-      // Get the hostname, if there is no hostname, fall back on protocol
-      // ex: `data:` uri, and `about:` pages
-      const hostname = parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
-      const customColors = await this.getCustomFlexboxColors();
-      const color = customColors[hostname] ? customColors[hostname] : FLEXBOX_COLOR;
+      const color = await this.getOverlayColor();
 
       this.store.dispatch(updateFlexbox({
         actorID: flexboxFront.actorID,
         color,
         flexItems,
         flexItemShown,
         highlighted,
         nodeFront: containerNodeFront,
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -10,17 +10,17 @@
 flexbox.header=Flexbox
 
 # LOCALIZATION (flexbox.flexContainer): The accordion header for the Flexbox panel
 # when a flex container is selected.
 flexbox.flexContainer=Flex Container
 
 # LOCALIZATION NOTE) (flexbox.flexItemOf): The accordion header for the Flexbox panel
 # when a flex item is selected. %s represents the flex container selector.
-flexbox.flexItemOf=Flex Item of %s
+flexbox.flexItemOf=Flex Item of %S
 
 # LOCALIZATION NOTE (flexbox.noFlexboxeOnThisPage): In the case where there are no CSS
 # flex containers to display.
 flexbox.noFlexboxeOnThisPage=Select a Flex container or item to continue.
 
 # LOCALIZATION NOTE (flexbox.flexContainerProperties): Header for the flex container
 # properties in the Flexbox panel.
 flexbox.flexContainerProperties=Flex Container Properties
--- a/dom/animation/PseudoElementHashEntry.h
+++ b/dom/animation/PseudoElementHashEntry.h
@@ -19,17 +19,17 @@ class PseudoElementHashEntry : public PL
 {
 public:
   typedef NonOwningAnimationTarget KeyType;
   typedef const NonOwningAnimationTarget* KeyTypePointer;
 
   explicit PseudoElementHashEntry(KeyTypePointer aKey)
     : mElement(aKey->mElement)
     , mPseudoType(aKey->mPseudoType) { }
-  explicit PseudoElementHashEntry(const PseudoElementHashEntry& aCopy)=default;
+  PseudoElementHashEntry(PseudoElementHashEntry&& aOther) = default;
 
   ~PseudoElementHashEntry() = default;
 
   KeyType GetKey() const { return { mElement, mPseudoType }; }
   bool KeyEquals(KeyTypePointer aKey) const
   {
     return mElement == aKey->mElement &&
            mPseudoType == aKey->mPseudoType;
--- a/dom/base/ContentFrameMessageManager.cpp
+++ b/dom/base/ContentFrameMessageManager.cpp
@@ -1,25 +1,29 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ContentFrameMessageManager.h"
+#include "js/RootingAPI.h"
 #include "mozilla/dom/ScriptSettings.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 JSObject*
 ContentFrameMessageManager::GetOrCreateWrapper()
 {
-  AutoJSAPI jsapi;
-  jsapi.Init();
+  JS::RootedValue val(RootingCx());
+  {
+    // Scope to run ~AutoJSAPI before working with a raw JSObject*.
+    AutoJSAPI jsapi;
+    jsapi.Init();
 
-  JS::RootedValue val(jsapi.cx());
-  if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
-    return nullptr;
+    if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
+      return nullptr;
+    }
   }
   MOZ_ASSERT(val.isObject());
   return &val.toObject();
 }
--- a/dom/base/ContentProcessMessageManager.cpp
+++ b/dom/base/ContentProcessMessageManager.cpp
@@ -109,23 +109,27 @@ ContentProcessMessageManager::WrapObject
                                          JS::Handle<JSObject*> aGivenProto)
 {
   return ContentProcessMessageManager_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 JSObject*
 ContentProcessMessageManager::GetOrCreateWrapper()
 {
-  AutoJSAPI jsapi;
-  jsapi.Init();
+  JS::RootedValue val(RootingCx());
+  {
+    // Scope to run ~AutoJSAPI before working with a raw JSObject*.
+    AutoJSAPI jsapi;
+    jsapi.Init();
 
-  JS::RootedValue val(jsapi.cx());
-  if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
-    return nullptr;
+    if (!GetOrCreateDOMReflectorNoWrap(jsapi.cx(), this, &val)) {
+      return nullptr;
+    }
   }
+  MOZ_ASSERT(val.isObject());
   return &val.toObject();
 }
 
 void
 ContentProcessMessageManager::LoadScript(const nsAString& aURL)
 {
   Init();
   JS::Rooted<JSObject*> messageManager(mozilla::dom::RootingCx(), GetOrCreateWrapper());
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -747,17 +747,17 @@ CustomElementRegistry::GetDocGroup() con
 {
   return mWindow ? mWindow->GetDocGroup() : nullptr;
 }
 
 int32_t
 CustomElementRegistry::InferNamespace(JSContext* aCx,
                                       JS::Handle<JSObject*> constructor)
 {
-  JSObject* XULConstructor = XULElement_Binding::GetConstructorObject(aCx);
+  JS::Rooted<JSObject*> XULConstructor(aCx, XULElement_Binding::GetConstructorObject(aCx));
 
   JS::Rooted<JSObject*> proto(aCx, constructor);
   while (proto) {
     if (proto == XULConstructor) {
       return kNameSpaceID_XUL;
     }
 
     JS_GetPrototype(aCx, proto, &proto);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -387,17 +387,18 @@ nsIdentifierMapEntry::nsIdentifierMapEnt
 nsIdentifierMapEntry::nsIdentifierMapEntry(const nsIdentifierMapEntry::AtomOrString* aKey)
   : mKey(aKey ? *aKey : nullptr)
 {}
 
 nsIdentifierMapEntry::~nsIdentifierMapEntry()
 {}
 
 nsIdentifierMapEntry::nsIdentifierMapEntry(nsIdentifierMapEntry&& aOther)
-  : mKey(std::move(aOther.mKey))
+  : PLDHashEntryHdr(std::move(aOther))
+  , mKey(std::move(aOther.mKey))
   , mIdContentList(std::move(aOther.mIdContentList))
   , mNameContentList(std::move(aOther.mNameContentList))
   , mChangeCallbacks(std::move(aOther.mChangeCallbacks))
   , mImageElement(std::move(aOther.mImageElement))
 {}
 
 void
 nsIdentifierMapEntry::Traverse(nsCycleCollectionTraversalCallback* aCallback)
@@ -12869,17 +12870,17 @@ nsIDocument::SetDocTreeHadPlayRevoked()
   }
 }
 
 void
 nsIDocument::MaybeAllowStorageForOpener()
 {
   if (StaticPrefs::network_cookie_cookieBehavior() !=
         nsICookieService::BEHAVIOR_REJECT_TRACKER ||
-      !StaticPrefs::browser_contentblocking_enabled()) {
+      !AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions()) {
     return;
   }
 
   // This will probably change for project fission, but currently this document
   // and the opener are on the same process. In the future, we should make this
   // part async.
 
   nsPIDOMWindowInner* inner = GetInnerWindow();
@@ -13601,17 +13602,17 @@ nsIDocument::HasStorageAccess(mozilla::E
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
   if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
     promise->MaybeResolve(true);
     return promise.forget();
   }
 
-  if (StaticPrefs::browser_contentblocking_enabled() &&
+  if (AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions() &&
       StaticPrefs::network_cookie_cookieBehavior() ==
         nsICookieService::BEHAVIOR_REJECT_TRACKER) {
     // If we need to abide by Content Blocking cookie restrictions, ensure to
     // first do all of our storage access checks.  If storage access isn't
     // disabled in our document, given that we're a third-party, we must either
     // not be a tracker, or be whitelisted for some reason (e.g. a storage
     // access permission being granted).  In that case, resolve the promise and
     // say we have obtained storage access.
@@ -13722,17 +13723,17 @@ nsIDocument::RequestStorageAccess(mozill
     // If the document is in PB mode, it doesn't have access to its persistent
     // cookie jar, so reject the promise here.
     promise->MaybeRejectWithUndefined();
     return promise.forget();
   }
 
   bool granted = true;
   bool isTrackingWindow = false;
-  if (StaticPrefs::browser_contentblocking_enabled() &&
+  if (AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions() &&
       StaticPrefs::network_cookie_cookieBehavior() ==
         nsICookieService::BEHAVIOR_REJECT_TRACKER) {
     // Only do something special for third-party tracking content.
     if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
       // Note: If this has returned true, the top-level document is guaranteed
       // to not be on the Content Blocking allow list.
       DebugOnly<bool> isOnAllowList = false;
       MOZ_ASSERT_IF(NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList(
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -2016,17 +2016,17 @@ nsGlobalWindowOuter::SetNewDocument(nsID
   // If we have a recorded interesting Large-Allocation header status, report it
   // to the newly attached document.
   ReportLargeAllocStatus();
   mLargeAllocStatus = LargeAllocStatus::NONE;
 
   mHasStorageAccess = false;
   nsIURI* uri = aDocument->GetDocumentURI();
   if (newInnerWindow) {
-    if (StaticPrefs::browser_contentblocking_enabled() &&
+    if (AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions() &&
         StaticPrefs::network_cookie_cookieBehavior() ==
           nsICookieService::BEHAVIOR_REJECT_TRACKER &&
         nsContentUtils::IsThirdPartyWindowOrChannel(newInnerWindow, nullptr,
                                                     uri) &&
         nsContentUtils::IsTrackingResourceWindow(newInnerWindow)) {
       // Grant storage access by default if the first-party storage access
       // permission has been granted already.
       // Don't notify in this case, since we would be notifying the user needlessly.
--- a/dom/base/nsIdentifierMapEntry.h
+++ b/dom/base/nsIdentifierMapEntry.h
@@ -177,18 +177,19 @@ public:
   };
 
   struct ChangeCallbackEntry : public PLDHashEntryHdr {
     typedef const ChangeCallback KeyType;
     typedef const ChangeCallback* KeyTypePointer;
 
     explicit ChangeCallbackEntry(const ChangeCallback* aKey) :
       mKey(*aKey) { }
-    ChangeCallbackEntry(const ChangeCallbackEntry& toCopy) :
-      mKey(toCopy.mKey) { }
+    ChangeCallbackEntry(ChangeCallbackEntry&& aOther) :
+      PLDHashEntryHdr(std::move(aOther)),
+      mKey(std::move(aOther.mKey)) { }
 
     KeyType GetKey() const { return mKey; }
     bool KeyEquals(KeyTypePointer aKey) const {
       return aKey->mCallback == mKey.mCallback &&
              aKey->mData == mKey.mData &&
              aKey->mForImage == mKey.mForImage;
     }
 
--- a/dom/base/nsNodeInfoManager.h
+++ b/dom/base/nsNodeInfoManager.h
@@ -143,16 +143,17 @@ private:
   bool InternalSVGEnabled();
   bool InternalMathMLEnabled();
 
   class NodeInfoInnerKey :
     public nsPtrHashKey<mozilla::dom::NodeInfo::NodeInfoInner>
   {
   public:
     explicit NodeInfoInnerKey(KeyTypePointer aKey) : nsPtrHashKey(aKey) {}
+    NodeInfoInnerKey(NodeInfoInnerKey&&) = default;
     ~NodeInfoInnerKey() = default;
     bool KeyEquals(KeyTypePointer aKey) const { return *mKey == *aKey; }
     static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->Hash(); }
   };
 
   struct NodeInfoCache : public mozilla::MruCache<
                               mozilla::dom::NodeInfo::NodeInfoInner,
                               mozilla::dom::NodeInfo*,
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -143,16 +143,21 @@ public:
     return CallbackPreserveColor();
   }
 
   nsIGlobalObject* IncumbentGlobalOrNull() const
   {
     return mIncumbentGlobal;
   }
 
+  void Reset()
+  {
+    ClearJSReferences();
+  }
+
   enum ExceptionHandling {
     // Report any exception and don't throw it to the caller code.
     eReportExceptions,
     // Throw an exception to the caller code if the thrown exception is a
     // binding object for a DOMException from the caller's scope, otherwise
     // report it.
     eRethrowContentExceptions,
     // Throw exceptions to the caller code, unless the caller realm is
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -100,16 +100,19 @@ CheckExpandoAndGeneration(JSObject* prox
 static inline void
 CheckDOMProxy(JSObject* proxy)
 {
 #ifdef DEBUG
   MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
   MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy));
   nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
   nsWrapperCache* cache;
+  // QI to nsWrapperCache cannot GC for very non-obvious reasons; see
+  // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548
+  JS::AutoSuppressGCAnalysis nogc;
   CallQueryInterface(native, &cache);
   MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy);
 #endif
 }
 
 // static
 JSObject*
 DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj)
--- a/dom/commandhandler/nsCommandParams.cpp
+++ b/dom/commandhandler/nsCommandParams.cpp
@@ -331,20 +331,20 @@ nsCommandParams::HashMatchEntry(const PL
   return thisEntry->mEntryName.Equals(keyString);
 }
 
 void
 nsCommandParams::HashMoveEntry(PLDHashTable* aTable,
                                const PLDHashEntryHdr* aFrom,
                                PLDHashEntryHdr* aTo)
 {
-  const HashEntry* fromEntry = static_cast<const HashEntry*>(aFrom);
+  auto* fromEntry = const_cast<HashEntry*>(static_cast<const HashEntry*>(aFrom));
   HashEntry* toEntry = static_cast<HashEntry*>(aTo);
 
-  new (toEntry) HashEntry(*fromEntry);
+  new (KnownNotNull, toEntry) HashEntry(std::move(*fromEntry));
 
   fromEntry->~HashEntry();
 }
 
 void
 nsCommandParams::HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
 {
   HashEntry* thisEntry = static_cast<HashEntry*>(aEntry);
--- a/dom/commandhandler/nsCommandParams.h
+++ b/dom/commandhandler/nsCommandParams.h
@@ -81,17 +81,17 @@ protected:
     HashEntry(uint8_t aType, const char* aEntryName)
       : mEntryName(aEntryName)
       , mEntryType(aType)
       , mData()
     {
       Reset(mEntryType);
     }
 
-    HashEntry(const HashEntry& aRHS)
+    explicit HashEntry(const HashEntry& aRHS)
       : mEntryType(aRHS.mEntryType)
     {
       Reset(mEntryType);
       switch (mEntryType) {
         case eBooleanType:
           mData.mBoolean = aRHS.mData.mBoolean;
           break;
         case eLongType:
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3684,25 +3684,20 @@ HTMLMediaElement::MozCaptureStreamUntilE
   }
 
   return stream.forget();
 }
 
 class MediaElementSetForURI : public nsURIHashKey
 {
 public:
-  explicit MediaElementSetForURI(const nsIURI* aKey)
-    : nsURIHashKey(aKey)
-  {
-  }
-  MediaElementSetForURI(const MediaElementSetForURI& toCopy)
-    : nsURIHashKey(toCopy)
-    , mElements(toCopy.mElements)
-  {
-  }
+  explicit MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {}
+  MediaElementSetForURI(MediaElementSetForURI&& aOther)
+    : nsURIHashKey(std::move(aOther))
+    , mElements(std::move(aOther.mElements)) {}
   nsTArray<HTMLMediaElement*> mElements;
 };
 
 typedef nsTHashtable<MediaElementSetForURI> MediaElementURITable;
 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
 // can't change while the element is in the table. The table is keyed by
 // the element's mLoadingSrc. Each entry has a list of all elements with the
 // same mLoadingSrc.
--- a/dom/indexedDB/test/test_third_party.html
+++ b/dom/indexedDB/test/test_third_party.html
@@ -57,17 +57,22 @@
     function setiframe() {
       let iframe = document.getElementById("iframe1");
 
       if (!testRunning) {
         testRunning = true;
         iframe.addEventListener("load", iframeLoaded);
       }
       SpecialPowers.pushPrefEnv({
-        "set": [["network.cookie.cookieBehavior", testData[testIndex].cookieBehavior]]
+        "set": [
+          ["browser.contentblocking.enabled", true],
+          ["browser.contentblocking.ui.enabled", true],
+          ["browser.contentblocking.rejecttrackers.ui.enabled", true],
+          ["network.cookie.cookieBehavior", testData[testIndex].cookieBehavior],
+        ]
       }, () => {
         iframe.src = testData[testIndex].host + iframe1Path;
       });
       // SpecialPowers.setIntPref("network.cookie.cookieBehavior", testData[testIndex].cookieBehavior);
     }
 
     function messageListener(event) {
       // eslint-disable-next-line no-eval
--- a/dom/serviceworkers/ServiceWorkerContainer.cpp
+++ b/dom/serviceworkers/ServiceWorkerContainer.cpp
@@ -82,21 +82,21 @@ ServiceWorkerContainer::IsEnabled(JSCont
   if (!StaticPrefs::dom_serviceWorkers_enabled()) {
     return false;
   }
 
   if (IsInPrivateBrowsing(aCx)) {
     return false;
   }
 
-  if (IsSecureContextOrObjectIsFromSecureContext(aCx, aGlobal)) {
+  if (IsSecureContextOrObjectIsFromSecureContext(aCx, global)) {
     return true;
   }
 
-  const bool isTestingEnabledInWindow = IsServiceWorkersTestingEnabledInWindow(aGlobal);
+  const bool isTestingEnabledInWindow = IsServiceWorkersTestingEnabledInWindow(global);
   const bool isTestingEnabledByPref = StaticPrefs::dom_serviceWorkers_testing_enabled();
   const bool isTestingEnabled = isTestingEnabledByPref || isTestingEnabledInWindow;
 
   return isTestingEnabled;
 }
 
 // static
 already_AddRefed<ServiceWorkerContainer>
--- a/dom/serviceworkers/test/test_third_party_iframes.html
+++ b/dom/serviceworkers/test/test_third_party_iframes.html
@@ -79,16 +79,19 @@ function runTest(aExpectedResponses) {
     responsesIndex++;
   };
 }
 
 // Verify that we can register and intercept a 3rd party iframe with
 // the given cookie policy.
 function testShouldIntercept(behavior, lifetime, done) {
   SpecialPowers.pushPrefEnv({"set": [
+      ["browser.contentblocking.enabled", true],
+      ["browser.contentblocking.ui.enabled", true],
+      ["browser.contentblocking.rejecttrackers.ui.enabled", true],
       ["network.cookie.cookieBehavior", behavior],
       ["network.cookie.lifetimePolicy", lifetime],
   ]}, function() {
     runTest([{
       status: "ok"
     }, {
       status: "registrationdone",
       next: function() {
--- a/dom/smil/nsSMILCompositor.h
+++ b/dom/smil/nsSMILCompositor.h
@@ -30,17 +30,18 @@ public:
   typedef const KeyType& KeyTypeRef;
   typedef const KeyType* KeyTypePointer;
 
   explicit nsSMILCompositor(KeyTypePointer aKey)
    : mKey(*aKey),
      mForceCompositing(false)
   { }
   nsSMILCompositor(nsSMILCompositor&& toMove)
-    : mKey(std::move(toMove.mKey)),
+    : PLDHashEntryHdr(std::move(toMove)),
+      mKey(std::move(toMove.mKey)),
       mAnimationFunctions(std::move(toMove.mAnimationFunctions)),
       mForceCompositing(false)
   { }
   ~nsSMILCompositor() { }
 
   // PLDHashEntryHdr methods
   KeyTypeRef GetKey() const { return mKey; }
   bool KeyEquals(KeyTypePointer aKey) const;
--- a/dom/storage/LocalStorageManager.h
+++ b/dom/storage/LocalStorageManager.h
@@ -61,18 +61,20 @@ private:
   class LocalStorageCacheHashKey : public nsCStringHashKey
   {
   public:
     explicit LocalStorageCacheHashKey(const nsACString* aKey)
       : nsCStringHashKey(aKey)
       , mCache(new LocalStorageCache(aKey))
     {}
 
-    LocalStorageCacheHashKey(const LocalStorageCacheHashKey& aOther)
-      : nsCStringHashKey(aOther)
+    LocalStorageCacheHashKey(LocalStorageCacheHashKey&& aOther)
+      : nsCStringHashKey(std::move(aOther))
+      , mCache(std::move(aOther.mCache))
+      , mCacheRef(std::move(aOther.mCacheRef))
     {
       NS_ERROR("Shouldn't be called");
     }
 
     LocalStorageCache* cache() { return mCache; }
     // Keep the cache referenced forever, used for sessionStorage.
     void HardRef() { mCacheRef = mCache; }
 
--- a/dom/tests/mochitest/bugs/test_bug1171215.html
+++ b/dom/tests/mochitest/bugs/test_bug1171215.html
@@ -22,18 +22,22 @@ https://bugzilla.mozilla.org/show_bug.cg
     document.cookie = "a=b";
 
     // Set a cookie in example.org so we can test that we can't read it in
     // third-party cases.
     f.contentWindow.location =
         "http://example.org/tests/dom/tests/mochitest/bugs/file_prime_cookie.html";
     waitForLoad().then(function() {
         // Cookies are set up, disallow third-party cookies and start the test.
-        SpecialPowers.pushPrefEnv({ set: [[ 'network.cookie.cookieBehavior', 1 ]] },
-                                  () => { continueTest(); });
+        SpecialPowers.pushPrefEnv({ set: [
+            ["browser.contentblocking.enabled", true],
+            ["browser.contentblocking.ui.enabled", true],
+            ["browser.contentblocking.rejecttrackers.ui.enabled", true],
+            ["network.cookie.cookieBehavior", 1],
+          ]}, () => { continueTest(); });
     }).catch((e) => { ok(false, `Got exception: ${e}`) });
   }
 
   function waitForLoad() {
     return new Promise((resolve) => {
       window.addEventListener("message", function(msg) {
         info(`got message ${msg.data}`);
         resolve(msg.data);
--- a/dom/tests/mochitest/general/storagePermissionsUtils.js
+++ b/dom/tests/mochitest/general/storagePermissionsUtils.js
@@ -21,17 +21,22 @@ if (inFrame) {
   };
 } else {
   finishTest = function() {
     SimpleTest.finish();
   };
 }
 
 function setCookieBehavior(behavior) {
-  return SpecialPowers.pushPrefEnv({"set": [[kPrefName, behavior]]});
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["browser.contentblocking.enabled", true],
+    ["browser.contentblocking.ui.enabled", true],
+    ["browser.contentblocking.rejecttrackers.ui.enabled", true],
+    [kPrefName, behavior],
+  ]});
 }
 
 function runIFrame(url) {
   return new Promise((resolve, reject) => {
     function onMessage(e)  {
       if (e.data == "done") {
         resolve();
         window.removeEventListener('message', onMessage);
--- a/dom/tests/mochitest/localstorage/test_localStorageCookieSettings.html
+++ b/dom/tests/mochitest/localstorage/test_localStorageCookieSettings.html
@@ -10,18 +10,22 @@
 <body>
 <iframe></iframe>
 
 <script type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 // Set cookies behavior to "always reject".
-SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 2]]},
-                          test1);
+SpecialPowers.pushPrefEnv({"set": [
+  ["browser.contentblocking.enabled", true],
+  ["browser.contentblocking.ui.enabled", true],
+  ["browser.contentblocking.rejecttrackers.ui.enabled", true],
+  ["network.cookie.cookieBehavior", 2],
+]}, test1);
 
 function test1() {
   try {
     localStorage.setItem("contentkey", "test-value");
     ok(false, "Setting localStorageItem should throw a security exception");
   }
   catch(ex) {
     is(ex.name, "SecurityError");
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -26,16 +26,17 @@
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "mozilla/ipc/BackgroundChild.h"
 #include "GeckoProfiler.h"
 #include "jsfriendapi.h"
 #include "js/LocaleSensitive.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
@@ -2267,17 +2268,17 @@ RuntimeService::ResumeWorkersForWindow(n
 
 void
 RuntimeService::PropagateFirstPartyStorageAccessGranted(nsPIDOMWindowInner* aWindow)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(StaticPrefs::network_cookie_cookieBehavior() ==
                nsICookieService::BEHAVIOR_REJECT_TRACKER &&
-             StaticPrefs::browser_contentblocking_enabled());
+             AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions());
 
   nsTArray<WorkerPrivate*> workers;
   GetWorkersForWindow(aWindow, workers);
 
   for (uint32_t index = 0; index < workers.Length(); index++) {
     workers[index]->PropagateFirstPartyStorageAccessGranted();
   }
 }
@@ -2879,17 +2880,17 @@ ResumeWorkersForWindow(nsPIDOMWindowInne
 }
 
 void
 PropagateFirstPartyStorageAccessGrantedToWorkers(nsPIDOMWindowInner* aWindow)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(StaticPrefs::network_cookie_cookieBehavior() ==
                nsICookieService::BEHAVIOR_REJECT_TRACKER &&
-             StaticPrefs::browser_contentblocking_enabled());
+             AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions());
 
   RuntimeService* runtime = RuntimeService::GetService();
   if (runtime) {
     runtime->PropagateFirstPartyStorageAccessGranted(aWindow);
   }
 }
 
 WorkerPrivate*
--- a/dom/workers/test/test_sharedWorker_thirdparty.html
+++ b/dom/workers/test/test_sharedWorker_thirdparty.html
@@ -42,16 +42,19 @@
     ]});
     let result = await testThirdPartyFrame('allowed');
     ok(result === 'allowed',
        'SharedWorker should be allowed when 3rd party iframes can access storage');
   });
 
   add_task(async function blocked() {
     await SpecialPowers.pushPrefEnv({ set: [
+      ["browser.contentblocking.enabled", true],
+      ["browser.contentblocking.ui.enabled", true],
+      ["browser.contentblocking.rejecttrackers.ui.enabled", true],
       ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN]
     ]});
     let result = await testThirdPartyFrame('blocked');
     ok(result === 'blocked',
        'SharedWorker should not be allowed when 3rd party iframes are denied storage');
   });
 
   </script>
--- a/dom/xslt/xslt/txExecutionState.h
+++ b/dom/xslt/xslt/txExecutionState.h
@@ -24,18 +24,20 @@ class txInstruction;
 
 class txLoadedDocumentEntry : public nsStringHashKey
 {
 public:
     explicit txLoadedDocumentEntry(KeyTypePointer aStr) : nsStringHashKey(aStr),
                                                           mLoadResult(NS_OK)
     {
     }
-    txLoadedDocumentEntry(const txLoadedDocumentEntry& aToCopy)
-        : nsStringHashKey(aToCopy)
+    txLoadedDocumentEntry(txLoadedDocumentEntry&& aOther)
+        : nsStringHashKey(std::move(aOther))
+        , mDocument(std::move(aOther.mDocument))
+        , mLoadResult(std::move(aOther.mLoadResult))
     {
         NS_ERROR("We're horked.");
     }
     ~txLoadedDocumentEntry()
     {
         if (mDocument) {
             txXPathNodeUtils::release(mDocument);
         }
--- a/extensions/cookie/test/file_testcommon.js
+++ b/extensions/cookie/test/file_testcommon.js
@@ -8,17 +8,22 @@ var gPopup;
 var gScript;
 
 var gLoads = 0;
 
 function setupTest(uri, cookies, loads) {
   SimpleTest.waitForExplicitFinish();
 
   var prefSet = new Promise(resolve => {
-    SpecialPowers.pushPrefEnv({ set: [["network.cookie.cookieBehavior", 1]] }, resolve);
+    SpecialPowers.pushPrefEnv({ set: [
+      ["browser.contentblocking.enabled", true],
+      ["browser.contentblocking.ui.enabled", true],
+      ["browser.contentblocking.rejecttrackers.ui.enabled", true],
+      ["network.cookie.cookieBehavior", 1],
+    ]}, resolve);
   });
 
   gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
   gExpectedCookies = cookies;
   gExpectedLoads = loads;
 
   // Listen for MessageEvents.
   window.addEventListener("message", messageReceiver);
--- a/extensions/cookie/test/unit/test_cookies_thirdparty.js
+++ b/extensions/cookie/test/unit/test_cookies_thirdparty.js
@@ -1,16 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // test third party cookie blocking, for the cases:
 // 1) with null channel
 // 2) with channel, but with no docshell parent
 
 function run_test() {
+  // Set the needed content blocking prefs
+  Services.prefs.setBoolPref("browser.contentblocking.enabled", true);
+  Services.prefs.setBoolPref("browser.contentblocking.ui.enabled", true);
+  Services.prefs.setBoolPref("browser.contentblocking.rejecttrackers.ui.enabled", true);
+
   // Create URIs and channels pointing to foo.com and bar.com.
   // We will use these to put foo.com into first and third party contexts.
   var spec1 = "http://foo.com/foo.html";
   var spec2 = "http://bar.com/bar.html";
   var uri1 = NetUtil.newURI(spec1);
   var uri2 = NetUtil.newURI(spec2);
   var channel1 = NetUtil.newChannel({uri: uri1, loadUsingSystemPrincipal: true});
   var channel2 = NetUtil.newChannel({uri: uri2, loadUsingSystemPrincipal: true});
--- a/gfx/thebes/gfxFontFeatures.h
+++ b/gfx/thebes/gfxFontFeatures.h
@@ -107,17 +107,20 @@ private:
     };
 
     class FeatureValueHashEntry : public PLDHashEntryHdr {
     public:
         typedef const FeatureValueHashKey &KeyType;
         typedef const FeatureValueHashKey *KeyTypePointer;
 
         explicit FeatureValueHashEntry(KeyTypePointer aKey) { }
-        FeatureValueHashEntry(const FeatureValueHashEntry& toCopy)
+        FeatureValueHashEntry(FeatureValueHashEntry&& other)
+            : PLDHashEntryHdr(std::move(other))
+            , mKey(std::move(other.mKey))
+            , mValues(std::move(other.mValues))
         {
             NS_ERROR("Should not be called");
         }
         ~FeatureValueHashEntry() { }
 
         bool KeyEquals(const KeyTypePointer aKey) const;
         static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
         static PLDHashNumber HashKey(const KeyTypePointer aKey);
--- a/gfx/thebes/gfxGlyphExtents.h
+++ b/gfx/thebes/gfxGlyphExtents.h
@@ -89,22 +89,22 @@ private:
         // When constructing a new entry in the hashtable, we'll leave this
         // blank. The caller of Put() will fill this in.
         explicit HashEntry(KeyTypePointer aPtr)
           : nsUint32HashKey(aPtr)
           , x(0.0)
           , y(0.0)
           , width(0.0)
           , height(0.0) {}
-        HashEntry(const HashEntry& toCopy)
-          : nsUint32HashKey(toCopy) {
-            x = toCopy.x;
-            y = toCopy.y;
-            width = toCopy.width;
-            height = toCopy.height;
+        HashEntry(HashEntry&& aOther)
+            : nsUint32HashKey(std::move(aOther))
+            , x(aOther.x)
+            , y(aOther.y)
+            , width(aOther.width)
+            , height(aOther.height) {
         }
 
         float x, y, width, height;
     };
 
     enum { BLOCK_SIZE_BITS = 7, BLOCK_SIZE = 1 << BLOCK_SIZE_BITS }; // 128-glyph blocks
 
     class GlyphWidths {
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -365,23 +365,16 @@ gfxPlatformFontList::InitFontList()
         return rv;
     }
 
     ApplyWhitelist();
     return NS_OK;
 }
 
 void
-gfxPlatformFontList::GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult)
-{
-    aResult = aKeyName;
-    ToLowerCase(aResult);
-}
-
-void
 gfxPlatformFontList::GenerateFontListKey(const nsACString& aKeyName, nsACString& aResult)
 {
     aResult = aKeyName;
     ToLowerCase(aResult);
 }
 
 #define OTHERNAMES_TIMEOUT 200
 
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -482,17 +482,16 @@ protected:
     virtual gfxFontEntry* LookupInFaceNameLists(const nsACString& aFontName);
 
     // commonly used fonts for which the name table should be loaded at startup
     virtual void PreloadNamesList();
 
     // load the bad underline blacklist from pref.
     void LoadBadUnderlineList();
 
-    void GenerateFontListKey(const nsAString& aKeyName, nsAString& aResult);
     void GenerateFontListKey(const nsACString& aKeyName, nsACString& aResult);
 
     virtual void GetFontFamilyNames(nsTArray<nsCString>& aFontFamilyNames);
 
     // helper function to map lang to lang group
     nsAtom* GetLangGroup(nsAtom* aLanguage);
 
     // gfxFontInfoLoader overrides, used to load in font cmaps
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -389,17 +389,18 @@ public:
             explicit Entry(KeyTypePointer aKey)
                 : mURI(aKey->mURI),
                   mPrincipal(aKey->mPrincipal),
                   mFontEntry(aKey->mFontEntry),
                   mPrivate(aKey->mPrivate)
             { }
 
             Entry(Entry&& aOther)
-                : mURI(std::move(aOther.mURI))
+                : PLDHashEntryHdr(std::move(aOther))
+                , mURI(std::move(aOther.mURI))
                 , mPrincipal(std::move(aOther.mPrincipal))
                 , mFontEntry(std::move(aOther.mFontEntry))
                 , mPrivate(std::move(aOther.mPrivate))
             { }
 
             ~Entry() { }
 
             bool KeyEquals(const KeyTypePointer aKey) const;
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -86,16 +86,17 @@ void main(void) {
     // out clip information.
     // TODO(gw): It's possible that we might want alpha
     //           shaders that don't clip in the future,
     //           but it's reasonable to assume that one
     //           implies the other, for now.
 #ifdef WR_FEATURE_ALPHA_PASS
     write_clip(
         vi.world_pos,
+        vi.snap_offset,
         clip_area
     );
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         ph.specific_prim_address,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -22,16 +22,17 @@ uniform sampler2DArray sCacheRGBA8;
 uniform sampler2DArray sSharedCacheA8;
 
 vec2 clamp_rect(vec2 pt, RectWithSize rect) {
     return clamp(pt, rect.p0, rect.p0 + rect.size);
 }
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
+flat varying vec4 vClipMaskUvSampleBounds;
 // XY and W are homogeneous coordinates, Z is the layer index
 varying vec4 vClipMaskUv;
 
 
 #ifdef WR_VERTEX_SHADER
 
 #define COLOR_MODE_FROM_PASS          0
 #define COLOR_MODE_ALPHA              1
@@ -217,24 +218,31 @@ VertexInfo write_transform_vertex(RectWi
         local_pos,
         vec2(0.0),
         world_pos
     );
 
     return vi;
 }
 
-void write_clip(vec4 world_pos, ClipArea area) {
+void write_clip(vec4 world_pos, vec2 snap_offset, ClipArea area) {
     vec2 uv = world_pos.xy * uDevicePixelRatio +
-        world_pos.w * (area.common_data.task_rect.p0 - area.screen_origin);
+        world_pos.w * (snap_offset + area.common_data.task_rect.p0 - area.screen_origin);
     vClipMaskUvBounds = vec4(
         area.common_data.task_rect.p0,
         area.common_data.task_rect.p0 + area.common_data.task_rect.size
     );
+    vClipMaskUvSampleBounds.xy = vClipMaskUvBounds.xy + vec2(0.5);
+    vClipMaskUvSampleBounds.zw = vClipMaskUvBounds.zw - vec2(0.5);
     vClipMaskUv = vec4(uv, area.common_data.texture_layer_index, world_pos.w);
+
+    vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
+    vClipMaskUv.xy /= texture_size;
+    vClipMaskUvBounds /= texture_size.xyxy;
+    vClipMaskUvSampleBounds /= texture_size.xyxy;
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
 float do_clip() {
     // check for the dummy bounds, which are given to the opaque objects
     if (vClipMaskUvBounds.xy == vClipMaskUvBounds.zw) {
@@ -245,19 +253,24 @@ float do_clip() {
     vec2 mask_uv = vClipMaskUv.xy * gl_FragCoord.w;
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, mask_uv),
         vec4(mask_uv, vClipMaskUvBounds.zw));
     // bail out if the pixel is outside the valid bounds
     if (!all(inside)) {
         return 0.0;
     }
+
     // finally, the slow path - fetch the mask value from an image
-    ivec3 tc = ivec3(mask_uv, vClipMaskUv.z);
-    return texelFetch(sCacheA8, tc, 0).r;
+
+    // TODO(gw): texelFetch here fails on some nVidia hardware in
+    //           some cases. For now, just use texture()
+    //           unconditionally.
+    mask_uv = clamp(mask_uv, vClipMaskUvSampleBounds.xy, vClipMaskUvSampleBounds.zw);
+    return texture(sCacheA8, vec3(mask_uv, vClipMaskUv.z)).r;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -75,16 +75,17 @@ void main(void) {
     vec4 final_pos = vec4(
         dest_origin * world_pos.w + world_pos.xy * uDevicePixelRatio,
         world_pos.w * ci.z,
         world_pos.w
     );
 
     write_clip(
         world_pos,
+        vec2(0.0),
         clip_area
     );
 
     gl_Position = uTransform * final_pos;
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -224,17 +224,17 @@ void main(void) {
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size;
     vUvClip = vec4(f, 1.0 - f);
 #else
     vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size;
 #endif
 
-    write_clip(vi.world_pos, clip_area);
+    write_clip(vi.world_pos, vi.snap_offset, clip_area);
 
     switch (color_mode) {
         case COLOR_MODE_ALPHA:
         case COLOR_MODE_BITMAP:
             vMaskSwizzle = vec2(0.0, 1.0);
             vColor = text.color;
             break;
         case COLOR_MODE_SUBPX_BG_PASS2:
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,16 +1,16 @@
 /* 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::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering};
 use api::{YuvColorSpace, YuvFormat, WorldPixel, WorldRect};
-use clip::{ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore};
+use clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use euclid::vec3;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
@@ -1770,22 +1770,24 @@ impl ClipBatcher {
         task_address: RenderTaskAddress,
         clip_node_range: ClipNodeRange,
         root_spatial_node_index: SpatialNodeIndex,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
         clip_scroll_tree: &ClipScrollTree,
         transforms: &mut TransformPalette,
+        clip_data_store: &ClipDataStore,
     ) {
         for i in 0 .. clip_node_range.count {
-            let (clip_node, flags, spatial_node_index) = clip_store.get_node_from_range(&clip_node_range, i);
+            let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
+            let clip_node = &clip_data_store[clip_instance.handle];
 
             let clip_transform_id = transforms.get_id(
-                spatial_node_index,
+                clip_instance.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 clip_scroll_tree,
             );
 
             let prim_transform_id = transforms.get_id(
                 root_spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 clip_scroll_tree,
@@ -1847,17 +1849,17 @@ impl ClipBatcher {
                         .or_insert(Vec::new())
                         .push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                             ..instance
                         });
                 }
                 ClipItem::Rectangle(_, mode) => {
-                    if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
+                    if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
                         mode == ClipMode::ClipOut {
                         self.rectangles.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
                 }
                 ClipItem::RoundedRectangle(..) => {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,15 +1,16 @@
 /* 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, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize};
-use api::{LayoutSizeAu, LayoutSideOffsets, LayoutPrimitiveInfo, LayoutToDeviceScale};
+use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
 use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
+use api::{AuHelpers};
 use app_units::Au;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegment};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, RectHelpers};
 
@@ -21,55 +22,64 @@ use util::{lerp, RectHelpers};
 
 /// Maximum resolution in device pixels at which borders are rasterized.
 pub const MAX_BORDER_RESOLUTION: u32 = 2048;
 /// Maximum number of dots or dashes per segment to avoid freezing and filling up
 /// memory with unreasonable inputs. It would be better to address this by not building
 /// a list of per-dot information in the first place.
 pub const MAX_DASH_COUNT: u32 = 2048;
 
-trait AuSizeConverter {
-    fn to_au(&self) -> LayoutSizeAu;
-}
-
-impl AuSizeConverter for LayoutSize {
-    fn to_au(&self) -> LayoutSizeAu {
-        LayoutSizeAu::new(
-            Au::from_f32_px(self.width),
-            Au::from_f32_px(self.height),
-        )
-    }
-}
-
 // TODO(gw): Perhaps there is a better way to store
 //           the border cache key than duplicating
 //           all the border structs with hashable
 //           variants...
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BorderRadiusAu {
     pub top_left: LayoutSizeAu,
     pub top_right: LayoutSizeAu,
     pub bottom_left: LayoutSizeAu,
     pub bottom_right: LayoutSizeAu,
 }
 
+impl BorderRadiusAu {
+    pub fn zero() -> Self {
+        BorderRadiusAu {
+            top_left: LayoutSizeAu::zero(),
+            top_right: LayoutSizeAu::zero(),
+            bottom_left: LayoutSizeAu::zero(),
+            bottom_right: LayoutSizeAu::zero(),
+        }
+    }
+}
+
 impl From<BorderRadius> for BorderRadiusAu {
     fn from(radius: BorderRadius) -> BorderRadiusAu {
         BorderRadiusAu {
             top_left: radius.top_left.to_au(),
             top_right: radius.top_right.to_au(),
             bottom_right: radius.bottom_right.to_au(),
             bottom_left: radius.bottom_left.to_au(),
         }
     }
 }
 
+impl From<BorderRadiusAu> for BorderRadius {
+    fn from(radius: BorderRadiusAu) -> Self {
+        BorderRadius {
+            top_left: LayoutSize::from_au(radius.top_left),
+            top_right: LayoutSize::from_au(radius.top_right),
+            bottom_right: LayoutSize::from_au(radius.bottom_right),
+            bottom_left: LayoutSize::from_au(radius.bottom_left),
+        }
+    }
+}
+
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BorderWidthsAu {
     pub left: Au,
     pub top: Au,
     pub right: Au,
     pub bottom: Au,
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,24 +1,26 @@
 /* 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, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayoutPrimitiveInfo};
 use api::{LayoutRect, LayoutSize, LayoutVector2D};
-use clip::ClipItem;
+use clip::ClipItemKey;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
 use render_task::RenderTaskCacheEntryHandle;
 use util::RectHelpers;
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowClipSource {
     // Parameters that define the shadow and are constant.
     pub shadow_radius: BorderRadius,
     pub blur_radius: f32,
     pub clip_mode: BoxShadowClipMode,
     pub stretch_mode_x: BoxShadowStretchMode,
     pub stretch_mode_y: BoxShadowStretchMode,
 
@@ -117,38 +119,42 @@ impl<'a> DisplayListFlattener<'a> {
             let mut clips = Vec::with_capacity(2);
             let (final_prim_rect, clip_radius) = match clip_mode {
                 BoxShadowClipMode::Outset => {
                     if !shadow_rect.is_well_formed_and_nonempty() {
                         return;
                     }
 
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
-                    clips.push(ClipItem::new_rounded_rect(
+                    clips.push(ClipItemKey::rounded_rect(
                         prim_info.rect,
                         border_radius,
                         ClipMode::ClipOut
                     ));
 
                     (shadow_rect, shadow_radius)
                 }
                 BoxShadowClipMode::Inset => {
                     if shadow_rect.is_well_formed_and_nonempty() {
-                        clips.push(ClipItem::new_rounded_rect(
+                        clips.push(ClipItemKey::rounded_rect(
                             shadow_rect,
                             shadow_radius,
                             ClipMode::ClipOut
                         ));
                     }
 
                     (prim_info.rect, border_radius)
                 }
             };
 
-            clips.push(ClipItem::new_rounded_rect(final_prim_rect, clip_radius, ClipMode::Clip));
+            clips.push(ClipItemKey::rounded_rect(
+                final_prim_rect,
+                clip_radius,
+                ClipMode::Clip,
+            ));
 
             self.add_primitive(
                 clip_and_scroll,
                 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
                 clips,
                 PrimitiveContainer::Brush(
                     BrushPrimitive::new(
                         BrushKind::new_solid(*color),
@@ -158,17 +164,17 @@ impl<'a> DisplayListFlattener<'a> {
             );
         } else {
             // Normal path for box-shadows with a valid blur radius.
             let blur_offset = BLUR_SAMPLE_SCALE * blur_radius;
             let mut extra_clips = vec![];
 
             // Add a normal clip mask to clip out the contents
             // of the surrounding primitive.
-            extra_clips.push(ClipItem::new_rounded_rect(
+            extra_clips.push(ClipItemKey::rounded_rect(
                 prim_info.rect,
                 border_radius,
                 prim_clip_mode,
             ));
 
             // Get the local rect of where the shadow will be drawn,
             // expanded to include room for the blurred region.
             let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
@@ -176,17 +182,17 @@ impl<'a> DisplayListFlattener<'a> {
             // Draw the box-shadow as a solid rect, using a box-shadow
             // clip mask item.
             let prim = BrushPrimitive::new(
                 BrushKind::new_solid(*color),
                 None,
             );
 
             // Create the box-shadow clip item.
-            let shadow_clip_source = ClipItem::new_box_shadow(
+            let shadow_clip_source = ClipItemKey::box_shadow(
                 shadow_rect,
                 shadow_radius,
                 dest_rect,
                 blur_radius,
                 clip_mode,
             );
 
             let prim_info = match clip_mode {
--- a/gfx/webrender/src/capture.rs
+++ b/gfx/webrender/src/capture.rs
@@ -7,16 +7,17 @@ use std::path::{Path, PathBuf};
 
 use api::{CaptureBits, ExternalImageData, ImageDescriptor, TexelRect};
 #[cfg(feature = "png")]
 use device::ReadPixelsFormat;
 use ron;
 use serde;
 
 
+#[derive(Clone)]
 pub struct CaptureConfig {
     pub root: PathBuf,
     pub bits: CaptureBits,
     #[cfg(feature = "capture")]
     pretty: ron::ser::PrettyConfig,
 }
 
 impl CaptureConfig {
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,23 +1,25 @@
 /* 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, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
-use api::{VoidPtrToSizeFn};
-use border::{ensure_no_corner_overlap};
+use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
+use app_units::Au;
+use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
+use intern;
 use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
 use std::os::raw::c_void;
 use util::{extract_inner_rect_safe, pack_as_float, project_rect, ScaleOffset};
 
@@ -85,39 +87,105 @@ use util::{extract_inner_rect_safe, pack
     | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
     +------------------+------------------+------------------+------------------+------------------+
     | flags            | flags            | flags            | flags            | flags            |
     | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    | ClipNodeIndex    |
     +------------------+------------------+------------------+------------------+------------------+
 
  */
 
+// Type definitions for interning clip nodes.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug)]
+pub struct ClipDataMarker;
+
+pub type ClipDataStore = intern::DataStore<ClipItemKey, ClipNode, ClipDataMarker>;
+pub type ClipDataHandle = intern::Handle<ClipDataMarker>;
+pub type ClipDataUpdateList = intern::UpdateList<ClipItemKey>;
+pub type ClipDataInterner = intern::Interner<ClipItemKey, ClipDataMarker>;
+
 // Result of comparing a clip node instance against a local rect.
 #[derive(Debug)]
 enum ClipResult {
     // The clip does not affect the region at all.
     Accept,
     // The clip prevents the region from being drawn.
     Reject,
     // The clip affects part of the region. This may
     // require a clip mask, depending on other factors.
     Partial,
 }
 
 // A clip node is a single clip source, along with some
 // positioning information and implementation details
 // that control where the GPU data for this clip source
 // can be found.
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNode {
     pub item: ClipItem,
     pub gpu_cache_handle: GpuCacheHandle,
 }
 
+// Convert from an interning key for a clip item
+// to a clip node, which is cached in the document.
+// TODO(gw): These enums are a bit messy - we should
+//           convert them to use named fields.
+impl From<ClipItemKey> for ClipNode {
+    fn from(item: ClipItemKey) -> Self {
+        let item = match item {
+            ClipItemKey::Rectangle(rect, mode) => {
+                ClipItem::Rectangle(LayoutRect::from_au(rect), mode)
+            }
+            ClipItemKey::RoundedRectangle(rect, radius, mode) => {
+                ClipItem::RoundedRectangle(
+                    LayoutRect::from_au(rect),
+                    radius.into(),
+                    mode,
+                )
+            }
+            ClipItemKey::LineDecoration(rect, style, orientation, wavy_line_thickness) => {
+                ClipItem::LineDecoration(LineDecorationClipSource {
+                    rect: LayoutRect::from_au(rect),
+                    style,
+                    orientation,
+                    wavy_line_thickness: wavy_line_thickness.to_f32_px(),
+                })
+            }
+            ClipItemKey::ImageMask(rect, image, repeat) => {
+                ClipItem::Image(ImageMask {
+                    image,
+                    rect: LayoutRect::from_au(rect),
+                    repeat,
+                })
+            }
+            ClipItemKey::BoxShadow(shadow_rect, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
+                ClipItem::new_box_shadow(
+                    LayoutRect::from_au(shadow_rect),
+                    shadow_radius.into(),
+                    LayoutRect::from_au(prim_shadow_rect),
+                    blur_radius.to_f32_px(),
+                    clip_mode,
+                )
+            }
+        };
+
+        ClipNode {
+            item,
+            gpu_cache_handle: GpuCacheHandle::new(),
+        }
+    }
+}
+
 // Flags that are attached to instances of clip nodes.
 bitflags! {
+    #[cfg_attr(feature = "capture", derive(Serialize))]
+    #[cfg_attr(feature = "replay", derive(Deserialize))]
     pub struct ClipNodeFlags: u8 {
         const SAME_SPATIAL_NODE = 0x1;
         const SAME_COORD_SYSTEM = 0x2;
     }
 }
 
 // Identifier for a clip chain. Clip chains are stored
 // in a contiguous array in the clip store. They are
@@ -132,60 +200,40 @@ pub struct ClipChainId(pub u32);
 impl ClipChainId {
     pub const NONE: Self = ClipChainId(u32::MAX);
 }
 
 // A clip chain node is an id for a range of clip sources,
 // and a link to a parent clip chain node, or ClipChainId::NONE.
 #[derive(Clone)]
 pub struct ClipChainNode {
-    pub clip_node_index: ClipNodeIndex,
+    pub handle: ClipDataHandle,
     pub spatial_node_index: SpatialNodeIndex,
     pub parent_clip_chain_id: ClipChainId,
 }
 
 // An index into the clip_nodes array.
 #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNodeIndex(pub u32);
 
 // When a clip node is found to be valid for a
 // clip chain instance, it's stored in an index
 // buffer style structure. This struct contains
 // an index to the node data itself, as well as
 // some flags describing how this clip node instance
 // is positioned.
-#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+#[derive(Clone, Copy, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNodeInstance {
-    index_and_flags: u32,
-    spatial_node_index: u32,
-}
-
-impl ClipNodeInstance {
-    fn new(
-        index: ClipNodeIndex,
-        flags: ClipNodeFlags,
-        spatial_node_index: SpatialNodeIndex,
-    ) -> ClipNodeInstance {
-        ClipNodeInstance {
-            index_and_flags: (index.0 & 0x00ffffff) | ((flags.bits() as u32) << 24),
-            spatial_node_index: spatial_node_index.0 as u32,
-        }
-    }
-
-    fn flags(&self) -> ClipNodeFlags {
-        ClipNodeFlags::from_bits_truncate((self.index_and_flags >> 24) as u8)
-    }
-
-    fn index(&self) -> usize {
-        (self.index_and_flags & 0x00ffffff) as usize
-    }
+    pub handle: ClipDataHandle,
+    pub flags: ClipNodeFlags,
+    pub spatial_node_index: SpatialNodeIndex,
 }
 
 // A range of clip node instances that were found by
 // building a clip chain instance.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNodeRange {
@@ -204,17 +252,17 @@ enum ClipSpaceConversion {
     ScaleOffset(ScaleOffset),
     Transform(LayoutToWorldTransform),
 }
 
 // Temporary information that is cached and reused
 // during building of a clip chain instance.
 struct ClipNodeInfo {
     conversion: ClipSpaceConversion,
-    node_index: ClipNodeIndex,
+    handle: ClipDataHandle,
     spatial_node_index: SpatialNodeIndex,
     has_non_root_coord_system: bool,
 }
 
 impl ClipNode {
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
@@ -308,19 +356,18 @@ impl ClipNode {
             ClipItem::RoundedRectangle(..) |
             ClipItem::LineDecoration(..) => {}
         }
     }
 }
 
 // The main clipping public interface that other modules access.
 pub struct ClipStore {
-    pub clip_nodes: Vec<ClipNode>,
     pub clip_chain_nodes: Vec<ClipChainNode>,
-    clip_node_indices: Vec<ClipNodeInstance>,
+    clip_node_instances: Vec<ClipNodeInstance>,
     clip_node_info: Vec<ClipNodeInfo>,
     clip_node_collectors: Vec<ClipNodeCollector>,
 }
 
 // A clip chain instance is what gets built for a given clip
 // chain id + local primitive region + positioning node.
 #[derive(Debug)]
 pub struct ClipChainInstance {
@@ -336,82 +383,48 @@ pub struct ClipChainInstance {
     // Combined clip rect in picture space (may
     // be more conservative that local_clip_rect).
     pub pic_clip_rect: PictureRect,
 }
 
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
-            clip_nodes: Vec::new(),
             clip_chain_nodes: Vec::new(),
-            clip_node_indices: Vec::new(),
+            clip_node_instances: Vec::new(),
             clip_node_info: Vec::new(),
             clip_node_collectors: Vec::new(),
         }
     }
 
     pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
         &self.clip_chain_nodes[clip_chain_id.0 as usize]
     }
 
-    pub fn add_clip_chain_node_index(
+    pub fn add_clip_chain_node(
         &mut self,
-        clip_node_index: ClipNodeIndex,
+        handle: ClipDataHandle,
         spatial_node_index: SpatialNodeIndex,
         parent_clip_chain_id: ClipChainId,
     ) -> ClipChainId {
         let id = ClipChainId(self.clip_chain_nodes.len() as u32);
         self.clip_chain_nodes.push(ClipChainNode {
-            clip_node_index,
+            handle,
             spatial_node_index,
             parent_clip_chain_id,
         });
         id
     }
 
-    pub fn add_clip_chain_node(
-        &mut self,
-        item: ClipItem,
-        spatial_node_index: SpatialNodeIndex,
-        parent_clip_chain_id: ClipChainId,
-    ) -> ClipChainId {
-        let clip_node_index = ClipNodeIndex(self.clip_nodes.len() as u32);
-        self.clip_nodes.push(ClipNode {
-            item,
-            gpu_cache_handle: GpuCacheHandle::new(),
-        });
-
-        self.add_clip_chain_node_index(
-            clip_node_index,
-            spatial_node_index,
-            parent_clip_chain_id,
-        )
-    }
-
-    pub fn get_node_from_range(
+    pub fn get_instance_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
-    ) -> (&ClipNode, ClipNodeFlags, SpatialNodeIndex) {
-        let instance = self.clip_node_indices[(node_range.first + index) as usize];
-        (
-            &self.clip_nodes[instance.index()],
-            instance.flags(),
-            SpatialNodeIndex(instance.spatial_node_index as usize),
-        )
-    }
-
-    pub fn get_node_from_range_mut(
-        &mut self,
-        node_range: &ClipNodeRange,
-        index: u32,
-    ) -> (&mut ClipNode, ClipNodeFlags) {
-        let instance = self.clip_node_indices[(node_range.first + index) as usize];
-        (&mut self.clip_nodes[instance.index()], instance.flags())
+    ) -> &ClipNodeInstance {
+        &self.clip_node_instances[(node_range.first + index) as usize]
     }
 
     // Notify the clip store that a new rasterization root has been created.
     // This means any clips from an earlier root should be collected rather
     // than applied on the primitive itself.
     pub fn push_raster_root(
         &mut self,
         raster_spatial_node_index: SpatialNodeIndex,
@@ -439,16 +452,17 @@ impl ClipStore {
         prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
         pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
         clip_scroll_tree: &ClipScrollTree,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
         world_rect: &WorldRect,
         clip_node_collector: &Option<ClipNodeCollector>,
+        clip_data_store: &mut ClipDataStore,
     ) -> Option<ClipChainInstance> {
         let mut local_clip_rect = local_prim_clip_rect;
 
         // Walk the clip chain to build local rects, and collect the
         // smallest possible local/device clip area.
 
         self.clip_node_info.clear();
         let mut current_clip_chain_id = clip_chain_id;
@@ -462,72 +476,72 @@ impl ClipStore {
             match self.clip_node_collectors.iter_mut().find(|c| {
                 clip_chain_node.spatial_node_index < c.raster_root
             }) {
                 Some(collector) => {
                     collector.insert(current_clip_chain_id);
                 }
                 None => {
                     if !add_clip_node_to_current_chain(
-                        clip_chain_node.clip_node_index,
+                        clip_chain_node.handle,
                         clip_chain_node.spatial_node_index,
                         spatial_node_index,
                         &mut local_clip_rect,
                         &mut self.clip_node_info,
-                        &self.clip_nodes,
+                        clip_data_store,
                         clip_scroll_tree,
                     ) {
                         return None;
                     }
                 }
             }
 
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         // Add any collected clips from primitives that should be
         // handled as part of this rasterization root.
         if let Some(clip_node_collector) = clip_node_collector {
             for clip_chain_id in &clip_node_collector.clips {
-                let (clip_node_index, clip_spatial_node_index) = {
+                let (handle, clip_spatial_node_index) = {
                     let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize];
-                    (clip_chain_node.clip_node_index, clip_chain_node.spatial_node_index)
+                    (clip_chain_node.handle, clip_chain_node.spatial_node_index)
                 };
 
                 if !add_clip_node_to_current_chain(
-                    clip_node_index,
+                    handle,
                     clip_spatial_node_index,
                     spatial_node_index,
                     &mut local_clip_rect,
                     &mut self.clip_node_info,
-                    &self.clip_nodes,
+                    clip_data_store,
                     clip_scroll_tree,
                 ) {
                     return None;
                 }
             }
         }
 
         let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
         let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
         let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
 
         // Now, we've collected all the clip nodes that *potentially* affect this
         // primitive region, and reduced the size of the prim region as much as possible.
 
         // Run through the clip nodes, and see which ones affect this prim region.
 
-        let first_clip_node_index = self.clip_node_indices.len() as u32;
+        let first_clip_node_index = self.clip_node_instances.len() as u32;
         let mut has_non_root_coord_system = false;
         let mut has_non_local_clips = false;
         let mut needs_mask = false;
 
         // For each potential clip node
         for node_info in self.clip_node_info.drain(..) {
-            let node = &mut self.clip_nodes[node_info.node_index.0 as usize];
+            let node = &mut clip_data_store[node_info.handle];
 
             // See how this clip affects the prim region.
             let clip_result = match node_info.conversion {
                 ClipSpaceConversion::Local => {
                     node.item.get_clip_result(&local_bounding_rect)
                 }
                 ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
                     has_non_local_clips = true;
@@ -591,32 +605,32 @@ impl ClipStore {
                         }
 
                         ClipItem::Rectangle(_, ClipMode::Clip) => {
                             !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
                         }
                     };
 
                     // Store this in the index buffer for this clip chain instance.
-                    let instance = ClipNodeInstance::new(
-                        node_info.node_index,
+                    let instance = ClipNodeInstance {
+                        handle: node_info.handle,
                         flags,
-                        node_info.spatial_node_index,
-                    );
-                    self.clip_node_indices.push(instance);
+                        spatial_node_index: node_info.spatial_node_index,
+                    };
+                    self.clip_node_instances.push(instance);
 
                     has_non_root_coord_system |= node_info.has_non_root_coord_system;
                 }
             }
         }
 
         // Get the range identifying the clip nodes in the index buffer.
         let clips_range = ClipNodeRange {
             first: first_clip_node_index,
-            count: self.clip_node_indices.len() as u32 - first_clip_node_index,
+            count: self.clip_node_instances.len() as u32 - first_clip_node_index,
         };
 
         // Return a valid clip chain instance
         Some(ClipChainInstance {
             clips_range,
             has_non_root_coord_system,
             has_non_local_clips,
             local_clip_rect,
@@ -624,26 +638,27 @@ impl ClipStore {
             needs_mask,
         })
     }
 
     /// Reports the heap usage of this clip store.
     pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
         let mut size = 0;
         unsafe {
-            size += op(self.clip_nodes.as_ptr() as *const c_void);
             size += op(self.clip_chain_nodes.as_ptr() as *const c_void);
-            size += op(self.clip_node_indices.as_ptr() as *const c_void);
+            size += op(self.clip_node_instances.as_ptr() as *const c_void);
             size += op(self.clip_node_info.as_ptr() as *const c_void);
         }
         size
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct LineDecorationClipSource {
     rect: LayoutRect,
     style: LineStyle,
     orientation: LineOrientation,
     wavy_line_thickness: f32,
 }
 
 
@@ -715,66 +730,132 @@ impl ClipRegion<Option<ComplexClipRegion
                         mode: region.mode,
                     })
                 },
             }
         }
     }
 }
 
-#[derive(Debug)]
+// The ClipItemKey is a hashable representation of the contents
+// of a clip item. It is used during interning to de-duplicate
+// clip nodes between frames and display lists. This allows quick
+// comparison of clip node equality by handle, and also allows
+// the uploaded GPU cache handle to be retained between display lists.
+// TODO(gw): Maybe we should consider constructing these directly
+//           in the DL builder?
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ClipItemKey {
+    Rectangle(LayoutRectAu, ClipMode),
+    RoundedRectangle(LayoutRectAu, BorderRadiusAu, ClipMode),
+    ImageMask(LayoutRectAu, ImageKey, bool),
+    BoxShadow(LayoutRectAu, BorderRadiusAu, LayoutRectAu, Au, BoxShadowClipMode),
+    LineDecoration(LayoutRectAu, LineStyle, LineOrientation, Au),
+}
+
+impl ClipItemKey {
+    pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
+        ClipItemKey::Rectangle(rect.to_au(), mode)
+    }
+
+    pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
+        if radii.is_zero() {
+            ClipItemKey::rectangle(rect, mode)
+        } else {
+            ensure_no_corner_overlap(&mut radii, &rect);
+            ClipItemKey::RoundedRectangle(
+                rect.to_au(),
+                radii.into(),
+                mode,
+            )
+        }
+    }
+
+    pub fn image_mask(image_mask: &ImageMask) -> Self {
+        ClipItemKey::ImageMask(
+            image_mask.rect.to_au(),
+            image_mask.image,
+            image_mask.repeat,
+        )
+    }
+
+    pub fn line_decoration(
+        rect: LayoutRect,
+        style: LineStyle,
+        orientation: LineOrientation,
+        wavy_line_thickness: f32,
+    ) -> Self {
+        ClipItemKey::LineDecoration(
+            rect.to_au(),
+            style,
+            orientation,
+            Au::from_f32_px(wavy_line_thickness),
+        )
+    }
+
+    pub fn box_shadow(
+        shadow_rect: LayoutRect,
+        shadow_radius: BorderRadius,
+        prim_shadow_rect: LayoutRect,
+        blur_radius: f32,
+        clip_mode: BoxShadowClipMode,
+    ) -> Self {
+        ClipItemKey::BoxShadow(
+            shadow_rect.to_au(),
+            shadow_radius.into(),
+            prim_shadow_rect.to_au(),
+            Au::from_f32_px(blur_radius),
+            clip_mode,
+        )
+    }
+
+    // Return a modified clip source that is the same as self
+    // but offset in local-space by a specified amount.
+    pub fn offset(&self, offset: &LayoutVector2D) -> Self {
+        let offset = offset.to_au();
+        match *self {
+            ClipItemKey::LineDecoration(rect, style, orientation, wavy_line_thickness) => {
+                ClipItemKey::LineDecoration(
+                    rect.translate(&offset),
+                    style,
+                    orientation,
+                    wavy_line_thickness,
+                )
+            }
+            _ => {
+                panic!("bug: other clip sources not expected here yet");
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClipItem {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
     Image(ImageMask),
     BoxShadow(BoxShadowClipSource),
     LineDecoration(LineDecorationClipSource),
 }
 
 impl ClipItem {
-    pub fn new_rounded_rect(
-        rect: LayoutRect,
-        mut radii: BorderRadius,
-        clip_mode: ClipMode
-    ) -> Self {
-        if radii.is_zero() {
-            ClipItem::Rectangle(rect, clip_mode)
-        } else {
-            ensure_no_corner_overlap(&mut radii, &rect);
-            ClipItem::RoundedRectangle(
-                rect,
-                radii,
-                clip_mode,
-            )
-        }
-    }
-
-    pub fn new_line_decoration(
-        rect: LayoutRect,
-        style: LineStyle,
-        orientation: LineOrientation,
-        wavy_line_thickness: f32,
-    ) -> Self {
-        ClipItem::LineDecoration(
-            LineDecorationClipSource {
-                rect,
-                style,
-                orientation,
-                wavy_line_thickness,
-            }
-        )
-    }
-
     pub fn new_box_shadow(
         shadow_rect: LayoutRect,
-        shadow_radius: BorderRadius,
+        mut shadow_radius: BorderRadius,
         prim_shadow_rect: LayoutRect,
         blur_radius: f32,
         clip_mode: BoxShadowClipMode,
     ) -> Self {
+        // Make sure corners don't overlap.
+        ensure_no_corner_overlap(&mut shadow_radius, &shadow_rect);
+
         // Get the fractional offsets required to match the
         // source rect with a minimal rect.
         let fract_offset = LayoutPoint::new(
             shadow_rect.origin.x.fract().abs(),
             shadow_rect.origin.y.fract().abs(),
         );
         let fract_size = LayoutSize::new(
             shadow_rect.size.width.fract().abs(),
@@ -853,32 +934,16 @@ impl ClipItem {
             stretch_mode_y,
             cache_handle: None,
             cache_key: None,
             clip_data_handle: GpuCacheHandle::new(),
             minimal_shadow_rect,
         })
     }
 
-    // Return a modified clip source that is the same as self
-    // but offset in local-space by a specified amount.
-    pub fn offset(&self, offset: &LayoutVector2D) -> Self {
-        match *self {
-            ClipItem::LineDecoration(ref info) => {
-                ClipItem::LineDecoration(LineDecorationClipSource {
-                    rect: info.rect.translate(offset),
-                    ..*info
-                })
-            }
-            _ => {
-                panic!("bug: other clip sources not expected here yet");
-            }
-        }
-    }
-
     // Get an optional clip rect that a clip source can provide to
     // reduce the size of a primitive region. This is typically
     // used to eliminate redundant clips, and reduce the size of
     // any clip mask that eventually gets drawn.
     fn get_local_clip_rect(&self) -> Option<LayoutRect> {
         match *self {
             ClipItem::Rectangle(clip_rect, ClipMode::Clip) => Some(clip_rect),
             ClipItem::Rectangle(_, ClipMode::ClipOut) => None,
@@ -1142,25 +1207,25 @@ impl ClipNodeCollector {
         self.clips.insert(clip_chain_id);
     }
 }
 
 // Add a clip node into the list of clips to be processed
 // for the current clip chain. Returns false if the clip
 // results in the entire primitive being culled out.
 fn add_clip_node_to_current_chain(
-    clip_node_index: ClipNodeIndex,
+    handle: ClipDataHandle,
     clip_spatial_node_index: SpatialNodeIndex,
     spatial_node_index: SpatialNodeIndex,
     local_clip_rect: &mut LayoutRect,
     clip_node_info: &mut Vec<ClipNodeInfo>,
-    clip_nodes: &[ClipNode],
+    clip_data_store: &ClipDataStore,
     clip_scroll_tree: &ClipScrollTree,
 ) -> bool {
-    let clip_node = &clip_nodes[clip_node_index.0 as usize];
+    let clip_node = &clip_data_store[handle];
     let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_spatial_node_index.0];
     let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0];
 
     // Determine the most efficient way to convert between coordinate
     // systems of the primitive and clip node.
     let conversion = if spatial_node_index == clip_spatial_node_index {
         Some(ClipSpaceConversion::Local)
     } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
@@ -1209,16 +1274,16 @@ fn add_clip_node_to_current_chain(
                     //           I have left this for now until we
                     //           find some good test cases where this
                     //           would be a worthwhile perf win.
                 }
             }
         }
         clip_node_info.push(ClipNodeInfo {
             conversion,
-            node_index: clip_node_index,
+            handle,
             spatial_node_index: clip_spatial_node_index,
             has_non_root_coord_system: clip_spatial_node.coordinate_system_id != CoordinateSystemId::root(),
         })
     }
 
     true
 }
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -8,17 +8,17 @@ use api::{ClipId, ColorF, ComplexClipReg
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
-use clip::{ClipChainId, ClipRegion, ClipItem, ClipStore};
+use clip::{ClipDataInterner, ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
@@ -154,29 +154,33 @@ pub struct DisplayListFlattener<'a> {
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
+
+    /// Reference to the clip interner for this document.
+    clip_interner: &'a mut ClipDataInterner,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
         scene_id: u64,
         picture_id_generator: &mut PictureIdGenerator,
+        clip_interner: &mut ClipDataInterner,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
         let background_color = root_pipeline
             .background_color
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
@@ -191,16 +195,17 @@ impl<'a> DisplayListFlattener<'a> {
             hit_testing_runs: Vec::new(),
             scrollbar_prims: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             picture_id_generator,
+            clip_interner,
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
@@ -719,32 +724,32 @@ impl<'a> DisplayListFlattener<'a> {
                     // as a clip chain (one clip item per node), eventually parented
                     // to the parent clip node. For a user defined clip chain, we will
                     // need to walk the linked list of clip chain nodes for each clip
                     // node, accumulating them into one clip chain that is then
                     // parented to the clip chain parent.
 
                     for _ in 0 .. item_clip_node.count {
                         // Get the id of the clip sources entry for that clip chain node.
-                        let (clip_node_index, spatial_node_index) = {
+                        let (handle, spatial_node_index) = {
                             let clip_chain = self
                                 .clip_store
                                 .get_clip_chain(clip_node_clip_chain_id);
 
                             clip_node_clip_chain_id = clip_chain.parent_clip_chain_id;
 
-                            (clip_chain.clip_node_index, clip_chain.spatial_node_index)
+                            (clip_chain.handle, clip_chain.spatial_node_index)
                         };
 
                         // Add a new clip chain node, which references the same clip sources, and
                         // parent it to the current parent.
                         clip_chain_id = self
                             .clip_store
-                            .add_clip_chain_node_index(
-                                clip_node_index,
+                            .add_clip_chain_node(
+                                handle,
                                 spatial_node_index,
                                 clip_chain_id,
                             );
                     }
                 }
 
                 // Map the last entry in the clip chain to the supplied ClipId. This makes
                 // this ClipId available as a source to other user defined clip chains.
@@ -786,29 +791,33 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     // Given a list of clip sources, a positioning node and
     // a parent clip chain, return a new clip chain entry.
     // If the supplied list of clip sources is empty, then
     // just return the parent clip chain id directly.
     fn build_clip_chain(
         &mut self,
-        clip_items: Vec<ClipItem>,
+        clip_items: Vec<ClipItemKey>,
         spatial_node_index: SpatialNodeIndex,
         parent_clip_chain_id: ClipChainId,
     ) -> ClipChainId {
         if clip_items.is_empty() {
             parent_clip_chain_id
         } else {
             let mut clip_chain_id = parent_clip_chain_id;
 
             for item in clip_items {
+                // Intern this clip item, and store the handle
+                // in the clip chain node.
+                let handle = self.clip_interner.intern(&item);
+
                 clip_chain_id = self.clip_store
                                     .add_clip_chain_node(
-                                        item,
+                                        handle,
                                         spatial_node_index,
                                         clip_chain_id,
                                     );
             }
 
             clip_chain_id
         }
     }
@@ -861,41 +870,44 @@ impl<'a> DisplayListFlattener<'a> {
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
     ) {
         // Add primitive to the top-most Picture on the stack.
         let pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
+        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_index) {
+            println!("\tadded to picture primitive {:?}", pic_prim_index);
+        }
         let pic = self.prim_store.get_pic_mut(pic_prim_index);
         pic.add_primitive(prim_index);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
-        clip_items: Vec<ClipItem>,
+        clip_items: Vec<ClipItemKey>,
         container: PrimitiveContainer,
     ) {
         if !self.shadow_stack.is_empty() {
             // TODO(gw): Restructure this so we don't need to move the shadow
             //           stack out (borrowck due to create_primitive below).
             let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
             for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
                 // Offset the local rect and clip rect by the shadow offset.
                 let mut info = info.clone();
                 info.rect = info.rect.translate(&shadow.offset);
                 info.clip_rect = info.clip_rect.translate(&shadow.offset);
 
                 // Offset any local clip sources by the shadow offset.
-                let clip_items: Vec<ClipItem> = clip_items
+                let clip_items: Vec<ClipItemKey> = clip_items
                     .iter()
                     .map(|cs| cs.offset(&shadow.offset))
                     .collect();
                 let clip_chain_id = self.build_clip_chain(
                     clip_items,
                     clip_and_scroll.spatial_node_index,
                     clip_and_scroll.clip_chain_id,
                 );
@@ -923,17 +935,17 @@ impl<'a> DisplayListFlattener<'a> {
             );
             let prim_index = self.create_primitive(
                 info,
                 clip_chain_id,
                 clip_and_scroll.spatial_node_index,
                 container,
             );
             if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?}", prim_index);
+                println!("Chasing {:?} by local rect", prim_index);
                 self.prim_store.chase_id = Some(prim_index);
             }
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             self.add_primitive_to_draw_list(
                 prim_index,
             );
         }
     }
@@ -1247,16 +1259,21 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayoutSize,
         content_size: &LayoutSize,
     ) {
+        if let ChasePrimitive::Index(prim_index) = self.config.chase_primitive {
+            println!("Chasing {:?} by index", prim_index);
+            self.prim_store.chase_id = Some(prim_index);
+        }
+
         self.push_reference_frame(
             ClipId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
             None,
             None,
             LayoutVector2D::zero(),
         );
@@ -1292,48 +1309,59 @@ impl<'a> DisplayListFlattener<'a> {
         let spatial_node = self.id_to_index_mapper.get_spatial_node_index(parent_id);
 
         // Add a mapping for this ClipId in case it's referenced as a positioning node.
         self.id_to_index_mapper
             .map_spatial_node(new_node_id, spatial_node);
 
         let mut clip_count = 0;
 
+        // Intern each clip item in this clip node, and add the interned
+        // handle to a clip chain node, parented to form a chain.
+        // TODO(gw): We could re-structure this to share some of the
+        //           interning and chaining code.
+
         // Build the clip sources from the supplied region.
+        let handle = self
+            .clip_interner
+            .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip));
+
         parent_clip_chain_index = self
             .clip_store
             .add_clip_chain_node(
-                ClipItem::Rectangle(clip_region.main, ClipMode::Clip),
+                handle,
                 spatial_node,
                 parent_clip_chain_index,
             );
         clip_count += 1;
 
-        if let Some(image_mask) = clip_region.image_mask {
+        if let Some(ref image_mask) = clip_region.image_mask {
+            let handle = self
+                .clip_interner
+                .intern(&ClipItemKey::image_mask(image_mask));
+
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
-                    ClipItem::Image(image_mask),
+                    handle,
                     spatial_node,
                     parent_clip_chain_index,
                 );
             clip_count += 1;
         }
 
         for region in clip_region.complex_clips {
-            let clip_item = ClipItem::new_rounded_rect(
-                region.rect,
-                region.radii,
-                region.mode,
-            );
+            let handle = self
+                .clip_interner
+                .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode));
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
-                    clip_item,
+                    handle,
                     spatial_node,
                     parent_clip_chain_index,
                 );
             clip_count += 1;
         }
 
         // Map the supplied ClipId -> clip chain id.
         self.id_to_index_mapper.add_clip_chain(
@@ -1427,17 +1455,17 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
-        extra_clips: Vec<ClipItem>,
+        extra_clips: Vec<ClipItemKey>,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
             // testing. This allows specifying invisible hit testing areas.
             self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
             return;
         }
 
@@ -1523,17 +1551,17 @@ impl<'a> DisplayListFlattener<'a> {
         let extra_clips = match style {
             LineStyle::Solid => {
                 Vec::new()
             }
             LineStyle::Wavy |
             LineStyle::Dotted |
             LineStyle::Dashed => {
                 vec![
-                    ClipItem::new_line_decoration(
+                    ClipItemKey::line_decoration(
                         info.rect,
                         style,
                         orientation,
                         wavy_line_thickness,
                     ),
                 ]
             }
         };
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,16 +1,16 @@
 /* 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::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode, PictureRect};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint, WorldRect, WorldPixel};
-use clip::ClipStore;
+use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::{PictureCompositeMode, PictureSurface, RasterConfig};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore, SpaceMapper};
@@ -27,16 +27,17 @@ use tiling::{ScrollbarPrimitive, Special
 use util;
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ChasePrimitive {
     Nothing,
+    Index(PrimitiveIndex),
     LocalRect(LayoutRect),
 }
 
 impl Default for ChasePrimitive {
     fn default() -> Self {
         ChasePrimitive::Nothing
     }
 }
@@ -78,16 +79,17 @@ pub struct FrameBuildingContext<'a> {
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
+    pub clip_data_store: &'a mut ClipDataStore,
 }
 
 pub struct PictureContext {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
@@ -177,16 +179,17 @@ impl FrameBuilder {
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &mut TransformPalette,
+        clip_data_store: &mut ClipDataStore,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.primitives.is_empty() {
             return None
         }
         self.prim_store.reset_prim_visibility();
 
@@ -214,16 +217,17 @@ impl FrameBuilder {
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
             transforms: transform_palette,
+            clip_data_store,
         };
 
         let prim_context = PrimitiveContext::new(
             &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
             root_spatial_node_index,
         );
 
         let (pic_context, mut pic_state) = self
@@ -318,16 +322,17 @@ impl FrameBuilder {
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
+        clip_data_store: &mut ClipDataStore,
     ) -> Frame {
         profile_scope!("build");
         debug_assert!(
             DeviceUintRect::new(DeviceUintPoint::zero(), self.window_size)
                 .contains_rect(&self.screen_rect)
         );
 
         let mut profile_counters = FrameProfileCounters::new();
@@ -356,16 +361,17 @@ impl FrameBuilder {
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
             &mut transform_palette,
+            clip_data_store,
         );
 
         resource_cache.block_until_all_resources_added(gpu_cache,
                                                        &mut render_tasks,
                                                        texture_cache_profile);
 
         let mut passes = vec![
             special_render_passes.alpha_glyph_pass,
@@ -399,16 +405,17 @@ impl FrameBuilder {
 
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 use_dual_source_blending,
                 clip_scroll_tree,
+                clip_data_store,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
@@ -440,17 +447,22 @@ impl FrameBuilder {
             deferred_resolves,
             gpu_cache_frame_id,
             has_been_rendered: false,
             has_texture_cache_tasks,
             prim_headers,
         }
     }
 
-    pub fn create_hit_tester(&mut self, clip_scroll_tree: &ClipScrollTree) -> HitTester {
+    pub fn create_hit_tester(
+        &mut self,
+        clip_scroll_tree: &ClipScrollTree,
+        clip_data_store: &ClipDataStore,
+    ) -> HitTester {
         HitTester::new(
             &self.hit_testing_runs,
             clip_scroll_tree,
-            &self.clip_store
+            &self.clip_store,
+            clip_data_store,
         )
     }
 }
 
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,20 +1,21 @@
 /* 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, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
 use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, VoidPtrToSizeFn, WorldPoint};
-use clip::{ClipNodeIndex, ClipChainNode, ClipNode, ClipItem, ClipStore};
-use clip::{ClipChainId, rounded_rectangle_contains_point};
+use clip::{ClipDataStore, ClipNode, ClipItem, ClipStore};
+use clip::{rounded_rectangle_contains_point};
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 use internal_types::FastHashMap;
 use prim_store::ScrollNodeAndClipChain;
 use std::os::raw::c_void;
+use std::u32;
 use util::LayoutToWorldFastTransform;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
 pub struct HitTestSpatialNode {
     /// The pipeline id of this node.
     pipeline_id: PipelineId,
@@ -44,16 +45,36 @@ impl HitTestClipNode {
         };
 
         HitTestClipNode {
             region,
         }
     }
 }
 
+// A hit testing clip chain node is the same as a
+// normal clip chain node, except that the clip
+// node is embedded inside the clip chain, rather
+// than referenced. This means we don't need to
+// copy the complete interned clip data store for
+// hit testing.
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct HitTestClipChainId(u32);
+
+impl HitTestClipChainId {
+    pub const NONE: Self = HitTestClipChainId(u32::MAX);
+}
+
+pub struct HitTestClipChainNode {
+    pub region: HitTestClipNode,
+    pub spatial_node_index: SpatialNodeIndex,
+    pub parent_clip_chain_id: HitTestClipChainId,
+}
+
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayoutRect,
     clip_rect: LayoutRect,
     tag: ItemTag,
     is_backface_visible: bool,
 }
 
@@ -91,76 +112,83 @@ impl HitTestRegion {
             HitTestRegion::Invalid => true,
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
     spatial_nodes: Vec<HitTestSpatialNode>,
-    clip_nodes: Vec<HitTestClipNode>,
-    clip_chains: Vec<ClipChainNode>,
+    clip_chains: Vec<HitTestClipChainNode>,
     pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
 }
 
 impl HitTester {
     pub fn new(
         runs: &Vec<HitTestingRun>,
         clip_scroll_tree: &ClipScrollTree,
-        clip_store: &ClipStore
+        clip_store: &ClipStore,
+        clip_data_store: &ClipDataStore,
     ) -> HitTester {
         let mut hit_tester = HitTester {
             runs: runs.clone(),
             spatial_nodes: Vec::new(),
-            clip_nodes: Vec::new(),
             clip_chains: Vec::new(),
             pipeline_root_nodes: FastHashMap::default(),
         };
-        hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store);
+        hit_tester.read_clip_scroll_tree(
+            clip_scroll_tree,
+            clip_store,
+            clip_data_store,
+        );
         hit_tester
     }
 
     fn read_clip_scroll_tree(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
-        clip_store: &ClipStore
+        clip_store: &ClipStore,
+        clip_data_store: &ClipDataStore,
     ) {
         self.spatial_nodes.clear();
         self.clip_chains.clear();
-        self.clip_nodes.clear();
 
         for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
             let index = SpatialNodeIndex(index);
 
             // If we haven't already seen a node for this pipeline, record this one as the root
             // node.
             self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
 
             self.spatial_nodes.push(HitTestSpatialNode {
                 pipeline_id: node.pipeline_id,
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
             });
         }
 
-        for node in &clip_store.clip_nodes {
-            self.clip_nodes.push(HitTestClipNode::new(node));
+        // For each clip chain node, extract the clip node from the clip
+        // data store, and store it inline with the clip chain node.
+        for node in &clip_store.clip_chain_nodes {
+            let clip_node = &clip_data_store[node.handle];
+            self.clip_chains.push(HitTestClipChainNode {
+                region: HitTestClipNode::new(clip_node),
+                spatial_node_index: node.spatial_node_index,
+                parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0),
+            });
         }
-
-        self.clip_chains
-            .extend_from_slice(&clip_store.clip_chain_nodes);
     }
 
     fn is_point_clipped_in_for_clip_chain(
         &self,
         point: WorldPoint,
-        clip_chain_id: ClipChainId,
+        clip_chain_id: HitTestClipChainId,
         test: &mut HitTest
     ) -> bool {
-        if clip_chain_id == ClipChainId::NONE {
+        if clip_chain_id == HitTestClipChainId::NONE {
             return true;
         }
 
         if let Some(result) = test.get_from_clip_chain_cache(clip_chain_id) {
             return result == ClippedIn::ClippedIn;
         }
 
         let descriptor = &self.clip_chains[clip_chain_id.0 as usize];
@@ -172,60 +200,60 @@ impl HitTester {
 
         if !parent_clipped_in {
             test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
             return false;
         }
 
         if !self.is_point_clipped_in_for_clip_node(
             point,
-            descriptor.clip_node_index,
+            clip_chain_id,
             descriptor.spatial_node_index,
             test,
         ) {
             test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
             return false;
         }
 
         test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn);
         true
     }
 
     fn is_point_clipped_in_for_clip_node(
         &self,
         point: WorldPoint,
-        node_index: ClipNodeIndex,
+        clip_chain_node_id: HitTestClipChainId,
         spatial_node_index: SpatialNodeIndex,
         test: &mut HitTest
     ) -> bool {
-        if let Some(clipped_in) = test.node_cache.get(&node_index) {
+        if let Some(clipped_in) = test.node_cache.get(&clip_chain_node_id) {
             return *clipped_in == ClippedIn::ClippedIn;
         }
 
-        let node = &self.clip_nodes[node_index.0 as usize];
+        let node = &self.clip_chains[clip_chain_node_id.0 as usize].region;
         let transform = self
             .spatial_nodes[spatial_node_index.0]
             .world_viewport_transform;
         let transformed_point = match transform
             .inverse()
             .and_then(|inverted| inverted.transform_point2d(&point))
         {
             Some(point) => point,
             None => {
-                test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
+                test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
                 return false;
             }
         };
 
         if !node.region.contains(&transformed_point) {
-            test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
+            test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
             return false;
         }
 
-        test.node_cache.insert(node_index, ClippedIn::ClippedIn);
+        test.node_cache.insert(clip_chain_node_id, ClippedIn::ClippedIn);
         true
     }
 
     pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
         let point = test.get_absolute_point(self);
 
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let spatial_node_index = clip_and_scroll.spatial_node_index;
@@ -241,17 +269,17 @@ impl HitTester {
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
                     !item.clip_rect.contains(&point_in_layer) {
                     continue;
                 }
 
-                let clip_chain_id = clip_and_scroll.clip_chain_id;
+                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                 clipped_in |=
                     self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                 if !clipped_in {
                     break;
                 }
 
                 return Some(spatial_node_index);
             }
@@ -285,17 +313,17 @@ impl HitTester {
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
                     !item.clip_rect.contains(&point_in_layer) {
                     continue;
                 }
 
-                let clip_chain_id = clip_and_scroll.clip_chain_id;
+                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                 clipped_in = clipped_in ||
                     self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                 if !clipped_in {
                     break;
                 }
 
                 // Don't hit items with backface-visibility:hidden if they are facing the back.
                 if !item.is_backface_visible {
@@ -338,17 +366,16 @@ impl HitTester {
     }
 
     // Reports the CPU heap usage of this HitTester struct.
     pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
         let mut size = 0;
         unsafe {
             size += op(self.runs.as_ptr() as *const c_void);
             size += op(self.spatial_nodes.as_ptr() as *const c_void);
-            size += op(self.clip_nodes.as_ptr() as *const c_void);
             size += op(self.clip_chains.as_ptr() as *const c_void);
             // We can't measure pipeline_root_nodes because we don't have the
             // real machinery from the malloc_size_of crate. We could estimate
             // it but it should generally be very small so we don't bother.
         }
         size
     }
 }
@@ -358,17 +385,17 @@ enum ClippedIn {
     ClippedIn,
     NotClippedIn,
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
-    node_cache: FastHashMap<ClipNodeIndex, ClippedIn>,
+    node_cache: FastHashMap<HitTestClipChainId, ClippedIn>,
     clip_chain_cache: Vec<Option<ClippedIn>>,
 }
 
 impl HitTest {
     pub fn new(
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags,
@@ -377,26 +404,26 @@ impl HitTest {
             pipeline_id,
             point,
             flags,
             node_cache: FastHashMap::default(),
             clip_chain_cache: Vec::new(),
         }
     }
 
-    fn get_from_clip_chain_cache(&mut self, index: ClipChainId) -> Option<ClippedIn> {
+    fn get_from_clip_chain_cache(&mut self, index: HitTestClipChainId) -> Option<ClippedIn> {
         let index = index.0 as usize;
         if index >= self.clip_chain_cache.len() {
             None
         } else {
             self.clip_chain_cache[index]
         }
     }
 
-    fn set_in_clip_chain_cache(&mut self, index: ClipChainId, value: ClippedIn) {
+    fn set_in_clip_chain_cache(&mut self, index: HitTestClipChainId, value: ClippedIn) {
         let index = index.0 as usize;
         if index >= self.clip_chain_cache.len() {
             self.clip_chain_cache.resize(index + 1, None);
         }
         self.clip_chain_cache[index] = Some(value);
     }
 
     fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/intern.rs
@@ -0,0 +1,301 @@
+/* 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 internal_types::FastHashMap;
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops;
+use std::u64;
+
+/*
+
+ The interning module provides a generic data structure
+ interning container. It is similar in concept to a
+ traditional string interning container, but it is
+ specialized to the WR thread model.
+
+ There is an Interner structure, that lives in the
+ scene builder thread, and a DataStore structure
+ that lives in the frame builder thread.
+
+ Hashing, interning and handle creation is done by
+ the interner structure during scene building.
+
+ Delta changes for the interner are pushed during
+ a transaction to the frame builder. The frame builder
+ is then able to access the content of the interned
+ handles quickly, via array indexing.
+
+ Epoch tracking ensures that the garbage collection
+ step which the interner uses to remove items is
+ only invoked on items that the frame builder thread
+ is no longer referencing.
+
+ Items in the data store are stored in a traditional
+ free-list structure, for content access and memory
+ usage efficiency.
+
+ */
+
+/// The epoch is incremented each time a scene is
+/// built. The most recently used scene epoch is
+/// stored inside each item and handle. This is
+/// then used for cache invalidation (item) and
+/// correctness validation (handle).
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone, PartialEq)]
+struct Epoch(u64);
+
+impl Epoch {
+    pub const INVALID: Self = Epoch(u64::MAX);
+}
+
+/// A list of updates to be applied to the data store,
+/// provided by the interning structure.
+pub struct UpdateList<S> {
+    /// The current epoch of the scene builder.
+    epoch: Epoch,
+    /// The additions and removals to apply.
+    updates: Vec<Update<S>>,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone)]
+pub struct Handle<T> {
+    index: usize,
+    epoch: Epoch,
+    _marker: PhantomData<T>,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum UpdateKind<S> {
+    Insert(S),
+    Remove,
+    UpdateEpoch,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct Update<S> {
+    index: usize,
+    kind: UpdateKind<S>,
+}
+
+/// The data item is stored with an epoch, for validating
+/// correct access patterns.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct Item<T> {
+    epoch: Epoch,
+    data: T,
+}
+
+/// The data store lives in the frame builder thread. It
+/// contains a free-list of items for fast access.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct DataStore<S, T, M> {
+    items: Vec<Item<T>>,
+    _source: PhantomData<S>,
+    _marker: PhantomData<M>,
+}
+
+impl<S, T, M> DataStore<S, T, M> where S: Debug, T: From<S>, M: Debug {
+    /// Construct a new data store
+    pub fn new() -> Self {
+        DataStore {
+            items: Vec::new(),
+            _source: PhantomData,
+            _marker: PhantomData,
+        }
+    }
+
+    /// Apply any updates from the scene builder thread to
+    /// this data store.
+    pub fn apply_updates(
+        &mut self,
+        update_list: UpdateList<S>,
+    ) {
+        for update in update_list.updates {
+            match update.kind {
+                UpdateKind::Insert(data) => {
+                    let item = Item {
+                        data: T::from(data),
+                        epoch: update_list.epoch,
+                    };
+                    if self.items.len() == update.index {
+                        self.items.push(item)
+                    } else {
+                        self.items[update.index] = item;
+                    }
+                }
+                UpdateKind::Remove => {
+                    self.items[update.index].epoch = Epoch::INVALID;
+                }
+                UpdateKind::UpdateEpoch => {
+                    self.items[update.index].epoch = update_list.epoch;
+                }
+            }
+        }
+    }
+}
+
+/// Retrieve an item from the store via handle
+impl<S, T, M> ops::Index<Handle<M>> for DataStore<S, T, M> {
+    type Output = T;
+    fn index(&self, handle: Handle<M>) -> &T {
+        let item = &self.items[handle.index];
+        assert_eq!(item.epoch, handle.epoch);
+        &item.data
+    }
+}
+
+/// Retrieve a mutable item from the store via handle
+/// Retrieve an item from the store via handle
+impl<S, T, M> ops::IndexMut<Handle<M>> for DataStore<S, T, M> {
+    fn index_mut(&mut self, handle: Handle<M>) -> &mut T {
+        let item = &mut self.items[handle.index];
+        assert_eq!(item.epoch, handle.epoch);
+        &mut item.data
+    }
+}
+
+/// The main interning data structure. This lives in the
+/// scene builder thread, and handles hashing and interning
+/// unique data structures. It also manages a free-list for
+/// the items in the data store, which is synchronized via
+/// an update list of additions / removals.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct Interner<S : Eq + Hash + Clone + Debug, M> {
+    /// Uniquely map an interning key to a handle
+    map: FastHashMap<S, Handle<M>>,
+    /// List of free slots in the data store for re-use.
+    free_list: Vec<usize>,
+    /// The next index to append items to if free-list is empty.
+    next_index: usize,
+    /// Pending list of updates that need to be applied.
+    updates: Vec<Update<S>>,
+    /// The current epoch for the interner.
+    current_epoch: Epoch,
+}
+
+impl<S, M> Interner<S, M> where S: Eq + Hash + Clone + Debug, M: Copy + Debug {
+    /// Construct a new interner
+    pub fn new() -> Self {
+        Interner {
+            map: FastHashMap::default(),
+            free_list: Vec::new(),
+            next_index: 0,
+            updates: Vec::new(),
+            current_epoch: Epoch(1),
+        }
+    }
+
+    /// Intern a data structure, and return a handle to
+    /// that data. The handle can then be stored in the
+    /// frame builder, and safely accessed via the data
+    /// store that lives in the frame builder thread.
+    pub fn intern(
+        &mut self,
+        data: &S,
+    ) -> Handle<M> {
+        // Use get_mut rather than entry here to avoid
+        // cloning the (sometimes large) key in the common
+        // case, where the data already exists in the interner.
+        if let Some(handle) = self.map.get_mut(data) {
+            // Update the epoch in the data store. This
+            // is not strictly needed for correctness, but
+            // is used to ensure items are only accessed
+            // via valid handles.
+            if handle.epoch != self.current_epoch {
+                self.updates.push(Update {
+                    index: handle.index,
+                    kind: UpdateKind::UpdateEpoch,
+                })
+            }
+            handle.epoch = self.current_epoch;
+            return *handle;
+        }
+
+        // We need to intern a new data item. First, find out
+        // if there is a spare slot in the free-list that we
+        // can use. Otherwise, append to the end of the list.
+        let index = match self.free_list.pop() {
+            Some(index) => index,
+            None => {
+                let index = self.next_index;
+                self.next_index += 1;
+                index
+            }
+        };
+
+        // Add a pending update to insert the new data.
+        self.updates.push(Update {
+            index,
+            kind: UpdateKind::Insert(data.clone()),
+        });
+
+        // Generate a handle for access via the data store.
+        let handle = Handle {
+            index,
+            epoch: self.current_epoch,
+            _marker: PhantomData,
+        };
+
+        // Store this handle so the next time it is
+        // interned, it gets re-used.
+        self.map.insert(data.clone(), handle);
+
+        handle
+    }
+
+    /// Retrieve the pending list of updates for an interner
+    /// that need to be applied to the data store. Also run
+    /// a GC step that removes old entries.
+    pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<S> {
+        let mut updates = mem::replace(&mut self.updates, Vec::new());
+        let free_list = &mut self.free_list;
+        let current_epoch = self.current_epoch.0;
+
+        // First, run a GC step. Walk through the handles, and
+        // if we find any that haven't been used for some time,
+        // remove them. If this ever shows up in profiles, we
+        // can make the GC step partial (scan only part of the
+        // map each frame). It also might make sense in the
+        // future to adjust how long items remain in the cache
+        // based on the current size of the list.
+        self.map.retain(|_, handle| {
+            if handle.epoch.0 + 10 < current_epoch {
+                // To expire an item:
+                //  - Add index to the free-list for re-use.
+                //  - Add an update to the data store to invalidate this slow.
+                //  - Remove from the hash map.
+                free_list.push(handle.index);
+                updates.push(Update {
+                    index: handle.index,
+                    kind: UpdateKind::Remove,
+                });
+                return false;
+            }
+
+            true
+        });
+
+        let updates = UpdateList {
+            updates,
+            epoch: self.current_epoch,
+        };
+
+        // Begin the next epoch
+        self.current_epoch = Epoch(self.current_epoch.0 + 1);
+
+        updates
+    }
+}
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -78,16 +78,17 @@ mod gamma_lut;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
 #[cfg(feature = "pathfinder")]
 mod gpu_glyph_renderer;
 mod gpu_types;
 mod hit_test;
 mod image;
+mod intern;
 mod internal_types;
 mod picture;
 mod prim_store;
 mod print_tree;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1720,16 +1720,17 @@ impl PrimitiveStore {
                     &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,
                     &frame_context.world_rect,
                     &clip_node_collector,
+                    frame_state.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
                     prim.metadata.clipped_world_rect = None;
                     return false;
                 }
@@ -2069,26 +2070,27 @@ fn write_brush_segment_description(
         metadata.local_rect,
         None,
         metadata.local_clip_rect
     );
 
     // Segment the primitive on all the local-space clip sources that we can.
     let mut local_clip_count = 0;
     for i in 0 .. clip_chain.clips_range.count {
-        let (clip_node, flags, _) = frame_state
+        let clip_instance = frame_state
             .clip_store
-            .get_node_from_range(&clip_chain.clips_range, i);
+            .get_instance_from_range(&clip_chain.clips_range, i);
+        let clip_node = &frame_state.clip_data_store[clip_instance.handle];
 
         // If this clip item is positioned by another positioning node, its relative position
         // could change during scrolling. This means that we would need to resegment. Instead
         // of doing that, only segment with clips that have the same positioning node.
         // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
         // when necessary while scrolling.
-        if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
+        if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
             continue;
         }
 
         local_clip_count += 1;
 
         let (local_clip_rect, radius, mode) = match clip_node.item {
             ClipItem::RoundedRectangle(rect, radii, clip_mode) => {
                 rect_clips_only = false;
@@ -2238,16 +2240,17 @@ impl Primitive {
                     &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,
                     &frame_context.world_rect,
                     clip_node_collector,
+                    frame_state.clip_data_store,
                 );
 
             match segment_clip_chain {
                 Some(segment_clip_chain) => {
                     if !segment_clip_chain.needs_mask ||
                        (!segment.may_need_clip_mask && !segment_clip_chain.has_non_local_clips) {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                         continue;
@@ -2270,16 +2273,17 @@ impl Primitive {
                     let clip_task = RenderTask::new_mask(
                         device_rect.to_i32(),
                         segment_clip_chain.clips_range,
                         root_spatial_node_index,
                         frame_state.clip_store,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_state.render_tasks,
+                        frame_state.clip_data_store,
                     );
 
                     let clip_task_id = frame_state.render_tasks.add(clip_task);
                     pic_state.tasks.push(clip_task_id);
                     segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
                 }
                 None => {
                     segment.clip_task_id = BrushSegmentTaskId::Empty;
@@ -2807,16 +2811,17 @@ impl Primitive {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
                     clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
+                    frame_state.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tcreated task {:?} with world rect {:?}",
                         clip_task_id, self.metadata.clipped_world_rect);
                 }
                 self.metadata.clip_task_id = Some(clip_task_id);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -11,16 +11,19 @@ use api::{IdNamespace, LayoutPoint, Pipe
 use api::{MemoryReport, VoidPtrToSizeFn};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey};
 use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
+#[cfg(feature = "replay")]
+use clip::ClipDataInterner;
+use clip::{ClipDataUpdateList, ClipDataStore};
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
@@ -109,16 +112,20 @@ struct Document {
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
 
     /// Track whether the last built frame is up to date or if it will need to be re-built
     /// before rendering again.
     frame_is_valid: bool,
     hit_tester_is_valid: bool,
+
+    // The store of currently active / available clip nodes. This is kept
+    // in sync with the clip interner in the scene builder for each document.
+    clip_data_store: ClipDataStore,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
     ) -> Self {
@@ -137,16 +144,17 @@ impl Document {
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
+            clip_data_store: ClipDataStore::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
@@ -262,18 +270,22 @@ impl Document {
                 &mut self.clip_scroll_tree,
                 &self.scene.pipelines,
                 accumulated_scale_factor,
                 self.view.layer,
                 pan,
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
+                &mut self.clip_data_store,
             );
-            self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
+            self.hit_tester = Some(frame_builder.create_hit_tester(
+                &self.clip_scroll_tree,
+                &self.clip_data_store,
+            ));
             frame
         };
 
         self.frame_is_valid = true;
         self.hit_tester_is_valid = true;
 
         RenderedDocument {
             frame,
@@ -595,16 +607,17 @@ impl RenderBackend {
                         );
                         if let Some(rasterizer) = txn.blob_rasterizer.take() {
                             self.resource_cache.set_blob_rasterizer(rasterizer);
                         }
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
+                            txn.clip_updates.take(),
                             replace(&mut txn.frame_ops, Vec::new()),
                             replace(&mut txn.notifications, Vec::new()),
                             txn.build_frame,
                             txn.render_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
@@ -900,16 +913,17 @@ impl RenderBackend {
             txn.blob_requests = blob_requests;
             txn.blob_rasterizer = blob_rasterizer;
         }
 
         if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() {
             self.update_document(
                 txn.document_id,
                 replace(&mut txn.resource_updates, Vec::new()),
+                None,
                 replace(&mut txn.frame_ops, Vec::new()),
                 replace(&mut txn.notifications, Vec::new()),
                 txn.build_frame,
                 txn.render_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
@@ -937,16 +951,17 @@ impl RenderBackend {
 
         tx.send(SceneBuilderRequest::Transaction(txn)).unwrap();
     }
 
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
+        clip_updates: Option<ClipDataUpdateList>,
         mut frame_ops: Vec<FrameMsg>,
         mut notifications: Vec<NotificationRequest>,
         mut build_frame: bool,
         mut render_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
@@ -960,16 +975,22 @@ impl RenderBackend {
         if build_frame {
             if let Some(ref sampler) = self.sampler {
                 frame_ops.append(&mut sampler.sample());
             }
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
+        // If there are any additions or removals of clip modes
+        // during the scene build, apply them to the data store now.
+        if let Some(clip_updates) = clip_updates {
+            doc.clip_data_store.apply_updates(clip_updates);
+        }
+
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
             build_frame |= op.build_frame;
             scroll |= op.scroll;
@@ -1275,18 +1296,24 @@ impl RenderBackend {
                     true,
                 );
                 //TODO: write down doc's pipeline info?
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", (id.0).0, id.1);
                 config.serialize(&rendered_document.frame, file_name);
             }
+
+            let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
+            config.serialize(&doc.clip_data_store, clip_data_name);
         }
 
+        debug!("\tscene builder");
+        self.scene_tx.send(SceneBuilderRequest::SaveScene(config.clone())).unwrap();
+
         debug!("\tresource cache");
         let (resources, deferred) = self.resource_cache.save_capture(&config.root);
 
         info!("\tbackend");
         let backend = PlainRenderBackend {
             default_device_pixel_ratio: self.default_device_pixel_ratio,
             frame_config: self.frame_config.clone(),
             documents: self.documents
@@ -1357,28 +1384,37 @@ impl RenderBackend {
 
         let mut last_scene_id = backend.last_scene_id;
         for (id, view) in backend.documents {
             debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
+            let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
+            let clip_interner = CaptureConfig::deserialize::<ClipDataInterner, _>(root, &clip_interner_name)
+                .expect(&format!("Unable to open {}.ron", clip_interner_name));
+
+            let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
+            let clip_data_store = CaptureConfig::deserialize::<ClipDataStore, _>(root, &clip_data_name)
+                .expect(&format!("Unable to open {}.ron", clip_data_name));
+
             let mut doc = Document {
                 scene: scene.clone(),
                 removed_pipelines: Vec::new(),
                 view: view.clone(),
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
+                clip_data_store,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
 
@@ -1409,16 +1445,17 @@ impl RenderBackend {
                 document_id: id,
                 scene: doc.scene.clone(),
                 view: view.clone(),
                 config: self.frame_config.clone(),
                 output_pipelines: doc.output_pipelines.clone(),
                 font_instances: self.resource_cache.get_font_instances(),
                 scene_id: last_scene_id,
                 build_frame,
+                clip_interner,
             });
 
             self.documents.insert(id, doc);
         }
 
         if !scenes_to_build.is_empty() {
             self.low_priority_scene_tx.send(
                 SceneBuilderRequest::LoadScenes(scenes_to_build)
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -2,17 +2,17 @@
  * 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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets, ImageDescriptor, ImageFormat};
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use border::BorderCacheKey;
 use box_shadow::{BoxShadowCacheKey};
-use clip::{ClipItem, ClipStore, ClipNodeRange};
+use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
 use clip_scroll_tree::SpatialNodeIndex;
 use device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{BorderInstance, ImageSource, RasterizationSpace, UvRectKind};
@@ -427,30 +427,32 @@ impl RenderTask {
     pub fn new_mask(
         outer_rect: DeviceIntRect,
         clip_node_range: ClipNodeRange,
         root_spatial_node_index: SpatialNodeIndex,
         clip_store: &mut ClipStore,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         render_tasks: &mut RenderTaskTree,
+        clip_data_store: &mut ClipDataStore,
     ) -> Self {
         let mut children = Vec::new();
 
         // Step through the clip sources that make up this mask. If we find
         // any box-shadow clip sources, request that image from the render
         // task cache. This allows the blurred box-shadow rect to be cached
         // in the texture cache across frames.
         // TODO(gw): Consider moving this logic outside this function, especially
         //           as we add more clip sources that depend on render tasks.
         // TODO(gw): If this ever shows up in a profile, we could pre-calculate
         //           whether a ClipSources contains any box-shadows and skip
         //           this iteration for the majority of cases.
         for i in 0 .. clip_node_range.count {
-            let (clip_node, _) = clip_store.get_node_from_range_mut(&clip_node_range, i);
+            let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
+            let clip_node = &mut clip_data_store[clip_instance.handle];
             match clip_node.item {
                 ClipItem::BoxShadow(ref mut info) => {
                     let (cache_size, cache_key) = info.cache_key
                         .as_ref()
                         .expect("bug: no cache key set")
                         .clone();
                     let blur_radius_dp = cache_key.blur_radius_dp as f32;
                     let clip_data_address = gpu_cache.get_address(&info.clip_data_handle);
@@ -735,17 +737,17 @@ impl RenderTask {
         };
 
         let (mut target_rect, target_index) = self.get_target_rect();
         // The primitives inside a fixed-location render task
         // are already placed to their corresponding positions,
         // so the shader doesn't need to shift by the origin.
         if let RenderTaskLocation::Fixed(_) = self.location {
             target_rect.origin = DeviceIntPoint::origin();
-        };
+        }
 
         RenderTaskData {
             data: [
                 target_rect.origin.x as f32,
                 target_rect.origin.y as f32,
                 target_rect.size.width as f32,
                 target_rect.size.height as f32,
                 target_index.0 as f32,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,17 +1,20 @@
 /* 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::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult};
 use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch};
 use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint};
 use api::channel::MsgSender;
+#[cfg(feature = "capture")]
+use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
+use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureIdGenerator;
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
@@ -62,16 +65,17 @@ pub struct BuiltTransaction {
     pub document_id: DocumentId,
     pub built_scene: Option<BuiltScene>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
     pub notifications: Vec<NotificationRequest>,
+    pub clip_updates: Option<ClipDataUpdateList>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub build_frame: bool,
     pub render_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
@@ -95,16 +99,17 @@ pub struct LoadScene {
     pub document_id: DocumentId,
     pub scene: Scene,
     pub scene_id: u64,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub font_instances: FontInstanceMap,
     pub view: DocumentView,
     pub config: FrameBuilderConfig,
     pub build_frame: bool,
+    pub clip_interner: ClipDataInterner,
 }
 
 pub struct BuiltScene {
     pub scene: Scene,
     pub frame_builder: FrameBuilder,
     pub clip_scroll_tree: ClipScrollTree,
 }
 
@@ -113,16 +118,18 @@ pub enum SceneBuilderRequest {
     Transaction(Box<Transaction>),
     DeleteDocument(DocumentId),
     WakeUp,
     Flush(MsgSender<()>),
     SetFrameBuilderConfig(FrameBuilderConfig),
     SimulateLongSceneBuild(u32),
     SimulateLongLowPrioritySceneBuild(u32),
     Stop,
+    #[cfg(feature = "capture")]
+    SaveScene(CaptureConfig),
     #[cfg(feature = "replay")]
     LoadScenes(Vec<LoadScene>),
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction(Box<BuiltTransaction>, Option<Sender<SceneSwapResult>>),
     FlushComplete(MsgSender<()>),
@@ -132,18 +139,36 @@ pub enum SceneBuilderResult {
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
     Complete(Sender<()>),
     Aborted,
 }
 
+// A document in the scene builder contains the current scene,
+// as well as a persistent clip interner. This allows clips
+// to be de-duplicated, and persisted in the GPU cache between
+// display lists.
+struct Document {
+    scene: Scene,
+    clip_interner: ClipDataInterner,
+}
+
+impl Document {
+    fn new(scene: Scene) -> Self {
+        Document {
+            scene,
+            clip_interner: ClipDataInterner::new(),
+        }
+    }
+}
+
 pub struct SceneBuilder {
-    documents: FastHashMap<DocumentId, Scene>,
+    documents: FastHashMap<DocumentId, Document>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
     hooks: Option<Box<SceneBuilderHooks + Send>>,
     picture_id_generator: PictureIdGenerator,
     simulate_slow_ms: u32,
 }
@@ -194,16 +219,20 @@ impl SceneBuilder {
                 }
                 Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
                     self.config = cfg;
                 }
                 #[cfg(feature = "replay")]
                 Ok(SceneBuilderRequest::LoadScenes(msg)) => {
                     self.load_scenes(msg);
                 }
+                #[cfg(feature = "capture")]
+                Ok(SceneBuilderRequest::SaveScene(config)) => {
+                    self.save_scene(config);
+                }
                 Ok(SceneBuilderRequest::Stop) => {
                     self.tx.send(SceneBuilderResult::Stopped).unwrap();
                     // We don't need to send a WakeUp to api_tx because we only
                     // get the Stop when the RenderBackend loop is exiting.
                     break;
                 }
                 Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
                     self.simulate_slow_ms = time_ms
@@ -219,74 +248,97 @@ impl SceneBuilder {
             }
         }
 
         if let Some(ref hooks) = self.hooks {
             hooks.deregister();
         }
     }
 
+    #[cfg(feature = "capture")]
+    fn save_scene(&mut self, config: CaptureConfig) {
+        for (id, doc) in &self.documents {
+            let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
+            config.serialize(&doc.clip_interner, clip_interner_name);
+        }
+    }
+
     #[cfg(feature = "replay")]
     fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
-        for item in scenes {
+        for mut item in scenes {
             self.config = item.config;
 
             let scene_build_start_time = precise_time_ns();
 
             let mut built_scene = None;
+            let mut clip_updates = None;
+
             if item.scene.has_root_pipeline() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
                     &item.scene,
                     &mut clip_scroll_tree,
                     item.font_instances,
                     &item.view,
                     &item.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     item.scene_id,
                     &mut self.picture_id_generator,
+                    &mut item.clip_interner,
                 );
 
+                clip_updates = Some(item.clip_interner.end_frame_and_get_pending_updates());
+
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
 
-            self.documents.insert(item.document_id, item.scene);
+            self.documents.insert(
+                item.document_id,
+                Document {
+                    scene: item.scene,
+                    clip_interner: item.clip_interner,
+                },
+            );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 build_frame: true,
                 render_frame: item.build_frame,
                 built_scene,
                 resource_updates: Vec::new(),
                 rasterized_blobs: Vec::new(),
                 blob_rasterizer: None,
                 frame_ops: Vec::new(),
                 removed_pipelines: Vec::new(),
                 notifications: Vec::new(),
                 scene_build_start_time,
                 scene_build_end_time: precise_time_ns(),
+                clip_updates,
             });
 
             self.forward_built_transaction(txn);
         }
     }
 
     /// Do the bulk of the work of the scene builder thread.
     fn process_transaction(&mut self, txn: &mut Transaction) -> Box<BuiltTransaction> {
 
         let scene_build_start_time = precise_time_ns();
 
-        let scene = self.documents.entry(txn.document_id).or_insert(Scene::new());
+        let doc = self.documents
+                      .entry(txn.document_id)
+                      .or_insert(Document::new(Scene::new()));
+        let scene = &mut doc.scene;
 
         for update in txn.display_list_updates.drain(..) {
             scene.set_display_list(
                 update.pipeline_id,
                 update.epoch,
                 update.built_display_list,
                 update.background,
                 update.viewport_size,
@@ -302,33 +354,38 @@ impl SceneBuilder {
             scene.set_root_pipeline_id(id);
         }
 
         for pipeline_id in &txn.removed_pipelines {
             scene.remove_pipeline(*pipeline_id)
         }
 
         let mut built_scene = None;
+        let mut clip_updates = None;
         if scene.has_root_pipeline() {
             if let Some(request) = txn.request_scene_build.take() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
                     &scene,
                     &mut clip_scroll_tree,
                     request.font_instances,
                     &request.view,
                     &request.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     request.scene_id,
                     &mut self.picture_id_generator,
+                    &mut doc.clip_interner,
                 );
 
+                // Retrieve the list of updates from the clip interner.
+                clip_updates = Some(doc.clip_interner.end_frame_and_get_pending_updates());
+
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
         }
 
@@ -355,16 +412,17 @@ impl SceneBuilder {
             render_frame: txn.render_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
             frame_ops: replace(&mut txn.frame_ops, Vec::new()),
             removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
             notifications: replace(&mut txn.notifications, Vec::new()),
+            clip_updates,
             scene_build_start_time,
             scene_build_end_time: precise_time_ns(),
         })
     }
 
     /// Send the result of process_transaction back to the render backend.
     fn forward_built_transaction(&mut self, txn: Box<BuiltTransaction>) {
         // We only need the pipeline info and the result channel if we
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,17 +1,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/. */
 
 use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat};
 use api::{LayoutRect, MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
-use clip::{ClipStore};
+use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, TransformData, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
@@ -43,16 +43,17 @@ pub struct ScrollbarPrimitive {
 pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub use_dual_source_blending: bool,
     pub clip_scroll_tree: &'a ClipScrollTree,
+    pub clip_data_store: &'a ClipDataStore,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
@@ -594,16 +595,17 @@ impl RenderTarget for AlphaRenderTarget 
                     task_address,
                     task_info.clip_node_range,
                     task_info.root_spatial_node_index,
                     ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                     ctx.clip_scroll_tree,
                     transforms,
+                    ctx.clip_data_store,
                 );
             }
             RenderTaskKind::ClipRegion(ref task) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add_clip_region(
                     task_address,
                     task.clip_data_address,
                 );
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -228,24 +228,24 @@ pub struct RectangleDisplayItem {
 pub struct LineDisplayItem {
     pub orientation: LineOrientation, // toggles whether above values are interpreted as x/y values
     pub wavy_line_thickness: f32,
     pub color: ColorF,
     pub style: LineStyle,
 }
 
 #[repr(u8)]
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)]
 pub enum LineOrientation {
     Vertical,
     Horizontal,
 }
 
 #[repr(u8)]
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)]
 pub enum LineStyle {
     Solid,
     Dotted,
     Dashed,
     Wavy,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -735,17 +735,17 @@ impl LocalClip {
                     complex,
                 )
             }
         }
     }
 }
 
 #[repr(C)]
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
 
 impl Not for ClipMode {
     type Output = ClipMode;
 
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -1,95 +1,155 @@
 /* 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/. */
 
+#![deny(missing_docs)]
+
 extern crate serde_bytes;
 
 use font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate};
 use std::sync::Arc;
 use {DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use {IdNamespace, TileOffset, TileSize};
 use euclid::size2;
 
+/// An opaque identifier describing an image registered with WebRender.
+/// This is used as a handle to reference images, and is used as the
+/// hash map key for the actual image storage in the `ResourceCache`.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub IdNamespace, pub u32);
 
 impl ImageKey {
+    /// Placeholder Image key, used to represent None.
     pub const DUMMY: Self = ImageKey(IdNamespace(0), 0);
 
+    /// Mints a new ImageKey. The given ID must be unique.
     pub fn new(namespace: IdNamespace, key: u32) -> Self {
         ImageKey(namespace, key)
     }
 }
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct ExternalImageId(pub u64);
 
+/// Specifies the type of texture target in driver terms.
 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
 pub enum TextureTarget {
+    /// Standard texture. This maps to GL_TEXTURE_2D in OpenGL.
     Default = 0,
+    /// Array texture. This maps to GL_TEXTURE_2D_ARRAY in OpenGL. See
+    /// https://www.khronos.org/opengl/wiki/Array_Texture for background
+    /// on Array textures.
     Array = 1,
+    /// Rectange texture. This maps to GL_TEXTURE_RECTANGLE in OpenGL. This
+    /// is similar to a standard texture, with a few subtle differences
+    /// (no mipmaps, non-power-of-two dimensions, different coordinate space)
+    /// that make it useful for representing the kinds of textures we use
+    /// in WebRender. See https://www.khronos.org/opengl/wiki/Rectangle_Texture
+    /// for background on Rectangle textures.
     Rect = 2,
+    /// External texture. This maps to GL_TEXTURE_EXTERNAL_OES in OpenGL, which
+    /// is an extension. This is used for image formats that OpenGL doesn't
+    /// understand, particularly YUV. See
+    /// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external.txt
     External = 3,
 }
 
+/// Storage format identifier for externally-managed images.
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
 pub enum ExternalImageType {
+    /// The image is texture-backed.
     TextureHandle(TextureTarget),
+    /// The image is heap-allocated by the embedding.
     Buffer,
 }
 
+/// Descriptor for external image resources. See `ImageData`.
 #[repr(C)]
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
 pub struct ExternalImageData {
+    /// The identifier of this external image, provided by the embedding.
     pub id: ExternalImageId,
+    /// For multi-plane images (i.e. YUV), indicates the plane of the
+    /// original image that this struct represents. 0 for single-plane images.
     pub channel_index: u8,
+    /// Storage format identifier.
     pub image_type: ExternalImageType,
 }
 
+/// Specifies the format of a series of pixels, in driver terms.
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageFormat {
+    /// One-channel, byte storage. The "red" doesn't map to the color
+    /// red per se, and is just the way that OpenGL has historically referred
+    /// to single-channel buffers.
     R8 = 1,
+    /// Four channels, byte storage.
     BGRA8 = 3,
+    /// Four channels, float storage.
     RGBAF32 = 4,
+    /// Two-channels, byte storage. Similar to `R8`, this just means
+    /// "two channels" rather than "red and green".
     RG8 = 5,
+    /// Four channels, signed integer storage.
     RGBAI32 = 6,
 }
 
 impl ImageFormat {
+    /// Returns the number of bytes per pixel for the given format.
     pub fn bytes_per_pixel(self) -> u32 {
         match self {
             ImageFormat::R8 => 1,
             ImageFormat::BGRA8 => 4,
             ImageFormat::RGBAF32 => 16,
             ImageFormat::RG8 => 2,
             ImageFormat::RGBAI32 => 16,
         }
     }
 }
 
+/// Metadata (but not storage) describing an image In WebRender.
 #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDescriptor {
+    /// Format of the image data.
     pub format: ImageFormat,
+    /// Width and length of the image data, in pixels.
     pub size: DeviceUintSize,
+    /// The number of bytes from the start of one row to the next. If non-None,
+    /// `compute_stride` will return this value, otherwise it returns
+    /// `width * bpp`. Different source of images have different alignment
+    /// constraints for rows, so the stride isn't always equal to width * bpp.
     pub stride: Option<u32>,
+    /// Offset in bytes of the first pixel of this image in its backing buffer.
+    /// This is used for tiling, wherein WebRender extracts chunks of input images
+    /// in order to cache, manipulate, and render them individually. This offset
+    /// tells the texture upload machinery where to find the bytes to upload for
+    /// this tile. Non-tiled images generally set this to zero.
     pub offset: u32,
+    /// Whether this image is opaque, or has an alpha channel. Avoiding blending
+    /// for opaque surfaces is an important optimization.
     pub is_opaque: bool,
+    /// Whether to allow the driver to automatically generate mipmaps. If images
+    /// are already downscaled appropriately, mipmap generation can be wasted
+    /// work, and cause performance problems on some cards/drivers.
+    ///
+    /// See https://github.com/servo/webrender/pull/2555/
     pub allow_mipmaps: bool,
 }
 
 impl ImageDescriptor {
+    /// Mints a new ImageDescriptor.
     pub fn new(
         width: u32,
         height: u32,
         format: ImageFormat,
         is_opaque: bool,
         allow_mipmaps: bool,
     ) -> Self {
         ImageDescriptor {
@@ -97,36 +157,48 @@ impl ImageDescriptor {
             format,
             stride: None,
             offset: 0,
             is_opaque,
             allow_mipmaps,
         }
     }
 
+    /// Returns the stride, either via an explicit stride stashed on the object
+    /// or by the default computation.
     pub fn compute_stride(&self) -> u32 {
         self.stride.unwrap_or(self.size.width * self.format.bytes_per_pixel())
     }
 
+    /// Computes the total size of the image, in bytes.
     pub fn compute_total_size(&self) -> u32 {
         self.compute_stride() * self.size.height
     }
 
+    /// Computes the bounding rectangle for the image, rooted at (0, 0).
     pub fn full_rect(&self) -> DeviceUintRect {
         DeviceUintRect::new(
             DeviceUintPoint::zero(),
             self.size,
         )
     }
 }
 
+/// Represents the backing store of an arbitrary series of pixels for display by
+/// WebRender. This storage can take several forms.
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
+    /// A simple series of bytes, provided by the embedding and owned by WebRender.
+    /// The format is stored out-of-band, currently in ImageDescriptor.
     Raw(#[serde(with = "serde_image_data_raw")] Arc<Vec<u8>>),
+    /// An series of commands that can be rasterized into an image via an
+    /// embedding-provided callback.
     Blob(#[serde(with = "serde_image_data_raw")] Arc<BlobImageData>),
+    /// An image owned by the embedding, and referenced by WebRender. This may
+    /// take the form of a texture or a heap-allocated buffer.
     External(ExternalImageData),
 }
 
 mod serde_image_data_raw {
     extern crate serde_bytes;
 
     use std::sync::Arc;
     use serde::{Deserializer, Serializer};
@@ -136,53 +208,62 @@ mod serde_image_data_raw {
     }
 
     pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Arc<Vec<u8>>, D::Error> {
         serde_bytes::deserialize(deserializer).map(Arc::new)
     }
 }
 
 impl ImageData {
+    /// Mints a new raw ImageData, taking ownership of the bytes.
     pub fn new(bytes: Vec<u8>) -> Self {
         ImageData::Raw(Arc::new(bytes))
     }
 
+    /// Mints a new raw ImageData from Arc-ed bytes.
     pub fn new_shared(bytes: Arc<Vec<u8>>) -> Self {
         ImageData::Raw(bytes)
     }
 
+    /// Mints a new Blob ImageData.
     pub fn new_blob_image(commands: BlobImageData) -> Self {
         ImageData::Blob(Arc::new(commands))
     }
 
+    /// Returns true if this ImageData represents a blob.
     #[inline]
     pub fn is_blob(&self) -> bool {
         match *self {
             ImageData::Blob(_) => true,
             _ => false,
         }
     }
 
+    /// Returns true if this variant of ImageData should go through the texture
+    /// cache.
     #[inline]
     pub fn uses_texture_cache(&self) -> bool {
         match *self {
             ImageData::External(ref ext_data) => match ext_data.image_type {
                 ExternalImageType::TextureHandle(_) => false,
                 ExternalImageType::Buffer => true,
             },
             ImageData::Blob(_) => true,
             ImageData::Raw(_) => true,
         }
     }
 }
 
 /// The resources exposed by the resource cache available for use by the blob rasterizer.
 pub trait BlobImageResources {
+    /// Returns the `FontTemplate` for the given key.
     fn get_font_data(&self, key: FontKey) -> &FontTemplate;
+    /// Returns the `FontInstanceData` for the given key, if found.
     fn get_font_instance_data(&self, key: FontInstanceKey) -> Option<FontInstanceData>;
+    /// Returns the image metadata and backing store for the given key, if found.
     fn get_image(&self, key: ImageKey) -> Option<(&ImageData, &ImageDescriptor)>;
 }
 
 /// A handler on the render backend that can create rasterizer objects which will
 /// be sent to the scene builder thread to execute the rasterization.
 ///
 /// The handler is responsible for collecting resources, managing/updating blob commands
 /// and creating the rasterizer objects, but isn't expected to do any rasterization itself.
@@ -217,49 +298,77 @@ pub trait BlobImageHandler: Send {
 
     /// A hook to let the handler clean up any state related a given namespace before the
     /// resource cache deletes them.
     fn clear_namespace(&mut self, namespace: IdNamespace);
 }
 
 /// A group of rasterization requests to execute synchronously on the scene builder thread.
 pub trait AsyncBlobImageRasterizer : Send {
+    /// Rasterize the requests.
     fn rasterize(&mut self, requests: &[BlobImageParams]) -> Vec<(BlobImageRequest, BlobImageResult)>;
 }
 
 
+/// Input parameters for the BlobImageRasterizer.
 #[derive(Copy, Clone, Debug)]
 pub struct BlobImageParams {
+    /// A key that identifies the blob image rasterization request.
     pub request: BlobImageRequest,
+    /// Description of the format of the blob's output image.
     pub descriptor: BlobImageDescriptor,
+    /// An optional sub-rectangle of the image to avoid re-rasterizing
+    /// the entire image when only a portion is updated.
+    ///
+    /// If set to None the entire image is rasterized.
     pub dirty_rect: Option<DeviceUintRect>,
 }
 
+/// Backing store for blob image command streams.
 pub type BlobImageData = Vec<u8>;
 
+/// Result type for blob raserization.
 pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
 
+/// Metadata (but not storage) for a blob image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug)]
 pub struct BlobImageDescriptor {
+    /// Size in device pixels of the blob's output image.
     pub size: DeviceUintSize,
+    /// When tiling, offset point in device pixels of this tile in the full
+    /// image. Generally (0, 0) outside of tiling.
     pub offset: DevicePoint,
+    /// Format for the data in the backing store.
     pub format: ImageFormat,
 }
 
+/// Representation of a rasterized blob image. This is obtained by passing
+/// `BlobImageData` to the embedding via the rasterization callback.
 pub struct RasterizedBlobImage {
+    /// The bounding rectangle for this bob image.
     pub rasterized_rect: DeviceUintRect,
+    /// Backing store. The format is stored out of band in `BlobImageDescriptor`.
     pub data: Arc<Vec<u8>>,
 }
 
+/// Error code for when blob rasterization failed.
 #[derive(Clone, Debug)]
 pub enum BlobImageError {
+    /// Out of memory.
     Oom,
-    InvalidKey,
-    InvalidData,
+    /// Other failure, embedding-specified.
     Other(String),
 }
 
+
+
+/// A key identifying blob image rasterization work requested from the blob
+/// image rasterizer.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct BlobImageRequest {
+    /// Unique handle to the image.
     pub key: ImageKey,
+    /// Tiling offset in number of tiles, if applicable.
+    ///
+    /// `None` if the image will not be tiled.
     pub tile: Option<TileOffset>,
 }
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -116,16 +116,17 @@ pub type RasterToLayoutTransform = Typed
 
 pub type PictureToRasterTransform = TypedTransform3D<f32, PicturePixel, RasterPixel>;
 pub type RasterToPictureTransform = TypedTransform3D<f32, RasterPixel, PicturePixel>;
 
 // Fixed position coordinates, to avoid float precision errors.
 pub type LayoutPointAu = TypedPoint2D<Au, LayoutPixel>;
 pub type LayoutRectAu = TypedRect<Au, LayoutPixel>;
 pub type LayoutSizeAu = TypedSize2D<Au, LayoutPixel>;
+pub type LayoutVector2DAu = TypedVector2D<Au, LayoutPixel>;
 
 /// Coordinates in normalized space (between zero and one).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct NormalizedCoordinates;
 
 pub type NormalizedRect = TypedRect<f32, NormalizedCoordinates>;
 
 /// Stores two coordinates in texel space. The coordinates
@@ -149,8 +150,85 @@ impl TexelRect {
 
     pub fn invalid() -> Self {
         TexelRect {
             uv0: DevicePoint::new(-1.0, -1.0),
             uv1: DevicePoint::new(-1.0, -1.0),
         }
     }
 }
+
+const MAX_AU_FLOAT: f32 = 1.0e6;
+
+pub trait AuHelpers<T> {
+    fn from_au(data: T) -> Self;
+    fn to_au(&self) -> T;
+}
+
+impl AuHelpers<LayoutSizeAu> for LayoutSize {
+    fn from_au(size: LayoutSizeAu) -> Self {
+        LayoutSize::new(
+            size.width.to_f32_px(),
+            size.height.to_f32_px(),
+        )
+    }
+
+    fn to_au(&self) -> LayoutSizeAu {
+        let width = self.width.min(2.0 * MAX_AU_FLOAT);
+        let height = self.height.min(2.0 * MAX_AU_FLOAT);
+
+        LayoutSizeAu::new(
+            Au::from_f32_px(width),
+            Au::from_f32_px(height),
+        )
+    }
+}
+
+impl AuHelpers<LayoutVector2DAu> for LayoutVector2D {
+    fn from_au(size: LayoutVector2DAu) -> Self {
+        LayoutVector2D::new(
+            size.x.to_f32_px(),
+            size.y.to_f32_px(),
+        )
+    }
+
+    fn to_au(&self) -> LayoutVector2DAu {
+        LayoutVector2DAu::new(
+            Au::from_f32_px(self.x),
+            Au::from_f32_px(self.y),
+        )
+    }
+}
+
+impl AuHelpers<LayoutPointAu> for LayoutPoint {
+    fn from_au(point: LayoutPointAu) -> Self {
+        LayoutPoint::new(
+            point.x.to_f32_px(),
+            point.y.to_f32_px(),
+        )
+    }
+
+    fn to_au(&self) -> LayoutPointAu {
+        let x = self.x.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT);
+        let y = self.y.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT);
+
+        LayoutPointAu::new(
+            Au::from_f32_px(x),
+            Au::from_f32_px(y),
+        )
+    }
+}
+
+impl AuHelpers<LayoutRectAu> for LayoutRect {
+    fn from_au(rect: LayoutRectAu) -> Self {
+        LayoutRect::new(
+            LayoutPoint::from_au(rect.origin),
+            LayoutSize::from_au(rect.size),
+        )
+    }
+
+    fn to_au(&self) -> LayoutRectAu {
+        LayoutRectAu::new(
+            self.origin.to_au(),
+            self.size.to_au(),
+        )
+    }
+}
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-5b5b4145ecef117acb02fd1f9b72bf02e85c650b
+f17f6a491d6ff3dc3e13e998dda788d8f5856338
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -1540,17 +1540,17 @@ impl YamlFrameReader {
                 Some(make_perspective(perspective_origin, value as f32))
             }
             Some(_) => None,
             _ => yaml["perspective"].as_matrix4d(),
         };
 
         let reference_frame_id = dl.push_reference_frame(info, transform.into(), perspective);
 
-        let numeric_id = yaml["reference-frame-id"].as_i64();
+        let numeric_id = yaml["id"].as_i64();
         if let Some(numeric_id) = numeric_id {
             self.add_clip_id_mapping(numeric_id as u64, reference_frame_id);
         }
 
         reference_frame_id
     }
 
     pub fn handle_reference_frame(
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -630,16 +630,32 @@ static bool
 WasmDebuggingIsSupported(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(wasm::HasSupport(cx) && cx->options().wasmBaseline());
     return true;
 }
 
 static bool
+WasmStreamingIsSupported(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setBoolean(wasm::HasStreamingSupport(cx));
+    return true;
+}
+
+static bool
+WasmCachingIsSupported(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setBoolean(wasm::HasCachingSupport(cx));
+    return true;
+}
+
+static bool
 WasmThreadsSupported(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 #ifdef ENABLE_WASM_THREAD_OPS
     bool isSupported = wasm::HasSupport(cx);
 # ifdef ENABLE_WASM_CRANELIFT
     if (cx->options().wasmForceCranelift())
         isSupported = false;
@@ -5848,16 +5864,24 @@ gc::ZealModeHelpText),
 "wasmIsSupportedByHardware()",
 "  Returns a boolean indicating whether WebAssembly is supported on the current hardware (regardless of whether we've enabled support)."),
 
     JS_FN_HELP("wasmDebuggingIsSupported", WasmDebuggingIsSupported, 0, 0,
 "wasmDebuggingIsSupported()",
 "  Returns a boolean indicating whether WebAssembly debugging is supported on the current device;\n"
 "  returns false also if WebAssembly is not supported"),
 
+    JS_FN_HELP("wasmStreamingIsSupported", WasmStreamingIsSupported, 0, 0,
+"wasmStreamingIsSupported()",
+"  Returns a boolean indicating whether WebAssembly caching is supported by the runtime."),
+
+    JS_FN_HELP("wasmCachingIsSupported", WasmCachingIsSupported, 0, 0,
+"wasmCachingIsSupported()",
+"  Returns a boolean indicating whether WebAssembly caching is supported by the runtime."),
+
     JS_FN_HELP("wasmThreadsSupported", WasmThreadsSupported, 0, 0,
 "wasmThreadsSupported()",
 "  Returns a boolean indicating whether the WebAssembly threads proposal is\n"
 "  supported on the current device."),
 
     JS_FN_HELP("wasmSaturatingTruncationSupported", WasmSaturatingTruncationSupported, 0, 0,
 "wasmSaturatingTruncationSupported()",
 "  Returns a boolean indicating whether the WebAssembly saturating truncates opcodes are\n"
--- a/js/src/jit-test/tests/wasm/bench/wasm_box2d.js
+++ b/js/src/jit-test/tests/wasm/bench/wasm_box2d.js
@@ -1,8 +1,22 @@
+const build = getBuildConfiguration();
+const isSimulator = build['arm-simulator'] ||
+                    build['arm64-simulator'] ||
+                    build['mips32-simulator'] ||
+                    build['mips64-simulator'];
+
+// This test often times out on debug simulators due to the extreme slowdown.
+if (build['debug'] && isSimulator)
+  quit();
+
+// All the glue code is wrapped in a function so it can be executed uncached and
+// cached (via the shared cacheEntry argument).
+function runBox2d(cacheEntry) {
+
 // The Module object: Our interface to the outside world. We import
 // and export values on it, and do the work to get that through
 // closure compiler if necessary. There are various ways Module can be used:
 // 1. Not defined. We create it here
 // 2. A function parameter, function(Module) { ..generated code.. }
 // 3. pre-run appended it, var Module = {}; ..generated code..
 // 4. External script tag defines var Module.
 // We need to do an eval in order to handle the closure compiler
@@ -23,183 +37,31 @@ if (!Module) Module = (typeof Module !==
 // defensive during initialization.
 var moduleOverrides = {};
 for (var key in Module) {
   if (Module.hasOwnProperty(key)) {
     moduleOverrides[key] = Module[key];
   }
 }
 
-// The environment setup code below is customized to use Module.
-// *** Environment setup code ***
-var ENVIRONMENT_IS_WEB = false;
-var ENVIRONMENT_IS_WORKER = false;
-var ENVIRONMENT_IS_NODE = false;
-var ENVIRONMENT_IS_SHELL = false;
-
-// Three configurations we can be running in:
-// 1) We could be the application main() thread running in the main JS UI thread. (ENVIRONMENT_IS_WORKER == false and ENVIRONMENT_IS_PTHREAD == false)
-// 2) We could be the application main() thread proxied to worker. (with Emscripten -s PROXY_TO_WORKER=1) (ENVIRONMENT_IS_WORKER == true, ENVIRONMENT_IS_PTHREAD == false)
-// 3) We could be an application pthread running in a worker. (ENVIRONMENT_IS_WORKER == true and ENVIRONMENT_IS_PTHREAD == true)
+if (!Module['print']) Module['print'] = print;
+if (typeof printErr != 'undefined') Module['printErr'] = print;
 
-if (Module['ENVIRONMENT']) {
-  if (Module['ENVIRONMENT'] === 'WEB') {
-    ENVIRONMENT_IS_WEB = true;
-  } else if (Module['ENVIRONMENT'] === 'WORKER') {
-    ENVIRONMENT_IS_WORKER = true;
-  } else if (Module['ENVIRONMENT'] === 'NODE') {
-    ENVIRONMENT_IS_NODE = true;
-  } else if (Module['ENVIRONMENT'] === 'SHELL') {
-    ENVIRONMENT_IS_SHELL = true;
-  } else {
-    throw new Error('The provided Module[\'ENVIRONMENT\'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.');
-  }
+if (typeof read != 'undefined') {
+Module['read'] = read;
 } else {
-  ENVIRONMENT_IS_WEB = typeof window === 'object';
-  ENVIRONMENT_IS_WORKER = typeof importScripts === 'function';
-  ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof require === 'function' && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER;
-  ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
+Module['read'] = function read() { throw 'no read() available' };
 }
 
-
-if (ENVIRONMENT_IS_NODE) {
-  // Expose functionality in the same simple way that the shells work
-  // Note that we pollute the global namespace here, otherwise we break in node
-  if (!Module['print']) Module['print'] = console.log;
-  if (!Module['printErr']) Module['printErr'] = console.warn;
-
-  var nodeFS;
-  var nodePath;
-
-  Module['read'] = function read(filename, binary) {
-    if (!nodeFS) nodeFS = require('fs');
-    if (!nodePath) nodePath = require('path');
-    filename = nodePath['normalize'](filename);
-    var ret = nodeFS['readFileSync'](filename);
-    return binary ? ret : ret.toString();
-  };
-
-  Module['readBinary'] = function readBinary(filename) {
-    var ret = Module['read'](filename, true);
-    if (!ret.buffer) {
-      ret = new Uint8Array(ret);
-    }
-    assert(ret.buffer);
-    return ret;
-  };
-
-  Module['load'] = function load(f) {
-    globalEval(read(f));
-  };
-
-  if (!Module['thisProgram']) {
-    if (process['argv'].length > 1) {
-      Module['thisProgram'] = process['argv'][1].replace(/\\/g, '/');
-    } else {
-      Module['thisProgram'] = 'unknown-program';
-    }
-  }
-
-  Module['arguments'] = process['argv'].slice(2);
-
-  if (typeof module !== 'undefined') {
-    module['exports'] = Module;
-  }
-
-  process['on']('uncaughtException', function(ex) {
-    // suppress ExitStatus exceptions from showing an error
-    if (!(ex instanceof ExitStatus)) {
-      throw ex;
-    }
-  });
-
-  Module['inspect'] = function () { return '[Emscripten Module object]'; };
+if (typeof scriptArgs != 'undefined') {
+Module['arguments'] = scriptArgs;
+} else if (typeof arguments != 'undefined') {
+Module['arguments'] = arguments;
 }
-else if (ENVIRONMENT_IS_SHELL) {
-  if (!Module['print']) Module['print'] = print;
-  if (typeof printErr != 'undefined') Module['printErr'] = printErr; // not present in v8 or older sm
-
-  if (typeof read != 'undefined') {
-    Module['read'] = read;
-  } else {
-    Module['read'] = function read() { throw 'no read() available' };
-  }
 
-  Module['readBinary'] = function readBinary(f) {
-    if (typeof readbuffer === 'function') {
-      return new Uint8Array(readbuffer(f));
-    }
-    var data = read(f, 'binary');
-    assert(typeof data === 'object');
-    return data;
-  };
-
-  if (typeof scriptArgs != 'undefined') {
-    Module['arguments'] = scriptArgs;
-  } else if (typeof arguments != 'undefined') {
-    Module['arguments'] = arguments;
-  }
-
-}
-else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
-  Module['read'] = function read(url) {
-    var xhr = new XMLHttpRequest();
-    xhr.open('GET', url, false);
-    xhr.send(null);
-    return xhr.responseText;
-  };
-
-  Module['readAsync'] = function readAsync(url, onload, onerror) {
-    var xhr = new XMLHttpRequest();
-    xhr.open('GET', url, true);
-    xhr.responseType = 'arraybuffer';
-    xhr.onload = function xhr_onload() {
-      if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0
-        onload(xhr.response);
-      } else {
-        onerror();
-      }
-    };
-    xhr.onerror = onerror;
-    xhr.send(null);
-  };
-
-  if (typeof arguments != 'undefined') {
-    Module['arguments'] = arguments;
-  }
-
-  if (typeof console !== 'undefined') {
-    if (!Module['print']) Module['print'] = function print(x) {
-      console.log(x);
-    };
-    if (!Module['printErr']) Module['printErr'] = function printErr(x) {
-      console.warn(x);
-    };
-  } else {
-    // Probably a worker, and without console.log. We can do very little here...
-    var TRY_USE_DUMP = false;
-    if (!Module['print']) Module['print'] = (TRY_USE_DUMP && (typeof(dump) !== "undefined") ? (function(x) {
-      dump(x);
-    }) : (function(x) {
-      // self.postMessage(x); // enable this if you want stdout to be sent as messages
-    }));
-  }
-
-  if (ENVIRONMENT_IS_WORKER) {
-    Module['load'] = importScripts;
-  }
-
-  if (typeof Module['setWindowTitle'] === 'undefined') {
-    Module['setWindowTitle'] = function(title) { document.title = title };
-  }
-}
-else {
-  // Unreachable because SHELL is dependant on the others
-  throw 'Unknown runtime environment. Where are we?';
-}
 
 function globalEval(x) {
   eval.call(null, x);
 }
 if (!Module['load'] && Module['read']) {
   Module['load'] = function load(f) {
     globalEval(Module['read'](f));
   };
@@ -1485,19 +1347,17 @@ function integrateWasmJS(Module) {
   // options one by one. Some of them can fail gracefully, and then we can try
   // the next.
 
   // inputs
 
   var method = Module['wasmJSMethod'] || 'native-wasm';
   Module['wasmJSMethod'] = method;
 
-  var wasmTextFile = Module['wasmTextFile'] || 'wasm_box2d.wast';
   var wasmBinaryFile = Module['wasmBinaryFile'] || scriptdir + 'wasm_box2d.wasm';
-  var asmjsCodeFile = Module['asmjsCodeFile'] || 'wasm_box2d.temp.asm.js';
 
   // utilities
 
   var wasmPageSize = 64*1024;
 
   var asm2wasmImports = { // special asm2wasm imports
     "f64-rem": function(x, y) {
       return x % y;
@@ -1585,28 +1445,16 @@ function integrateWasmJS(Module) {
     for (var i in imports) {
       var fixed = i;
       if (fixed[0] == '_') fixed = fixed.substr(1);
       ret[fixed] = imports[i];
     }
     return ret;
   }
 
-  function getBinary() {
-    var binary;
-    if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
-      binary = Module['wasmBinary'];
-      assert(binary, "on the web, we need the wasm binary to be preloaded and set on Module['wasmBinary']. emcc.py will do that for you when generating HTML (but not JS)");
-      binary = new Uint8Array(binary);
-    } else {
-      binary = Module['readBinary'](wasmBinaryFile);
-    }
-    return binary;
-  }
-
   // do-method functions
 
   function doJustAsm(global, env, providedBuffer) {
     // if no Module.asm, or it's the method handler helper (see below), then apply
     // the asmjs
     if (typeof Module['asm'] !== 'function' || Module['asm'] === methodHandler) {
       if (!Module['asmPreload']) {
         // you can load the .asm.js file before this, to avoid this sync xhr and eval
@@ -1645,102 +1493,32 @@ function integrateWasmJS(Module) {
     function receiveInstance(instance) {
       exports = instance.exports;
       if (exports.memory) mergeMemory(exports.memory);
       Module['asm'] = exports;
       Module["usingWasm"] = true;
     }
     Module['printErr']('asynchronously preparing wasm');
     addRunDependency('wasm-instantiate'); // we can't run yet
-    WebAssembly.instantiate(getBinary(), info).then(function(output) {
+
+    (wasmStreamingIsSupported()
+     ? WebAssembly.instantiateStreaming(cacheEntry, info)
+     : WebAssembly.instantiate(cacheEntry.getBuffer(), info))
+    .then(function(output) {
+      if (!cacheEntry.module)
+        cacheEntry.module = output.module;
+
       // receiveInstance() will swap in the exports (to Module.asm) so they can be called
       receiveInstance(output.instance);
       removeRunDependency('wasm-instantiate');
-    }).catch(function(reason) {
+    })
+    .catch(function(reason) {
       Module['printErr']('failed to asynchronously prepare wasm:\n  ' + reason);
     });
     return {}; // no exports yet; we'll fill them in later
-    var instance;
-    try {
-      instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), info)
-    } catch (e) {
-      Module['printErr']('failed to compile wasm module: ' + e);
-      if (e.toString().indexOf('imported Memory with incompatible size') >= 0) {
-        Module['printErr']('Memory size incompatibility issues may be due to changing TOTAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set TOTAL_MEMORY at runtime to something smaller than it was at compile time).');
-      }
-      return false;
-    }
-    receiveInstance(instance);
-    return exports;
-  }
-
-  function doWasmPolyfill(global, env, providedBuffer, method) {
-    if (typeof WasmJS !== 'function') {
-      Module['printErr']('WasmJS not detected - polyfill not bundled?');
-      return false;
-    }
-
-    // Use wasm.js to polyfill and execute code in a wasm interpreter.
-    var wasmJS = WasmJS({});
-
-    // XXX don't be confused. Module here is in the outside program. wasmJS is the inner wasm-js.cpp.
-    wasmJS['outside'] = Module; // Inside wasm-js.cpp, Module['outside'] reaches the outside module.
-
-    // Information for the instance of the module.
-    wasmJS['info'] = info;
-
-    wasmJS['lookupImport'] = lookupImport;
-
-    assert(providedBuffer === Module['buffer']); // we should not even need to pass it as a 3rd arg for wasm, but that's the asm.js way.
-
-    info.global = global;
-    info.env = env;
-
-    // polyfill interpreter expects an ArrayBuffer
-    assert(providedBuffer === Module['buffer']);
-    env['memory'] = providedBuffer;
-    assert(env['memory'] instanceof ArrayBuffer);
-
-    wasmJS['providedTotalMemory'] = Module['buffer'].byteLength;
-
-    // Prepare to generate wasm, using either asm2wasm or s-exprs
-    var code;
-    if (method === 'interpret-binary') {
-      code = getBinary();
-    } else {
-      code = Module['read'](method == 'interpret-asm2wasm' ? asmjsCodeFile : wasmTextFile);
-    }
-    var temp;
-    if (method == 'interpret-asm2wasm') {
-      temp = wasmJS['_malloc'](code.length + 1);
-      wasmJS['writeAsciiToMemory'](code, temp);
-      wasmJS['_load_asm2wasm'](temp);
-    } else if (method === 'interpret-s-expr') {
-      temp = wasmJS['_malloc'](code.length + 1);
-      wasmJS['writeAsciiToMemory'](code, temp);
-      wasmJS['_load_s_expr2wasm'](temp);
-    } else if (method === 'interpret-binary') {
-      temp = wasmJS['_malloc'](code.length);
-      wasmJS['HEAPU8'].set(code, temp);
-      wasmJS['_load_binary2wasm'](temp, code.length);
-    } else {
-      throw 'what? ' + method;
-    }
-    wasmJS['_free'](temp);
-
-    wasmJS['_instantiate'](temp);
-
-    if (Module['newBuffer']) {
-      mergeMemory(Module['newBuffer']);
-      Module['newBuffer'] = null;
-    }
-
-    exports = wasmJS['asmExports'];
-
-    return exports;
   }
 
   // We may have a preloaded value in Module.asm, save it
   Module['asmPreload'] = Module['asm'];
 
   // Memory growth integration code
   Module['reallocBuffer'] = function(size) {
     var PAGE_MULTIPLE = Module["usingWasm"] ? WASM_PAGE_SIZE : ASMJS_PAGE_SIZE; // In wasm, heap size must be a multiple of 64KB. In asm.js, they need to be multiples of 16MB.
@@ -1793,42 +1571,17 @@ function integrateWasmJS(Module) {
 
     if (!env['memoryBase']) {
       env['memoryBase'] = Module['STATIC_BASE']; // tell the memory segments where to place themselves
     }
     if (!env['tableBase']) {
       env['tableBase'] = 0; // table starts at 0 by default, in dynamic linking this will change
     }
 
-    // try the methods. each should return the exports if it succeeded
-
-    var exports;
-    var methods = method.split(',');
-
-    for (var i = 0; i < methods.length; i++) {
-      var curr = methods[i];
-
-      Module['printErr']('trying binaryen method: ' + curr);
-
-      if (curr === 'native-wasm') {
-        if (exports = doNativeWasm(global, env, providedBuffer)) break;
-      } else if (curr === 'asmjs') {
-        if (exports = doJustAsm(global, env, providedBuffer)) break;
-      } else if (curr === 'interpret-asm2wasm' || curr === 'interpret-s-expr' || curr === 'interpret-binary') {
-        if (exports = doWasmPolyfill(global, env, providedBuffer, curr)) break;
-      } else {
-        throw 'bad method: ' + curr;
-      }
-    }
-
-    if (!exports) throw 'no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: https://github.com/kripken/emscripten/wiki/WebAssembly#binaryen-methods';
-
-    Module['printErr']('binaryen method succeeded.');
-
-    return exports;
+    return doNativeWasm(global, env, providedBuffer);
   };
 
   var methodHandler = Module['asm']; // note our method handler, as we may modify Module['asm'] later
 }
 
 integrateWasmJS(Module);
 
 // === Body ===
@@ -2535,21 +2288,16 @@ function copyTempDouble(ptr) {
             if (event.source === window && event.data === emscriptenMainLoopMessageId) {
               event.stopPropagation();
               setImmediates.shift()();
             }
           }
           window.addEventListener("message", Browser_setImmediate_messageHandler, true);
           window['setImmediate'] = function Browser_emulated_setImmediate(func) {
             setImmediates.push(func);
-            if (ENVIRONMENT_IS_WORKER) {
-              if (Module['setImmediates'] === undefined) Module['setImmediates'] = [];
-              Module['setImmediates'].push(func);
-              window.postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js
-            } else window.postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself.
           }
         }
         Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setImmediate() {
           window['setImmediate'](Browser.mainLoop.runner);
         };
         Browser.mainLoop.method = 'immediate';
       }
       return 0;
@@ -2920,22 +2668,17 @@ function copyTempDouble(ptr) {
 Module["requestFullScreen"] = function Module_requestFullScreen(lockPointer, resizeCanvas, vrDevice) { Module.printErr("Module.requestFullScreen is deprecated. Please call Module.requestFullscreen instead."); Module["requestFullScreen"] = Module["requestFullscreen"]; Browser.requestFullScreen(lockPointer, resizeCanvas, vrDevice) };
   Module["requestFullscreen"] = function Module_requestFullscreen(lockPointer, resizeCanvas, vrDevice) { Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice) };
   Module["requestAnimationFrame"] = function Module_requestAnimationFrame(func) { Browser.requestAnimationFrame(func) };
   Module["setCanvasSize"] = function Module_setCanvasSize(width, height, noUpdates) { Browser.setCanvasSize(width, height, noUpdates) };
   Module["pauseMainLoop"] = function Module_pauseMainLoop() { Browser.mainLoop.pause() };
   Module["resumeMainLoop"] = function Module_resumeMainLoop() { Browser.mainLoop.resume() };
   Module["getUserMedia"] = function Module_getUserMedia() { Browser.getUserMedia() }
   Module["createContext"] = function Module_createContext(canvas, useWebGL, setInModule, webGLContextAttributes) { return Browser.createContext(canvas, useWebGL, setInModule, webGLContextAttributes) };
-if (ENVIRONMENT_IS_NODE) {
-    _emscripten_get_now = function _emscripten_get_now_actual() {
-      var t = process['hrtime']();
-      return t[0] * 1e3 + t[1] / 1e6;
-    };
-  } else if (typeof dateNow !== 'undefined') {
+  if (typeof dateNow !== 'undefined') {
     _emscripten_get_now = dateNow;
   } else if (typeof self === 'object' && self['performance'] && typeof self['performance']['now'] === 'function') {
     _emscripten_get_now = function() { return self['performance']['now'](); };
   } else if (typeof performance === 'object' && typeof performance['now'] === 'function') {
     _emscripten_get_now = function() { return performance['now'](); };
   } else {
     _emscripten_get_now = Date.now;
   };
@@ -3124,62 +2867,20 @@ Module['asm'] = asm;
 
 
 if (memoryInitializer) {
   if (typeof Module['locateFile'] === 'function') {
     memoryInitializer = Module['locateFile'](memoryInitializer);
   } else if (Module['memoryInitializerPrefixURL']) {
     memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
   }
-  if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) {
-    var data = Module['readBinary'](memoryInitializer);
-    HEAPU8.set(data, Runtime.GLOBAL_BASE);
-  } else {
-    addRunDependency('memory initializer');
-    var applyMemoryInitializer = function(data) {
-      if (data.byteLength) data = new Uint8Array(data);
-      HEAPU8.set(data, Runtime.GLOBAL_BASE);
-      // Delete the typed array that contains the large blob of the memory initializer request response so that
-      // we won't keep unnecessary memory lying around. However, keep the XHR object itself alive so that e.g.
-      // its .status field can still be accessed later.
-      if (Module['memoryInitializerRequest']) delete Module['memoryInitializerRequest'].response;
-      removeRunDependency('memory initializer');
-    }
-    function doBrowserLoad() {
-      Module['readAsync'](memoryInitializer, applyMemoryInitializer, function() {
-        throw 'could not load memory initializer ' + memoryInitializer;
-      });
-    }
-    if (Module['memoryInitializerRequest']) {
-      // a network request has already been created, just use that
-      function useRequest() {
-        var request = Module['memoryInitializerRequest'];
-        if (request.status !== 200 && request.status !== 0) {
-          // If you see this warning, the issue may be that you are using locateFile or memoryInitializerPrefixURL, and defining them in JS. That
-          // means that the HTML file doesn't know about them, and when it tries to create the mem init request early, does it to the wrong place.
-          // Look in your browser's devtools network console to see what's going on.
-          console.warn('a problem seems to have happened with Module.memoryInitializerRequest, status: ' + request.status + ', retrying ' + memoryInitializer);
-          doBrowserLoad();
-          return;
-        }
-        applyMemoryInitializer(request.response);
-      }
-      if (Module['memoryInitializerRequest'].response) {
-        setTimeout(useRequest, 0); // it's already here; but, apply it asynchronously
-      } else {
-        Module['memoryInitializerRequest'].addEventListener('load', useRequest); // wait for it
-      }
-    } else {
-      // fetch it from the network ourselves
-      doBrowserLoad();
-    }
-  }
+  var data = Module['readBinary'](memoryInitializer);
+  HEAPU8.set(data, Runtime.GLOBAL_BASE);
 }
 
-
 function ExitStatus(status) {
   this.name = "ExitStatus";
   this.message = "Program terminated with exit(" + status + ")";
   this.status = status;
 };
 ExitStatus.prototype = new Error();
 ExitStatus.prototype.constructor = ExitStatus;
 
@@ -3287,38 +2988,16 @@ function run(args) {
     }, 1);
   } else {
     doRun();
   }
 }
 Module['run'] = Module.run = run;
 
 function exit(status, implicit) {
-  if (implicit && Module['noExitRuntime']) {
-    return;
-  }
-
-  if (Module['noExitRuntime']) {
-  } else {
-
-    ABORT = true;
-    EXITSTATUS = status;
-    STACKTOP = initialStackTop;
-
-    exitRuntime();
-
-    if (Module['onExit']) Module['onExit'](status);
-  }
-
-  if (ENVIRONMENT_IS_NODE) {
-    process['exit'](status);
-  } else if (ENVIRONMENT_IS_SHELL && typeof quit === 'function') {
-    quit(status);
-  }
-  // if we reach here, we must throw an exception to halt the current execution
   throw new ExitStatus(status);
 }
 Module['exit'] = Module.exit = exit;
 
 var abortDecorators = [];
 
 function abort(what) {
   if (what !== undefined) {
@@ -3356,20 +3035,21 @@ if (Module['preInit']) {
 // shouldRunNow refers to calling main(), not run().
 var shouldRunNow = true;
 if (Module['noInitialRun']) {
   shouldRunNow = false;
 }
 
 
 run();
-
-// {{POST_RUN_ADDITIONS}}
+drainJobQueue();
 
+};  // End of function wrapping the whole top-level Emscripten glue code
 
-
+setBufferStreamParams(/* delayMillis = */ 1, /* chunkSize = */ 1000);
+const cacheEntry = streamCacheEntry(os.file.readFile(scriptdir + 'wasm_box2d.wasm', 'binary'));
 
-
-// {{MODULE_ADDITIONS}}
+runBox2d(cacheEntry);
 
-
+while (!wasmHasTier2CompilationCompleted(cacheEntry.module)) sleep(1);
+assertEq(cacheEntry.cached, wasmCachingIsSupported());
 
-//# sourceMappingURL=wasm_box2d.js.map
+runBox2d(cacheEntry);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/caching.js
@@ -0,0 +1,60 @@
+if (!wasmStreamingIsSupported())
+    quit();
+
+const {Module, Instance, compileStreaming} = WebAssembly;
+
+function testCached(code, imports, test) {
+    if (typeof code === 'string')
+        code = wasmTextToBinary(code);
+
+    let success = false;
+    let cache = streamCacheEntry(code);
+    assertEq(cache.cached, false);
+    compileStreaming(cache)
+    .then(m => {
+         test(new Instance(m, imports));
+         while (!wasmHasTier2CompilationCompleted(m)) {
+            sleep(1);
+         }
+         assertEq(cache.cached, wasmCachingIsSupported());
+         return compileStreaming(cache);
+     })
+     .then(m => {
+         test(new Instance(m, imports));
+         assertEq(cache.cached, wasmCachingIsSupported());
+         success = true;
+     })
+     .catch(err => { print(String(err) + " at:\n" + err.stack) });
+
+     drainJobQueue();
+     assertEq(success, true);
+}
+
+testCached(
+    `(module
+       (func (export "run") (result i32)
+         (i32.const 42)))`,
+    undefined,
+    i => { assertEq(i.exports.run(), 42); }
+);
+
+testCached(
+    `(module
+       (type $T (func (result i32)))
+       (func $t1 (import "" "t1") (type $T))
+       (func $t2 (import "" "t2") (type $T))
+       (func $t3 (type $T) (i32.const 30))
+       (func $t4 (type $T) (i32.const 40))
+       (table anyfunc (elem $t1 $t2 $t3 $t4))
+       (func (export "run") (param i32) (result i32)
+         (call_indirect $T (get_local 0))))`,
+    {'':{ t1() { return 10 }, t2() { return 20 } }},
+    i => {
+        assertEq(i.exports.run(0), 10);
+        assertEq(i.exports.run(1), 20);
+        assertEq(i.exports.run(2), 30);
+        assertEq(i.exports.run(3), 40);
+    }
+);
+
+// Note: a fuller behavioral test of caching is in bench/wasm_box2d.js.
--- a/js/src/jit-test/tests/wasm/streaming.js
+++ b/js/src/jit-test/tests/wasm/streaming.js
@@ -1,14 +1,10 @@
-try {
-    WebAssembly.compileStreaming();
-} catch (err) {
-    assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true);
+if (!wasmStreamingIsSupported())
     quit();
-}
 
 function testInstantiate(source, importObj, exportName, expectedValue) {
     var result;
     WebAssembly.instantiateStreaming(code, importObj).then(r => { result = r });
     drainJobQueue();
     assertEq(result !== undefined, true);
     assertEq(result.module instanceof WebAssembly.Module, true);
     assertEq(result.instance instanceof WebAssembly.Instance, true);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4922,16 +4922,22 @@ JS::InitDispatchToEventLoop(JSContext* c
 }
 
 JS_PUBLIC_API(void)
 JS::ShutdownAsyncTasks(JSContext* cx)
 {
     cx->runtime()->offThreadPromiseState.ref().shutdown(cx);
 }
 
+JS_PUBLIC_API(bool)
+JS::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId)
+{
+    return wasm::GetOptimizedEncodingBuildId(buildId);
+}
+
 JS_PUBLIC_API(void)
 JS::InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback callback)
 {
     cx->runtime()->consumeStreamCallback = callback;
 }
 
 JS_PUBLIC_API(void)
 JS_RequestInterruptCallback(JSContext* cx)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3414,29 +3414,65 @@ class JS_PUBLIC_API(Dispatchable)
  */
 
 typedef bool
 (*DispatchToEventLoopCallback)(void* closure, Dispatchable* dispatchable);
 
 extern JS_PUBLIC_API(void)
 InitDispatchToEventLoop(JSContext* cx, DispatchToEventLoopCallback callback, void* closure);
 
+/* Vector of characters used for holding build ids. */
+
+typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
+
 /**
  * The ConsumeStreamCallback is called from an active JSContext, passing a
  * StreamConsumer that wishes to consume the given host object as a stream of
  * bytes with the given MIME type. On failure, the embedding must report the
  * appropriate error on 'cx'. On success, the embedding must call
  * consumer->consumeChunk() repeatedly on any thread until exactly one of:
  *  - consumeChunk() returns false
  *  - the embedding calls consumer->streamClosed()
  * before JS_DestroyContext(cx) or JS::ShutdownAsyncTasks(cx) is called.
  *
  * Note: consumeChunk() and streamClosed() may be called synchronously by
  * ConsumeStreamCallback.
- */
+ *
+ * When streamClosed() is called, the embedding may optionally pass an
+ * OptimizedEncodingListener*, indicating that there is a cache entry associated
+ * with this stream that can store an optimized encoding of the bytes that were
+ * just streamed at some point in the future by having SpiderMonkey call
+ * storeOptimizedEncoding(). Until the optimized encoding is ready, SpiderMonkey
+ * will hold an outstanding refcount to keep the listener alive.
+ *
+ * After storeOptimizedEncoding() is called, on cache hit, the embedding
+ * may call consumeOptimizedEncoding() instead of consumeChunk()/streamClosed().
+ * The embedding must ensure that the GetOptimizedEncodingBuildId() at the time
+ * when an optimized encoding is created is the same as when it is later
+ * consumed.
+ */
+
+class OptimizedEncodingListener
+{
+  protected:
+    virtual ~OptimizedEncodingListener() {}
+
+  public:
+    // SpiderMonkey will hold an outstanding reference count as long as it holds
+    // a pointer to OptimizedEncodingListener.
+    virtual MozExternalRefCountType MOZ_XPCOM_ABI AddRef() = 0;
+    virtual MozExternalRefCountType MOZ_XPCOM_ABI Release() = 0;
+
+    // SpiderMonkey may optionally call storeOptimizedEncoding() after it has
+    // finished processing a streamed resource.
+    virtual void storeOptimizedEncoding(const uint8_t* bytes, size_t length) = 0;
+};
+
+extern JS_PUBLIC_API(bool)
+GetOptimizedEncodingBuildId(BuildIdCharVector* buildId);
 
 class JS_PUBLIC_API(StreamConsumer)
 {
   protected:
     // AsyncStreamConsumers are created and destroyed by SpiderMonkey.
     StreamConsumer() = default;
     virtual ~StreamConsumer() = default;
 
@@ -3444,21 +3480,27 @@ class JS_PUBLIC_API(StreamConsumer)
     // Called by the embedding as each chunk of bytes becomes available.
     // If this function returns 'false', the stream must drop all pointers to
     // this StreamConsumer.
     virtual bool consumeChunk(const uint8_t* begin, size_t length) = 0;
 
     // Called by the embedding when the stream is closed according to the
     // contract described above.
     enum CloseReason { EndOfFile, Error };
-    virtual void streamClosed(CloseReason reason) = 0;
+    virtual void streamClosed(CloseReason reason,
+                              OptimizedEncodingListener* listener = nullptr) = 0;
+
+    // Called by the embedding *instead of* consumeChunk()/streamClosed() if an
+    // optimized encoding is available from a previous streaming of the same
+    // contents with the same optimized build id.
+    virtual void consumeOptimizedEncoding(const uint8_t* begin, size_t length) = 0;
 
     // Provides optional stream attributes such as base or source mapping URLs.
-    // Necessarily called before consumeChunk() or streamClosed(). The caller
-    // retains ownership of the given strings.
+    // Necessarily called before consumeChunk(), streamClosed() or
+    // consumeOptimizedEncoding(). The caller retains ownership of the strings.
     virtual void noteResponseURLs(const char* maybeUrl, const char* maybeSourceMapUrl) = 0;
 };
 
 enum class MimeType { Wasm };
 
 typedef bool
 (*ConsumeStreamCallback)(JSContext* cx, JS::HandleObject obj, MimeType mimeType,
                          StreamConsumer* consumer);
@@ -4631,18 +4673,16 @@ SetAsmJSCacheOps(JSContext* cx, const As
 /**
  * Return the buildId (represented as a sequence of characters) associated with
  * the currently-executing build. If the JS engine is embedded such that a
  * single cache entry can be observed by different compiled versions of the JS
  * engine, it is critical that the buildId shall change for each new build of
  * the JS engine.
  */
 
-typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
-
 typedef bool
 (* BuildIdOp)(BuildIdCharVector* buildId);
 
 extern JS_PUBLIC_API(void)
 SetProcessBuildIdOp(BuildIdOp buildIdOp);
 
 /**
  * The WasmModule interface allows the embedding to hold a reference to the
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/GuardObjects.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
+#include "mozilla/Variant.h"
 
 #include <chrono>
 #ifdef JS_POSIX_NSPR
 # include <dlfcn.h>
 #endif
 #ifdef XP_WIN
 # include <direct.h>
 # include <process.h>
@@ -134,23 +135,25 @@ using namespace js::shell;
 
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
 
 using js::shell::RCFile;
 
 using mozilla::ArrayEqual;
 using mozilla::ArrayLength;
+using mozilla::AsVariant;
 using mozilla::Atomic;
 using mozilla::MakeScopeExit;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::NumberEqualsInt32;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
+using mozilla::Variant;
 
 // Avoid an unnecessary NSPR dependency on Linux and OS X just for the shell.
 #ifdef JS_POSIX_NSPR
 
 enum PRLibSpecType { PR_LibSpec_Pathname };
 
 struct PRLibSpec {
     PRLibSpecType type;
@@ -6814,24 +6817,196 @@ SetSharedObject(JSContext* cx, unsigned 
         mbx->tag = tag;
         mbx->val = value;
     }
 
     args.rval().setUndefined();
     return true;
 }
 
+typedef Vector<uint8_t, 0, SystemAllocPolicy> Uint8Vector;
+
+class StreamCacheEntry : public AtomicRefCounted<StreamCacheEntry>,
+                         public JS::OptimizedEncodingListener
+{
+    typedef AtomicRefCounted<StreamCacheEntry> AtomicBase;
+
+    Uint8Vector bytes_;
+    ExclusiveData<Uint8Vector> optimized_;
+
+  public:
+    explicit StreamCacheEntry(Uint8Vector&& original)
+      : bytes_(std::move(original)),
+        optimized_(mutexid::ShellStreamCacheEntryState)
+    {}
+
+    // Implement JS::OptimizedEncodingListener:
+
+    MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override {
+        AtomicBase::AddRef();
+        return 1;  // unused
+    }
+    MozExternalRefCountType MOZ_XPCOM_ABI Release() override {
+        AtomicBase::Release();
+        return 0;  // unused
+    }
+
+    const Uint8Vector& bytes() const {
+        return bytes_;
+    }
+
+    void storeOptimizedEncoding(const uint8_t* srcBytes, size_t srcLength) override {
+        MOZ_ASSERT(srcLength > 0);
+
+        // Tolerate races since a single StreamCacheEntry object can be used as
+        // the source of multiple streaming compilations.
+        auto dstBytes = optimized_.lock();
+        if (dstBytes->length() > 0) {
+            return;
+        }
+
+        if (!dstBytes->resize(srcLength)) {
+            return;
+        }
+        memcpy(dstBytes->begin(), srcBytes, srcLength);
+    }
+
+    bool hasOptimizedEncoding() const {
+        return !optimized_.lock()->empty();
+    }
+    const Uint8Vector& optimizedEncoding() const {
+        return optimized_.lock().get();
+    }
+};
+
+typedef RefPtr<StreamCacheEntry> StreamCacheEntryPtr;
+
+class StreamCacheEntryObject : public NativeObject
+{
+    static const unsigned CACHE_ENTRY_SLOT = 0;
+    static const ClassOps classOps_;
+    static const JSPropertySpec properties_;
+
+    static void finalize(FreeOp*, JSObject* obj) {
+        obj->as<StreamCacheEntryObject>().cache().Release();
+    }
+
+    static bool cachedGetter(JSContext* cx, unsigned argc, Value* vp) {
+        CallArgs args = CallArgsFromVp(argc, vp);
+        if (!args.thisv().isObject() || !args.thisv().toObject().is<StreamCacheEntryObject>()) {
+            return false;
+        }
+
+        StreamCacheEntryObject& obj = args.thisv().toObject().as<StreamCacheEntryObject>();
+        args.rval().setBoolean(obj.cache().hasOptimizedEncoding());
+        return true;
+    }
+    static bool getBuffer(JSContext* cx, unsigned argc, Value* vp) {
+        CallArgs args = CallArgsFromVp(argc, vp);
+        if (!args.thisv().isObject() || !args.thisv().toObject().is<StreamCacheEntryObject>()) {
+            return false;
+        }
+
+        auto& bytes = args.thisv().toObject().as<StreamCacheEntryObject>().cache().bytes();
+        RootedArrayBufferObject buffer(cx, ArrayBufferObject::create(cx, bytes.length()));
+        if (!buffer) {
+            return false;
+        }
+
+        memcpy(buffer->dataPointer(), bytes.begin(), bytes.length());
+
+        args.rval().setObject(*buffer);
+        return true;
+    }
+
+  public:
+    static const unsigned RESERVED_SLOTS = 1;
+    static const Class class_;
+    static const JSPropertySpec properties[];
+
+    static bool construct(JSContext* cx, unsigned argc, Value* vp) {
+        CallArgs args = CallArgsFromVp(argc, vp);
+        if (!args.requireAtLeast(cx, "streamCacheEntry", 1)) {
+            return false;
+        }
+
+        SharedMem<uint8_t*> ptr;
+        size_t numBytes;
+        if (!args[0].isObject() || !IsBufferSource(&args[0].toObject(), &ptr, &numBytes)) {
+            RootedObject callee(cx, &args.callee());
+            ReportUsageErrorASCII(cx, callee, "Argument must be an ArrayBuffer");
+            return false;
+        }
+
+        Uint8Vector bytes;
+        if (!bytes.resize(numBytes)) {
+            return false;
+        }
+
+        memcpy(bytes.begin(), ptr.unwrap(), numBytes);
+
+        RefPtr<StreamCacheEntry> cache = cx->new_<StreamCacheEntry>(std::move(bytes));
+        if (!cache) {
+            return false;
+        }
+
+        RootedNativeObject obj(cx, NewObjectWithGivenProto<StreamCacheEntryObject>(cx, nullptr));
+        if (!obj) {
+            return false;
+        }
+        obj->initReservedSlot(CACHE_ENTRY_SLOT, PrivateValue(cache.forget().take()));
+
+        if (!JS_DefineProperty(cx, obj, "cached", cachedGetter, nullptr, 0)) {
+            return false;
+        }
+        if (!JS_DefineFunction(cx, obj, "getBuffer", getBuffer, 0, 0)) {
+            return false;
+        }
+
+        args.rval().setObject(*obj);
+        return true;
+    }
+
+    StreamCacheEntry& cache() const {
+        return *(StreamCacheEntry*)getReservedSlot(CACHE_ENTRY_SLOT).toPrivate();
+    }
+};
+
+const ClassOps StreamCacheEntryObject::classOps_ =
+{
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* enumerate */
+    nullptr, /* newEnumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    StreamCacheEntryObject::finalize
+};
+
+const Class StreamCacheEntryObject::class_ =
+{
+    "StreamCacheEntryObject",
+    JSCLASS_HAS_RESERVED_SLOTS(StreamCacheEntryObject::RESERVED_SLOTS) |
+    JSCLASS_BACKGROUND_FINALIZE,
+    &StreamCacheEntryObject::classOps_
+};
+
 struct BufferStreamJob
 {
-    Vector<uint8_t, 0, SystemAllocPolicy> bytes;
+    Variant<Uint8Vector, StreamCacheEntryPtr> source;
     Thread thread;
     JS::StreamConsumer* consumer;
 
-    explicit BufferStreamJob(JS::StreamConsumer* consumer)
-      : consumer(consumer)
+    BufferStreamJob(Uint8Vector&& source, JS::StreamConsumer* consumer)
+      : source(AsVariant<Uint8Vector>(std::move(source))),
+        consumer(consumer)
+    {}
+    BufferStreamJob(StreamCacheEntry& source, JS::StreamConsumer* consumer)
+      : source(AsVariant<StreamCacheEntryPtr>(&source)),
+        consumer(consumer)
     {}
 };
 
 struct BufferStreamState
 {
     Vector<UniquePtr<BufferStreamJob>, 0, SystemAllocPolicy> jobs;
     size_t delayMillis;
     size_t chunkSize;
@@ -6849,22 +7024,41 @@ struct BufferStreamState
     }
 };
 
 static ExclusiveWaitableData<BufferStreamState>* bufferStreamState;
 
 static void
 BufferStreamMain(BufferStreamJob* job)
 {
-    const uint8_t* const bytes = job->bytes.begin();
-
-    size_t byteOffset = 0;
+    const uint8_t* bytes;
+    size_t byteLength;
+    JS::OptimizedEncodingListener* listener;
+    if (job->source.is<StreamCacheEntryPtr>()) {
+        StreamCacheEntry& cache = *job->source.as<StreamCacheEntryPtr>();
+        if (cache.hasOptimizedEncoding()) {
+            const Uint8Vector& optimized = cache.optimizedEncoding();
+            job->consumer->consumeOptimizedEncoding(optimized.begin(), optimized.length());
+            goto done;
+        }
+
+        bytes = cache.bytes().begin();
+        byteLength = cache.bytes().length();
+        listener = &cache;
+    } else {
+        bytes = job->source.as<Uint8Vector>().begin();
+        byteLength = job->source.as<Uint8Vector>().length();
+        listener = nullptr;
+    }
+
+    size_t byteOffset;
+    byteOffset = 0;
     while (true) {
-        if (byteOffset == job->bytes.length()) {
-            job->consumer->streamClosed(JS::StreamConsumer::EndOfFile);
+        if (byteOffset == byteLength) {
+            job->consumer->streamClosed(JS::StreamConsumer::EndOfFile, listener);
             break;
         }
 
         bool shutdown;
         size_t delayMillis;
         size_t chunkSize;
         {
             auto state = bufferStreamState->lock();
@@ -6875,25 +7069,26 @@ BufferStreamMain(BufferStreamJob* job)
 
         if (shutdown) {
             job->consumer->streamClosed(JS::StreamConsumer::Error);
             break;
         }
 
         std::this_thread::sleep_for(std::chrono::milliseconds(delayMillis));
 
-        chunkSize = Min(chunkSize, job->bytes.length() - byteOffset);
+        chunkSize = Min(chunkSize, byteLength - byteOffset);
 
         if (!job->consumer->consumeChunk(bytes + byteOffset, chunkSize)) {
             break;
         }
 
         byteOffset += chunkSize;
     }
 
+  done:
     auto state = bufferStreamState->lock();
     size_t jobIndex = 0;
     while (state->jobs[jobIndex].get() != job) {
         jobIndex++;
     }
     job->thread.detach();  // quiet assert in ~Thread() called by erase().
     state->jobs.erase(state->jobs.begin() + jobIndex);
     if (state->jobs.empty()) {
@@ -6944,35 +7139,40 @@ ConsumeBufferSource(JSContext* cx, JS::H
         consumer->noteResponseURLs(urlStr
                                    ? reinterpret_cast<const char*>(urlStr->latin1Chars(nogc))
                                    : nullptr,
                                    mapUrlStr
                                    ? reinterpret_cast<const char*>(mapUrlStr->latin1Chars(nogc))
                                    : nullptr);
     }
 
+    UniquePtr<BufferStreamJob> job;
+
     SharedMem<uint8_t*> dataPointer;
     size_t byteLength;
-    if (!IsBufferSource(obj, &dataPointer, &byteLength)) {
-        JS_ReportErrorASCII(cx, "shell streaming consumes a buffer source (buffer or view)");
-        return false;
-    }
-
-    auto job = cx->make_unique<BufferStreamJob>(consumer);
+    if (IsBufferSource(obj, &dataPointer, &byteLength)) {
+        Uint8Vector bytes;
+        if (!bytes.resize(byteLength)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        memcpy(bytes.begin(), dataPointer.unwrap(), byteLength);
+        job = cx->make_unique<BufferStreamJob>(std::move(bytes), consumer);
+    } else if (obj->is<StreamCacheEntryObject>()) {
+        job = cx->make_unique<BufferStreamJob>(obj->as<StreamCacheEntryObject>().cache(), consumer);
+    } else {
+        JS_ReportErrorASCII(cx, "shell streaming consumes a buffer source (buffer or view) "
+                                "or StreamCacheEntryObject");
+        return false;
+    }
     if (!job) {
         return false;
     }
 
-    if (!job->bytes.resize(byteLength)) {
-        JS_ReportOutOfMemory(cx);
-        return false;
-    }
-
-    memcpy(job->bytes.begin(), dataPointer.unwrap(), byteLength);
-
     BufferStreamJob* jobPtr = job.get();
 
     {
         auto state = bufferStreamState->lock();
         MOZ_ASSERT(!state->shutdown);
         if (!state->jobs.append(std::move(job))) {
             JS_ReportOutOfMemory(cx);
             return false;
@@ -8278,16 +8478,22 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
 
     JS_FN_HELP("cacheEntry", CacheEntry, 1, 0,
 "cacheEntry(code)",
 "  Return a new opaque object which emulates a cache entry of a script.  This\n"
 "  object encapsulates the code and its cached content. The cache entry is filled\n"
 "  and read by the \"evaluate\" function by using it in place of the source, and\n"
 "  by setting \"saveBytecode\" and \"loadBytecode\" options."),
 
+    JS_FN_HELP("streamCacheEntry", StreamCacheEntryObject::construct, 1, 0,
+"streamCacheEntry(buffer)",
+"  Create a shell-only object that holds wasm bytecode and can be streaming-\n"
+"  compiled and cached by WebAssembly.{compile,instantiate}Streaming(). On a\n"
+"  second compilation of the same cache entry, the cached code will be used."),
+
     JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
 "printProfilerEvents()",
 "  Register a callback with the profiler that prints javascript profiler events\n"
 "  to stderr.  Callback is only registered if profiling is enabled."),
 
     JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
 "enableSingleStepProfiling()",
 "  This function will fail on platforms that don't support single-step profiling\n"
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -30,33 +30,34 @@
                                       \
   _(GCLock,                      400) \
                                       \
   _(SharedImmutableStringsCache, 500) \
   _(FutexThread,                 500) \
   _(GeckoProfilerStrings,        500) \
   _(ProtectedRegionTree,         500) \
   _(ShellOffThreadState,         500) \
+  _(ShellStreamCacheEntryState,  500) \
   _(SimulatorCacheLock,          500) \
   _(Arm64SimulatorLock,          500) \
   _(IonSpewer,                   500) \
   _(PerfSpewer,                  500) \
   _(CacheIRSpewer,               500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(ProcessExecutableRegion,     500) \
   _(OffThreadPromiseState,       500) \
   _(BufferStreamState,           500) \
   _(SharedArrayGrow,             500) \
   _(RuntimeScriptData,           500) \
   _(WasmFuncTypeIdSet,           500) \
   _(WasmCodeProfilingLabels,     500) \
   _(WasmCompileTaskState,        500) \
-  _(WasmCodeStreamEnd,           500) \
-  _(WasmTailBytesPtr,            500) \
+  _(WasmCodeBytesEnd,            500) \
+  _(WasmStreamEnd,               500) \
   _(WasmStreamStatus,            500) \
   _(WasmRuntimeInstances,        500) \
                                       \
   _(IcuTimeZoneStateMutex,       600) \
   _(ThreadId,                    600) \
   _(WasmCodeSegmentMap,          600) \
   _(WasmDeferredValidation,      600) \
   _(TraceLoggerGraphState,       600) \
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -2215,17 +2215,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
                 return nullptr;
             }
         }
 
         if (!mg.finishFuncDefs()) {
             return nullptr;
         }
 
-        return mg.finishModule(*bytes, linkData);
+        return mg.finishModule(*bytes, nullptr, linkData);
     }
 };
 
 /*****************************************************************************/
 // Numeric literal utilities
 
 static bool
 IsNumericNonFloatLiteral(ParseNode* pn)
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -582,25 +582,25 @@ wasm::CompileTier2(const CompileArgs& ar
 
     // The caller doesn't care about success or failure; only that compilation
     // is inactive, so there is no success to return here.
 }
 
 class StreamingDecoder
 {
     Decoder d_;
-    const ExclusiveStreamEnd& streamEnd_;
+    const ExclusiveBytesPtr& codeBytesEnd_;
     const Atomic<bool>& cancelled_;
 
   public:
     StreamingDecoder(const ModuleEnvironment& env, const Bytes& begin,
-                     const ExclusiveStreamEnd& streamEnd, const Atomic<bool>& cancelled,
+                     const ExclusiveBytesPtr& codeBytesEnd, const Atomic<bool>& cancelled,
                      UniqueChars* error, UniqueCharsVector* warnings)
       : d_(begin, env.codeSection->start, error, warnings),
-        streamEnd_(streamEnd),
+        codeBytesEnd_(codeBytesEnd),
         cancelled_(cancelled)
     {}
 
     bool fail(const char* msg) {
         return d_.fail(msg);
     }
 
     bool done() const {
@@ -609,22 +609,22 @@ class StreamingDecoder
 
     size_t currentOffset() const {
         return d_.currentOffset();
     }
 
     bool waitForBytes(size_t numBytes) {
         numBytes = Min(numBytes, d_.bytesRemain());
         const uint8_t* requiredEnd = d_.currentPosition() + numBytes;
-        auto streamEnd = streamEnd_.lock();
-        while (streamEnd < requiredEnd) {
+        auto codeBytesEnd = codeBytesEnd_.lock();
+        while (codeBytesEnd < requiredEnd) {
             if (cancelled_) {
                 return false;
             }
-            streamEnd.wait();
+            codeBytesEnd.wait();
         }
         return true;
     }
 
     bool readVarU32(uint32_t* u32) {
         return waitForBytes(MaxVarU32DecodedBytes) &&
                d_.readVarU32(u32);
     }
@@ -668,18 +668,18 @@ CreateBytecode(const Bytes& env, const B
 
     return bytecode;
 }
 
 SharedModule
 wasm::CompileStreaming(const CompileArgs& args,
                        const Bytes& envBytes,
                        const Bytes& codeBytes,
-                       const ExclusiveStreamEnd& codeStreamEnd,
-                       const ExclusiveTailBytesPtr& tailBytesPtr,
+                       const ExclusiveBytesPtr& codeBytesEnd,
+                       const ExclusiveStreamEndData& exclusiveStreamEnd,
                        const Atomic<bool>& cancelled,
                        UniqueChars* error,
                        UniqueCharsVector* warnings)
 {
     MOZ_ASSERT(wasm::HaveSignalHandlers());
 
     CompilerEnvironment compilerEnv(args);
     Maybe<ModuleEnvironment> env;
@@ -699,46 +699,47 @@ wasm::CompileStreaming(const CompileArgs
 
     ModuleGenerator mg(args, env.ptr(), &cancelled, error);
     if (!mg.init()) {
         return nullptr;
     }
 
     {
         MOZ_ASSERT(env->codeSection->size == codeBytes.length());
-        StreamingDecoder d(*env, codeBytes, codeStreamEnd, cancelled, error, warnings);
+        StreamingDecoder d(*env, codeBytes, codeBytesEnd, cancelled, error, warnings);
 
         if (!DecodeCodeSection(*env, d, mg)) {
             return nullptr;
         }
 
         MOZ_ASSERT(d.done());
     }
 
     {
-        auto tailBytesPtrGuard = tailBytesPtr.lock();
-        while (!tailBytesPtrGuard) {
+        auto streamEnd = exclusiveStreamEnd.lock();
+        while (!streamEnd->reached) {
             if (cancelled) {
                 return nullptr;
             }
-            tailBytesPtrGuard.wait();
+            streamEnd.wait();
         }
     }
 
-    const Bytes& tailBytes = *tailBytesPtr.lock();
+    const StreamEndData& streamEnd = exclusiveStreamEnd.lock();
+    const Bytes& tailBytes = *streamEnd.tailBytes;
 
     {
         Decoder d(tailBytes, env->codeSection->end(), error, warnings);
 
         if (!DecodeModuleTail(d, env.ptr(), mg.deferredValidationState())) {
             return nullptr;
         }
 
         MOZ_ASSERT(d.done());
     }
 
     SharedBytes bytecode = CreateBytecode(envBytes, codeBytes, tailBytes, error);
     if (!bytecode) {
         return nullptr;
     }
 
-    return mg.finishModule(*bytecode);
+    return mg.finishModule(*bytecode, streamEnd.tier2Listener);
 }
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -96,36 +96,46 @@ CompileTier2(const CompileArgs& args, co
              Atomic<bool>* cancelled);
 
 // Compile the given WebAssembly module which has been broken into three
 // partitions:
 //  - envBytes contains a complete ModuleEnvironment that has already been
 //    copied in from the stream.
 //  - codeBytes is pre-sized to hold the complete code section when the stream
 //    completes.
-//  - The range [codeBytes.begin(), codeStreamEnd) contains the bytes currently
-//    read from the stream and codeStreamEnd will advance until either
-//    the stream is cancelled or codeStreamEnd == codeBytes.end().
-//  - tailBytesPtr is null until the module has finished streaming at which
-//    point tailBytesPtr will point to the complete tail bytes.
+//  - The range [codeBytes.begin(), codeBytesEnd) contains the bytes currently
+//    read from the stream and codeBytesEnd will advance until either
+//    the stream is cancelled or codeBytesEnd == codeBytes.end().
+//  - streamEnd contains the final information received after the code section:
+//    the remaining module bytecodes and maybe a JS::OptimizedEncodingListener.
+//    When the stream is successfully closed, streamEnd.reached is set.
 // The ExclusiveWaitableData are notified when CompileStreaming() can make
-// progress (i.e., codeStreamEnd advances or tailBytes is set to non-null).
+// progress (i.e., codeBytesEnd advances or streamEnd.reached is set).
 // If cancelled is set to true, compilation aborts and returns null. After
 // cancellation is set, both ExclusiveWaitableData will be notified and so every
 // wait() loop must check cancelled.
 
-typedef ExclusiveWaitableData<const uint8_t*> ExclusiveStreamEnd;
-typedef ExclusiveWaitableData<const Bytes*> ExclusiveTailBytesPtr;
+typedef ExclusiveWaitableData<const uint8_t*> ExclusiveBytesPtr;
+
+struct StreamEndData
+{
+    bool reached;
+    const Bytes* tailBytes;
+    Tier2Listener tier2Listener;
+
+    StreamEndData() : reached(false) {}
+};
+typedef ExclusiveWaitableData<StreamEndData> ExclusiveStreamEndData;
 
 SharedModule
 CompileStreaming(const CompileArgs& args,
                  const Bytes& envBytes,
                  const Bytes& codeBytes,
-                 const ExclusiveStreamEnd& codeStreamEnd,
-                 const ExclusiveTailBytesPtr& tailBytesPtr,
+                 const ExclusiveBytesPtr& codeBytesEnd,
+                 const ExclusiveStreamEndData& streamEnd,
                  const Atomic<bool>& cancelled,
                  UniqueChars* error,
                  UniqueCharsVector* warnings);
 
 }  // namespace wasm
 }  // namespace js
 
 #endif // namespace wasm_compile_h
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -1012,17 +1012,19 @@ ModuleGenerator::finishMetadata(const By
         sha1Sum.finish(hash);
         memcpy(metadata_->debugHash, hash, sizeof(ModuleHash));
     }
 
     return true;
 }
 
 SharedModule
-ModuleGenerator::finishModule(const ShareableBytes& bytecode, UniqueLinkData* linkData)
+ModuleGenerator::finishModule(const ShareableBytes& bytecode,
+                              JS::OptimizedEncodingListener* maybeTier2Listener,
+                              UniqueLinkData* maybeLinkDataOut)
 {
     MOZ_ASSERT(mode() == CompileMode::Once || mode() == CompileMode::Tier1);
 
     UniqueCodeTier codeTier = finishCodeTier();
     if (!codeTier) {
         return nullptr;
     }
 
@@ -1123,23 +1125,25 @@ ModuleGenerator::finishModule(const Shar
                                           std::move(debugUnlinkedCode),
                                           std::move(debugLinkData),
                                           debugBytecode);
     if (!module) {
         return nullptr;
     }
 
     if (mode() == CompileMode::Tier1) {
-        module->startTier2(*compileArgs_, bytecode);
+        module->startTier2(*compileArgs_, bytecode, maybeTier2Listener);
+    } else if (tier() == Tier::Serialized && maybeTier2Listener) {
+        module->serialize(*linkData_, *maybeTier2Listener);
     }
 
-    if (linkData) {
+    if (maybeLinkDataOut) {
         MOZ_ASSERT(isAsmJS());
         MOZ_ASSERT(!env_->debugEnabled());
-        *linkData = std::move(linkData_);
+        *maybeLinkDataOut = std::move(linkData_);
     }
 
     return module;
 }
 
 bool
 ModuleGenerator::finishTier2(const Module& module)
 {
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -226,17 +226,19 @@ class MOZ_STACK_CLASS ModuleGenerator
     // or finishTier2().
 
     MOZ_MUST_USE bool finishFuncDefs();
 
     // If env->mode is Once or Tier1, finishModule() must be called to generate
     // a new Module. Otherwise, if env->mode is Tier2, finishTier2() must be
     // called to augment the given Module with tier 2 code.
 
-    SharedModule finishModule(const ShareableBytes& bytecode, UniqueLinkData* linkData = nullptr);
+    SharedModule finishModule(const ShareableBytes& bytecode,
+                              JS::OptimizedEncodingListener* maybeTier2Listener = nullptr,
+                              UniqueLinkData* maybeLinkDataOut = nullptr);
     MOZ_MUST_USE bool finishTier2(const Module& module);
 
     ExclusiveDeferredValidationState& deferredValidationState() {
         return deferredValidationState_;
     }
 };
 
 } // namespace wasm
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -110,16 +110,35 @@ HasAvailableCompilerTier(JSContext* cx)
 bool
 wasm::HasSupport(JSContext* cx)
 {
     return cx->options().wasm() &&
            HasCompilerSupport(cx) &&
            HasAvailableCompilerTier(cx);
 }
 
+bool
+wasm::HasStreamingSupport(JSContext* cx)
+{
+    // This should match EnsureStreamSupport().
+
+    return HasSupport(cx) &&
+           cx->runtime()->offThreadPromiseState.ref().initialized() &&
+           CanUseExtraThreads() &&
+           cx->runtime()->consumeStreamCallback;
+}
+
+bool
+wasm::HasCachingSupport(JSContext* cx)
+{
+    return HasStreamingSupport(cx) &&
+           cx->options().wasmIon() &&
+           IonCanCompile();
+}
+
 static bool
 ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, MutableHandleVal val)
 {
     switch (targetType.code()) {
       case ValType::I32: {
         int32_t i32;
         if (!ToInt32(cx, v, &i32)) {
             return false;
@@ -2930,16 +2949,18 @@ WebAssembly_validate(JSContext* cx, unsi
 
     callArgs.rval().setBoolean(validated);
     return true;
 }
 
 static bool
 EnsureStreamSupport(JSContext* cx)
 {
+    // This should match wasm::HasStreamingSupport().
+
     if (!EnsurePromiseSupport(cx)) {
         return false;
     }
 
     if (!CanUseExtraThreads()) {
         JS_ReportErrorASCII(cx, "WebAssembly.compileStreaming not supported with --no-threads");
         return false;
     }
@@ -2969,22 +2990,23 @@ class CompileStreamTask : public Promise
     const bool                   instantiate_;
     const PersistentRootedObject importObj_;
 
     // Mutated on a stream thread (consumeChunk() and streamClosed()):
     ExclusiveStreamState         streamState_;
     Bytes                        envBytes_;        // immutable after Env state
     SectionRange                 codeSection_;     // immutable after Env state
     Bytes                        codeBytes_;       // not resized after Env state
-    uint8_t*                     codeStreamEnd_;
-    ExclusiveStreamEnd           exclusiveCodeStreamEnd_;
+    uint8_t*                     codeBytesEnd_;
+    ExclusiveBytesPtr            exclusiveCodeBytesEnd_;
     Bytes                        tailBytes_;       // immutable after Tail state
-    ExclusiveTailBytesPtr        exclusiveTailBytes_;
+    ExclusiveStreamEndData       exclusiveStreamEnd_;
     Maybe<uint32_t>              streamError_;
     Atomic<bool>                 streamFailed_;
+    Tier2Listener                tier2Listener_;
 
     // Mutated on helper thread (execute()):
     SharedModule                 module_;
     UniqueChars                  compileError_;
     UniqueCharsVector            warnings_;
 
     // Called on some thread before consumeChunk() or streamClosed():
 
@@ -3032,18 +3054,18 @@ class CompileStreamTask : public Promise
     }
 
     // See setClosedAndDestroyAfterHelperThreadStarted() comment.
     bool rejectAndDestroyAfterHelperThreadStarted(unsigned errorNumber) {
         MOZ_ASSERT(streamState_.lock() == Code || streamState_.lock() == Tail);
         MOZ_ASSERT(!streamError_);
         streamError_ = Some(errorNumber);
         streamFailed_ = true;
-        exclusiveCodeStreamEnd_.lock().notify_one();
-        exclusiveTailBytes_.lock().notify_one();
+        exclusiveCodeBytesEnd_.lock().notify_one();
+        exclusiveStreamEnd_.lock().notify_one();
         setClosedAndDestroyAfterHelperThreadStarted();
         return false;
     }
 
     bool consumeChunk(const uint8_t* begin, size_t length) override {
         switch (streamState_.lock().get()) {
           case Env: {
             if (!envBytes_.append(begin, length)) {
@@ -3062,18 +3084,18 @@ class CompileStreamTask : public Promise
             if (codeSection_.size > MaxCodeSectionBytes) {
                 return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
             }
 
             if (!codeBytes_.resize(codeSection_.size)) {
                 return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
             }
 
-            codeStreamEnd_ = codeBytes_.begin();
-            exclusiveCodeStreamEnd_.lock().get() = codeStreamEnd_;
+            codeBytesEnd_ = codeBytes_.begin();
+            exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_;
 
             if (!StartOffThreadPromiseHelperTask(this)) {
                 return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
             }
 
             // Set the state to Code iff StartOffThreadPromiseHelperTask()
             // succeeds so that the state tells us whether we are before or
             // after the helper thread started.
@@ -3081,27 +3103,27 @@ class CompileStreamTask : public Promise
 
             if (extraBytes) {
                 return consumeChunk(begin + length - extraBytes, extraBytes);
             }
 
             return true;
           }
           case Code: {
-            size_t copyLength = Min<size_t>(length, codeBytes_.end() - codeStreamEnd_);
-            memcpy(codeStreamEnd_, begin, copyLength);
-            codeStreamEnd_ += copyLength;
+            size_t copyLength = Min<size_t>(length, codeBytes_.end() - codeBytesEnd_);
+            memcpy(codeBytesEnd_, begin, copyLength);
+            codeBytesEnd_ += copyLength;
 
             {
-                auto codeStreamEnd = exclusiveCodeStreamEnd_.lock();
-                codeStreamEnd.get() = codeStreamEnd_;
+                auto codeStreamEnd = exclusiveCodeBytesEnd_.lock();
+                codeStreamEnd.get() = codeBytesEnd_;
                 codeStreamEnd.notify_one();
             }
 
-            if (codeStreamEnd_ != codeBytes_.end()) {
+            if (codeBytesEnd_ != codeBytes_.end()) {
                 return true;
             }
 
             streamState_.lock().get() = Tail;
 
             if (uint32_t extraBytes = length - copyLength) {
                 return consumeChunk(begin + copyLength, extraBytes);
             }
@@ -3116,36 +3138,40 @@ class CompileStreamTask : public Promise
             return true;
           }
           case Closed:
             MOZ_CRASH("consumeChunk() in Closed state");
         }
         MOZ_CRASH("unreachable");
     }
 
-    void streamClosed(JS::StreamConsumer::CloseReason closeReason) override {
+    void streamClosed(JS::StreamConsumer::CloseReason closeReason,
+                      JS::OptimizedEncodingListener* tier2Listener) override {
         switch (closeReason) {
           case JS::StreamConsumer::EndOfFile:
             switch (streamState_.lock().get()) {
               case Env: {
                 SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
                 if (!bytecode) {
                     rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
                     return;
                 }
                 module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
                 setClosedAndDestroyBeforeHelperThreadStarted();
                 return;
               }
               case Code:
               case Tail:
                 {
-                    auto tailBytes = exclusiveTailBytes_.lock();
-                    tailBytes.get() = &tailBytes_;
-                    tailBytes.notify_one();
+                    auto streamEnd = exclusiveStreamEnd_.lock();
+                    MOZ_ASSERT(!streamEnd->reached);
+                    streamEnd->reached = true;
+                    streamEnd->tailBytes = &tailBytes_;
+                    streamEnd->tier2Listener = tier2Listener;
+                    streamEnd.notify_one();
                 }
                 setClosedAndDestroyAfterHelperThreadStarted();
                 return;
               case Closed:
                 MOZ_CRASH("streamClosed() in Closed state");
             }
             break;
           case JS::StreamConsumer::Error:
@@ -3160,24 +3186,31 @@ class CompileStreamTask : public Promise
               case Closed:
                 MOZ_CRASH("streamClosed() in Closed state");
             }
             break;
         }
         MOZ_CRASH("unreachable");
     }
 
+    void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
+        module_ = Module::deserialize(begin, length);
+
+        MOZ_ASSERT(streamState_.lock().get() == Env);
+        setClosedAndDestroyBeforeHelperThreadStarted();
+    }
+
     // Called on a helper thread:
 
     void execute() override {
         module_ = CompileStreaming(*compileArgs_,
                                    envBytes_,
                                    codeBytes_,
-                                   exclusiveCodeStreamEnd_,
-                                   exclusiveTailBytes_,
+                                   exclusiveCodeBytesEnd_,
+                                   exclusiveStreamEnd_,
                                    streamFailed_,
                                    &compileError_,
                                    &warnings_);
 
         // When execute() returns, the CompileStreamTask will be dispatched
         // back to its JS thread to call resolve() and then be destroyed. We
         // can't let this happen until the stream has been closed lest
         // consumeChunk() or streamClosed() be called on a dead object.
@@ -3204,19 +3237,19 @@ class CompileStreamTask : public Promise
                       CompileArgs& compileArgs, bool instantiate,
                       HandleObject importObj)
       : PromiseHelperTask(cx, promise),
         compileArgs_(&compileArgs),
         instantiate_(instantiate),
         importObj_(cx, importObj),
         streamState_(mutexid::WasmStreamStatus, Env),
         codeSection_{},
-        codeStreamEnd_(nullptr),
-        exclusiveCodeStreamEnd_(mutexid::WasmCodeStreamEnd, nullptr),
-        exclusiveTailBytes_(mutexid::WasmTailBytesPtr, nullptr),
+        codeBytesEnd_(nullptr),
+        exclusiveCodeBytesEnd_(mutexid::WasmCodeBytesEnd, nullptr),
+        exclusiveStreamEnd_(mutexid::WasmStreamEnd),
         streamFailed_(false)
     {
         MOZ_ASSERT_IF(importObj_, instantiate_);
     }
 };
 
 // A short-lived object that captures the arguments of a
 // WebAssembly.{compileStreaming,instantiateStreaming} while waiting for
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -35,21 +35,32 @@ namespace wasm {
 
 // Return whether WebAssembly can be compiled on this platform.
 // This must be checked and must be true to call any of the top-level wasm
 // eval/compile methods.
 
 bool
 HasCompilerSupport(JSContext* cx);
 
-// Return whether WebAssembly is enabled on this platform.
+// Return whether WebAssembly is supported on this platform. This determines
+// whether the WebAssembly object is exposed to JS and takes into account
+// configuration options that disable various modes.
 
 bool
 HasSupport(JSContext* cx);
 
+// Return whether WebAssembly streaming/caching is supported on this platform.
+// This takes into account prefs and necessary embedding callbacks.
+
+bool
+HasStreamingSupport(JSContext* cx);
+
+bool
+HasCachingSupport(JSContext* cx);
+
 // Compiles the given binary wasm module given the ArrayBufferObject
 // and links the module's imports with the given import object.
 
 MOZ_MUST_USE bool
 Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj,
      MutableHandleWasmInstanceObject instanceObj);
 
 // These accessors can be used to probe JS values for being an exported wasm
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -49,40 +49,51 @@ class Module::Tier2GeneratorTaskImpl : p
                            Module& module)
       : compileArgs_(&compileArgs),
         bytecode_(&bytecode),
         module_(&module),
         cancelled_(false)
     {}
 
     ~Tier2GeneratorTaskImpl() override {
+        module_->tier2Listener_ = nullptr;
         module_->testingTier2Active_ = false;
     }
 
     void cancel() override {
         cancelled_ = true;
     }
 
     void execute() override {
         CompileTier2(*compileArgs_, bytecode_->bytes, *module_, &cancelled_);
     }
 };
 
+Module::~Module()
+{
+    // Note: Modules can be destroyed on any thread.
+    MOZ_ASSERT(!tier2Listener_);
+    MOZ_ASSERT(!testingTier2Active_);
+}
+
 void
-Module::startTier2(const CompileArgs& args, const ShareableBytes& bytecode)
+Module::startTier2(const CompileArgs& args,
+                   const ShareableBytes& bytecode,
+                   JS::OptimizedEncodingListener* listener)
 {
     MOZ_ASSERT(!testingTier2Active_);
 
     auto task = MakeUnique<Tier2GeneratorTaskImpl>(args, bytecode, *this);
     if (!task) {
         return;
     }
 
-    // This flag will be cleared asynchronously by ~Tier2GeneratorTaskImpl()
-    // on success or failure.
+    // These will be cleared asynchronously by ~Tier2GeneratorTaskImpl() if not
+    // sooner by finishTier2().
+    tier2Listener_ = listener;
     testingTier2Active_ = true;
 
     StartOffThreadWasmTier2Generator(std::move(task));
 }
 
 bool
 Module::finishTier2(const LinkData& linkData2, UniqueCodeTier code2) const
 {
@@ -151,48 +162,64 @@ Module::finishTier2(const LinkData& link
         // set*Entry functions.
         if (cr.isFunction()) {
             code().setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry());
         } else if (cr.isJitEntry()) {
             code().setJitEntry(cr.funcIndex(), base + cr.begin());
         }
     }
 
+    // Tier-2 is done; let everyone know.
+
+    testingTier2Active_ = false;
+    if (tier2Listener_) {
+        serialize(linkData2, *tier2Listener_);
+        tier2Listener_ = nullptr;
+    }
+
     return true;
 }
 
 void
 Module::testingBlockOnTier2Complete() const
 {
     while (testingTier2Active_) {
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
 }
 
 /* virtual */ size_t
 Module::serializedSize(const LinkData& linkData) const
 {
-    return linkData.serializedSize() +
+    JS::BuildIdCharVector buildId;
+    JS::GetOptimizedEncodingBuildId(&buildId);
+
+    return SerializedPodVectorSize(buildId) +
+           linkData.serializedSize() +
            SerializedVectorSize(imports_) +
            SerializedVectorSize(exports_) +
            SerializedVectorSize(structTypes_) +
            SerializedVectorSize(dataSegments_) +
            SerializedVectorSize(elemSegments_) +
            SerializedVectorSize(customSections_) +
            code_->serializedSize();
 }
 
 /* virtual */ void
 Module::serialize(const LinkData& linkData, uint8_t* begin, size_t size) const
 {
     MOZ_RELEASE_ASSERT(!testingTier2Active_);
     MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
     MOZ_RELEASE_ASSERT(code_->hasTier(Tier::Serialized));
 
+    JS::BuildIdCharVector buildId;
+    JS::GetOptimizedEncodingBuildId(&buildId);
+
     uint8_t* cursor = begin;
+    cursor = SerializePodVector(cursor, buildId);
     cursor = linkData.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializeVector(cursor, structTypes_);
     cursor = SerializeVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
     cursor = SerializeVector(cursor, customSections_);
     cursor = code_->serialize(cursor, linkData);
@@ -207,16 +234,27 @@ Module::deserialize(const uint8_t* begin
         metadata = js_new<Metadata>();
         if (!metadata) {
             return nullptr;
         }
     }
 
     const uint8_t* cursor = begin;
 
+    JS::BuildIdCharVector currentBuildId;
+    JS::GetOptimizedEncodingBuildId(&currentBuildId);
+
+    JS::BuildIdCharVector deserializedBuildId;
+    cursor = DeserializePodVector(cursor, &deserializedBuildId);
+    if (!cursor) {
+        return nullptr;
+    }
+
+    MOZ_RELEASE_ASSERT(EqualContainers(currentBuildId, deserializedBuildId));
+
     LinkData linkData(Tier::Serialized);
     cursor = linkData.deserialize(cursor);
     if (!cursor) {
         return nullptr;
     }
 
     ImportVector imports;
     cursor = DeserializeVector(cursor, &imports);
@@ -274,27 +312,67 @@ Module::deserialize(const uint8_t* begin
                           std::move(imports),
                           std::move(exports),
                           std::move(structTypes),
                           std::move(dataSegments),
                           std::move(elemSegments),
                           std::move(customSections));
 }
 
+void
+Module::serialize(const LinkData& linkData, JS::OptimizedEncodingListener& listener) const
+{
+    Vector<uint8_t, 0, SystemAllocPolicy> bytes;
+    if (!bytes.resize(serializedSize(linkData))) {
+        return;
+    }
+
+    serialize(linkData, bytes.begin(), bytes.length());
+
+    listener.storeOptimizedEncoding(bytes.begin(), bytes.length());
+}
+
 /* virtual */ JSObject*
 Module::createObject(JSContext* cx)
 {
     if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
         return nullptr;
     }
 
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     return WasmModuleObject::create(cx, *this, proto);
 }
 
+bool
+wasm::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId)
+{
+    // From a JS API perspective, the "build id" covers everything that can
+    // cause machine code to become invalid, so include both the actual build-id
+    // and cpu-id.
+
+    if (!GetBuildId || !GetBuildId(buildId)) {
+        return false;
+    }
+
+    uint32_t cpu = ObservedCPUFeatures();
+
+    if (!buildId->reserve(buildId->length() + 10 /* "()" + 8 nibbles */)) {
+        return false;
+    }
+
+    buildId->infallibleAppend('(');
+    while (cpu) {
+        buildId->infallibleAppend('0' + (cpu & 0xf));
+        cpu >>= 4;
+    }
+    buildId->infallibleAppend(')');
+
+    return true;
+}
+
 struct MemUnmap
 {
     uint32_t size;
     MemUnmap() : size(0) {}
     explicit MemUnmap(uint32_t size) : size(size) {}
     void operator()(uint8_t* p) { MOZ_ASSERT(size); PR_MemUnmap(p, size); }
 };
 
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -28,21 +28,28 @@
 #include "wasm/WasmTable.h"
 #include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
 struct CompileArgs;
 
-// Module represents a compiled wasm module and primarily provides two
-// operations: instantiation and serialization. A Module can be instantiated any
-// number of times to produce new Instance objects. A Module can be serialized
-// any number of times such that the serialized bytes can be deserialized later
-// to produce a new, equivalent Module.
+// In the context of wasm, the OptimizedEncodingListener specifically is
+// listening for the completion of tier-2.
+
+typedef RefPtr<JS::OptimizedEncodingListener> Tier2Listener;
+
+// Module represents a compiled wasm module and primarily provides three
+// operations: instantiation, tiered compilation, serialization. A Module can be
+// instantiated any number of times to produce new Instance objects. A Module
+// can have a single tier-2 task initiated to augment a Module's code with a
+// higher tier. A Module can  have its optimized code serialized at any point
+// where the LinkData is also available, which is primarily (1) at the end of
+// module generation, (2) at the end of tier-2 compilation.
 //
 // Fully linked-and-instantiated code (represented by Code and its owned
 // ModuleSegment) can be shared between instances, provided none of those
 // instances are being debugged. If patchable code is needed then each instance
 // must have its own Code. Module eagerly creates a new Code and gives it to the
 // first instance; it then instantiates new Code objects from a copy of the
 // unlinked code that it keeps around for that purpose.
 
@@ -64,18 +71,24 @@ class Module : public JS::WasmModule
     // This could all be removed if debugging didn't need to perform
     // per-instance code patching.
 
     mutable Atomic<bool>    debugCodeClaimed_;
     const UniqueConstBytes  debugUnlinkedCode_;
     const UniqueLinkData    debugLinkData_;
     const SharedBytes       debugBytecode_;
 
-    // This flag is only used for testing purposes and is racily updated as soon
-    // as tier-2 compilation finishes (in success or failure).
+    // This field is set during tier-2 compilation and cleared on success or
+    // failure. These happen on different threads and are serialized by the
+    // control flow of helper tasks.
+
+    mutable Tier2Listener   tier2Listener_;
+
+    // This flag is only used for testing purposes and is cleared on success or
+    // failure. The field is racily polled from various threads.
 
     mutable Atomic<bool>    testingTier2Active_;
 
     bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
     bool instantiateTable(JSContext* cx,
                           MutableHandleWasmTableObject table,
                           SharedTableVector* tables) const;
@@ -111,17 +124,17 @@ class Module : public JS::WasmModule
         debugCodeClaimed_(false),
         debugUnlinkedCode_(std::move(debugUnlinkedCode)),
         debugLinkData_(std::move(debugLinkData)),
         debugBytecode_(debugBytecode),
         testingTier2Active_(false)
     {
         MOZ_ASSERT_IF(metadata().debugEnabled, debugUnlinkedCode_ && debugLinkData_);
     }
-    ~Module() override { /* Note: can be called on any thread */ }
+    ~Module() override;
 
     const Code& code() const { return *code_; }
     const ModuleSegment& moduleSegment(Tier t) const { return code_->segment(t); }
     const Metadata& metadata() const { return code_->metadata(); }
     const MetadataTier& metadata(Tier t) const { return code_->metadata(t); }
     const ImportVector& imports() const { return imports_; }
     const ExportVector& exports() const { return exports_; }
     const CustomSectionVector& customSections() const { return customSections_; }
@@ -139,27 +152,29 @@ class Module : public JS::WasmModule
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Tier-2 compilation may be initiated after the Module is constructed at
     // most once. When tier-2 compilation completes, ModuleGenerator calls
     // finishTier2() from a helper thread, passing tier-variant data which will
     // be installed and made visible.
 
-    void startTier2(const CompileArgs& args, const ShareableBytes& bytecode);
+    void startTier2(const CompileArgs& args,
+                    const ShareableBytes& bytecode,
+                    JS::OptimizedEncodingListener* listener);
     bool finishTier2(const LinkData& linkData2, UniqueCodeTier code2) const;
 
     void testingBlockOnTier2Complete() const;
     bool testingTier2Active() const { return testingTier2Active_; }
 
-    // Currently dead, but will be ressurrected with shell tests (bug 1330661)
-    // and HTTP cache integration.
+    // Code caching support.
 
     size_t serializedSize(const LinkData& linkData) const;
     void serialize(const LinkData& linkData, uint8_t* begin, size_t size) const;
+    void serialize(const LinkData& linkData, JS::OptimizedEncodingListener& listener) const;
     static RefPtr<Module> deserialize(const uint8_t* begin, size_t size,
                                       Metadata* maybeMetadata = nullptr);
 
     // JS API and JS::WasmModule implementation:
 
     JSObject* createObject(JSContext* cx) override;
 
     // about:memory reporting:
@@ -175,15 +190,18 @@ class Module : public JS::WasmModule
     bool extractCode(JSContext* cx, Tier tier, MutableHandleValue vp) const;
 };
 
 typedef RefPtr<Module> MutableModule;
 typedef RefPtr<const Module> SharedModule;
 
 // JS API implementations:
 
+bool
+GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId);
+
 RefPtr<JS::WasmModule>
 DeserializeModule(PRFileDesc* bytecode, UniqueChars filename, unsigned line);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_module_h
--- a/layout/painting/RetainedDisplayListHelpers.h
+++ b/layout/painting/RetainedDisplayListHelpers.h
@@ -25,17 +25,17 @@ class DisplayItemHashEntry : public PLDH
 public:
   typedef DisplayItemKey KeyType;
   typedef const DisplayItemKey* KeyTypePointer;
 
   explicit DisplayItemHashEntry(KeyTypePointer aKey)
     : mKey(*aKey)
   {
   }
-  explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy) = default;
+  DisplayItemHashEntry(DisplayItemHashEntry&&) = default;
 
   ~DisplayItemHashEntry() = default;
 
   KeyType GetKey() const { return mKey; }
   bool KeyEquals(KeyTypePointer aKey) const { return mKey == *aKey; }
 
   static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
   static PLDHashNumber HashKey(KeyTypePointer aKey)
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -17,17 +17,17 @@ fails-if(Android) == boxshadow-fileuploa
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-98,0-152) fuzzy-if(skiaContent,0-13,0-28) fuzzy-if(webrender,19-19,50-50) == boxshadow-inner-basic.html boxshadow-inner-basic-ref.svg
 fuzzy-if(skiaContent,0-1,0-17) random-if(layersGPUAccelerated) == boxshadow-mixed.html boxshadow-mixed-ref.html
 fuzzy-if(skiaContent,0-1,0-17) == boxshadow-mixed-2.html boxshadow-mixed-2-ref.html
 random-if(d2d) fuzzy-if(skiaContent,0-1,0-212) fuzzy-if(webrender,0-127,0-3528) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
 fuzzy-if(skiaContent,0-1,0-50) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul
 random-if(d2d) fuzzy-if(skiaContent,0-1,0-14) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
 random-if(d2d) fuzzy-if(skiaContent,0-1,0-22) == boxshadow-twocorners.html boxshadow-twocorners-ref.html
 random-if(d2d) fuzzy-if(skiaContent,0-1,0-36) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
-fuzzy(0-2,0-440) fails-if(webrender) == boxshadow-skiprect.html boxshadow-skiprect-ref.html
+fuzzy(0-2,0-440) fails-if(webrender&&gtkWidget) == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
 == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
 fuzzy(0-3,0-500) fuzzy-if(d2d,0-2,0-1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html
 == boxshadow-inset-neg-spread.html about:blank
 == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html
 fuzzy(0-26,0-3610) fuzzy-if(d2d,0-26,0-5910) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264
 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -57,17 +57,17 @@ fuzzy-if(winWidget&&!layersGPUAccelerate
 # Different, but equivalent (for the given transform) transform origins
 fuzzy-if(webrender,0-1,0-4) == rotatex-transformorigin-1a.html rotatex-transformorigin-1-ref.html
 fuzzy-if((gtkWidget&&layersOMTC)||(winWidget&&!layersGPUAccelerated),0-1,0-86) == overflow-hidden-1a.html overflow-hidden-1-ref.html
 fails-if(webrender) == transform-style-flat-1a.html transform-style-flat-1-ref.html
 == willchange-containing-block.html?willchange willchange-containing-block.html?ref
 != willchange-containing-block.html?willchange willchange-containing-block.html?noblock
 fuzzy-if(winWidget&&!layersGPUAccelerated,0-1,0-606) == scroll-perspective-1.html scroll-perspective-1-ref.html
 # Bugs
-fails-if(!layersGPUAccelerated) == 1035611-1.html 1035611-1-ref.html # Bug 1072898 for !layersGPUAccelerated failures
+fails-if(!layersGPUAccelerated) fuzzy-if(webrender,0-23,0-826) == 1035611-1.html 1035611-1-ref.html # Bug 1072898 for !layersGPUAccelerated failures
 != 1157984-1.html about:blank # Bug 1157984
 fuzzy(0-3,0-99) == animate-cube-radians.html animate-cube-radians-ref.html # subpixel AA
 fuzzy(0-3,0-99) fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated,0-16,0-6) == animate-cube-radians-zoom.html animate-cube-radians-zoom-ref.html
 != animate-cube-radians-ref.html animate-cube-radians-zoom-ref.html
 fuzzy(0-3,0-99) == animate-cube-degrees.html animate-cube-degrees-ref.html # subpixel AA
 == animate-cube-degrees-zoom.html animate-cube-degrees-zoom-ref.html
 != animate-cube-degrees-ref.html animate-cube-degrees-zoom-ref.html
 fuzzy-if(gtkWidget,0-128,0-100) fuzzy-if(Android||OSX==1010||(gtkWidget&&layersGPUAccelerated),0-143,0-100) fuzzy-if(winWidget||OSX<1010,0-141,0-100) == preserves3d-nested.html preserves3d-nested-ref.html
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -19,16 +19,17 @@
 #include "nsTObserverArray.h"
 #include "nsURIHashKey.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class nsICSSLoaderObserver;
 class nsIConsoleReportCollector;
 class nsIContent;
 class nsIDocument;
@@ -65,21 +66,21 @@ public:
     : nsURIHashKey(aURI),
       mPrincipal(aPrincipal),
       mCORSMode(aCORSMode),
       mReferrerPolicy(aReferrerPolicy)
   {
     MOZ_COUNT_CTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
   }
 
-  URIPrincipalReferrerPolicyAndCORSModeHashKey(const URIPrincipalReferrerPolicyAndCORSModeHashKey& toCopy)
-    : nsURIHashKey(toCopy),
-      mPrincipal(toCopy.mPrincipal),
-      mCORSMode(toCopy.mCORSMode),
-      mReferrerPolicy(toCopy.mReferrerPolicy)
+  URIPrincipalReferrerPolicyAndCORSModeHashKey(URIPrincipalReferrerPolicyAndCORSModeHashKey&& toMove)
+    : nsURIHashKey(std::move(toMove)),
+      mPrincipal(std::move(toMove.mPrincipal)),
+      mCORSMode(std::move(toMove.mCORSMode)),
+      mReferrerPolicy(std::move(toMove.mReferrerPolicy))
   {
     MOZ_COUNT_CTOR(URIPrincipalReferrerPolicyAndCORSModeHashKey);
   }
 
   explicit URIPrincipalReferrerPolicyAndCORSModeHashKey(css::SheetLoadData* aLoadData);
 
   ~URIPrincipalReferrerPolicyAndCORSModeHashKey()
   {
--- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
@@ -784,16 +784,27 @@ WebrtcGmpVideoDecoder::GmpInitDone(GMPVi
     nsTArray<UniquePtr<GMPDecodeData>> temp;
     temp.SwapElements(mQueuedFrames);
     for (auto& queued : temp) {
       Decode_g(RefPtr<WebrtcGmpVideoDecoder>(this),
                nsAutoPtr<GMPDecodeData>(queued.release()));
     }
   }
 
+  // This is an ugly solution to asynchronous decoding errors
+  // from Decode_g() not being returned to the synchronous Decode() method.
+  // If we don't return an error code at this point, our caller ultimately won't know to request
+  // a PLI and the video stream will remain frozen unless an IDR happens to arrive for other reasons.
+  // Bug 1492852 tracks implementing a proper solution.
+  if(mDecoderStatus != GMPNoErr){
+    LOG(LogLevel::Error, ("%s: Decoder status is bad (%u)!",
+          __PRETTY_FUNCTION__, static_cast<unsigned>(mDecoderStatus)));
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
 void
 WebrtcGmpVideoDecoder::Close_g()
 {
   GMPVideoDecoderProxy* gmp(mGMP);
   mGMP = nullptr;
@@ -814,25 +825,36 @@ WebrtcGmpVideoDecoder::Decode(const webr
                               int64_t aRenderTimeMs)
 {
   MOZ_ASSERT(mGMPThread);
   MOZ_ASSERT(!NS_IsMainThread());
   if (!aInputImage._length) {
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
 
+  // This is an ugly solution to asynchronous decoding errors
+  // from Decode_g() not being returned to the synchronous Decode() method.
+  // If we don't return an error code at this point, our caller ultimately won't know to request
+  // a PLI and the video stream will remain frozen unless an IDR happens to arrive for other reasons.
+  // Bug 1492852 tracks implementing a proper solution.
   nsAutoPtr<GMPDecodeData> decodeData(new GMPDecodeData(aInputImage,
                                                         aMissingFrames,
                                                         aRenderTimeMs));
 
   mGMPThread->Dispatch(WrapRunnableNM(&WebrtcGmpVideoDecoder::Decode_g,
                                       RefPtr<WebrtcGmpVideoDecoder>(this),
                                       decodeData),
                        NS_DISPATCH_NORMAL);
 
+  if(mDecoderStatus != GMPNoErr){
+    LOG(LogLevel::Error, ("%s: Decoder status is bad (%u)!",
+          __PRETTY_FUNCTION__, static_cast<unsigned>(mDecoderStatus)));
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
 /* static */
 // Using nsAutoPtr because WrapRunnable doesn't support move semantics
 void
 WebrtcGmpVideoDecoder::Decode_g(const RefPtr<WebrtcGmpVideoDecoder>& aThis,
                                 nsAutoPtr<GMPDecodeData> aDecodeData)
@@ -840,35 +862,39 @@ WebrtcGmpVideoDecoder::Decode_g(const Re
   if (!aThis->mGMP) {
     if (aThis->mInitting) {
       // InitDone hasn't been called yet (race)
       aThis->mQueuedFrames.AppendElement(aDecodeData.forget());
       return;
     }
     // destroyed via Terminate(), failed to init, or just not initted yet
     LOGD(("GMP Decode: not initted yet"));
+
+    aThis->mDecoderStatus = GMPDecodeErr;
     return;
   }
 
   MOZ_ASSERT(aThis->mQueuedFrames.IsEmpty());
   MOZ_ASSERT(aThis->mHost);
 
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = aThis->mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
   if (err != GMPNoErr) {
     LOG(LogLevel::Error, ("%s: CreateFrame failed (%u)!",
         __PRETTY_FUNCTION__, static_cast<unsigned>(err)));
+    aThis->mDecoderStatus = err;
     return;
   }
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
   err = frame->CreateEmptyFrame(aDecodeData->mImage._length);
   if (err != GMPNoErr) {
     LOG(LogLevel::Error, ("%s: CreateEmptyFrame failed (%u)!",
         __PRETTY_FUNCTION__, static_cast<unsigned>(err)));
+    aThis->mDecoderStatus = err;
     return;
   }
 
   // XXX At this point, we only will get mode1 data (a single length and a buffer)
   // Session_info.cc/etc code needs to change to support mode 0.
   *(reinterpret_cast<uint32_t*>(frame->Buffer())) = frame->Size();
 
   // XXX It'd be wonderful not to have to memcpy the encoded data!
@@ -880,16 +906,17 @@ WebrtcGmpVideoDecoder::Decode_g(const Re
   frame->SetCompleteFrame(aDecodeData->mImage._completeFrame);
   frame->SetBufferType(GMP_BufferLength32);
 
   GMPVideoFrameType ft;
   int32_t ret = WebrtcFrameTypeToGmpFrameType(aDecodeData->mImage._frameType, &ft);
   if (ret != WEBRTC_VIDEO_CODEC_OK) {
     LOG(LogLevel::Error, ("%s: WebrtcFrameTypeToGmpFrameType failed (%u)!",
         __PRETTY_FUNCTION__, static_cast<unsigned>(ret)));
+    aThis->mDecoderStatus = GMPDecodeErr;
     return;
   }
 
   // Bug XXXXXX: Set codecSpecific info
   GMPCodecSpecificInfo info;
   memset(&info, 0, sizeof(info));
   info.mCodecType = kGMPVideoCodecH264;
   info.mCodecSpecific.mH264.mSimulcastIdx = 0;
@@ -901,23 +928,21 @@ WebrtcGmpVideoDecoder::Decode_g(const Re
 
   nsresult rv = aThis->mGMP->Decode(std::move(frame),
                                     aDecodeData->mMissingFrames,
                                     codecSpecificInfo,
                                     aDecodeData->mRenderTimeMs);
   if (NS_FAILED(rv)) {
     LOG(LogLevel::Error, ("%s: Decode failed (rv=%u)!",
         __PRETTY_FUNCTION__, static_cast<unsigned>(rv)));
+    aThis->mDecoderStatus = GMPDecodeErr;
+    return;
   }
 
-  if(aThis->mDecoderStatus != GMPNoErr){
-    aThis->mDecoderStatus = GMPNoErr;
-    LOG(LogLevel::Error, ("%s: Decoder status is bad (%u)!",
-        __PRETTY_FUNCTION__, static_cast<unsigned>(aThis->mDecoderStatus)));
-  }
+  aThis->mDecoderStatus = GMPNoErr;
 }
 
 int32_t
 WebrtcGmpVideoDecoder::RegisterDecodeCompleteCallback( webrtc::DecodedImageCallback* aCallback)
 {
   MutexAutoLock lock(mCallbackMutex);
   mCallback = aCallback;
 
--- a/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
@@ -479,17 +479,17 @@ private:
   bool mInitting;
   // Frames queued for decode while mInitting is true
   nsTArray<UniquePtr<GMPDecodeData>> mQueuedFrames;
   GMPVideoHost* mHost;
   // Protects mCallback
   Mutex mCallbackMutex;
   webrtc::DecodedImageCallback* mCallback;
   Atomic<uint64_t> mCachedPluginId;
-  GMPErr mDecoderStatus;
+  Atomic<GMPErr, ReleaseAcquire> mDecoderStatus;
   std::string mPCHandle;
 };
 
 // Basically a strong ref to a WebrtcGmpVideoDecoder, that also translates
 // from Release() to WebrtcGmpVideoDecoder::ReleaseGmp(), since we need
 // WebrtcGmpVideoDecoder::Release() for managing the refcount.
 // The webrtc.org code gets one of these, so it doesn't unilaterally delete
 // the "real" encoder.
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -2047,27 +2047,30 @@ public:
     , mWeakRef(do_GetWeakReference(aObserver))
     , mStrongRef(nullptr)
   {
     MOZ_COUNT_CTOR(PrefCallback);
     nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
     mCanonical = canonical;
   }
 
-  // Copy constructor needs to be explicit or the linker complains.
+  // This is explicitly not a copy constructor.
   explicit PrefCallback(const PrefCallback*& aCopy)
     : mDomain(aCopy->mDomain)
     , mBranch(aCopy->mBranch)
     , mWeakRef(aCopy->mWeakRef)
     , mStrongRef(aCopy->mStrongRef)
     , mCanonical(aCopy->mCanonical)
   {
     MOZ_COUNT_CTOR(PrefCallback);
   }
 
+  PrefCallback(const PrefCallback&) = delete;
+  PrefCallback(PrefCallback&&) = default;
+
   ~PrefCallback() { MOZ_COUNT_DTOR(PrefCallback); }
 
   bool KeyEquals(const PrefCallback* aKey) const
   {
     // We want to be able to look up a weakly-referencing PrefCallback after
     // its observer has died so we can remove it from the table. Once the
     // callback's observer dies, its canonical pointer is stale -- in
     // particular, we may have allocated a new observer in the same spot in
new file mode 100644
--- /dev/null
+++ b/modules/libpref/init/ContentBlockingDefaultPrefValues.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Default value of browser.contentblocking.enabled.
+// Please note that privacy protections provided by Gecko may depend on this preference.
+// Turning this off may disable some protections.  Please do not turn this pref off without
+// realizing the implications of what you're doing.
+#define CONTENTBLOCKING_ENABLED true
+
+// Default value of browser.contentblocking.ui.enabled.
+// Enable the new Content Blocking UI only on Nightly.
+#ifdef NIGHTLY_BUILD
+# define CONTENTBLOCKING_UI_ENABLED true
+#else
+# define CONTENTBLOCKING_UI_ENABLED false
+#endif
+
+// Default value of browser.contentblocking.rejecttrackers.ui.enabled.
+#define CONTENTBLOCKING_REJECTTRACKERS_UI_ENABLED true
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1471,26 +1471,43 @@ VARCACHE_PREF(
 //---------------------------------------------------------------------------
 
 PREF("preferences.allow.omt-write", bool, true)
 
 //---------------------------------------------------------------------------
 // Privacy prefs
 //---------------------------------------------------------------------------
 
+#include "ContentBlockingDefaultPrefValues.h"
+
 // Whether Content Blocking has been enabled.
-// Please note that privacy protections provided by Gecko may depend on this preference.
-// Turning this off may disable some protections.  Please do not turn this pref off without
-// realizing the implications of what you're doing.
 VARCACHE_PREF(
   "browser.contentblocking.enabled",
    browser_contentblocking_enabled,
-  bool, true
+  bool, CONTENTBLOCKING_ENABLED
+)
+
+// Whether Content Blocking UI has been enabled.
+VARCACHE_PREF(
+  "browser.contentblocking.ui.enabled",
+   browser_contentblocking_ui_enabled,
+  bool, CONTENTBLOCKING_UI_ENABLED
 )
 
+// Whether Content Blocking Third-Party Cookies UI has been enabled.
+VARCACHE_PREF(
+  "browser.contentblocking.rejecttrackers.ui.enabled",
+   browser_contentblocking_rejecttrackers_ui_enabled,
+  bool, CONTENTBLOCKING_REJECTTRACKERS_UI_ENABLED
+)
+
+#undef CONTENTBLOCKING_ENABLED
+#undef CONTENTBLOCKING_UI_ENABLED
+#undef CONTENTBLOCKING_REJECTTRACKERS_UI_ENABLED
+
 // Whether FastBlock has been enabled.
 VARCACHE_PREF(
   "browser.fastblock.enabled",
   browser_fastblock_enabled,
   bool, false
 )
 
 // Anti-tracking permission expiration
--- a/modules/libpref/moz.build
+++ b/modules/libpref/moz.build
@@ -20,16 +20,17 @@ XPIDL_SOURCES += [
     'nsIPrefLocalizedString.idl',
     'nsIPrefService.idl',
     'nsIRelativeFilePref.idl',
 ]
 
 XPIDL_MODULE = 'pref'
 
 EXPORTS.mozilla += [
+    'init/ContentBlockingDefaultPrefValues.h',
     'init/StaticPrefList.h',
     'nsRelativeFilePref.h',
     'Preferences.h',
     'StaticPrefs.h',
 ]
 
 UNIFIED_SOURCES += [
     'Preferences.cpp',
--- a/netwerk/base/nsURIHashKey.h
+++ b/netwerk/base/nsURIHashKey.h
@@ -4,31 +4,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsURIHashKey_h__
 #define nsURIHashKey_h__
 
 #include "PLDHashTable.h"
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 #include "nsHashKeys.h"
+#include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 
 /**
  * Hashtable key class to use with nsTHashtable/nsBaseHashtable
  */
 class nsURIHashKey : public PLDHashEntryHdr
 {
 public:
     typedef nsIURI* KeyType;
     typedef const nsIURI* KeyTypePointer;
 
     explicit nsURIHashKey(const nsIURI* aKey) :
         mKey(const_cast<nsIURI*>(aKey)) { MOZ_COUNT_CTOR(nsURIHashKey); }
-    nsURIHashKey(const nsURIHashKey& toCopy) :
-        mKey(toCopy.mKey) { MOZ_COUNT_CTOR(nsURIHashKey); }
+    nsURIHashKey(nsURIHashKey&& toMove)
+        : PLDHashEntryHdr(std::move(toMove))
+        , mKey(std::move(toMove.mKey))
+    {
+        MOZ_COUNT_CTOR(nsURIHashKey);
+    }
     ~nsURIHashKey() { MOZ_COUNT_DTOR(nsURIHashKey); }
 
     nsIURI* GetKey() const { return mKey; }
 
     bool KeyEquals(const nsIURI* aKey) const {
         bool eq;
         if (!mKey) {
             return !aKey;
--- a/netwerk/cache/nsCacheEntry.cpp
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -497,18 +497,18 @@ nsCacheEntryHashTable::MatchEntry(const 
 }
 
 
 void
 nsCacheEntryHashTable::MoveEntry(PLDHashTable * /* table */,
                                  const PLDHashEntryHdr *from,
                                  PLDHashEntryHdr       *to)
 {
-    ((nsCacheEntryHashTableEntry *)to)->cacheEntry =
-        ((nsCacheEntryHashTableEntry *)from)->cacheEntry;
+    new (KnownNotNull, to) nsCacheEntryHashTableEntry(std::move(*((nsCacheEntryHashTableEntry *)from)));
+    // No need to destroy `from`.
 }
 
 
 void
 nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */,
                                   PLDHashEntryHdr * hashEntry)
 {
     ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = nullptr;
--- a/netwerk/cache/nsDiskCacheBinding.cpp
+++ b/netwerk/cache/nsDiskCacheBinding.cpp
@@ -38,17 +38,18 @@ MatchEntry(const PLDHashEntryHdr *      
     return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key));
 }
 
 static void
 MoveEntry(PLDHashTable *           /* table */,
           const PLDHashEntryHdr *     src,
           PLDHashEntryHdr       *     dst)
 {
-    ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding;
+    new (KnownNotNull, dst) HashTableEntry(std::move(*(HashTableEntry*)src));
+    // No need to delete `src`.
 }
 
 
 static void
 ClearEntry(PLDHashTable *      /* table */,
            PLDHashEntryHdr *      header)
 {
     ((HashTableEntry *)header)->mBinding = nullptr;
--- a/netwerk/cookie/nsCookieKey.h
+++ b/netwerk/cookie/nsCookieKey.h
@@ -23,20 +23,18 @@ public:
     , mOriginAttributes(attrs)
   {}
 
   explicit nsCookieKey(KeyTypePointer other)
     : mBaseDomain(other->mBaseDomain)
     , mOriginAttributes(other->mOriginAttributes)
   {}
 
-  nsCookieKey(KeyType other)
-    : mBaseDomain(other.mBaseDomain)
-    , mOriginAttributes(other.mOriginAttributes)
-  {}
+  nsCookieKey(nsCookieKey&& other) = default;
+  nsCookieKey& operator=(nsCookieKey&&) = default;
 
   bool KeyEquals(KeyTypePointer other) const
   {
     return mBaseDomain == other->mBaseDomain &&
            mOriginAttributes == other->mOriginAttributes;
   }
 
   static KeyTypePointer KeyToPointer(KeyType aKey)
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2953,17 +2953,17 @@ nsCookieService::Read()
     OriginAttributes attrs;
     stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
     // If PopulateFromSuffix failed we just ignore the OA attributes
     // that we don't support
     Unused << attrs.PopulateFromSuffix(suffix);
 
     nsCookieKey key(baseDomain, attrs);
     CookieDomainTuple* tuple = mReadArray.AppendElement();
-    tuple->key = key;
+    tuple->key = std::move(key);
     tuple->cookie = GetCookieFromRow(stmt, attrs);
   }
 
   COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %zu cookies read", mReadArray.Length()));
 
   return RESULT_OK;
 }
 
--- a/parser/html/nsHtml5AtomTable.cpp
+++ b/parser/html/nsHtml5AtomTable.cpp
@@ -6,18 +6,18 @@
 #include "nsThreadUtils.h"
 
 nsHtml5AtomEntry::nsHtml5AtomEntry(KeyTypePointer aStr)
   : nsStringHashKey(aStr)
   , mAtom(nsDynamicAtom::Create(*aStr))
 {
 }
 
-nsHtml5AtomEntry::nsHtml5AtomEntry(const nsHtml5AtomEntry& aOther)
-  : nsStringHashKey(aOther)
+nsHtml5AtomEntry::nsHtml5AtomEntry(nsHtml5AtomEntry&& aOther)
+  : nsStringHashKey(std::move(aOther))
   , mAtom(nullptr)
 {
   MOZ_ASSERT_UNREACHABLE("nsHtml5AtomTable is broken; tried to copy an entry");
 }
 
 nsHtml5AtomEntry::~nsHtml5AtomEntry()
 {
   nsDynamicAtom::Destroy(mAtom);
--- a/parser/html/nsHtml5AtomTable.h
+++ b/parser/html/nsHtml5AtomTable.h
@@ -11,17 +11,17 @@
 #include "nsISerialEventTarget.h"
 
 #define RECENTLY_USED_PARSER_ATOMS_SIZE 31
 
 class nsHtml5AtomEntry : public nsStringHashKey
 {
 public:
   explicit nsHtml5AtomEntry(KeyTypePointer aStr);
-  nsHtml5AtomEntry(const nsHtml5AtomEntry& aOther);
+  nsHtml5AtomEntry(nsHtml5AtomEntry&& aOther);
   ~nsHtml5AtomEntry();
   inline nsAtom* GetAtom() { return mAtom; }
 
 private:
   nsDynamicAtom* mAtom;
 };
 
 /**
--- a/security/manager/ssl/nsCertOverrideService.h
+++ b/security/manager/ssl/nsCertOverrideService.h
@@ -76,17 +76,18 @@ class nsCertOverrideEntry final : public
     typedef const char* KeyTypePointer;
 
     // do nothing with aHost - we require mHead to be set before we're live!
     explicit nsCertOverrideEntry(KeyTypePointer aHostWithPortUTF8)
     {
     }
 
     nsCertOverrideEntry(nsCertOverrideEntry&& toMove)
-      : mSettings(std::move(toMove.mSettings))
+      : PLDHashEntryHdr(std::move(toMove))
+      , mSettings(std::move(toMove.mSettings))
       , mHostWithPort(std::move(toMove.mHostWithPort))
     {
     }
 
     ~nsCertOverrideEntry()
     {
     }
 
--- a/security/manager/ssl/nsClientAuthRemember.h
+++ b/security/manager/ssl/nsClientAuthRemember.h
@@ -60,17 +60,18 @@ class nsClientAuthRememberEntry final : 
     typedef const char* KeyTypePointer;
 
     // do nothing with aHost - we require mHead to be set before we're live!
     explicit nsClientAuthRememberEntry(KeyTypePointer aHostWithCertUTF8)
     {
     }
 
     nsClientAuthRememberEntry(nsClientAuthRememberEntry&& aToMove)
-      : mSettings(std::move(aToMove.mSettings))
+      : PLDHashEntryHdr(std::move(aToMove))
+      , mSettings(std::move(aToMove.mSettings))
       , mEntryKey(std::move(aToMove.mEntryKey))
     {
     }
 
     ~nsClientAuthRememberEntry()
     {
     }
 
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -13574,16 +13574,22 @@
     ]
    ],
    "pointerevents/extension/pointerevent_coalesced_events_attributes-manual.html": [
     [
      "/pointerevents/extension/pointerevent_coalesced_events_attributes-manual.html",
      {}
     ]
    ],
+   "pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html": [
+    [
+     "/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html",
+     {}
+    ]
+   ],
    "pointerevents/extension/pointerevent_pointerrawmove-manual.html": [
     [
      "/pointerevents/extension/pointerevent_pointerrawmove-manual.html",
      {}
     ]
    ],
    "pointerevents/extension/pointerevent_pointerrawmove_in_pointerlock-manual.html": [
     [
@@ -14018,16 +14024,28 @@
     ]
    ],
    "pointerevents/pointerlock/pointerevent_pointerlock_supercedes_capture-manual.html": [
     [
      "/pointerevents/pointerlock/pointerevent_pointerlock_supercedes_capture-manual.html",
      {}
     ]
    ],
+   "pointerevents/pointerlock/pointerevent_pointermove_in_pointerlock-manual.html": [
+    [
+     "/pointerevents/pointerlock/pointerevent_pointermove_in_pointerlock-manual.html",
+     {}
+    ]
+   ],
+   "pointerevents/pointerlock/pointerevent_pointermove_on_chorded_mouse_button_when_locked-manual.html": [
+    [
+     "/pointerevents/pointerlock/pointerevent_pointermove_on_chorded_mouse_button_when_locked-manual.html",
+     {}
+    ]
+   ],
    "pointerlock/mouse_buttons_back_forward-manual.html": [
     [
      "/pointerlock/mouse_buttons_back_forward-manual.html",
      {}
     ]
    ],
    "pointerlock/movementX_Y_basic-manual.html": [
     [
@@ -110557,53 +110575,53 @@
      {}
     ]
    ],
    "css/css-contain/contain-size-023.html": [
     [
      "/css/css-contain/contain-size-023.html",
      [
       [
-       "/css/css-contain/reference/contain-size-021-ref.html",
+       "/css/css-contain/reference/contain-size-023-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
    "css/css-contain/contain-size-025.html": [
     [
      "/css/css-contain/contain-size-025.html",
      [
       [
-       "/css/css-contain/reference/contain-size-021-ref.html",
+       "/css/css-contain/reference/contain-size-025-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
    "css/css-contain/contain-size-027.html": [
     [
      "/css/css-contain/contain-size-027.html",
      [
       [
-       "/css/css-contain/reference/contain-size-021-ref.html",
+       "/css/css-contain/reference/contain-size-027-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
    "css/css-contain/contain-size-041.html": [
     [
      "/css/css-contain/contain-size-041.html",
      [
       [
-       "/css/css-contain/reference/contain-size-021-ref.html",
+       "/css/css-contain/reference/contain-size-025-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
    "css/css-contain/contain-size-042.html": [
     [
@@ -122728,16 +122746,52 @@
       [
        "/css/css-images/linear-gradient-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-images/multiple-position-color-stop-conic.html": [
+    [
+     "/css/css-images/multiple-position-color-stop-conic.html",
+     [
+      [
+       "/css/css-images/support/100x100-blue-green.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-images/multiple-position-color-stop-linear.html": [
+    [
+     "/css/css-images/multiple-position-color-stop-linear.html",
+     [
+      [
+       "/css/css-images/support/100x100-blue-green.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-images/multiple-position-color-stop-radial.html": [
+    [
+     "/css/css-images/multiple-position-color-stop-radial.html",
+     [
+      [
+       "/css/css-images/support/100x100-blue-green.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-images/tiled-gradients.html": [
     [
      "/css/css-images/tiled-gradients.html",
      [
       [
        "/css/css-images/tiled-gradients-ref.html",
        "=="
       ]
@@ -134588,16 +134642,28 @@
       [
        "/css/css-text/astral-bidi/cypriot.html",
        "!="
       ]
      ],
      {}
     ]
    ],
+   "css/css-text/hyphens/hyphens-auto-001.html": [
+    [
+     "/css/css-text/hyphens/hyphens-auto-001.html",
+     [
+      [
+       "/css/css-text/hyphens/reference/hyphens-auto-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-text/i18n/css3-text-line-break-opclns-001.html": [
     [
      "/css/css-text/i18n/css3-text-line-break-opclns-001.html",
      [
       [
        "/css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html",
        "=="
       ]
@@ -185864,28 +185930,52 @@
       [
        "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-auto-margins-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering.html",
      [
       [
        "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-float.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-float.html",
      [
       [
        "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-float-ref.html",
        "=="
       ]
@@ -185912,16 +186002,28 @@
       [
        "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-position-relative-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-hr-element-0/align.html": [
     [
      "/html/rendering/non-replaced-elements/the-hr-element-0/align.html",
      [
       [
        "/html/rendering/non-replaced-elements/the-hr-element-0/align-ref.html",
        "=="
       ]
@@ -247854,16 +247956,31 @@
      {}
     ]
    ],
    "css/css-contain/reference/contain-size-022-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-contain/reference/contain-size-023-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-contain/reference/contain-size-025-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-contain/reference/contain-size-027-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-contain/reference/contain-size-051-ref.html": [
     [
      {}
     ]
    ],
    "css/css-contain/reference/contain-size-056-ref.html": [
     [
      {}
@@ -258539,16 +258656,21 @@
      {}
     ]
    ],
    "css/css-images/linear-gradient-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-images/support/100x100-blue-green.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-images/support/1x1-green.gif": [
     [
      {}
     ]
    ],
    "css/css-images/support/1x1-green.png": [
     [
      {}
@@ -258774,16 +258896,21 @@
      {}
     ]
    ],
    "css/css-images/tiled-radial-gradients-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-inline/META.yml": [
+    [
+     {}
+    ]
+   ],
    "css/css-layout-api/META.yml": [
     [
      {}
     ]
    ],
    "css/css-layout-api/auto-block-size-absolute-ref.html": [
     [
      {}
@@ -263004,16 +263131,21 @@
      {}
     ]
    ],
    "css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht": [
     [
      {}
     ]
    ],
+   "css/css-text/hyphens/reference/hyphens-auto-001-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-text/i18n/META.yml": [
     [
      {}
     ]
    ],
    "css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html": [
     [
      {}
@@ -272609,16 +272741,21 @@
      {}
     ]
    ],
    "css/support/import-red.css": [
     [
      {}
     ]
    ],
+   "css/support/inheritance-testcommon.js": [
+    [
+     {}
+    ]
+   ],
    "css/support/parsing-testcommon.js": [
     [
      {}
     ]
    ],
    "css/support/pattern-grg-rgr-grg.png": [
     [
      {}
@@ -288564,21 +288701,31 @@
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-auto-margins-ref.html": [
     [
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering-ref.html": [
     [
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-dynamic-update.html": [
     [
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-float-ref.html": [
     [
      {}
@@ -288589,16 +288736,21 @@
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-position-relative-ref.html": [
     [
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/no-red-ref.html": [
     [
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/resources/fieldset-vertical.css": [
     [
      {}
@@ -295684,16 +295836,21 @@
      {}
     ]
    ],
    "pointerevents/pointerlock/resources/pointerevent_movementxy-iframe.html": [
     [
      {}
     ]
    ],
+   "pointerevents/pointerlock/resources/pointerevent_pointermove_in_pointerlock-iframe.html": [
+    [
+     {}
+    ]
+   ],
    "pointerevents/resources/pointerevent_attributes_hoverable_pointers-iframe.html": [
     [
      {}
     ]
    ],
    "pointerevents/resources/pointerevent_fractional_coordinates-iframe.html": [
     [
      {}
@@ -299449,16 +299606,21 @@
      {}
     ]
    ],
    "resources/test/tests/unit/exceptional-cases.html": [
     [
      {}
     ]
    ],
+   "resources/test/tests/unit/test-return-restrictions.html": [
+    [
+     {}
+    ]
+   ],
    "resources/test/tox.ini": [
     [
      {}
     ]
    ],
    "resources/test/variants.js": [
     [
      {}
@@ -326949,22 +327111,16 @@
     ]
    ],
    "background-fetch/content-security-policy.https.window.js": [
     [
      "/background-fetch/content-security-policy.https.window.html",
      {}
     ]
    ],
-   "background-fetch/dangling-markup.https.window.js": [
-    [
-     "/background-fetch/dangling-markup.https.window.html",
-     {}
-    ]
-   ],
    "background-fetch/fetch.https.window.js": [
     [
      "/background-fetch/fetch.https.window.html",
      {}
     ]
    ],
    "background-fetch/get-ids.https.window.js": [
     [
@@ -328817,16 +328973,22 @@
     ]
    ],
    "content-security-policy/connect-src/connect-src-websocket-blocked.sub.html": [
     [
      "/content-security-policy/connect-src/connect-src-websocket-blocked.sub.html",
      {}
     ]
    ],
+   "content-security-policy/connect-src/connect-src-websocket-self.sub.html": [
+    [
+     "/content-security-policy/connect-src/connect-src-websocket-self.sub.html",
+     {}
+    ]
+   ],
    "content-security-policy/connect-src/connect-src-xmlhttprequest-allowed.sub.html": [
     [
      "/content-security-policy/connect-src/connect-src-xmlhttprequest-allowed.sub.html",
      {}
     ]
    ],
    "content-security-policy/connect-src/connect-src-xmlhttprequest-blocked.sub.html": [
     [
@@ -332481,16 +332643,22 @@
     ]
    ],
    "css/css-animations/idlharness.html": [
     [
      "/css/css-animations/idlharness.html",
      {}
     ]
    ],
+   "css/css-animations/inheritance.html": [
+    [
+     "/css/css-animations/inheritance.html",
+     {}
+    ]
+   ],
    "css/css-animations/parsing/animation-delay-invalid.html": [
     [
      "/css/css-animations/parsing/animation-delay-invalid.html",
      {}
     ]
    ],
    "css/css-animations/parsing/animation-delay-valid.html": [
     [
@@ -333749,16 +333917,112 @@
     ]
    ],
    "css/css-flexbox/order_value.html": [
     [
      "/css/css-flexbox/order_value.html",
      {}
     ]
    ],
+   "css/css-flexbox/parsing/flex-basis-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-basis-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-basis-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-basis-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-direction-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-direction-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-direction-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-direction-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-flow-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-flow-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-flow-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-flow-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-grow-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-grow-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-grow-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-grow-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-shrink-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-shrink-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-shrink-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-shrink-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-wrap-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-wrap-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/flex-wrap-valid.html": [
+    [
+     "/css/css-flexbox/parsing/flex-wrap-valid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/order-invalid.html": [
+    [
+     "/css/css-flexbox/parsing/order-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-flexbox/parsing/order-valid.html": [
+    [
+     "/css/css-flexbox/parsing/order-valid.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/percentage-heights-000.html": [
     [
      "/css/css-flexbox/percentage-heights-000.html",
      {}
     ]
    ],
    "css/css-flexbox/percentage-heights-001.html": [
     [
@@ -335575,16 +335839,22 @@
     ]
    ],
    "css/css-images/parsing/object-position-valid.html": [
     [
      "/css/css-images/parsing/object-position-valid.html",
      {}
     ]
    ],
+   "css/css-inline/inheritance.html": [
+    [
+     "/css/css-inline/inheritance.html",
+     {}
+    ]
+   ],
    "css/css-layout-api/at-supports-rule.https.html": [
     [
      "/css/css-layout-api/at-supports-rule.https.html",
      {}
     ]
    ],
    "css/css-layout-api/computed-style-layout-function.https.html": [
     [
@@ -338881,16 +339151,22 @@
     ]
    ],
    "css/css-transitions/idlharness.html": [
     [
      "/css/css-transitions/idlharness.html",
      {}
     ]
    ],
+   "css/css-transitions/inheritance.html": [
+    [
+     "/css/css-transitions/inheritance.html",
+     {}
+    ]
+   ],
    "css/css-transitions/parsing/transition-delay-invalid.html": [
     [
      "/css/css-transitions/parsing/transition-delay-invalid.html",
      {}
     ]
    ],
    "css/css-transitions/parsing/transition-delay-valid.html": [
     [
@@ -343123,16 +343399,22 @@
     ]
    ],
    "css/motion/animation/offset-rotate-interpolation.html": [
     [
      "/css/motion/animation/offset-rotate-interpolation.html",
      {}
     ]
    ],
+   "css/motion/offset-path-serialization.html": [
+    [
+     "/css/motion/offset-path-serialization.html",
+     {}
+    ]
+   ],
    "css/motion/offset-supports-calc.html": [
     [
      "/css/motion/offset-supports-calc.html",
      {}
     ]
    ],
    "css/motion/parsing/offset-anchor-parsing-invalid.html": [
     [
@@ -360953,16 +361235,28 @@
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-multicol.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-multicol.html",
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-percentage-padding.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-percentage-padding.html",
+     {}
+    ]
+   ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-justify-self.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-justify-self.html",
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-text-align.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-text-align.html",
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align.html": [
     [
@@ -360995,16 +361289,22 @@
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-grid-flex-multicol.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-grid-flex-multicol.html",
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-margin-inline.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-margin-inline.html",
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-sans-fieldset-display.html": [
     [
      "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-sans-fieldset-display.html",
      {}
     ]
    ],
    "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.html": [
     [
@@ -366899,22 +367199,16 @@
     ]
    ],
    "html/semantics/scripting-1/the-script-element/fetch-src/failure.html": [
     [
      "/html/semantics/scripting-1/the-script-element/fetch-src/failure.html",
      {}
     ]
    ],
-   "html/semantics/scripting-1/the-script-element/goal-parameter.htm": [
-    [
-     "/html/semantics/scripting-1/the-script-element/goal-parameter.htm",
-     {}
-    ]
-   ],
    "html/semantics/scripting-1/the-script-element/historical.html": [
     [
      "/html/semantics/scripting-1/the-script-element/historical.html",
      {}
     ]
    ],
    "html/semantics/scripting-1/the-script-element/load-error-events-1.html": [
     [
@@ -397513,16 +397807,22 @@
     ]
    ],
    "shadow-dom/event-composed.html": [
     [
      "/shadow-dom/event-composed.html",
      {}
     ]
    ],
+   "shadow-dom/event-dispatch-order.tentative.html": [
+    [
+     "/shadow-dom/event-dispatch-order.tentative.html",
+     {}
+    ]
+   ],
    "shadow-dom/event-inside-shadow-tree.html": [
     [
      "/shadow-dom/event-inside-shadow-tree.html",
      {}
     ]
    ],
    "shadow-dom/event-inside-slotted-node.html": [
     [
@@ -399683,16 +399983,22 @@
     ]
    ],
    "svg/path/property/getComputedStyle.svg": [
     [
      "/svg/path/property/getComputedStyle.svg",
      {}
     ]
    ],
+   "svg/path/property/serialization.svg": [
+    [
+     "/svg/path/property/serialization.svg",
+     {}
+    ]
+   ],
    "svg/scripted/text-attrs-dxdy-have-length.svg": [
     [
      "/svg/scripted/text-attrs-dxdy-have-length.svg",
      {}
     ]
    ],
    "svg/scripted/text-attrs-xyrotate-have-length.svg": [
     [
@@ -400013,16 +400319,22 @@
     ]
    ],
    "trusted-types/DOMParser-parseFromString.tentative.html": [
     [
      "/trusted-types/DOMParser-parseFromString.tentative.html",
      {}
     ]
    ],
+   "trusted-types/DOMWindowTimers-setTimeout-setInterval.tentative.html": [
+    [
+     "/trusted-types/DOMWindowTimers-setTimeout-setInterval.tentative.html",
+     {}
+    ]
+   ],
    "trusted-types/Document-write.tentative.html": [
     [
      "/trusted-types/Document-write.tentative.html",
      {}
     ]
    ],
    "trusted-types/Element-insertAdjacentHTML.tentative.html": [
     [
@@ -400121,16 +400433,22 @@
     ]
    ],
    "trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html": [
     [
      "/trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html",
      {}
     ]
    ],
+   "trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html": [
+    [
+     "/trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html",
+     {}
+    ]
+   ],
    "trusted-types/block-string-assignment-to-Document-write.tentative.html": [
     [
      "/trusted-types/block-string-assignment-to-Document-write.tentative.html",
      {}
     ]
    ],
    "trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html": [
     [
@@ -441692,30 +442010,26 @@
   "background-fetch/META.yml": [
    "8ce9f8faa2acdfe7a2ef8dfc6c1ad8cbdf01c72d",
    "support"
   ],
   "background-fetch/content-security-policy.https.window.js": [
    "0b5b1cb5e94d3f27bebbb5a462bf1e823dfc57b4",
    "testharness"
   ],
-  "background-fetch/dangling-markup.https.window.js": [
-   "764257d7d1a8eecfba6735647637055b21dcb94b",
-   "testharness"
-  ],
   "background-fetch/fetch.https.window.js": [
-   "b730c8909de945e01059ec4de9bcb39a2f9b8b41",
+   "70dacd717a0aac009c708c1157dcfe5149a6a069",
    "testharness"
   ],
   "background-fetch/get-ids.https.window.js": [
    "4c8bf26190719108e65b446a34aafe0aeb1377be",
    "testharness"
   ],
   "background-fetch/get.https.window.js": [
-   "e4dd16cacd2e65a607854988e36b620ec4f1e301",
+   "cd1dc3a6c49264d2cbf5ccc9e499b5f98b36d8cb",
    "testharness"
   ],
   "background-fetch/idlharness.https.any.js": [
    "f2c8a56590aedc52051e7ad8f51b3725091821dc",
    "testharness"
   ],
   "background-fetch/mixed-content-and-allowed-schemes.https.window.js": [
    "9f24f13581819ef444a89cb7549de900f5b98ef6",
@@ -460877,33 +461191,37 @@
    "95b4ce9a19116479a8840517dcfabeaf168729b2",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-beacon-redirect-to-blocked.sub.html": [
    "7328d7a704a3bc803e2497293f61af1d32746fae",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-eventsource-allowed.sub.html": [
-   "3025e8a571a5dbcc016d831e590e5df45b20416e",
+   "8922d99e0392fa6a4ecd30663981208d88e33d1f",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-eventsource-blocked.sub.html": [
-   "9b08365cec961473754beb5592ab7573376b6a0d",
+   "df8a9a1e3db136aaa43c62e8629ff46b1c230dfa",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-eventsource-redirect-to-blocked.sub.html": [
    "32709cd2d4acc8dee18f6d4aa8d4e1a9547f82a3",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-websocket-allowed.sub.html": [
-   "6216444e08ec3555089e5536fc58eff913bec548",
+   "4263d97fe2dfbb9e2a0f0851c07798d40a5671a9",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-websocket-blocked.sub.html": [
-   "249c7a346a4e2bddab6d97f546ec6eeafab7623d",
+   "02c52837bb8bd5cbc26f54f899fe25b5d68bd561",
+   "testharness"
+  ],
+  "content-security-policy/connect-src/connect-src-websocket-self.sub.html": [
+   "6db324ea0e70350b1781b036afc14cc37f588dfc",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-xmlhttprequest-allowed.sub.html": [
    "bde5eeea10acb7117dcf8bb1656cb0b520c5b3c7",
    "testharness"
   ],
   "content-security-policy/connect-src/connect-src-xmlhttprequest-blocked.sub.html": [
    "f4215909d9f505627eb0211b490f69a80eefbc37",
@@ -526056,16 +526374,20 @@
   "css/css-animations/event-order.tentative.html": [
    "93d452ace07163b10af18245d6799d9823448e1e",
    "testharness"
   ],
   "css/css-animations/idlharness.html": [
    "1d3ed2b9b806792c7efaeeee9ab264101dd222bc",
    "testharness"
   ],
+  "css/css-animations/inheritance.html": [
+   "6e7697b9d81db6888077318389e1d70964241b3c",
+   "testharness"
+  ],
   "css/css-animations/parsing/animation-delay-invalid.html": [
    "a58d2cd11bc572d3a7899fb70b8b04adbd76a713",
    "testharness"
   ],
   "css/css-animations/parsing/animation-delay-valid.html": [
    "5ff0416cc64241f81025f39af20d186042d49a30",
    "testharness"
   ],
@@ -530869,17 +531191,17 @@
    "3e4f3dafbf213c18a09cfd2a7512dd485c13f839",
    "reftest"
   ],
   "css/css-contain/contain-layout-independent-formatting-context-001.html": [
    "94c88bc49cb123e98b114136b7836cd6f24c9574",
    "reftest"
   ],
   "css/css-contain/contain-layout-independent-formatting-context-002.html": [
-   "a7e8baed56db08e70c1c536cacd971d3885002b0",
+   "8491e3d28f90f09beb4121eed411d1a3e122284b",
    "reftest"
   ],
   "css/css-contain/contain-layout-independent-formatting-context-003.html": [
    "a10afd3cdee90b988e0c3d25b533d3c1b4e8bb06",
    "reftest"
   ],
   "css/css-contain/contain-layout-ink-overflow-013.html": [
    "d1431737352ef766f333e160888334b9efa0f66b",
@@ -531141,29 +531463,29 @@
    "4d1cbc403bd48b21f8d657d4adc7a50bffbb618c",
    "reftest"
   ],
   "css/css-contain/contain-size-021.html": [
    "14bd8119a0e4bd0bc98ffacc87628616d0891a4a",
    "reftest"
   ],
   "css/css-contain/contain-size-023.html": [
-   "ce795c6402dab0f7e8c5482eb8a670d0afdd7456",
+   "a5492094e8ff437c2496927da3cef551cab738f0",
    "reftest"
   ],
   "css/css-contain/contain-size-025.html": [
-   "5cce18c3bfd4f41988bcfcce4a49848ceeb44acb",
+   "bb17fe2e15f077d357251793c0f31fe2601d49a0",
    "reftest"
   ],
   "css/css-contain/contain-size-027.html": [
-   "266402cb31406fc3f44831e5536a074e6afa5918",
+   "ed4bb24c9b0718c3b5f81425016d9e43076f4bfc",
    "reftest"
   ],
   "css/css-contain/contain-size-041.html": [
-   "44069a0c69b43a7588a89bf127ac9de436710a10",
+   "333a2ceebf99ec6041cad12f68328a5542646169",
    "reftest"
   ],
   "css/css-contain/contain-size-042.html": [
    "e1d6b3ddfcb1da6b86955d04eacbfc68522b0273",
    "reftest"
   ],
   "css/css-contain/contain-size-051.html": [
    "d529df9ff7e7ed6d56819a035e8b40327a5d8617",
@@ -531393,23 +531715,35 @@
    "75ae2bcc2b2d8d019c979c86f2500f9a2ec6ab90",
    "support"
   ],
   "css/css-contain/reference/contain-size-005-ref.html": [
    "001fc900613b81f6ece82548b045a5af79dfadaf",
    "support"
   ],
   "css/css-contain/reference/contain-size-021-ref.html": [
-   "de9cea10501b0014169e8260737f11e387270394",
+   "639daa8437239e6d9cf74bcb5af82755b9e2cdbb",
    "support"
   ],
   "css/css-contain/reference/contain-size-022-ref.html": [
    "3ca9ac9ee4d4edd11249af01607c450b88ee8998",
    "support"
   ],
+  "css/css-contain/reference/contain-size-023-ref.html": [
+   "1073100b0cecdb56cb59687ca63bdccdbee9ec1f",
+   "support"
+  ],
+  "css/css-contain/reference/contain-size-025-ref.html": [
+   "de9cea10501b0014169e8260737f11e387270394",
+   "support"
+  ],
+  "css/css-contain/reference/contain-size-027-ref.html": [
+   "ce4c699dc1b5811aaa689b14ab4781ae0c51acd4",
+   "support"
+  ],
   "css/css-contain/reference/contain-size-051-ref.html": [
    "07455aaed22aef0b4e6d303185a33929053b6c15",
    "support"
   ],
   "css/css-contain/reference/contain-size-056-ref.html": [
    "2e73f0e42b770cc1b486ba6d54a848a71ce6086d",
    "support"
   ],
@@ -536368,16 +536702,80 @@
   "css/css-flexbox/order/order-with-row-reverse.html": [
    "04d28b860a4d3560735ef6397c21ac77501abd3d",
    "reftest"
   ],
   "css/css-flexbox/order_value.html": [
    "6d34ddae30be6558c92fda3990d7aeaada5544e3",
    "testharness"
   ],
+  "css/css-flexbox/parsing/flex-basis-invalid.html": [
+   "502a21c0160f7b6df688cfe79ed503a23a55bbf8",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-basis-valid.html": [
+   "25c91ad0597ed9e6b6f99efcfdbdd7b30f4f77e6",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-direction-invalid.html": [
+   "81fa8beb7eec51d559a96a2278132c1401fbbc4b",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-direction-valid.html": [
+   "75e108478cb362c035979a239e259b337eb43783",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-flow-invalid.html": [
+   "e82c284632dd5b3babbd3979958874013bf3b9aa",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-flow-valid.html": [
+   "01acd435096db60d214a0b07cb24fccdfded9c93",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-grow-invalid.html": [
+   "7af51e77a8562d2d1d7aec91c30a7f698c572f67",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-grow-valid.html": [
+   "9abc79cb796bd71a71ad95fb0fc3d0bf98381377",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-invalid.html": [
+   "ae010d7b4b4540f6591c1ad42f1c89753a71afc6",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-shrink-invalid.html": [
+   "9fa53de05130e104f76f241733f56ec6e6ffc640",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-shrink-valid.html": [
+   "1376e8a43ce1eabc4ce0d08671765c09f376338f",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-valid.html": [
+   "f3eaf118da61d2eb9cd6f30e7f703a0de8053305",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-wrap-invalid.html": [
+   "45b0029402b99438a79b79c2338b0608bcafe819",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/flex-wrap-valid.html": [
+   "a3a56cc2da080a9edffb711106beed031c0bb66f",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/order-invalid.html": [
+   "7a60396b681a5fa56c541439ea7882ff8a81e39a",
+   "testharness"
+  ],
+  "css/css-flexbox/parsing/order-valid.html": [
+   "fa101d7ee8d532e8de516ff9ab0f1147aefaf328",
+   "testharness"
+  ],
   "css/css-flexbox/percentage-heights-000.html": [
    "cf501031448c8d3d020061d29a2c7e6991deea92",
    "testharness"
   ],
   "css/css-flexbox/percentage-heights-001.html": [
    "8a9c0c5e7c6cb27e79488d998b7aa401ad8e6265",
    "testharness"
   ],
@@ -546872,16 +547270,28 @@
   "css/css-images/linear-gradient-2.html": [
    "bdfd96ea85592ddf9c8dbb8ef8d11afaef6d9ef7",
    "reftest"
   ],
   "css/css-images/linear-gradient-ref.html": [
    "6643bb918b5f79ce72d3f16ec999d675e04f59d3",
    "support"
   ],
+  "css/css-images/multiple-position-color-stop-conic.html": [
+   "d1608c0cfe8baa967cb1bdb6cb47b4635bf009b1",
+   "reftest"
+  ],
+  "css/css-images/multiple-position-color-stop-linear.html": [
+   "2e1f9ed7be488a6c6fa68f4bc4252524ddab82b2",
+   "reftest"
+  ],
+  "css/css-images/multiple-position-color-stop-radial.html": [
+   "74e25df788b0736f687f5f6394c31437cc834f57",
+   "reftest"
+  ],
   "css/css-images/parsing/gradient-position-invalid.html": [
    "63ac09fc17ede201f4008f68d5b6c626bbe5f238",
    "testharness"
   ],
   "css/css-images/parsing/gradient-position-valid.html": [
    "9857496fe4407fb0b9b4d6fe32ef9ed982c9cc88",
    "testharness"
   ],
@@ -546920,16 +547330,20 @@
   "css/css-images/parsing/object-position-invalid.html": [
    "63e47cf17a6590d4d26fa111897547a8e9883fb9",
    "testharness"
   ],
   "css/css-images/parsing/object-position-valid.html": [
    "b9dab78b518138272a1e29a6a2690d13f3e208e1",
    "testharness"
   ],
+  "css/css-images/support/100x100-blue-green.html": [
+   "e4f35c3e430cc2994709f6805576d1fe2c1f9cf3",
+   "support"
+  ],
   "css/css-images/support/1x1-green.gif": [
    "e023d92c7ad04264d06196d47a5edd828a7f71db",
    "support"
   ],
   "css/css-images/support/1x1-green.png": [
    "b98ca0ba0a03c580ac339e4a3653539cfa8edc71",
    "support"
   ],
@@ -547116,16 +547530,24 @@
   "css/css-images/tiled-radial-gradients-ref.html": [
    "30046e4f2c0331d5d87ad44b6ea04cabb2213a9f",
    "support"
   ],
   "css/css-images/tiled-radial-gradients.html": [
    "6615e8be358ecf0121a3449664997f1ac1d47e11",
    "reftest"
   ],
+  "css/css-inline/META.yml": [
+   "323d367b9231922b2dec7cf3bdbcdbab9c459ed8",
+   "support"
+  ],
+  "css/css-inline/inheritance.html": [
+   "f4c93b5b2531e0c4b32ed99a34ec71abe33c6cd9",
+   "testharness"
+  ],
   "css/css-layout-api/META.yml": [
    "c85c2d4ccc0b02f3e22ca444952fb7583d96e7c7",
    "support"
   ],
   "css/css-layout-api/at-supports-rule.https.html": [
    "0f23325ce60d67c445745377b443fa0b0de5954d",
    "testharness"
   ],
@@ -551037,17 +551459,17 @@
    "26485da32b751b8c66191f3e64814051bd91c284",
    "support"
   ],
   "css/css-properties-values-api/typedom.tentative.html": [
    "98150558dae5020333dbf0bc7b8cecdbe83dfc36",
    "testharness"
   ],
   "css/css-properties-values-api/unit-cycles.html": [
-   "b5c996a442984ee8f2b8fd9a61e41bf7d194799a",
+   "d65348543c4145bbd693aac67372390bfd91fc11",
    "testharness"
   ],
   "css/css-properties-values-api/url-resolution.html": [
    "d9327896496a1f170570b132dc7797aaea5e2b1f",
    "testharness"
   ],
   "css/css-properties-values-api/var-reference-registered-properties-cycles.html": [
    "58d6c846ae3a4a1c62f239786dfe8827eb921d05",
@@ -556360,16 +556782,24 @@
   "css/css-text/hanging-punctuation/reference/hanging-punctuation-force-end-001-ref.xht": [
    "a9e7e0c1c3e53559b82bd3e10024bf24ad6d45d5",
    "support"
   ],
   "css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht": [
    "1f8aa4a57bb653409c5211b186f0e8b6214525ac",
    "support"
   ],
+  "css/css-text/hyphens/hyphens-auto-001.html": [
+   "39814d3e18c622c022d5e3314580ebb187d217da",
+   "reftest"
+  ],
+  "css/css-text/hyphens/reference/hyphens-auto-001-ref.html": [
+   "c1355d0abba28491a126533c24039cffc6ee4eb0",
+   "support"
+  ],
   "css/css-text/i18n/META.yml": [
    "9fa8c3b6c9bdbfa2b9731c89b23ffc6f47ceba2b",
    "support"
   ],
   "css/css-text/i18n/css3-text-line-break-baspglwj-001.html": [
    "2d927eae085ed301f9450da4f77538fc1481201a",
    "testharness"
   ],
@@ -565952,16 +566382,20 @@
   "css/css-transitions/historical.html": [
    "8d0360a8ecf7a37b81acb10917b63abc7c9543cc",
    "testharness"
   ],
   "css/css-transitions/idlharness.html": [
    "4cc7ee50eb4915fcf95843f7eeee266abfa7b81a",
    "testharness"
   ],
+  "css/css-transitions/inheritance.html": [
+   "986436950e419a1670c75a45ab5eb39b7db6edca",
+   "testharness"
+  ],
   "css/css-transitions/parsing/transition-delay-invalid.html": [
    "b34d50551ce433ebe672c7fddb4a549582c754db",
    "testharness"
   ],
   "css/css-transitions/parsing/transition-delay-valid.html": [
    "d6b42b9c059456f10c425f7217ade6b2cd84c1f4",
    "testharness"
   ],
@@ -579552,16 +579986,20 @@
   "css/motion/offset-path-ray-ref.html": [
    "fde97bd6b15cca64c06cd305822ad87dc008410f",
    "support"
   ],
   "css/motion/offset-path-ray.html": [
    "6c39e7b8f4cfafe05c07d166eb65570432912b7a",
    "reftest"
   ],
+  "css/motion/offset-path-serialization.html": [
+   "1c000919518e4052d1c9546580c9b9cca89883c7",
+   "testharness"
+  ],
   "css/motion/offset-path-string-001.html": [
    "79d957d82b8e3c603ed16598f461a805c90681dd",
    "reftest"
   ],
   "css/motion/offset-path-string-002.html": [
    "0d2fcbbb661c2fe0e5b57ff780d78b2f8b6f627b",
    "reftest"
   ],
@@ -579601,25 +580039,25 @@
    "5b081bcaab7e9d6a72842c4f8f0a504816672b92",
    "testharness"
   ],
   "css/motion/parsing/offset-parsing-invalid.html": [
    "cc2eb500eb88d24055dfe84e0c052546d90e9a29",
    "testharness"
   ],
   "css/motion/parsing/offset-parsing-valid.html": [
-   "b93091b2088987cb402c364e0d3794d847522a16",
+   "3fe8a5b20d805ae3d330b8c653593b6b0c0e0d9e",
    "testharness"
   ],
   "css/motion/parsing/offset-path-parsing-invalid.html": [
    "c69f7c81a9d479c2334769b85d0553f659b8bd83",
    "testharness"
   ],
   "css/motion/parsing/offset-path-parsing-valid.html": [
-   "bda82928726dc8c3a30207f9871a4883e2db5376",
+   "0ed360f6e369e5b53ae4ae0b1b535a7ff8e31ca9",
    "testharness"
   ],
   "css/motion/parsing/offset-position-parsing-invalid.html": [
    "5e749e5b4ec5fe9bb39d1135a9ae74c524677e26",
    "testharness"
   ],
   "css/motion/parsing/offset-position-parsing-valid.html": [
    "d40f70e8c0010cf3f860b3ad760dea5d7b64efdd",
@@ -581608,16 +582046,20 @@
   "css/support/import-green.css": [
    "537104e663364492c6ef388e4afce190e9c5bc58",
    "support"
   ],
   "css/support/import-red.css": [
    "9945ef47114c2841a746c99a2fb1e93e050aac8b",
    "support"
   ],
+  "css/support/inheritance-testcommon.js": [
+   "50bf4c3ae2ba9490fc30af46849820c81b72b774",
+   "support"
+  ],
   "css/support/parsing-testcommon.js": [
    "b075882f89aae49b419220b234534241cde5fd42",
    "support"
   ],
   "css/support/pattern-grg-rgr-grg.png": [
    "9b88fbd81149891234185f54f8b4a0431759f181",
    "support"
   ],
@@ -582281,33 +582723,33 @@
    "36b570f24580adebdeee7afb9c8147e85da4fd2f",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001.html": [
    "191d6b84cb6b98e0b84b6c46ccc0a6f7ce795191",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002-ref.html": [
-   "376e0086f85f195ea583d30b01c214687ec5387e",
+   "40dd0f2f5173b4d8e544c47fba6c2eb62dee4faf",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html": [
    "b9ee7277ab9554e9d515b5c6b7c8badc72260b84",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-001-ref.html": [
    "a14d630680fb8ccf5fcadeda56e7e95954479269",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-001.html": [
    "fa4a56d4088039402b4d22f9aae45da6379fde72",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-002-ref.html": [
-   "cba35db9970673d52937a0bb3cf0e754d5d88e28",
+   "ea15f8de198622cdc8472b28a3c7235b4c921205",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-002.html": [
    "5fca4c308266de9b130faaebc852774f0a8d8c8d",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-001-ref.html": [
    "671b315a147a361e87d67d8be4a210c79aa3e99b",
@@ -582321,25 +582763,25 @@
    "671b315a147a361e87d67d8be4a210c79aa3e99b",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-002.html": [
    "7274c116f3f8d2e3cfab8b5f93a0f9ee04a39b49",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-003-ref.html": [
-   "dc3d2bc4623663790162d67eca6d9ec6101abbdb",
+   "cadaadd95a35752e6a93564349ddb8bafdc2aeda",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-003.html": [
    "f8539f8b9fa40dc83c5e169af2474ab4b920fa81",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-004-ref.html": [
-   "dc3d2bc4623663790162d67eca6d9ec6101abbdb",
+   "cadaadd95a35752e6a93564349ddb8bafdc2aeda",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-004.html": [
    "b891d541596e0dd13ba41a9f19227e54e421b1c1",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-safe-001-ref.html": [
    "5e7fd9f9a9bb52235d9bca30dec9297011dfa06c",
@@ -582361,57 +582803,57 @@
    "ce217e7fffbe933016bb77a0117b0d230c0f276b",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-002.html": [
    "2156f4bbf5191069d9183dfd23697f855f64971f",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-003-ref.html": [
-   "8877789be1ca254a9487faa9845125942562d71a",
+   "7eca626d2df3522741a36169ae2776eb9735c633",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-003.html": [
    "b727d3b37ed6b79ec1cd28a1f362e6eb22e17d3f",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-004-ref.html": [
-   "54d6d46f8207176519ad8dab38bc8a4f07d74edb",
+   "66d0d29f3c3e6bc459bd37d24a0a3448d81fd472",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-004.html": [
    "f350ef8ce6571101ab6b158c36b9baa78fa91c87",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-001-ref.html": [
    "6d197e490fe69c5e45f48d884fe265e5080c97e0",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-001.html": [
    "99cfcbb9b4e65447a5e950d06e7d563e70a46037",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-002-ref.html": [
-   "741a795f20eeb22818e03a8901b1ff3bc2daf011",
+   "f7396eda984b03ffd8660f5287963448c9d552e2",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-002.html": [
    "b82d308af1b7e1c289680a581e706627dd461126",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-001-ref.html": [
    "78d621219ab0fd36fb625252a977ab1166e0c42b",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-001.html": [
    "fdd4f6220be512fa581152960147f01dd0ea2d25",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-002-ref.html": [
-   "a1d4e8163fd9434bcfac71ab020fe9d51242ff30",
+   "e71555d429498deae363039e015ef303aa2c6f68",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-002.html": [
    "61c1b97b51f84040be5647d0dc9a2a2eaff96a43",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-001-ref.html": [
    "8e5db1efc59b812a5ef71b90ccd94bbc7e48f870",
@@ -582425,25 +582867,25 @@
    "c7176a7c21025ac178194c3592ef2fe30e4a86ee",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-002.html": [
    "d7e510e1b5eee5987c49eeec9c11667117d64bb2",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-003-ref.html": [
-   "b38590c8021c41941369ac598ebe83e463cb045d",
+   "8c88ad97b9c456702e2c649b2fb486f06538ca85",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-003.html": [
    "2224ff684aff7ef3c7a5639dd4951e9e8febc450",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-004-ref.html": [
-   "1ebc9e62c976a5c804ad875c83842dbf06a9eb89",
+   "3e4c10049db205a89e0f3462f96ef5386e29296f",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-004.html": [
    "a22fdc24fb72284ac13327d99592cac0edc8198a",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-001-ref.html": [
    "9fff3eabcc0860a981714bb81895b61560861d07",
@@ -582457,25 +582899,25 @@
    "9fff3eabcc0860a981714bb81895b61560861d07",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-002.html": [
    "7b72c6aaeb17bf164263b2dc02cb958ab13a36b6",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-003-ref.html": [
-   "15507845661d589776ef71975dbe26e1bc82aa7c",
+   "c0d0b784415ad0c1b2e8af582aec8825c4860c71",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-003.html": [
    "582262bd3deb174ba560467c971d190d6fceb4a8",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-004-ref.html": [
-   "15507845661d589776ef71975dbe26e1bc82aa7c",
+   "c0d0b784415ad0c1b2e8af582aec8825c4860c71",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-004.html": [
    "63c893b6c4f116a2ab39124220767a64eb424ffa",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/align3/reftest.list": [
    "69ac9afde219ab488d47afd6807e91fb7ed9fd6f",
@@ -583473,17 +583915,17 @@
    "1d32dacacf275cfe4a1e5635d43b84db56b37a95",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin.html": [
    "af90fcab8c8adb1d663676a14b62aee97e098d0c",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list": [
-   "f931e37deaed4091e155621247f12dadfb536939",
+   "3a48ae4bdac346b6dba24c74a542b6f8ef392937",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/ref-common.css": [
    "92d77de0e5da9dac150abfdead25b2ab8345c7f0",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/test-common.css": [
    "dcbf4e1e0240bf622edcea2b7f68f58d89c5ff82",
@@ -586605,17 +587047,17 @@
    "e05938167c4081491c2a9b00d833f458ce044ad0",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002p.html": [
    "3c6b7a3d400b11e4b444d6dbc5437216b3581021",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/images3/reftest.list": [
-   "f7429e372013e09046a722f61afd856a3bd3e604",
+   "1a21c76db1c380794b05300512c5042927c640f5",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-noSize.svg": [
    "db715d875ee4477bb931c2f8b68a385cb6500547",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-parDefault.svg": [
    "1b0bca07375afb8b2eebe0e498fdf02ab2930177",
@@ -588301,17 +588743,17 @@
    "1d487fe3a9826c76846f13f8f574c45c66d5f819",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh": [
    "75f00e6d0a6860e3361e1fdf56777e5125849a9d",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/reftest.list": [
-   "8d1b308ec9cc2da1dff566a455365b58066ada80",
+   "42f029ce03d3421c7162e06814ca1dd915b82158",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/ruby-text-decoration-01-ref.html": [
    "c1b08d05c3e29b02fc69625fd6884e2edf8fb61b",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/ruby-text-decoration-01.html": [
    "943581a921919c9f4636d440c8ebbd6ff3740022",
@@ -607896,16 +608338,20 @@
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-painting-order-ref.html": [
    "13b262a804ab781905b3e4d3483554d9dd95b4d8",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-painting-order.html": [
    "7bd2cedb1b6771398c808d1b7c8f68840bfb9b6f",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-percentage-padding.html": [
+   "61ad4ed4c9445a3c035f85db7ab0928cbb11b91a",
+   "testharness"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-shadow-dom.html": [
    "3b46eb03c6c41bc616ebcc85bc6d635496e5eeed",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-transform-translatez-ref.html": [
    "8200e671944bc74644b1d9d540f9ce7496f86a63",
    "support"
   ],
@@ -607916,16 +608362,20 @@
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-vertical-ref.html": [
    "29c28ea5f89f49cbf8d35961cd29580a0bd42256",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-vertical.html": [
    "c11b466669665a29fc4f33b4bbc14c6b5598d545",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-justify-self.html": [
+   "29df29d17778a9d3d592c5e942f093a44f419b3c",
+   "testharness"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-text-align.html": [
    "01483bf8ad3cee01272ba36bc0ffaf73c1b12cad",
    "testharness"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align.html": [
    "e7745998194730d11840664b61afc6efe0c8039d",
    "testharness"
   ],
@@ -607936,28 +608386,44 @@
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-auto-margins.html": [
    "dd1964ba25133850b764c29037fb1113a7c6736f",
    "reftest"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-formatting-context.html": [
    "4e9539179739a3690aab276f2ba98c25bd4dfe9b",
    "testharness"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins-ref.html": [
+   "e0528855df0705fb9c13461f30e7d8c2dd7fbc21",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-block-margins.html": [
+   "957eca57350eebb6a773dc784b6f701e5327b02d",
+   "reftest"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering-ref.html": [
    "e6eff47e53c7a40e973b7f9dc298af2343f59941",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none-rendering.html": [
    "abf3c45df71ee6617ddb8b6d402a103f54624820",
    "reftest"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-none.html": [
    "689454ac493a05b28658edf549d71c6aa1c7be0e",
    "testharness"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering-ref.html": [
+   "189b195f6154e7afc354726a13f8e79290f6edfb",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering.html": [
+   "ba6610503a6d14cf8d9a7b661cb76f3350317d2c",
+   "reftest"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display.html": [
    "914547fc6cdde3e464b28eb7cc9737d17305f9af",
    "testharness"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-dynamic-update.html": [
    "5dc68244fe0f896388ce7a0ff9d8f49397395078",
    "support"
   ],
@@ -607980,28 +608446,40 @@
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-list-item-ref.html": [
    "ee76e93b64e1da9ab5883c328d9fff1eb865acb1",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-list-item.html": [
    "c26b50ce662cbea50a69e8e718b9b2b889568f64",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-margin-inline.html": [
+   "8daf78db999c8c437d311c8bb2f7b36f590ae277",
+   "testharness"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-position-relative-ref.html": [
    "fd6c11a00566759fbf1e749d49ad396cf1a7ee08",
    "support"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-position-relative.html": [
    "993836126143a8921e31bc8e61bef955eb302503",
    "reftest"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-sans-fieldset-display.html": [
    "5f27ca5c299de72c6e8aab9275fcf70b3c4246a3",
    "testharness"
   ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall-ref.html": [
+   "004ce42129c4652d1f9e75f7953f3e74e98d0fb5",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall.html": [
+   "7b9495946e20d47aaf9fc32692f3663343280e26",
+   "reftest"
+  ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.html": [
    "1cda91f32baf119b8dd827275a3ba8b10c484084",
    "testharness"
   ],
   "html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/min-inline-size.html": [
    "92c33029701b41678f4f514d28c6260338bd8e77",
    "testharness"
   ],
@@ -614020,20 +614498,16 @@
   "html/semantics/scripting-1/the-script-element/fetch-src/failure.html": [
    "b49e51740fac5eac6b60586d8a8623efe06000b9",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/fetch-src/unreachable.js": [
    "ca7fdba71f164c0a1e7c195675497b02f2e0a0a5",
    "support"
   ],
-  "html/semantics/scripting-1/the-script-element/goal-parameter.htm": [
-   "fe4d6759b91b09da5a6c7bb0b53095315d10d4a9",
-   "testharness"
-  ],
   "html/semantics/scripting-1/the-script-element/historical.html": [
    "1f1a91228c2174773243163e5b588e56c2c74fc1",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/is-module-goal.mjs": [
    "b533fc2e906b4b9a0a912044b19ee523ae5eef7a",
    "support"
   ],
@@ -618501,17 +618975,17 @@
    "28fda2cbc251347ba5fddb27cb11aa555d55aa64",
    "support"
   ],
   "interfaces/paint-timing.idl": [
    "a8f065a3dea52974be6b6052604c43efb3f93ccd",
    "support"
   ],
   "interfaces/payment-handler.idl": [
-   "b19a7302dbacaf8ef425e568d5d5409b4b2811d5",
+   "5648ae33457dcdcd2f0eca90d69fbe1a5fac3d5b",
    "support"
   ],
   "interfaces/payment-method-basic-card.idl": [
    "00d6ad1f0a28decdc867023d13f72c45b7d1778f",
    "support"
   ],
   "interfaces/payment-request.idl": [
    "e3afbd47db3b4d2c85a1e53599862b2cb273f828",
@@ -619013,17 +619487,17 @@
    "403d01060821820b9d3a772b28a826698c8796e6",
    "support"
   ],
   "lifecycle/resources/window.html": [
    "69fdbc0986633793d501a4bddfee9e88f76e1348",
    "support"
   ],
   "lint.whitelist": [
-   "837a9d01a75baf99c490af64a0b17978b062e5bd",
+   "e39672d0c6eebcba441c089ce358cf5b2dcf79dd",
    "support"
   ],
   "longtask-timing/META.yml": [
    "91c07f9fd3f3097367f2ad87a2ebb0d98b11d4e2",
    "support"
   ],
   "longtask-timing/idlharness.window.js": [
    "96d939f04e7fbba348bf1edccf64c17c7af0535d",
@@ -620225,17 +620699,17 @@
    "3b86e9de35b449e21992e98e218e629b672a825f",
    "testharness"
   ],
   "mediacapture-fromelement/idlharness.window.js": [
    "c58e63ff12acefc73fc3cfa2f35836778696c827",
    "testharness"
   ],
   "mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.html": [
-   "59a54f51d600fa5c8ee1c092afa5a07f6ae2ae3f",
+   "9ec5261b9dc48ea143c60946d72233a9eb5d7261",
    "testharness"
   ],
   "mediacapture-image/ImageCapture-creation.https.html": [
    "387dceee37a8ee8149f63e5af8c1bd8e5cb8763a",
    "testharness"
   ],
   "mediacapture-image/ImageCapture-grabFrame.html": [
    "bf5e9400a038f2432ce1bee3a3b345f3c82e5ea5",
@@ -620249,45 +620723,45 @@
    "1b4fef5e5a19ed3d7e61320bfa752a19da1f595a",
    "support"
   ],
   "mediacapture-image/MediaStreamTrack-applyConstraints-fast.html": [
    "75ed17a9e3302b0cb2b388846ad73d55c6d9c166",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.html": [
-   "1a1b58e5415f9e04ddeb8d1874448c3b98af35e3",
+   "4900336549f4a92f449233d24abf97f84f055102",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-applyConstraints-reject.html": [
-   "8f08d250fd1a569d84ecd4ce36fe5ffa29f583f7",
+   "21719636ea870f0cfe8e3b8c8a0c838512c1eb87",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-applyConstraints.html": [
-   "8dcff926173266c9e5f14c37ff408832a8a07d7e",
+   "c87f954c682cb6869040ebc67f6245902cec1267",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-getCapabilities-fast.html": [
    "55272d1499517a6fda0b7e06068928b4e1127b27",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-getCapabilities.html": [
-   "eceaf5b1a8665211e42094730619d38143782fdc",
+   "9e34f2389cc4e446767df2aa4991cb43b8c95c7a",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-getConstraints-fast.html": [
-   "5a5ce5f2dad58cb47e8b4215bc563e24b34b3119",
+   "16f869cf119faa7eaa043ff6d65fa31b2998315b",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-getSettings-fast.html": [
    "a4ecbe61181640f7f993f7f5c9cd3fd4e992f72c",
    "testharness"
   ],
   "mediacapture-image/MediaStreamTrack-getSettings.html": [
-   "5e9256eeb740663675ea93d692481556efd97628",
+   "8535f298bfab2ebca9dd3b37a0cc4f8538623203",
    "testharness"
   ],
   "mediacapture-image/detached-HTMLCanvasElement.html": [
    "e27950fc5edfea8357344f91198f0105775fef2c",
    "testharness"
   ],
   "mediacapture-image/getPhotoCapabilities.html": [
    "f416cd670783f727a5ea462c1303a407d40397db",
@@ -630916,22 +631390,26 @@
   "pointerevents/extension/pointerevent_coalesced_events_attributes-manual.html": [
    "89149468a5d23d494f6b09d4142b43c8d1d2289c",
    "manual"
   ],
   "pointerevents/extension/pointerevent_constructor.html": [
    "43d0aa5d38b2ac65b5100c9ba8fa2b794ffd03ee",
    "testharness"
   ],
+  "pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html": [
+   "6efded85b4562bd960a4b5e584e68984a1ce6316",
+   "manual"
+  ],
   "pointerevents/extension/pointerevent_pointerrawmove-manual.html": [
    "0c4ccf9ad48d49c62b3d138845dfa3ac631a8e4e",
    "manual"
   ],
   "pointerevents/extension/pointerevent_pointerrawmove_in_pointerlock-manual.html": [
-   "30fff15bd92f536de826b7ef98419b6a30284d41",
+   "970355863bb7d6affb0b50289af75b98cfccbb57",
    "manual"
   ],
   "pointerevents/extension/pointerevent_touch-action-pan-down-css_touch-manual.html": [
    "592139f9f41abb2a3fe6bf1b99af87d1c5e651f3",
    "manual"
   ],
   "pointerevents/extension/pointerevent_touch-action-pan-left-css_touch-manual.html": [
    "7030d5e487ce3cbf94a2d7004562b518af56f8f0",
@@ -631244,20 +631722,32 @@
   "pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture-manual.html": [
    "8ac35f82856a38e887ec84ad67f5c374014905c5",
    "manual"
   ],
   "pointerevents/pointerlock/pointerevent_pointerlock_supercedes_capture-manual.html": [
    "d8dbeaae172dfd6314341c55ef1fa3b9945f60fc",
    "manual"
   ],
+  "pointerevents/pointerlock/pointerevent_pointermove_in_pointerlock-manual.html": [
+   "80c01d61c8f906364fea0497d32cbd44ad8fdd3f",
+   "manual"
+  ],
+  "pointerevents/pointerlock/pointerevent_pointermove_on_chorded_mouse_button_when_locked-manual.html": [
+   "b2fadbfe05f7efcc674f2b4cac61a15a67560597",
+   "manual"
+  ],
   "pointerevents/pointerlock/resources/pointerevent_movementxy-iframe.html": [
    "627af3b61cad74bb112558169b1e66f6a24b1129",
    "support"
   ],
+  "pointerevents/pointerlock/resources/pointerevent_pointermove_in_pointerlock-iframe.html": [
+   "b7cc7068e96c7dbbf7f8ffa9e5ce9bd1ce63686f",
+   "support"
+  ],
   "pointerevents/resources/pointerevent_attributes_hoverable_pointers-iframe.html": [
    "5e55868282ce5ce549f1d32092839f05bd43aba7",
    "support"
   ],
   "pointerevents/resources/pointerevent_fractional_coordinates-iframe.html": [
    "5245a3f2e16bf13967187231db515433217912aa",
    "support"
   ],
@@ -640381,21 +640871,21 @@
    "6805c323df5a975231648b830e33ce183c3cbbd3",
    "support"
   ],
   "resources/chromium/image_capture-mojom.js.headers": [
    "6c61a34a4ec2e75096db0eb9f7748b142f0db7bb",
    "support"
   ],
   "resources/chromium/image_capture.mojom.js": [
-   "11123feb87d06d53f8e933aa18fad118ae6c8737",
+   "bf8dd16ca75d4be1095808138d09a9c400159b86",
    "support"
   ],
   "resources/chromium/mock-imagecapture.js": [
-   "878f59eb0095b65cabffe95c5538bcb79fa97b0d",
+   "329cbc3a761dc5720b7a67ed09ef68aec6a8bc3d",
    "support"
   ],
   "resources/chromium/mojo_bindings.js": [
    "67d6a8828551c1f703ef29831592f2e4d8a42485",
    "support"
   ],
   "resources/chromium/mojo_bindings.js.headers": [
    "6805c323df5a975231648b830e33ce183c3cbbd3",
@@ -640748,16 +641238,20 @@
   "resources/test/tests/unit/basic.html": [
    "7eef4a8fa5b50547bce915170a9b3e1e0312adf4",
    "support"
   ],
   "resources/test/tests/unit/exceptional-cases.html": [
    "df9e1239a2ec48dd8b489fb7001a5295e334f963",
    "support"
   ],
+  "resources/test/tests/unit/test-return-restrictions.html": [
+   "8472ba9c9ed3f0baed605813fad1717769875759",
+   "support"
+  ],
   "resources/test/tox.ini": [
    "d3a30f870a1572d4423ae99f64c67d63afa345da",
    "support"
   ],
   "resources/test/variants.js": [
    "611d27803447a174bd68b27515d468af0849a13c",
    "support"
   ],
@@ -640781,17 +641275,17 @@
    "5e8f640c6659d176eaca4c71cc1798b7285540b7",
    "support"
   ],
   "resources/testharness.css.headers": [
    "e828b629858d07afd989b80894986315bac16cc7",
    "support"
   ],
   "resources/testharness.js": [
-   "85e211ff60ae559d7ff39994c33a2f05056e1ef2",
+   "352e8b76266b7f5ce3e17278721b55a0fe80a505",
    "support"
   ],
   "resources/testharness.js.headers": [
    "5e8f640c6659d176eaca4c71cc1798b7285540b7",
    "support"
   ],
   "resources/testharnessreport.js": [
    "e5cb40fe0ef652be407d4c48b1c59391864cec7b",
@@ -645852,16 +646346,20 @@
   "shadow-dom/event-composed-path.html": [
    "8c37b1c3bdc1b0e28e69fbe6dca94865bb5f30bd",
    "testharness"
   ],
   "shadow-dom/event-composed.html": [
    "2d6a5e36585b623a89b1e5f4e059d881027a0b94",
    "testharness"
   ],
+  "shadow-dom/event-dispatch-order.tentative.html": [
+   "1e88740f53a2dc25d9650e4f54c3011e2b0e9355",
+   "testharness"
+  ],
   "shadow-dom/event-inside-shadow-tree.html": [
    "a7405a59560c790c5708a7eaa4e65b6669adc0dd",
    "testharness"
   ],
   "shadow-dom/event-inside-slotted-node.html": [
    "56773142ba6f5904af86696e9801aaf4c6f74a9a",
    "testharness"
   ],
@@ -645921,17 +646419,17 @@
    "f0e8ec33ff7162169c00e877f9aba3bba45aebac",
    "support"
   ],
   "shadow-dom/resources/shadow-dom-utils.js": [
    "37c9a9a23cd16ff587cae11b76ff44f97633fa46",
    "support"
   ],
   "shadow-dom/resources/shadow-dom.js": [
-   "3e55684dac1c4fbe1064c6d5d8b8d7ee86224921",
+   "192ad45413035ae629ba8158a5ceaca171af11fa",
    "support"
   ],
   "shadow-dom/scroll-to-the-fragment-in-shadow-tree.html": [
    "c87932eb62f435ecf24cf30229669b569161b1c7",
    "testharness"
   ],
   "shadow-dom/slotchange-customelements.html": [
    "b0cf93277df950f46f6e6a7c5266798c47426651",
@@ -650317,31 +650815,35 @@
    "09d4c70712f8f4cbb37e8dd6d075529500a68179",
    "testharness"
   ],
   "svg/path/property/d-interpolation-single.svg": [
    "107b607beca6056bf7fc21f0b0e954bb37da5c03",
    "testharness"
   ],
   "svg/path/property/getComputedStyle.svg": [
-   "5830191931fb4f7dd0d4e929333248b8d3019e79",
+   "392c570e3525062886f7d4bda0ab7e006e3e370c",
    "testharness"
   ],
   "svg/path/property/priority-ref.svg": [
    "cbc7c385f6d78d492a7d43e45cd03da6b78433f3",
    "support"
   ],
   "svg/path/property/priority.svg": [
    "ef187c191369e894920c7dc38b47d5cc4b984f01",
    "reftest"
   ],
   "svg/path/property/resources/interpolation-test-common.js": [
    "b7f8cd308d7cbcf1aaa4ab686179e456f61c7d6f",
    "support"
   ],
+  "svg/path/property/serialization.svg": [
+   "297f8ede687a28a12ced98a4b89051dd9ddf5090",
+   "testharness"
+  ],
   "svg/pservers/reftests/radialgradient-basic-002-ref.svg": [
    "26f4f508f35855ffd35baffb1aff129fe1ebf1be",
    "support"
   ],
   "svg/pservers/reftests/radialgradient-basic-002.svg": [
    "04d8d3025ee0f039a05bdd439f2dc02c13f49a23",
    "reftest"
   ],
@@ -656141,17 +656643,17 @@
    "0ef13c96e38c46a1bf0636792a9933126d1cad06",
    "support"
   ],
   "tools/wpt/requirements.txt": [
    "46c4b13a3bf3b045fc477272f9b555ef90971e73",
    "support"
   ],
   "tools/wpt/run.py": [
-   "1b8455a92e4931319295fd9a9f66484cc84e67a9",
+   "5bdf6c350656d4746213f1a87c8f9534edc138df",
    "support"
   ],
   "tools/wpt/testfiles.py": [
    "35a4b97d7ffaa24f630d70c6877c719d155c2ec7",
    "support"
   ],
   "tools/wpt/tests/latest_mozilla_central.txt": [
    "7078a36b0c5bd5b4fe6f55f2ecf5fcbc2c535b4f",
@@ -656497,17 +656999,17 @@
    "1c4e3d8cfe95a08433dfd0ee864dc7933871b694",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorservodriver.py": [
    "d015e77b8c72b4f7d1dcc32f6d1c5613f90b959b",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorwebdriver.py": [
-   "127c909e810a26f5d16b268061981c63ee837bb6",
+   "6dde7191ec6059e80a9ad15f96a0eba0573d9b56",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/executorwebkit.py": [
    "c728ae18e03b09f6c690be82efc78bd0c2ff7347",
    "support"
   ],
   "tools/wptrunner/wptrunner/executors/process.py": [
    "fb8c17a96ba04ce601ad3cb9ad4c7588f947e949",
@@ -657116,16 +657618,20 @@
   "touch-events/touch-touchevent-constructor.html": [
    "15b2db735fd0d7a01d9e9bd3a1f3719f790d62e5",
    "testharness"
   ],
   "trusted-types/DOMParser-parseFromString.tentative.html": [
    "2dfc37686bca15431c216a50d29f9f9eed2782e0",
    "testharness"
   ],
+  "trusted-types/DOMWindowTimers-setTimeout-setInterval.tentative.html": [
+   "5bd4003cfdd7c9e2f3be395483ab827ee5b12ef3",
+   "testharness"
+  ],
   "trusted-types/Document-write.tentative.html": [
    "79247fb4d68e6724b98c62d3b62a0e6b20784f4d",
    "testharness"
   ],
   "trusted-types/Element-insertAdjacentHTML.tentative.html": [
    "d5db7936b1f98012ee3750f6d3056f4a5b172615",
    "testharness"
   ],
@@ -657192,16 +657698,20 @@
   "trusted-types/Window-open.tentative.html": [
    "172d566e57fc635b551b5d355661db690869b220",
    "testharness"
   ],
   "trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html": [
    "366bdd2ab6d2d3c24d89cfba2eeea17c045d0a24",
    "testharness"
   ],
+  "trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html": [
+   "f27678ab364c4be2b1ab21e1b4ce1074f02a8c74",
+   "testharness"
+  ],
   "trusted-types/block-string-assignment-to-Document-write.tentative.html": [
    "ce530d49bb6072dc64d9399abc833b7f54d94741",
    "testharness"
   ],
   "trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html": [
    "593d6c64a7c466395972251f5ae0c1ac8a7e1998",
    "testharness"
   ],
@@ -659805,17 +660315,17 @@
    "9d07d53df46164d53b1a3c8f4a10787608c5f014",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [
    "454a294239aa61130dc56af910fe1aa8faf4900b",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html": [
-   "0bcff3bc972181127279f226f8d6a3421cd864b1",
+   "60ea1850fc5c15ea91c075d8929d8a3dce2b4f89",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html": [
    "dd8617503951aafd71015d591914a1b54e3907a7",
    "testharness"
   ],
   "web-animations/timing-model/animations/the-current-time-of-an-animation.html": [
    "a0e1a111fd433761afc25486fe4fb23c0ef1164b",
deleted file mode 100644
--- a/testing/web-platform/meta/background-fetch/dangling-markup.https.window.js.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[dangling-markup.https.window.html]
-  [fetch to URL containing \\n and < should reject]
-    expected: FAIL
-
--- a/testing/web-platform/meta/background-fetch/fetch.https.window.js.ini
+++ b/testing/web-platform/meta/background-fetch/fetch.https.window.js.ini
@@ -15,8 +15,11 @@
     expected: FAIL
 
   [Fetches can have requests with duplicate URLs]
     expected: FAIL
 
   [Fetches can have requests with a body]
     expected: FAIL
 
+  [recordsAvailable is false after onbackgroundfetchsuccess finishes execution.]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-animations/inheritance.html.ini
@@ -0,0 +1,4 @@
+[inheritance.html]
+  [Inheritance of CSS Animations properties]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-contain/contain-size-021.html.ini
@@ -0,0 +1,2 @@
+[contain-size-021.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-contain/contain-size-023.html.ini
@@ -0,0 +1,2 @@
+[contain-size-023.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-contain/contain-size-027.html.ini
@@ -0,0 +1,2 @@
+[contain-size-027.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-basis-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-basis-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-basis with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-basis-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-basis-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-basis with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-direction-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-direction-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-direction with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-direction-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-direction-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-direction with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-flow-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-flow-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-flow with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-flow-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-flow-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-flow with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-grow-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-grow-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-grow with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-grow-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-grow-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-grow with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-shrink-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-shrink-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-shrink with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-shrink-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-shrink-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-shrink with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-wrap-invalid.html.ini
@@ -0,0 +1,4 @@
+[flex-wrap-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-wrap with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/flex-wrap-valid.html.ini
@@ -0,0 +1,4 @@
+[flex-wrap-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing flex-wrap with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/order-invalid.html.ini
@@ -0,0 +1,4 @@
+[order-invalid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing order with invalid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-flexbox/parsing/order-valid.html.ini
@@ -0,0 +1,4 @@
+[order-valid.html]
+  [CSS Flexible Box Layout Module Level 1: parsing order with valid values]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-images/multiple-position-color-stop-conic.html.ini
@@ -0,0 +1,2 @@
+[multiple-position-color-stop-conic.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-images/multiple-position-color-stop-linear.html.ini
@@ -0,0 +1,2 @@
+[multiple-position-color-stop-linear.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-images/multiple-position-color-stop-radial.html.ini
@@ -0,0 +1,2 @@
+[multiple-position-color-stop-radial.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-inline/inheritance.html.ini
@@ -0,0 +1,7 @@
+[inheritance.html]
+  [Property dominant-baseline inherits]
+    expected: FAIL
+
+  [Property dominant-baseline has initial value normal]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-transitions/inheritance.html.ini
@@ -0,0 +1,4 @@
+[inheritance.html]
+  [Inheritance of CSS Transitions properties]
+    expected: FAIL
+
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[vh_not_refreshing_on_chrome.html]
-  expected:
-    if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/motion/offset-path-serialization.html.ini
@@ -0,0 +1,4 @@
+[offset-path-serialization.html]
+  [Motion Path Module Level 1: path serialization]
+    expected: FAIL
+
--- a/testing/web-platform/meta/css/motion/parsing/offset-parsing-valid.html.ini
+++ b/testing/web-platform/meta/css/motion/parsing/offset-parsing-valid.html.ini
@@ -171,8 +171,20 @@
     expected: FAIL
 
   [e.style['offset'\] = "path(\\"M 0 0 H 1\\") auto" should set the property value]
     expected: FAIL
 
   [e.style['offset'\] = "path(\\"m 0 0 h 100\\") 100px 0deg" should set the property value]
     expected: FAIL
 
+  [e.style['offset'\] = "path('m 20 0 h 100') -7rad 8px / auto" should set the property value]
+    expected: FAIL
+
+  [e.style['offset'\] = "path('m 0 30 v 100') -7rad 8px / left top" should set the property value]
+    expected: FAIL
+
+  [e.style['offset'\] = "path(  'm 1 2   v 3.00 z')" should set the property value]
+    expected: FAIL
+
+  [e.style['offset'\] = "path(\\"M 0 0 H 100\\") 100px 0deg" should set the property value]
+    expected: FAIL
+
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-002.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-img-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-img-002.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-rtl-003.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-rtl-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-rtl-004.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-vertWM-003.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-vertWM-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-align-self-vertWM-004.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-002.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-img-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-img-002.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-rtl-003.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-rtl-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-rtl-004.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-003.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-vertWM-003.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-justify-self-vertWM-004.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grid-abspos-staticpos-justify-self-vertWM-004.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002o.html.ini
+++ b/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002o.html.ini
@@ -1,2 +1,4 @@
 [object-position-svg-002o.html]
-  expected: FAIL
+  expected:
+    if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
+    FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-align-justify-self.html.ini
@@ -0,0 +1,19 @@
+[legend-align-justify-self.html]
+  [<fieldset><legend align="cEnTeR">x</legend></fieldset>]
+    expected: FAIL
+
+  [<fieldset><legend align="right">x</legend></fieldset>]
+    expected: FAIL
+
+  [<fieldset><legend align="left">x</legend></fieldset>]
+    expected: FAIL
+
+  [<fieldset><legend align="center">x</legend></fieldset>]
+    expected: FAIL
+
+  [<fieldset><legend align="rIgHt">x</legend></fieldset>]
+    expected: FAIL
+
+  [<fieldset><legend align="lEfT">x</legend></fieldset>]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-display-rendering.html.ini
@@ -0,0 +1,3 @@
+[legend-display-rendering.html]
+  expected: FAIL
+  restart-after: @True
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall.html.ini
@@ -0,0 +1,2 @@
+[legend-tall.html]
+  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/goal-parameter.htm.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-[goal-parameter.htm]
-  [Errors on type=text/javascript when given content-type=text/javascript;goal=]
-    expected: FAIL