Merge mozilla-central to inbound. a=merge CLOSED TREE
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Wed, 09 Jan 2019 11:54:44 +0200
changeset 510137 70e065271c4b6e4bb9d49b3014f557fa970548b8
parent 510136 76f3be6b25e1398e4fdafd67cd2378f12f668b6f (current diff)
parent 510116 26808f3dc0feb87880648c54ba23495d00457287 (diff)
child 510138 e57137ffb27d6f6b7c2a0575e9c46f770491088d
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -100,19 +100,22 @@ bool BasicLayerManager::PushGroupForLaye
 
     // PushGroup/PopGroup do not support non operator over.
     gfxRect rect = aContext->GetClipExtents(gfxContext::eDeviceSpace);
     rect.RoundOut();
     IntRect surfRect;
     ToRect(rect).ToIntRect(&surfRect);
 
     if (!surfRect.IsEmpty()) {
-      RefPtr<DrawTarget> dt =
-          aContext->GetDrawTarget()->CreateSimilarDrawTarget(
-              surfRect.Size(), SurfaceFormat::B8G8R8A8);
+      RefPtr<DrawTarget> dt;
+      if (aContext->GetDrawTarget()->CanCreateSimilarDrawTarget(
+              surfRect.Size(), SurfaceFormat::B8G8R8A8)) {
+        dt = aContext->GetDrawTarget()->CreateSimilarDrawTarget(
+            surfRect.Size(), SurfaceFormat::B8G8R8A8);
+      }
 
       RefPtr<gfxContext> ctx =
           gfxContext::CreateOrNull(dt, ToRect(rect).TopLeft());
       if (!ctx) {
         gfxCriticalNote
             << "BasicLayerManager context problem in PushGroupForLayer "
             << gfx::hexa(dt);
         return false;
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -394,24 +394,29 @@ void AsyncImagePipelineManager::ApplyAsy
 
   aPipeline->mIsChanged = false;
 
   wr::LayoutSize contentSize{aPipeline->mScBounds.Width(),
                              aPipeline->mScBounds.Height()};
   wr::DisplayListBuilder builder(aPipelineId, contentSize);
 
   float opacity = 1.0f;
-  Maybe<wr::WrClipId> referenceFrameId = builder.PushStackingContext(
+  Maybe<wr::WrSpatialId> referenceFrameId = builder.PushStackingContext(
       wr::ToRoundedLayoutRect(aPipeline->mScBounds), nullptr, nullptr, &opacity,
       aPipeline->mScTransform.IsIdentity() ? nullptr : &aPipeline->mScTransform,
       wr::TransformStyle::Flat, nullptr, aPipeline->mMixBlendMode,
       nsTArray<wr::WrFilterOp>(), true,
       // This is fine to do unconditionally because we only push images here.
       wr::RasterSpace::Screen());
 
+  Maybe<wr::SpaceAndClipChainHelper> spaceAndClipChainHelper;
+  if (referenceFrameId) {
+    spaceAndClipChainHelper.emplace(builder, referenceFrameId.ref());
+  }
+
   if (aPipeline->mCurrentTexture && !keys.IsEmpty()) {
     LayoutDeviceRect rect(0, 0, aPipeline->mCurrentTexture->GetSize().width,
                           aPipeline->mCurrentTexture->GetSize().height);
     if (aPipeline->mScaleToSize.isSome()) {
       rect = LayoutDeviceRect(0, 0, aPipeline->mScaleToSize.value().width,
                               aPipeline->mScaleToSize.value().height);
     }
 
@@ -426,16 +431,17 @@ void AsyncImagePipelineManager::ApplyAsy
     } else {
       MOZ_ASSERT(keys.Length() == 1);
       builder.PushImage(wr::ToRoundedLayoutRect(rect),
                         wr::ToRoundedLayoutRect(rect), true, aPipeline->mFilter,
                         keys[0]);
     }
   }
 
+  spaceAndClipChainHelper.reset();
   builder.PopStackingContext(referenceFrameId.isSome());
 
   wr::BuiltDisplayList dl;
   wr::LayoutSize builderContentSize;
   builder.Finalize(builderContentSize, dl);
   aSceneBuilderTxn.SetDisplayList(
       gfx::Color(0.f, 0.f, 0.f, 0.f), aEpoch,
       LayerSize(aPipeline->mScBounds.Width(), aPipeline->mScBounds.Height()),
--- a/gfx/layers/wr/ClipManager.cpp
+++ b/gfx/layers/wr/ClipManager.cpp
@@ -12,22 +12,20 @@
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "nsDisplayList.h"
 #include "nsStyleStructInlines.h"
 #include "UnitTransforms.h"
 
 #define CLIP_LOG(...)
-/*
-#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__)
-#define CLIP_LOG(...) if (XRE_IsContentProcess()) {         \
-                        printf_stderr("CLIP: " __VA_ARGS__) \
-                      }
-*/
+
+//#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__)
+
+//#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
 
 ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {}
 
 void ClipManager::BeginBuild(WebRenderLayerManager* aManager,
                              wr::DisplayListBuilder& aBuilder) {
@@ -61,88 +59,93 @@ void ClipManager::BeginList(const Stacki
       mCacheStack.emplace();
     }
   }
 
   ItemClips clips(nullptr, nullptr, false);
   if (!mItemClipStack.empty()) {
     clips.CopyOutputsFrom(mItemClipStack.top());
   }
+
+  if (aStackingContext.ReferenceFrameId()) {
+    clips.mScrollId = aStackingContext.ReferenceFrameId().ref();
+  }
+
   mItemClipStack.push(clips);
 }
 
 void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
   MOZ_ASSERT(!mItemClipStack.empty());
-  mItemClipStack.top().Unapply(mBuilder);
+  mBuilder->SetClipChainLeaf(Nothing());
   mItemClipStack.pop();
 
   if (aStackingContext.AffectsClipPositioning()) {
     if (aStackingContext.ReferenceFrameId()) {
       PopOverrideForASR(mItemClipStack.empty() ? nullptr
                                                : mItemClipStack.top().mASR);
     } else {
       MOZ_ASSERT(!mCacheStack.empty());
       mCacheStack.pop();
     }
   }
 }
 
 void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                                     const wr::WrClipId& aClipId) {
-  Maybe<wr::WrClipId> scrollId = GetScrollLayer(aASR);
-  MOZ_ASSERT(scrollId.isSome());
+                                     const wr::WrSpatialId& aSpatialId) {
+  Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
+  MOZ_ASSERT(spaceAndClip.isSome());
 
-  CLIP_LOG("Pushing override %zu -> %s\n", scrollId->id,
-           Stringify(aClipId.id).c_str());
-  auto it = mASROverride.insert({*scrollId, std::stack<wr::WrClipId>()});
-  it.first->second.push(aClipId);
+  CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
+           Stringify(aSpatialId.id).c_str());
+
+  auto it =
+      mASROverride.insert({spaceAndClip->space, std::stack<wr::WrSpatialId>()});
+  it.first->second.push(aSpatialId);
 
   // Start a new cache
   mCacheStack.emplace();
 }
 
 void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
   MOZ_ASSERT(!mCacheStack.empty());
   mCacheStack.pop();
 
-  Maybe<wr::WrClipId> scrollId = GetScrollLayer(aASR);
-  MOZ_ASSERT(scrollId.isSome());
+  Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR);
+  MOZ_ASSERT(spaceAndClip.isSome());
 
-  auto it = mASROverride.find(*scrollId);
-  MOZ_ASSERT(it != mASROverride.end());
-  MOZ_ASSERT(!(it->second.empty()));
-  CLIP_LOG("Popping override %zu -> %s\n", scrollId->id,
+  auto it = mASROverride.find(spaceAndClip->space);
+  CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id,
            Stringify(it->second.top().id).c_str());
+
   it->second.pop();
   if (it->second.empty()) {
     mASROverride.erase(it);
   }
 }
 
-Maybe<wr::WrClipId> ClipManager::ClipIdAfterOverride(
-    const Maybe<wr::WrClipId>& aClipId) {
-  if (!aClipId) {
-    return Nothing();
-  }
-  auto it = mASROverride.find(*aClipId);
+wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
+    const wr::WrSpatialId& aSpatialId) {
+  auto it = mASROverride.find(aSpatialId);
   if (it == mASROverride.end()) {
-    return aClipId;
+    return aSpatialId;
   }
   MOZ_ASSERT(!it->second.empty());
-  CLIP_LOG("Overriding %zu with %s\n", aClipId->id,
+  CLIP_LOG("Overriding %zu with %s\n", aSpatialId.id,
            Stringify(it->second.top().id).c_str());
-  return Some(it->second.top());
+
+  return it->second.top();
 }
 
-void ClipManager::BeginItem(nsDisplayItem* aItem,
-                            const StackingContextHelper& aStackingContext) {
-  CLIP_LOG("processing item %p\n", aItem);
-
+wr::WrSpaceAndClipChain ClipManager::SwitchItem(
+    nsDisplayItem* aItem, const StackingContextHelper& aStackingContext) {
   const DisplayItemClipChain* clip = aItem->GetClipChain();
   const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
+  CLIP_LOG("processing item %p (%s) asr %p\n", aItem,
+           DisplayItemTypeName(aItem->GetType()), asr);
+
   DisplayItemType type = aItem->GetType();
   if (type == DisplayItemType::TYPE_STICKY_POSITION) {
     // For sticky position items, the ASR is computed differently depending
     // on whether the item has a fixed descendant or not. But for WebRender
     // purposes we always want to use the ASR that would have been used if it
     // didn't have fixed descendants, which is stored as the "container ASR" on
     // the sticky item.
     asr = static_cast<nsDisplayStickyPosition*>(aItem)->GetContainerASR();
@@ -169,134 +172,127 @@ void ClipManager::BeginItem(nsDisplayIte
   MOZ_ASSERT(!mItemClipStack.empty());
   if (clips.HasSameInputs(mItemClipStack.top())) {
     // Early-exit because if the clips are the same as aItem's previous sibling,
     // then we don't need to do do the work of popping the old stuff and then
     // pushing it right back on for the new item. Note that if aItem doesn't
     // have a previous sibling, that means BeginList would have been called
     // just before this, which will have pushed a ItemClips(nullptr, nullptr)
     // onto mItemClipStack, so the HasSameInputs check should return false.
-    CLIP_LOG("early-exit for %p\n", aItem);
-    return;
+    CLIP_LOG("\tearly-exit for %p\n", aItem);
+    return mItemClipStack.top().GetSpaceAndClipChain();
   }
 
   // Pop aItem's previous sibling's stuff from mBuilder in preparation for
   // pushing aItem's stuff.
-  mItemClipStack.top().Unapply(mBuilder);
   mItemClipStack.pop();
 
   // Zoom display items report their bounds etc using the parent document's
   // APD because zoom items act as a conversion layer between the two different
   // APDs.
   int32_t auPerDevPixel;
   if (type == DisplayItemType::TYPE_ZOOM) {
     auPerDevPixel =
         static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
   } else {
     auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   }
 
   // If the leaf of the clip chain is going to be merged with the display item's
   // clip rect, then we should create a clip chain id from the leaf's parent.
   if (separateLeaf) {
+    CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n");
     clip = clip->mParent;
   }
 
   // There are two ASR chains here that we need to be fully defined. One is the
   // ASR chain pointed to by |asr|. The other is the
   // ASR chain pointed to by clip->mASR. We pick the leafmost
   // of these two chains because that one will include the other. Calling
   // DefineScrollLayers with this leafmost ASR will recursively define all the
   // ASRs that we care about for this item, but will not actually push
   // anything onto the WR stack.
   const ActiveScrolledRoot* leafmostASR = asr;
   if (clip) {
     leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR);
   }
-  Maybe<wr::WrClipId> leafmostId =
+  Maybe<wr::WrSpaceAndClip> leafmostId =
       DefineScrollLayers(leafmostASR, aItem, aStackingContext);
 
   // Define all the clips in the item's clip chain, and obtain a clip chain id
   // for it.
   clips.mClipChainId = DefineClipChain(clip, auPerDevPixel, aStackingContext);
 
-  if (clip) {
-    // If the clip's ASR is different, then we need to set the scroll id
-    // explicitly to match the desired ASR.
-    Maybe<wr::WrClipId> scrollId = GetScrollLayer(asr);
-    MOZ_ASSERT(scrollId.isSome());
-    clips.mScrollId = ClipIdAfterOverride(scrollId);
-  } else {
-    // If we don't have a clip at all, then we don't want to explicitly push
-    // the ASR either, because as with the first clause of this if condition,
-    // the item might get hoisted out of a stacking context that was pushed
-    // between the |asr| and this |aItem|. Instead we just leave clips.mScrollId
-    // empty and things seem to work out.
-    // XXX: there might be cases where things don't just "work out", in which
-    // case we might need to do something smarter here.
-  }
+  Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(asr);
+  MOZ_ASSERT(spaceAndClip.isSome());
+  clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space);
+  CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->space.id,
+           (int)clips.mScrollId.id);
 
   // Now that we have the scroll id and a clip id for the item, push it onto
   // the WR stack.
-  clips.Apply(mBuilder, auPerDevPixel);
+  clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel);
+  auto spaceAndClipChain = clips.GetSpaceAndClipChain();
   mItemClipStack.push(clips);
 
   CLIP_LOG("done setup for %p\n", aItem);
+  return spaceAndClipChain;
 }
 
-Maybe<wr::WrClipId> ClipManager::GetScrollLayer(
+Maybe<wr::WrSpaceAndClip> ClipManager::GetScrollLayer(
     const ActiveScrolledRoot* aASR) {
   for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
-    Maybe<wr::WrClipId> scrollId =
+    Maybe<wr::WrSpaceAndClip> spaceAndClip =
         mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId());
-    if (scrollId) {
-      return scrollId;
+    if (spaceAndClip) {
+      return spaceAndClip;
     }
 
     // If this ASR doesn't have a scroll ID, then we should check its ancestor.
     // There may not be one defined because the ASR may not be scrollable or we
     // failed to get the scroll metadata.
   }
 
-  Maybe<wr::WrClipId> scrollId = mBuilder->GetScrollIdForDefinedScrollLayer(
-      ScrollableLayerGuid::NULL_SCROLL_ID);
-  MOZ_ASSERT(scrollId.isSome());
-  return scrollId;
+  Maybe<wr::WrSpaceAndClip> spaceAndClip =
+      mBuilder->GetScrollIdForDefinedScrollLayer(
+          ScrollableLayerGuid::NULL_SCROLL_ID);
+  MOZ_ASSERT(spaceAndClip.isSome());
+  return spaceAndClip;
 }
 
-Maybe<wr::WrClipId> ClipManager::DefineScrollLayers(
+Maybe<wr::WrSpaceAndClip> ClipManager::DefineScrollLayers(
     const ActiveScrolledRoot* aASR, nsDisplayItem* aItem,
     const StackingContextHelper& aSc) {
   if (!aASR) {
     // Recursion base case
     return Nothing();
   }
   ScrollableLayerGuid::ViewID viewId = aASR->GetViewId();
-  Maybe<wr::WrClipId> scrollId =
+  Maybe<wr::WrSpaceAndClip> spaceAndClip =
       mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
-  if (scrollId) {
+  if (spaceAndClip) {
     // If we've already defined this scroll layer before, we can early-exit
-    return scrollId;
+    return spaceAndClip;
   }
   // Recurse to define the ancestors
-  Maybe<wr::WrClipId> ancestorScrollId =
+  Maybe<wr::WrSpaceAndClip> ancestorSpaceAndClip =
       DefineScrollLayers(aASR->mParent, aItem, aSc);
 
   Maybe<ScrollMetadata> metadata =
       aASR->mScrollableFrame->ComputeScrollMetadata(
           mManager, aItem->ReferenceFrame(), Nothing(), nullptr);
   if (!metadata) {
     MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
-    return ancestorScrollId;
+    return ancestorSpaceAndClip;
   }
 
   FrameMetrics& metrics = metadata->GetMetrics();
   if (!metrics.IsScrollable()) {
     // This item is a scrolling no-op, skip over it in the ASR chain.
-    return ancestorScrollId;
+    return ancestorSpaceAndClip;
   }
 
   LayoutDeviceRect contentRect =
       metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
   LayoutDeviceRect clipBounds = LayoutDeviceRect::FromUnknownRect(
       metrics.GetCompositionBounds().ToUnknownRect());
   // The content rect that we hand to PushScrollLayer should be relative to
   // the same origin as the clipBounds that we hand to PushScrollLayer - that
@@ -304,22 +300,23 @@ Maybe<wr::WrClipId> ClipManager::DefineS
   // However, when we get the scrollable rect from the FrameMetrics, the origin
   // has nothing to do with the position of the frame but instead represents
   // the minimum allowed scroll offset of the scrollable content. While APZ
   // uses this to clamp the scroll position, we don't need to send this to
   // WebRender at all. Instead, we take the position from the composition
   // bounds.
   contentRect.MoveTo(clipBounds.TopLeft());
 
-  Maybe<wr::WrClipId> parent = ClipIdAfterOverride(ancestorScrollId);
-  scrollId = Some(mBuilder->DefineScrollLayer(
-      viewId, parent, wr::ToRoundedLayoutRect(contentRect),
-      wr::ToRoundedLayoutRect(clipBounds)));
-
-  return scrollId;
+  Maybe<wr::WrSpaceAndClip> parent = ancestorSpaceAndClip;
+  if (parent) {
+    parent->space = SpatialIdAfterOverride(parent->space);
+  }
+  return Some(mBuilder->DefineScrollLayer(viewId, parent,
+                                          wr::ToRoundedLayoutRect(contentRect),
+                                          wr::ToRoundedLayoutRect(clipBounds)));
 }
 
 Maybe<wr::WrClipChainId> ClipManager::DefineClipChain(
     const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel,
     const StackingContextHelper& aSc) {
   AutoTArray<wr::WrClipId, 6> clipIds;
   // Iterate through the clips in the current item's clip chain, define them
   // in WR, and put their IDs into |clipIds|.
@@ -340,25 +337,25 @@ Maybe<wr::WrClipChainId> ClipManager::De
     }
 
     LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
         chain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
     nsTArray<wr::ComplexClipRegion> wrRoundedRects;
     chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, aSc,
                                       wrRoundedRects);
 
-    Maybe<wr::WrClipId> scrollId = GetScrollLayer(chain->mASR);
+    Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(chain->mASR);
     // Before calling DefineClipChain we defined the ASRs by calling
     // DefineScrollLayers, so we must have a scrollId here.
-    MOZ_ASSERT(scrollId.isSome());
+    MOZ_ASSERT(spaceAndClip.isSome());
 
     // Define the clip
-    Maybe<wr::WrClipId> parent = ClipIdAfterOverride(scrollId);
+    spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space);
     wr::WrClipId clipId = mBuilder->DefineClip(
-        parent, wr::ToRoundedLayoutRect(clip), &wrRoundedRects);
+        spaceAndClip, wr::ToRoundedLayoutRect(clip), &wrRoundedRects);
     clipIds.AppendElement(clipId);
     cache[chain] = clipId;
     CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id);
   }
 
   // Now find the parent display item's clipchain id
   Maybe<wr::WrClipChainId> parentChainId;
   if (!mItemClipStack.empty()) {
@@ -381,48 +378,45 @@ ClipManager::~ClipManager() {
   MOZ_ASSERT(!mBuilder);
   MOZ_ASSERT(mCacheStack.empty());
   MOZ_ASSERT(mItemClipStack.empty());
 }
 
 ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
                                   const DisplayItemClipChain* aChain,
                                   bool aSeparateLeaf)
-    : mASR(aASR),
-      mChain(aChain),
-      mSeparateLeaf(aSeparateLeaf),
-      mApplied(false) {}
+    : mASR(aASR), mChain(aChain), mSeparateLeaf(aSeparateLeaf) {
+  mScrollId.id = 0;
+}
 
-void ClipManager::ItemClips::Apply(wr::DisplayListBuilder* aBuilder,
-                                   int32_t aAppUnitsPerDevPixel) {
-  MOZ_ASSERT(!mApplied);
-  mApplied = true;
-
+void ClipManager::ItemClips::UpdateSeparateLeaf(
+    wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) {
   Maybe<wr::LayoutRect> clipLeaf;
   if (mSeparateLeaf) {
     MOZ_ASSERT(mChain);
     clipLeaf.emplace(wr::ToRoundedLayoutRect(LayoutDeviceRect::FromAppUnits(
         mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
   }
 
-  aBuilder->PushClipAndScrollInfo(mScrollId.ptrOr(nullptr),
-                                  mClipChainId.ptrOr(nullptr), clipLeaf);
-}
-
-void ClipManager::ItemClips::Unapply(wr::DisplayListBuilder* aBuilder) {
-  if (mApplied) {
-    mApplied = false;
-    aBuilder->PopClipAndScrollInfo(mScrollId.ptrOr(nullptr));
-  }
+  aBuilder.SetClipChainLeaf(clipLeaf);
 }
 
 bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) {
   return mASR == aOther.mASR && mChain == aOther.mChain &&
          mSeparateLeaf == aOther.mSeparateLeaf;
 }
 
 void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) {
   mScrollId = aOther.mScrollId;
   mClipChainId = aOther.mClipChainId;
 }
 
+wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const {
+  auto spaceAndClipChain = wr::RootScrollNodeWithChain();
+  spaceAndClipChain.space = mScrollId;
+  if (mClipChainId) {
+    spaceAndClipChain.clip_chain = mClipChainId->id;
+  }
+  return spaceAndClipChain;
+}
+
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/wr/ClipManager.h
+++ b/gfx/layers/wr/ClipManager.h
@@ -55,32 +55,32 @@ class ClipManager {
 
   void BeginBuild(WebRenderLayerManager* aManager,
                   wr::DisplayListBuilder& aBuilder);
   void EndBuild();
 
   void BeginList(const StackingContextHelper& aStackingContext);
   void EndList(const StackingContextHelper& aStackingContext);
 
-  void BeginItem(nsDisplayItem* aItem,
-                 const StackingContextHelper& aStackingContext);
+  wr::WrSpaceAndClipChain SwitchItem(
+      nsDisplayItem* aItem, const StackingContextHelper& aStackingContext);
   ~ClipManager();
 
   void PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                          const wr::WrClipId& aClipId);
+                          const wr::WrSpatialId& aSpatialId);
   void PopOverrideForASR(const ActiveScrolledRoot* aASR);
 
  private:
-  Maybe<wr::WrClipId> ClipIdAfterOverride(const Maybe<wr::WrClipId>& aClipId);
+  wr::WrSpatialId SpatialIdAfterOverride(const wr::WrSpatialId& aSpatialId);
 
-  Maybe<wr::WrClipId> GetScrollLayer(const ActiveScrolledRoot* aASR);
+  Maybe<wr::WrSpaceAndClip> GetScrollLayer(const ActiveScrolledRoot* aASR);
 
-  Maybe<wr::WrClipId> DefineScrollLayers(const ActiveScrolledRoot* aASR,
-                                         nsDisplayItem* aItem,
-                                         const StackingContextHelper& aSc);
+  Maybe<wr::WrSpaceAndClip> DefineScrollLayers(
+      const ActiveScrolledRoot* aASR, nsDisplayItem* aItem,
+      const StackingContextHelper& aSc);
 
   Maybe<wr::WrClipChainId> DefineClipChain(const DisplayItemClipChain* aChain,
                                            int32_t aAppUnitsPerDevPixel,
                                            const StackingContextHelper& aSc);
 
   WebRenderLayerManager* MOZ_NON_OWNING_REF mManager;
   wr::DisplayListBuilder* mBuilder;
 
@@ -111,39 +111,37 @@ class ClipManager {
   // differently.
   // Any time ClipManager wants to define a new clip as a child of ASR X, it
   // should first check the cache overrides to see if there is a cache override
   // item ((a) or (b) above) that is already a child of X, and then define that
   // clip as a child of Y instead. This map stores X -> Y, which allows
   // ClipManager to do the necessary lookup. Note that there theoretically might
   // be multiple different "Y" clips (in case of nested cache overrides), which
   // is why we need a stack.
-  std::unordered_map<wr::WrClipId, std::stack<wr::WrClipId>> mASROverride;
+  std::unordered_map<wr::WrSpatialId, std::stack<wr::WrSpatialId>> mASROverride;
 
   // This holds some clip state for a single nsDisplayItem
   struct ItemClips {
     ItemClips(const ActiveScrolledRoot* aASR,
               const DisplayItemClipChain* aChain, bool aSeparateLeaf);
 
     // These are the "inputs" - they come from the nsDisplayItem
     const ActiveScrolledRoot* mASR;
     const DisplayItemClipChain* mChain;
     bool mSeparateLeaf;
 
     // These are the "outputs" - they are pushed to WR as needed
-    Maybe<wr::WrClipId> mScrollId;
+    wr::WrSpatialId mScrollId;
     Maybe<wr::WrClipChainId> mClipChainId;
 
-    // State tracking
-    bool mApplied;
-
-    void Apply(wr::DisplayListBuilder* aBuilder, int32_t aAppUnitsPerDevPixel);
-    void Unapply(wr::DisplayListBuilder* aBuilder);
+    void UpdateSeparateLeaf(wr::DisplayListBuilder& aBuilder,
+                            int32_t aAppUnitsPerDevPixel);
     bool HasSameInputs(const ItemClips& aOther);
     void CopyOutputsFrom(const ItemClips& aOther);
+    wr::WrSpaceAndClipChain GetSpaceAndClipChain() const;
   };
 
   // A stack of ItemClips corresponding to the nsDisplayItem ancestry. Each
   // time we recurse into a nsDisplayItem's child list, this stack size
   // increases by one. The topmost item on the stack is for the display item
   // we are currently processing and items deeper on the stack are for that
   // display item's ancestors.
   std::stack<ItemClips> mItemClipStack;
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -69,16 +69,20 @@ StackingContextHelper::StackingContextHe
 
   mReferenceFrameId = mBuilder->PushStackingContext(
       wr::ToLayoutRect(aBounds), aClipNodeId, aAnimation, aOpacityPtr,
       aTransformPtr,
       aIsPreserve3D ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat,
       aPerspectivePtr, wr::ToMixBlendMode(aMixBlendMode), aFilters,
       aBackfaceVisible, rasterSpace);
 
+  if (mReferenceFrameId) {
+    mSpaceAndClipChainHelper.emplace(aBuilder, mReferenceFrameId.ref());
+  }
+
   mAffectsClipPositioning =
       mReferenceFrameId.isSome() || (aBounds.TopLeft() != LayoutDevicePoint());
 
   // If the parent stacking context has a deferred transform item, inherit it
   // into this stacking context, as long as the ASR hasn't changed. Refer to
   // the comments on StackingContextHelper::mDeferredTransformItem for an
   // explanation of what goes in these fields.
   if (aParentSC.mDeferredTransformItem &&
@@ -93,16 +97,17 @@ StackingContextHelper::StackingContextHe
       mDeferredTransformItem = aParentSC.mDeferredTransformItem;
       mDeferredAncestorTransform = aParentSC.mDeferredAncestorTransform;
     }
   }
 }
 
 StackingContextHelper::~StackingContextHelper() {
   if (mBuilder) {
+    mSpaceAndClipChainHelper.reset();
     mBuilder->PopStackingContext(mReferenceFrameId.isSome());
   }
 }
 
 const Maybe<nsDisplayTransform*>&
 StackingContextHelper::GetDeferredTransformItem() const {
   return mDeferredTransformItem;
 }
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -61,32 +61,33 @@ class MOZ_RAII StackingContextHelper {
   const gfx::Matrix& GetSnappingSurfaceTransform() const {
     return mSnappingSurfaceTransform;
   }
 
   const Maybe<nsDisplayTransform*>& GetDeferredTransformItem() const;
   Maybe<gfx::Matrix4x4> GetDeferredTransformMatrix() const;
 
   bool AffectsClipPositioning() const { return mAffectsClipPositioning; }
-  Maybe<wr::WrClipId> ReferenceFrameId() const { return mReferenceFrameId; }
+  Maybe<wr::WrSpatialId> ReferenceFrameId() const { return mReferenceFrameId; }
 
  private:
   wr::DisplayListBuilder* mBuilder;
   gfx::Size mScale;
   gfx::Matrix mInheritedTransform;
 
   // The "snapping surface" defines the space that we want to snap in.
   // You can think of it as the nearest physical surface.
   // Animated transforms create a new snapping surface, so that changes to their
   // transform don't affect the snapping of their contents. Non-animated
   // transforms do *not* create a new snapping surface, so that for example the
   // existence of a non-animated identity transform does not affect snapping.
   gfx::Matrix mSnappingSurfaceTransform;
   bool mAffectsClipPositioning;
-  Maybe<wr::WrClipId> mReferenceFrameId;
+  Maybe<wr::WrSpatialId> mReferenceFrameId;
+  Maybe<wr::SpaceAndClipChainHelper> mSpaceAndClipChainHelper;
 
   // The deferred transform item is used when building the WebRenderScrollData
   // structure. The backstory is that APZ needs to know about transforms that
   // apply to the different APZC instances. Prior to bug 1423370, we would do
   // this by creating a new WebRenderLayerScrollData for each nsDisplayTransform
   // item we encountered. However, this was unnecessarily expensive because it
   // turned out a lot of nsDisplayTransform items didn't have new ASRs defined
   // as descendants, so we'd create the WebRenderLayerScrollData and send it
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1121,28 +1121,34 @@ void Grouper::ConstructGroups(nsDisplayL
 
   nsDisplayItem* item = aList->GetBottom();
   nsDisplayItem* startOfCurrentGroup = item;
   while (item) {
     if (IsItemProbablyActive(item, mDisplayListBuilder)) {
       currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder,
                              aBuilder, aResources, this, startOfCurrentGroup,
                              item);
-      mClipManager.BeginItem(item, aSc);
-      sIndent++;
-      // Note: this call to CreateWebRenderCommands can recurse back into
-      // this function.
-      RenderRootStateManager* manager =
-          aCommandBuilder->mManager->GetRenderRootStateManager();
-      bool createdWRCommands = item->CreateWebRenderCommands(
-          aBuilder, aResources, aSc, manager, mDisplayListBuilder);
-      sIndent--;
-      MOZ_RELEASE_ASSERT(createdWRCommands,
-                         "active transforms should always succeed at creating "
-                         "WebRender commands");
+
+      {
+        auto spaceAndClipChain = mClipManager.SwitchItem(item, aSc);
+        wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
+
+        sIndent++;
+        // Note: this call to CreateWebRenderCommands can recurse back into
+        // this function.
+        RenderRootStateManager* manager =
+            aCommandBuilder->mManager->GetRenderRootStateManager();
+        bool createdWRCommands = item->CreateWebRenderCommands(
+            aBuilder, aResources, aSc, manager, mDisplayListBuilder);
+        sIndent--;
+        MOZ_RELEASE_ASSERT(
+            createdWRCommands,
+            "active transforms should always succeed at creating "
+            "WebRender commands");
+      }
 
       RefPtr<WebRenderGroupData> groupData =
           aCommandBuilder->CreateOrRecycleWebRenderUserData<WebRenderGroupData>(
               item);
 
       // Initialize groupData->mFollowingGroup
       // TODO: compute the group bounds post-grouping, so that they can be
       // tighter for just the sublist that made it into this group.
@@ -1589,17 +1595,20 @@ void WebRenderCommandBuilder::CreateWebR
       // If we're going to create a new layer data for this item, stash the
       // ASR so that if we recurse into a sublist they will know where to stop
       // walking up their ASR chain when building scroll metadata.
       if (forceNewLayerData) {
         mAsrStack.push_back(asr);
       }
     }
 
-    mClipManager.BeginItem(item, aSc);
+    // This is where we emulate the clip/scroll stack that was previously
+    // implemented on the WR display list side.
+    auto spaceAndClipChain = mClipManager.SwitchItem(item, aSc);
+    wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain);
 
     {  // scope restoreDoGrouping
       AutoRestore<bool> restoreDoGrouping(mDoGrouping);
       if (itemType == DisplayItemType::TYPE_SVG_WRAPPER) {
         // Inside an <svg>, all display items that are not LAYER_ACTIVE wrapper
         // display items (like animated transforms / opacity) share the same
         // animated geometry root, so we can combine subsequent items of that
         // type into the same image.
@@ -1700,19 +1709,19 @@ void WebRenderCommandBuilder::CreateWebR
       }
     }
   }
 
   mDumpIndent--;
   mClipManager.EndList(aSc);
 }
 
-void WebRenderCommandBuilder::PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                                                 const wr::WrClipId& aClipId) {
-  mClipManager.PushOverrideForASR(aASR, aClipId);
+void WebRenderCommandBuilder::PushOverrideForASR(
+    const ActiveScrolledRoot* aASR, const wr::WrSpatialId& aSpatialId) {
+  mClipManager.PushOverrideForASR(aASR, aSpatialId);
 }
 
 void WebRenderCommandBuilder::PopOverrideForASR(
     const ActiveScrolledRoot* aASR) {
   mClipManager.PopOverrideForASR(aASR);
 }
 
 Maybe<wr::ImageKey> WebRenderCommandBuilder::CreateImageKey(
--- a/gfx/layers/wr/WebRenderCommandBuilder.h
+++ b/gfx/layers/wr/WebRenderCommandBuilder.h
@@ -55,17 +55,17 @@ class WebRenderCommandBuilder {
                               wr::IpcResourceUpdateQueue& aResourceUpdates,
                               nsDisplayList* aDisplayList,
                               nsDisplayListBuilder* aDisplayListBuilder,
                               WebRenderScrollData& aScrollData,
                               wr::LayoutSize& aContentSize,
                               const nsTArray<wr::WrFilterOp>& aFilters);
 
   void PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                          const wr::WrClipId& aClipId);
+                          const wr::WrSpatialId& aSpatialId);
   void PopOverrideForASR(const ActiveScrolledRoot* aASR);
 
   Maybe<wr::ImageKey> CreateImageKey(
       nsDisplayItem* aItem, ImageContainer* aContainer,
       mozilla::wr::DisplayListBuilder& aBuilder,
       mozilla::wr::IpcResourceUpdateQueue& aResources,
       mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc,
       gfx::IntSize& aSize, const Maybe<LayoutDeviceRect>& aAsyncImageBounds);
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/1513133.html
@@ -0,0 +1,11 @@
+<style>
+#a { scale: 47 9 }
+#b {
+  font: 31em Ahem, serif;
+  border-top-left-radius: 2vmin;
+  mix-blend-mode: color-burn;
+  -webkit-background-clip: text;
+}
+</style>
+<pre id="a">
+<textarea id="b">
--- a/gfx/tests/crashtests/crashtests.list
+++ b/gfx/tests/crashtests/crashtests.list
@@ -172,11 +172,12 @@ load 1470440.html
 load 1478035.html
 load 1490704-1.html
 load 1501518.html
 load 1503986-1.html
 load 1505426-1.html
 load 1508811.html
 load 1508822.html
 load 1509099.html
+load 1513133.html
 load 1496194.html
 load 1509123.html
 load texture-allocator-zero-region.html
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -12,22 +12,20 @@
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/webrender/RenderCompositor.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "mozilla/layers/SynchronousTask.h"
 #include "TextDrawTarget.h"
 
 #define WRDL_LOG(...)
-/*
-#define WRDL_LOG(...) printf_stderr("WRDL(%p): " __VA_ARGS__)
-#define WRDL_LOG(...) if (XRE_IsContentProcess()) {              \
-                        printf_stderr("WRDL(%p): " __VA_ARGS__); \
-                      }
-*/
+
+//#define WRDL_LOG(...) printf_stderr("WRDL(%p): " __VA_ARGS__)
+
+//#define WRDL_LOG(...) if (XRE_IsContentProcess()) printf_stderr("WRDL(%p): " __VA_ARGS__)
 
 namespace mozilla {
 namespace wr {
 
 using layers::Stringify;
 
 MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderMallocSizeOf)
 MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(WebRenderMallocEnclosingSizeOf)
@@ -641,17 +639,18 @@ void WebRenderAPI::SetFrameStartTime(con
 void WebRenderAPI::RunOnRenderThread(UniquePtr<RendererEvent> aEvent) {
   auto event = reinterpret_cast<uintptr_t>(aEvent.release());
   wr_api_send_external_event(mDocHandle, event);
 }
 
 DisplayListBuilder::DisplayListBuilder(PipelineId aId,
                                        const wr::LayoutSize& aContentSize,
                                        size_t aCapacity)
-    : mActiveFixedPosTracker(nullptr) {
+    : mCurrentSpaceAndClipChain(wr::RootScrollNodeWithChain()),
+      mActiveFixedPosTracker(nullptr) {
   MOZ_COUNT_CTOR(DisplayListBuilder);
   mWrState = wr_state_new(aId, aContentSize, aCapacity);
 }
 
 DisplayListBuilder::~DisplayListBuilder() {
   MOZ_COUNT_DTOR(DisplayListBuilder);
   wr_state_delete(mWrState);
 }
@@ -667,17 +666,17 @@ usize DisplayListBuilder::Dump(usize aIn
 }
 
 void DisplayListBuilder::Finalize(wr::LayoutSize& aOutContentSize,
                                   BuiltDisplayList& aOutDisplayList) {
   wr_api_finalize_builder(mWrState, &aOutContentSize, &aOutDisplayList.dl_desc,
                           &aOutDisplayList.dl.inner);
 }
 
-Maybe<wr::WrClipId> DisplayListBuilder::PushStackingContext(
+Maybe<wr::WrSpatialId> DisplayListBuilder::PushStackingContext(
     const wr::LayoutRect& aBounds, const wr::WrClipId* aClipNodeId,
     const WrAnimationProperty* aAnimation, const float* aOpacity,
     const gfx::Matrix4x4* aTransform, wr::TransformStyle aTransformStyle,
     const gfx::Matrix4x4* aPerspective, const wr::MixBlendMode& aMixBlendMode,
     const nsTArray<wr::WrFilterOp>& aFilters, bool aIsBackfaceVisible,
     const wr::RasterSpace& aRasterSpace) {
   MOZ_ASSERT(mClipChainLeaf.isNothing(),
              "Non-empty leaf from clip chain given, but not used with SC!");
@@ -693,25 +692,23 @@ Maybe<wr::WrClipId> DisplayListBuilder::
   }
 
   const wr::LayoutTransform* maybePerspective =
       aPerspective ? &perspective : nullptr;
   WRDL_LOG("PushStackingContext b=%s t=%s\n", mWrState,
            Stringify(aBounds).c_str(),
            aTransform ? Stringify(*aTransform).c_str() : "none");
 
-  bool outIsReferenceFrame = false;
-  uintptr_t outReferenceFrameId = 0;
-  wr_dp_push_stacking_context(
-      mWrState, aBounds, aClipNodeId, aAnimation, aOpacity, maybeTransform,
-      aTransformStyle, maybePerspective, aMixBlendMode, aFilters.Elements(),
-      aFilters.Length(), aIsBackfaceVisible, aRasterSpace, &outIsReferenceFrame,
-      &outReferenceFrameId);
-  return outIsReferenceFrame ? Some(wr::WrClipId{outReferenceFrameId})
-                             : Nothing();
+  auto spatialId = wr_dp_push_stacking_context(
+      mWrState, aBounds, mCurrentSpaceAndClipChain.space, aClipNodeId,
+      aAnimation, aOpacity, maybeTransform, aTransformStyle, maybePerspective,
+      aMixBlendMode, aFilters.Elements(), aFilters.Length(), aIsBackfaceVisible,
+      aRasterSpace);
+
+  return spatialId.id != 0 ? Some(spatialId) : Nothing();
 }
 
 void DisplayListBuilder::PopStackingContext(bool aIsReferenceFrame) {
   WRDL_LOG("PopStackingContext\n", mWrState);
   wr_dp_pop_stacking_context(mWrState, aIsReferenceFrame);
 }
 
 wr::WrClipChainId DisplayListBuilder::DefineClipChain(
@@ -722,158 +719,176 @@ wr::WrClipChainId DisplayListBuilder::De
                              aClips.Elements(), aClips.Length());
   WRDL_LOG("DefineClipChain id=%" PRIu64 " p=%s clips=%zu\n", mWrState,
            clipchainId, aParent ? Stringify(aParent->id).c_str() : "(nil)",
            aClips.Length());
   return wr::WrClipChainId{clipchainId};
 }
 
 wr::WrClipId DisplayListBuilder::DefineClip(
-    const Maybe<wr::WrClipId>& aParentId, const wr::LayoutRect& aClipRect,
+    const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
     const nsTArray<wr::ComplexClipRegion>* aComplex,
     const wr::WrImageMask* aMask) {
-  size_t clip_id =
-      wr_dp_define_clip(mWrState, aParentId.ptrOr(nullptr), aClipRect,
-                        aComplex ? aComplex->Elements() : nullptr,
-                        aComplex ? aComplex->Length() : 0, aMask);
+  WrClipId clipId;
+  if (aParent) {
+    clipId = wr_dp_define_clip_with_parent_clip(
+        mWrState, aParent.ptr(), aClipRect,
+        aComplex ? aComplex->Elements() : nullptr,
+        aComplex ? aComplex->Length() : 0, aMask);
+  } else {
+    clipId = wr_dp_define_clip_with_parent_clip_chain(
+        mWrState, &mCurrentSpaceAndClipChain, aClipRect,
+        aComplex ? aComplex->Elements() : nullptr,
+        aComplex ? aComplex->Length() : 0, aMask);
+  }
+
   WRDL_LOG("DefineClip id=%zu p=%s r=%s m=%p b=%s complex=%zu\n", mWrState,
-           clip_id, aParentId ? Stringify(aParentId->id).c_str() : "(nil)",
+           clipId.id, aParent ? Stringify(aParent->clip.id).c_str() : "(nil)",
            Stringify(aClipRect).c_str(), aMask,
            aMask ? Stringify(aMask->rect).c_str() : "none",
            aComplex ? aComplex->Length() : 0);
-  return wr::WrClipId{clip_id};
+
+  return clipId;
 }
 
-void DisplayListBuilder::PushClip(const wr::WrClipId& aClipId) {
-  WRDL_LOG("PushClip id=%zu\n", mWrState, aClipId.id);
-  wr_dp_push_clip(mWrState, aClipId);
-}
-
-void DisplayListBuilder::PopClip() {
-  WRDL_LOG("PopClip\n", mWrState);
-  wr_dp_pop_clip(mWrState);
-}
-
-wr::WrClipId DisplayListBuilder::DefineStickyFrame(
+wr::WrSpatialId DisplayListBuilder::DefineStickyFrame(
     const wr::LayoutRect& aContentRect, const float* aTopMargin,
     const float* aRightMargin, const float* aBottomMargin,
     const float* aLeftMargin, const StickyOffsetBounds& aVerticalBounds,
     const StickyOffsetBounds& aHorizontalBounds,
     const wr::LayoutVector2D& aAppliedOffset) {
-  size_t id = wr_dp_define_sticky_frame(
-      mWrState, aContentRect, aTopMargin, aRightMargin, aBottomMargin,
-      aLeftMargin, aVerticalBounds, aHorizontalBounds, aAppliedOffset);
+  auto spatialId = wr_dp_define_sticky_frame(
+      mWrState, mCurrentSpaceAndClipChain.space, aContentRect, aTopMargin,
+      aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds,
+      aHorizontalBounds, aAppliedOffset);
+
   WRDL_LOG("DefineSticky id=%zu c=%s t=%s r=%s b=%s l=%s v=%s h=%s a=%s\n",
-           mWrState, id, Stringify(aContentRect).c_str(),
+           mWrState, spatialId.id, Stringify(aContentRect).c_str(),
            aTopMargin ? Stringify(*aTopMargin).c_str() : "none",
            aRightMargin ? Stringify(*aRightMargin).c_str() : "none",
            aBottomMargin ? Stringify(*aBottomMargin).c_str() : "none",
            aLeftMargin ? Stringify(*aLeftMargin).c_str() : "none",
            Stringify(aVerticalBounds).c_str(),
            Stringify(aHorizontalBounds).c_str(),
            Stringify(aAppliedOffset).c_str());
-  return wr::WrClipId{id};
+
+  return spatialId;
 }
 
-Maybe<wr::WrClipId> DisplayListBuilder::GetScrollIdForDefinedScrollLayer(
+Maybe<wr::WrSpaceAndClip> DisplayListBuilder::GetScrollIdForDefinedScrollLayer(
     layers::ScrollableLayerGuid::ViewID aViewId) const {
   if (aViewId == layers::ScrollableLayerGuid::NULL_SCROLL_ID) {
     return Some(wr::RootScrollNode());
   }
 
   auto it = mScrollIds.find(aViewId);
   if (it == mScrollIds.end()) {
     return Nothing();
   }
 
   return Some(it->second);
 }
 
-wr::WrClipId DisplayListBuilder::DefineScrollLayer(
+wr::WrSpaceAndClip DisplayListBuilder::DefineScrollLayer(
     const layers::ScrollableLayerGuid::ViewID& aViewId,
-    const Maybe<wr::WrClipId>& aParentId, const wr::LayoutRect& aContentRect,
-    const wr::LayoutRect& aClipRect) {
+    const Maybe<wr::WrSpaceAndClip>& aParent,
+    const wr::LayoutRect& aContentRect, const wr::LayoutRect& aClipRect) {
   auto it = mScrollIds.find(aViewId);
   if (it != mScrollIds.end()) {
     return it->second;
   }
 
+  auto rootSpaceAndClip = wr::RootScrollNode();
   // We haven't defined aViewId before, so let's define it now.
-  size_t numericScrollId = wr_dp_define_scroll_layer(
-      mWrState, aViewId, aParentId.ptrOr(nullptr), aContentRect, aClipRect);
+  auto spaceAndClip = wr_dp_define_scroll_layer(
+      mWrState, aViewId, aParent ? aParent.ptr() : &rootSpaceAndClip,
+      aContentRect, aClipRect);
 
   WRDL_LOG("DefineScrollLayer id=%" PRIu64 "/%zu p=%s co=%s cl=%s\n", mWrState,
-           aViewId, numericScrollId,
-           aParentId ? Stringify(aParentId->id).c_str() : "(nil)",
+           aViewId, spaceAndClip.space.id,
+           aParent ? Stringify(aParent->space.id).c_str() : "(nil)",
            Stringify(aContentRect).c_str(), Stringify(aClipRect).c_str());
 
-  auto clipId = wr::WrClipId{numericScrollId};
-  mScrollIds[aViewId] = clipId;
-  return clipId;
-}
-
-void DisplayListBuilder::PushClipAndScrollInfo(
-    const wr::WrClipId* aScrollId, const wr::WrClipChainId* aClipChainId,
-    const Maybe<wr::LayoutRect>& aClipChainLeaf) {
-  if (aScrollId) {
-    WRDL_LOG("PushClipAndScroll s=%zu c=%s\n", mWrState, aScrollId->id,
-             aClipChainId ? Stringify(aClipChainId->id).c_str() : "none");
-    wr_dp_push_clip_and_scroll_info(
-        mWrState, *aScrollId, aClipChainId ? &(aClipChainId->id) : nullptr);
-  }
-  mClipChainLeaf = aClipChainLeaf;
-}
-
-void DisplayListBuilder::PopClipAndScrollInfo(const wr::WrClipId* aScrollId) {
-  if (aScrollId) {
-    WRDL_LOG("PopClipAndScroll\n", mWrState);
-    wr_dp_pop_clip_and_scroll_info(mWrState);
-  }
-  mClipChainLeaf.reset();
+  mScrollIds[aViewId] = spaceAndClip;
+  return spaceAndClip;
 }
 
 void DisplayListBuilder::PushRect(const wr::LayoutRect& aBounds,
                                   const wr::LayoutRect& aClip,
                                   bool aIsBackfaceVisible,
                                   const wr::ColorF& aColor) {
   wr::LayoutRect clip = MergeClipLeaf(aClip);
   WRDL_LOG("PushRect b=%s cl=%s c=%s\n", mWrState, Stringify(aBounds).c_str(),
            Stringify(clip).c_str(), Stringify(aColor).c_str());
-  wr_dp_push_rect(mWrState, aBounds, clip, aIsBackfaceVisible, aColor);
+  wr_dp_push_rect(mWrState, aBounds, clip, aIsBackfaceVisible,
+                  &mCurrentSpaceAndClipChain, aColor);
+}
+
+void DisplayListBuilder::PushRoundedRect(const wr::LayoutRect& aBounds,
+                                         const wr::LayoutRect& aClip,
+                                         bool aIsBackfaceVisible,
+                                         const wr::ColorF& aColor) {
+  wr::LayoutRect clip = MergeClipLeaf(aClip);
+  WRDL_LOG("PushRoundedRect b=%s cl=%s c=%s\n", mWrState,
+           Stringify(aBounds).c_str(), Stringify(clip).c_str(),
+           Stringify(aColor).c_str());
+
+  AutoTArray<wr::ComplexClipRegion, 1> clips;
+  clips.AppendElement(wr::SimpleRadii(aBounds, aBounds.size.width / 2));
+  // TODO: use `mCurrentSpaceAndClipChain.clip_chain` as a parent?
+  auto clipId = DefineClip(Nothing(), aBounds, &clips, nullptr);
+  auto spaceAndClip = WrSpaceAndClip{mCurrentSpaceAndClipChain.space, clipId};
+
+  wr_dp_push_rect_with_parent_clip(mWrState, aBounds, clip, aIsBackfaceVisible,
+                                   &spaceAndClip, aColor);
 }
 
 void DisplayListBuilder::PushClearRect(const wr::LayoutRect& aBounds) {
   wr::LayoutRect clip = MergeClipLeaf(aBounds);
   WRDL_LOG("PushClearRect b=%s c=%s\n", mWrState, Stringify(aBounds).c_str(),
            Stringify(clip).c_str());
-  wr_dp_push_clear_rect(mWrState, aBounds, clip);
+  wr_dp_push_clear_rect(mWrState, aBounds, clip, &mCurrentSpaceAndClipChain);
+}
+
+void DisplayListBuilder::PushClearRectWithComplexRegion(
+    const wr::LayoutRect& aBounds, const wr::ComplexClipRegion& aRegion) {
+  wr::LayoutRect clip = MergeClipLeaf(aBounds);
+  WRDL_LOG("PushClearRectWithComplexRegion b=%s c=%s\n", mWrState,
+           Stringify(aBounds).c_str(), Stringify(clip).c_str());
+
+  AutoTArray<wr::ComplexClipRegion, 1> clips;
+  auto clipId = DefineClip(Nothing(), aBounds, &clips, nullptr);
+  auto spaceAndClip = WrSpaceAndClip{mCurrentSpaceAndClipChain.space, clipId};
+
+  wr_dp_push_clear_rect_with_parent_clip(mWrState, aBounds, clip,
+                                         &spaceAndClip);
 }
 
 void DisplayListBuilder::PushLinearGradient(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutPoint& aStartPoint,
     const wr::LayoutPoint& aEndPoint, const nsTArray<wr::GradientStop>& aStops,
     wr::ExtendMode aExtendMode, const wr::LayoutSize aTileSize,
     const wr::LayoutSize aTileSpacing) {
-  wr_dp_push_linear_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
-                             aIsBackfaceVisible, aStartPoint, aEndPoint,
-                             aStops.Elements(), aStops.Length(), aExtendMode,
-                             aTileSize, aTileSpacing);
+  wr_dp_push_linear_gradient(
+      mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+      &mCurrentSpaceAndClipChain, aStartPoint, aEndPoint, aStops.Elements(),
+      aStops.Length(), aExtendMode, aTileSize, aTileSpacing);
 }
 
 void DisplayListBuilder::PushRadialGradient(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutPoint& aCenter,
     const wr::LayoutSize& aRadius, const nsTArray<wr::GradientStop>& aStops,
     wr::ExtendMode aExtendMode, const wr::LayoutSize aTileSize,
     const wr::LayoutSize aTileSpacing) {
-  wr_dp_push_radial_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
-                             aIsBackfaceVisible, aCenter, aRadius,
-                             aStops.Elements(), aStops.Length(), aExtendMode,
-                             aTileSize, aTileSpacing);
+  wr_dp_push_radial_gradient(
+      mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+      &mCurrentSpaceAndClipChain, aCenter, aRadius, aStops.Elements(),
+      aStops.Length(), aExtendMode, aTileSize, aTileSpacing);
 }
 
 void DisplayListBuilder::PushImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, wr::ImageRendering aFilter, wr::ImageKey aImage,
     bool aPremultipliedAlpha, const wr::ColorF& aColor) {
   wr::LayoutSize size;
   size.width = aBounds.size.width;
@@ -886,156 +901,163 @@ void DisplayListBuilder::PushImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutSize& aStretchSize,
     const wr::LayoutSize& aTileSpacing, wr::ImageRendering aFilter,
     wr::ImageKey aImage, bool aPremultipliedAlpha, const wr::ColorF& aColor) {
   wr::LayoutRect clip = MergeClipLeaf(aClip);
   WRDL_LOG("PushImage b=%s cl=%s s=%s t=%s\n", mWrState,
            Stringify(aBounds).c_str(), Stringify(clip).c_str(),
            Stringify(aStretchSize).c_str(), Stringify(aTileSpacing).c_str());
-  wr_dp_push_image(mWrState, aBounds, clip, aIsBackfaceVisible, aStretchSize,
-                   aTileSpacing, aFilter, aImage, aPremultipliedAlpha, aColor);
+  wr_dp_push_image(mWrState, aBounds, clip, aIsBackfaceVisible,
+                   &mCurrentSpaceAndClipChain, aStretchSize, aTileSpacing,
+                   aFilter, aImage, aPremultipliedAlpha, aColor);
 }
 
 void DisplayListBuilder::PushYCbCrPlanarImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
     wr::ImageKey aImageChannel1, wr::ImageKey aImageChannel2,
     wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
     wr::ImageRendering aRendering) {
   wr_dp_push_yuv_planar_image(mWrState, aBounds, MergeClipLeaf(aClip),
-                              aIsBackfaceVisible, aImageChannel0,
-                              aImageChannel1, aImageChannel2, aColorDepth,
-                              aColorSpace, aRendering);
+                              aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+                              aImageChannel0, aImageChannel1, aImageChannel2,
+                              aColorDepth, aColorSpace, aRendering);
 }
 
 void DisplayListBuilder::PushNV12Image(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
     wr::ImageKey aImageChannel1, wr::WrColorDepth aColorDepth,
     wr::WrYuvColorSpace aColorSpace, wr::ImageRendering aRendering) {
   wr_dp_push_yuv_NV12_image(mWrState, aBounds, MergeClipLeaf(aClip),
-                            aIsBackfaceVisible, aImageChannel0, aImageChannel1,
-                            aColorDepth, aColorSpace, aRendering);
+                            aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+                            aImageChannel0, aImageChannel1, aColorDepth,
+                            aColorSpace, aRendering);
 }
 
 void DisplayListBuilder::PushYCbCrInterleavedImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
     wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
     wr::ImageRendering aRendering) {
   wr_dp_push_yuv_interleaved_image(mWrState, aBounds, MergeClipLeaf(aClip),
-                                   aIsBackfaceVisible, aImageChannel0,
+                                   aIsBackfaceVisible,
+                                   &mCurrentSpaceAndClipChain, aImageChannel0,
                                    aColorDepth, aColorSpace, aRendering);
 }
 
 void DisplayListBuilder::PushIFrame(const wr::LayoutRect& aBounds,
                                     bool aIsBackfaceVisible,
                                     PipelineId aPipeline,
                                     bool aIgnoreMissingPipeline) {
   wr_dp_push_iframe(mWrState, aBounds, MergeClipLeaf(aBounds),
-                    aIsBackfaceVisible, aPipeline, aIgnoreMissingPipeline);
+                    aIsBackfaceVisible, &mCurrentSpaceAndClipChain, aPipeline,
+                    aIgnoreMissingPipeline);
 }
 
 void DisplayListBuilder::PushBorder(const wr::LayoutRect& aBounds,
                                     const wr::LayoutRect& aClip,
                                     bool aIsBackfaceVisible,
                                     const wr::LayoutSideOffsets& aWidths,
                                     const Range<const wr::BorderSide>& aSides,
                                     const wr::BorderRadius& aRadius,
                                     wr::AntialiasBorder aAntialias) {
   MOZ_ASSERT(aSides.length() == 4);
   if (aSides.length() != 4) {
     return;
   }
   wr_dp_push_border(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
-                    aAntialias, aWidths, aSides[0], aSides[1], aSides[2],
-                    aSides[3], aRadius);
+                    &mCurrentSpaceAndClipChain, aAntialias, aWidths, aSides[0],
+                    aSides[1], aSides[2], aSides[3], aRadius);
 }
 
 void DisplayListBuilder::PushBorderImage(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
     wr::ImageKey aImage, const int32_t aWidth, const int32_t aHeight,
     const wr::SideOffsets2D<int32_t>& aSlice,
     const wr::SideOffsets2D<float>& aOutset,
     const wr::RepeatMode& aRepeatHorizontal,
     const wr::RepeatMode& aRepeatVertical) {
   wr_dp_push_border_image(mWrState, aBounds, MergeClipLeaf(aClip),
-                          aIsBackfaceVisible, aWidths, aImage, aWidth, aHeight,
-                          aSlice, aOutset, aRepeatHorizontal, aRepeatVertical);
+                          aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+                          aWidths, aImage, aWidth, aHeight, aSlice, aOutset,
+                          aRepeatHorizontal, aRepeatVertical);
 }
 
 void DisplayListBuilder::PushBorderGradient(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
     const int32_t aWidth, const int32_t aHeight,
     const wr::SideOffsets2D<int32_t>& aSlice,
     const wr::LayoutPoint& aStartPoint, const wr::LayoutPoint& aEndPoint,
     const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
     const wr::SideOffsets2D<float>& aOutset) {
-  wr_dp_push_border_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
-                             aIsBackfaceVisible, aWidths, aWidth, aHeight,
-                             aSlice, aStartPoint, aEndPoint, aStops.Elements(),
-                             aStops.Length(), aExtendMode, aOutset);
+  wr_dp_push_border_gradient(
+      mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+      &mCurrentSpaceAndClipChain, aWidths, aWidth, aHeight, aSlice, aStartPoint,
+      aEndPoint, aStops.Elements(), aStops.Length(), aExtendMode, aOutset);
 }
 
 void DisplayListBuilder::PushBorderRadialGradient(
     const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
     const wr::LayoutPoint& aCenter, const wr::LayoutSize& aRadius,
     const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
     const wr::SideOffsets2D<float>& aOutset) {
-  wr_dp_push_border_radial_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
-                                    aIsBackfaceVisible, aWidths, aCenter,
-                                    aRadius, aStops.Elements(), aStops.Length(),
-                                    aExtendMode, aOutset);
+  wr_dp_push_border_radial_gradient(
+      mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+      &mCurrentSpaceAndClipChain, aWidths, aCenter, aRadius, aStops.Elements(),
+      aStops.Length(), aExtendMode, aOutset);
 }
 
 void DisplayListBuilder::PushText(const wr::LayoutRect& aBounds,
                                   const wr::LayoutRect& aClip,
                                   bool aIsBackfaceVisible,
                                   const wr::ColorF& aColor,
                                   wr::FontInstanceKey aFontKey,
                                   Range<const wr::GlyphInstance> aGlyphBuffer,
                                   const wr::GlyphOptions* aGlyphOptions) {
   wr_dp_push_text(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
-                  aColor, aFontKey, &aGlyphBuffer[0], aGlyphBuffer.length(),
-                  aGlyphOptions);
+                  &mCurrentSpaceAndClipChain, aColor, aFontKey,
+                  &aGlyphBuffer[0], aGlyphBuffer.length(), aGlyphOptions);
 }
 
 void DisplayListBuilder::PushLine(const wr::LayoutRect& aClip,
                                   bool aIsBackfaceVisible,
                                   const wr::Line& aLine) {
   wr::LayoutRect clip = MergeClipLeaf(aClip);
-  wr_dp_push_line(mWrState, &clip, aIsBackfaceVisible, &aLine.bounds,
+  wr_dp_push_line(mWrState, &clip, aIsBackfaceVisible,
+                  &mCurrentSpaceAndClipChain, &aLine.bounds,
                   aLine.wavyLineThickness, aLine.orientation, &aLine.color,
                   aLine.style);
 }
 
 void DisplayListBuilder::PushShadow(const wr::LayoutRect& aRect,
                                     const wr::LayoutRect& aClip,
                                     bool aIsBackfaceVisible,
                                     const wr::Shadow& aShadow) {
   wr_dp_push_shadow(mWrState, aRect, MergeClipLeaf(aClip), aIsBackfaceVisible,
-                    aShadow);
+                    &mCurrentSpaceAndClipChain, aShadow);
 }
 
 void DisplayListBuilder::PopAllShadows() { wr_dp_pop_all_shadows(mWrState); }
 
 void DisplayListBuilder::PushBoxShadow(
     const wr::LayoutRect& aRect, const wr::LayoutRect& aClip,
     bool aIsBackfaceVisible, const wr::LayoutRect& aBoxBounds,
     const wr::LayoutVector2D& aOffset, const wr::ColorF& aColor,
     const float& aBlurRadius, const float& aSpreadRadius,
     const wr::BorderRadius& aBorderRadius,
     const wr::BoxShadowClipMode& aClipMode) {
   wr_dp_push_box_shadow(mWrState, aRect, MergeClipLeaf(aClip),
-                        aIsBackfaceVisible, aBoxBounds, aOffset, aColor,
-                        aBlurRadius, aSpreadRadius, aBorderRadius, aClipMode);
+                        aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+                        aBoxBounds, aOffset, aColor, aBlurRadius, aSpreadRadius,
+                        aBorderRadius, aClipMode);
 }
 
 Maybe<layers::ScrollableLayerGuid::ViewID>
 DisplayListBuilder::GetContainingFixedPosScrollTarget(
     const ActiveScrolledRoot* aAsr) {
   return mActiveFixedPosTracker
              ? mActiveFixedPosTracker->GetScrollTargetForASR(aAsr)
              : Nothing();
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -323,64 +323,62 @@ class DisplayListBuilder {
   void Restore();
   void ClearSave();
   usize Dump(usize aIndent, const Maybe<usize>& aStart,
              const Maybe<usize>& aEnd);
 
   void Finalize(wr::LayoutSize& aOutContentSize,
                 wr::BuiltDisplayList& aOutDisplayList);
 
-  Maybe<wr::WrClipId> PushStackingContext(
+  Maybe<wr::WrSpatialId> PushStackingContext(
       const wr::LayoutRect&
           aBounds,  // TODO: We should work with strongly typed rects
       const wr::WrClipId* aClipNodeId,
       const wr::WrAnimationProperty* aAnimation, const float* aOpacity,
       const gfx::Matrix4x4* aTransform, wr::TransformStyle aTransformStyle,
       const gfx::Matrix4x4* aPerspective, const wr::MixBlendMode& aMixBlendMode,
       const nsTArray<wr::WrFilterOp>& aFilters, bool aIsBackfaceVisible,
       const wr::RasterSpace& aRasterSpace);
   void PopStackingContext(bool aIsReferenceFrame);
 
   wr::WrClipChainId DefineClipChain(const Maybe<wr::WrClipChainId>& aParent,
                                     const nsTArray<wr::WrClipId>& aClips);
 
   wr::WrClipId DefineClip(
-      const Maybe<wr::WrClipId>& aParentId, const wr::LayoutRect& aClipRect,
+      const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
       const nsTArray<wr::ComplexClipRegion>* aComplex = nullptr,
       const wr::WrImageMask* aMask = nullptr);
-  void PushClip(const wr::WrClipId& aClipId);
-  void PopClip();
 
-  wr::WrClipId DefineStickyFrame(const wr::LayoutRect& aContentRect,
-                                 const float* aTopMargin,
-                                 const float* aRightMargin,
-                                 const float* aBottomMargin,
-                                 const float* aLeftMargin,
-                                 const StickyOffsetBounds& aVerticalBounds,
-                                 const StickyOffsetBounds& aHorizontalBounds,
-                                 const wr::LayoutVector2D& aAppliedOffset);
+  wr::WrSpatialId DefineStickyFrame(const wr::LayoutRect& aContentRect,
+                                    const float* aTopMargin,
+                                    const float* aRightMargin,
+                                    const float* aBottomMargin,
+                                    const float* aLeftMargin,
+                                    const StickyOffsetBounds& aVerticalBounds,
+                                    const StickyOffsetBounds& aHorizontalBounds,
+                                    const wr::LayoutVector2D& aAppliedOffset);
 
-  Maybe<wr::WrClipId> GetScrollIdForDefinedScrollLayer(
+  Maybe<wr::WrSpaceAndClip> GetScrollIdForDefinedScrollLayer(
       layers::ScrollableLayerGuid::ViewID aViewId) const;
-  wr::WrClipId DefineScrollLayer(
+  wr::WrSpaceAndClip DefineScrollLayer(
       const layers::ScrollableLayerGuid::ViewID& aViewId,
-      const Maybe<wr::WrClipId>& aParentId,
+      const Maybe<wr::WrSpaceAndClip>& aParent,
       const wr::LayoutRect&
           aContentRect,  // TODO: We should work with strongly typed rects
       const wr::LayoutRect& aClipRect);
 
-  void PushClipAndScrollInfo(const wr::WrClipId* aScrollId,
-                             const wr::WrClipChainId* aClipChainId,
-                             const Maybe<wr::LayoutRect>& aClipChainLeaf);
-  void PopClipAndScrollInfo(const wr::WrClipId* aScrollId);
-
   void PushRect(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                 bool aIsBackfaceVisible, const wr::ColorF& aColor);
+  void PushRoundedRect(const wr::LayoutRect& aBounds,
+                       const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+                       const wr::ColorF& aColor);
 
   void PushClearRect(const wr::LayoutRect& aBounds);
+  void PushClearRectWithComplexRegion(const wr::LayoutRect& aBounds,
+                                      const wr::ComplexClipRegion& aRegion);
 
   void PushLinearGradient(const wr::LayoutRect& aBounds,
                           const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                           const wr::LayoutPoint& aStartPoint,
                           const wr::LayoutPoint& aEndPoint,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::LayoutSize aTileSize,
@@ -505,16 +503,20 @@ class DisplayListBuilder {
       wr::IpcResourceUpdateQueue& aResources,
       const layers::StackingContextHelper& aSc,
       layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
       nsRect& aBounds, const gfx::Point& aDeviceOffset);
 
   // Try to avoid using this when possible.
   wr::WrState* Raw() { return mWrState; }
 
+  void SetClipChainLeaf(const Maybe<wr::LayoutRect>& aClipRect) {
+    mClipChainLeaf = aClipRect;
+  }
+
   // A chain of RAII objects, each holding a (ASR, ViewID) tuple of data. The
   // topmost object is pointed to by the mActiveFixedPosTracker pointer in
   // the wr::DisplayListBuilder.
   class MOZ_RAII FixedPosScrollTargetTracker {
    public:
     FixedPosScrollTargetTracker(DisplayListBuilder& aBuilder,
                                 const ActiveScrolledRoot* aAsr,
                                 layers::ScrollableLayerGuid::ViewID aScrollId);
@@ -537,30 +539,66 @@ class DisplayListBuilder {
     return aClip;
   }
 
   wr::WrState* mWrState;
 
   // Track each scroll id that we encountered. We use this structure to
   // ensure that we don't define a particular scroll layer multiple times,
   // as that results in undefined behaviour in WR.
-  std::unordered_map<layers::ScrollableLayerGuid::ViewID, wr::WrClipId>
+  std::unordered_map<layers::ScrollableLayerGuid::ViewID, wr::WrSpaceAndClip>
       mScrollIds;
 
+  wr::WrSpaceAndClipChain mCurrentSpaceAndClipChain;
+
   // Contains the current leaf of the clip chain to be merged with the
   // display item's clip rect when pushing an item. May be set to Nothing() if
   // there is no clip rect to merge with.
   Maybe<wr::LayoutRect> mClipChainLeaf;
 
   RefPtr<layout::TextDrawTarget> mCachedTextDT;
   RefPtr<gfxContext> mCachedContext;
 
   FixedPosScrollTargetTracker* mActiveFixedPosTracker;
 
   friend class WebRenderAPI;
+  friend class SpaceAndClipChainHelper;
+};
+
+// This is a RAII class that overrides the current Wr's SpatialId and
+// ClipChainId.
+class MOZ_RAII SpaceAndClipChainHelper {
+ public:
+  SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
+                          wr::WrSpaceAndClipChain aSpaceAndClipChain
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : mBuilder(aBuilder),
+        mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
+    aBuilder.mCurrentSpaceAndClipChain = aSpaceAndClipChain;
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  }
+  SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
+                          wr::WrSpatialId aSpatialId
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : mBuilder(aBuilder),
+        mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
+    aBuilder.mCurrentSpaceAndClipChain.space = aSpatialId;
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  }
+
+  ~SpaceAndClipChainHelper() {
+    mBuilder.mCurrentSpaceAndClipChain = mOldSpaceAndClipChain;
+  }
+
+ private:
+  SpaceAndClipChainHelper(const SpaceAndClipChainHelper&) = delete;
+
+  DisplayListBuilder& mBuilder;
+  wr::WrSpaceAndClipChain mOldSpaceAndClipChain;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 Maybe<wr::ImageFormat> SurfaceFormatToImageFormat(gfx::SurfaceFormat aFormat);
 
 }  // namespace wr
 }  // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/WebRenderTypes.cpp
+++ b/gfx/webrender_bindings/WebRenderTypes.cpp
@@ -23,14 +23,24 @@ void Assign_WrVecU8(wr::WrVecU8& aVec, m
   aVec.data = aOther.mData;
   aVec.length = aOther.mLen;
   aVec.capacity = aOther.mCapacity;
   aOther.mData = nullptr;
   aOther.mLen = 0;
   aOther.mCapacity = 0;
 }
 
-/*static*/ WrClipId RootScrollNode() {
-  return WrClipId{wr_root_scroll_node_id()};
+WrSpaceAndClip RootScrollNode() {
+  WrSpaceAndClip sac;
+  sac.clip = wr_root_clip_id();
+  sac.space = wr_root_scroll_node_id();
+  return sac;
+}
+
+WrSpaceAndClipChain RootScrollNodeWithChain() {
+  WrSpaceAndClipChain sacc;
+  sacc.clip_chain = wr::ROOT_CLIP_CHAIN;
+  sacc.space = wr_root_scroll_node_id();
+  return sacc;
 }
 
 }  // namespace wr
 }  // namespace mozilla
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -793,26 +793,33 @@ static inline wr::WrFilterOpType ToWrFil
       return wr::WrFilterOpType::Sepia;
     case NS_STYLE_FILTER_DROP_SHADOW:
       return wr::WrFilterOpType::DropShadow;
   }
   MOZ_ASSERT_UNREACHABLE("Tried to convert unknown filter type.");
   return wr::WrFilterOpType::Grayscale;
 }
 
-extern WrClipId RootScrollNode();
 
 // Corresponds to a clip id for a clip chain in webrender. Similar to
 // WrClipId but a separate struct so we don't get them mixed up in C++.
 struct WrClipChainId {
   uint64_t id;
 
   bool operator==(const WrClipChainId& other) const { return id == other.id; }
+
+  static WrClipChainId Empty() {
+    WrClipChainId id = {0};
+    return id;
+  }
 };
 
+WrSpaceAndClip RootScrollNode();
+WrSpaceAndClipChain RootScrollNodeWithChain();
+
 enum class WebRenderError : int8_t {
   INITIALIZE = 0,
   MAKE_CURRENT,
   RENDER,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
@@ -852,16 +859,16 @@ static inline wr::SyntheticItalics Degre
   return synthetic_italics;
 }
 
 }  // namespace wr
 }  // namespace mozilla
 
 namespace std {
 template <>
-struct hash<mozilla::wr::WrClipId> {
-  std::size_t operator()(mozilla::wr::WrClipId const& aKey) const noexcept {
+struct hash<mozilla::wr::WrSpatialId> {
+  std::size_t operator()(mozilla::wr::WrSpatialId const& aKey) const noexcept {
     return std::hash<size_t>{}(aKey.id);
   }
 };
 }  // namespace std
 
 #endif /* GFX_WEBRENDERTYPES_H */
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-9019124fdccab50096ef5af76d1acbd251c1ad07
+fb4b9342aa1b047ac46b89cb7a70987816bee686
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -39,16 +39,19 @@ use core_graphics::font::CGFont;
 extern "C" {
     #[cfg(target_os = "android")]
     fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
 }
 
 /// The unique id for WR resource identification.
 static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1);
 
+/// Special value handled in this wrapper layer to signify a redundant clip chain.
+pub const ROOT_CLIP_CHAIN: u64 = !0;
+
 fn next_namespace_id() -> IdNamespace {
     IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
 }
 
 /// Whether a border should be antialiased.
 #[repr(C)]
 #[derive(Eq, PartialEq, Copy, Clone)]
 pub enum AntialiasBorder {
@@ -109,16 +112,60 @@ type WrImageKey = ImageKey;
 pub type WrFontKey = FontKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
 pub type WrFontInstanceKey = FontInstanceKey;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrYuvColorSpace = YuvColorSpace;
 /// cbindgen:field-names=[mNamespace, mHandle]
 type WrColorDepth = ColorDepth;
 
+
+#[repr(C)]
+pub struct WrSpaceAndClip {
+    space: WrSpatialId,
+    clip: WrClipId,
+}
+
+impl WrSpaceAndClip {
+    fn from_webrender(sac: SpaceAndClipInfo) -> Self {
+        WrSpaceAndClip {
+            space: WrSpatialId { id: sac.spatial_id.0 },
+            clip: WrClipId::from_webrender(sac.clip_id),
+        }
+    }
+
+    fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo {
+        SpaceAndClipInfo {
+            spatial_id: self.space.to_webrender(pipeline_id),
+            clip_id: self.clip.to_webrender(pipeline_id),
+        }
+    }
+}
+
+#[repr(C)]
+pub struct WrSpaceAndClipChain {
+    space: WrSpatialId,
+    clip_chain: u64,
+}
+
+impl WrSpaceAndClipChain {
+    fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo {
+        //Warning: special case here to support dummy clip chain
+        SpaceAndClipInfo {
+            spatial_id: self.space.to_webrender(pipeline_id),
+            clip_id: if self.clip_chain == ROOT_CLIP_CHAIN {
+                ClipId::root(pipeline_id)
+            } else {
+                ClipId::ClipChain(ClipChainId(self.clip_chain, pipeline_id))
+            },
+        }
+    }
+}
+
+
 fn make_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
     if ptr.is_null() {
         &[]
     } else {
         unsafe { slice::from_raw_parts(ptr, len) }
     }
 }
 
@@ -1854,29 +1901,29 @@ pub extern "C" fn wr_dp_restore(state: &
 #[no_mangle]
 pub extern "C" fn wr_dp_clear_save(state: &mut WrState) {
     state.frame_builder.dl_builder.clear_save();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_stacking_context(state: &mut WrState,
                                               mut bounds: LayoutRect,
+                                              spatial_id: WrSpatialId,
                                               clip_node_id: *const WrClipId,
                                               animation: *const WrAnimationProperty,
                                               opacity: *const f32,
                                               transform: *const LayoutTransform,
                                               transform_style: TransformStyle,
                                               perspective: *const LayoutTransform,
                                               mix_blend_mode: MixBlendMode,
                                               filters: *const WrFilterOp,
                                               filter_count: usize,
                                               is_backface_visible: bool,
                                               glyph_raster_space: RasterSpace,
-                                              out_is_reference_frame: &mut bool,
-                                              out_reference_frame_id: &mut usize) {
+                                              ) -> WrSpatialId {
     debug_assert!(unsafe { !is_in_render_thread() });
 
     let c_filters = make_slice(filters, filter_count);
     let mut filters : Vec<FilterOp> = c_filters.iter().map(|c_filter| {
         match c_filter.filter_type {
             WrFilterOpType::Blur => FilterOp::Blur(c_filter.argument),
             WrFilterOpType::Brightness => FilterOp::Brightness(c_filter.argument),
             WrFilterOpType::Contrast => FilterOp::Contrast(c_filter.argument),
@@ -1891,20 +1938,16 @@ pub extern "C" fn wr_dp_push_stacking_co
                                                                c_filter.color),
             WrFilterOpType::ColorMatrix => FilterOp::ColorMatrix(c_filter.matrix),
             WrFilterOpType::SrgbToLinear => FilterOp::SrgbToLinear,
             WrFilterOpType::LinearToSrgb => FilterOp::LinearToSrgb,
         }
     }).collect();
 
     let clip_node_id_ref = unsafe { clip_node_id.as_ref() };
-    let clip_node_id = match clip_node_id_ref {
-        Some(clip_node_id) => Some(unpack_clip_id(*clip_node_id, state.pipeline_id)),
-        None => None,
-    };
 
     let transform_ref = unsafe { transform.as_ref() };
     let mut transform_binding = match transform_ref {
         Some(transform) => Some(PropertyBinding::Value(transform.clone())),
         None => None,
     };
 
     let opacity_ref = unsafe { opacity.as_ref() };
@@ -1939,250 +1982,290 @@ pub extern "C" fn wr_dp_push_stacking_co
     }
 
     let perspective_ref = unsafe { perspective.as_ref() };
     let perspective = match perspective_ref {
         Some(perspective) => Some(perspective.clone()),
         None => None,
     };
 
-    *out_is_reference_frame = transform_binding.is_some() || perspective.is_some();
-    if *out_is_reference_frame {
-        let ref_frame_id = state.frame_builder
-            .dl_builder
-            .push_reference_frame(&bounds, transform_style, transform_binding, perspective);
-        *out_reference_frame_id = pack_clip_id(ref_frame_id);
+    let mut wr_spatial_id = spatial_id.to_webrender(state.pipeline_id);
+    let wr_clip_id = clip_node_id_ref.map(|id| id.to_webrender(state.pipeline_id));
+
+    let is_reference_frame = transform_binding.is_some() || perspective.is_some();
+    // Note: 0 has special meaning in WR land, standing for ROOT_REFERENCE_FRAME.
+    // However, it is never returned by `push_reference_frame`, and we need to return
+    // an option here across FFI, so we take that 0 value for the None semantics.
+    // This is resolved into proper `Maybe<WrSpatialId>` inside `WebRenderAPI::PushStackingContext`.
+    let mut result = WrSpatialId { id: 0 };
+    if is_reference_frame {
+        wr_spatial_id = state.frame_builder.dl_builder.push_reference_frame(
+            &bounds,
+            wr_spatial_id,
+            transform_style,
+            transform_binding,
+            perspective,
+        );
 
         bounds.origin = LayoutPoint::zero();
-        state.frame_builder.dl_builder.push_clip_id(ref_frame_id);
+        result.id = wr_spatial_id.0;
+        assert_ne!(wr_spatial_id.0, 0);
     }
 
     let prim_info = LayoutPrimitiveInfo {
         is_backface_visible,
         tag: state.current_tag,
         .. LayoutPrimitiveInfo::new(bounds)
     };
 
     state.frame_builder
          .dl_builder
          .push_stacking_context(&prim_info,
-                                clip_node_id,
+                                wr_spatial_id,
+                                wr_clip_id,
                                 transform_style,
                                 mix_blend_mode,
                                 &filters,
                                 glyph_raster_space);
+
+    result
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState,
                                              is_reference_frame: bool) {
     debug_assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_stacking_context();
     if is_reference_frame {
-        state.frame_builder.dl_builder.pop_clip_id();
         state.frame_builder.dl_builder.pop_reference_frame();
     }
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_clipchain(state: &mut WrState,
                                          parent_clipchain_id: *const u64,
                                          clips: *const WrClipId,
                                          clips_count: usize)
                                          -> u64 {
     debug_assert!(unsafe { is_in_main_thread() });
-    let parent = unsafe { parent_clipchain_id.as_ref() }.map(|id| ClipChainId(*id, state.pipeline_id));
+    let parent = unsafe { parent_clipchain_id.as_ref() }
+        .map(|id| ClipChainId(*id, state.pipeline_id));
+
     let pipeline_id = state.pipeline_id;
     let clips = make_slice(clips, clips_count)
         .iter()
-        .map(|id| unpack_clip_id(*id, pipeline_id));
+        .map(|clip_id| clip_id.to_webrender(pipeline_id));
 
     let clipchain_id = state.frame_builder.dl_builder.define_clip_chain(parent, clips);
     assert!(clipchain_id.1 == state.pipeline_id);
     clipchain_id.0
 }
 
 #[no_mangle]
-pub extern "C" fn wr_dp_define_clip(state: &mut WrState,
-                                    parent_id: *const WrClipId,
-                                    clip_rect: LayoutRect,
-                                    complex: *const ComplexClipRegion,
-                                    complex_count: usize,
-                                    mask: *const WrImageMask)
-                                    -> usize {
-    debug_assert!(unsafe { is_in_main_thread() });
-
-    let parent_id = unsafe { parent_id.as_ref() };
-    let complex_slice = make_slice(complex, complex_count);
-    let complex_iter = complex_slice.iter().cloned();
-    let mask : Option<ImageMask> = unsafe { mask.as_ref() }.map(|x| x.into());
-
-    let clip_id = if let Some(&pid) = parent_id {
-        state.frame_builder.dl_builder.define_clip_with_parent(
-            unpack_clip_id(pid, state.pipeline_id),
-            clip_rect, complex_iter, mask)
-    } else {
-        state.frame_builder.dl_builder.define_clip(clip_rect, complex_iter, mask)
-    };
-    pack_clip_id(clip_id)
+pub extern "C" fn wr_dp_define_clip_with_parent_clip(
+    state: &mut WrState,
+    parent: &WrSpaceAndClip,
+    clip_rect: LayoutRect,
+    complex: *const ComplexClipRegion,
+    complex_count: usize,
+    mask: *const WrImageMask,
+) -> WrClipId {
+    wr_dp_define_clip_impl(
+        &mut state.frame_builder,
+        parent.to_webrender(state.pipeline_id),
+        clip_rect,
+        make_slice(complex, complex_count),
+        unsafe { mask.as_ref() }.map(|m| m.into()),
+    )
 }
 
 #[no_mangle]
-pub extern "C" fn wr_dp_push_clip(state: &mut WrState,
-                                  clip_id: WrClipId) {
-    debug_assert!(unsafe { is_in_main_thread() });
-    state.frame_builder.dl_builder.push_clip_id(unpack_clip_id(clip_id, state.pipeline_id));
+pub extern "C" fn wr_dp_define_clip_with_parent_clip_chain(
+    state: &mut WrState,
+    parent: &WrSpaceAndClipChain,
+    clip_rect: LayoutRect,
+    complex: *const ComplexClipRegion,
+    complex_count: usize,
+    mask: *const WrImageMask,
+) -> WrClipId {
+    wr_dp_define_clip_impl(
+        &mut state.frame_builder,
+        parent.to_webrender(state.pipeline_id),
+        clip_rect,
+        make_slice(complex, complex_count),
+        unsafe { mask.as_ref() }.map(|m| m.into()),
+    )
 }
 
-#[no_mangle]
-pub extern "C" fn wr_dp_pop_clip(state: &mut WrState) {
-    debug_assert!(unsafe { !is_in_render_thread() });
-    state.frame_builder.dl_builder.pop_clip_id();
+fn wr_dp_define_clip_impl(
+    frame_builder: &mut WebRenderFrameBuilder,
+    parent: SpaceAndClipInfo,
+    clip_rect: LayoutRect,
+    complex_regions: &[ComplexClipRegion],
+    mask: Option<ImageMask>,
+) -> WrClipId {
+    debug_assert!(unsafe { is_in_main_thread() });
+    let clip_id = frame_builder.dl_builder.define_clip(
+        &parent,
+        clip_rect,
+        complex_regions.iter().cloned(),
+        mask,
+    );
+    WrClipId::from_webrender(clip_id)
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_sticky_frame(state: &mut WrState,
+                                            parent_spatial_id: WrSpatialId,
                                             content_rect: LayoutRect,
                                             top_margin: *const f32,
                                             right_margin: *const f32,
                                             bottom_margin: *const f32,
                                             left_margin: *const f32,
                                             vertical_bounds: StickyOffsetBounds,
                                             horizontal_bounds: StickyOffsetBounds,
                                             applied_offset: LayoutVector2D)
-                                            -> usize {
+                                            -> WrSpatialId {
     assert!(unsafe { is_in_main_thread() });
-    let clip_id = state.frame_builder.dl_builder.define_sticky_frame(
-        content_rect, SideOffsets2D::new(
+    let spatial_id = state.frame_builder.dl_builder.define_sticky_frame(
+        parent_spatial_id.to_webrender(state.pipeline_id),
+        content_rect,
+        SideOffsets2D::new(
             unsafe { top_margin.as_ref() }.cloned(),
             unsafe { right_margin.as_ref() }.cloned(),
             unsafe { bottom_margin.as_ref() }.cloned(),
             unsafe { left_margin.as_ref() }.cloned()
         ),
-        vertical_bounds, horizontal_bounds, applied_offset);
-    pack_clip_id(clip_id)
+        vertical_bounds,
+        horizontal_bounds,
+        applied_offset,
+    );
+
+    WrSpatialId { id: spatial_id.0 }
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_scroll_layer(state: &mut WrState,
-                                            scroll_id: u64,
-                                            parent_id: *const WrClipId,
+                                            external_scroll_id: u64,
+                                            parent: &WrSpaceAndClip,
                                             content_rect: LayoutRect,
                                             clip_rect: LayoutRect)
-                                            -> usize {
+                                            -> WrSpaceAndClip {
     assert!(unsafe { is_in_main_thread() });
 
-    let parent_id = unsafe { parent_id.as_ref() };
-
-    let new_id = if let Some(&pid) = parent_id {
-        state.frame_builder.dl_builder.define_scroll_frame_with_parent(
-            unpack_clip_id(pid, state.pipeline_id),
-            Some(ExternalScrollId(scroll_id, state.pipeline_id)),
-            content_rect,
-            clip_rect,
-            vec![],
-            None,
-            ScrollSensitivity::Script
-        )
-    } else {
-        state.frame_builder.dl_builder.define_scroll_frame(
-            Some(ExternalScrollId(scroll_id, state.pipeline_id)),
-            content_rect,
-            clip_rect,
-            vec![],
-            None,
-            ScrollSensitivity::Script
-        )
-    };
-
-    pack_clip_id(new_id)
-}
-
-#[no_mangle]
-pub extern "C" fn wr_dp_push_scroll_layer(state: &mut WrState,
-                                          scroll_id: WrClipId) {
-    debug_assert!(unsafe { is_in_main_thread() });
-    let clip_id = unpack_clip_id(scroll_id, state.pipeline_id);
-    state.frame_builder.dl_builder.push_clip_id(clip_id);
-}
-
-#[no_mangle]
-pub extern "C" fn wr_dp_pop_scroll_layer(state: &mut WrState) {
-    debug_assert!(unsafe { is_in_main_thread() });
-    state.frame_builder.dl_builder.pop_clip_id();
-}
-
-#[no_mangle]
-pub extern "C" fn wr_dp_push_clip_and_scroll_info(state: &mut WrState,
-                                                  scroll_id: WrClipId,
-                                                  clip_chain_id: *const u64) {
-    debug_assert!(unsafe { is_in_main_thread() });
-
-    let clip_chain_id = unsafe { clip_chain_id.as_ref() };
-    let info = if let Some(&ccid) = clip_chain_id {
-        ClipAndScrollInfo::new(
-            unpack_clip_id(scroll_id, state.pipeline_id),
-            ClipId::ClipChain(ClipChainId(ccid, state.pipeline_id)))
-    } else {
-        ClipAndScrollInfo::simple(unpack_clip_id(scroll_id, state.pipeline_id))
-    };
-    state.frame_builder.dl_builder.push_clip_and_scroll_info(info);
-}
-
-#[no_mangle]
-pub extern "C" fn wr_dp_pop_clip_and_scroll_info(state: &mut WrState) {
-    debug_assert!(unsafe { is_in_main_thread() });
-    state.frame_builder.dl_builder.pop_clip_id();
+    let space_and_clip = state.frame_builder.dl_builder.define_scroll_frame(
+        &parent.to_webrender(state.pipeline_id),
+        Some(ExternalScrollId(external_scroll_id, state.pipeline_id)),
+        content_rect,
+        clip_rect,
+        vec![],
+        None,
+        ScrollSensitivity::Script
+    );
+
+    WrSpaceAndClip::from_webrender(space_and_clip)
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_iframe(state: &mut WrState,
                                     rect: LayoutRect,
                                     clip: LayoutRect,
                                     is_backface_visible: bool,
+                                    parent: &WrSpaceAndClipChain,
                                     pipeline_id: WrPipelineId,
                                     ignore_missing_pipeline: bool) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder.dl_builder.push_iframe(&prim_info, pipeline_id, ignore_missing_pipeline);
+    state.frame_builder.dl_builder.push_iframe(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        pipeline_id,
+        ignore_missing_pipeline,
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_rect(state: &mut WrState,
                                   rect: LayoutRect,
                                   clip: LayoutRect,
                                   is_backface_visible: bool,
+                                  parent: &WrSpaceAndClipChain,
                                   color: ColorF) {
     debug_assert!(unsafe { !is_in_render_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder.dl_builder.push_rect(&prim_info,
-                                             color);
+    state.frame_builder.dl_builder.push_rect(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        color,
+    );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_rect_with_parent_clip(
+    state: &mut WrState,
+    rect: LayoutRect,
+    clip: LayoutRect,
+    is_backface_visible: bool,
+    parent: &WrSpaceAndClip,
+    color: ColorF,
+) {
+    debug_assert!(unsafe { !is_in_render_thread() });
+
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
+    prim_info.tag = state.current_tag;
+    state.frame_builder.dl_builder.push_rect(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        color,
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_clear_rect(state: &mut WrState,
                                         rect: LayoutRect,
-                                        clip: LayoutRect) {
+                                        clip: LayoutRect,
+                                        parent: &WrSpaceAndClipChain) {
     debug_assert!(unsafe { !is_in_render_thread() });
 
     let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
-    state.frame_builder.dl_builder.push_clear_rect(&prim_info);
+    state.frame_builder.dl_builder.push_clear_rect(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+    );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_clear_rect_with_parent_clip(
+    state: &mut WrState,
+    rect: LayoutRect,
+    clip: LayoutRect,
+    parent: &WrSpaceAndClip,
+) {
+    debug_assert!(unsafe { !is_in_render_thread() });
+
+    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    state.frame_builder.dl_builder.push_clear_rect(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_image(state: &mut WrState,
                                    bounds: LayoutRect,
                                    clip: LayoutRect,
                                    is_backface_visible: bool,
+                                   parent: &WrSpaceAndClipChain,
                                    stretch_size: LayoutSize,
                                    tile_spacing: LayoutSize,
                                    image_rendering: ImageRendering,
                                    key: WrImageKey,
                                    premultiplied_alpha: bool,
                                    color: ColorF) {
     debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
@@ -2192,175 +2275,192 @@ pub extern "C" fn wr_dp_push_image(state
     let alpha_type = if premultiplied_alpha {
         AlphaType::PremultipliedAlpha
     } else {
         AlphaType::Alpha
     };
     state.frame_builder
          .dl_builder
          .push_image(&prim_info,
+                     &parent.to_webrender(state.pipeline_id),
                      stretch_size,
                      tile_spacing,
                      image_rendering,
                      alpha_type,
                      key,
                      color);
 }
 
 /// Push a 3 planar yuv image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState,
                                               bounds: LayoutRect,
                                               clip: LayoutRect,
                                               is_backface_visible: bool,
+                                              parent: &WrSpaceAndClipChain,
                                               image_key_0: WrImageKey,
                                               image_key_1: WrImageKey,
                                               image_key_2: WrImageKey,
                                               color_depth: WrColorDepth,
                                               color_space: WrYuvColorSpace,
                                               image_rendering: ImageRendering) {
     debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
+                         &parent.to_webrender(state.pipeline_id),
                          YuvData::PlanarYCbCr(image_key_0, image_key_1, image_key_2),
                          color_depth,
                          color_space,
                          image_rendering);
 }
 
 /// Push a 2 planar NV12 image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_NV12_image(state: &mut WrState,
                                             bounds: LayoutRect,
                                             clip: LayoutRect,
                                             is_backface_visible: bool,
+                                            parent: &WrSpaceAndClipChain,
                                             image_key_0: WrImageKey,
                                             image_key_1: WrImageKey,
                                             color_depth: WrColorDepth,
                                             color_space: WrYuvColorSpace,
                                             image_rendering: ImageRendering) {
     debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
+                         &parent.to_webrender(state.pipeline_id),
                          YuvData::NV12(image_key_0, image_key_1),
                          color_depth,
                          color_space,
                          image_rendering);
 }
 
 /// Push a yuv interleaved image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_interleaved_image(state: &mut WrState,
                                                    bounds: LayoutRect,
                                                    clip: LayoutRect,
                                                    is_backface_visible: bool,
+                                                   parent: &WrSpaceAndClipChain,
                                                    image_key_0: WrImageKey,
                                                    color_depth: WrColorDepth,
                                                    color_space: WrYuvColorSpace,
                                                    image_rendering: ImageRendering) {
     debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
+                         &parent.to_webrender(state.pipeline_id),
                          YuvData::InterleavedYCbCr(image_key_0),
                          color_depth,
                          color_space,
                          image_rendering);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_text(state: &mut WrState,
                                   bounds: LayoutRect,
                                   clip: LayoutRect,
                                   is_backface_visible: bool,
+                                  parent: &WrSpaceAndClipChain,
                                   color: ColorF,
                                   font_key: WrFontInstanceKey,
                                   glyphs: *const GlyphInstance,
                                   glyph_count: u32,
                                   glyph_options: *const GlyphOptions) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let glyph_slice = make_slice(glyphs, glyph_count as usize);
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_text(&prim_info,
+                    &parent.to_webrender(state.pipeline_id),
                     &glyph_slice,
                     font_key,
                     color,
                     unsafe { glyph_options.as_ref().cloned() });
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_shadow(state: &mut WrState,
                                     bounds: LayoutRect,
                                     clip: LayoutRect,
                                     is_backface_visible: bool,
+                                    parent: &WrSpaceAndClipChain,
                                     shadow: Shadow) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder.dl_builder.push_shadow(&prim_info, shadow.into());
+    state.frame_builder.dl_builder.push_shadow(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        shadow.into(),
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_all_shadows(state: &mut WrState) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     state.frame_builder.dl_builder.pop_all_shadows();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_line(state: &mut WrState,
                                   clip: &LayoutRect,
                                   is_backface_visible: bool,
+                                  parent: &WrSpaceAndClipChain,
                                   bounds: &LayoutRect,
                                   wavy_line_thickness: f32,
                                   orientation: LineOrientation,
                                   color: &ColorF,
                                   style: LineStyle) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(*bounds, (*clip).into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_line(&prim_info,
+                    &parent.to_webrender(state.pipeline_id),
                     wavy_line_thickness,
                     orientation,
                     color,
                     style);
 
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border(state: &mut WrState,
                                     rect: LayoutRect,
                                     clip: LayoutRect,
                                     is_backface_visible: bool,
+                                    parent: &WrSpaceAndClipChain,
                                     do_aa: AntialiasBorder,
                                     widths: LayoutSideOffsets,
                                     top: BorderSide,
                                     right: BorderSide,
                                     bottom: BorderSide,
                                     left: BorderSide,
                                     radius: BorderRadius) {
     debug_assert!(unsafe { is_in_main_thread() });
@@ -2375,25 +2475,27 @@ pub extern "C" fn wr_dp_push_border(stat
     });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_border(&prim_info,
+                      &parent.to_webrender(state.pipeline_id),
                       widths,
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_image(state: &mut WrState,
                                           rect: LayoutRect,
                                           clip: LayoutRect,
                                           is_backface_visible: bool,
+                                          parent: &WrSpaceAndClipChain,
                                           widths: LayoutSideOffsets,
                                           image: WrImageKey,
                                           width: i32,
                                           height: i32,
                                           slice: SideOffsets2D<i32>,
                                           outset: SideOffsets2D<f32>,
                                           repeat_horizontal: RepeatMode,
                                           repeat_vertical: RepeatMode) {
@@ -2406,25 +2508,30 @@ pub extern "C" fn wr_dp_push_border_imag
         fill: false,
         outset: outset.into(),
         repeat_horizontal: repeat_horizontal.into(),
         repeat_vertical: repeat_vertical.into(),
     });
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder .dl_builder
-         .push_border(&prim_info, widths.into(), border_details);
+    state.frame_builder.dl_builder.push_border(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        widths.into(),
+        border_details,
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
                                              is_backface_visible: bool,
+                                             parent: &WrSpaceAndClipChain,
                                              widths: LayoutSideOffsets,
                                              width: i32,
                                              height: i32,
                                              slice: SideOffsets2D<i32>,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
                                              stops_count: usize,
@@ -2451,26 +2558,30 @@ pub extern "C" fn wr_dp_push_border_grad
         outset: outset.into(),
         repeat_horizontal: RepeatMode::Stretch,
         repeat_vertical: RepeatMode::Stretch,
     });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder
-         .dl_builder
-         .push_border(&prim_info, widths.into(), border_details);
+    state.frame_builder.dl_builder.push_border(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        widths.into(),
+        border_details,
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_radial_gradient(state: &mut WrState,
                                                     rect: LayoutRect,
                                                     clip: LayoutRect,
                                                     is_backface_visible: bool,
+                                                    parent: &WrSpaceAndClipChain,
                                                     widths: LayoutSideOffsets,
                                                     center: LayoutPoint,
                                                     radius: LayoutSize,
                                                     stops: *const GradientStop,
                                                     stops_count: usize,
                                                     extend_mode: ExtendMode,
                                                     outset: SideOffsets2D<f32>) {
     debug_assert!(unsafe { is_in_main_thread() });
@@ -2500,26 +2611,30 @@ pub extern "C" fn wr_dp_push_border_radi
         fill: false,
         outset: outset.into(),
         repeat_horizontal: RepeatMode::Stretch,
         repeat_vertical: RepeatMode::Stretch,
     });
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder
-         .dl_builder
-         .push_border(&prim_info, widths.into(), border_details);
+    state.frame_builder.dl_builder.push_border(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        widths.into(),
+        border_details,
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_linear_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
                                              is_backface_visible: bool,
+                                             parent: &WrSpaceAndClipChain,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
                                              tile_size: LayoutSize,
                                              tile_spacing: LayoutSize) {
     debug_assert!(unsafe { is_in_main_thread() });
@@ -2531,29 +2646,31 @@ pub extern "C" fn wr_dp_push_linear_grad
                         .dl_builder
                         .create_gradient(start_point.into(),
                                          end_point.into(),
                                          stops_vector,
                                          extend_mode.into());
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder
-         .dl_builder
-         .push_gradient(&prim_info,
-                        gradient,
-                        tile_size.into(),
-                        tile_spacing.into());
+    state.frame_builder.dl_builder.push_gradient(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        gradient,
+        tile_size.into(),
+        tile_spacing.into(),
+    );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_radial_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
                                              is_backface_visible: bool,
+                                             parent: &WrSpaceAndClipChain,
                                              center: LayoutPoint,
                                              radius: LayoutSize,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
                                              tile_size: LayoutSize,
                                              tile_spacing: LayoutSize) {
     debug_assert!(unsafe { is_in_main_thread() });
@@ -2565,44 +2682,46 @@ pub extern "C" fn wr_dp_push_radial_grad
                         .dl_builder
                         .create_radial_gradient(center.into(),
                                                 radius.into(),
                                                 stops_vector,
                                                 extend_mode.into());
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
-    state.frame_builder
-         .dl_builder
-         .push_radial_gradient(&prim_info,
-                               gradient,
-                               tile_size,
-                               tile_spacing);
+    state.frame_builder.dl_builder.push_radial_gradient(
+        &prim_info,
+        &parent.to_webrender(state.pipeline_id),
+        gradient,
+        tile_size,
+        tile_spacing);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_box_shadow(state: &mut WrState,
                                         rect: LayoutRect,
                                         clip: LayoutRect,
                                         is_backface_visible: bool,
+                                        parent: &WrSpaceAndClipChain,
                                         box_bounds: LayoutRect,
                                         offset: LayoutVector2D,
                                         color: ColorF,
                                         blur_radius: f32,
                                         spread_radius: f32,
                                         border_radius: BorderRadius,
                                         clip_mode: BoxShadowClipMode) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
     prim_info.is_backface_visible = is_backface_visible;
     prim_info.tag = state.current_tag;
     state.frame_builder
          .dl_builder
          .push_box_shadow(&prim_info,
+                          &parent.to_webrender(state.pipeline_id),
                           box_bounds,
                           offset,
                           color,
                           blur_radius,
                           spread_radius,
                           border_radius,
                           clip_mode);
 }
@@ -2709,47 +2828,58 @@ extern "C" {
                                tile_size: Option<&u16>,
                                tile_offset: Option<&TileOffset>,
                                dirty_rect: Option<&LayoutIntRect>,
                                output: MutByteSlice)
                                -> bool;
 }
 
 #[no_mangle]
-pub extern "C" fn wr_root_scroll_node_id() -> usize {
+pub extern "C" fn wr_root_scroll_node_id() -> WrSpatialId {
+    // The PipelineId doesn't matter here, since we just want the numeric part of the id
+    // produced for any given root reference frame.
+    WrSpatialId { id: SpatialId::root_scroll_node(PipelineId(0, 0)).0 }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_root_clip_id() -> WrClipId {
     // The PipelineId doesn't matter here, since we just want the numeric part of the id
     // produced for any given root reference frame.
-    pack_clip_id(ClipId::root_scroll_node(PipelineId(0, 0)))
+    WrClipId::from_webrender(ClipId::root(PipelineId(0, 0)))
+ }
+
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct WrClipId {
+    id: usize,
 }
 
-fn pack_clip_id(id: ClipId) -> usize {
-    let (id, type_value) = match id {
-        ClipId::Spatial(id, _) => (id, 0),
-        ClipId::Clip(id, _) => (id, 1),
-        ClipId::ClipChain(..) => unreachable!("Tried to pack a clip chain id"),
-    };
-
-    assert!(id <= usize::max_value() >> 1);
-    return (id << 1) + type_value;
+impl WrClipId {
+    fn to_webrender(&self, pipeline_id: WrPipelineId) -> ClipId {
+        ClipId::Clip(self.id, pipeline_id)
+    }
+
+    fn from_webrender(clip_id: ClipId) -> Self {
+        match clip_id {
+            ClipId::Clip(id, _) => WrClipId { id },
+            ClipId::ClipChain(_) => panic!("Unexpected clip chain"),
+        }
+    }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy)]
-pub struct WrClipId {
-    id: usize
+pub struct WrSpatialId {
+    id: usize,
 }
 
-fn unpack_clip_id(id: WrClipId, pipeline_id: PipelineId) -> ClipId {
-    let type_value = id.id & 0b01;
-    let id = id.id >> 1;
-
-    match type_value {
-        0 => ClipId::Spatial(id, pipeline_id),
-        1 => ClipId::Clip(id, pipeline_id),
-        _ => unreachable!("Got a bizarre value for the clip type"),
+impl WrSpatialId {
+    fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpatialId {
+        SpatialId::new(self.id, pipeline_id)
     }
 }
 
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_device_delete(device: *mut Device) {
     Box::from_raw(device);
 }
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -91,16 +91,18 @@ struct FontInstanceFlags {
     LCD_VERTICAL = 1 << 19
   };
 };
 
 struct Transaction;
 struct WrWindowId;
 struct WrPipelineInfo;
 
+const uint64_t ROOT_CLIP_CHAIN = ~0;
+
 }  // namespace wr
 }  // namespace mozilla
 
 void apz_register_updater(mozilla::wr::WrWindowId aWindowId);
 void apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId);
 void apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId,
                          mozilla::wr::WrPipelineInfo aInfo);
 void apz_run_updater(mozilla::wr::WrWindowId aWindowId);
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -646,16 +646,34 @@ struct WrDebugFlags {
 struct WrClipId {
   uintptr_t id;
 
   bool operator==(const WrClipId& aOther) const {
     return id == aOther.id;
   }
 };
 
+struct WrSpatialId {
+  uintptr_t id;
+
+  bool operator==(const WrSpatialId& aOther) const {
+    return id == aOther.id;
+  }
+};
+
+struct WrSpaceAndClip {
+  WrSpatialId space;
+  WrClipId clip;
+
+  bool operator==(const WrSpaceAndClip& aOther) const {
+    return space == aOther.space &&
+           clip == aOther.clip;
+  }
+};
+
 /// A 2d Rectangle optionally tagged with a unit.
 template<typename T, typename U>
 struct TypedRect {
   TypedPoint2D<T, U> origin;
   TypedSize2D<T, U> size;
 
   bool operator==(const TypedRect& aOther) const {
     return origin == aOther.origin &&
@@ -721,16 +739,26 @@ struct WrImageMask {
 
   bool operator==(const WrImageMask& aOther) const {
     return image == aOther.image &&
            rect == aOther.rect &&
            repeat == aOther.repeat;
   }
 };
 
+struct WrSpaceAndClipChain {
+  WrSpatialId space;
+  uint64_t clip_chain;
+
+  bool operator==(const WrSpaceAndClipChain& aOther) const {
+    return space == aOther.space &&
+           clip_chain == aOther.clip_chain;
+  }
+};
+
 /// The minimum and maximum allowable offset for a sticky frame in a single dimension.
 struct StickyOffsetBounds {
   /// The minimum offset for this frame, typically a negative value, which specifies how
   /// far in the negative direction the sticky frame can offset its contents in this
   /// dimension.
   float min;
   /// The maximum offset for this frame, typically a positive value, which specifies how
   /// far in the positive direction the sticky frame can offset its contents in this
@@ -1305,91 +1333,91 @@ WR_INLINE
 void wr_device_delete(Device *aDevice)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 void wr_dp_clear_save(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
-uintptr_t wr_dp_define_clip(WrState *aState,
-                            const WrClipId *aParentId,
-                            LayoutRect aClipRect,
-                            const ComplexClipRegion *aComplex,
-                            uintptr_t aComplexCount,
-                            const WrImageMask *aMask)
+WrClipId wr_dp_define_clip_with_parent_clip(WrState *aState,
+                                            const WrSpaceAndClip *aParent,
+                                            LayoutRect aClipRect,
+                                            const ComplexClipRegion *aComplex,
+                                            uintptr_t aComplexCount,
+                                            const WrImageMask *aMask)
+WR_FUNC;
+
+WR_INLINE
+WrClipId wr_dp_define_clip_with_parent_clip_chain(WrState *aState,
+                                                  const WrSpaceAndClipChain *aParent,
+                                                  LayoutRect aClipRect,
+                                                  const ComplexClipRegion *aComplex,
+                                                  uintptr_t aComplexCount,
+                                                  const WrImageMask *aMask)
 WR_FUNC;
 
 WR_INLINE
 uint64_t wr_dp_define_clipchain(WrState *aState,
                                 const uint64_t *aParentClipchainId,
                                 const WrClipId *aClips,
                                 uintptr_t aClipsCount)
 WR_FUNC;
 
 WR_INLINE
-uintptr_t wr_dp_define_scroll_layer(WrState *aState,
-                                    uint64_t aScrollId,
-                                    const WrClipId *aParentId,
-                                    LayoutRect aContentRect,
-                                    LayoutRect aClipRect)
+WrSpaceAndClip wr_dp_define_scroll_layer(WrState *aState,
+                                         uint64_t aExternalScrollId,
+                                         const WrSpaceAndClip *aParent,
+                                         LayoutRect aContentRect,
+                                         LayoutRect aClipRect)
 WR_FUNC;
 
 WR_INLINE
-uintptr_t wr_dp_define_sticky_frame(WrState *aState,
-                                    LayoutRect aContentRect,
-                                    const float *aTopMargin,
-                                    const float *aRightMargin,
-                                    const float *aBottomMargin,
-                                    const float *aLeftMargin,
-                                    StickyOffsetBounds aVerticalBounds,
-                                    StickyOffsetBounds aHorizontalBounds,
-                                    LayoutVector2D aAppliedOffset)
+WrSpatialId wr_dp_define_sticky_frame(WrState *aState,
+                                      WrSpatialId aParentSpatialId,
+                                      LayoutRect aContentRect,
+                                      const float *aTopMargin,
+                                      const float *aRightMargin,
+                                      const float *aBottomMargin,
+                                      const float *aLeftMargin,
+                                      StickyOffsetBounds aVerticalBounds,
+                                      StickyOffsetBounds aHorizontalBounds,
+                                      LayoutVector2D aAppliedOffset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_pop_all_shadows(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
-void wr_dp_pop_clip(WrState *aState)
-WR_FUNC;
-
-WR_INLINE
-void wr_dp_pop_clip_and_scroll_info(WrState *aState)
-WR_FUNC;
-
-WR_INLINE
-void wr_dp_pop_scroll_layer(WrState *aState)
-WR_FUNC;
-
-WR_INLINE
 void wr_dp_pop_stacking_context(WrState *aState,
                                 bool aIsReferenceFrame)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border(WrState *aState,
                        LayoutRect aRect,
                        LayoutRect aClip,
                        bool aIsBackfaceVisible,
+                       const WrSpaceAndClipChain *aParent,
                        AntialiasBorder aDoAa,
                        LayoutSideOffsets aWidths,
                        BorderSide aTop,
                        BorderSide aRight,
                        BorderSide aBottom,
                        BorderSide aLeft,
                        BorderRadius aRadius)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
                                 bool aIsBackfaceVisible,
+                                const WrSpaceAndClipChain *aParent,
                                 LayoutSideOffsets aWidths,
                                 int32_t aWidth,
                                 int32_t aHeight,
                                 SideOffsets2D<int32_t> aSlice,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 uintptr_t aStopsCount,
@@ -1397,214 +1425,228 @@ void wr_dp_push_border_gradient(WrState 
                                 SideOffsets2D<float> aOutset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_image(WrState *aState,
                              LayoutRect aRect,
                              LayoutRect aClip,
                              bool aIsBackfaceVisible,
+                             const WrSpaceAndClipChain *aParent,
                              LayoutSideOffsets aWidths,
                              WrImageKey aImage,
                              int32_t aWidth,
                              int32_t aHeight,
                              SideOffsets2D<int32_t> aSlice,
                              SideOffsets2D<float> aOutset,
                              RepeatMode aRepeatHorizontal,
                              RepeatMode aRepeatVertical)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_radial_gradient(WrState *aState,
                                        LayoutRect aRect,
                                        LayoutRect aClip,
                                        bool aIsBackfaceVisible,
+                                       const WrSpaceAndClipChain *aParent,
                                        LayoutSideOffsets aWidths,
                                        LayoutPoint aCenter,
                                        LayoutSize aRadius,
                                        const GradientStop *aStops,
                                        uintptr_t aStopsCount,
                                        ExtendMode aExtendMode,
                                        SideOffsets2D<float> aOutset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_box_shadow(WrState *aState,
                            LayoutRect aRect,
                            LayoutRect aClip,
                            bool aIsBackfaceVisible,
+                           const WrSpaceAndClipChain *aParent,
                            LayoutRect aBoxBounds,
                            LayoutVector2D aOffset,
                            ColorF aColor,
                            float aBlurRadius,
                            float aSpreadRadius,
                            BorderRadius aBorderRadius,
                            BoxShadowClipMode aClipMode)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_clear_rect(WrState *aState,
                            LayoutRect aRect,
-                           LayoutRect aClip)
+                           LayoutRect aClip,
+                           const WrSpaceAndClipChain *aParent)
 WR_FUNC;
 
 WR_INLINE
-void wr_dp_push_clip(WrState *aState,
-                     WrClipId aClipId)
-WR_FUNC;
-
-WR_INLINE
-void wr_dp_push_clip_and_scroll_info(WrState *aState,
-                                     WrClipId aScrollId,
-                                     const uint64_t *aClipChainId)
+void wr_dp_push_clear_rect_with_parent_clip(WrState *aState,
+                                            LayoutRect aRect,
+                                            LayoutRect aClip,
+                                            const WrSpaceAndClip *aParent)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_iframe(WrState *aState,
                        LayoutRect aRect,
                        LayoutRect aClip,
                        bool aIsBackfaceVisible,
+                       const WrSpaceAndClipChain *aParent,
                        WrPipelineId aPipelineId,
                        bool aIgnoreMissingPipeline)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_image(WrState *aState,
                       LayoutRect aBounds,
                       LayoutRect aClip,
                       bool aIsBackfaceVisible,
+                      const WrSpaceAndClipChain *aParent,
                       LayoutSize aStretchSize,
                       LayoutSize aTileSpacing,
                       ImageRendering aImageRendering,
                       WrImageKey aKey,
                       bool aPremultipliedAlpha,
                       ColorF aColor)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_line(WrState *aState,
                      const LayoutRect *aClip,
                      bool aIsBackfaceVisible,
+                     const WrSpaceAndClipChain *aParent,
                      const LayoutRect *aBounds,
                      float aWavyLineThickness,
                      LineOrientation aOrientation,
                      const ColorF *aColor,
                      LineStyle aStyle)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_linear_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
                                 bool aIsBackfaceVisible,
+                                const WrSpaceAndClipChain *aParent,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 uintptr_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 LayoutSize aTileSize,
                                 LayoutSize aTileSpacing)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_radial_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
                                 bool aIsBackfaceVisible,
+                                const WrSpaceAndClipChain *aParent,
                                 LayoutPoint aCenter,
                                 LayoutSize aRadius,
                                 const GradientStop *aStops,
                                 uintptr_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 LayoutSize aTileSize,
                                 LayoutSize aTileSpacing)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_rect(WrState *aState,
                      LayoutRect aRect,
                      LayoutRect aClip,
                      bool aIsBackfaceVisible,
+                     const WrSpaceAndClipChain *aParent,
                      ColorF aColor)
 WR_FUNC;
 
 WR_INLINE
-void wr_dp_push_scroll_layer(WrState *aState,
-                             WrClipId aScrollId)
+void wr_dp_push_rect_with_parent_clip(WrState *aState,
+                                      LayoutRect aRect,
+                                      LayoutRect aClip,
+                                      bool aIsBackfaceVisible,
+                                      const WrSpaceAndClip *aParent,
+                                      ColorF aColor)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_shadow(WrState *aState,
                        LayoutRect aBounds,
                        LayoutRect aClip,
                        bool aIsBackfaceVisible,
+                       const WrSpaceAndClipChain *aParent,
                        Shadow aShadow)
 WR_FUNC;
 
 WR_INLINE
-void wr_dp_push_stacking_context(WrState *aState,
-                                 LayoutRect aBounds,
-                                 const WrClipId *aClipNodeId,
-                                 const WrAnimationProperty *aAnimation,
-                                 const float *aOpacity,
-                                 const LayoutTransform *aTransform,
-                                 TransformStyle aTransformStyle,
-                                 const LayoutTransform *aPerspective,
-                                 MixBlendMode aMixBlendMode,
-                                 const WrFilterOp *aFilters,
-                                 uintptr_t aFilterCount,
-                                 bool aIsBackfaceVisible,
-                                 RasterSpace aGlyphRasterSpace,
-                                 bool *aOutIsReferenceFrame,
-                                 uintptr_t *aOutReferenceFrameId)
+WrSpatialId wr_dp_push_stacking_context(WrState *aState,
+                                        LayoutRect aBounds,
+                                        WrSpatialId aSpatialId,
+                                        const WrClipId *aClipNodeId,
+                                        const WrAnimationProperty *aAnimation,
+                                        const float *aOpacity,
+                                        const LayoutTransform *aTransform,
+                                        TransformStyle aTransformStyle,
+                                        const LayoutTransform *aPerspective,
+                                        MixBlendMode aMixBlendMode,
+                                        const WrFilterOp *aFilters,
+                                        uintptr_t aFilterCount,
+                                        bool aIsBackfaceVisible,
+                                        RasterSpace aGlyphRasterSpace)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_text(WrState *aState,
                      LayoutRect aBounds,
                      LayoutRect aClip,
                      bool aIsBackfaceVisible,
+                     const WrSpaceAndClipChain *aParent,
                      ColorF aColor,
                      WrFontInstanceKey aFontKey,
                      const GlyphInstance *aGlyphs,
                      uint32_t aGlyphCount,
                      const GlyphOptions *aGlyphOptions)
 WR_FUNC;
 
 /// Push a 2 planar NV12 image.
 WR_INLINE
 void wr_dp_push_yuv_NV12_image(WrState *aState,
                                LayoutRect aBounds,
                                LayoutRect aClip,
                                bool aIsBackfaceVisible,
+                               const WrSpaceAndClipChain *aParent,
                                WrImageKey aImageKey0,
                                WrImageKey aImageKey1,
                                WrColorDepth aColorDepth,
                                WrYuvColorSpace aColorSpace,
                                ImageRendering aImageRendering)
 WR_FUNC;
 
 /// Push a yuv interleaved image.
 WR_INLINE
 void wr_dp_push_yuv_interleaved_image(WrState *aState,
                                       LayoutRect aBounds,
                                       LayoutRect aClip,
                                       bool aIsBackfaceVisible,
+                                      const WrSpaceAndClipChain *aParent,
                                       WrImageKey aImageKey0,
                                       WrColorDepth aColorDepth,
                                       WrYuvColorSpace aColorSpace,
                                       ImageRendering aImageRendering)
 WR_FUNC;
 
 /// Push a 3 planar yuv image.
 WR_INLINE
 void wr_dp_push_yuv_planar_image(WrState *aState,
                                  LayoutRect aBounds,
                                  LayoutRect aClip,
                                  bool aIsBackfaceVisible,
+                                 const WrSpaceAndClipChain *aParent,
                                  WrImageKey aImageKey0,
                                  WrImageKey aImageKey1,
                                  WrImageKey aImageKey2,
                                  WrColorDepth aColorDepth,
                                  WrYuvColorSpace aColorSpace,
                                  ImageRendering aImageRendering)
 WR_FUNC;
 
@@ -1818,17 +1860,21 @@ WR_FUNC;
 WR_INLINE
 void wr_resource_updates_update_image(Transaction *aTxn,
                                       WrImageKey aKey,
                                       const WrImageDescriptor *aDescriptor,
                                       WrVecU8 *aBytes)
 WR_FUNC;
 
 WR_INLINE
-uintptr_t wr_root_scroll_node_id()
+WrClipId wr_root_clip_id()
+WR_FUNC;
+
+WR_INLINE
+WrSpatialId wr_root_scroll_node_id()
 WR_FUNC;
 
 extern void wr_schedule_render(WrWindowId aWindowId);
 
 WR_INLINE
 void wr_set_item_tag(WrState *aState,
                      uint64_t aScrollId,
                      uint16_t aHitInfo)
--- a/gfx/wr/direct-composition/src/main_windows.rs
+++ b/gfx/wr/direct-composition/src/main_windows.rs
@@ -137,22 +137,31 @@ impl Rectangle {
 
         let rect = euclid::TypedRect::new(euclid::TypedPoint2D::zero(), layout_size);
 
         let region = api::ComplexClipRegion::new(
             rect,
             api::BorderRadius::uniform(20.),
             api::ClipMode::Clip
         );
-        let clip_id = builder.define_clip(rect, vec![region], None);
-        builder.push_clip_id(clip_id);
+        let clip_id = builder.define_clip(
+            &api::SpaceAndClipInfo::root_scroll(pipeline_id),
+            rect,
+            vec![region],
+            None,
+        );
 
-        builder.push_rect(&api::PrimitiveInfo::new(rect), self.color);
-
-        builder.pop_clip_id();
+        builder.push_rect(
+            &api::PrimitiveInfo::new(rect),
+            &api::SpaceAndClipInfo {
+                spatial_id: api::SpatialId::root_scroll_node(pipeline_id),
+                clip_id,
+            },
+            self.color,
+        );
 
         let mut transaction = api::Transaction::new();
         transaction.set_display_list(
             api::Epoch(0),
             None,
             layout_size,
             builder.finalize(),
             true,
--- a/gfx/wr/examples/alpha_perf.rs
+++ b/gfx/wr/examples/alpha_perf.rs
@@ -21,33 +21,35 @@ struct App {
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         for _ in 0 .. self.rect_count {
-            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 0.05));
+            builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 0.05));
         }
 
         builder.pop_stacking_context();
     }
 
     fn on_event(
         &mut self,
         event: winit::WindowEvent,
--- a/gfx/wr/examples/animation.rs
+++ b/gfx/wr/examples/animation.rs
@@ -35,98 +35,102 @@ struct App {
 }
 
 impl App {
     fn add_rounded_rect(
         &mut self,
         bounds: LayoutRect,
         color: ColorF,
         builder: &mut DisplayListBuilder,
+        pipeline_id: PipelineId,
         property_key: PropertyBindingKey<LayoutTransform>,
         opacity_key: Option<PropertyBindingKey<f32>>,
     ) {
         let filters = match opacity_key {
             Some(opacity_key) => {
                 vec![
                     FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity),
                 ]
             }
             None => {
                 vec![]
             }
         };
 
-        let reference_frame_id = builder.push_reference_frame(
+        let spatial_id = builder.push_reference_frame(
             &LayoutRect::new(bounds.origin, LayoutSize::zero()),
+            SpatialId::root_scroll_node(pipeline_id),
             TransformStyle::Flat,
             Some(PropertyBinding::Binding(property_key, LayoutTransform::identity())),
             None,
         );
 
-        builder.push_clip_id(reference_frame_id);
-
         builder.push_stacking_context(
             &LayoutPrimitiveInfo::new(LayoutRect::zero()),
+            spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &filters,
             RasterSpace::Screen,
         );
 
+        let space_and_clip = SpaceAndClipInfo {
+            spatial_id,
+            clip_id: ClipId::root(pipeline_id),
+        };
         let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         let complex_clip = ComplexClipRegion {
             rect: clip_bounds,
             radii: BorderRadius::uniform(30.0),
             mode: ClipMode::Clip,
         };
-        let clip_id = builder.define_clip(clip_bounds, vec![complex_clip], None);
-        builder.push_clip_id(clip_id);
+        let clip_id = builder.define_clip(&space_and_clip, clip_bounds, vec![complex_clip], None);
 
         // Fill it with a white rect
         builder.push_rect(
             &LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), bounds.size)),
+            &SpaceAndClipInfo {
+                spatial_id,
+                clip_id,
+            },
             color,
         );
 
-        builder.pop_clip_id();
-
         builder.pop_stacking_context();
-
-        builder.pop_clip_id();
         builder.pop_reference_frame();
     }
 }
 
 impl Example for App {
     const WIDTH: u32 = 2048;
     const HEIGHT: u32 = 1536;
 
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let opacity_key = self.opacity_key;
 
         let bounds = (150, 150).to(250, 250);
         let key0 = self.property_key0;
-        self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, key0, Some(opacity_key));
+        self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key));
 
         let bounds = (400, 400).to(600, 600);
         let key1 = self.property_key1;
-        self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, key1, None);
+        self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, pipeline_id, key1, None);
 
         let bounds = (200, 500).to(350, 580);
         let key2 = self.property_key2;
-        self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, key2, None);
+        self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, pipeline_id, key2, None);
     }
 
     fn on_event(&mut self, win_event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         let mut rebuild_display_list = false;
 
         match win_event {
             winit::WindowEvent::KeyboardInput {
                 input: winit::KeyboardInput {
--- a/gfx/wr/examples/basic.rs
+++ b/gfx/wr/examples/basic.rs
@@ -181,23 +181,27 @@ impl Example for App {
     const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::FULL_COMPILE;
 
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+        let spatial_id = root_space_and_clip.spatial_id;
+
         builder.push_stacking_context(
             &info,
+            spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         let image_mask_key = api.generate_image_key();
@@ -212,56 +216,64 @@ impl Example for App {
             rect: (75, 75).by(100, 100),
             repeat: false,
         };
         let complex = ComplexClipRegion::new(
             (50, 50).to(150, 150),
             BorderRadius::uniform(20.0),
             ClipMode::Clip
         );
-        let id = builder.define_clip(bounds, vec![complex], Some(mask));
-        builder.push_clip_id(id);
+        let clip_id = builder.define_clip(&root_space_and_clip, bounds, vec![complex], Some(mask));
 
-        let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &LayoutPrimitiveInfo::new((100, 100).to(200, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
 
-        let info = LayoutPrimitiveInfo::new((250, 100).to(350, 200));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &LayoutPrimitiveInfo::new((250, 100).to(350, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
         let border_side = BorderSide {
             color: ColorF::new(0.0, 0.0, 1.0, 1.0),
             style: BorderStyle::Groove,
         };
         let border_widths = LayoutSideOffsets::new_all_same(10.0);
         let border_details = BorderDetails::Normal(NormalBorder {
             top: border_side,
             right: border_side,
             bottom: border_side,
             left: border_side,
             radius: BorderRadius::uniform(20.0),
             do_aa: true,
         });
 
-        let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
-        builder.push_border(&info, border_widths, border_details);
-        builder.pop_clip_id();
+        builder.push_border(
+            &LayoutPrimitiveInfo::new((100, 100).to(200, 200)),
+            &SpaceAndClipInfo { spatial_id, clip_id },
+            border_widths,
+            border_details,
+        );
 
         if false {
             // draw box shadow?
             let rect = LayoutRect::zero();
             let simple_box_bounds = (20, 200).by(50, 50);
             let offset = vec2(10.0, 10.0);
             let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
             let blur_radius = 0.0;
             let spread_radius = 0.0;
             let simple_border_radius = 8.0;
             let box_shadow_type = BoxShadowClipMode::Inset;
-            let info = LayoutPrimitiveInfo::with_clip_rect(rect, bounds);
 
             builder.push_box_shadow(
-                &info,
+                &LayoutPrimitiveInfo::with_clip_rect(rect, bounds),
+                &root_space_and_clip,
                 simple_box_bounds,
                 offset,
                 color,
                 blur_radius,
                 spread_radius,
                 BorderRadius::uniform(simple_border_radius),
                 box_shadow_type,
             );
--- a/gfx/wr/examples/blob.rs
+++ b/gfx/wr/examples/blob.rs
@@ -12,17 +12,17 @@ extern crate winit;
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::sync::Arc;
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
-use webrender::api::ColorF;
+use webrender::api::{ColorF, SpaceAndClipInfo};
 use webrender::euclid::size2;
 
 // This example shows how to implement a very basic BlobImageHandler that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
 type ImageRenderingCommands = api::ColorU;
 
@@ -195,17 +195,17 @@ struct App {}
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: api::DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img1,
             api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
             serialize_blob(api::ColorU::new(50, 50, 150, 255)),
             Some(128),
@@ -215,40 +215,42 @@ impl Example for App {
         txn.add_blob_image(
             blob_img2,
             api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
             serialize_blob(api::ColorU::new(50, 150, 50, 255)),
             None,
         );
 
         let bounds = api::LayoutRect::new(api::LayoutPoint::zero(), builder.content_size());
-        let info = api::LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
-            &info,
+            &api::LayoutPrimitiveInfo::new(bounds),
+            space_and_clip.spatial_id,
             None,
             api::TransformStyle::Flat,
             api::MixBlendMode::Normal,
             &[],
             api::RasterSpace::Screen,
         );
 
-        let info = api::LayoutPrimitiveInfo::new((30, 30).by(500, 500));
         builder.push_image(
-            &info,
+            &api::LayoutPrimitiveInfo::new((30, 30).by(500, 500)),
+            &space_and_clip,
             api::LayoutSize::new(500.0, 500.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             api::AlphaType::PremultipliedAlpha,
             blob_img1.as_image(),
             ColorF::WHITE,
         );
 
-        let info = api::LayoutPrimitiveInfo::new((600, 600).by(200, 200));
         builder.push_image(
-            &info,
+            &api::LayoutPrimitiveInfo::new((600, 600).by(200, 200)),
+            &space_and_clip,
             api::LayoutSize::new(200.0, 200.0),
             api::LayoutSize::new(0.0, 0.0),
             api::ImageRendering::Auto,
             api::AlphaType::PremultipliedAlpha,
             blob_img2.as_image(),
             ColorF::WHITE,
         );
 
--- a/gfx/wr/examples/document.rs
+++ b/gfx/wr/examples/document.rs
@@ -85,47 +85,50 @@ impl App {
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         base_builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         framebuffer_size: DeviceIntSize,
-        _: PipelineId,
+        _pipeline_id: PipelineId,
         _: DocumentId,
     ) {
         if self.documents.is_empty() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 base_builder.content_size().width;
             // this is the first run, hack around the boilerplate,
             // which assumes an example only needs one document
             self.init(api, framebuffer_size, device_pixel_ratio);
         }
 
         for doc in &self.documents {
+            let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id);
             let mut builder = DisplayListBuilder::new(
                 doc.pipeline_id,
                 doc.content_rect.size,
             );
             let local_rect = LayoutRect::new(
                 LayoutPoint::zero(),
                 doc.content_rect.size,
             );
 
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new(doc.content_rect),
+                space_and_clip.spatial_id,
                 None,
                 TransformStyle::Flat,
                 MixBlendMode::Normal,
                 &[],
                 RasterSpace::Screen,
             );
             builder.push_rect(
                 &LayoutPrimitiveInfo::new(local_rect),
+                &space_and_clip,
                 doc.color,
             );
             builder.pop_stacking_context();
 
             let mut txn = Transaction::new();
             txn.set_display_list(
                 Epoch(0),
                 None,
--- a/gfx/wr/examples/frame_output.rs
+++ b/gfx/wr/examples/frame_output.rs
@@ -102,31 +102,33 @@ impl App {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(TextureTarget::Default),
             }),
             None,
         );
 
         let info = LayoutPrimitiveInfo::new(document.content_rect);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         let mut builder = DisplayListBuilder::new(
             document.pipeline_id,
             document.content_rect.size,
         );
 
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 0.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 0.0, 1.0));
         builder.pop_stacking_context();
 
         txn.set_root_pipeline(pipeline_id);
         txn.set_display_list(
             Epoch(0),
             Some(document.color),
             document.content_rect.size,
             builder.finalize(),
@@ -140,37 +142,41 @@ impl App {
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         if self.output_document.is_none() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 builder.content_size().width;
             self.init_output_document(api, DeviceIntSize::new(200, 200), device_pixel_ratio);
         }
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         builder.push_image(
             &info,
+            &space_and_clip,
             info.rect.size,
             LayoutSize::zero(),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             self.external_image_key.unwrap(),
             ColorF::WHITE,
         );
 
--- a/gfx/wr/examples/iframe.rs
+++ b/gfx/wr/examples/iframe.rs
@@ -30,66 +30,66 @@ impl Example for App {
         document_id: DocumentId,
     ) {
         // All the sub_* things are for the nested pipeline
         let sub_size = DeviceIntSize::new(100, 100);
         let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32);
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
         let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size);
+        let mut space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         let info = LayoutPrimitiveInfo::new(sub_bounds);
         sub_builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         // green rect visible == success
-        sub_builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        sub_builder.push_rect(&info, &space_and_clip, ColorF::new(0.0, 1.0, 0.0, 1.0));
         sub_builder.pop_stacking_context();
 
         let mut txn = Transaction::new();
         txn.set_display_list(
             Epoch(0),
             None,
             sub_bounds.size,
             sub_builder.finalize(),
             true,
         );
         api.send_transaction(document_id, txn);
 
-        let info = LayoutPrimitiveInfo::new(sub_bounds);
-        let reference_frame_id = builder.push_reference_frame(
+        space_and_clip.spatial_id = builder.push_reference_frame(
             &sub_bounds,
+            space_and_clip.spatial_id,
             TransformStyle::Flat,
             Some(PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity())),
             None,
         );
-        builder.push_clip_id(reference_frame_id);
-
 
         // And this is for the root pipeline
+        let info = LayoutPrimitiveInfo::new(sub_bounds);
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
         // red rect under the iframe: if this is visible, things have gone wrong
-        builder.push_rect(&info, ColorF::new(1.0, 0.0, 0.0, 1.0));
-        builder.push_iframe(&info, sub_pipeline_id, false);
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 0.0, 0.0, 1.0));
+        builder.push_iframe(&info, &space_and_clip, sub_pipeline_id, false);
         builder.pop_stacking_context();
-
-        builder.pop_clip_id();
         builder.pop_reference_frame();
     }
 }
 
 fn main() {
     let mut app = App {};
     boilerplate::main_wrapper(&mut app, None);
 }
--- a/gfx/wr/examples/image_resize.rs
+++ b/gfx/wr/examples/image_resize.rs
@@ -21,60 +21,65 @@ struct App {
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32);
         txn.add_image(
             self.image_key,
             image_descriptor,
             image_data,
             None,
         );
 
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         let image_size = LayoutSize::new(100.0, 100.0);
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             self.image_key,
             ColorF::WHITE,
         );
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Pixelated,
             AlphaType::PremultipliedAlpha,
             self.image_key,
             ColorF::WHITE,
         );
 
--- a/gfx/wr/examples/multiwindow.rs
+++ b/gfx/wr/examples/multiwindow.rs
@@ -182,33 +182,35 @@ impl Window {
                 .get_inner_size()
                 .unwrap()
                 .to_physical(device_pixel_ratio as f64);
             DeviceIntSize::new(size.width as i32, size.height as i32)
         };
         let layout_size = framebuffer_size.to_f32() / euclid::TypedScale::new(device_pixel_ratio);
         let mut txn = Transaction::new();
         let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id);
 
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         let info = LayoutPrimitiveInfo::new(LayoutRect::new(
             LayoutPoint::new(100.0, 100.0),
             LayoutSize::new(100.0, 200.0)
         ));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         let text_bounds = LayoutRect::new(
             LayoutPoint::new(100.0, 50.0),
             LayoutSize::new(700.0, 200.0)
         );
         let glyphs = vec![
             GlyphInstance {
                 index: 48,
@@ -258,16 +260,17 @@ impl Window {
                 index: 17,
                 point: LayoutPoint::new(650.0, 100.0),
             },
         ];
 
         let info = LayoutPrimitiveInfo::new(text_bounds);
         builder.push_text(
             &info,
+            &space_and_clip,
             &glyphs,
             self.font_instance_key,
             ColorF::new(1.0, 1.0, 0.0, 1.0),
             None,
         );
 
         builder.pop_stacking_context();
 
--- a/gfx/wr/examples/scrolling.rs
+++ b/gfx/wr/examples/scrolling.rs
@@ -22,124 +22,130 @@ struct App {
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         builder.push_stacking_context(
             &info,
+            root_space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         if true {
             // scrolling and clips stuff
             // let's make a scrollbox
             let scrollbox = (0, 0).to(300, 400);
             builder.push_stacking_context(
                 &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
+                root_space_and_clip.spatial_id,
                 None,
                 TransformStyle::Flat,
                 MixBlendMode::Normal,
                 &[],
                 RasterSpace::Screen,
             );
             // set the scrolling clip
-            let clip_id = builder.define_scroll_frame(
+            let space_and_clip1 = builder.define_scroll_frame(
+                &root_space_and_clip,
                 None,
                 (0, 0).by(1000, 1000),
                 scrollbox,
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
-            builder.push_clip_id(clip_id);
 
             // now put some content into it.
             // start with a white background
             let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
             info.tag = Some((0, 1));
-            builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
             // let's make a 50x50 blue square as a visual reference
             let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
             info.tag = Some((0, 2));
-            builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(0.0, 0.0, 1.0, 1.0));
 
             // and a 50x50 green square next to it with an offset clip
             // to see what that looks like
             let mut info =
                 LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
             info.tag = Some((0, 3));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+            builder.push_rect(&info, &space_and_clip1, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
             // Below the above rectangles, set up a nested scrollbox. It's still in
             // the same stacking context, so note that the rects passed in need to
             // be relative to the stacking context.
-            let nested_clip_id = builder.define_scroll_frame(
+            let space_and_clip2 = builder.define_scroll_frame(
+                &space_and_clip1,
                 None,
                 (0, 100).to(300, 1000),
                 (0, 100).to(200, 300),
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
-            builder.push_clip_id(nested_clip_id);
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
             let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
             info.tag = Some((0, 4));
-            builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
             // add a teal square to visualize the scrolling/clipping behaviour
             // as you scroll the nested scrollbox
             let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
             info.tag = Some((0, 5));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             // Add a sticky frame. It will "stick" twice while scrolling, once
             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
             // and once at a margin of 10px from the top, for 60 pixels of
             // scrolling.
             let sticky_id = builder.define_sticky_frame(
+                space_and_clip2.spatial_id,
                 (50, 350).by(50, 50),
                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                 StickyOffsetBounds::new(-40.0, 60.0),
                 StickyOffsetBounds::new(0.0, 0.0),
                 LayoutVector2D::new(0.0, 0.0)
             );
 
-            builder.push_clip_id(sticky_id);
             let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
             info.tag = Some((0, 6));
-            builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
-            builder.pop_clip_id(); // sticky_id
+            builder.push_rect(
+                &info,
+                &SpaceAndClipInfo {
+                    spatial_id: sticky_id,
+                    clip_id: space_and_clip2.clip_id,
+                },
+                ColorF::new(0.5, 0.5, 1.0, 1.0),
+            );
 
             // just for good measure add another teal square further down and to
             // the right, which can be scrolled into view by the user
             let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
             info.tag = Some((0, 7));
-            builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
+            builder.push_rect(&info, &space_and_clip2, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
-            builder.pop_clip_id(); // nested_clip_id
-
-            builder.pop_clip_id(); // clip_id
             builder.pop_stacking_context();
         }
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         let mut txn = Transaction::new();
--- a/gfx/wr/examples/texture_cache_stress.rs
+++ b/gfx/wr/examples/texture_cache_stress.rs
@@ -86,23 +86,26 @@ struct App {
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         let x0 = 50.0;
@@ -141,16 +144,17 @@ impl Example for App {
                     LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y),
                     image_size,
                 ),
                 bounds,
             );
 
             builder.push_image(
                 &info,
+                &space_and_clip,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 *key,
                 ColorF::WHITE,
             );
         }
@@ -158,16 +162,17 @@ impl Example for App {
         if let Some(image_key) = self.image_key {
             let image_size = LayoutSize::new(100.0, 100.0);
             let info = LayoutPrimitiveInfo::with_clip_rect(
                 LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size),
                 bounds,
             );
             builder.push_image(
                 &info,
+                &space_and_clip,
                 image_size,
                 LayoutSize::zero(),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 image_key,
                 ColorF::WHITE,
             );
         }
@@ -175,16 +180,17 @@ impl Example for App {
         let swap_key = self.swap_keys[self.swap_index];
         let image_size = LayoutSize::new(64.0, 64.0);
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size),
             bounds,
         );
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             LayoutSize::zero(),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             swap_key,
             ColorF::WHITE,
         );
         self.swap_index = 1 - self.swap_index;
--- a/gfx/wr/examples/yuv.rs
+++ b/gfx/wr/examples/yuv.rs
@@ -83,23 +83,26 @@ struct App {
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
         _framebuffer_size: DeviceIntSize,
-        _pipeline_id: PipelineId,
+        pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
+
         builder.push_stacking_context(
             &info,
+            space_and_clip.spatial_id,
             None,
             TransformStyle::Flat,
             MixBlendMode::Normal,
             &[],
             RasterSpace::Screen,
         );
 
         let yuv_chanel1 = api.generate_image_key();
@@ -156,28 +159,30 @@ impl Example for App {
         );
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
             bounds,
         );
         builder.push_yuv_image(
             &info,
+            &space_and_clip,
             YuvData::NV12(yuv_chanel1, yuv_chanel2),
             ColorDepth::Color8,
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
         let info = LayoutPrimitiveInfo::with_clip_rect(
             LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
             bounds,
         );
         builder.push_yuv_image(
             &info,
+            &space_and_clip,
             YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
             ColorDepth::Color8,
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
         builder.pop_stacking_context();
     }
--- a/gfx/wr/webrender/doc/CLIPPING_AND_POSITIONING.md
+++ b/gfx/wr/webrender/doc/CLIPPING_AND_POSITIONING.md
@@ -103,50 +103,35 @@ any `ClipNode` appended to the chain. An
 to avoid having to render a mask for a large rounded rectangle when the rest of
 the clip chain constrains the content to an area completely inside that
 rectangle. Avoiding mask rasterization in this case and others has large
 performance impacts on WebRender.
 
 # Clipping and Positioning in the Display List
 
 Each non-structural WebRender display list item has
- * A `ClipId` of a `SpatialNode` or `ClipNode` for positioning
+ * A `SpatialId` of a `SpatialNode` for positioning
  * A `ClipId` of a `ClipNode` or a `ClipChain` for clipping
  * An item-specific rectangular clip rectangle
 
 The positioning node determines how that item is positioned. It's assumed that
 the positioning node and the item are children of the same reference frame. The
 clipping node determines how that item is clipped. This should be fully
 independent of how the node is positioned and items can be clipped by any
 `ClipChain` regardless of the reference frame of their member clips. Finally,
 the item-specific clipping rectangle is applied directly to the item and should
 never result in the creation of a clip mask itself.
 
-Perhaps the most inconvenient holdover from the previous single-tree
-hierarchical design is that `SpatialNodes`, `ClipNodes`, and `ClipChains` all
-share a single `ClipId` id type. This means that the client must be a bit
-careful when using the API. For instance, when specifying the parent of
-`ClipNode` one can use the `ClipId` or another `ClipNode` or a `SpatialNode`,
-but not one for a `ClipChain`.
-
-WebRender's internal representation of clipping and positioning is not a perfect
-match to the display list representation of these concepts. This is due, again,
-to the evolutionary nature of the design. The general trend is that the display
-list gradually moves toward the internal representation. The most important of
-these incongruities is that while `ClipNodes`, sticky frames, and scroll frames
-are defined and simply return a `ClipId`, reference frames return a `ClipId` and
-also are pushed and popped like stacking contexts.
-
-## Converting `ClipId` to global `ClipScrollTree` indices
+## Converting user-exposed `ClipId`/`SpatialId` to internal indices
 
 WebRender must access `ClipNodes` and `SpatialNodes` quite a bit when building
-scenes and frames, so it tries to convert `ClipIds`, which are already
+scenes and frames, so it tries to convert `ClipId`/`SpatialId`, which are already
 per-pipeline indices, to global scene-wide indices.  Internally this is a
-conversion from `ClipId` into `SpatialNodeIndex` or
-`ClipChainIndex`. In order to make this conversion cheaper, the
+conversion from `ClipId` into `ClipNodeIndex` or `ClipChainIndex`, and from
+`SpatialId` into `SpatialNodeIndex`. In order to make this conversion cheaper, the
 `DisplayListFlattner` assigns offsets for each pipeline and node type in the
 scene-wide `ClipScrollTree`.
 
 Nodes are added to their respective arrays sequentially as the display list is
 processed during "flattening." When encountering an iframe, the
 `DisplayListFlattener` must start processing the nodes for that iframe's
 pipeline, meaning that nodes are now being added out of order to the node arrays
 of the `ClipScrollTree`. In this case, the `ClipScrollTree` fills in the gaps in
@@ -155,13 +140,12 @@ the node arrays with placeholder nodes.
 # Hit Testing
 
 Hit testing is the responsibility of the `HitTester` data structure. This
 structure copies information necessary for hit testing from the
 `ClipScrollTree`. This is done so that hit testing can still take place while a
 new `ClipScrollTree` is under construction.
 
 # Ideas for the Future
-1. Expose the difference between ids for `SpatialNodes`, `ClipNodes`, and
-   `ClipChains` in the API.
+1. Expose the difference between `ClipId` and `ClipChainId` in the API.
 2. Prevent having to duplicate the `ClipScrollTree` for hit testing.
 3. Avoid having to create placeholder nodes in the `ClipScrollTree` while
    processing iframes.
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -199,16 +199,17 @@ bitflags! {
 pub struct ClipChainId(pub u32);
 
 // The root of each clip chain is the NONE id. The
 // value is specifically set to u32::MAX so that if
 // any code accidentally tries to access the root
 // node, a bounds error will occur.
 impl ClipChainId {
     pub const NONE: Self = ClipChainId(u32::MAX);
+    pub const INVALID: Self = ClipChainId(0xDEADBEEF);
 }
 
 // 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, Debug)]
 pub struct ClipChainNode {
     pub handle: ClipDataHandle,
     pub local_pos: LayoutPoint,
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -1,15 +1,15 @@
 /* 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::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
-use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
-use api::{TransformStyle, LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
+use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity};
+use api::{LayoutSize, LayoutTransform, PropertyBinding, TransformStyle, WorldPoint};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use scene::SceneProperties;
 use smallvec::SmallVec;
 use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
 use util::{LayoutToWorldFastTransform, ScaleOffset};
 
@@ -41,16 +41,17 @@ impl CoordinateSystem {
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SpatialNodeIndex(pub u32);
 
+//Note: these have to match ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
 pub const ROOT_SPATIAL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
 const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
 
 impl SpatialNodeIndex {
     pub fn new(index: usize) -> Self {
         debug_assert!(index < ::std::u32::MAX as usize);
         SpatialNodeIndex(index as u32)
     }
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1,21 +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::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DisplayItemRef, ExtendMode, ExternalScrollId, AuHelpers};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, ScrollFrameDisplayItem, ScrollSensitivity};
-use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{Shadow, SpaceAndClipInfo, SpatialId, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
@@ -44,74 +44,59 @@ use util::{MaxRect, VecHelper};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
 
 impl ClipNode {
-    fn new(id: ClipChainId, count: usize) -> ClipNode {
+    fn new(id: ClipChainId, count: usize) -> Self {
         ClipNode {
             id,
             count,
         }
     }
 }
 
-/// A data structure that keeps track of mapping between API ClipIds and the indices used
-/// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
-/// responsible for mapping both ClipId to ClipChainIndex and ClipId to SpatialNodeIndex.
+/// A data structure that keeps track of mapping between API Ids for clips/spatials and the indices
+/// used internally in the ClipScrollTree to avoid having to do HashMap lookups. NodeIdToIndexMapper
+/// is responsible for mapping both ClipId to ClipChainIndex and SpatialId to SpatialNodeIndex.
 #[derive(Default)]
-pub struct ClipIdToIndexMapper {
+pub struct NodeIdToIndexMapper {
     clip_node_map: FastHashMap<ClipId, ClipNode>,
-    spatial_node_map: FastHashMap<ClipId, SpatialNodeIndex>,
+    spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
 }
 
-impl ClipIdToIndexMapper {
+impl NodeIdToIndexMapper {
     pub fn add_clip_chain(
         &mut self,
         id: ClipId,
         index: ClipChainId,
         count: usize,
     ) {
         let _old_value = self.clip_node_map.insert(id, ClipNode::new(index, count));
         debug_assert!(_old_value.is_none());
     }
 
-    pub fn map_to_parent_clip_chain(
-        &mut self,
-        id: ClipId,
-        parent_id: &ClipId,
-    ) {
-        let parent_node = self.clip_node_map[parent_id];
-        self.add_clip_chain(id, parent_node.id, parent_node.count);
-    }
-
-    pub fn map_spatial_node(&mut self, id: ClipId, index: SpatialNodeIndex) {
+    pub fn map_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
         let _old_value = self.spatial_node_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
 
     fn get_clip_node(&self, id: &ClipId) -> ClipNode {
         self.clip_node_map[id]
     }
 
-    pub fn get_clip_chain_id(&self, id: &ClipId) -> ClipChainId {
-        self.clip_node_map[id].id
+    pub fn get_clip_chain_id(&self, id: ClipId) -> ClipChainId {
+        self.clip_node_map[&id].id
     }
 
-    pub fn get_spatial_node_index(&self, id: ClipId) -> SpatialNodeIndex {
-        match id {
-            ClipId::Clip(..) |
-            ClipId::Spatial(..) => {
-                self.spatial_node_map[&id]
-            }
-            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
-        }
+    pub fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
+        self.spatial_node_map[&id]
     }
 }
 
 /// A structure that converts a serialized display list into a form that WebRender
 /// can use to later build a frame. This structure produces a FrameBuilder. Public
 /// members are typically those that are destructured into the FrameBuilder.
 pub struct DisplayListFlattener<'a> {
     /// The scene that we are currently flattening.
@@ -122,19 +107,19 @@ pub struct DisplayListFlattener<'a> {
 
     /// The map of all font instances.
     font_instances: FontInstanceMap,
 
     /// A set of pipelines that the caller has requested be made available as
     /// output textures.
     output_pipelines: &'a FastHashSet<PipelineId>,
 
-    /// The data structure that converting between ClipId and the various index
-    /// types that the ClipScrollTree uses.
-    id_to_index_mapper: ClipIdToIndexMapper,
+    /// The data structure that converting between ClipId/SpatialId and the various
+    /// index types that the ClipScrollTree uses.
+    id_to_index_mapper: NodeIdToIndexMapper,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
     /// Maintains state for any currently active shadows
     pending_shadow_items: VecDeque<ShadowItem>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
@@ -183,17 +168,17 @@ impl<'a> DisplayListFlattener<'a> {
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
 
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
-            id_to_index_mapper: ClipIdToIndexMapper::default(),
+            id_to_index_mapper: NodeIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
             pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(&prim_store_stats),
             clip_store: ClipStore::new(),
             resources,
             root_pic_index: PictureIndex(0),
@@ -448,52 +433,55 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     fn flatten_root(
         &mut self,
         pipeline: &'a ScenePipeline,
         frame_size: &LayoutSize,
     ) {
         let pipeline_id = pipeline.pipeline_id;
-        let reference_frame_info = self.simple_scroll_and_clip_chain(
-            &ClipId::root_reference_frame(pipeline_id),
-        );
-
-        let root_scroll_node = ClipId::root_scroll_node(pipeline_id);
 
         self.push_stacking_context(
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
-            root_scroll_node,
-            None,
+            ROOT_SPATIAL_NODE_INDEX,
+            ClipChainId::NONE,
             RasterSpace::Screen,
         );
 
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         // TODO(gw): In future, we can probably remove this code completely and handle
         //           it as part of the tile cache background color clearing.
         if self.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
+                    let reference_frame_info = ScrollNodeAndClipChain::new(
+                        self.id_to_index_mapper.get_spatial_node_index(SpatialId::root_reference_frame(pipeline_id)),
+                        ClipChainId::NONE,
+                    );
                     let root_bounds = LayoutRect::new(LayoutPoint::zero(), *frame_size);
                     let info = LayoutPrimitiveInfo::new(root_bounds);
                     self.add_solid_rectangle(
                         reference_frame_info,
                         &info,
                         bg_color,
                     );
                 }
             }
         }
 
-        self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
+        self.flatten_items(
+            &mut pipeline.display_list.iter(),
+            pipeline_id,
+            LayoutVector2D::zero(),
+        );
 
         self.pop_stacking_context();
     }
 
     fn flatten_items(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
@@ -501,22 +489,20 @@ impl<'a> DisplayListFlattener<'a> {
     ) {
         loop {
             let subtraversal = {
                 let item = match traversal.next() {
                     Some(item) => item,
                     None => break,
                 };
 
-                if SpecificDisplayItem::PopReferenceFrame == *item.item() {
-                    return;
-                }
-
-                if SpecificDisplayItem::PopStackingContext == *item.item() {
-                    return;
+                match item.item() {
+                    SpecificDisplayItem::PopReferenceFrame |
+                    SpecificDisplayItem::PopStackingContext => return,
+                    _ => (),
                 }
 
                 self.flatten_item(
                     item,
                     pipeline_id,
                     reference_frame_relative_offset,
                 )
             };
@@ -528,187 +514,188 @@ impl<'a> DisplayListFlattener<'a> {
             }
         }
     }
 
     fn flatten_sticky_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &StickyFrameDisplayItem,
-        clip_and_scroll: &ScrollNodeAndClipChain,
-        parent_id: &ClipId,
+        parent_node_index: SpatialNodeIndex,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let frame_rect = item.rect().translate(reference_frame_relative_offset);
         let sticky_frame_info = StickyFrameInfo::new(
             frame_rect,
             info.margins,
             info.vertical_offset_bounds,
             info.horizontal_offset_bounds,
             info.previously_applied_offset,
         );
 
         let index = self.clip_scroll_tree.add_sticky_frame(
-            clip_and_scroll.spatial_node_index, /* parent id */
+            parent_node_index,
             sticky_frame_info,
             info.id.pipeline_id(),
         );
         self.id_to_index_mapper.map_spatial_node(info.id, index);
-        self.id_to_index_mapper.map_to_parent_clip_chain(info.id, parent_id);
     }
 
     fn flatten_scroll_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &ScrollFrameDisplayItem,
+        parent_node_index: SpatialNodeIndex,
         pipeline_id: PipelineId,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
         let clip_region = ClipRegion::create_for_clip_node(
             *item.clip_rect(),
             complex_clips,
             info.image_mask,
             reference_frame_relative_offset,
         );
         // Just use clip rectangle as the frame rect for this scroll frame.
         // This is useful when calculating scroll extents for the
         // SpatialNode::scroll(..) API as well as for properly setting sticky
         // positioning offsets.
-        let frame_rect = item.clip_rect().translate(reference_frame_relative_offset);
-        let content_rect = item.rect().translate(reference_frame_relative_offset);
+        let frame_rect = clip_region.main.translate(reference_frame_relative_offset);
+        let content_size = item.rect().size;
 
-        debug_assert!(info.clip_id != info.scroll_frame_id);
-
-        self.add_clip_node(info.clip_id, clip_and_scroll_ids, clip_region);
+        self.add_clip_node(info.clip_id, item.space_and_clip_info(), clip_region);
 
         self.add_scroll_frame(
             info.scroll_frame_id,
-            info.clip_id,
+            parent_node_index,
             info.external_id,
             pipeline_id,
             &frame_rect,
-            &content_rect.size,
+            &content_size,
             info.scroll_sensitivity,
             ScrollFrameKind::Explicit,
         );
     }
 
     fn flatten_reference_frame(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
-        item: &DisplayItemRef,
+        parent_spatial_node: SpatialNodeIndex,
+        origin: LayoutPoint,
         reference_frame: &ReferenceFrame,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
         reference_frame_relative_offset: LayoutVector2D,
     ) {
         self.push_reference_frame(
             reference_frame.id,
-            Some(clip_and_scroll_ids.scroll_node_id),
-            clip_and_scroll_ids.clip_node_id,
+            Some(parent_spatial_node),
             pipeline_id,
             reference_frame.transform_style,
             reference_frame.transform,
             reference_frame.perspective,
-            reference_frame_relative_offset + item.rect().origin.to_vector(),
+            reference_frame_relative_offset + origin.to_vector(),
         );
 
         self.flatten_items(traversal, pipeline_id, LayoutVector2D::zero());
     }
 
+
     fn flatten_stacking_context(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
-        item: &DisplayItemRef,
         stacking_context: &StackingContext,
-        scroll_node_id: ClipId,
-        reference_frame_relative_offset: LayoutVector2D,
+        spatial_node_index: SpatialNodeIndex,
+        origin: LayoutPoint,
+        filters: ItemRange<FilterOp>,
+        reference_frame_relative_offset: &LayoutVector2D,
         is_backface_visible: bool,
     ) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
             // TODO(optimization?): self.traversal.display_list()
             let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
             CompositeOps::new(
-                stacking_context.filter_ops_for_compositing(display_list, item.filters()),
+                stacking_context.filter_ops_for_compositing(display_list, filters),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
+        let clip_chain_id = match stacking_context.clip_id {
+            Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
+            None => ClipChainId::NONE,
+        };
+
         self.push_stacking_context(
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
-            scroll_node_id,
-            stacking_context.clip_node_id,
+            spatial_node_index,
+            clip_chain_id,
             stacking_context.raster_space,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
-            reference_frame_relative_offset + item.rect().origin.to_vector(),
+            *reference_frame_relative_offset + origin.to_vector(),
         );
 
         self.pop_stacking_context();
     }
 
     fn flatten_iframe(
         &mut self,
         item: &DisplayItemRef,
         info: &IframeDisplayItem,
-        clip_and_scroll_ids: &ClipAndScrollInfo,
+        spatial_node_index: SpatialNodeIndex,
         reference_frame_relative_offset: &LayoutVector2D,
     ) {
         let iframe_pipeline_id = info.pipeline_id;
         let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
             Some(pipeline) => pipeline,
             None => {
                 debug_assert!(info.ignore_missing_pipeline);
                 return
             },
         };
 
         let clip_chain_index = self.add_clip_node(
-            info.clip_id,
-            clip_and_scroll_ids,
+            ClipId::root(iframe_pipeline_id),
+            item.space_and_clip_info(),
             ClipRegion::create_for_clip_node_with_local_clip(
                 item.clip_rect(),
-                reference_frame_relative_offset
+                reference_frame_relative_offset,
             ),
         );
         self.pipeline_clip_chain_stack.push(clip_chain_index);
 
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
-        self.push_reference_frame(
-            ClipId::root_reference_frame(iframe_pipeline_id),
-            Some(info.clip_id),
-            None,
+        let spatial_node_index = self.push_reference_frame(
+            SpatialId::root_reference_frame(iframe_pipeline_id),
+            Some(spatial_node_index),
             iframe_pipeline_id,
             TransformStyle::Flat,
             None,
             None,
             origin,
         );
 
         let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
         self.add_scroll_frame(
-            ClipId::root_scroll_node(iframe_pipeline_id),
-            ClipId::root_reference_frame(iframe_pipeline_id),
+            SpatialId::root_scroll_node(iframe_pipeline_id),
+            spatial_node_index,
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             ScrollFrameKind::PipelineRoot,
         );
 
@@ -721,20 +708,27 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     fn flatten_item<'b>(
         &'b mut self,
         item: DisplayItemRef<'a, 'b>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayoutVector2D,
     ) -> Option<BuiltDisplayListIter<'a>> {
-        let clip_and_scroll_ids = item.clip_and_scroll();
-        let clip_and_scroll = self.map_clip_and_scroll(&clip_and_scroll_ids);
+        let space_and_clip = item.space_and_clip_info();
+        let clip_and_scroll = ScrollNodeAndClipChain::new(
+            self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id),
+            if space_and_clip.clip_id.is_valid() {
+                self.id_to_index_mapper.get_clip_chain_id(space_and_clip.clip_id)
+            } else {
+                ClipChainId::INVALID
+            },
+        );
+        let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
 
-        let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset);
         match *item.item() {
             SpecificDisplayItem::Image(ref info) => {
                 self.add_image(
                     clip_and_scroll,
                     &prim_info,
                     info.stretch_size,
                     info.tile_spacing,
                     None,
@@ -856,83 +850,83 @@ impl<'a> DisplayListFlattener<'a> {
                     pipeline_id,
                 );
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
-                    &item,
                     &info.stacking_context,
-                    clip_and_scroll_ids.scroll_node_id,
-                    reference_frame_relative_offset,
+                    clip_and_scroll.spatial_node_index,
+                    item.rect().origin,
+                    item.filters(),
+                    &reference_frame_relative_offset,
                     prim_info.is_backface_visible,
                 );
                 return Some(subtraversal);
             }
             SpecificDisplayItem::PushReferenceFrame(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_reference_frame(
                     &mut subtraversal,
                     pipeline_id,
-                    &item,
+                    clip_and_scroll.spatial_node_index,
+                    item.rect().origin,
                     &info.reference_frame,
-                    &clip_and_scroll_ids,
                     reference_frame_relative_offset,
                 );
                 return Some(subtraversal);
-
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(
                     &item,
                     info,
-                    &clip_and_scroll_ids,
-                    &reference_frame_relative_offset
+                    clip_and_scroll.spatial_node_index,
+                    &reference_frame_relative_offset,
                 );
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let clip_region = ClipRegion::create_for_clip_node(
                     *item.clip_rect(),
                     complex_clips,
                     info.image_mask,
                     &reference_frame_relative_offset,
                 );
-                self.add_clip_node(info.id, &clip_and_scroll_ids, clip_region);
+                self.add_clip_node(info.id, space_and_clip, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
                 // For a user defined clip-chain the parent (if specified) must
                 // refer to another user defined clip-chain. If none is specified,
                 // the parent is the root clip-chain for the given pipeline. This
                 // is used to provide a root clip chain for iframes.
                 let mut parent_clip_chain_id = match info.parent {
                     Some(id) => {
-                        self.id_to_index_mapper.get_clip_chain_id(&ClipId::ClipChain(id))
+                        self.id_to_index_mapper.get_clip_chain_id(ClipId::ClipChain(id))
                     }
                     None => {
                         self.pipeline_clip_chain_stack.last().cloned().unwrap()
                     }
                 };
 
                 // Create a linked list of clip chain nodes. To do this, we will
                 // create a clip chain node + clip source for each listed clip id,
                 // and link these together, with the root of this list parented to
                 // the parent clip chain node found above. For this API, the clip
                 // id that is specified for an existing clip chain node is used to
                 // get the index of the clip sources that define that clip node.
                 let mut clip_chain_id = parent_clip_chain_id;
 
                 // For each specified clip id
-                for item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
+                for clip_item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
                     // Map the ClipId to an existing clip chain node.
                     let item_clip_node = self
                         .id_to_index_mapper
-                        .get_clip_node(&item);
+                        .get_clip_node(&clip_item);
 
                     let mut clip_node_clip_chain_id = item_clip_node.id;
 
                     // Each 'clip node' (as defined by the WR API) can contain one or
                     // more clip items (e.g. rects, image masks, rounded rects). When
                     // each of these clip nodes is stored internally, they are stored
                     // as a clip chain (one clip item per node), eventually parented
                     // to the parent clip node. For a user defined clip chain, we will
@@ -968,35 +962,35 @@ impl<'a> DisplayListFlattener<'a> {
                 // 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.
                 self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id, 0);
             },
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 self.flatten_scroll_frame(
                     &item,
                     info,
+                    clip_and_scroll.spatial_node_index,
                     pipeline_id,
-                    &clip_and_scroll_ids,
-                    &reference_frame_relative_offset
+                    &reference_frame_relative_offset,
                 );
             }
             SpecificDisplayItem::StickyFrame(ref info) => {
                 self.flatten_sticky_frame(
                     &item,
                     info,
-                    &clip_and_scroll,
-                    &clip_and_scroll_ids.scroll_node_id,
-                    &reference_frame_relative_offset
+                    clip_and_scroll.spatial_node_index,
+                    &reference_frame_relative_offset,
                 );
             }
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => {}
 
-            SpecificDisplayItem::PopStackingContext | SpecificDisplayItem::PopReferenceFrame => {
+            SpecificDisplayItem::PopReferenceFrame |
+            SpecificDisplayItem::PopStackingContext => {
                 unreachable!("Should have returned in parent method.")
             }
             SpecificDisplayItem::PushShadow(shadow) => {
                 self.push_shadow(shadow, clip_and_scroll);
             }
             SpecificDisplayItem::PopAllShadows => {
                 self.pop_all_shadows();
             }
@@ -1025,23 +1019,22 @@ impl<'a> DisplayListFlattener<'a> {
 
             for (local_pos, item) in clip_items {
                 // Intern this clip item, and store the handle
                 // in the clip chain node.
                 let handle = self.resources
                     .clip_interner
                     .intern(&item, || ());
 
-                clip_chain_id = self.clip_store
-                                    .add_clip_chain_node(
-                                        handle,
-                                        local_pos,
-                                        spatial_node_index,
-                                        clip_chain_id,
-                                    );
+                clip_chain_id = self.clip_store.add_clip_chain_node(
+                    handle,
+                    local_pos,
+                    spatial_node_index,
+                    clip_chain_id,
+                );
             }
 
             clip_chain_id
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
@@ -1213,26 +1206,20 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
-        spatial_node: ClipId,
-        clipping_node: Option<ClipId>,
+        spatial_node_index: SpatialNodeIndex,
+        clip_chain_id: ClipChainId,
         requested_raster_space: RasterSpace,
     ) {
-        let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
-        let clip_chain_id = match clipping_node {
-            Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
-            None => ClipChainId::NONE,
-        };
-
         // Check if this stacking context is the root of a pipeline, and the caller
         // has requested it as an output frame.
         let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
             Some(pipeline_id)
         } else {
             None
         };
 
@@ -1289,17 +1276,17 @@ impl<'a> DisplayListFlattener<'a> {
         } else {
             Picture3DContext::Out
         };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        let should_isolate = clipping_node.is_some();
+        let should_isolate = clip_chain_id != ClipChainId::NONE;
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
             primitives: Vec::new(),
             pipeline_id,
             is_backface_visible,
             requested_raster_space,
@@ -1581,100 +1568,88 @@ impl<'a> DisplayListFlattener<'a> {
         assert!(
             self.pending_shadow_items.is_empty(),
             "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
-        reference_frame_id: ClipId,
-        parent_scroll_id: Option<ClipId>,
-        parent_clip_id: Option<ClipId>,
+        reference_frame_id: SpatialId,
+        parent_index: Option<SpatialNodeIndex>,
         pipeline_id: PipelineId,
         transform_style: TransformStyle,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayoutVector2D,
     ) -> SpatialNodeIndex {
-        let parent_index = parent_scroll_id.map(|id| self.id_to_index_mapper.get_spatial_node_index(id));
         let index = self.clip_scroll_tree.add_reference_frame(
             parent_index,
             transform_style,
             source_transform,
             source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.id_to_index_mapper.map_spatial_node(reference_frame_id, index);
 
-        match parent_clip_id.or(parent_scroll_id) {
-            Some(ref parent_id) =>
-                self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
-            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainId::NONE, 0),
-        }
         index
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayoutSize,
         content_size: &LayoutSize,
     ) {
         if let ChasePrimitive::Id(id) = self.config.chase_primitive {
             println!("Chasing {:?} by index", id);
             register_prim_chase_id(id);
         }
 
-        self.push_reference_frame(
-            ClipId::root_reference_frame(pipeline_id),
-            None,
+        self.id_to_index_mapper.add_clip_chain(ClipId::root(pipeline_id), ClipChainId::NONE, 0);
+
+        let spatial_node_index = self.push_reference_frame(
+            SpatialId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
             TransformStyle::Flat,
             None,
             None,
             LayoutVector2D::zero(),
         );
 
         self.add_scroll_frame(
-            ClipId::root_scroll_node(pipeline_id),
-            ClipId::root_reference_frame(pipeline_id),
+            SpatialId::root_scroll_node(pipeline_id),
+            spatial_node_index,
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             ScrollFrameKind::PipelineRoot,
         );
     }
 
     pub fn add_clip_node<I>(
         &mut self,
         new_node_id: ClipId,
-        parent: &ClipAndScrollInfo,
+        space_and_clip: &SpaceAndClipInfo,
         clip_region: ClipRegion<I>,
     ) -> ClipChainId
     where
         I: IntoIterator<Item = ComplexClipRegion>
     {
         // Add a new ClipNode - this is a ClipId that identifies a list of clip items,
         // and the positioning node associated with those clip sources.
 
         // Map from parent ClipId to existing clip-chain.
-        let mut parent_clip_chain_index = self
-            .id_to_index_mapper
-            .get_clip_chain_id(&parent.clip_node_id());
+        let mut parent_clip_chain_index = self.id_to_index_mapper.get_clip_chain_id(space_and_clip.clip_id);
         // Map the ClipId for the positioning node to a spatial node index.
-        let spatial_node = self.id_to_index_mapper.get_spatial_node_index(parent.scroll_node_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 spatial_node = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
 
         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.
 
@@ -1735,37 +1710,35 @@ impl<'a> DisplayListFlattener<'a> {
             clip_count,
         );
 
         parent_clip_chain_index
     }
 
     pub fn add_scroll_frame(
         &mut self,
-        new_node_id: ClipId,
-        parent_id: ClipId,
+        new_node_id: SpatialId,
+        parent_node_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
         frame_kind: ScrollFrameKind,
     ) -> SpatialNodeIndex {
-        let parent_node_index = self.id_to_index_mapper.get_spatial_node_index(parent_id);
         let node_index = self.clip_scroll_tree.add_scroll_frame(
             parent_node_index,
             external_id,
             pipeline_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
             frame_kind,
         );
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
-        self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
@@ -2472,27 +2445,16 @@ impl<'a> DisplayListFlattener<'a> {
                 yuv_key,
                 format,
                 color_space,
                 image_rendering,
             },
         );
     }
 
-    pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
-        ScrollNodeAndClipChain::new(
-            self.id_to_index_mapper.get_spatial_node_index(info.scroll_node_id),
-            self.id_to_index_mapper.get_clip_chain_id(&info.clip_node_id())
-        )
-    }
-
-    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
-        self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
-    }
-
     pub fn add_primitive_instance_to_3d_root(&mut self, instance: PrimitiveInstance) {
         // find the 3D root and append to the children list
         for sc in self.sc_stack.iter_mut().rev() {
             match sc.context_3d {
                 Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
                     prims.push(instance);
                     break;
                 }
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -75,16 +75,17 @@ pub type TileOffset = TypedPoint2D<i32, 
 pub type TileSize = TypedSize2D<i32, TileCoordinate>;
 pub struct TileIndex(pub usize);
 
 /// The size in device pixels of a cached tile. The currently chosen
 /// size is arbitrary. We should do some profiling to find the best
 /// size for real world pages.
 pub const TILE_SIZE_WIDTH: i32 = 1024;
 pub const TILE_SIZE_HEIGHT: i32 = 256;
+const FRAMES_BEFORE_CACHING: usize = 2;
 
 #[derive(Debug)]
 pub struct GlobalTransformInfo {
     /// Current (quantized) value of the transform, that is
     /// independent of the value of the spatial node index.
     /// Only calculated on first use.
     current: Option<TransformKey>,
     /// Tiles check this to see if the dependencies have changed.
@@ -122,30 +123,32 @@ struct TileId(usize);
 
 /// Information about a cached tile.
 #[derive(Debug)]
 pub struct Tile {
     /// The current world rect of thie tile.
     world_rect: WorldRect,
     /// The current local rect of this tile.
     pub local_rect: LayoutRect,
-    /// The valid rect within this tile.
-    valid_rect: WorldRect,
     /// The currently visible rect within this tile, updated per frame.
     /// If None, this tile is not currently visible.
     visible_rect: Option<WorldRect>,
     /// Uniquely describes the content of this tile, in a way that can be
     /// (reasonably) efficiently hashed and compared.
     descriptor: TileDescriptor,
     /// Handle to the cached texture for this tile.
     pub handle: TextureCacheHandle,
     /// If true, this tile is marked valid, and the existing texture
     /// cache handle can be used. Tiles are invalidated during the
     /// build_dirty_regions method.
     is_valid: bool,
+    /// If true, the content on this tile is the same as last frame.
+    is_same_content: bool,
+    /// The number of frames this tile has had the same content.
+    same_frames: usize,
     /// The tile id is stable between display lists and / or frames,
     /// if the tile is retained. Useful for debugging tile evictions.
     id: TileId,
     /// The set of transforms that affect primitives on this tile we
     /// care about. Stored as a set here, and then collected, sorted
     /// and converted to transform key values during post_update.
     transforms: FastHashSet<SpatialNodeIndex>,
 }
@@ -153,46 +156,76 @@ pub struct Tile {
 impl Tile {
     /// Construct a new, invalid tile.
     fn new(
         id: TileId,
     ) -> Self {
         Tile {
             local_rect: LayoutRect::zero(),
             world_rect: WorldRect::zero(),
-            valid_rect: WorldRect::zero(),
             visible_rect: None,
             handle: TextureCacheHandle::invalid(),
             descriptor: TileDescriptor::new(),
+            is_same_content: false,
             is_valid: false,
+            same_frames: 0,
             transforms: FastHashSet::default(),
             id,
         }
     }
 
     /// Clear the dependencies for a tile.
     fn clear(&mut self) {
         self.transforms.clear();
         self.descriptor.clear();
     }
+
+    /// Update state related to whether a tile has the same
+    /// content and is valid to use.
+    fn update_validity(&mut self, tile_bounding_rect: &WorldRect) {
+        // Check if the contents of the primitives, clips, and
+        // other dependencies are the same.
+        self.is_same_content &= self.descriptor.is_same_content();
+
+        // The tile is only valid if:
+        // - The content is the same *and*
+        // - The valid part of the tile is the same wrt to world clips.
+        self.is_valid &= self.is_same_content;
+        self.is_valid &= self.descriptor.is_valid(&tile_bounding_rect);
+
+        // Update count of how many times this tile has had the same content.
+        if !self.is_same_content {
+            self.same_frames = 0;
+        }
+        self.same_frames += 1;
+    }
 }
 
 /// Defines a key that uniquely identifies a primitive instance.
 #[derive(Debug, Clone, PartialEq)]
 pub struct PrimitiveDescriptor {
     /// Uniquely identifies the content of the primitive template.
     prim_uid: ItemUid,
-    /// The origin in local space of this primitive.
-    origin: LayoutPoint,
+    /// The origin in world space of this primitive.
+    origin: WorldPoint,
     /// The first clip in the clip_uids array of clips that affect this tile.
     first_clip: u16,
     /// The number of clips that affect this primitive instance.
     clip_count: u16,
 }
 
+/// Defines the region of a primitive that exists on a tile.
+#[derive(Debug)]
+pub struct PrimitiveRegion {
+    /// The (prim relative) portion of on this tile.
+    prim_region: WorldRect,
+    /// Location within the tile.
+    tile_offset: WorldPoint,
+}
+
 /// Uniquely describes the content of this tile, in a way that can be
 /// (reasonably) efficiently hashed and compared.
 #[derive(Debug)]
 pub struct TileDescriptor {
     /// List of primitive instance unique identifiers. The uid is guaranteed
     /// to uniquely describe the content of the primitive template, while
     /// the other parameters describe the clip chain and instance params.
     prims: ComparableVec<PrimitiveDescriptor>,
@@ -209,81 +242,105 @@ pub struct TileDescriptor {
     /// List of image keys that this tile depends on.
     image_keys: ComparableVec<ImageKey>,
 
     /// The set of opacity bindings that this tile depends on.
     // TODO(gw): Ugh, get rid of all opacity binding support!
     opacity_bindings: ComparableVec<OpacityBinding>,
 
     /// List of the required valid rectangles for each primitive.
-    needed_rects: Vec<WorldRect>,
+    needed_regions: Vec<PrimitiveRegion>,
 
     /// List of the currently valid rectangles for each primitive.
-    current_rects: Vec<WorldRect>,
+    current_regions: Vec<PrimitiveRegion>,
 
     /// List of the (quantized) transforms that we care about
     /// tracking for this tile.
     transforms: ComparableVec<TransformKey>,
 }
 
 impl TileDescriptor {
     fn new() -> Self {
         TileDescriptor {
             prims: ComparableVec::new(),
             clip_uids: ComparableVec::new(),
             clip_vertices: ComparableVec::new(),
             opacity_bindings: ComparableVec::new(),
             image_keys: ComparableVec::new(),
-            needed_rects: Vec::new(),
-            current_rects: Vec::new(),
+            needed_regions: Vec::new(),
+            current_regions: Vec::new(),
             transforms: ComparableVec::new(),
         }
     }
 
     /// Clear the dependency information for a tile, when the dependencies
     /// are being rebuilt.
     fn clear(&mut self) {
         self.prims.reset();
         self.clip_uids.reset();
         self.clip_vertices.reset();
         self.opacity_bindings.reset();
         self.image_keys.reset();
-        self.needed_rects.clear();
+        self.needed_regions.clear();
         self.transforms.reset();
     }
 
-    /// Check if the dependencies of this tile are valid.
-    fn is_valid(&self) -> bool {
+    /// Return true if the content of the tile is the same
+    /// as last frame. This doesn't check validity of the
+    /// tile based on the currently valid regions.
+    fn is_same_content(&self) -> bool {
+        self.image_keys.is_valid() &&
+        self.opacity_bindings.is_valid() &&
+        self.clip_uids.is_valid() &&
+        self.clip_vertices.is_valid() &&
+        self.prims.is_valid() &&
+        self.transforms.is_valid()
+    }
+
+    /// Check if the tile is valid, given that the rest of the content is the same.
+    fn is_valid(&self, tile_bounding_rect: &WorldRect) -> bool {
         // For a tile to be valid, it needs to ensure that the currently valid
         // rect of each primitive encloses the required valid rect.
         // TODO(gw): This is only needed for tiles that are partially rendered
         //           (i.e. those clipped to edge of screen). We can make this much
         //           faster by skipping this step for tiles that are not clipped!
         // TODO(gw): For partial tiles that *do* need this test, we can probably
         //           make it faster again by caching and checking the relative
         //           transforms of primitives on this tile.
-        let rects_valid = if self.needed_rects.len() == self.current_rects.len() {
-            for (needed, current) in self.needed_rects.iter().zip(self.current_rects.iter()) {
-                if !current.contains_rect(needed) {
+        if self.needed_regions.len() == self.current_regions.len() {
+            for (needed, current) in self.needed_regions.iter().zip(self.current_regions.iter()) {
+                let needed_region = needed
+                    .prim_region
+                    .translate(&needed.tile_offset.to_vector())
+                    .intersection(tile_bounding_rect);
+
+                let needed_rect = match needed_region {
+                    Some(rect) => rect,
+                    None => continue,
+                };
+
+                let current_region = current
+                    .prim_region
+                    .translate(&current.tile_offset.to_vector())
+                    .intersection(tile_bounding_rect);
+
+                let current_rect = match current_region {
+                    Some(rect) => rect,
+                    None => return false,
+                };
+
+                if needed_rect != current_rect {
                     return false;
                 }
             }
 
             true
         } else {
             false
-        };
-
-        self.image_keys.is_valid() &&
-        self.opacity_bindings.is_valid() &&
-        self.clip_uids.is_valid() &&
-        self.clip_vertices.is_valid() &&
-        self.prims.is_valid() &&
-        self.transforms.is_valid() &&
-        rects_valid
+        }
     }
 }
 
 /// Represents the dirty region of a tile cache picture.
 /// In future, we will want to support multiple dirty
 /// regions.
 #[derive(Debug)]
 pub struct DirtyRegion {
@@ -607,33 +664,36 @@ impl TileCache {
         // TODO(gw): We don't actually need to update the prim dependencies each frame.
         //           For common cases, such as only being one main scroll root, we could
         //           detect this and skip the dependency update on scroll frames.
         self.needs_update = true;
         self.world_bounding_rect = WorldRect::zero();
 
         // Do tile invalidation for any dependencies that we know now.
         for tile in &mut self.tiles {
-            // Invalidate the tile if any images have changed
+            // Start frame assuming that the tile has the same content.
+            tile.is_same_content = true;
+
+            // Content has changed if any images have changed
             for image_key in tile.descriptor.image_keys.items() {
                 if resource_cache.is_image_dirty(*image_key) {
-                    tile.is_valid = false;
+                    tile.is_same_content = false;
                     break;
                 }
             }
 
-            // Invalidate the tile if any opacity bindings changed.
+            // Content has changed if any opacity bindings changed.
             for binding in tile.descriptor.opacity_bindings.items() {
                 if let OpacityBinding::Binding(id) = binding {
                     let changed = match self.opacity_bindings.get(id) {
                         Some(info) => info.changed,
                         None => true,
                     };
                     if changed {
-                        tile.is_valid = false;
+                        tile.is_same_content = false;
                         break;
                     }
                 }
             }
 
             if self.needs_update {
                 // Clear any dependencies so that when we rebuild them we
                 // can compare if the tile has the same content.
@@ -865,57 +925,45 @@ impl TileCache {
                 // area, just ignore those.
                 if x < 0 || x >= self.tile_count.width || y < 0 || y >= self.tile_count.height {
                     continue;
                 }
 
                 let index = (y * self.tile_count.width + x) as usize;
                 let tile = &mut self.tiles[index];
 
-                // TODO(gw): For now, we need to always build the dependencies each
-                //           frame, so can't early exit here. In future, we should
-                //           support retaining the tile descriptor from when the
-                //           tile goes off-screen, which will mean we can then
-                //           compare against that next time it becomes visible.
-                let visible_rect = match tile.visible_rect {
-                    Some(visible_rect) => visible_rect,
-                    None => WorldRect::zero(),
-                };
-
                 // Work out the needed rect for the primitive on this tile.
                 // TODO(gw): We should be able to remove this for any tile that is not
                 //           a partially clipped tile, which would be a significant
                 //           optimization for the common case (non-clipped tiles).
 
                 // Get the required tile-local rect that this primitive occupies.
                 // Ensure that even if it's currently clipped out of this tile,
                 // we still insert a rect of zero size, so that the tile descriptor's
                 // needed rects array matches.
-                let needed_rect = world_clip_rect
-                    .intersection(&visible_rect)
-                    .map(|rect| {
-                        rect.translate(&-tile.world_rect.origin.to_vector())
-                    })
-                    .unwrap_or(WorldRect::zero());
+                let prim_region = world_clip_rect.translate(&-world_rect.origin.to_vector());
 
-                tile.descriptor.needed_rects.push(needed_rect);
+                tile.descriptor.needed_regions.push(PrimitiveRegion {
+                    prim_region,
+                    tile_offset: world_rect.origin - tile.world_rect.origin.to_vector(),
+                });
 
                 // Mark if the tile is cacheable at all.
-                tile.is_valid &= is_cacheable;
+                tile.is_same_content &= is_cacheable;
 
                 // Include any image keys this tile depends on.
                 tile.descriptor.image_keys.extend_from_slice(&image_keys);
 
                 // // Include any opacity bindings this primitive depends on.
                 tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
 
                 // Update the tile descriptor, used for tile comparison during scene swaps.
                 tile.descriptor.prims.push(PrimitiveDescriptor {
                     prim_uid: prim_instance.uid(),
-                    origin: prim_instance.prim_origin,
+                    origin: world_rect.origin - tile.world_rect.origin.to_vector(),
                     first_clip: tile.descriptor.clip_uids.len() as u16,
                     clip_count: clip_chain_uids.len() as u16,
                 });
                 tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
                 tile.descriptor.clip_vertices.extend_from_slice(&clip_vertices);
 
                 tile.transforms.insert(prim_instance.spatial_node_index);
                 for spatial_node_index in &clip_spatial_nodes {
@@ -993,66 +1041,76 @@ impl TileCache {
             }
 
             let visible_rect = match tile.visible_rect {
                 Some(rect) => rect,
                 None => continue,
             };
 
             // Check the content of the tile is the same
-            tile.is_valid &= tile.descriptor.is_valid();
+            let tile_bounding_rect = match visible_rect.intersection(&self.world_bounding_rect) {
+                Some(rect) => rect.translate(&-tile.world_rect.origin.to_vector()),
+                None => continue,
+            };
+
+            tile.update_validity(&tile_bounding_rect);
+
+            // If there are no primitives there is no need to draw or cache it.
+            if tile.descriptor.prims.is_empty() {
+                continue;
+            }
 
             // Decide how to handle this tile when drawing this frame.
             if tile.is_valid {
-                // If the tile is valid, we will generally want to draw it
-                // on screen. However, if there are no primitives there is
-                // no need to draw it.
-                if !tile.descriptor.prims.is_empty() {
-                    self.tiles_to_draw.push(TileIndex(i));
-                }
+                self.tiles_to_draw.push(TileIndex(i));
             } else {
                 // Add the tile rect to the dirty rect.
                 dirty_world_rect = dirty_world_rect.union(&visible_rect);
 
-                // Ensure that this texture is allocated.
-                resource_cache.texture_cache.update(
-                    &mut tile.handle,
-                    descriptor,
-                    TextureFilter::Linear,
-                    None,
-                    [0.0; 3],
-                    DirtyRect::All,
-                    gpu_cache,
-                    None,
-                    UvRectKind::Rect,
-                    Eviction::Eager,
-                );
+                // Only cache tiles that have had the same content for at least two
+                // frames. This skips caching on pages / benchmarks that are changing
+                // every frame, which is wasteful.
+                if tile.same_frames > FRAMES_BEFORE_CACHING {
+                    // Ensure that this texture is allocated.
+                    resource_cache.texture_cache.update(
+                        &mut tile.handle,
+                        descriptor,
+                        TextureFilter::Linear,
+                        None,
+                        [0.0; 3],
+                        DirtyRect::All,
+                        gpu_cache,
+                        None,
+                        UvRectKind::Rect,
+                        Eviction::Eager,
+                    );
 
-                let cache_item = resource_cache
-                    .get_texture_cache_item(&tile.handle);
+                    let cache_item = resource_cache
+                        .get_texture_cache_item(&tile.handle);
 
-                let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
-                tile.valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
+                    let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
+                    let valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
 
-                // Store a blit operation to be done after drawing the
-                // frame in order to update the cached texture tile.
-                let dest_rect = (tile.valid_rect * frame_context.device_pixel_scale).round().to_i32();
-                self.pending_blits.push(TileBlit {
-                    target: cache_item,
-                    src_offset: src_origin,
-                    dest_offset: dest_rect.origin,
-                    size: dest_rect.size,
-                });
+                    // Store a blit operation to be done after drawing the
+                    // frame in order to update the cached texture tile.
+                    let dest_rect = (valid_rect * frame_context.device_pixel_scale).round().to_i32();
+                    self.pending_blits.push(TileBlit {
+                        target: cache_item,
+                        src_offset: src_origin,
+                        dest_offset: dest_rect.origin,
+                        size: dest_rect.size,
+                    });
 
-                // We can consider this tile valid now.
-                tile.is_valid = true;
-                tile.descriptor.current_rects = mem::replace(
-                    &mut tile.descriptor.needed_rects,
-                    Vec::new(),
-                );
+                    // We can consider this tile valid now.
+                    tile.is_valid = true;
+                    tile.descriptor.current_regions = mem::replace(
+                        &mut tile.descriptor.needed_regions,
+                        Vec::new(),
+                    );
+                }
             }
         }
 
         // Store the dirty region for drawing the main scene.
         self.dirty_region = if dirty_world_rect.is_empty() {
             None
         } else {
             let dirty_device_rect = (dirty_world_rect * frame_context.device_pixel_scale).round().to_i32();
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1584,18 +1584,18 @@ impl ToDebugString for SpecificDisplayIt
             SpecificDisplayItem::ClipChain(..) => String::from("clip_chain"),
             SpecificDisplayItem::Gradient(..) => String::from("gradient"),
             SpecificDisplayItem::Iframe(..) => String::from("iframe"),
             SpecificDisplayItem::Image(..) => String::from("image"),
             SpecificDisplayItem::Line(..) => String::from("line"),
             SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
             SpecificDisplayItem::PopReferenceFrame => String::from("pop_reference_frame"),
             SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
+            SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
-            SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
             SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
             SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
             SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
             SpecificDisplayItem::ScrollFrame(..) => String::from("scroll_frame"),
             SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
             SpecificDisplayItem::StickyFrame(..) => String::from("sticky_frame"),
             SpecificDisplayItem::Text(..) => String::from("text"),
             SpecificDisplayItem::YuvImage(..) => String::from("yuv_image"),
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -15,69 +15,43 @@ use image::ColorDepth;
 // Taken from nsCSSRendering.cpp in Gecko.
 pub const MAX_BLUR_RADIUS: f32 = 300.;
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub struct ClipAndScrollInfo {
-    pub scroll_node_id: ClipId,
-    pub clip_node_id: Option<ClipId>,
-}
-
-impl ClipAndScrollInfo {
-    pub fn simple(node_id: ClipId) -> ClipAndScrollInfo {
-        ClipAndScrollInfo {
-            scroll_node_id: node_id,
-            clip_node_id: None,
-        }
-    }
-
-    pub fn new(scroll_node_id: ClipId, clip_node_id: ClipId) -> ClipAndScrollInfo {
-        ClipAndScrollInfo {
-            scroll_node_id,
-            clip_node_id: Some(clip_node_id),
-        }
-    }
-
-    pub fn clip_node_id(&self) -> ClipId {
-        self.clip_node_id.unwrap_or(self.scroll_node_id)
-    }
-}
-
 /// A tag that can be used to identify items during hit testing. If the tag
 /// is missing then the item doesn't take part in hit testing at all. This
 /// is composed of two numbers. In Servo, the first is an identifier while the
 /// second is used to select the cursor that should be used during mouse
 /// movement. In Gecko, the first is a scrollframe identifier, while the second
 /// is used to store various flags that APZ needs to properly process input
 /// events.
 pub type ItemTag = (u64, u16);
 
 /// The DI is generic over the specifics, while allows to use
 /// the "complete" version of it for convenient serialization.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GenericDisplayItem<T> {
     pub item: T,
-    pub clip_and_scroll: ClipAndScrollInfo,
     pub layout: LayoutPrimitiveInfo,
+    pub space_and_clip: SpaceAndClipInfo,
 }
 
 pub type DisplayItem = GenericDisplayItem<SpecificDisplayItem>;
 
 /// A modified version of DI where every field is borrowed instead of owned.
 /// It allows us to reduce copies during serialization.
 #[derive(Serialize)]
 pub struct SerializedDisplayItem<'a> {
     pub item: &'a SpecificDisplayItem,
-    pub clip_and_scroll: &'a ClipAndScrollInfo,
     pub layout: &'a LayoutPrimitiveInfo,
+    pub space_and_clip: &'a SpaceAndClipInfo,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PrimitiveInfo<T> {
     pub rect: TypedRect<f32, T>,
     pub clip_rect: TypedRect<f32, T>,
     pub is_backface_visible: bool,
     pub tag: Option<ItemTag>,
@@ -98,16 +72,39 @@ impl LayoutPrimitiveInfo {
             is_backface_visible: true,
             tag: None,
         }
     }
 }
 
 pub type LayoutPrimitiveInfo = PrimitiveInfo<LayoutPixel>;
 
+/// Per-primitive information about the nodes in the clip tree and
+/// the spatial tree that the primitive belongs to.
+///
+/// Note: this is a separate struct from `PrimitiveInfo` because
+/// it needs indirectional mapping during the DL flattening phase,
+/// turning into `ScrollNodeAndClipChain`.
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct SpaceAndClipInfo {
+    pub spatial_id: SpatialId,
+    pub clip_id: ClipId,
+}
+
+impl SpaceAndClipInfo {
+    /// Create a new space/clip info associated with the root
+    /// scroll frame.
+    pub fn root_scroll(pipeline_id: PipelineId) -> Self {
+        SpaceAndClipInfo {
+            spatial_id: SpatialId::root_scroll_node(pipeline_id),
+            clip_id: ClipId::root(pipeline_id),
+        }
+    }
+}
+
 #[repr(u64)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
@@ -116,20 +113,20 @@ pub enum SpecificDisplayItem {
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     ClipChain(ClipChainItem),
     Iframe(IframeDisplayItem),
+    PushReferenceFrame(ReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
-    PushReferenceFrame(PushReferenceFrameDisplayListItem),
-    PopReferenceFrame,
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
     PushCacheMarker(CacheMarkerDisplayItem),
     PopCacheMarker,
 }
 
 /// This is a "complete" version of the DI specifics,
@@ -149,20 +146,20 @@ pub enum CompletelySpecificDisplayItem {
     Text(TextDisplayItem, Vec<GlyphInstance>),
     Image(ImageDisplayItem),
     YuvImage(YuvImageDisplayItem),
     Border(BorderDisplayItem),
     BoxShadow(BoxShadowDisplayItem),
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
+    PushReferenceFrame(ReferenceFrameDisplayListItem),
+    PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem, Vec<FilterOp>),
     PopStackingContext,
-    PushReferenceFrame(PushReferenceFrameDisplayListItem),
-    PopReferenceFrame,
     SetGradientStops(Vec<GradientStop>),
     PushShadow(Shadow),
     PopAllShadows,
     PushCacheMarker(CacheMarkerDisplayItem),
     PopCacheMarker,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -189,17 +186,17 @@ pub struct StickyOffsetBounds {
 impl StickyOffsetBounds {
     pub fn new(min: f32, max: f32) -> StickyOffsetBounds {
         StickyOffsetBounds { min, max }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StickyFrameDisplayItem {
-    pub id: ClipId,
+    pub id: SpatialId,
 
     /// The margins that should be maintained between the edge of the parent viewport and this
     /// sticky frame. A margin of None indicates that the sticky frame should not stick at all
     /// to that particular edge of the viewport.
     pub margins: SideOffsets2D<Option<f32>>,
 
     /// The minimum and maximum vertical offsets for this sticky frame. Ignoring these constraints,
     /// the sticky frame will continue to stick to the edge of the viewport as its original
@@ -225,17 +222,17 @@ pub struct StickyFrameDisplayItem {
 pub enum ScrollSensitivity {
     ScriptAndInputEvents,
     Script,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ScrollFrameDisplayItem {
     pub clip_id: ClipId,
-    pub scroll_frame_id: ClipId,
+    pub scroll_frame_id: SpatialId,
     pub external_id: Option<ExternalScrollId>,
     pub image_mask: Option<ImageMask>,
     pub scroll_sensitivity: ScrollSensitivity,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
@@ -512,44 +509,44 @@ pub struct ClipChainItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradientDisplayItem {
     pub gradient: RadialGradient,
     pub tile_size: LayoutSize,
     pub tile_spacing: LayoutSize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct PushReferenceFrameDisplayListItem {
+pub struct ReferenceFrameDisplayListItem {
     pub reference_frame: ReferenceFrame,
 }
 
 /// Provides a hint to WR that it should try to cache the items
 /// within a cache marker context in an off-screen surface.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct CacheMarkerDisplayItem {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ReferenceFrame {
     pub transform_style: TransformStyle,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub perspective: Option<LayoutTransform>,
-    pub id: ClipId,
+    pub id: SpatialId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
     pub stacking_context: StackingContext,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
-    pub clip_node_id: Option<ClipId>,
+    pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
 } // IMPLICIT: filters: Vec<FilterOp>
 
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
     Flat = 0,
@@ -642,17 +639,16 @@ impl FilterOp {
             }
             filter => filter,
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
-    pub clip_id: ClipId,
     pub pipeline_id: PipelineId,
     pub ignore_missing_pipeline: bool,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {
     pub image_key: ImageKey,
     pub stretch_size: LayoutSize,
@@ -852,58 +848,92 @@ impl ComplexClipRegion {
             }
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipChainId(pub u64, pub PipelineId);
 
+/// A reference to a clipping node defining how an item is clipped.
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
-    Spatial(usize, PipelineId),
     Clip(usize, PipelineId),
     ClipChain(ClipChainId),
 }
 
-const ROOT_REFERENCE_FRAME_CLIP_ID: usize = 0;
-const ROOT_SCROLL_NODE_CLIP_ID: usize = 1;
+const ROOT_CLIP_ID: usize = 0;
 
 impl ClipId {
-    pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, pipeline_id)
+    /// Return the root clip ID - effectively doing no clipping.
+    pub fn root(pipeline_id: PipelineId) -> Self {
+        ClipId::Clip(ROOT_CLIP_ID, pipeline_id)
     }
 
-    pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
-        ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, pipeline_id)
+    /// Return an invalid clip ID - needed in places where we carry
+    /// one but need to not attempt to use it.
+    pub fn invalid() -> Self {
+        ClipId::Clip(!0, PipelineId::dummy())
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
-            ClipId::Spatial(_, pipeline_id) |
             ClipId::Clip(_, pipeline_id) |
             ClipId::ClipChain(ClipChainId(_, pipeline_id)) => pipeline_id,
         }
     }
 
-    pub fn is_root_scroll_node(&self) -> bool {
+    pub fn is_root(&self) -> bool {
         match *self {
-            ClipId::Spatial(ROOT_SCROLL_NODE_CLIP_ID, _) => true,
-            _ => false,
+            ClipId::Clip(id, _) => id == ROOT_CLIP_ID,
+            ClipId::ClipChain(_) => false,
         }
     }
 
-    pub fn is_root_reference_frame(&self) -> bool {
+    pub fn is_valid(&self) -> bool {
         match *self {
-            ClipId::Spatial(ROOT_REFERENCE_FRAME_CLIP_ID, _) => true,
-            _ => false,
+            ClipId::Clip(id, _) => id != !0,
+            _ => true,
         }
     }
 }
 
+/// A reference to a spatial node defining item positioning.
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub struct SpatialId(pub usize, PipelineId);
+
+const ROOT_REFERENCE_FRAME_SPATIAL_ID: usize = 0;
+const ROOT_SCROLL_NODE_SPATIAL_ID: usize = 1;
+
+impl SpatialId {
+    pub fn new(spatial_node_index: usize, pipeline_id: PipelineId) -> Self {
+        SpatialId(spatial_node_index, pipeline_id)
+    }
+
+    pub fn root_reference_frame(pipeline_id: PipelineId) -> Self {
+        SpatialId(ROOT_REFERENCE_FRAME_SPATIAL_ID, pipeline_id)
+    }
+
+    pub fn root_scroll_node(pipeline_id: PipelineId) -> Self {
+        SpatialId(ROOT_SCROLL_NODE_SPATIAL_ID, pipeline_id)
+    }
+
+    pub fn pipeline_id(&self) -> PipelineId {
+        self.1
+    }
+
+    pub fn is_root_reference_frame(&self) -> bool {
+        self.0 == ROOT_REFERENCE_FRAME_SPATIAL_ID
+    }
+
+    pub fn is_root_scroll_node(&self) -> bool {
+        self.0 == ROOT_SCROLL_NODE_SPATIAL_ID
+    }
+}
+
 /// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which
 /// may change from frame to frame. This should be unique within a pipeline. WebRender makes no
 /// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of
 /// every pipeline, which always has an external id.
 ///
 /// When setting display lists with the `preserve_frame_state` this id is used to preserve scroll
 /// offsets between different sets of ClipScrollNodes which are ScrollFrames.
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -10,40 +10,40 @@ use serde::de::Deserializer;
 use serde::ser::{Serializer, SerializeSeq};
 use serde::{Deserialize, Serialize};
 use std::io::{Read, stdout, Write};
 use std::marker::PhantomData;
 use std::ops::Range;
 use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
 use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, CacheMarkerDisplayItem};
-use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
+use {BoxShadowDisplayItem, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
 use {FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, Gradient, GradientBuilder};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
 use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets, LayoutSize};
 use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode};
-use {PipelineId, PropertyBinding, PushReferenceFrameDisplayListItem};
+use {PipelineId, PropertyBinding, ReferenceFrameDisplayListItem};
 use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
 use {RectangleDisplayItem, ReferenceFrame, ScrollFrameDisplayItem, ScrollSensitivity};
-use {SerializedDisplayItem, Shadow, SpecificDisplayItem};
+use {SerializedDisplayItem, Shadow, SpaceAndClipInfo, SpatialId, SpecificDisplayItem};
 use {StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
 use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem, ColorDepth};
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2
 pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
 
-// We start at 2, because the root reference is always 0 and the root scroll node is always 1.
+// See ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
 // TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only
 // used by Servo.
 const FIRST_SPATIAL_NODE_INDEX: usize = 2;
 
-// There are no default clips, so we start at the 0 index for clips.
-const FIRST_CLIP_NODE_INDEX: usize = 0;
+// See ROOT_SCROLL_NODE_SPATIAL_ID
+const FIRST_CLIP_NODE_INDEX: usize = 1;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
 }
@@ -208,19 +208,18 @@ impl<'a> BuiltDisplayListIter<'a> {
 
     pub fn new_with_list_and_data(list: &'a BuiltDisplayList, data: &'a [u8]) -> Self {
         BuiltDisplayListIter {
             list,
             data,
             cur_item: DisplayItem {
                 // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
-                clip_and_scroll:
-                    ClipAndScrollInfo::simple(ClipId::root_scroll_node(PipelineId::dummy())),
                 layout: LayoutPrimitiveInfo::new(LayoutRect::zero()),
+                space_and_clip: SpaceAndClipInfo::root_scroll(PipelineId::dummy())
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
         }
@@ -365,18 +364,18 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
             tag: layout.tag,
         }
     }
 
     pub fn clip_rect(&self) -> &LayoutRect {
         &self.iter.cur_item.layout.clip_rect
     }
 
-    pub fn clip_and_scroll(&self) -> ClipAndScrollInfo {
-        self.iter.cur_item.clip_and_scroll
+    pub fn space_and_clip_info(&self) -> &SpaceAndClipInfo {
+        &self.iter.cur_item.space_and_clip
     }
 
     pub fn item(&self) -> &SpecificDisplayItem {
         &self.iter.cur_item.item
     }
 
     pub fn complex_clip(&self) -> (ItemRange<ComplexClipRegion>, usize) {
         self.iter.cur_complex_clip
@@ -485,33 +484,33 @@ impl Serialize for BuiltDisplayList {
                     ),
                     SpecificDisplayItem::Image(v) => Image(v),
                     SpecificDisplayItem::YuvImage(v) => YuvImage(v),
                     SpecificDisplayItem::Border(v) => Border(v),
                     SpecificDisplayItem::BoxShadow(v) => BoxShadow(v),
                     SpecificDisplayItem::Gradient(v) => Gradient(v),
                     SpecificDisplayItem::RadialGradient(v) => RadialGradient(v),
                     SpecificDisplayItem::Iframe(v) => Iframe(v),
+                    SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
+                    SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
                     SpecificDisplayItem::PushStackingContext(v) => PushStackingContext(
                         v,
                         item.iter.list.get(item.iter.cur_filters).collect()
                     ),
                     SpecificDisplayItem::PopStackingContext => PopStackingContext,
-                    SpecificDisplayItem::PushReferenceFrame(v) => PushReferenceFrame(v),
-                    SpecificDisplayItem::PopReferenceFrame => PopReferenceFrame,
                     SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
                     SpecificDisplayItem::PushShadow(v) => PushShadow(v),
                     SpecificDisplayItem::PopAllShadows => PopAllShadows,
                     SpecificDisplayItem::PushCacheMarker(m) => PushCacheMarker(m),
                     SpecificDisplayItem::PopCacheMarker => PopCacheMarker,
                 },
-                clip_and_scroll: display_item.clip_and_scroll,
                 layout: display_item.layout,
+                space_and_clip: display_item.space_and_clip,
             };
             seq.serialize_element(&serial_di)?
         }
         seq.end()
     }
 }
 
 // The purpose of this implementation is to deserialize
@@ -546,17 +545,17 @@ impl<'de> Deserialize<'de> for BuiltDisp
                         DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids);
                         SpecificDisplayItem::ClipChain(specific_item)
                     }
                     ScrollFrame(specific_item, complex_clips) => {
                         total_spatial_nodes += 1;
                         total_clip_nodes += 1;
                         DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
                         SpecificDisplayItem::ScrollFrame(specific_item)
-                    },
+                    }
                     StickyFrame(specific_item) => {
                         total_spatial_nodes += 1;
                         SpecificDisplayItem::StickyFrame(specific_item)
                     }
                     Rectangle(specific_item) => SpecificDisplayItem::Rectangle(specific_item),
                     ClearRectangle => SpecificDisplayItem::ClearRectangle,
                     Line(specific_item) => SpecificDisplayItem::Line(specific_item),
                     Text(specific_item, glyphs) => {
@@ -569,37 +568,37 @@ impl<'de> Deserialize<'de> for BuiltDisp
                     BoxShadow(specific_item) => SpecificDisplayItem::BoxShadow(specific_item),
                     Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
                     RadialGradient(specific_item) =>
                         SpecificDisplayItem::RadialGradient(specific_item),
                     Iframe(specific_item) => {
                         total_clip_nodes += 1;
                         SpecificDisplayItem::Iframe(specific_item)
                     }
+                    PushReferenceFrame(v) => {
+                        total_spatial_nodes += 1;
+                        SpecificDisplayItem::PushReferenceFrame(v)
+                    }
+                    PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     PushStackingContext(specific_item, filters) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, filters);
                         SpecificDisplayItem::PushStackingContext(specific_item)
                     },
                     PopStackingContext => SpecificDisplayItem::PopStackingContext,
-                    PushReferenceFrame(specific_item) => {
-                        total_spatial_nodes += 1;
-                        SpecificDisplayItem::PushReferenceFrame(specific_item)
-                    }
-                    PopReferenceFrame => SpecificDisplayItem::PopReferenceFrame,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => SpecificDisplayItem::PopAllShadows,
                     PushCacheMarker(marker) => SpecificDisplayItem::PushCacheMarker(marker),
                     PopCacheMarker => SpecificDisplayItem::PopCacheMarker,
                 },
-                clip_and_scroll: complete.clip_and_scroll,
                 layout: complete.layout,
+                space_and_clip: complete.space_and_clip,
             };
             serialize_fast(&mut data, &item);
             // the aux data is serialized after the item, hence the temporary
             data.extend(temp.drain(..));
         }
 
         Ok(BuiltDisplayList {
             data,
@@ -827,27 +826,25 @@ impl<'a, 'b> Read for UnsafeReader<'a, '
         self.read_internal(buf);
         Ok(())
     }
 }
 
 #[derive(Clone, Debug)]
 pub struct SaveState {
     dl_len: usize,
-    clip_stack_len: usize,
     next_clip_index: usize,
     next_spatial_index: usize,
     next_clip_chain_id: u64,
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
-    clip_stack: Vec<ClipAndScrollInfo>,
     next_clip_index: usize,
     next_spatial_index: usize,
     next_clip_chain_id: u64,
     builder_start_time: u64,
 
     /// The size of the content of this display list. This is used to allow scrolling
     /// outside the bounds of the display list items themselves.
     content_size: LayoutSize,
@@ -864,19 +861,16 @@ impl DisplayListBuilder {
         content_size: LayoutSize,
         capacity: usize,
     ) -> Self {
         let start_time = precise_time_ns();
 
         DisplayListBuilder {
             data: Vec::with_capacity(capacity),
             pipeline_id,
-            clip_stack: vec![
-                ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id)),
-            ],
             next_clip_index: FIRST_CLIP_NODE_INDEX,
             next_spatial_index: FIRST_SPATIAL_NODE_INDEX,
             next_clip_chain_id: 0,
             builder_start_time: start_time,
             content_size,
             save_state: None,
         }
     }
@@ -892,29 +886,27 @@ impl DisplayListBuilder {
     ///
     /// * Doesn't support popping clips that were pushed before the save.
     /// * Doesn't support nested saves.
     /// * Must call `clear_save()` if the restore becomes unnecessary.
     pub fn save(&mut self) {
         assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves");
 
         self.save_state = Some(SaveState {
-            clip_stack_len: self.clip_stack.len(),
             dl_len: self.data.len(),
             next_clip_index: self.next_clip_index,
             next_spatial_index: self.next_spatial_index,
             next_clip_chain_id: self.next_clip_chain_id,
         });
     }
 
     /// Restores the state of the builder to when `save()` was last called.
     pub fn restore(&mut self) {
         let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
 
-        self.clip_stack.truncate(state.clip_stack_len);
         self.data.truncate(state.dl_len);
         self.next_clip_index = state.next_clip_index;
         self.next_spatial_index = state.next_spatial_index;
         self.next_clip_chain_id = state.next_clip_chain_id;
     }
 
     /// Discards the builder's save (indicating the attempted operation was successful).
     pub fn clear_save(&mut self) {
@@ -964,46 +956,41 @@ impl DisplayListBuilder {
         index
     }
 
     /// Add an item to the display list.
     ///
     /// NOTE: It is usually preferable to use the specialized methods to push
     /// display items. Pushing unexpected or invalid items here may
     /// result in WebRender panicking or behaving in unexpected ways.
-    pub fn push_item(&mut self, item: &SpecificDisplayItem, layout: &LayoutPrimitiveInfo) {
-        serialize_fast(
-            &mut self.data,
-            SerializedDisplayItem {
-                item,
-                clip_and_scroll: self.clip_stack.last().unwrap(),
-                layout,
-            },
-        )
-    }
-
-    fn push_item_with_clip_scroll_info(
+    #[inline]
+    pub fn push_item(
         &mut self,
         item: &SpecificDisplayItem,
         layout: &LayoutPrimitiveInfo,
-        clip_and_scroll: &ClipAndScrollInfo
+        space_and_clip: &SpaceAndClipInfo,
     ) {
         serialize_fast(
             &mut self.data,
             SerializedDisplayItem {
                 item,
-                clip_and_scroll,
                 layout,
+                space_and_clip,
             },
         )
     }
 
+    #[inline]
     fn push_new_empty_item(&mut self, item: &SpecificDisplayItem) {
-        let layout = &LayoutPrimitiveInfo::new(LayoutRect::zero());
-        self.push_item(item, layout)
+        let pipeline_id = self.pipeline_id;
+        self.push_item(
+            item,
+            &LayoutPrimitiveInfo::new(LayoutRect::zero()),
+            &SpaceAndClipInfo::root_scroll(pipeline_id),
+        )
     }
 
     fn push_iter_impl<I>(data: &mut Vec<u8>, iter_source: I)
     where
         I: IntoIterator,
         I::IntoIter: ExactSizeIterator + Clone,
         I::Item: Serialize,
     {
@@ -1041,99 +1028,112 @@ impl DisplayListBuilder {
     where
         I: IntoIterator,
         I::IntoIter: ExactSizeIterator + Clone,
         I::Item: Serialize,
     {
         Self::push_iter_impl(&mut self.data, iter);
     }
 
-    pub fn push_rect(&mut self, layout: &LayoutPrimitiveInfo, color: ColorF) {
+    pub fn push_rect(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+        color: ColorF,
+    ) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem { color });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
-    pub fn push_clear_rect(&mut self, layout: &LayoutPrimitiveInfo) {
-        self.push_item(&SpecificDisplayItem::ClearRectangle, layout);
+    pub fn push_clear_rect(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+    ) {
+        self.push_item(&SpecificDisplayItem::ClearRectangle, layout, space_and_clip);
     }
 
     pub fn push_line(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         color: &ColorF,
         style: LineStyle,
     ) {
         let item = SpecificDisplayItem::Line(LineDisplayItem {
             wavy_line_thickness,
             orientation,
             color: *color,
             style,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_image(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         image_rendering: ImageRendering,
         alpha_type: AlphaType,
         key: ImageKey,
         color: ColorF,
     ) {
         let item = SpecificDisplayItem::Image(ImageDisplayItem {
             image_key: key,
             stretch_size,
             tile_spacing,
             image_rendering,
             alpha_type,
             color,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Push a yuv image. All planar data in yuv image should use the same buffer type.
     pub fn push_yuv_image(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         yuv_data: YuvData,
         color_depth: ColorDepth,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     ) {
         let item = SpecificDisplayItem::YuvImage(YuvImageDisplayItem {
             yuv_data,
             color_depth,
             color_space,
             image_rendering,
         });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_text(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         glyphs: &[GlyphInstance],
         font_key: FontInstanceKey,
         color: ColorF,
         glyph_options: Option<GlyphOptions>,
     ) {
         let item = SpecificDisplayItem::Text(TextDisplayItem {
             color,
             font_key,
             glyph_options,
         });
 
         for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
-            self.push_item(&item, layout);
+            self.push_item(&item, layout, space_and_clip);
             self.push_iter(split_glyphs);
         }
     }
 
     /// NOTE: gradients must be pushed in the order they're created
     /// because create_gradient stores the stops in anticipation.
     pub fn create_gradient(
         &mut self,
@@ -1161,27 +1161,29 @@ impl DisplayListBuilder {
         let gradient = builder.radial_gradient(center, radius, extend_mode);
         self.push_stops(builder.stops());
         gradient
     }
 
     pub fn push_border(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         widths: LayoutSideOffsets,
         details: BorderDetails,
     ) {
         let item = SpecificDisplayItem::Border(BorderDisplayItem { details, widths });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_box_shadow(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         box_bounds: LayoutRect,
         offset: LayoutVector2D,
         color: ColorF,
         blur_radius: f32,
         spread_radius: f32,
         border_radius: BorderRadius,
         clip_mode: BoxShadowClipMode,
     ) {
@@ -1190,17 +1192,17 @@ impl DisplayListBuilder {
             offset,
             color,
             blur_radius,
             spread_radius,
             border_radius,
             clip_mode,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Pushes a linear gradient to be displayed.
     ///
     /// The gradient itself is described in the
     /// `gradient` parameter. It is drawn on
     /// a "tile" with the dimensions from `tile_size`.
     /// These tiles are now repeated to the right and
@@ -1210,65 +1212,74 @@ impl DisplayListBuilder {
     ///
     /// The origin of the tiles is given in `layout.rect.origin`.
     /// If the gradient should only be displayed once limit
     /// the `layout.rect.size` to a single tile.
     /// The gradient is only visible within the local clip.
     pub fn push_gradient(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         gradient: Gradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
     ) {
         let item = SpecificDisplayItem::Gradient(GradientDisplayItem {
             gradient,
             tile_size,
             tile_spacing,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     /// Pushes a radial gradient to be displayed.
     ///
     /// See [`push_gradient`](#method.push_gradient) for explanation.
     pub fn push_radial_gradient(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         gradient: RadialGradient,
         tile_size: LayoutSize,
         tile_spacing: LayoutSize,
     ) {
         let item = SpecificDisplayItem::RadialGradient(RadialGradientDisplayItem {
             gradient,
             tile_size,
             tile_spacing,
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
     pub fn push_reference_frame(
         &mut self,
         rect: &LayoutRect,
+        parent: SpatialId,
         transform_style: TransformStyle,
         transform: Option<PropertyBinding<LayoutTransform>>,
         perspective: Option<LayoutTransform>,
-    ) -> ClipId {
+    ) -> SpatialId {
         let id = self.generate_spatial_index();
-        let item = SpecificDisplayItem::PushReferenceFrame(PushReferenceFrameDisplayListItem {
+
+        let item = SpecificDisplayItem::PushReferenceFrame(ReferenceFrameDisplayListItem {
             reference_frame: ReferenceFrame {
                 transform_style,
                 transform,
                 perspective,
                 id,
             },
         });
-        self.push_item(&item, &LayoutPrimitiveInfo::new(*rect));
+
+        let layout = LayoutPrimitiveInfo::new(*rect);
+        self.push_item(&item, &layout, &SpaceAndClipInfo {
+            spatial_id: parent,
+            clip_id: ClipId::invalid(),
+        });
         id
     }
 
     pub fn push_cache_marker(&mut self) {
         self.push_new_empty_item(&SpecificDisplayItem::PushCacheMarker(CacheMarkerDisplayItem {
             // The display item itself is empty for now while we experiment with
             // the API. In future it may contain extra information, such as details
             // on whether the surface is known to be opaque and/or a background color
@@ -1282,32 +1293,36 @@ impl DisplayListBuilder {
 
     pub fn pop_reference_frame(&mut self) {
         self.push_new_empty_item(&SpecificDisplayItem::PopReferenceFrame);
     }
 
     pub fn push_stacking_context(
         &mut self,
         layout: &LayoutPrimitiveInfo,
-        clip_node_id: Option<ClipId>,
+        spatial_id: SpatialId,
+        clip_id: Option<ClipId>,
         transform_style: TransformStyle,
         mix_blend_mode: MixBlendMode,
         filters: &[FilterOp],
         raster_space: RasterSpace,
     ) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 transform_style,
                 mix_blend_mode,
-                clip_node_id,
+                clip_id,
                 raster_space,
             },
         });
 
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, &SpaceAndClipInfo {
+            spatial_id,
+            clip_id: ClipId::invalid(),
+        });
         self.push_iter(filters);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(&SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn push_stops(&mut self, stops: &[GradientStop]) {
@@ -1318,82 +1333,61 @@ impl DisplayListBuilder {
         self.push_iter(stops);
     }
 
     fn generate_clip_index(&mut self) -> ClipId {
         self.next_clip_index += 1;
         ClipId::Clip(self.next_clip_index - 1, self.pipeline_id)
     }
 
-    fn generate_spatial_index(&mut self) -> ClipId {
+    fn generate_spatial_index(&mut self) -> SpatialId {
         self.next_spatial_index += 1;
-        ClipId::Spatial(self.next_spatial_index - 1, self.pipeline_id)
+        SpatialId::new(self.next_spatial_index - 1, self.pipeline_id)
     }
 
     fn generate_clip_chain_id(&mut self) -> ClipChainId {
         self.next_clip_chain_id += 1;
         ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id)
     }
 
     pub fn define_scroll_frame<I>(
         &mut self,
+        parent_space_and_clip: &SpaceAndClipInfo,
         external_id: Option<ExternalScrollId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
         scroll_sensitivity: ScrollSensitivity,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        let parent = self.clip_stack.last().unwrap().scroll_node_id;
-        self.define_scroll_frame_with_parent(
-            parent,
-            external_id,
-            content_rect,
-            clip_rect,
-            complex_clips,
-            image_mask,
-            scroll_sensitivity)
-    }
-
-    pub fn define_scroll_frame_with_parent<I>(
-        &mut self,
-        parent: ClipId,
-        external_id: Option<ExternalScrollId>,
-        content_rect: LayoutRect,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-        scroll_sensitivity: ScrollSensitivity,
-    ) -> ClipId
+    ) -> SpaceAndClipInfo
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let clip_id = self.generate_clip_index();
         let scroll_frame_id = self.generate_spatial_index();
         let item = SpecificDisplayItem::ScrollFrame(ScrollFrameDisplayItem {
             clip_id,
             scroll_frame_id,
             external_id,
             image_mask,
             scroll_sensitivity,
         });
 
-        self.push_item_with_clip_scroll_info(
+        self.push_item(
             &item,
             &LayoutPrimitiveInfo::with_clip_rect(content_rect, clip_rect),
-            &ClipAndScrollInfo::simple(parent),
+            parent_space_and_clip,
         );
         self.push_iter(complex_clips);
 
-        scroll_frame_id
+        SpaceAndClipInfo {
+            spatial_id: scroll_frame_id,
+            clip_id,
+        }
     }
 
     pub fn define_clip_chain<I>(
         &mut self,
         parent: Option<ClipChainId>,
         clips: I,
     ) -> ClipChainId
     where
@@ -1403,141 +1397,101 @@ impl DisplayListBuilder {
         let id = self.generate_clip_chain_id();
         self.push_new_empty_item(&SpecificDisplayItem::ClipChain(ClipChainItem { id, parent }));
         self.push_iter(clips);
         id
     }
 
     pub fn define_clip<I>(
         &mut self,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        let clip_and_scroll = self.clip_stack.last().unwrap().clone();
-        self.define_clip_impl(
-            clip_and_scroll,
-            clip_rect,
-            complex_clips,
-            image_mask,
-        )
-    }
-
-    pub fn define_clip_with_parent<I>(
-        &mut self,
-        parent: ClipId,
-        clip_rect: LayoutRect,
-        complex_clips: I,
-        image_mask: Option<ImageMask>,
-    ) -> ClipId
-    where
-        I: IntoIterator<Item = ComplexClipRegion>,
-        I::IntoIter: ExactSizeIterator + Clone,
-    {
-        self.define_clip_impl(
-            ClipAndScrollInfo::simple(parent),
-            clip_rect,
-            complex_clips,
-            image_mask,
-        )
-    }
-
-    fn define_clip_impl<I>(
-        &mut self,
-        scrollinfo: ClipAndScrollInfo,
+        parent_space_and_clip: &SpaceAndClipInfo,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<ImageMask>,
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let id = self.generate_clip_index();
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id,
             image_mask,
         });
 
-        let layout = LayoutPrimitiveInfo::new(clip_rect);
-
-        self.push_item_with_clip_scroll_info(&item, &layout, &scrollinfo);
+        self.push_item(
+            &item,
+            &LayoutPrimitiveInfo::new(clip_rect),
+            parent_space_and_clip,
+        );
         self.push_iter(complex_clips);
         id
     }
 
     pub fn define_sticky_frame(
         &mut self,
+        parent_spatial_id: SpatialId,
         frame_rect: LayoutRect,
         margins: SideOffsets2D<Option<f32>>,
         vertical_offset_bounds: StickyOffsetBounds,
         horizontal_offset_bounds: StickyOffsetBounds,
         previously_applied_offset: LayoutVector2D,
-    ) -> ClipId {
+    ) -> SpatialId {
         let id = self.generate_spatial_index();
         let item = SpecificDisplayItem::StickyFrame(StickyFrameDisplayItem {
             id,
             margins,
             vertical_offset_bounds,
             horizontal_offset_bounds,
             previously_applied_offset,
         });
 
-        let layout = LayoutPrimitiveInfo::new(frame_rect);
-        self.push_item(&item, &layout);
+        self.push_item(
+            &item,
+            &LayoutPrimitiveInfo::new(frame_rect),
+            &SpaceAndClipInfo {
+                spatial_id: parent_spatial_id,
+                clip_id: ClipId::invalid(),
+            },
+        );
         id
     }
 
-    pub fn push_clip_id(&mut self, id: ClipId) {
-        self.clip_stack.push(ClipAndScrollInfo::simple(id));
-    }
-
-    pub fn push_clip_and_scroll_info(&mut self, layout: ClipAndScrollInfo) {
-        self.clip_stack.push(layout);
-    }
-
-    pub fn pop_clip_id(&mut self) {
-        self.clip_stack.pop();
-        if let Some(save_state) = self.save_state.as_ref() {
-            assert!(self.clip_stack.len() >= save_state.clip_stack_len,
-                    "Cannot pop clips that were pushed before the DisplayListBuilder save.");
-        }
-        assert!(!self.clip_stack.is_empty());
-    }
-
     pub fn push_iframe(
         &mut self,
         layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
         pipeline_id: PipelineId,
         ignore_missing_pipeline: bool
     ) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem {
-            clip_id: self.generate_clip_index(),
             pipeline_id,
             ignore_missing_pipeline,
         });
-        self.push_item(&item, layout);
+        self.push_item(&item, layout, space_and_clip);
     }
 
-    pub fn push_shadow(&mut self, layout: &LayoutPrimitiveInfo, shadow: Shadow) {
-        self.push_item(&SpecificDisplayItem::PushShadow(shadow), layout);
+    pub fn push_shadow(
+        &mut self,
+        layout: &LayoutPrimitiveInfo,
+        space_and_clip: &SpaceAndClipInfo,
+        shadow: Shadow,
+    ) {
+        self.push_item(&SpecificDisplayItem::PushShadow(shadow), layout, space_and_clip);
     }
 
     pub fn pop_all_shadows(&mut self) {
         self.push_new_empty_item(&SpecificDisplayItem::PopAllShadows);
     }
 
     pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
         assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save");
 
         let end_time = precise_time_ns();
+
         (
             self.pipeline_id,
             self.content_size,
             BuiltDisplayList {
                 descriptor: BuiltDisplayListDescriptor {
                     builder_start_time: self.builder_start_time,
                     builder_finish_time: end_time,
                     send_start_time: 0,
--- a/gfx/wr/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml
+++ b/gfx/wr/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml
@@ -2,27 +2,27 @@ root:
   items:
     - type: scroll-frame
       bounds: [10, 10, 100, 300]
       content-size: [100, 700]
       id: 41
       scroll-offset: [0, 50]
       items:
       # The rectangles below should stay in place even when the parent scroll area scrolls,
-      # because they are use the root reference frame as their scroll node (fixed position).
+      # because they use the root reference frame as their scroll node (fixed position).
       # On the other hand, the clip item here will scroll with its parent scroll area. Normally
       # fixed position items  would only be clipped by their reference frame (in this case the
-      # root), but ince  these items specify an auxiliary clip, they will be clipped by their
+      # root), but since these items specify an auxiliary clip, they will be clipped by their
       # sibling clip (42).
-      - type: clip
-        bounds: [10, 60, 50, 50]
-        id: 42
-      - type: stacking-context
-        bounds: [10, 10, 100, 100]
-        items:
-          - type: rect
-            bounds: [0, 0, 100, 50]
-            color: green
-            clip-and-scroll: [root-reference-frame, 42]
-          - type: rect
-            bounds: [0, 50, 100, 50]
-            color: red
-            clip-and-scroll: [root-reference-frame, 42]
+        - type: clip
+          bounds: [10, 60, 50, 50]
+          id: 42
+        - type: stacking-context
+          bounds: [10, 10, 100, 100]
+          items:
+            - type: rect
+              bounds: [0, 0, 100, 50]
+              color: green
+              clip-and-scroll: [root-reference-frame, 42]
+            - type: rect
+              bounds: [0, 50, 100, 50]
+              color: red
+              clip-and-scroll: [root-reference-frame, 42]
--- a/gfx/wr/wrench/reftests/scrolling/fixed-position.yaml
+++ b/gfx/wr/wrench/reftests/scrolling/fixed-position.yaml
@@ -1,55 +1,55 @@
 root:
   bounds: [0, 0, 1024, 10000]
   scroll-offset: [0, 100]
   items:
-    # This stacking context should not scroll out of view because it is fixed position.
     - type: stacking-context
       bounds: [0, 0, 50, 50]
       items:
+        # This item should not scroll out of view because it is fixed position.
         - type: rect
           bounds: [0, 0, 50, 50]
           color: green
           clip-and-scroll: root-reference-frame
-    # Even though there is a fixed position stacking context, it should scroll,
-    # because it is fixed relative to its reference frame. The reference frame
-    # of this stacking context is the stacking context parent because it has
-    # a transformation.
     - type: stacking-context
       bounds: [0, 0, 50, 50]
       transform: translate(60, 100)
       id: 100
       items:
         - type: stacking-context
           bounds: [0, 0, 50, 50]
           items:
+            # Even though there is a custom clip-scroll ID, it should scroll,
+            # because it is fixed relative to its reference frame. The reference frame
+            # of this stacking context is the stacking context parent because it has
+            # a transformation.
             - type: rect
               bounds: [0, 0, 50, 50]
               color: green
               clip-and-scroll: 100
-    # This is similar to the previous case, but ensures that this still works
-    # even with an identity transform.
     - type: stacking-context
       bounds: [120, 0, 50, 200]
       transform: translate(0, 0)
       id: 101
       items:
+        # This is similar to the previous case, but ensures that this still works
+        # even with an identity transform.
         - type: stacking-context
           bounds: [0, 0, 50, 200]
           items:
             - type: rect
               bounds: [0, 100, 50, 50]
               color: green
               clip-and-scroll: 101
-    # This is similar to the previous case, but for perspective.
     - type: stacking-context
       bounds: [180, 0, 50, 200]
       perspective: 1
       id: 102
       items:
+        # This is similar to the previous case, but for perspective.
         - type: stacking-context
           bounds: [0, 0, 50, 200]
           items:
             - type: rect
               bounds: [0, 100, 50, 50]
               color: green
               clip-and-scroll: 102
--- a/gfx/wr/wrench/reftests/scrolling/scroll-layer-with-mask.yaml
+++ b/gfx/wr/wrench/reftests/scrolling/scroll-layer-with-mask.yaml
@@ -1,34 +1,32 @@
 root:
   items:
     -
       type: stacking-context
       # We give this stacking context a little offset here to
       # ensure that offsets within reference frames are handled
       # properly when drawing the mask.
       bounds: [10, 10, 100, 100]
-      scroll-policy: scrollable
       items:
       - type: scroll-frame
         bounds: [0, 0, 100, 100]
         image-mask:
           image: "mask.png"
           rect: [0, 0, 100, 100]
           repeat: false
         items:
-          - type: rect
-            bounds: [0, 0, 100, 100]
-            color: green
+        - type: rect
+          bounds: [0, 0, 100, 100]
+          color: green
 
     # The same test, but this time ensure that scroll offsets don't affect the masking.
     -
       type: stacking-context
       bounds: [100, 0, 100, 100]
-      scroll-policy: scrollable
       items:
       - type: scroll-frame
         bounds: [10, 10, 100, 300]
         scroll-offset: [0, 100]
         image-mask:
           image: "mask.png"
           rect: [10, 10, 100, 100]
           repeat: false
--- a/gfx/wr/wrench/reftests/scrolling/translate-nested.yaml
+++ b/gfx/wr/wrench/reftests/scrolling/translate-nested.yaml
@@ -1,28 +1,28 @@
 ---
-root: 
-  items: 
-    - 
+root:
+  items:
+    -
       bounds: [8, 8, 500, 500]
       type: "stacking-context"
-      items: 
-        - 
+      items:
+        -
           bounds: [0, 0, 200, 200]
           type: clip
           id: 2
           items:
-          -
-            bounds: [0, 0, 200, 200]
-            type: rect
-            color: red
-          -
-            bounds: [0, 0, 200, 200]
-            type: "stacking-context"
-            transform: translate(100, 0)
-            items:
-              -
-                bounds: [-100, 0, 200, 200]
-                clip-rect: [-300, -300, 900, 900]
-                type: rect
-                color: green
+            -
+              bounds: [0, 0, 200, 200]
+              type: rect
+              color: red
+            -
+              bounds: [0, 0, 200, 200]
+              type: "stacking-context"
+              transform: translate(100, 0)
+              items:
+                -
+                  bounds: [-100, 0, 200, 200]
+                  clip-rect: [-300, -300, 900, 900]
+                  type: rect
+                  color: green
   id: [0, 0]
 pipelines: []
--- a/gfx/wr/wrench/script/headless.py
+++ b/gfx/wr/wrench/script/headless.py
@@ -44,37 +44,16 @@ def is_windows():
 def is_macos():
     return sys.platform == 'darwin'
 
 
 def is_linux():
     return sys.platform.startswith('linux')
 
 
-def debugger():
-    if "DEBUGGER" in os.environ:
-        return os.environ["DEBUGGER"]
-    return None
-
-
-def use_gdb():
-    return debugger() in ['gdb', 'cgdb', 'rust-gdb']
-
-
-def use_rr():
-    return debugger() == 'rr'
-
-
-def optimized_build():
-    if "OPTIMIZED" in os.environ:
-        opt = os.environ["OPTIMIZED"]
-        return opt not in ["0", "false"]
-    return True
-
-
 def set_osmesa_env(bin_path):
     """Set proper LD_LIBRARY_PATH and DRIVE for software rendering on Linux and OSX"""
     if is_linux():
         osmesa_path = path.join(find_dep_path_newest('osmesa-src', bin_path), "out", "lib", "gallium")
         print(osmesa_path)
         os.environ["LD_LIBRARY_PATH"] = osmesa_path
         os.environ["GALLIUM_DRIVER"] = "softpipe"
     elif is_macos():
@@ -83,39 +62,16 @@ def set_osmesa_env(bin_path):
         glapi_path = path.join(find_dep_path_newest('osmesa-src', bin_path),
                                "out", "src", "mapi", "shared-glapi", ".libs")
         os.environ["DYLD_LIBRARY_PATH"] = osmesa_path + ":" + glapi_path
         os.environ["GALLIUM_DRIVER"] = "softpipe"
 
 
 extra_flags = os.getenv('CARGOFLAGS', None)
 extra_flags = extra_flags.split(' ') if extra_flags else []
-build_cmd = ['cargo', 'build'] + extra_flags + ['--verbose', '--features', 'headless']
-if optimized_build():
-    build_cmd += ['--release']
-
-objdir = ''
-if optimized_build():
-    objdir = '../target/release/'
-else:
-    objdir = '../target/debug/'
-
-subprocess.check_call(build_cmd)
-
-set_osmesa_env(objdir)
-
-dbg_cmd = []
-if use_rr():
-    dbg_cmd = ['rr', 'record']
-elif use_gdb():
-    dbg_cmd = [debugger(), '--args']
-elif debugger():
-    print("Unknown debugger: " + debugger())
-    sys.exit(1)
-
+subprocess.check_call(['cargo', 'build'] + extra_flags + ['--release', '--verbose', '--features', 'headless'])
+set_osmesa_env('../target/release/')
 # TODO(gw): We have an occasional accuracy issue or bug (could be WR or OSMesa)
 #           where the output of a previous test that uses intermediate targets can
 #           cause 1.0 / 255.0 pixel differences in a subsequent test. For now, we
 #           run tests with no-scissor mode, which ensures a complete target clear
 #           between test runs. But we should investigate this further...
-cmd = dbg_cmd + [objdir + '/wrench', '--no-scissor', '-h'] + sys.argv[1:]
-print('Running: `' + ' '.join(cmd) + '`')
-subprocess.check_call(cmd)
+subprocess.check_call(['../target/release/wrench', '--no-scissor', '-h'] + sys.argv[1:])
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -104,16 +104,17 @@ impl<'a> RawtestHarness<'a> {
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(448.899994, 74.0, 151.000031, 56.));
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
             size(151., 56.0),
             size(151.0, 56.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -165,39 +166,43 @@ impl<'a> RawtestHarness<'a> {
         });
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., -9600.0, 1510.000031, 111256.));
 
         let image_size = size(1510., 111256.);
 
-        let clip_id = builder.define_clip(rect(40., 41., 200., 201.), vec![], None);
+        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+        let clip_id = builder.define_clip(
+            &root_space_and_clip,
+            rect(40., 41., 200., 201.),
+            vec![],
+            None,
+        );
 
-        builder.push_clip_id(clip_id);
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &SpaceAndClipInfo { clip_id, spatial_id: root_space_and_clip.spatial_id },
             image_size * 2.,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
         txn.set_blob_image_visible_area(
             blob_img,
             DeviceIntRect {
                 origin: point2(0, 111256 / 30),
                 size: size2(1510, 111256 / 30),
             }
         );
 
-        builder.pop_clip_id();
-
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels = self.render_and_get_pixels(window_rect);
 
         // make sure we didn't request too many blobs
         assert!(called.load(Ordering::SeqCst) < 20);
@@ -237,17 +242,17 @@ impl<'a> RawtestHarness<'a> {
         txn = Transaction::new();
         txn.delete_blob_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
 
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_insufficient_blob_visible_area(&mut self) {
-        println!("\tinsufficient blob visible area.");
+        println!("\tinsufficient blob visible area...");
 
         // This test compares two almost identical display lists containing the a blob
         // image. The only difference is that one of the display lists specifies a visible
         // area for its blob image which is too small, causing frame building to run into
         // missing tiles, and forcing it to exercise the code path where missing tiles are
         // rendered synchronously on demand.
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
@@ -256,16 +261,17 @@ impl<'a> RawtestHarness<'a> {
         let test_size = DeviceIntSize::new(800, 800);
         let window_rect = DeviceIntRect::new(
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(800.0, 800.0);
         let image_size = size(800.0, 800.0);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 800.0, 800.0));
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
 
         let blob_img1 = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img1,
             ImageDescriptor::new(
@@ -276,16 +282,17 @@ impl<'a> RawtestHarness<'a> {
                 false
             ),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             Some(100),
         );
 
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img1.as_image(),
             ColorF::WHITE,
         );
 
@@ -312,16 +319,17 @@ impl<'a> RawtestHarness<'a> {
         // This will force sync rasterization of the missing tiles during frame building.
         txn.set_blob_image_visible_area(blob_img2, DeviceIntRect {
             origin: point2(200, 200),
             size: size2(80, 80),
         });
 
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img2.as_image(),
             ColorF::WHITE,
         );
 
@@ -344,16 +352,17 @@ impl<'a> RawtestHarness<'a> {
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceIntSize::new(800, 800);
 
         let window_rect = DeviceIntRect::new(
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         // This exposes a crash in tile decomposition
         let mut txn = Transaction::new();
         let layout_size = LayoutSize::new(800., 800.);
 
         let blob_img = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img,
@@ -366,16 +375,17 @@ impl<'a> RawtestHarness<'a> {
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -391,16 +401,17 @@ impl<'a> RawtestHarness<'a> {
 
         let info = LayoutPrimitiveInfo::new(rect(-10000., 0.0, 1510., 1510.));
 
         let image_size = size(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -421,16 +432,17 @@ impl<'a> RawtestHarness<'a> {
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
         let image_size = size(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
+            &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -456,16 +468,18 @@ impl<'a> RawtestHarness<'a> {
 
         let test_size = DeviceIntSize::new(400, 400);
 
         let window_rect = DeviceIntRect::new(
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         let mut txn = Transaction::new();
         {
             let api = &self.wrench.api;
 
             blob_img = api.generate_blob_image_key();
             txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
@@ -482,16 +496,17 @@ impl<'a> RawtestHarness<'a> {
         });
 
         // draw the blob the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -505,16 +520,17 @@ impl<'a> RawtestHarness<'a> {
 
         // draw the blob image a second time at a different location
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -529,35 +545,33 @@ impl<'a> RawtestHarness<'a> {
 
         // use png;
         // png::save_flipped("out1.png", &pixels_first, window_rect.size);
         // png::save_flipped("out2.png", &pixels_second, window_rect.size);
 
         assert!(pixels_first != pixels_second);
 
         // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        self.wrench.api.update_resources(txn.resource_updates);
-
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_epoch_test(&mut self) {
         println!("\tblob update epoch test...");
         let (blob_img, blob_img2);
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceIntSize::new(400, 400);
 
         let window_rect = DeviceIntRect::new(
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         let mut txn = Transaction::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
 
             blob_img = api.generate_blob_image_key();
             txn.add_blob_image(
                 blob_img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
@@ -594,25 +608,27 @@ impl<'a> RawtestHarness<'a> {
 
         // create two blob images and draw them
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         let info2 = LayoutPrimitiveInfo::new(rect(200.0, 60.0, 200.0, 200.0));
         let push_images = |builder: &mut DisplayListBuilder| {
             builder.push_image(
                 &info,
+                &space_and_clip,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 blob_img.as_image(),
                 ColorF::WHITE,
             );
             builder.push_image(
                 &info2,
+                &space_and_clip,
                 size(200.0, 200.0),
                 size(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 blob_img2.as_image(),
                 ColorF::WHITE,
             );
         };
@@ -661,35 +677,31 @@ impl<'a> RawtestHarness<'a> {
         let _pixels_third = self.render_and_get_pixels(window_rect);
 
         // the first image should be requested 3 times
         assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
         // the second image should've been requested twice
         assert_eq!(img2_requested.load(Ordering::SeqCst), 2);
 
         // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        txn.delete_blob_image(blob_img2);
-        self.wrench.api.update_resources(txn.resource_updates);
-
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_test(&mut self) {
         println!("\tblob update test...");
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceIntSize::new(400, 400);
 
         let window_rect = DeviceIntRect::new(
             point(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
         let mut txn = Transaction::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_blob_image_key();
             txn.add_blob_image(
                 img,
                 ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
                 blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
@@ -699,16 +711,17 @@ impl<'a> RawtestHarness<'a> {
         };
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -726,16 +739,17 @@ impl<'a> RawtestHarness<'a> {
             &rect(100, 100, 100, 100).into(),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
@@ -751,34 +765,30 @@ impl<'a> RawtestHarness<'a> {
             &rect(200, 200, 100, 100).into(),
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
+            &space_and_clip,
             size(200.0, 200.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_third = self.render_and_get_pixels(window_rect);
 
         assert!(pixels_first == pixels_second);
         assert!(pixels_first != pixels_third);
-
-        // cleanup
-        txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
-        self.wrench.api.update_resources(txn.resource_updates);
     }
 
     // Ensures that content doing a save-restore produces the same results as not
     fn test_save_restore(&mut self) {
         println!("\tsave/restore...");
         let window_size = self.window.get_inner_size();
 
         let test_size = DeviceIntSize::new(400, 400);
@@ -787,64 +797,81 @@ impl<'a> RawtestHarness<'a> {
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
 
         let mut do_test = |should_try_and_fail| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
-            let clip = builder.define_clip(
+            let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
+            let clip_id = builder.define_clip(
+                &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
                 rect(110., 120., 200., 200.),
                 None::<ComplexClipRegion>,
                 None
             );
-            builder.push_clip_id(clip);
-            builder.push_rect(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
-                              ColorF::new(0.0, 0.0, 1.0, 1.0));
+            builder.push_rect(
+                &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                &SpaceAndClipInfo { spatial_id, clip_id },
+                ColorF::new(0.0, 0.0, 1.0, 1.0),
+            );
 
             if should_try_and_fail {
                 builder.save();
-                let clip = builder.define_clip(
+                let clip_id = builder.define_clip(
+                    &SpaceAndClipInfo { spatial_id, clip_id },
                     rect(80., 80., 90., 90.),
                     None::<ComplexClipRegion>,
                     None
                 );
-                builder.push_clip_id(clip);
-                builder.push_rect(&PrimitiveInfo::new(rect(110., 110., 50., 50.)),
-                                  ColorF::new(0.0, 1.0, 0.0, 1.0));
-                builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
-                                    Shadow {
-                                        offset: LayoutVector2D::new(1.0, 1.0),
-                                        blur_radius: 1.0,
-                                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
-                                    });
-                builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
-                                  0.0, LineOrientation::Horizontal,
-                                  &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+                let space_and_clip = SpaceAndClipInfo {
+                    spatial_id,
+                    clip_id
+                };
+                builder.push_rect(
+                    &PrimitiveInfo::new(rect(110., 110., 50., 50.)),
+                    &space_and_clip,
+                    ColorF::new(0.0, 1.0, 0.0, 1.0),
+                );
+                builder.push_shadow(
+                    &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                    &space_and_clip,
+                    Shadow {
+                        offset: LayoutVector2D::new(1.0, 1.0),
+                        blur_radius: 1.0,
+                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
+                    },
+                );
+                builder.push_line(
+                    &PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                    &space_and_clip,
+                    0.0, LineOrientation::Horizontal,
+                    &ColorF::new(0.0, 0.0, 0.0, 1.0),
+                    LineStyle::Solid,
+                );
                 builder.restore();
             }
 
             {
                 builder.save();
-                let clip = builder.define_clip(
+                let clip_id = builder.define_clip(
+                    &SpaceAndClipInfo { spatial_id, clip_id },
                     rect(80., 80., 100., 100.),
                     None::<ComplexClipRegion>,
                     None
                 );
-                builder.push_clip_id(clip);
-                builder.push_rect(&PrimitiveInfo::new(rect(150., 150., 100., 100.)),
-                                  ColorF::new(0.0, 0.0, 1.0, 1.0));
-
-                builder.pop_clip_id();
+                builder.push_rect(
+                    &PrimitiveInfo::new(rect(150., 150., 100., 100.)),
+                    &SpaceAndClipInfo { spatial_id, clip_id },
+                    ColorF::new(0.0, 0.0, 1.0, 1.0),
+                );
                 builder.clear_save();
             }
 
-            builder.pop_clip_id();
-
             let txn = Transaction::new();
 
             self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
 
             self.render_and_get_pixels(window_rect)
         };
 
         let first = do_test(false);
@@ -861,34 +888,42 @@ impl<'a> RawtestHarness<'a> {
 
         let test_size = DeviceIntSize::new(400, 400);
 
         let window_rect = DeviceIntRect::new(
             DeviceIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut do_test = |shadow_is_red| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
             let shadow_color = if shadow_is_red {
                 ColorF::new(1.0, 0.0, 0.0, 1.0)
             } else {
                 ColorF::new(0.0, 1.0, 0.0, 1.0)
             };
 
-            builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+            builder.push_shadow(
+                &PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                &space_and_clip,
                 Shadow {
                     offset: LayoutVector2D::new(1.0, 1.0),
                     blur_radius: 1.0,
                     color: shadow_color,
-                });
-            builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
-                              0.0, LineOrientation::Horizontal,
-                              &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+                },
+            );
+            builder.push_line(
+                &PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                &space_and_clip,
+                0.0, LineOrientation::Horizontal,
+                &ColorF::new(0.0, 0.0, 0.0, 1.0),
+                LineStyle::Solid,
+            );
             builder.pop_all_shadows();
 
             let txn = Transaction::new();
             self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
 
             self.render_and_get_pixels(window_rect)
         };
 
@@ -918,16 +953,17 @@ impl<'a> RawtestHarness<'a> {
             ImageData::new(vec![0xFF, 0, 0, 0xFF]),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         builder.push_image(
             &LayoutPrimitiveInfo::new(rect(300.0, 70.0, 150.0, 50.0)),
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
             size(150.0, 50.0),
             size(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             image,
             ColorF::WHITE,
         );
 
@@ -986,17 +1022,21 @@ impl<'a> RawtestHarness<'a> {
 
         let layout_size = LayoutSize::new(120.0, 0.0);
         let window_size = DeviceIntSize::new(layout_size.width as i32, layout_size.height as i32);
         let doc_id = self.wrench.api.add_document(window_size, 1);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(),
                                                             LayoutSize::new(100.0, 100.0)));
-        builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
+        builder.push_rect(
+            &info,
+            &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
+            ColorF::new(0.0, 1.0, 0.0, 1.0),
+        );
 
         let mut txn = Transaction::new();
         txn.set_root_pipeline(self.wrench.root_pipeline_id);
         txn.set_display_list(
             Epoch(1),
             Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
             layout_size,
             builder.finalize(),
@@ -1012,62 +1052,79 @@ impl<'a> RawtestHarness<'a> {
 
 
     fn test_hit_testing(&mut self) {
         println!("\thit testing test...");
 
         let layout_size = LayoutSize::new(400., 400.);
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
+        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
+
         // Add a rectangle that covers the entire scene.
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), layout_size));
         info.tag = Some((0, 1));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         // Add a simple 100x100 rectangle at 100,0.
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(
             LayoutPoint::new(100., 0.),
             LayoutSize::new(100., 100.)
         ));
         info.tag = Some((0, 2));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.push_rect(&info, &space_and_clip, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
             ComplexClipRegion::new(
                 *rect,
                 BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
                 ClipMode::Clip
             )
         };
 
-
         // Add a rectangle that is clipped by a rounded rect clip item.
         let rect = LayoutRect::new(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
-        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
-        builder.push_clip_id(clip_id);
-        let mut info = LayoutPrimitiveInfo::new(rect);
-        info.tag = Some((0, 4));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-        builder.pop_clip_id();
-
+        let temp_clip_id = builder.define_clip(
+            &space_and_clip,
+            rect,
+            vec![make_rounded_complex_clip(&rect, 20.)],
+            None,
+        );
+        builder.push_rect(
+            &LayoutPrimitiveInfo {
+                tag: Some((0, 4)),
+                .. LayoutPrimitiveInfo::new(rect)
+            },
+            &SpaceAndClipInfo {
+                clip_id: temp_clip_id,
+                spatial_id: space_and_clip.spatial_id,
+            },
+            ColorF::new(1.0, 1.0, 1.0, 1.0),
+        );
 
         // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
         let rect = LayoutRect::new(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
-        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
+        let clip_id = builder.define_clip(
+            &space_and_clip,
+            rect,
+            vec![make_rounded_complex_clip(&rect, 20.)],
+            None,
+        );
         let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
-        builder.push_clip_and_scroll_info(ClipAndScrollInfo::new(
-            ClipId::root_scroll_node(self.wrench.root_pipeline_id),
-            ClipId::ClipChain(clip_chain_id),
-        ));
-        let mut info = LayoutPrimitiveInfo::new(rect);
-        info.tag = Some((0, 5));
-        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
-        builder.pop_clip_id();
-
+        builder.push_rect(
+            &LayoutPrimitiveInfo {
+                tag: Some((0, 5)),
+                .. LayoutPrimitiveInfo::new(rect)
+            },
+            &SpaceAndClipInfo {
+                clip_id: ClipId::ClipChain(clip_chain_id),
+                spatial_id: space_and_clip.spatial_id,
+            },
+            ColorF::new(1.0, 1.0, 1.0, 1.0),
+        );
 
         let mut epoch = Epoch(0);
         let txn = Transaction::new();
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         // We render to ensure that the hit tester is up to date with the current scene.
         self.rx.recv().unwrap();
         self.wrench.render();
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -219,17 +219,21 @@ pub struct YamlFrameReader {
 
     fonts: HashMap<FontDescriptor, FontKey>,
     font_instances: HashMap<(FontKey, Au, FontInstanceFlags, Option<ColorU>, SyntheticItalics), FontInstanceKey>,
     font_render_mode: Option<FontRenderMode>,
     allow_mipmaps: bool,
 
     /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
     /// and having each of those ids correspond to a unique ClipId.
-    clip_id_map: HashMap<u64, ClipId>,
+    user_clip_id_map: HashMap<u64, ClipId>,
+    user_spatial_id_map: HashMap<u64, SpatialId>,
+
+    clip_id_stack: Vec<ClipId>,
+    spatial_id_stack: Vec<SpatialId>,
 }
 
 impl YamlFrameReader {
     pub fn new(yaml_path: &Path) -> YamlFrameReader {
         YamlFrameReader {
             watch_source: false,
             list_resources: false,
             frame_built: false,
@@ -238,19 +242,22 @@ impl YamlFrameReader {
             frame_count: 0,
             display_lists: Vec::new(),
             queue_depth: 1,
             include_only: vec![],
             scroll_offsets: HashMap::new(),
             fonts: HashMap::new(),
             font_instances: HashMap::new(),
             font_render_mode: None,
+            allow_mipmaps: false,
             image_map: HashMap::new(),
-            clip_id_map: HashMap::new(),
-            allow_mipmaps: false,
+            user_clip_id_map: HashMap::new(),
+            user_spatial_id_map: HashMap::new(),
+            clip_id_stack: Vec::new(),
+            spatial_id_stack: Vec::new(),
         }
     }
 
     pub fn deinit(mut self, wrench: &mut Wrench) {
         let mut txn = Transaction::new();
 
         for (_, font_instance) in self.font_instances.drain() {
             txn.delete_font_instance(font_instance);
@@ -258,16 +265,23 @@ impl YamlFrameReader {
 
         for (_, font) in self.fonts.drain() {
             txn.delete_font(font);
         }
 
         wrench.api.update_resources(txn.resource_updates);
     }
 
+    fn top_space_and_clip(&self) -> SpaceAndClipInfo {
+        SpaceAndClipInfo {
+            spatial_id: *self.spatial_id_stack.last().unwrap(),
+            clip_id: *self.clip_id_stack.last().unwrap(),
+        }
+    }
+
     pub fn yaml_path(&self) -> &PathBuf {
         &self.yaml_path
     }
 
     pub fn new_from_args(args: &clap::ArgMatches) -> YamlFrameReader {
         let yaml_file = args.value_of("INPUT").map(|s| PathBuf::from(s)).unwrap();
 
         let mut y = YamlFrameReader::new(&yaml_file);
@@ -311,23 +325,31 @@ impl YamlFrameReader {
 
     pub fn build_pipeline(
         &mut self,
         wrench: &mut Wrench,
         pipeline_id: PipelineId,
         yaml: &Yaml
     ) {
         // Don't allow referencing clips between pipelines for now.
-        self.clip_id_map.clear();
+        self.user_clip_id_map.clear();
+        self.user_spatial_id_map.clear();
+        self.clip_id_stack.clear();
+        self.clip_id_stack.push(ClipId::root(pipeline_id));
+        self.spatial_id_stack.clear();
+        self.spatial_id_stack.push(SpatialId::root_scroll_node(pipeline_id));
 
         let content_size = self.get_root_size_from_yaml(wrench, yaml);
         let mut builder = DisplayListBuilder::new(pipeline_id, content_size);
         let mut info = LayoutPrimitiveInfo::new(LayoutRect::zero());
         self.add_stacking_context_from_yaml(&mut builder, wrench, yaml, true, &mut info);
         self.display_lists.push(builder.finalize());
+
+        assert_eq!(self.clip_id_stack.len(), 1);
+        assert_eq!(self.spatial_id_stack.len(), 1);
     }
 
     fn to_complex_clip_region(&mut self, item: &Yaml) -> ComplexClipRegion {
         let rect = item["rect"]
             .as_rect()
             .expect("Complex clip entry must have rect");
         let radius = item["radius"]
             .as_border_radius()
@@ -357,60 +379,94 @@ impl YamlFrameReader {
             Yaml::Array(ref array) => StickyOffsetBounds::new(
                 array[0].as_f32().unwrap_or(0.0),
                 array[1].as_f32().unwrap_or(0.0),
             ),
             _ => StickyOffsetBounds::new(0.0, 0.0),
         }
     }
 
-    pub fn u64_to_clip_id(&self, number: u64, pipeline_id: PipelineId) -> ClipId {
-        match number {
-            0 => ClipId::root_reference_frame(pipeline_id),
-            1 => ClipId::root_scroll_node(pipeline_id),
-            _ => self.clip_id_map[&number],
-        }
-
-    }
-
     pub fn to_clip_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<ClipId> {
         match *item {
-            Yaml::Integer(value) => Some(self.u64_to_clip_id(value as u64, pipeline_id)),
-            Yaml::String(ref id_string) if id_string == "root-reference-frame" =>
-                Some(ClipId::root_reference_frame(pipeline_id)),
-            Yaml::String(ref id_string) if id_string == "root-scroll-node" =>
-                Some(ClipId::root_scroll_node(pipeline_id)),
+            Yaml::Integer(value) => Some(self.user_clip_id_map[&(value as u64)]),
+            Yaml::String(ref id_string) if id_string == "root_clip" =>
+                Some(ClipId::root(pipeline_id)),
             _ => None,
         }
     }
 
+    pub fn to_spatial_id(&self, item: &Yaml, pipeline_id: PipelineId) -> SpatialId {
+        match *item {
+            Yaml::Integer(value) => self.user_spatial_id_map[&(value as u64)],
+            Yaml::String(ref id_string) if id_string == "root-reference-frame" =>
+                SpatialId::root_reference_frame(pipeline_id),
+            Yaml::String(ref id_string) if id_string == "root-scroll-node" =>
+                SpatialId::root_scroll_node(pipeline_id),
+            _ => {
+                println!("Unable to parse SpatialId {:?}", item);
+                SpatialId::root_reference_frame(pipeline_id)
+            }
+        }
+    }
+
     pub fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) {
-        assert!(numeric_id != 0, "id=0 is reserved for the root reference frame");
-        assert!(numeric_id != 1, "id=1 is reserved for the root scroll node");
-        self.clip_id_map.insert(numeric_id, real_id);
+        assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip");
+        self.user_clip_id_map.insert(numeric_id, real_id);
+    }
+
+    pub fn add_spatial_id_mapping(&mut self, numeric_id: u64, real_id: SpatialId) {
+        assert_ne!(numeric_id, 0, "id=0 is reserved for the root reference frame");
+        assert_ne!(numeric_id, 1, "id=1 is reserved for the root scroll node");
+        self.user_spatial_id_map.insert(numeric_id, real_id);
     }
 
     fn to_clip_and_scroll_info(
         &self,
         item: &Yaml,
         pipeline_id: PipelineId
-    ) -> Option<ClipAndScrollInfo> {
+    ) -> (Option<ClipId>, Option<SpatialId>) {
+        // Note: this is tricky. In the old code the single ID could be used either way
+        // for clip/scroll. In the new code we are trying to reflect that and not break
+        // all the reftests. Ultimately we'd want the clip and scroll nodes to be
+        // provided separately in YAML.
         match *item {
+            Yaml::BadValue => (None, None),
             Yaml::Array(ref array) if array.len() == 2 => {
-                let scroll_id = match self.to_clip_id(&array[0], pipeline_id) {
-                    Some(id) => id,
-                    None => return None,
-                };
-                let clip_id = match self.to_clip_id(&array[1], pipeline_id) {
-                    Some(id) => id,
-                    None => return None,
-                };
-                Some(ClipAndScrollInfo::new(scroll_id, clip_id))
+                let scroll_id = self.to_spatial_id(&array[0], pipeline_id);
+                let clip_id = self.to_clip_id(&array[1], pipeline_id);
+                (clip_id, Some(scroll_id))
+            }
+            Yaml::String(ref id_string) if id_string == "root-reference-frame" => {
+                let scroll_id = SpatialId::root_reference_frame(pipeline_id);
+                let clip_id = ClipId::root(pipeline_id);
+                (Some(clip_id), Some(scroll_id))
+            }
+            Yaml::String(ref id_string) if id_string == "root-scroll-node" => {
+                let scroll_id = SpatialId::root_scroll_node(pipeline_id);
+                (None, Some(scroll_id))
             }
-            _ => self.to_clip_id(item, pipeline_id).map(|id| ClipAndScrollInfo::simple(id)),
+            Yaml::String(ref id_string) if id_string == "root_clip" => {
+                let clip_id = ClipId::root(pipeline_id);
+                (Some(clip_id), None)
+            }
+            Yaml::Integer(value) => {
+                let scroll_id = self.user_spatial_id_map
+                    .get(&(value as u64))
+                    .cloned();
+                let clip_id = self.user_clip_id_map
+                    .get(&(value as u64))
+                    .cloned();
+                assert!(scroll_id.is_some() || clip_id.is_some(),
+                    "clip-and-scroll index not found: {}", value);
+                (clip_id, scroll_id)
+            }
+            _ => {
+                println!("Unable to parse clip/scroll {:?}", item);
+                (None, None)
+            }
         }
     }
 
     fn to_hit_testing_tag(&self, item: &Yaml) -> Option<ItemTag> {
         match *item {
             Yaml::Array(ref array) if array.len() == 2 => {
                 match (array[0].as_i64(), array[1].as_i64()) {
                     (Some(first), Some(second)) => Some((first as u64, second as u16)),
@@ -704,29 +760,29 @@ impl YamlFrameReader {
             "rect"
         } else {
             "bounds"
         };
         info.rect = item[bounds_key]
             .as_rect()
             .expect("rect type must have bounds");
         let color = item["color"].as_colorf().unwrap_or(ColorF::WHITE);
-        dl.push_rect(&info, color);
+        dl.push_rect(&info, &self.top_space_and_clip(), color);
     }
 
     fn handle_clear_rect(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
         info.rect = item["bounds"]
             .as_rect()
             .expect("clear-rect type must have bounds");
-        dl.push_clear_rect(&info);
+        dl.push_clear_rect(&info, &self.top_space_and_clip());
     }
 
     fn handle_line(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
@@ -772,16 +828,17 @@ impl YamlFrameReader {
                     LayoutRect::new(LayoutPoint::new(baseline, start),
                                     LayoutSize::new(width, end - start))
                 }
             };
         }
 
         dl.push_line(
             &info,
+            &self.top_space_and_clip(),
             wavy_line_thickness,
             orientation,
             &color,
             style,
         );
     }
 
     fn handle_gradient(
@@ -798,17 +855,17 @@ impl YamlFrameReader {
         let bounds = item[bounds_key]
             .as_rect()
             .expect("gradient must have bounds");
         info.rect = bounds;
         let gradient = self.to_gradient(dl, item);
         let tile_size = item["tile-size"].as_size().unwrap_or(bounds.size);
         let tile_spacing = item["tile-spacing"].as_size().unwrap_or(LayoutSize::zero());
 
-        dl.push_gradient(&info, gradient, tile_size, tile_spacing);
+        dl.push_gradient(&info, &self.top_space_and_clip(), gradient, tile_size, tile_spacing);
     }
 
     fn handle_radial_gradient(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
@@ -820,17 +877,23 @@ impl YamlFrameReader {
         let bounds = item[bounds_key]
             .as_rect()
             .expect("radial gradient must have bounds");
         info.rect = bounds;
         let gradient = self.to_radial_gradient(dl, item);
         let tile_size = item["tile-size"].as_size().unwrap_or(bounds.size);
         let tile_spacing = item["tile-spacing"].as_size().unwrap_or(LayoutSize::zero());
 
-        dl.push_radial_gradient(&info, gradient, tile_size, tile_spacing);
+        dl.push_radial_gradient(
+            &info,
+            &self.top_space_and_clip(),
+            gradient,
+            tile_size,
+            tile_spacing,
+        );
     }
 
     fn handle_border(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
@@ -982,17 +1045,17 @@ impl YamlFrameReader {
                     None
                 }
             }
         } else {
             println!("Unable to parse border {:?}", item);
             None
         };
         if let Some(details) = border_details {
-            dl.push_border(&info, widths, details);
+            dl.push_border(&info, &self.top_space_and_clip(), widths, details);
         }
     }
 
     fn handle_box_shadow(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
@@ -1023,16 +1086,17 @@ impl YamlFrameReader {
                 s => panic!("Unknown box shadow clip mode {}", s),
             }
         } else {
             BoxShadowClipMode::Outset
         };
 
         dl.push_box_shadow(
             &info,
+            &self.top_space_and_clip(),
             box_bounds,
             offset,
             color,
             blur_radius,
             spread_radius,
             border_radius,
             clip_mode,
         );
@@ -1085,16 +1149,17 @@ impl YamlFrameReader {
         let bounds = item["bounds"].as_vec_f32().unwrap();
         info.rect = LayoutRect::new(
             LayoutPoint::new(bounds[0], bounds[1]),
             LayoutSize::new(bounds[2], bounds[3]),
         );
 
         dl.push_yuv_image(
             &info,
+            &self.top_space_and_clip(),
             yuv_data,
             color_depth,
             color_space,
             ImageRendering::Auto,
         );
     }
 
     fn handle_image(
@@ -1145,17 +1210,26 @@ impl YamlFrameReader {
         let alpha_type = match item["alpha-type"].as_str() {
             Some("premultiplied-alpha") | None => AlphaType::PremultipliedAlpha,
             Some("alpha") => AlphaType::Alpha,
             Some(_) => panic!(
                 "AlphaType can be premultiplied-alpha or alpha -- got {:?}",
                 item
             ),
         };
-        dl.push_image(&info, stretch_size, tile_spacing, rendering, alpha_type, image_key, ColorF::WHITE);
+        dl.push_image(
+            &info,
+            &self.top_space_and_clip(),
+            stretch_size,
+            tile_spacing,
+            rendering,
+            alpha_type,
+            image_key,
+            ColorF::WHITE,
+        );
     }
 
     fn handle_text(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
@@ -1260,29 +1334,36 @@ impl YamlFrameReader {
                     };
                     gi
                 })
                 .collect::<Vec<_>>();
             (glyphs, bounds)
         };
         info.rect = rect;
 
-        dl.push_text(&info, &glyphs, font_instance_key, color, None);
+        dl.push_text(
+            &info,
+            &self.top_space_and_clip(),
+            &glyphs,
+            font_instance_key,
+            color,
+            None,
+        );
     }
 
     fn handle_iframe(
         &mut self,
         dl: &mut DisplayListBuilder,
         item: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
         info.rect = item["bounds"].as_rect().expect("iframe must have bounds");
         let pipeline_id = item["id"].as_pipeline_id().unwrap();
         let ignore = item["ignore_missing_pipeline"].as_bool().unwrap_or(true);
-        dl.push_iframe(&info, pipeline_id, ignore);
+        dl.push_iframe(&info, &self.top_space_and_clip(), pipeline_id, ignore);
     }
 
     pub fn get_complex_clip_for_item(&mut self, yaml: &Yaml) -> Option<ComplexClipRegion> {
         let complex_clip = &yaml["complex-clip"];
         if complex_clip.is_badvalue() {
             return None;
         }
         Some(self.to_complex_clip_region(complex_clip))
@@ -1327,34 +1408,42 @@ impl YamlFrameReader {
             // We never skip stacking contexts and reference frames because
             // they are structural elements of the display list.
             if item_type != "stacking-context" &&
                 item_type != "reference-frame" &&
                 self.include_only.contains(&item_type.to_owned()) {
                 continue;
             }
 
-            let clip_scroll_info = self.to_clip_and_scroll_info(
+            let (set_clip_id, set_scroll_id) = self.to_clip_and_scroll_info(
                 &item["clip-and-scroll"],
                 dl.pipeline_id
             );
-            if let Some(clip_scroll_info) = clip_scroll_info {
-                dl.push_clip_and_scroll_info(clip_scroll_info);
+            if let Some(clip_id) = set_clip_id {
+                self.clip_id_stack.push(clip_id);
+            }
+            if let Some(scroll_id) = set_scroll_id {
+                self.spatial_id_stack.push(scroll_id);
             }
 
             let complex_clip = self.get_complex_clip_for_item(item);
             let clip_rect = item["clip-rect"].as_rect().unwrap_or(full_clip);
 
             let mut pushed_clip = false;
             if let Some(complex_clip) = complex_clip {
                 match item_type {
                     "clip" | "clip-chain" | "scroll-frame" => {},
                     _ => {
-                        let id = dl.define_clip(clip_rect, vec![complex_clip], None);
-                        dl.push_clip_id(id);
+                        let id = dl.define_clip(
+                            &self.top_space_and_clip(),
+                            clip_rect,
+                            vec![complex_clip],
+                            None,
+                        );
+                        self.clip_id_stack.push(id);
                         pushed_clip = true;
                     }
                 }
             }
 
             let mut info = LayoutPrimitiveInfo::with_clip_rect(LayoutRect::zero(), clip_rect);
             info.is_backface_visible = item["backface-visible"].as_bool().unwrap_or(true);;
             info.tag = self.to_hit_testing_tag(&item["hit-testing-tag"]);
@@ -1380,24 +1469,24 @@ impl YamlFrameReader {
                 }
                 "reference-frame" => self.handle_reference_frame(dl, wrench, item),
                 "shadow" => self.handle_push_shadow(dl, item, &mut info),
                 "pop-all-shadows" => self.handle_pop_all_shadows(dl),
                 _ => println!("Skipping unknown item type: {:?}", item),
             }
 
             if pushed_clip {
-                dl.pop_clip_id();
-
+                self.clip_id_stack.pop().unwrap();
             }
-
-            if clip_scroll_info.is_some() {
-                dl.pop_clip_id();
+            if set_clip_id.is_some() {
+                self.clip_id_stack.pop().unwrap();
             }
-
+            if set_scroll_id.is_some() {
+                self.spatial_id_stack.pop().unwrap();
+            }
         }
     }
 
     pub fn handle_scroll_frame(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
@@ -1414,98 +1503,105 @@ impl YamlFrameReader {
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
         let external_id =  yaml["scroll-offset"].as_point().map(|size| {
             let id = ExternalScrollId((self.scroll_offsets.len() + 1) as u64, dl.pipeline_id);
             self.scroll_offsets.insert(id, LayoutPoint::new(size.x, size.y));
             id
         });
 
-        let real_id = dl.define_scroll_frame(
+        let space_and_clip = dl.define_scroll_frame(
+            &self.top_space_and_clip(),
             external_id,
             content_rect,
             clip_rect,
             complex_clips,
             image_mask,
             ScrollSensitivity::ScriptAndInputEvents,
         );
         if let Some(numeric_id) = numeric_id {
-            self.add_clip_id_mapping(numeric_id, real_id);
+            self.add_spatial_id_mapping(numeric_id, space_and_clip.spatial_id);
+            self.add_clip_id_mapping(numeric_id, space_and_clip.clip_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.spatial_id_stack.push(space_and_clip.spatial_id);
+            self.clip_id_stack.push(space_and_clip.clip_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.clip_id_stack.pop().unwrap();
+            self.spatial_id_stack.pop().unwrap();
         }
     }
 
     pub fn handle_sticky_frame(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
         let bounds = yaml["bounds"].as_rect().expect("sticky frame must have a bounds");
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
 
         let real_id = dl.define_sticky_frame(
+            *self.spatial_id_stack.last().unwrap(),
             bounds,
             SideOffsets2D::new(
                 yaml["margin-top"].as_f32(),
                 yaml["margin-right"].as_f32(),
                 yaml["margin-bottom"].as_f32(),
                 yaml["margin-left"].as_f32(),
             ),
             self.to_sticky_offset_bounds(&yaml["vertical-offset-bounds"]),
             self.to_sticky_offset_bounds(&yaml["horizontal-offset-bounds"]),
             yaml["previously-applied-offset"].as_vector().unwrap_or(LayoutVector2D::zero()),
         );
 
         if let Some(numeric_id) = numeric_id {
-            self.add_clip_id_mapping(numeric_id, real_id);
+            self.add_spatial_id_mapping(numeric_id, real_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.spatial_id_stack.push(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.spatial_id_stack.pop().unwrap();
         }
     }
 
     pub fn handle_push_shadow(
         &mut self,
         dl: &mut DisplayListBuilder,
         yaml: &Yaml,
         info: &mut LayoutPrimitiveInfo,
     ) {
         let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0);
         let offset = yaml["offset"].as_vector().unwrap_or(LayoutVector2D::zero());
         let color = yaml["color"].as_colorf().unwrap_or(ColorF::BLACK);
 
         dl.push_shadow(
             &info,
+            &self.top_space_and_clip(),
             Shadow {
                 blur_radius,
                 offset,
                 color,
             },
         );
     }
 
     pub fn handle_pop_all_shadows(&mut self, dl: &mut DisplayListBuilder) {
         dl.pop_all_shadows();
     }
 
     pub fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) {
         let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id");
         let clip_ids: Vec<ClipId> = yaml["clips"]
             .as_vec_u64()
-            .unwrap_or_else(Vec::new)
-            .iter().map(|id| self.u64_to_clip_id(*id, builder.pipeline_id))
+            .unwrap_or_default()
+            .iter()
+            .map(|id| self.user_clip_id_map[id])
             .collect();
 
         let parent = self.to_clip_id(&yaml["parent"], builder.pipeline_id);
         let parent = match parent {
             Some(ClipId::ClipChain(clip_chain_id)) => Some(clip_chain_id),
             Some(_) => panic!("Tried to create a ClipChain with a non-ClipChain parent"),
             None => None,
         };
@@ -1515,41 +1611,48 @@ impl YamlFrameReader {
     }
 
     pub fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) {
         let clip_rect = yaml["bounds"].as_rect().expect("clip must have a bounds");
         let numeric_id = yaml["id"].as_i64();
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
-        let real_id = dl.define_clip(clip_rect, complex_clips, image_mask);
+        let space_and_clip = self.top_space_and_clip();
+        let real_id = dl.define_clip(
+            &space_and_clip,
+            clip_rect,
+            complex_clips,
+            image_mask,
+        );
         if let Some(numeric_id) = numeric_id {
             self.add_clip_id_mapping(numeric_id as u64, real_id);
+            self.add_spatial_id_mapping(numeric_id as u64, space_and_clip.spatial_id);
         }
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(real_id);
+            self.clip_id_stack.push(real_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
+            self.clip_id_stack.pop().unwrap();
         }
     }
 
     pub fn get_root_size_from_yaml(&mut self, wrench: &mut Wrench, yaml: &Yaml) -> LayoutSize {
         yaml["bounds"]
             .as_rect()
             .map(|rect| rect.size)
             .unwrap_or(wrench.window_size_f32())
     }
 
     pub fn push_reference_frame(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
-    ) -> ClipId {
+    ) -> SpatialId {
         let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
         let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
         let default_transform_origin = LayoutPoint::new(
             bounds.origin.x + bounds.size.width * 0.5,
             bounds.origin.y + bounds.size.height * 0.5,
         );
 
         let transform_style = yaml["transform-style"]
@@ -1573,70 +1676,71 @@ impl YamlFrameReader {
                 Some(make_perspective(perspective_origin, value as f32))
             }
             Some(_) => None,
             _ => yaml["perspective"].as_matrix4d(),
         };
 
         let reference_frame_id = dl.push_reference_frame(
             &bounds,
+            *self.spatial_id_stack.last().unwrap(),
             transform_style,
             transform.into(),
             perspective,
         );
 
         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);
+            self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id);
         }
 
         reference_frame_id
     }
 
     pub fn handle_reference_frame(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
-        let reference_frame_id = self.push_reference_frame(dl, wrench, yaml);
+        let real_id = self.push_reference_frame(dl, wrench, yaml);
+        self.spatial_id_stack.push(real_id);
 
         if !yaml["items"].is_badvalue() {
-            dl.push_clip_id(reference_frame_id);
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
-            dl.pop_clip_id();
         }
 
+        self.spatial_id_stack.pop().unwrap();
         dl.pop_reference_frame();
     }
 
     pub fn add_stacking_context_from_yaml(
         &mut self,
         dl: &mut DisplayListBuilder,
         wrench: &mut Wrench,
         yaml: &Yaml,
         is_root: bool,
         info: &mut LayoutPrimitiveInfo,
     ) {
         let default_bounds = LayoutRect::new(LayoutPoint::zero(), wrench.window_size_f32());
-        let bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
-        info.rect = bounds;
-        info.clip_rect = bounds;
+        let mut bounds = yaml["bounds"].as_rect().unwrap_or(default_bounds);
 
         let reference_frame_id = if !yaml["transform"].is_badvalue() ||
             !yaml["perspective"].is_badvalue() {
             let reference_frame_id = self.push_reference_frame(dl, wrench, yaml);
-            info.rect.origin = LayoutPoint::zero();
-            info.clip_rect.origin = LayoutPoint::zero();
+            self.spatial_id_stack.push(reference_frame_id);
+            bounds.origin = LayoutPoint::zero();
             Some(reference_frame_id)
         } else {
             None
         };
 
+        // note: this API is deprecated, use the standard clip-and-scroll field instead
         let clip_node_id = self.to_clip_id(&yaml["clip-node"], dl.pipeline_id);
+
         let transform_style = yaml["transform-style"]
             .as_transform_style()
             .unwrap_or(TransformStyle::Flat);
         let mix_blend_mode = yaml["mix-blend-mode"]
             .as_mix_blend_mode()
             .unwrap_or(MixBlendMode::Normal);
         let raster_space = yaml["raster-space"]
             .as_raster_space()
@@ -1646,40 +1750,37 @@ impl YamlFrameReader {
             if let Some(size) = yaml["scroll-offset"].as_point() {
                 let external_id = ExternalScrollId(0, dl.pipeline_id);
                 self.scroll_offsets.insert(external_id, LayoutPoint::new(size.x, size.y));
             }
         }
 
         let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
 
-        if let Some(reference_frame_id) = reference_frame_id {
-            dl.push_clip_id(reference_frame_id);
-        }
+        info.rect = bounds;
+        info.clip_rect = bounds;
 
         dl.push_stacking_context(
             &info,
+            *self.spatial_id_stack.last().unwrap(),
             clip_node_id,
             transform_style,
             mix_blend_mode,
             &filters,
             raster_space,
         );
 
         if !yaml["items"].is_badvalue() {
             self.add_display_list_items_from_yaml(dl, wrench, &yaml["items"]);
         }
 
         dl.pop_stacking_context();
 
         if reference_frame_id.is_some() {
-            dl.pop_clip_id();
-        }
-
-        if reference_frame_id.is_some() {
+            self.spatial_id_stack.pop().unwrap();
             dl.pop_reference_frame();
         }
     }
 }
 
 impl WrenchThing for YamlFrameReader {
     fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
         if !self.frame_built || self.watch_source {
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -12,17 +12,17 @@ use scene::{Scene, SceneProperties};
 use std::collections::HashMap;
 use std::io::Write;
 use std::path::{Path, PathBuf};
 use std::{fmt, fs};
 use super::CURRENT_FRAME_NUMBER;
 use time;
 use webrender;
 use webrender::api::*;
-use webrender::api::SpecificDisplayItem::*;
+use webrender::api::SpecificDisplayItem as Sdi;
 use webrender::api::channel::Payload;
 use yaml_helper::StringEnum;
 use yaml_rust::{Yaml, YamlEmitter};
 
 type Table = yaml_rust::yaml::Hash;
 
 fn array_elements_are_same<T: PartialEq>(v: &[T]) -> bool {
     if !v.is_empty() {
@@ -201,42 +201,37 @@ fn write_reference_frame(
         "transform",
         &properties.resolve_layout_transform(&reference_frame.transform)
     );
 
     if let Some(perspective) = reference_frame.perspective {
         matrix4d_node(parent, "perspective", &perspective);
     }
 
-    usize_node(parent, "id", clip_id_mapper.add_id(reference_frame.id));
+    usize_node(parent, "id", clip_id_mapper.add_spatial_id(reference_frame.id));
 }
 
 fn write_stacking_context(
     parent: &mut Table,
     sc: &StackingContext,
     properties: &SceneProperties,
     filter_iter: AuxIter<FilterOp>,
-    clip_id_mapper: &ClipIdMapper,
 ) {
     enum_node(parent, "transform-style", sc.transform_style);
 
     let raster_space = match sc.raster_space {
         RasterSpace::Local(scale) => {
             format!("local({})", scale)
         }
         RasterSpace::Screen => {
             "screen".to_owned()
         }
     };
     str_node(parent, "raster-space", &raster_space);
 
-    if let Some(clip_node_id) = sc.clip_node_id {
-        yaml_node(parent, "clip-node", clip_id_mapper.map_id(&clip_node_id));
-    }
-
     // mix_blend_mode
     if sc.mix_blend_mode != MixBlendMode::Normal {
         enum_node(parent, "mix-blend-mode", sc.mix_blend_mode)
     }
     // filters
     let mut filters = vec![];
     for filter in filter_iter {
         match filter {
@@ -762,37 +757,41 @@ impl YamlFrameWriter {
             if let Some(tag) = info.tag {
                 yaml_node(
                     &mut v,
                     "hit-testing-tag",
                      Yaml::Array(vec![Yaml::Integer(tag.0 as i64), Yaml::Integer(tag.1 as i64)])
                 );
             }
 
-            yaml_node(&mut v, "clip-and-scroll", clip_id_mapper.map_info(&base.clip_and_scroll()));
+            let space_and_clip = base.space_and_clip_info();
+            let clip_id = if space_and_clip.clip_id.is_root() { None } else { Some(space_and_clip.clip_id) };
+            yaml_node(&mut v, "clip-and-scroll",
+                clip_id_mapper.map_clip_and_scroll_ids(clip_id, space_and_clip.spatial_id)
+            );
             bool_node(&mut v, "backface-visible", base.is_backface_visible());
 
             match *base.item() {
-                Rectangle(item) => {
+                Sdi::Rectangle(item) => {
                     str_node(&mut v, "type", "rect");
                     color_node(&mut v, "color", item.color);
                 }
-                ClearRectangle => {
+                Sdi::ClearRectangle => {
                     str_node(&mut v, "type", "clear-rect");;
                 }
-                Line(item) => {
+                Sdi::Line(item) => {
                     str_node(&mut v, "type", "line");
                     if let LineStyle::Wavy = item.style {
                         f32_node(&mut v, "thickness", item.wavy_line_thickness);
                     }
                     str_node(&mut v, "orientation", item.orientation.as_str());
                     color_node(&mut v, "color", item.color);
                     str_node(&mut v, "style", item.style.as_str());
                 }
-                Text(item) => {
+                Sdi::Text(item) => {
                     let gi = display_list.get(base.glyphs());
                     let mut indices: Vec<u32> = vec![];
                     let mut offsets: Vec<f32> = vec![];
                     for g in gi {
                         indices.push(g.index);
                         offsets.push(g.point.x);
                         offsets.push(g.point.y);
                     }
@@ -836,17 +835,17 @@ impl YamlFrameWriter {
 
                             path_node(&mut v, "font", path_opt.as_ref().unwrap());
                             if index != 0 {
                                 u32_node(&mut v, "font-index", index);
                             }
                         }
                     }
                 }
-                Image(item) => {
+                Sdi::Image(item) => {
                     if let Some(path) = self.path_for_image(item.image_key) {
                         path_node(&mut v, "image", &path);
                     }
                     if let Some(&CachedImage {
                         tiling: Some(tile_size),
                         ..
                     }) = self.images.get(&item.image_key)
                     {
@@ -859,22 +858,22 @@ impl YamlFrameWriter {
                         ImageRendering::CrispEdges => str_node(&mut v, "rendering", "crisp-edges"),
                         ImageRendering::Pixelated => str_node(&mut v, "rendering", "pixelated"),
                     };
                     match item.alpha_type {
                         AlphaType::PremultipliedAlpha => str_node(&mut v, "alpha-type", "premultiplied-alpha"),
                         AlphaType::Alpha => str_node(&mut v, "alpha-type", "alpha"),
                     };
                 }
-                YuvImage(_) => {
+                Sdi::YuvImage(_) => {
                     str_node(&mut v, "type", "yuv-image");
                     // TODO
                     println!("TODO YAML YuvImage");
                 }
-                Border(item) => {
+                Sdi::Border(item) => {
                     str_node(&mut v, "type", "border");
                     match item.details {
                         BorderDetails::Normal(ref details) => {
                             let trbl =
                                 vec![&details.top, &details.right, &details.bottom, &details.left];
                             let widths: Vec<f32> = vec![
                                 item.widths.top,
                                 item.widths.right,
@@ -979,33 +978,33 @@ impl YamlFrameWriter {
                                 }
                                 RepeatMode::Repeat => str_node(&mut v, "repeat-vertical", "repeat"),
                                 RepeatMode::Round => str_node(&mut v, "repeat-vertical", "round"),
                                 RepeatMode::Space => str_node(&mut v, "repeat-vertical", "space"),
                             };
                         }
                     }
                 }
-                BoxShadow(item) => {
+                Sdi::BoxShadow(item) => {
                     str_node(&mut v, "type", "box-shadow");
                     rect_node(&mut v, "box-bounds", &item.box_bounds);
                     vector_node(&mut v, "offset", &item.offset);
                     color_node(&mut v, "color", item.color);
                     f32_node(&mut v, "blur-radius", item.blur_radius);
                     f32_node(&mut v, "spread-radius", item.spread_radius);
                     if let Some(radius_node) = maybe_radius_yaml(&item.border_radius) {
                         yaml_node(&mut v, "border-radius", radius_node);
                     }
                     let clip_mode = match item.clip_mode {
                         BoxShadowClipMode::Outset => "outset",
                         BoxShadowClipMode::Inset => "inset",
                     };
                     str_node(&mut v, "clip-mode", clip_mode);
                 }
-                Gradient(item) => {
+                Sdi::Gradient(item) => {
                     str_node(&mut v, "type", "gradient");
                     point_node(&mut v, "start", &item.gradient.start_point);
                     point_node(&mut v, "end", &item.gradient.end_point);
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
                     let mut stops = vec![];
                     for stop in display_list.get(base.gradient_stops()) {
                         stops.push(Yaml::Real(stop.offset.to_string()));
@@ -1013,115 +1012,114 @@ impl YamlFrameWriter {
                     }
                     yaml_node(&mut v, "stops", Yaml::Array(stops));
                     bool_node(
                         &mut v,
                         "repeat",
                         item.gradient.extend_mode == ExtendMode::Repeat,
                     );
                 }
-                RadialGradient(item) => {
+                Sdi::RadialGradient(item) => {
                     str_node(&mut v, "type", "radial-gradient");
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
                     radial_gradient_to_yaml(
                         &mut v,
                         &item.gradient,
                         base.gradient_stops(),
                         display_list
                     );
                 }
-                Iframe(item) => {
+                Sdi::Iframe(item) => {
                     str_node(&mut v, "type", "iframe");
                     u32_vec_node(&mut v, "id", &[item.pipeline_id.0, item.pipeline_id.1]);
                     bool_node(&mut v, "ignore_missing_pipeline", item.ignore_missing_pipeline);
                 }
-                PushStackingContext(item) => {
+                Sdi::PushStackingContext(item) => {
                     str_node(&mut v, "type", "stacking-context");
                     let filters = display_list.get(base.filters());
                     write_stacking_context(
                         &mut v,
                         &item.stacking_context,
                         &scene.properties,
                         filters,
+                    );
+
+                    let mut sub_iter = base.sub_iter();
+                    self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
+                    continue_traversal = Some(sub_iter);
+                }
+                Sdi::PushReferenceFrame(item) => {
+                    str_node(&mut v, "type", "reference-frame");
+                    write_reference_frame(
+                        &mut v,
+                        &item.reference_frame,
+                        &scene.properties,
                         clip_id_mapper,
                     );
 
                     let mut sub_iter = base.sub_iter();
                     self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
                     continue_traversal = Some(sub_iter);
                 }
-                PushReferenceFrame(item) => {
-                    str_node(&mut v, "type", "reference-frame");
-                    write_reference_frame(
-                        &mut v,
-                        &item.reference_frame,
-                        &scene.properties,
-                        clip_id_mapper
-                    );
-
-                    let mut sub_iter = base.sub_iter();
-                    self.write_display_list(&mut v, display_list, scene, &mut sub_iter, clip_id_mapper);
-                    continue_traversal = Some(sub_iter);
-                }
-                Clip(item) => {
+                Sdi::Clip(item) => {
                     str_node(&mut v, "type", "clip");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_clip_id(item.id));
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
                         complex_clip_count,
                         complex_clips,
                         display_list,
                     ) {
                         yaml_node(&mut v, "complex", complex);
                     }
 
                     if let Some(mask_yaml) = self.make_clip_mask_image_node(&item.image_mask) {
                         yaml_node(&mut v, "image-mask", mask_yaml);
                     }
                 }
-                ClipChain(item) => {
+                Sdi::ClipChain(item) => {
                     str_node(&mut v, "type", "clip-chain");
 
                     let id = ClipId::ClipChain(item.id);
-                    u32_node(&mut v, "id", clip_id_mapper.add_id(id) as u32);
+                    u32_node(&mut v, "id", clip_id_mapper.add_clip_id(id) as u32);
 
                     let clip_ids = display_list.get(base.clip_chain_items()).map(|clip_id| {
-                        clip_id_mapper.map_id(&clip_id)
+                        clip_id_mapper.map_clip_id(&clip_id)
                     }).collect();
                     yaml_node(&mut v, "clips", Yaml::Array(clip_ids));
 
                     if let Some(parent) = item.parent {
                         let parent = ClipId::ClipChain(parent);
-                        yaml_node(&mut v, "parent", clip_id_mapper.map_id(&parent));
+                        yaml_node(&mut v, "parent", clip_id_mapper.map_clip_id(&parent));
                     }
                 }
-                ScrollFrame(item) => {
+                Sdi::ScrollFrame(item) => {
                     str_node(&mut v, "type", "scroll-frame");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.scroll_frame_id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_spatial_id(item.scroll_frame_id));
                     size_node(&mut v, "content-size", &base.rect().size);
                     rect_node(&mut v, "bounds", &base.clip_rect());
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
                         complex_clip_count,
                         complex_clips,
                         display_list,
                     ) {
                         yaml_node(&mut v, "complex", complex);
                     }
 
                     if let Some(mask_yaml) = self.make_clip_mask_image_node(&item.image_mask) {
                         yaml_node(&mut v, "image-mask", mask_yaml);
                     }
                 }
-                StickyFrame(item) => {
+                Sdi::StickyFrame(item) => {
                     str_node(&mut v, "type", "sticky-frame");
-                    usize_node(&mut v, "id", clip_id_mapper.add_id(item.id));
+                    usize_node(&mut v, "id", clip_id_mapper.add_spatial_id(item.id));
                     rect_node(&mut v, "bounds", &base.clip_rect());
 
                     if let Some(margin) = item.margins.top {
                         f32_node(&mut v, "margin-top", margin);
                     }
                     if let Some(margin) = item.margins.bottom {
                         f32_node(&mut v, "margin-bottom", margin);
                     }
@@ -1146,31 +1144,32 @@ impl YamlFrameWriter {
 
                     let applied = vec![
                         Yaml::Real(item.previously_applied_offset.x.to_string()),
                         Yaml::Real(item.previously_applied_offset.y.to_string()),
                     ];
                     yaml_node(&mut v, "previously-applied-offset", Yaml::Array(applied));
                 }
 
-                PopCacheMarker => return,
-                PushCacheMarker(_) => {
+                Sdi::PopReferenceFrame |
+                Sdi::PopStackingContext => return,
+
+                Sdi::PopCacheMarker => return,
+                Sdi::PushCacheMarker(_) => {
                     str_node(&mut v, "type", "cache-marker");
                 }
 
-                PopStackingContext => return,
-                PopReferenceFrame => return,
-                SetGradientStops => panic!("dummy item yielded?"),
-                PushShadow(shadow) => {
+                Sdi::SetGradientStops => panic!("dummy item yielded?"),
+                Sdi::PushShadow(shadow) => {
                     str_node(&mut v, "type", "shadow");
                     vector_node(&mut v, "offset", &shadow.offset);
                     color_node(&mut v, "color", shadow.color);
                     f32_node(&mut v, "blur-radius", shadow.blur_radius);
                 }
-                PopAllShadows => {
+                Sdi::PopAllShadows => {
                     str_node(&mut v, "type", "pop-all-shadows");
                 }
             }
             if !v.is_empty() {
                 list.push(Yaml::Hash(v));
             }
         }
     }
@@ -1242,50 +1241,69 @@ impl webrender::ApiRecordingReceiver for
         if self.frame_writer.dl_descriptor.is_some() {
             self.frame_writer
                 .finish_write_display_list(&mut self.scene, data);
         }
     }
 }
 
 /// This structure allows mapping both `Clip` and `ClipExternalId`
-/// `ClipIds` onto one set of numeric ids. This prevents ids
-/// from clashing in the yaml output.
+/// `ClipIds` onto one set of numeric ids. It also handles `SpatialId`
+/// in a separate map. This prevents ids from clashing in the yaml output.
 struct ClipIdMapper {
-    hash_map: HashMap<ClipId, usize>,
+    clip_map: HashMap<ClipId, usize>,
+    spatial_map: HashMap<SpatialId, usize>,
     current_clip_id: usize,
+    current_spatial_id: usize,
 }
 
 impl ClipIdMapper {
-    fn new() -> ClipIdMapper {
+    fn new() -> Self {
         ClipIdMapper {
-            hash_map: HashMap::new(),
-            current_clip_id: 2,
+            clip_map: HashMap::new(),
+            spatial_map: HashMap::new(),
+            current_clip_id: 1, // see FIRST_CLIP_NODE_INDEX
+            current_spatial_id: 2, // see FIRST_SPATIAL_NODE_INDEX
         }
     }
 
-    fn add_id(&mut self, id: ClipId) -> usize {
-        self.hash_map.insert(id, self.current_clip_id);
+    fn add_clip_id(&mut self, id: ClipId) -> usize {
+        self.clip_map.insert(id, self.current_clip_id);
         self.current_clip_id += 1;
         self.current_clip_id - 1
     }
 
-    fn map_id(&self, id: &ClipId) -> Yaml {
+    fn add_spatial_id(&mut self, id: SpatialId) -> usize {
+        self.spatial_map.insert(id, self.current_spatial_id);
+        self.current_spatial_id += 1;
+        self.current_spatial_id - 1
+    }
+
+    fn map_spatial_id(&self, id: &SpatialId) -> Yaml {
         if id.is_root_reference_frame() {
             Yaml::String("root-reference-frame".to_owned())
         } else if id.is_root_scroll_node() {
             Yaml::String("root-scroll-node".to_owned())
         } else {
-            Yaml::Integer(*self.hash_map.get(id).unwrap() as i64)
+            Yaml::Integer(self.spatial_map[id] as i64)
         }
     }
 
-    fn map_info(&self, info: &ClipAndScrollInfo) -> Yaml {
-        let scroll_node_yaml = self.map_id(&info.scroll_node_id);
-        match info.clip_node_id {
+    fn map_clip_id(&self, id: &ClipId) -> Yaml {
+        assert!(id.is_valid());
+        if id.is_root() {
+            Yaml::String("root_clip".to_owned())
+        } else {
+            Yaml::Integer(self.clip_map[id] as i64)
+        }
+    }
+
+    fn map_clip_and_scroll_ids(&self, clip_id: Option<ClipId>, spatial_id: SpatialId) -> Yaml {
+        let scroll_node_yaml = self.map_spatial_id(&spatial_id);
+        match clip_id {
             Some(ref clip_node_id) => Yaml::Array(vec![
                 scroll_node_yaml,
-                self.map_id(&clip_node_id)
+                self.map_clip_id(&clip_node_id)
             ]),
             None => scroll_node_yaml,
         }
     }
 }
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -480,22 +480,17 @@ bool BulletRenderer::CreateWebRenderComm
       wr::BorderSide sides[4] = {side, side, side, side};
       Range<const wr::BorderSide> sidesRange(sides, 4);
       aBuilder.PushBorder(dest, dest, isBackfaceVisible, borderWidths,
                           sidesRange,
                           wr::ToBorderRadius(radii, radii, radii, radii));
       return true;
     }
     case NS_STYLE_LIST_STYLE_DISC: {
-      AutoTArray<wr::ComplexClipRegion, 1> clips;
-      clips.AppendElement(wr::SimpleRadii(dest, dest.size.width / 2));
-      auto clipId = aBuilder.DefineClip(Nothing(), dest, &clips, nullptr);
-      aBuilder.PushClip(clipId);
-      aBuilder.PushRect(dest, dest, isBackfaceVisible, color);
-      aBuilder.PopClip();
+      aBuilder.PushRoundedRect(dest, dest, isBackfaceVisible, color);
       return true;
     }
     case NS_STYLE_LIST_STYLE_SQUARE: {
       aBuilder.PushRect(dest, dest, isBackfaceVisible, color);
       return true;
     }
     default:
       if (!aManager->CommandBuilder().PushItemAsImage(
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1661,22 +1661,17 @@ ImgDrawResult nsImageFrame::DisplayAltFe
       // filled circle in bottom right quadrant of stroked rect:
       nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
       rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
                     size / 2 - twoPX);
       devPxRect = LayoutDeviceRect::FromAppUnits(
           rect, PresContext()->AppUnitsPerDevPixel());
       dest = wr::ToRoundedLayoutRect(devPxRect);
 
-      AutoTArray<wr::ComplexClipRegion, 1> clips;
-      clips.AppendElement(wr::SimpleRadii(dest, dest.size.width / 2));
-      auto clipId = aBuilder.DefineClip(Nothing(), dest, &clips, nullptr);
-      aBuilder.PushClip(clipId);
-      aBuilder.PushRect(dest, wrBounds, isBackfaceVisible, color);
-      aBuilder.PopClip();
+      aBuilder.PushRoundedRect(dest, wrBounds, isBackfaceVisible, color);
     }
 
     // Reduce the inner rect by the width of the icon, and leave an
     // additional ICON_PADDING pixels for padding
     int32_t paddedIconSize =
         nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
     if (wm.IsVertical()) {
       inner.y += paddedIconSize;
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -3297,45 +3297,40 @@ void nsCSSBorderRenderer::DrawBorders() 
 }
 
 void nsCSSBorderRenderer::CreateWebRenderCommands(
     nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
     wr::IpcResourceUpdateQueue& aResources,
     const layers::StackingContextHelper& aSc) {
   LayoutDeviceRect outerRect = LayoutDeviceRect::FromUnknownRect(mOuterRect);
   wr::LayoutRect roundedRect = wr::ToRoundedLayoutRect(outerRect);
+  wr::LayoutRect clipRect = roundedRect;
   wr::BorderSide side[4];
   NS_FOR_CSS_SIDES(i) {
     side[i] =
         wr::ToBorderSide(ToDeviceColor(mBorderColors[i]), mBorderStyles[i]);
   }
 
   wr::BorderRadius borderRadius =
       wr::ToBorderRadius(LayoutDeviceSize::FromUnknownSize(mBorderRadii[0]),
                          LayoutDeviceSize::FromUnknownSize(mBorderRadii[1]),
                          LayoutDeviceSize::FromUnknownSize(mBorderRadii[3]),
                          LayoutDeviceSize::FromUnknownSize(mBorderRadii[2]));
 
   if (mLocalClip) {
-    LayoutDeviceRect clip =
+    LayoutDeviceRect localClip =
         LayoutDeviceRect::FromUnknownRect(mLocalClip.value());
-    wr::LayoutRect clipRect = wr::ToRoundedLayoutRect(clip);
-    wr::WrClipId clipId = aBuilder.DefineClip(Nothing(), clipRect);
-    aBuilder.PushClip(clipId);
+    clipRect = wr::ToRoundedLayoutRect(localClip.Intersect(outerRect));
   }
 
   Range<const wr::BorderSide> wrsides(side, 4);
-  aBuilder.PushBorder(roundedRect, roundedRect, mBackfaceIsVisible,
+  aBuilder.PushBorder(roundedRect, clipRect, mBackfaceIsVisible,
                       wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1],
                                          mBorderWidths[2], mBorderWidths[3]),
                       wrsides, borderRadius);
-
-  if (mLocalClip) {
-    aBuilder.PopClip();
-  }
 }
 
 /* static */ Maybe<nsCSSBorderImageRenderer>
 nsCSSBorderImageRenderer::CreateBorderImageRenderer(
     nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
     const nsStyleBorder& aStyleBorder, const nsRect& aDirtyRect,
     Sides aSkipSides, uint32_t aFlags, ImgDrawResult* aDrawResult) {
   MOZ_ASSERT(aDrawResult);
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6875,16 +6875,18 @@ bool nsDisplayStickyPosition::CreateWebR
     // the sticky items correctly on the gecko side and WR will never need to
     // adjust their position itself.
     if (!stickyScrollContainer->ScrollFrame()
              ->IsMaybeAsynchronouslyScrolled()) {
       stickyScrollContainer = nullptr;
     }
   }
 
+  Maybe<wr::SpaceAndClipChainHelper> saccHelper;
+
   if (stickyScrollContainer) {
     float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
 
     bool snap;
     nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap);
 
     Maybe<float> topMargin;
     Maybe<float> rightMargin;
@@ -6992,35 +6994,34 @@ bool nsDisplayStickyPosition::CreateWebR
       }
     }
 
     LayoutDeviceRect bounds =
         LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
     wr::LayoutVector2D applied = {
         NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
         NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)};
-    wr::WrClipId id = aBuilder.DefineStickyFrame(
+    wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame(
         wr::ToRoundedLayoutRect(bounds), topMargin.ptrOr(nullptr),
         rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr),
         leftMargin.ptrOr(nullptr), vBounds, hBounds, applied);
 
-    aBuilder.PushClip(id);
-    aManager->CommandBuilder().PushOverrideForASR(mContainerASR, id);
+    saccHelper.emplace(aBuilder, spatialId);
+    aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId);
   }
 
   {
     StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this,
                              aBuilder);
     nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc,
                                                aManager, aDisplayListBuilder);
   }
 
   if (stickyScrollContainer) {
     aManager->CommandBuilder().PopOverrideForASR(mContainerASR);
-    aBuilder.PopClip();
   }
 
   return true;
 }
 
 nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
     nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame,
     nsIFrame* aScrollFrame)
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -62,20 +62,25 @@ relativesrcdir dom/locales:
   locale/@AB_CD@/browser/overrides/dom/dom.properties          (%chrome/dom/dom.properties)
 #about:plugins
   locale/@AB_CD@/browser/overrides/plugins.properties          (%chrome/plugins.properties)
 
 # Fluent files
 # Note: All rules must be wildcards, as localized files are optional
 # Only run this if we're not en-US, as en-US is already built
 # by toolkit/locales/jar.mn.
+# If you're including files from a subdirectory, ensure that you're
+# putting them into the corresponding subdirectory in the target.
+# The wildcard ** does that for us in toolkit.
 
 #define EN_US en-US
 #if AB_CD != EN_US
 [localization] @AB_CD@.jar:
 relativesrcdir toolkit/locales:
 #about:crashes
   crashreporter                                    (%crashreporter/**/*.ftl)
+#about:about
+  toolkit/about                                    (%toolkit/about/*About.ftl)
+#about:support
+  toolkit/about                                    (%toolkit/about/*Support.ftl)
 #endif
-#about:about
-  toolkit                                          (%toolkit/about/*About.ftl)
-#about:support
-  toolkit                                          (%toolkit/about/*Support.ftl)
+# Do not add files below the endif. Reviewers, expand more context above
+# for comments.
--- a/services/settings/remote-settings.js
+++ b/services/settings/remote-settings.js
@@ -61,17 +61,17 @@ XPCOMUtils.defineLazyPreferenceGetter(th
 async function jexlFilterFunc(entry, environment) {
   const { filter_expression } = entry;
   if (!filter_expression) {
     return entry;
   }
   let result;
   try {
     const context = {
-      environment,
+      env: environment,
     };
     result = await FilterExpressions.eval(filter_expression, context);
   } catch (e) {
     Cu.reportError(e);
   }
   return result ? entry : null;
 }
 
--- a/services/settings/test/unit/test_remote_settings_jexl_filters.js
+++ b/services/settings/test/unit/test_remote_settings_jexl_filters.js
@@ -51,23 +51,23 @@ add_task(async function test_returns_ent
   }, {
     willMatch: true,
     filter_expression: "[42]",
   }, {
     willMatch: true,
     filter_expression: "1 == 2 || 1 == 1",
   }, {
     willMatch: true,
-    filter_expression: 'environment.appID == "xpcshell@tests.mozilla.org"',
+    filter_expression: 'env.appID == "xpcshell@tests.mozilla.org"',
   }, {
     willMatch: false,
-    filter_expression: "environment.version == undefined",
+    filter_expression: "env.version == undefined",
   }, {
     willMatch: true,
-    filter_expression: "environment.unknown == undefined",
+    filter_expression: "env.unknown == undefined",
   }, {
     willMatch: false,
     filter_expression: "1 == 2",
   }]);
 
   const list = await client.get();
   equal(list.length, 5);
   ok(list.every(e => e.willMatch));
--- a/toolkit/components/normandy/lib/RecipeRunner.jsm
+++ b/toolkit/components/normandy/lib/RecipeRunner.jsm
@@ -279,17 +279,17 @@ var RecipeRunner = {
 
   getFilterContext(recipe) {
     const environment = cacheProxy(ClientEnvironment);
     environment.recipe = {
       id: recipe.id,
       arguments: recipe.arguments,
     };
     return {
-      environment,
+      env: environment,
       // Backwards compatibility -- see bug 1477255.
       normandy: environment,
     };
   },
 
   /**
    * Evaluate a recipe's filter expression against the environment.
    * @param {object} recipe
--- a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
+++ b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
@@ -32,39 +32,39 @@ add_task(async function getFilterContext
     "syncMobileDevices",
     "syncSetup",
     "syncTotalDevices",
     "telemetry",
     "userId",
     "version",
   ];
   for (const key of expectedNormandyKeys) {
-    ok(key in context.environment, `environment.${key} is available`);
+    ok(key in context.env, `env.${key} is available`);
     ok(key in context.normandy, `normandy.${key} is available`);
   }
-  Assert.deepEqual(context.normandy, context.environment,
+  Assert.deepEqual(context.normandy, context.env,
                    "context offers normandy as backwards-compatible alias for context.environment");
 
   is(
-    context.environment.recipe.id,
+    context.env.recipe.id,
     recipe.id,
     "environment.recipe is the recipe passed to getFilterContext",
   );
   delete recipe.unrelated;
   Assert.deepEqual(
-    context.environment.recipe,
+    context.env.recipe,
     recipe,
     "environment.recipe drops unrecognized attributes from the recipe",
   );
 
   // Filter context attributes are cached.
   await SpecialPowers.pushPrefEnv({set: [["app.normandy.user_id", "some id"]]});
-  is(context.environment.userId, "some id", "User id is read from prefs when accessed");
+  is(context.env.userId, "some id", "User id is read from prefs when accessed");
   await SpecialPowers.pushPrefEnv({set: [["app.normandy.user_id", "real id"]]});
-  is(context.environment.userId, "some id", "userId was cached");
+  is(context.env.userId, "some id", "userId was cached");
 
 });
 
 add_task(async function checkFilter() {
   const check = filter => RecipeRunner.checkFilter({filter_expression: filter});
 
   // Errors must result in a false return value.
   ok(!(await check("invalid ( + 5yntax")), "Invalid filter expressions return false");
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -3886,24 +3886,20 @@ void nsWindow::UpdateThemeGeometries(
   layerManager->SetRegionToClear(clearRegion);
 }
 
 void nsWindow::AddWindowOverlayWebRenderCommands(
     layers::WebRenderBridgeChild* aWrBridge, wr::DisplayListBuilder& aBuilder,
     wr::IpcResourceUpdateQueue& aResources) {
   if (mWindowButtonsRect) {
     wr::LayoutRect rect = wr::ToLayoutRect(*mWindowButtonsRect);
-    nsTArray<wr::ComplexClipRegion> roundedClip;
-    roundedClip.AppendElement(wr::ToComplexClipRegion(
+    auto complexRegion = wr::ToComplexClipRegion(
         RoundedRect(IntRectToRect(mWindowButtonsRect->ToUnknownRect()),
-                    RectCornerRadii(0, 0, 3, 3))));
-    wr::WrClipId clipId = aBuilder.DefineClip(Nothing(), rect, &roundedClip);
-    aBuilder.PushClip(clipId);
-    aBuilder.PushClearRect(rect);
-    aBuilder.PopClip();
+                    RectCornerRadii(0, 0, 3, 3)));
+    aBuilder.PushClearRectWithComplexRegion(rect, complexRegion);
   }
 }
 
 uint32_t nsWindow::GetMaxTouchPoints() const {
   return WinUtils::GetMaxTouchPoints();
 }
 
 void nsWindow::SetWindowClass(const nsAString& xulWinType) {