Merge inbound to mozilla-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Sun, 30 Sep 2018 12:46:16 +0300
changeset 494660 7e9ab0e7b608
parent 494645 c3d7f8a2a6d4 (current diff)
parent 494659 3920c858319d (diff)
child 494661 021a82ef604b
child 494662 463e670ab9dd
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
7e9ab0e7b608 / 64.0a1 / 20180930100113 / files
nightly linux64
7e9ab0e7b608 / 64.0a1 / 20180930100113 / files
nightly mac
7e9ab0e7b608 / 64.0a1 / 20180930100113 / files
nightly win32
7e9ab0e7b608 / 64.0a1 / 20180930100113 / files
nightly win64
7e9ab0e7b608 / 64.0a1 / 20180930100113 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central a=merge
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5434,23 +5434,17 @@ CanvasRenderingContext2D::GetImageData(J
   if (!mCanvasElement && !mDocShell) {
     NS_ERROR("No canvas element and no docshell in GetImageData!!!");
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Check only if we have a canvas element; if we were created with a docshell,
   // then it's special internal use.
-  if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
-      // We could ask bindings for the caller type, but they already hand us a
-      // JSContext, and we're at least _somewhat_ perf-sensitive (so may not
-      // want to compute the caller type in the common non-write-only case), so
-      // let's just use what we have.
-      !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission))
-  {
+  if (mCanvasElement && !mCanvasElement->CallerCanRead(aCx)) {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   if (!IsFinite(aSx) || !IsFinite(aSy) ||
       !IsFinite(aSw) || !IsFinite(aSh)) {
     aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -228,18 +228,19 @@ DoDrawImageSecurityCheck(dom::HTMLCanvas
                          bool CORSUsed)
 {
     // Callers should ensure that mCanvasElement is non-null before calling this
     if (!aCanvasElement) {
         NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
         return;
     }
 
-    if (aCanvasElement->IsWriteOnly())
+    if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
         return;
+    }
 
     // If we explicitly set WriteOnly just do it and get out
     if (forceWriteOnly) {
         aCanvasElement->SetWriteOnly();
         return;
     }
 
     // No need to do a security check if the image used CORS for the load
@@ -248,16 +249,35 @@ DoDrawImageSecurityCheck(dom::HTMLCanvas
 
     MOZ_ASSERT(aPrincipal, "Must have a principal here");
 
     if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) {
         // This canvas has access to that image anyway
         return;
     }
 
+    if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
+        // This is a resource from an extension content script principal.
+
+        if (aCanvasElement->mExpandedReader &&
+            aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) {
+            // This canvas already allows reading from this principal.
+            return;
+        }
+
+        if (!aCanvasElement->mExpandedReader) {
+            // Allow future reads from this same princial only.
+            aCanvasElement->SetWriteOnly(aPrincipal);
+            return;
+        }
+
+        // If we got here, this must be the *second* extension tainting
+        // the canvas.  Fall through to mark it WriteOnly for everyone.
+    }
+
     aCanvasElement->SetWriteOnly();
 }
 
 bool
 CoerceDouble(const JS::Value& v, double* d)
 {
     if (v.isDouble()) {
         *d = v.toDouble();
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -665,19 +665,18 @@ HTMLCanvasElement::ParseAttribute(int32_
 
 void
 HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
                              JS::Handle<JS::Value> aParams,
                              nsAString& aDataURL,
                              nsIPrincipal& aSubjectPrincipal,
                              ErrorResult& aRv)
 {
-  // do a trust check if this is a write-only canvas
-  if (mWriteOnly &&
-      !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
+  // mWriteOnly check is redundant, but optimizes for the common case.
+  if (mWriteOnly && !CallerCanRead(aCx)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   aRv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, aDataURL);
 }
 
 void
@@ -876,19 +875,18 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
 void
 HTMLCanvasElement::ToBlob(JSContext* aCx,
                           BlobCallback& aCallback,
                           const nsAString& aType,
                           JS::Handle<JS::Value> aParams,
                           nsIPrincipal& aSubjectPrincipal,
                           ErrorResult& aRv)
 {
-  // do a trust check if this is a write-only canvas
-  if (mWriteOnly &&
-      !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
+  // mWriteOnly check is redundant, but optimizes for the common case.
+  if (mWriteOnly && !CallerCanRead(aCx)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
   MOZ_ASSERT(global);
 
   nsIntSize elemSize = GetWidthHeight();
@@ -1088,20 +1086,47 @@ bool
 HTMLCanvasElement::IsWriteOnly()
 {
   return mWriteOnly;
 }
 
 void
 HTMLCanvasElement::SetWriteOnly()
 {
+  mExpandedReader = nullptr;
   mWriteOnly = true;
 }
 
 void
+HTMLCanvasElement::SetWriteOnly(nsIPrincipal* aExpandedReader)
+{
+  mExpandedReader = aExpandedReader;
+  mWriteOnly = true;
+}
+
+bool
+HTMLCanvasElement::CallerCanRead(JSContext* aCx)
+{
+  if (!mWriteOnly) {
+    return true;
+  }
+
+  nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(aCx);
+
+  // If mExpandedReader is set, this canvas was tainted only by
+  // mExpandedReader's resources. So allow reading if the subject
+  // principal subsumes mExpandedReader.
+  if (mExpandedReader && prin->Subsumes(mExpandedReader)) {
+    return true;
+  }
+
+  return nsContentUtils::PrincipalHasPermission(prin, nsGkAtoms::all_urlsPermission);
+}
+
+void
 HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect)
 {
   // We don't need to flush anything here; if there's no frame or if
   // we plan to reframe we don't need to invalidate it anyway.
   nsIFrame *frame = GetPrimaryFrame();
   if (!frame)
     return;
 
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -226,16 +226,22 @@ public:
   bool IsWriteOnly();
 
   /**
    * Force the canvas to be write-only.
    */
   void SetWriteOnly();
 
   /**
+   * Force the canvas to be write-only, except for readers from
+   * a specific extension's content script expanded principal.
+   */
+  void SetWriteOnly(nsIPrincipal* aExpandedReader);
+
+  /**
    * Notify that some canvas content has changed and the window may
    * need to be updated. aDamageRect is in canvas coordinates.
    */
   void InvalidateCanvasContent(const mozilla::gfx::Rect* aDamageRect);
   /*
    * Notify that we need to repaint the entire canvas, including updating of
    * the layer tree.
    */
@@ -390,18 +396,25 @@ protected:
   RefPtr<OffscreenCanvas> mOffscreenCanvas;
   RefPtr<HTMLCanvasElementObserver> mContextObserver;
 
 public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
   // We also transitively set it when script paints a canvas which
   // is itself write-only.
-  bool                     mWriteOnly;
+  bool mWriteOnly;
 
+  // When this canvas is (only) tainted by an image from an extension
+  // content script, allow reads from the same extension afterwards.
+  RefPtr<nsIPrincipal> mExpandedReader;
+
+  // Determines if the caller should be able to read the content.
+  bool CallerCanRead(JSContext* aCx);
+  
   bool IsPrintCallbackDone();
 
   void HandlePrintCallback(nsPresContext::nsPresContextType aType);
 
   nsresult DispatchPrintCallback(nsITimerCallback* aCallback);
 
   void ResetPrintCallback();
 
--- a/dom/performance/tests/test_worker_performance_now.html
+++ b/dom/performance/tests/test_worker_performance_now.html
@@ -18,20 +18,14 @@ SpecialPowers.setBoolPref("privacy.reduc
 var worker = new Worker('test_worker_performance_now.js');
 worker.onmessage = function(event) {
   if (event.data.type == 'finish') {
     SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
     SimpleTest.finish();
 
   } else if (event.data.type == 'status') {
     ok(event.data.status, event.data.msg);
-
-  } else if (event.data.type == 'getOSCPU') {
-    worker.postMessage({
-      type: 'returnOSCPU',
-      result: navigator.oscpu
-    });
   }
 }
 
 </script>
 </body>
 </html>
--- a/dom/performance/tests/test_worker_performance_now.js
+++ b/dom/performance/tests/test_worker_performance_now.js
@@ -2,75 +2,56 @@ function ok(a, msg) {
   dump("OK: " + !!a + "  =>  " + a + ": " + msg + "\n");
   postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
 }
 
 function workerTestDone() {
   postMessage({ type: 'finish' });
 }
 
-function workerTestGetOSCPU(cb) {
-  addEventListener('message', function workerTestGetOSCPUCB(e) {
-    if (e.data.type !== 'returnOSCPU') {
-      return;
-    }
-    removeEventListener('message', workerTestGetOSCPUCB);
-    cb(e.data.result);
-  });
-  postMessage({
-    type: 'getOSCPU'
-  });
-}
-
 ok(self.performance, "Performance object should exist.");
 ok(typeof self.performance.now == 'function', "Performance object should have a 'now' method.");
 var n = self.performance.now(), d = Date.now();
 ok(n >= 0, "The value of now() should be equal to or greater than 0.");
 ok(self.performance.now() >= n, "The value of now() should monotonically increase.");
 
-// The spec says performance.now() should have micro-second resolution, but allows 1ms if the platform doesn't support it.
-// Our implementation does provide micro-second resolution, except for windows XP combined with some HW properties
-// where we can't use QueryPerformanceCounters (see comments at mozilla-central/xpcom/ds/TimeStamp_windows.cpp).
-// This XP-low-res case results in about 15ms resolutions, and can be identified when perf.now() returns only integers.
-//
-// Since setTimeout might return too early/late, our goal is that perf.now() changed within 2ms
-// (or 25ms for XP-low-res), rather than specific number of setTimeout(N) invocations.
+// Spin on setTimeout() until performance.now() increases. Due to recent
+// security developments, the hr-time working group has not yet reached
+// consensus on what the recommend minimum clock resolution should be:
+// https://w3c.github.io/hr-time/#clock-resolution
+// Since setTimeout might return too early/late, our goal is for
+// performance.now() to increase before a 2 ms deadline rather than specific
+// number of setTimeout(N) invocations.
 // See bug 749894 (intermittent failures of this test)
-var platformPossiblyLowRes;
-workerTestGetOSCPU(function(oscpu) {
-    platformPossiblyLowRes = oscpu.indexOf("Windows NT 5.1") == 0; // XP only
-    setTimeout(checkAfterTimeout, 1);
-});
-var allInts = (n % 1) == 0; // Indicator of limited HW resolution.
+setTimeout(checkAfterTimeout, 1);
+
 var checks = 0;
 
 function checkAfterTimeout() {
   checks++;
   var d2 = Date.now();
   var n2 = self.performance.now();
 
-  allInts = allInts && (n2 % 1) == 0;
-  var lowResCounter = platformPossiblyLowRes && allInts;
-
-  if ( n2 == n && checks < 50 && // 50 is just a failsafe. Our real goals are 2ms or 25ms.
-       ( (d2 - d) < 2 // The spec allows 1ms resolution. We allow up to measured 2ms to ellapse.
-         ||
-         lowResCounter &&
-         (d2 - d) < 25
-       )
-     ) {
+  // Spin on setTimeout() until performance.now() increases. Abort the test
+  // if it runs for more than 2 ms or 50 timeouts.
+  let elapsedTime = d2 - d;
+  let elapsedPerf = n2 - n;
+  if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) {
     setTimeout(checkAfterTimeout, 1);
     return;
   }
 
-  // Loose spec: 1ms resolution, or 15ms resolution for the XP-low-res case.
-  // We shouldn't test that dt is actually within 2/25ms since the iterations break if it isn't, and timeout could be late.
-  ok(n2 > n, "Loose - the value of now() should increase within 2ms (or 25ms if low-res counter) (delta now(): " + (n2 - n) + " ms).");
+  // Our implementation provides 1 ms resolution (bug 1451790), but we
+  // can't assert that elapsedPerf >= 1 ms because this worker test runs with
+  // "privacy.reduceTimerPrecision" == false so performance.now() is not
+  // limited to 1 ms resolution.
+  ok(elapsedPerf > 0,
+     `Loose - the value of now() should increase after 2ms. ` +
+     `delta now(): ${elapsedPerf} ms`);
 
-  // Strict spec: if it's not the XP-low-res case, while the spec allows 1ms resolution, it prefers microseconds, which we provide.
-  // Since the fastest setTimeout return which I observed was ~500 microseconds, a microseconds counter should change in 1 iteretion.
-  ok(n2 > n && (lowResCounter || checks == 1),
-     "Strict - [if high-res counter] the value of now() should increase after one setTimeout (hi-res: " + (!lowResCounter) +
-                                                                                              ", iters: " + checks +
-                                                                                              ", dt: " + (d2 - d) +
-                                                                                              ", now(): " + n2 + ").");
+  // If we need more than 1 iteration, then either performance.now() resolution
+  // is shorter than 1 ms or setTimeout() is returning too early.
+  ok(checks == 1,
+     `Strict - the value of now() should increase after one setTimeout. ` +
+     `iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`);
+
   workerTestDone();
 };
--- a/dom/tests/mochitest/general/test_performance_now.html
+++ b/dom/tests/mochitest/general/test_performance_now.html
@@ -8,62 +8,53 @@
 </head>
 <body>
   <script>
     ok(window.performance, "Performance object should exist.");
     ok(typeof window.performance.now == 'function', "Performance object should have a 'now' method.");
     var n = window.performance.now(), d = Date.now();
     ok(n >= 0, "The value of now() should be equal to or greater than 0.");
     ok(window.performance.now() >= n, "The value of now() should monotonically increase.");
+
     SimpleTest.waitForExplicitFinish();
-    var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduceTimerPrecision");
-    SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
-    SimpleTest.requestFlakyTimeout("untriaged");
+    SimpleTest.requestFlakyTimeout("using setTimeout() to measure performance.now()");
 
-    // The spec says performance.now() should have micro-second resolution, but allows 1ms if the platform doesn't support it.
-    // Our implementation does provide micro-second resolution, except for windows XP combined with some HW properties
-    // where we can't use QueryPerformanceCounters (see comments at mozilla-central/xpcom/ds/TimeStamp_windows.cpp).
-    // This XP-low-res case results in about 15ms resolutions, and can be identified when perf.now() returns only integers.
-    //
-    // Since setTimeout might return too early/late, our goal is that perf.now() changed within 2ms
-    // (or 25ms for XP-low-res), rather than specific number of setTimeout(N) invocations.
+    // Spin on setTimeout() until performance.now() increases. Due to recent
+    // security developments, the hr-time working group has not yet reached
+    // consensus on what the recommend minimum clock resolution should be:
+    // https://w3c.github.io/hr-time/#clock-resolution
+    // Since setTimeout might return too early/late, our goal is for
+    // performance.now() to increase before a 2 ms deadline rather than specific
+    // number of setTimeout(N) invocations.
     // See bug 749894 (intermittent failures of this test)
-    var platformPossiblyLowRes = navigator.oscpu.indexOf("Windows NT 5.1") == 0; // XP only
-    var allInts = (n % 1) == 0; // Indicator of limited HW resolution.
     var checks = 0;
 
     function checkAfterTimeout() {
       checks++;
       var d2 = Date.now();
       var n2 = window.performance.now();
 
-      allInts = allInts && (n2 % 1) == 0;
-      var lowResCounter = platformPossiblyLowRes && allInts;
-
-      if ( n2 == n && checks < 50 && // 50 is just a failsafe. Our real goals are 2ms or 25ms.
-           ( (d2 - d) < 2 // The spec allows 1ms resolution. We allow up to measured 2ms to ellapse.
-             ||
-             lowResCounter &&
-             (d2 - d) < 25
-           )
-         ) {
+      // Spin on setTimeout() until performance.now() increases. Abort the
+      // test if it runs for more than 2 ms or 50 timeouts.
+      let elapsedTime = d2 - d;
+      let elapsedPerf = n2 - n;
+      if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) {
         setTimeout(checkAfterTimeout, 1);
         return;
       }
 
-      // Loose spec: 1ms resolution, or 15ms resolution for the XP-low-res case.
-      // We shouldn't test that dt is actually within 2/25ms since the iterations break if it isn't, and timeout could be late.
-      ok(n2 > n, "Loose - the value of now() should increase within 2ms (or 25ms if low-res counter) (delta now(): " + (n2 - n) + " ms).");
+      // Our implementation provides 1 ms resolution (bug 1451790).
+      ok(elapsedPerf >= 1,
+         `Loose - the value of now() should increase by no less than 1 ms ` +
+         `after 2 ms. delta now(): ${elapsedPerf} ms`);
 
-      // Strict spec: if it's not the XP-low-res case, while the spec allows 1ms resolution, it prefers microseconds, which we provide.
-      // Since the fastest setTimeout return which I observed was ~500 microseconds, a microseconds counter should change in 1 iteretion.
-      ok(n2 > n && (lowResCounter || checks == 1),
-         "Strict - [if high-res counter] the value of now() should increase after one setTimeout (hi-res: " + (!lowResCounter) +
-                                                                                                  ", iters: " + checks +
-                                                                                                  ", dt: " + (d2 - d) +
-                                                                                                  ", now(): " + n2 + ").");
-      SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", reduceTimePrecisionPrevPrefValue);
+      // If we need more than 1 iteration, then either performance.now()
+      // resolution is shorter than 1 ms or setTimeout() is returning too early.
+      ok(checks == 1,
+         `Strict - the value of now() should increase after one setTimeout. ` +
+         `iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`);
+
       SimpleTest.finish();
     };
     setTimeout(checkAfterTimeout, 1);
   </script>
 </body>
 </html>
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -134,17 +134,17 @@ struct OpUpdateImage {
 struct OpUpdateBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   ImageKey key;
   ImageIntRect dirtyRect;
 };
 
 struct OpSetImageVisibleArea {
-  Rect area;
+  ImageIntRect area;
   ImageKey key;
 };
 
 struct OpUpdateExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
   ImageIntRect dirtyRect;
 };
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -323,17 +323,18 @@ void
 IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
                                             wr::ImageKey aKey,
                                             ImageIntRect aDirtyRect)
 {
   mUpdates.AppendElement(layers::OpUpdateExternalImage(aExtId, aKey, aDirtyRect));
 }
 
 void
-IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey, const gfx::Rect& aArea)
+IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey,
+                                            const ImageIntRect& aArea)
 {
   mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
 }
 
 void
 IpcResourceUpdateQueue::DeleteImage(ImageKey aKey)
 {
   mUpdates.AppendElement(layers::OpDeleteImage(aKey));
--- a/gfx/layers/wr/IpcResourceUpdateQueue.h
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -98,17 +98,17 @@ public:
                        const ImageDescriptor& aDescriptor,
                        Range<uint8_t> aBytes,
                        ImageIntRect aDirtyRect);
 
   void UpdateExternalImage(ExternalImageId aExtID,
                            ImageKey aKey,
                            ImageIntRect aDirtyRect);
 
-  void SetImageVisibleArea(ImageKey aKey, const gfx::Rect& aArea);
+  void SetImageVisibleArea(ImageKey aKey, const ImageIntRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
   bool AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
 
   bool AddFontDescriptor(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
 
   void DeleteFont(wr::FontKey aKey);
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -338,17 +338,17 @@ WebRenderBridgeParent::UpdateResources(c
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
         aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToDeviceUintRect(op.dirtyRect()));
         break;
       }
       case OpUpdateResource::TOpSetImageVisibleArea: {
         const auto& op = cmd.get_OpSetImageVisibleArea();
-        wr::NormalizedRect area;
+        wr::DeviceUintRect area;
         area.origin.x = op.area().x;
         area.origin.y = op.area().y;
         area.size.width = op.area().width;
         area.size.height = op.area().height;
         aUpdates.SetImageVisibleArea(op.key(), area);
         break;
       }
       case OpUpdateResource::TOpAddExternalImage: {
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderCommandBuilder.h"
 
 #include "BasicLayers.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/ClipManager.h"
 #include "mozilla/layers/ImageClient.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
 #include "mozilla/layers/SharedSurfacesChild.h"
 #include "mozilla/layers/SourceSurfaceSharedData.h"
@@ -58,33 +59,16 @@ static void GP(const char *fmt, ...) {
 // and the invalid rect will be the new area
 
 struct BlobItemData;
 static void DestroyBlobGroupDataProperty(nsTArray<BlobItemData*>* aArray);
 NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty,
                                     nsTArray<BlobItemData*>,
                                     DestroyBlobGroupDataProperty);
 
-static void
-SetBlobImageVisibleArea(wr::IpcResourceUpdateQueue& aResources,
-                        wr::ImageKey aImageKey,
-                        const LayoutDeviceRect& aImageRect,
-                        const LayoutDeviceRect& aPaintRect)
-{
-  LayoutDeviceRect visibleRect = aImageRect.Intersect(aPaintRect);
-  // Send the visible rect in normalized coordinates.
-  Rect visibleArea = Rect((visibleRect.x - aImageRect.x) / aImageRect.width,
-                          (visibleRect.y - aImageRect.y) / aImageRect.height,
-                          visibleRect.width / aImageRect.width,
-                          visibleRect.height / aImageRect.height);
-
-  aResources.SetImageVisibleArea(aImageKey, visibleArea);
-}
-
-
 // These are currently manually allocated and ownership is help by the mDisplayItems
 // hash table in DIGroup
 struct BlobItemData
 {
   // a weak pointer to the frame for this item.
   // DisplayItemData has a mFrameList to deal with merged frames. Hopefully we
   // don't need to worry about that.
   nsIFrame* mFrame;
@@ -343,17 +327,17 @@ struct DIGroup
   //    The advantage of a Vec is that everything stays compact
   //    and we don't need to heap allocate the BlobItemData's
   nsTHashtable<nsPtrHashKey<BlobItemData>> mDisplayItems;
 
   nsPoint mAnimatedGeometryRootOrigin;
   nsPoint mLastAnimatedGeometryRootOrigin;
   IntRect mInvalidRect;
   nsRect mGroupBounds;
-  LayoutDeviceRect mPaintRect;
+  LayerIntRect mPaintRect;
   int32_t mAppUnitsPerDevPixel;
   gfx::Size mScale;
   FrameMetrics::ViewID mScrollId;
   LayerPoint mResidualOffset;
   LayerIntRect mLayerBounds;
   Maybe<wr::ImageKey> mKey;
   std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
   std::vector<RefPtr<ScaledFont>> mFonts;
@@ -626,17 +610,19 @@ struct DIGroup
     LayerIntRect layerBounds = mLayerBounds;
     IntSize dtSize = layerBounds.Size().ToUnknownSize();
     LayoutDeviceRect bounds = (LayerRect(layerBounds) - mResidualOffset) / scale;
 
     if (mInvalidRect.IsEmpty()) {
       GP("Not repainting group because it's empty\n");
       GP("End EndGroup\n");
       if (mKey) {
-        SetBlobImageVisibleArea(aResources, mKey.value(), bounds, mPaintRect);
+        aResources.SetImageVisibleArea(
+          mKey.value(),
+          ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
         PushImage(aBuilder, bounds);
       }
       return;
     }
 
     gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
     std::vector<RefPtr<ScaledFont>> fonts;
     RefPtr<WebRenderDrawEventRecorder> recorder =
@@ -698,17 +684,19 @@ struct DIGroup
       MOZ_RELEASE_ASSERT(bottomRight.x <= dtSize.width && bottomRight.y <= dtSize.height);
       GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
       if (!aResources.UpdateBlobImage(mKey.value(), descriptor, bytes, ViewAs<ImagePixel>(mInvalidRect))) {
         return;
       }
     }
     mFonts = std::move(fonts);
     mInvalidRect.SetEmpty();
-    SetBlobImageVisibleArea(aResources, mKey.value(), mPaintRect, bounds);
+    aResources.SetImageVisibleArea(
+      mKey.value(),
+      ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
     PushImage(aBuilder, bounds);
     GP("End EndGroup\n\n");
   }
 
   void PushImage(wr::DisplayListBuilder& aBuilder, const LayoutDeviceRect& bounds)
   {
     wr::LayoutRect dest = wr::ToLayoutRect(bounds);
     GP("PushImage: %f %f %f %f\n", dest.origin.x, dest.origin.y, dest.size.width, dest.size.height);
@@ -754,16 +742,18 @@ struct DIGroup
       }
 
       if (mInvalidRect.Contains(bounds)) {
         GP("Wholely contained\n");
         BlobItemData* data = GetBlobItemData(item);
         data->mInvalid = false;
       } else {
         BlobItemData* data = GetBlobItemData(item);
+        if (data->mInvalid)
+          gfxCriticalError() << "DisplayItem" << item->Name() << "should be invalid";
         // if the item is invalid it needs to be fully contained
         MOZ_RELEASE_ASSERT(!data->mInvalid);
       }
 
       nsDisplayList* children = item->GetChildren();
       if (children) {
         GP("doing children in EndGroup\n");
         aGrouper->PaintContainerItem(this, item, bounds, children, aContext, aRecorder);
@@ -1227,26 +1217,33 @@ WebRenderCommandBuilder::DoGroupingForDi
   FrameMetrics::ViewID scrollId = FrameMetrics::NULL_SCROLL_ID;
   if (const ActiveScrolledRoot* asr = aWrappingItem->GetActiveScrolledRoot()) {
     scrollId = asr->GetViewId();
   }
 
   g.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
   group.mResidualOffset = residualOffset;
   group.mGroupBounds = groupBounds;
-  group.mPaintRect = LayoutDeviceRect::FromAppUnits(
-    aWrappingItem->GetPaintRect(),
-    appUnitsPerDevPixel
-  );
   group.mAppUnitsPerDevPixel = appUnitsPerDevPixel;
   group.mLayerBounds = LayerIntRect::FromUnknownRect(ScaleToOutsidePixelsOffset(group.mGroupBounds,
                                                                                 scale.width,
                                                                                 scale.height,
                                                                                 group.mAppUnitsPerDevPixel,
                                                                                 residualOffset));
+  group.mPaintRect = LayerIntRect::FromUnknownRect(
+                       ScaleToOutsidePixelsOffset(aWrappingItem->GetPaintRect(),
+                                                  scale.width,
+                                                  scale.height,
+                                                  group.mAppUnitsPerDevPixel,
+                                                  residualOffset))
+                       .Intersect(group.mLayerBounds);
+  // XXX: Make the paint rect relative to the layer bounds. After we include
+  // mLayerBounds.TopLeft() in the blob image we want to stop doing this
+  // adjustment.
+  group.mPaintRect = group.mPaintRect - group.mLayerBounds.TopLeft();
   g.mTransform = Matrix::Scaling(scale.width, scale.height)
                                 .PostTranslate(residualOffset.x, residualOffset.y);
   group.mScale = scale;
   group.mScrollId = scrollId;
   group.mAnimatedGeometryRootOrigin = group.mGroupBounds.TopLeft();
   g.ConstructGroups(aDisplayListBuilder, this, aBuilder, aResources, &group, aList, aSc);
   mClipManager.EndList(aSc);
 }
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -417,30 +417,30 @@ impl ClipStore {
     pub fn get_instance_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
     ) -> &ClipNodeInstance {
         &self.clip_node_instances[(node_range.first + index) as usize]
     }
 
-    // Notify the clip store that a new rasterization root has been created.
+    // Notify the clip store that a new surface has been created.
     // This means any clips from an earlier root should be collected rather
     // than applied on the primitive itself.
-    pub fn push_raster_root(
+    pub fn push_surface(
         &mut self,
-        raster_spatial_node_index: SpatialNodeIndex,
+        spatial_node_index: SpatialNodeIndex,
     ) {
         self.clip_node_collectors.push(
-            ClipNodeCollector::new(raster_spatial_node_index),
+            ClipNodeCollector::new(spatial_node_index),
         );
     }
 
-    // Mark the end of a rasterization root.
-    pub fn pop_raster_root(
+    // Mark the end of a rendering surface.
+    pub fn pop_surface(
         &mut self,
     ) -> ClipNodeCollector {
         self.clip_node_collectors.pop().unwrap()
     }
 
     // The main interface other code uses. Given a local primitive, positioning
     // information, and a clip chain id, build an optimized clip chain instance.
     pub fn build_clip_chain_instance(
@@ -469,17 +469,17 @@ impl ClipStore {
 
         // for each clip chain node
         while current_clip_chain_id != ClipChainId::NONE {
             let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
 
             // Check if any clip node index should actually be
             // handled during compositing of a rasterization root.
             match self.clip_node_collectors.iter_mut().find(|c| {
-                clip_chain_node.spatial_node_index < c.raster_root
+                clip_chain_node.spatial_node_index < c.spatial_node_index
             }) {
                 Some(collector) => {
                     collector.insert(current_clip_chain_id);
                 }
                 None => {
                     if !add_clip_node_to_current_chain(
                         clip_chain_node.handle,
                         clip_chain_node.spatial_node_index,
@@ -1181,26 +1181,26 @@ pub fn project_inner_rect(
         WorldSize::new(xs[2] - xs[1], ys[2] - ys[1]),
     ))
 }
 
 // Collects a list of unique clips to be applied to a rasterization
 // root at the end of primitive preparation.
 #[derive(Debug)]
 pub struct ClipNodeCollector {
-    raster_root: SpatialNodeIndex,
+    spatial_node_index: SpatialNodeIndex,
     clips: FastHashSet<ClipChainId>,
 }
 
 impl ClipNodeCollector {
     pub fn new(
-        raster_root: SpatialNodeIndex,
+        spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         ClipNodeCollector {
-            raster_root,
+            spatial_node_index,
             clips: FastHashSet::default(),
         }
     }
 
     pub fn insert(
         &mut self,
         clip_chain_id: ClipChainId,
     ) {
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -8,17 +8,17 @@ use api::{ClipId, ColorF, ComplexClipReg
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
-use clip::{ClipDataInterner, ClipChainId, ClipRegion, ClipItemKey, ClipStore};
+use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
@@ -26,28 +26,22 @@ use internal_types::{FastHashMap, FastHa
 use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
+use scene_builder::DocumentResources;
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem};
-use tiling::{CompositeOps, ScrollbarPrimitive};
+use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
-static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
-    r: 0.3,
-    g: 0.3,
-    b: 0.3,
-    a: 0.6,
-};
-
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
 
 impl ClipNode {
     fn new(id: ClipChainId, count: usize) -> ClipNode {
@@ -139,51 +133,49 @@ pub struct DisplayListFlattener<'a> {
     sc_stack: Vec<FlattenedStackingContext>,
 
     /// A stack of the currently active shadows
     shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
     pipeline_clip_chain_stack: Vec<ClipChainId>,
 
-    /// A list of scrollbar primitives.
-    pub scrollbar_prims: Vec<ScrollbarPrimitive>,
-
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
 
-    /// Reference to the clip interner for this document.
-    clip_interner: &'a mut ClipDataInterner,
+    /// Reference to the document resources, which contains
+    /// shared (interned) data between display lists.
+    resources: &'a mut DocumentResources,
 
     /// The estimated count of primtives we expect to encounter during flattening.
     prim_count_estimate: usize,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
         scene_id: u64,
         picture_id_generator: &mut PictureIdGenerator,
-        clip_interner: &mut ClipDataInterner,
+        resources: &mut DocumentResources,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
         let background_color = root_pipeline
             .background_color
             .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
@@ -191,24 +183,23 @@ impl<'a> DisplayListFlattener<'a> {
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
-            scrollbar_prims: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             picture_id_generator,
-            clip_interner,
+            resources,
             prim_count_estimate: 0,
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
@@ -254,17 +245,16 @@ 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);
-        let scroll_frame_info = self.simple_scroll_and_clip_chain(&root_scroll_node);
 
         self.push_stacking_context(
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
             root_scroll_node,
@@ -290,27 +280,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
         }
 
         self.prim_count_estimate += pipeline.display_list.prim_count_estimate();
         self.prim_store.primitives.reserve(self.prim_count_estimate);
 
         self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayoutVector2D::zero());
 
-        if self.config.enable_scrollbars {
-            let scrollbar_rect = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(10.0, 70.0));
-            let container_rect = LayoutRect::new(LayoutPoint::zero(), *frame_size);
-            self.add_scroll_bar(
-                reference_frame_info.spatial_node_index,
-                &LayoutPrimitiveInfo::new(scrollbar_rect),
-                DEFAULT_SCROLLBAR_COLOR,
-                ScrollbarInfo(scroll_frame_info.spatial_node_index, container_rect),
-            );
-        }
-
         self.pop_stacking_context();
     }
 
     fn flatten_items(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         reference_frame_relative_offset: LayoutVector2D,
@@ -814,17 +793,17 @@ impl<'a> DisplayListFlattener<'a> {
         if clip_items.is_empty() {
             parent_clip_chain_id
         } else {
             let mut clip_chain_id = parent_clip_chain_id;
 
             for item in clip_items {
                 // Intern this clip item, and store the handle
                 // in the clip chain node.
-                let handle = self.clip_interner.intern(&item);
+                let handle = self.resources.clip_interner.intern(&item);
 
                 clip_chain_id = self.clip_store
                                     .add_clip_chain_node(
                                         handle,
                                         spatial_node_index,
                                         clip_chain_id,
                                     );
             }
@@ -1332,45 +1311,48 @@ impl<'a> DisplayListFlattener<'a> {
 
         // Intern each clip item in this clip node, and add the interned
         // handle to a clip chain node, parented to form a chain.
         // TODO(gw): We could re-structure this to share some of the
         //           interning and chaining code.
 
         // Build the clip sources from the supplied region.
         let handle = self
+            .resources
             .clip_interner
             .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip));
 
         parent_clip_chain_index = self
             .clip_store
             .add_clip_chain_node(
                 handle,
                 spatial_node,
                 parent_clip_chain_index,
             );
         clip_count += 1;
 
         if let Some(ref image_mask) = clip_region.image_mask {
             let handle = self
+                .resources
                 .clip_interner
                 .intern(&ClipItemKey::image_mask(image_mask));
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
                     parent_clip_chain_index,
                 );
             clip_count += 1;
         }
 
         for region in clip_region.complex_clips {
             let handle = self
+                .resources
                 .clip_interner
                 .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode));
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
@@ -1442,19 +1424,20 @@ impl<'a> DisplayListFlattener<'a> {
         } else {
             RasterSpace::Local(1.0)
         };
 
         // Create a picture that the shadow primitives will be added to. If the
         // blur radius is 0, the code in Picture::prepare_for_render will
         // detect this and mark the picture to be drawn directly into the
         // parent picture, which avoids an intermediate surface and blur.
+        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
         let shadow_pic = PicturePrimitive::new_image(
             self.picture_id_generator.next(),
-            Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
+            Some(PictureCompositeMode::Filter(blur_filter)),
             false,
             pipeline_id,
             None,
             is_passthrough,
             raster_space,
         );
 
         // Create the primitive to draw the shadow picture into the scene.
@@ -1523,50 +1506,16 @@ impl<'a> DisplayListFlattener<'a> {
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::Brush(prim),
         );
     }
 
-    pub fn add_scroll_bar(
-        &mut self,
-        spatial_node_index: SpatialNodeIndex,
-        info: &LayoutPrimitiveInfo,
-        color: ColorF,
-        scrollbar_info: ScrollbarInfo,
-    ) {
-        if color.a == 0.0 {
-            return;
-        }
-
-        let prim = BrushPrimitive::new(
-            BrushKind::new_solid(color),
-            None,
-        );
-
-        let prim_index = self.create_primitive(
-            info,
-            ClipChainId::NONE,
-            spatial_node_index,
-            PrimitiveContainer::Brush(prim),
-        );
-
-        self.add_primitive_to_draw_list(
-            prim_index,
-        );
-
-        self.scrollbar_prims.push(ScrollbarPrimitive {
-            prim_index,
-            scroll_frame_index: scrollbar_info.0,
-            frame_rect: scrollbar_info.1,
-        });
-    }
-
     pub fn add_line(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         wavy_line_thickness: f32,
         orientation: LineOrientation,
         line_color: &ColorF,
         style: LineStyle,
@@ -2112,11 +2061,8 @@ struct FlattenedStackingContext {
     leaf_prim_index: PrimitiveIndex,
 
     /// If true, this stacking context establishes a new
     /// 3d rendering context.
     establishes_3d_context: bool,
     participating_in_3d_context: bool,
     has_mix_blend_mode: bool,
 }
-
-#[derive(Debug)]
-pub struct ScrollbarInfo(pub SpatialNodeIndex, pub LayoutRect);
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -10,27 +10,26 @@ use clip_scroll_tree::{ClipScrollTree, R
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::{PictureCompositeMode, PictureSurface, RasterConfig};
 use prim_store::{PrimitiveIndex, PrimitiveStore, SpaceMapper};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_backend::FrameId;
+use render_backend::{FrameResources, FrameId};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
 use spatial_node::SpatialNode;
 use std::f32;
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
-use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
-use util;
+use tiling::{SpecialRenderPasses};
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ChasePrimitive {
     Nothing,
     Index(PrimitiveIndex),
@@ -42,34 +41,32 @@ impl Default for ChasePrimitive {
         ChasePrimitive::Nothing
     }
 }
 
 #[derive(Clone, Copy)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
-    pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
     pub chase_primitive: ChasePrimitive,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
-    pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub world_rect: WorldRect,
@@ -80,27 +77,26 @@ pub struct FrameBuildingContext<'a> {
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
-    pub clip_data_store: &'a mut ClipDataStore,
+    pub resources: &'a mut FrameResources,
     pub segment_builder: SegmentBuilder,
 }
 
 pub struct PictureContext {
     pub pipeline_id: PipelineId,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
     pub is_passthrough: bool,
-    pub establishes_raster_root: bool,
     pub raster_space: RasterSpace,
 }
 
 #[derive(Debug)]
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
     pub is_cacheable: bool,
@@ -130,25 +126,23 @@ impl<'a> PrimitiveContext<'a> {
     }
 }
 
 impl FrameBuilder {
     #[cfg(feature = "replay")]
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
-            scrollbar_prims: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
             config: FrameBuilderConfig {
-                enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
     }
 
@@ -156,17 +150,16 @@ impl FrameBuilder {
         screen_rect: DeviceUintRect,
         background_color: Option<ColorF>,
         window_size: DeviceUintSize,
         scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
-            scrollbar_prims: flattener.scrollbar_prims,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
         }
@@ -181,17 +174,17 @@ impl FrameBuilder {
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &mut TransformPalette,
-        clip_data_store: &mut ClipDataStore,
+        resources: &mut FrameResources,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.primitives.is_empty() {
             return None
         }
         self.prim_store.reset_prim_visibility();
 
@@ -219,17 +212,17 @@ impl FrameBuilder {
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             special_render_passes,
             transforms: transform_palette,
-            clip_data_store,
+            resources,
             segment_builder: SegmentBuilder::new(),
         };
 
         let prim_context = PrimitiveContext::new(
             &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
             root_spatial_node_index,
         );
 
@@ -285,59 +278,30 @@ impl FrameBuilder {
         pic.raster_config = Some(RasterConfig {
             composite_mode: PictureCompositeMode::Blit,
             surface: Some(PictureSurface::RenderTask(render_task_id)),
             raster_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
         });
         Some(render_task_id)
     }
 
-    fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
-        static SCROLLBAR_PADDING: f32 = 8.0;
-
-        for scrollbar_prim in &self.scrollbar_prims {
-            let metadata = &mut self.prim_store.primitives[scrollbar_prim.prim_index.0].metadata;
-            let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0];
-
-            // Invalidate what's in the cache so it will get rebuilt.
-            gpu_cache.invalidate(&metadata.gpu_location);
-
-            let scrollable_distance = scroll_frame.scrollable_size().height;
-            if scrollable_distance <= 0.0 {
-                metadata.local_clip_rect.size = LayoutSize::zero();
-                continue;
-            }
-            let amount_scrolled = -scroll_frame.scroll_offset().y / scrollable_distance;
-
-            let frame_rect = scrollbar_prim.frame_rect;
-            let min_y = frame_rect.origin.y + SCROLLBAR_PADDING;
-            let max_y = frame_rect.origin.y + frame_rect.size.height -
-                (SCROLLBAR_PADDING + metadata.local_rect.size.height);
-
-            metadata.local_rect.origin.x = frame_rect.origin.x + frame_rect.size.width -
-                (metadata.local_rect.size.width + SCROLLBAR_PADDING);
-            metadata.local_rect.origin.y = util::lerp(min_y, max_y, amount_scrolled);
-            metadata.local_clip_rect = metadata.local_rect;
-        }
-    }
-
     pub fn build(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         frame_id: FrameId,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
-        clip_data_store: &mut ClipDataStore,
+        resources: &mut FrameResources,
     ) -> Frame {
         profile_scope!("build");
         debug_assert!(
             DeviceUintRect::new(DeviceUintPoint::zero(), self.window_size)
                 .contains_rect(&self.screen_rect)
         );
 
         let mut profile_counters = FrameProfileCounters::new();
@@ -350,35 +314,33 @@ impl FrameBuilder {
 
         let mut transform_palette = TransformPalette::new();
         clip_scroll_tree.update_tree(
             pan,
             scene_properties,
             Some(&mut transform_palette),
         );
 
-        self.update_scroll_bars(clip_scroll_tree, gpu_cache);
-
         let mut render_tasks = RenderTaskTree::new(frame_id);
 
         let screen_size = self.screen_rect.size.to_i32();
         let mut special_render_passes = SpecialRenderPasses::new(&screen_size);
 
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
             &mut transform_palette,
-            clip_data_store,
+            resources,
         );
 
         resource_cache.block_until_all_resources_added(gpu_cache,
                                                        &mut render_tasks,
                                                        texture_cache_profile);
 
         let mut passes = vec![
             special_render_passes.alpha_glyph_pass,
@@ -412,17 +374,17 @@ impl FrameBuilder {
 
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 use_dual_source_blending,
                 clip_scroll_tree,
-                clip_data_store,
+                resources,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/image.rs
+++ b/gfx/webrender/src/image.rs
@@ -1,14 +1,14 @@
 /* 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::{TileOffset, TileRange, LayoutRect, LayoutSize, LayoutPoint};
-use api::{DeviceUintSize, NormalizedRect};
+use api::{DeviceUintSize, DeviceUintRect};
 use euclid::{vec2, point2};
 use prim_store::EdgeAaSegmentMask;
 
 /// If repetitions are far enough apart that only one is within
 /// the primitive rect, then we can simplify the parameters and
 /// treat the primitive as not repeated.
 /// This can let us avoid unnecessary work later to handle some
 /// of the parameters.
@@ -222,32 +222,31 @@ pub fn for_each_tile(
             }
 
             callback(&segment_rect, tile_offset, edge_flags);
         }
     }
 }
 
 pub fn compute_tile_range(
-    visible_area: &NormalizedRect,
-    image_size: &DeviceUintSize,
+    visible_area: &DeviceUintRect,
     tile_size: u16,
 ) -> TileRange {
     // Tile dimensions in normalized coordinates.
-    let tw = (image_size.width as f32) / (tile_size as f32);
-    let th = (image_size.height as f32) / (tile_size as f32);
+    let tw = 1. / (tile_size as f32);
+    let th = 1. / (tile_size as f32);
 
     let t0 = point2(
-        f32::floor(visible_area.origin.x * tw),
-        f32::floor(visible_area.origin.y * th),
+        f32::floor(visible_area.origin.x as f32 * tw),
+        f32::floor(visible_area.origin.y as f32 * th),
     ).cast::<u16>();
 
     let t1 = point2(
-        f32::ceil(visible_area.max_x() * tw),
-        f32::ceil(visible_area.max_y() * th),
+        f32::ceil(visible_area.max_x() as f32 * tw),
+        f32::ceil(visible_area.max_y() as f32 * th),
     ).cast::<u16>();
 
     TileRange {
         origin: t0,
         size: (t1 - t0).to_size(),
     }
 }
 
@@ -318,9 +317,9 @@ mod tests {
               &size2(400, 400),
               36,
               &mut |_tile_rect, _tile_offset, _tile_flags| {
                 count += 1;
               },
         );
         assert_eq!(count, 0);
     }
-}
\ No newline at end of file
+}
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -287,19 +287,19 @@ impl PicturePrimitive {
         let establishes_raster_root = has_surface && wants_raster_root;
 
         let raster_spatial_node_index = if establishes_raster_root {
             surface_spatial_node_index
         } else {
             raster_spatial_node_index
         };
 
-        if establishes_raster_root {
+        if has_surface {
             frame_state.clip_store
-                       .push_raster_root(raster_spatial_node_index);
+                       .push_surface(surface_spatial_node_index);
         }
 
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             frame_context.world_rect,
             frame_context.clip_scroll_tree,
         );
@@ -356,17 +356,16 @@ impl PicturePrimitive {
         };
 
         let context = PictureContext {
             pipeline_id: self.pipeline_id,
             apply_local_clip_rect: self.apply_local_clip_rect,
             inflation_factor,
             allow_subpixel_aa,
             is_passthrough: self.raster_config.is_none(),
-            establishes_raster_root,
             raster_space,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
@@ -420,20 +419,20 @@ impl PicturePrimitive {
                 }
             }
             None => {
                 assert!(self.raster_config.is_none());
                 LayoutRect::zero()
             }
         };
 
-        let clip_node_collector = if context.establishes_raster_root {
-            Some(frame_state.clip_store.pop_raster_root())
+        let clip_node_collector = if context.is_passthrough {
+            None
         } else {
-            None
+            Some(frame_state.clip_store.pop_surface())
         };
 
         (local_rect, clip_node_collector)
     }
 
     pub fn take_state(&mut self) -> PictureState {
         self.state.take().expect("bug: no state present!")
     }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1728,17 +1728,17 @@ impl PrimitiveStore {
                     &pic_state.map_local_to_pic,
                     &pic_state.map_pic_to_world,
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     &frame_context.world_rect,
                     &clip_node_collector,
-                    frame_state.clip_data_store,
+                    &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
                     prim.metadata.clipped_world_rect = None;
                     return false;
                 }
@@ -2082,17 +2082,17 @@ fn write_brush_segment_description(
     );
 
     // Segment the primitive on all the local-space clip sources that we can.
     let mut local_clip_count = 0;
     for i in 0 .. clip_chain.clips_range.count {
         let clip_instance = frame_state
             .clip_store
             .get_instance_from_range(&clip_chain.clips_range, i);
-        let clip_node = &frame_state.clip_data_store[clip_instance.handle];
+        let clip_node = &frame_state.resources.clip_data_store[clip_instance.handle];
 
         // If this clip item is positioned by another positioning node, its relative position
         // could change during scrolling. This means that we would need to resegment. Instead
         // of doing that, only segment with clips that have the same positioning node.
         // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
         // when necessary while scrolling.
         if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
             continue;
@@ -2249,17 +2249,17 @@ impl Primitive {
                     &pic_state.map_local_to_pic,
                     &pic_state.map_pic_to_world,
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     &frame_context.world_rect,
                     clip_node_collector,
-                    frame_state.clip_data_store,
+                    &mut frame_state.resources.clip_data_store,
                 );
 
             match segment_clip_chain {
                 Some(segment_clip_chain) => {
                     if !segment_clip_chain.needs_mask ||
                        (!segment.may_need_clip_mask && !segment_clip_chain.has_non_local_clips) {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                         continue;
@@ -2282,17 +2282,17 @@ impl Primitive {
                     let clip_task = RenderTask::new_mask(
                         device_rect.to_i32(),
                         segment_clip_chain.clips_range,
                         root_spatial_node_index,
                         frame_state.clip_store,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_state.render_tasks,
-                        frame_state.clip_data_store,
+                        &mut frame_state.resources.clip_data_store,
                     );
 
                     let clip_task_id = frame_state.render_tasks.add(clip_task);
                     pic_state.tasks.push(clip_task_id);
                     segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id);
                 }
                 None => {
                     segment.clip_task_id = BrushSegmentTaskId::Empty;
@@ -2823,17 +2823,17 @@ impl Primitive {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
                     clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
-                    frame_state.clip_data_store,
+                    &mut frame_state.resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tcreated task {:?} with world rect {:?}",
                         clip_task_id, self.metadata.clipped_world_rect);
                 }
                 self.metadata.clip_task_id = Some(clip_task_id);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -11,19 +11,17 @@ use api::{IdNamespace, LayoutPoint, Pipe
 use api::{MemoryReport, VoidPtrToSizeFn};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey};
 use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
-#[cfg(feature = "replay")]
-use clip::ClipDataInterner;
-use clip::{ClipDataUpdateList, ClipDataStore};
+use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
@@ -75,16 +73,34 @@ impl DocumentView {
     }
 }
 
 #[derive(Copy, Clone, Hash, PartialEq, PartialOrd, Debug, Eq, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
+// A collection of resources that are shared by clips, primitives
+// between display lists.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FrameResources {
+    // The store of currently active / available clip nodes. This is kept
+    // in sync with the clip interner in the scene builder for each document.
+    pub clip_data_store: ClipDataStore,
+}
+
+impl FrameResources {
+    fn new() -> Self {
+        FrameResources {
+            clip_data_store: ClipDataStore::new(),
+        }
+    }
+}
+
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
 
     // Temporary list of removed pipelines received from the scene builder
     // thread and forwarded to the renderer.
     removed_pipelines: Vec<PipelineId>,
@@ -113,19 +129,17 @@ struct Document {
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
 
     /// Track whether the last built frame is up to date or if it will need to be re-built
     /// before rendering again.
     frame_is_valid: bool,
     hit_tester_is_valid: bool,
 
-    // The store of currently active / available clip nodes. This is kept
-    // in sync with the clip interner in the scene builder for each document.
-    clip_data_store: ClipDataStore,
+    resources: FrameResources,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
     ) -> Self {
@@ -144,17 +158,17 @@ impl Document {
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
-            clip_data_store: ClipDataStore::new(),
+            resources: FrameResources::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
@@ -273,21 +287,21 @@ impl Document {
                 &mut self.clip_scroll_tree,
                 &self.scene.pipelines,
                 accumulated_scale_factor,
                 self.view.layer,
                 pan,
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
-                &mut self.clip_data_store,
+                &mut self.resources,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(
                 &self.clip_scroll_tree,
-                &self.clip_data_store,
+                &self.resources.clip_data_store,
             ));
             frame
         };
 
         self.frame_is_valid = true;
         self.hit_tester_is_valid = true;
 
         RenderedDocument {
@@ -304,17 +318,17 @@ impl Document {
             self.clip_scroll_tree.update_tree(
                 pan,
                 &self.dynamic_properties,
                 None,
             );
 
             self.hit_tester = Some(frame_builder.create_hit_tester(
                 &self.clip_scroll_tree,
-                &self.clip_data_store,
+                &self.resources.clip_data_store,
             ));
         }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
         let removed_pipelines = replace(&mut self.removed_pipelines, Vec::new());
         PipelineInfo {
             epochs: self.scene.pipeline_epochs.clone(),
@@ -414,16 +428,17 @@ pub struct RenderBackend {
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Box<RenderNotifier>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     sampler: Option<Box<AsyncPropertySampler + Send>>,
     size_of_op: Option<VoidPtrToSizeFn>,
+    namespace_alloc_by_client: bool,
 
     last_scene_id: u64,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
@@ -433,16 +448,17 @@ impl RenderBackend {
         scene_rx: Receiver<SceneBuilderResult>,
         default_device_pixel_ratio: f32,
         resource_cache: ResourceCache,
         notifier: Box<RenderNotifier>,
         frame_config: FrameBuilderConfig,
         recorder: Option<Box<ApiRecordingReceiver>>,
         sampler: Option<Box<AsyncPropertySampler + Send>>,
         size_of_op: Option<VoidPtrToSizeFn>,
+        namespace_alloc_by_client: bool,
     ) -> RenderBackend {
         RenderBackend {
             api_rx,
             payload_rx,
             result_tx,
             scene_tx,
             low_priority_scene_tx,
             scene_rx,
@@ -452,16 +468,17 @@ impl RenderBackend {
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
             sampler,
             size_of_op,
             last_scene_id: 0,
+            namespace_alloc_by_client,
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
         frame_counter: u32,
@@ -624,17 +641,17 @@ impl RenderBackend {
                         );
                         if let Some(rasterizer) = txn.blob_rasterizer.take() {
                             self.resource_cache.set_blob_rasterizer(rasterizer);
                         }
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
-                            txn.clip_updates.take(),
+                            txn.doc_resource_updates.take(),
                             replace(&mut txn.frame_ops, Vec::new()),
                             replace(&mut txn.notifications, Vec::new()),
                             txn.render_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
@@ -724,18 +741,23 @@ impl RenderBackend {
                 let mut glyph_indices = Vec::new();
                 for ch in text.chars() {
                     let index = self.resource_cache.get_glyph_index(font_key, ch);
                     glyph_indices.push(index);
                 }
                 tx.send(glyph_indices).unwrap();
             }
             ApiMsg::CloneApi(sender) => {
+                assert!(!self.namespace_alloc_by_client);
                 sender.send(self.next_namespace_id()).unwrap();
             }
+            ApiMsg::CloneApiByClient(namespace_id) => {
+                assert!(self.namespace_alloc_by_client);
+                debug_assert!(!self.documents.iter().any(|(did, _doc)| did.0 == namespace_id));
+            }
             ApiMsg::AddDocument(document_id, initial_size, layer) => {
                 let document = Document::new(
                     initial_size,
                     layer,
                     self.default_device_pixel_ratio,
                 );
                 self.documents.insert(document_id, document);
             }
@@ -968,17 +990,17 @@ impl RenderBackend {
 
         tx.send(SceneBuilderRequest::Transaction(txn)).unwrap();
     }
 
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
-        clip_updates: Option<ClipDataUpdateList>,
+        doc_resource_updates: Option<DocumentResourceUpdates>,
         mut frame_ops: Vec<FrameMsg>,
         mut notifications: Vec<NotificationRequest>,
         mut render_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
@@ -993,18 +1015,18 @@ impl RenderBackend {
                 frame_ops.append(&mut sampler.sample());
             }
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         // If there are any additions or removals of clip modes
         // during the scene build, apply them to the data store now.
-        if let Some(clip_updates) = clip_updates {
-            doc.clip_data_store.apply_updates(clip_updates);
+        if let Some(updates) = doc_resource_updates {
+            doc.resources.clip_data_store.apply_updates(updates.clip_updates);
         }
 
         // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
         // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
@@ -1311,18 +1333,18 @@ impl RenderBackend {
                 );
                 //TODO: write down doc's pipeline info?
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", (id.0).0, id.1);
                 config.serialize(&rendered_document.frame, file_name);
             }
 
-            let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
-            config.serialize(&doc.clip_data_store, clip_data_name);
+            let frame_resources_name = format!("frame-resources-{}-{}", (id.0).0, id.1);
+            config.serialize(&doc.resources, frame_resources_name);
         }
 
         debug!("\tscene builder");
         self.scene_tx.send(SceneBuilderRequest::SaveScene(config.clone())).unwrap();
 
         debug!("\tresource cache");
         let (resources, deferred) = self.resource_cache.save_capture(&config.root);
 
@@ -1398,37 +1420,37 @@ impl RenderBackend {
 
         let mut last_scene_id = backend.last_scene_id;
         for (id, view) in backend.documents {
             debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
-            let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
-            let clip_interner = CaptureConfig::deserialize::<ClipDataInterner, _>(root, &clip_interner_name)
-                .expect(&format!("Unable to open {}.ron", clip_interner_name));
+            let doc_resources_name = format!("doc-resources-{}-{}", (id.0).0, id.1);
+            let doc_resources = CaptureConfig::deserialize::<DocumentResources, _>(root, &doc_resources_name)
+                .expect(&format!("Unable to open {}.ron", doc_resources_name));
 
-            let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1);
-            let clip_data_store = CaptureConfig::deserialize::<ClipDataStore, _>(root, &clip_data_name)
-                .expect(&format!("Unable to open {}.ron", clip_data_name));
+            let frame_resources_name = format!("frame-resources-{}-{}", (id.0).0, id.1);
+            let frame_resources = CaptureConfig::deserialize::<FrameResources, _>(root, &frame_resources_name)
+                .expect(&format!("Unable to open {}.ron", frame_resources_name));
 
             let mut doc = Document {
                 scene: scene.clone(),
                 removed_pipelines: Vec::new(),
                 view: view.clone(),
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
-                clip_data_store,
+                resources: frame_resources,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
 
@@ -1459,17 +1481,17 @@ impl RenderBackend {
                 document_id: id,
                 scene: doc.scene.clone(),
                 view: view.clone(),
                 config: self.frame_config.clone(),
                 output_pipelines: doc.output_pipelines.clone(),
                 font_instances: self.resource_cache.get_font_instances(),
                 scene_id: last_scene_id,
                 build_frame,
-                clip_interner,
+                doc_resources,
             });
 
             self.documents.insert(id, doc);
         }
 
         if !scenes_to_build.is_empty() {
             self.low_priority_scene_tx.send(
                 SceneBuilderRequest::LoadScenes(scenes_to_build)
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -1716,17 +1716,16 @@ impl Renderer {
 
         let default_font_render_mode = match (options.enable_aa, options.enable_subpixel_aa) {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let config = FrameBuilderConfig {
-            enable_scrollbars: options.enable_scrollbars,
             default_font_render_mode,
             dual_source_blending_is_enabled: true,
             dual_source_blending_is_supported: ext_dual_source_blending,
             chase_primitive: options.chase_primitive,
         };
 
         let device_pixel_ratio = options.device_pixel_ratio;
         // First set the flags to default and later call set_debug_flags to ensure any
@@ -1754,16 +1753,17 @@ impl Renderer {
                             thread_listener.thread_stopped(&format!("WRWorker#{}", idx));
                         }
                     })
                     .build();
                 Arc::new(worker.unwrap())
             });
         let sampler = options.sampler;
         let size_of_op = options.size_of_op;
+        let namespace_alloc_by_client = options.namespace_alloc_by_client;
 
         let blob_image_handler = options.blob_image_handler.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
         let thread_listener_for_lp_scene_builder = thread_listener.clone();
         let scene_builder_hooks = options.scene_builder_hooks;
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
@@ -1837,16 +1837,17 @@ impl Renderer {
                 scene_rx,
                 device_pixel_ratio,
                 resource_cache,
                 backend_notifier,
                 config,
                 recorder,
                 sampler,
                 size_of_op,
+                namespace_alloc_by_client,
             );
             backend.run(backend_profile_counters);
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_stopped(&rb_thread_name);
             }
         })?;
 
         let ext_debug_marker = device.supports_extension("GL_EXT_debug_marker");
@@ -4395,17 +4396,16 @@ pub trait AsyncPropertySampler {
 }
 
 pub struct RendererOptions {
     pub device_pixel_ratio: f32,
     pub resource_override_path: Option<PathBuf>,
     pub enable_aa: bool,
     pub enable_dithering: bool,
     pub max_recorded_profiles: usize,
-    pub enable_scrollbars: bool,
     pub precache_shaders: bool,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_color: Option<ColorF>,
     pub enable_clear_scissor: bool,
     pub max_texture_size: Option<u32>,
     pub scatter_gpu_cache_updates: bool,
     pub upload_method: UploadMethod,
@@ -4417,28 +4417,28 @@ pub struct RendererOptions {
     pub cached_programs: Option<Rc<ProgramCache>>,
     pub debug_flags: DebugFlags,
     pub renderer_id: Option<u64>,
     pub disable_dual_source_blending: bool,
     pub scene_builder_hooks: Option<Box<SceneBuilderHooks + Send>>,
     pub sampler: Option<Box<AsyncPropertySampler + Send>>,
     pub chase_primitive: ChasePrimitive,
     pub support_low_priority_transactions: bool,
+    pub namespace_alloc_by_client: bool,
 }
 
 impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
             enable_dithering: true,
             debug_flags: DebugFlags::empty(),
             max_recorded_profiles: 0,
-            enable_scrollbars: false,
             precache_shaders: false,
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
             enable_clear_scissor: true,
             max_texture_size: None,
             // Scattered GPU cache updates haven't met a test that would show their superiority yet.
             scatter_gpu_cache_updates: false,
@@ -4452,16 +4452,17 @@ impl Default for RendererOptions {
             size_of_op: None,
             renderer_id: None,
             cached_programs: None,
             disable_dual_source_blending: false,
             scene_builder_hooks: None,
             sampler: None,
             chase_primitive: ChasePrimitive::Nothing,
             support_low_priority_transactions: false,
+            namespace_alloc_by_client: false,
         }
     }
 }
 
 #[cfg(not(feature = "debugger"))]
 pub struct DebugServer;
 
 #[cfg(not(feature = "debugger"))]
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -6,17 +6,17 @@ use api::{AddFont, BlobImageResources, A
 use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest, RasterizedBlobImage};
 use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType, BlobImageResult, BlobImageParams};
 use api::{FontInstanceData, FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{MemoryReport, VoidPtrToSizeFn};
-use api::{TileOffset, TileSize, TileRange, NormalizedRect, BlobImageData};
+use api::{TileOffset, TileSize, TileRange, BlobImageData};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
@@ -550,17 +550,16 @@ impl ResourceCache {
                         );
                     }
                 }
                 ResourceUpdate::SetImageVisibleArea(ref key, ref area) => {
                     if let Some(template) = self.blob_image_templates.get_mut(&key) {
                         if let Some(tile_size) = template.tiling {
                             template.viewport_tiles = Some(compute_tile_range(
                                 &area,
-                                &template.descriptor.size,
                                 tile_size,
                             ));
                         }
                     }
                 }
                 _ => {}
             }
         }
@@ -1035,41 +1034,38 @@ impl ResourceCache {
             let template = self.blob_image_templates.get_mut(key).unwrap();
 
             if let Some(tile_size) = template.tiling {
                 // If we know that only a portion of the blob image is in the viewport,
                 // only request these visible tiles since blob images can be huge.
                 let mut tiles = template.viewport_tiles.unwrap_or_else(|| {
                     // Default to requesting the full range of tiles.
                     compute_tile_range(
-                        &NormalizedRect {
-                            origin: point2(0.0, 0.0),
-                            size: size2(1.0, 1.0),
+                        &DeviceUintRect {
+                            origin: point2(0, 0),
+                            size: template.descriptor.size,
                         },
-                        &template.descriptor.size,
                         tile_size,
                     )
                 });
 
                 // Don't request tiles that weren't invalidated.
                 if let Some(dirty_rect) = template.dirty_rect {
-                    let f32_size = template.descriptor.size.to_f32();
-                    let normalized_dirty_rect = NormalizedRect {
+                    let dirty_rect = DeviceUintRect {
                         origin: point2(
-                            dirty_rect.origin.x as f32 / f32_size.width,
-                            dirty_rect.origin.y as f32 / f32_size.height,
+                            dirty_rect.origin.x,
+                            dirty_rect.origin.y,
                         ),
                         size: size2(
-                            dirty_rect.size.width as f32 / f32_size.width,
-                            dirty_rect.size.height as f32 / f32_size.height,
+                            dirty_rect.size.width,
+                            dirty_rect.size.height,
                         ),
                     };
                     let dirty_tiles = compute_tile_range(
-                        &normalized_dirty_rect,
-                        &template.descriptor.size,
+                        &dirty_rect,
                         tile_size,
                     );
 
                     tiles = tiles.intersection(&dirty_tiles).unwrap_or(TileRange::zero());
                 }
 
                 // This code tries to keep things sane if Gecko sends
                 // nonsensical blob image requests.
@@ -1168,17 +1164,17 @@ impl ResourceCache {
         let handler = self.blob_image_handler.as_mut().unwrap();
         handler.prepare_resources(&self.resources, &blob_request_params);
         (Some(handler.create_blob_rasterizer()), blob_request_params)
     }
 
     fn discard_tiles_outside_visible_area(
         &mut self,
         key: ImageKey,
-        area: &NormalizedRect
+        area: &DeviceUintRect
     ) {
         let template = match self.blob_image_templates.get(&key) {
             Some(template) => template,
             None => {
                 //println!("Missing image template (key={:?})!", key);
                 return;
             }
         };
@@ -1189,17 +1185,16 @@ impl ResourceCache {
 
         let tiles = match self.rasterized_blob_images.get_mut(&key) {
             Some(RasterizedBlob::Tiled(tiles)) => tiles,
             _ => { return; }
         };
 
         let tile_range = compute_tile_range(
             &area,
-            &template.descriptor.size,
             tile_size,
         );
 
         tiles.retain(|tile, _| { tile_range.contains(tile) });
     }
 
     pub fn request_glyphs(
         &mut self,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -20,16 +20,20 @@ use renderer::{PipelineInfo, SceneBuilde
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
 use std::thread;
 use std::time::Duration;
 
+pub struct DocumentResourceUpdates {
+    pub clip_updates: ClipDataUpdateList,
+}
+
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
     pub request_scene_build: Option<SceneRequest>,
     pub blob_requests: Vec<BlobImageParams>,
@@ -64,17 +68,17 @@ pub struct BuiltTransaction {
     pub document_id: DocumentId,
     pub built_scene: Option<BuiltScene>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
     pub notifications: Vec<NotificationRequest>,
-    pub clip_updates: Option<ClipDataUpdateList>,
+    pub doc_resource_updates: Option<DocumentResourceUpdates>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub render_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
@@ -97,17 +101,17 @@ pub struct LoadScene {
     pub document_id: DocumentId,
     pub scene: Scene,
     pub scene_id: u64,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub font_instances: FontInstanceMap,
     pub view: DocumentView,
     pub config: FrameBuilderConfig,
     pub build_frame: bool,
-    pub clip_interner: ClipDataInterner,
+    pub doc_resources: DocumentResources,
 }
 
 pub struct BuiltScene {
     pub scene: Scene,
     pub frame_builder: FrameBuilder,
     pub clip_scroll_tree: ClipScrollTree,
 }
 
@@ -139,30 +143,50 @@ pub enum SceneBuilderResult {
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
     Complete(Sender<()>),
     Aborted,
 }
 
+// This struct contains all items that can be shared between
+// display lists. We want to intern and share the same clips,
+// primitives and other things between display lists so that:
+// - GPU cache handles remain valid, reducing GPU cache updates.
+// - Comparison of primitives and pictures between two
+//   display lists is (a) fast (b) done during scene building.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct DocumentResources {
+    pub clip_interner: ClipDataInterner,
+}
+
+impl DocumentResources {
+    fn new() -> Self {
+        DocumentResources {
+            clip_interner: ClipDataInterner::new(),
+        }
+    }
+}
+
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
 struct Document {
     scene: Scene,
-    clip_interner: ClipDataInterner,
+    resources: DocumentResources,
 }
 
 impl Document {
     fn new(scene: Scene) -> Self {
         Document {
             scene,
-            clip_interner: ClipDataInterner::new(),
+            resources: DocumentResources::new(),
         }
     }
 }
 
 pub struct SceneBuilder {
     documents: FastHashMap<DocumentId, Document>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
@@ -255,78 +279,87 @@ impl SceneBuilder {
         if let Some(ref hooks) = self.hooks {
             hooks.deregister();
         }
     }
 
     #[cfg(feature = "capture")]
     fn save_scene(&mut self, config: CaptureConfig) {
         for (id, doc) in &self.documents {
-            let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1);
-            config.serialize(&doc.clip_interner, clip_interner_name);
+            let doc_resources_name = format!("doc-resources-{}-{}", (id.0).0, id.1);
+            config.serialize(&doc.resources, doc_resources_name);
         }
     }
 
     #[cfg(feature = "replay")]
     fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
         for mut item in scenes {
             self.config = item.config;
 
             let scene_build_start_time = precise_time_ns();
 
             let mut built_scene = None;
-            let mut clip_updates = None;
+            let mut doc_resource_updates = None;
 
             if item.scene.has_root_pipeline() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
                     &item.scene,
                     &mut clip_scroll_tree,
                     item.font_instances,
                     &item.view,
                     &item.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     item.scene_id,
                     &mut self.picture_id_generator,
-                    &mut item.clip_interner,
+                    &mut item.doc_resources,
                 );
 
-                clip_updates = Some(item.clip_interner.end_frame_and_get_pending_updates());
+                let clip_updates = item
+                    .doc_resources
+                    .clip_interner
+                    .end_frame_and_get_pending_updates();
+
+                doc_resource_updates = Some(
+                    DocumentResourceUpdates {
+                        clip_updates,
+                    }
+                );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
 
             self.documents.insert(
                 item.document_id,
                 Document {
                     scene: item.scene,
-                    clip_interner: item.clip_interner,
+                    resources: item.doc_resources,
                 },
             );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 render_frame: item.build_frame,
                 built_scene,
                 resource_updates: Vec::new(),
                 rasterized_blobs: Vec::new(),
                 blob_rasterizer: None,
                 frame_ops: Vec::new(),
                 removed_pipelines: Vec::new(),
                 notifications: Vec::new(),
                 scene_build_start_time,
                 scene_build_end_time: precise_time_ns(),
-                clip_updates,
+                doc_resource_updates,
             });
 
             self.forward_built_transaction(txn);
         }
     }
 
     /// Do the bulk of the work of the scene builder thread.
     fn process_transaction(&mut self, txn: &mut Transaction) -> Box<BuiltTransaction> {
@@ -357,37 +390,46 @@ impl SceneBuilder {
             scene.set_root_pipeline_id(id);
         }
 
         for pipeline_id in &txn.removed_pipelines {
             scene.remove_pipeline(*pipeline_id)
         }
 
         let mut built_scene = None;
-        let mut clip_updates = None;
+        let mut doc_resource_updates = None;
         if scene.has_root_pipeline() {
             if let Some(request) = txn.request_scene_build.take() {
                 let mut clip_scroll_tree = ClipScrollTree::new();
                 let mut new_scene = Scene::new();
 
                 let frame_builder = DisplayListFlattener::create_frame_builder(
                     &scene,
                     &mut clip_scroll_tree,
                     request.font_instances,
                     &request.view,
                     &request.output_pipelines,
                     &self.config,
                     &mut new_scene,
                     request.scene_id,
                     &mut self.picture_id_generator,
-                    &mut doc.clip_interner,
+                    &mut doc.resources,
                 );
 
                 // Retrieve the list of updates from the clip interner.
-                clip_updates = Some(doc.clip_interner.end_frame_and_get_pending_updates());
+                let clip_updates = doc
+                    .resources
+                    .clip_interner
+                    .end_frame_and_get_pending_updates();
+
+                doc_resource_updates = Some(
+                    DocumentResourceUpdates {
+                        clip_updates,
+                    }
+                );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
             }
         }
@@ -414,17 +456,17 @@ impl SceneBuilder {
             render_frame: txn.render_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
             frame_ops: replace(&mut txn.frame_ops, Vec::new()),
             removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
             notifications: replace(&mut txn.notifications, Vec::new()),
-            clip_updates,
+            doc_resource_updates,
             scene_build_start_time,
             scene_build_end_time: precise_time_ns(),
         })
     }
 
     /// Send the result of process_transaction back to the render backend.
     fn forward_built_transaction(&mut self, txn: Box<BuiltTransaction>) {
         // We only need the pipeline info and the result channel if we
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,60 +1,54 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat};
-use api::{LayoutRect, MixBlendMode, PipelineId};
+use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
-use clip::{ClipDataStore, ClipStore};
-use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
+use clip::ClipStore;
+use clip_scroll_tree::{ClipScrollTree};
 use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
-use prim_store::{PrimitiveIndex, PrimitiveStore, DeferredResolve};
+use prim_store::{PrimitiveStore, DeferredResolve};
 use profiler::FrameProfileCounters;
+use render_backend::FrameResources;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
 use webrender_api::{DevicePixel, FontRenderMode};
 
 const MIN_TARGET_SIZE: u32 = 2048;
 const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
 const STYLE_MASK: i32 = 0x00FF_FF00;
 
-#[derive(Debug)]
-pub struct ScrollbarPrimitive {
-    pub scroll_frame_index: SpatialNodeIndex,
-    pub prim_index: PrimitiveIndex,
-    pub frame_rect: LayoutRect,
-}
-
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub use_dual_source_blending: bool,
     pub clip_scroll_tree: &'a ClipScrollTree,
-    pub clip_data_store: &'a ClipDataStore,
+    pub resources: &'a FrameResources,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
@@ -589,17 +583,17 @@ impl RenderTarget for AlphaRenderTarget 
                     task_address,
                     task_info.clip_node_range,
                     task_info.root_spatial_node_index,
                     ctx.resource_cache,
                     gpu_cache,
                     clip_store,
                     ctx.clip_scroll_tree,
                     transforms,
-                    ctx.clip_data_store,
+                    &ctx.resources.clip_data_store,
                 );
             }
             RenderTaskKind::ClipRegion(ref task) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add_clip_region(
                     task_address,
                     task.clip_data_address,
                 );
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -12,28 +12,28 @@ use std::marker::PhantomData;
 use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::sync::Arc;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
 use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
-use {NativeFontHandle, WorldPoint, NormalizedRect};
+use {NativeFontHandle, WorldPoint};
 
 pub type TileSize = u16;
 /// Documents are rendered in the ascending order of their associated layer values.
 pub type DocumentLayer = i8;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ResourceUpdate {
     AddImage(AddImage),
     UpdateImage(UpdateImage),
     DeleteImage(ImageKey),
-    SetImageVisibleArea(ImageKey, NormalizedRect),
+    SetImageVisibleArea(ImageKey, DeviceUintRect),
     AddFont(AddFont),
     DeleteFont(FontKey),
     AddFontInstance(AddFontInstance),
     DeleteFontInstance(FontInstanceKey),
 }
 
 /// A Transaction is a group of commands to apply atomically to a document.
 ///
@@ -316,17 +316,17 @@ impl Transaction {
             dirty_rect,
         }));
     }
 
     pub fn delete_image(&mut self, key: ImageKey) {
         self.resource_updates.push(ResourceUpdate::DeleteImage(key));
     }
 
-    pub fn set_image_visible_area(&mut self, key: ImageKey, area: NormalizedRect) {
+    pub fn set_image_visible_area(&mut self, key: ImageKey, area: DeviceUintRect) {
         self.resource_updates.push(ResourceUpdate::SetImageVisibleArea(key, area))
     }
 
     pub fn add_raw_font(&mut self, key: FontKey, bytes: Vec<u8>, index: u32) {
         self.resource_updates
             .push(ResourceUpdate::AddFont(AddFont::Raw(key, bytes, index)));
     }
 
@@ -657,16 +657,18 @@ pub enum ApiMsg {
         FontInstanceKey,
         Vec<GlyphIndex>,
         MsgSender<Vec<Option<GlyphDimensions>>>,
     ),
     /// Gets the glyph indices from a string
     GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
     /// Adds a new document namespace.
     CloneApi(MsgSender<IdNamespace>),
+    /// Adds a new document namespace.
+    CloneApiByClient(IdNamespace),
     /// Adds a new document with given initial size.
     AddDocument(DocumentId, DeviceUintSize, DocumentLayer),
     /// A message targeted at a particular document.
     UpdateDocument(DocumentId, TransactionMsg),
     /// Deletes an existing document.
     DeleteDocument(DocumentId),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
@@ -690,16 +692,17 @@ pub enum ApiMsg {
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
             ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
+            ApiMsg::CloneApiByClient(..) => "ApiMsg::CloneApiByClient",
             ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
             ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
             ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
             ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
             ApiMsg::ReportMemory(..) => "ApiMsg::ReportMemory",
             ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand",
@@ -859,16 +862,33 @@ impl RenderApiSender {
         };
         RenderApi {
             api_sender: self.api_sender.clone(),
             payload_sender: self.payload_sender.clone(),
             namespace_id,
             next_id: Cell::new(ResourceId(0)),
         }
     }
+
+    /// Creates a new resource API object with a dedicated namespace.
+    /// Namespace id is allocated by client.
+    ///
+    /// The function could be used only when RendererOptions::namespace_alloc_by_client is true.
+    /// When the option is true, create_api() could not be used to prevent namespace id conflict.
+    pub fn create_api_by_client(&self, namespace_id: IdNamespace) -> RenderApi {
+        let msg = ApiMsg::CloneApiByClient(namespace_id);
+        self.api_sender.send(msg).expect("Failed to send CloneApiByClient message");
+        RenderApi {
+            api_sender: self.api_sender.clone(),
+            payload_sender: self.payload_sender.clone(),
+            namespace_id,
+            next_id: Cell::new(ResourceId(0)),
+        }
+    }
+
 }
 
 pub struct RenderApi {
     api_sender: MsgSender<ApiMsg>,
     payload_sender: PayloadSender,
     namespace_id: IdNamespace,
     next_id: Cell<ResourceId>,
 }
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -118,22 +118,16 @@ pub type PictureToRasterTransform = Type
 pub type RasterToPictureTransform = TypedTransform3D<f32, RasterPixel, PicturePixel>;
 
 // Fixed position coordinates, to avoid float precision errors.
 pub type LayoutPointAu = TypedPoint2D<Au, LayoutPixel>;
 pub type LayoutRectAu = TypedRect<Au, LayoutPixel>;
 pub type LayoutSizeAu = TypedSize2D<Au, LayoutPixel>;
 pub type LayoutVector2DAu = TypedVector2D<Au, LayoutPixel>;
 
-/// Coordinates in normalized space (between zero and one).
-#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct NormalizedCoordinates;
-
-pub type NormalizedRect = TypedRect<f32, NormalizedCoordinates>;
-
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
 /// updated on the CPU when the texture size changes.
 #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
 pub struct TexelRect {
     pub uv0: DevicePoint,
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -697,17 +697,19 @@ TransactionBuilder::UpdateExternalImageW
                                                             aKey,
                                                             &aDescriptor,
                                                             aExtID,
                                                             aBufferType,
                                                             aChannelIndex,
                                                             aDirtyRect);
 }
 
-void TransactionBuilder::SetImageVisibleArea(ImageKey aKey, const wr::NormalizedRect& aArea)
+void
+TransactionBuilder::SetImageVisibleArea(ImageKey aKey,
+                                        const wr::DeviceUintRect& aArea)
 {
   wr_resource_updates_set_image_visible_area(mTxn, aKey, &aArea);
 }
 
 void
 TransactionBuilder::DeleteImage(ImageKey aKey)
 {
   wr_resource_updates_delete_image(mTxn, aKey);
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -136,17 +136,17 @@ public:
 
   void UpdateExternalImageWithDirtyRect(ImageKey aKey,
                                         const ImageDescriptor& aDescriptor,
                                         ExternalImageId aExtID,
                                         wr::WrExternalImageBufferType aBufferType,
                                         const wr::DeviceUintRect& aDirtyRect,
                                         uint8_t aChannelIndex = 0);
 
-  void SetImageVisibleArea(ImageKey aKey, const wr::NormalizedRect& aArea);
+  void SetImageVisibleArea(ImageKey aKey, const wr::DeviceUintRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
   void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
 
   void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
 
   void DeleteFont(wr::FontKey aKey);
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-0d2e9611c07e04ac830277f16a3b46bf125b9e7c
+d7a6d081384ce0da9dd359b0cf4b9f758aab1b67
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1354,17 +1354,17 @@ pub extern "C" fn wr_resource_updates_up
         None
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_set_image_visible_area(
     txn: &mut Transaction,
     key: WrImageKey,
-    area: &NormalizedRect,
+    area: &DeviceUintRect,
 ) {
     txn.set_image_visible_area(key, *area);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_external_image(
     txn: &mut Transaction,
     key: WrImageKey,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -272,19 +272,16 @@ struct Arc;
 // surface) in physical pixels.
 struct DevicePixel;
 
 struct DocumentHandle;
 
 // Geometry in a stacking context's local coordinate space (logical pixels).
 struct LayoutPixel;
 
-// Coordinates in normalized space (between zero and one).
-struct NormalizedCoordinates;
-
 // The renderer is responsible for submitting to the GPU the work prepared by the
 // RenderBackend.
 struct Renderer;
 
 // Offset in number of tiles.
 struct Tiles;
 
 // Represents the work associated to a transaction before scene building.
@@ -1047,18 +1044,16 @@ struct WrImageDescriptor {
     return format == aOther.format &&
            width == aOther.width &&
            height == aOther.height &&
            stride == aOther.stride &&
            opacity == aOther.opacity;
   }
 };
 
-using NormalizedRect = TypedRect<float, NormalizedCoordinates>;
-
 struct WrTransformProperty {
   uint64_t id;
   LayoutTransform transform;
 };
 
 struct WrOpacityProperty {
   uint64_t id;
   float opacity;
@@ -1710,17 +1705,17 @@ WR_FUNC;
 WR_INLINE
 void wr_resource_updates_delete_image(Transaction *aTxn,
                                       WrImageKey aKey)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_set_image_visible_area(Transaction *aTxn,
                                                 WrImageKey aKey,
-                                                const NormalizedRect *aArea)
+                                                const DeviceUintRect *aArea)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_update_blob_image(Transaction *aTxn,
                                            WrImageKey aImageKey,
                                            const WrImageDescriptor *aDescriptor,
                                            WrVecU8 *aBytes,
                                            DeviceUintRect aDirtyRect)
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -180,19 +180,19 @@ impl<'a> RawtestHarness<'a> {
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img,
             ColorF::WHITE,
         );
         txn.set_image_visible_area(
             blob_img,
-            NormalizedRect {
-                origin: point2(0.0, 0.03),
-                size: size2(1.0, 0.03),
+            DeviceUintRect {
+                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);
@@ -305,19 +305,19 @@ impl<'a> RawtestHarness<'a> {
                 false,
                 false
             ),
             ImageData::new_blob_image(blob::serialize_blob(ColorU::new(50, 50, 150, 255))),
             Some(100),
         );
         // Set a visible rectangle that is too small.
         // This will force sync rasterization of the missing tiles during frame building.
-        txn.set_image_visible_area(blob_img2, NormalizedRect {
-            origin: point2(0.25, 0.25),
-            size: size2(0.1, 0.1),
+        txn.set_image_visible_area(blob_img2, DeviceUintRect {
+            origin: point2(200, 200),
+            size: size2(80, 80),
         });
 
         builder.push_image(
             &info,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1582,21 +1582,19 @@ RestyleManager::ProcessRestyledFrames(ns
         // need the nsChangeHint_UpdateTransformLayer hint if we already have
         // a nsChangeHint_AddOrRemoveTransform hint, and since we
         // will fail an assertion in ApplyRenderingChangeToTree if we try
         // specify nsChangeHint_UpdateTransformLayer but don't have any
         // transform style, we just drop the unneeded hint here.
         hint &= ~nsChangeHint_UpdateTransformLayer;
       }
 
-      if (hint & nsChangeHint_UpdateEffects) {
-        for (nsIFrame* cont = frame; cont;
-             cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
-          SVGObserverUtils::UpdateEffects(cont);
-        }
+      if ((hint & nsChangeHint_UpdateEffects) &&
+          frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
+        SVGObserverUtils::UpdateEffects(frame);
       }
       if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
           ((hint & nsChangeHint_UpdateOpacityLayer) &&
            frame->IsFrameOfType(nsIFrame::eSVG) &&
            !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
         SVGObserverUtils::InvalidateRenderingObservers(frame);
         frame->SchedulePaint();
       }
--- a/layout/base/UnitTransforms.h
+++ b/layout/base/UnitTransforms.h
@@ -16,24 +16,26 @@ namespace mozilla {
 
 // Convenience functions for converting an entity from one strongly-typed
 // coordinate system to another without changing the values it stores (this
 // can be thought of as a cast).
 // To use these functions, you must provide a justification for each use!
 // Feel free to add more justifications to PixelCastJustification, along with
 // a comment that explains under what circumstances it is appropriate to use.
 
-enum class PixelCastJustification : uint8_t {
+enum class PixelCastJustification : uint8_t
+{
   // For the root layer, Screen Pixel = Parent Layer Pixel.
   ScreenIsParentLayerForRoot,
   // On the layout side, Screen Pixel = LayoutDevice at the outer-window level.
   LayoutDeviceIsScreenForBounds,
   // For the root layer, Render Target Pixel = Parent Layer Pixel.
   RenderTargetIsParentLayerForRoot,
-  // For the root composition size we want to view it as layer pixels in any layer
+  // For the root composition size we want to view it as layer pixels in any
+  // layer
   ParentLayerToLayerForRootComposition,
   // The Layer coordinate space for one layer is the ParentLayer coordinate
   // space for its children
   MovingDownToChildren,
   // The transform that is usually used to convert between two coordinate
   // systems is not available (for example, because the object that stores it
   // is being destroyed), so fall back to the identity.
   TransformNotAvailable,
@@ -50,17 +52,19 @@ enum class PixelCastJustification : uint
   // ScreenIsParentLayerForRoot, which is how we're using it.
   LayoutDeviceIsParentLayerForRCDRSF,
   // Used to treat the product of AsyncTransformComponentMatrix objects
   // as an AsyncTransformMatrix. See the definitions of these matrices in
   // LayersTypes.h for details.
   MultipleAsyncTransforms,
   // We have reason to believe a layer doesn't have a local transform.
   // Should only be used if we've already checked or asserted this.
-  NoTransformOnLayer
+  NoTransformOnLayer,
+  // LayerPixels are ImagePixels
+  LayerIsImage,
 };
 
 template <class TargetUnits, class SourceUnits>
 gfx::CoordTyped<TargetUnits> ViewAs(const gfx::CoordTyped<SourceUnits>& aCoord, PixelCastJustification) {
   return gfx::CoordTyped<TargetUnits>(aCoord.value);
 }
 template <class TargetUnits, class SourceUnits>
 gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2712,20 +2712,21 @@ ComputeClipForMaskItem(nsDisplayListBuil
 
     // Use an infinite dirty rect to pass into nsCSSRendering::
     // GetImageLayerClip() because we don't have an actual dirty rect to
     // pass in. This is fine because the only time GetImageLayerClip() will
     // not intersect the incoming dirty rect with something is in the "NoClip"
     // case, and we handle that specially.
     nsRect dirtyRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
 
-    nsIFrame* firstFrame = nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
-    SVGObserverUtils::EffectProperties effectProperties =
-        SVGObserverUtils::GetEffectProperties(firstFrame);
-    nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+    nsIFrame* firstFrame =
+      nsLayoutUtils::FirstContinuationOrIBSplitSibling(aMaskedFrame);
+    nsTArray<nsSVGMaskFrame*> maskFrames;
+    // XXX check return value?
+    SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
 
     for (uint32_t i = 0; i < maskFrames.Length(); ++i) {
       gfxRect clipArea;
       if (maskFrames[i]) {
         clipArea = maskFrames[i]->GetMaskArea(aMaskedFrame);
         clipArea = cssToDevMatrix.TransformBounds(clipArea);
       } else {
         const auto& layer = svgReset->mMask.mLayers[i];
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -9567,19 +9567,19 @@ ComputeMaskGeometry(PaintFramesParams& a
 {
   // Properties are added lazily and may have been removed by a restyle, so
   // make sure all applicable ones are set again.
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame);
 
   const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
 
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
-  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+  nsTArray<nsSVGMaskFrame*> maskFrames;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
 
   if (maskFrames.Length() == 0) {
     return;
   }
 
   gfxContext& ctx = aParams.ctx;
   nsIFrame* frame = aParams.frame;
 
@@ -9710,22 +9710,21 @@ nsDisplayMasksAndClipPaths::IsValidMask(
   }
 
   if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) {
     return false;
   }
 
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
 
   if (SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) ==
         SVGObserverUtils::eHasRefsSomeInvalid ||
-      effectProperties.HasInvalidMask()) {
+      SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) ==
+        SVGObserverUtils::eHasRefsSomeInvalid) {
     return false;
   }
 
   return true;
 }
 
 
 
@@ -10030,18 +10029,16 @@ nsDisplayMasksAndClipPaths::GetClipWithR
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo)
 {
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
   bool first = true;
   aTo += " effects=(";
   if (mFrame->StyleEffects()->mOpacity != 1.0f && mHandleOpacity) {
     first = false;
     aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity);
   }
   nsSVGClipPathFrame* clipPathFrame;
   // XXX Check return value?
@@ -10056,17 +10053,19 @@ nsDisplayMasksAndClipPaths::PrintEffects
   } else if (mFrame->StyleSVGReset()->HasClipPath()) {
     if (!first) {
       aTo += ", ";
     }
     aTo += "clip(basic-shape)";
     first = false;
   }
 
-  nsTArray<nsSVGMaskFrame*> masks = effectProperties.GetMaskFrames();
+  nsTArray<nsSVGMaskFrame*> masks;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks);
   if (!masks.IsEmpty() && masks[0]) {
     if (!first) {
       aTo += ", ";
     }
     aTo += "mask";
   }
   aTo += ")";
 }
--- a/layout/svg/SVGObserverUtils.cpp
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -561,30 +561,16 @@ nsSVGPaintingProperty::OnRenderingChange
 
   if (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
     frame->InvalidateFrameSubtree();
   } else {
     InvalidateAllContinuations(frame);
   }
 }
 
-static SVGMaskObserverList*
-GetOrCreateMaskProperty(nsIFrame* aFrame)
-{
-  SVGMaskObserverList *prop =
-    aFrame->GetProperty(SVGObserverUtils::MaskProperty());
-  if (prop)
-    return prop;
-
-  prop = new SVGMaskObserverList(aFrame);
-  NS_ADDREF(prop);
-  aFrame->SetProperty(SVGObserverUtils::MaskProperty(), prop);
-  return prop;
-}
-
 static already_AddRefed<URLAndReferrerInfo>
 ResolveURLUsingLocalRef(nsIFrame* aFrame, const css::URLValueData* aURL)
 {
   MOZ_ASSERT(aFrame);
 
   if (!aURL) {
     return nullptr;
   }
@@ -668,16 +654,18 @@ SVGObserverUtils::GetMarkerFrames(nsIFra
   return foundMarker;
 }
 
 // Note that the returned list will be empty in the case of a 'filter' property
 // that only specifies CSS filter functions (no url()'s to SVG filters).
 static SVGFilterObserverListForCSSProp*
 GetOrCreateFilterObserverListForCSS(nsIFrame* aFrame)
 {
+  MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation");
+
   const nsStyleEffects* effects = aFrame->StyleEffects();
   if (!effects->HasFilters()) {
     return nullptr;
   }
   SVGFilterObserverListForCSSProp* observers =
     aFrame->GetProperty(SVGObserverUtils::FilterProperty());
   if (observers) {
     return observers;
@@ -752,16 +740,18 @@ SVGObserverUtils::DetachFromCanvasContex
   static_cast<SVGFilterObserverListForCanvasContext*>(aAutoObserver)->
     DetachFromContext();
 }
 
 
 static nsSVGPaintingProperty*
 GetOrCreateClipPathObserver(nsIFrame* aClippedFrame)
 {
+  MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(), "Require first continuation");
+
   const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset();
   if (svgStyleReset->mClipPath.GetType() != StyleShapeSourceType::URL) {
     return nullptr;
   }
   css::URLValue* url = svgStyleReset->mClipPath.GetURL();
   RefPtr<URLAndReferrerInfo> pathURI = ResolveURLUsingLocalRef(aClippedFrame, url);
   return SVGObserverUtils::GetPaintingProperty(pathURI, aClippedFrame,
                                          SVGObserverUtils::ClipPathProperty());
@@ -788,16 +778,81 @@ SVGObserverUtils::GetAndObserveClipPath(
     return eHasRefsSomeInvalid;
   }
   if (aClipPathFrame) {
     *aClipPathFrame = frame;
   }
   return frame ? eHasRefsAllValid : eHasNoRefs;
 }
 
+static SVGMaskObserverList*
+GetOrCreateMaskObserverList(nsIFrame* aMaskedFrame)
+{
+  MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(), "Require first continuation");
+
+  const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset();
+  if (!style->HasMask()) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(style->mMask.mImageCount > 0);
+
+  SVGMaskObserverList* prop =
+    aMaskedFrame->GetProperty(SVGObserverUtils::MaskProperty());
+  if (prop) {
+    return prop;
+  }
+  prop = new SVGMaskObserverList(aMaskedFrame);
+  NS_ADDREF(prop);
+  aMaskedFrame->SetProperty(SVGObserverUtils::MaskProperty(), prop);
+  return prop;
+}
+
+SVGObserverUtils::ReferenceState
+SVGObserverUtils::GetAndObserveMasks(nsIFrame* aMaskedFrame,
+                                     nsTArray<nsSVGMaskFrame*>* aMaskFrames)
+{
+  SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame);
+  if (!observerList) {
+    return eHasNoRefs;
+  }
+
+  const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
+    observerList->GetObservers();
+  if (observers.IsEmpty()) {
+    return eHasNoRefs;
+  }
+
+  ReferenceState state = eHasRefsAllValid;
+
+  for (size_t i = 0; i < observers.Length(); i++) {
+    bool frameTypeOK = true;
+    nsSVGMaskFrame* maskFrame = static_cast<nsSVGMaskFrame*>(
+      observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask,
+                                                 &frameTypeOK));
+    MOZ_ASSERT(!maskFrame || frameTypeOK);
+    // XXXjwatt: this looks fishy
+    if (!frameTypeOK) {
+      // We can not find the specific SVG mask resource in the downloaded SVG
+      // document. There are two possibilities:
+      // 1. The given resource id is invalid.
+      // 2. The given resource id refers to a viewbox.
+      //
+      // Hand it over to the style image.
+      observerList->ResolveImage(i);
+      state = eHasRefsSomeInvalid;
+    }
+    if (aMaskFrames) {
+      aMaskFrames->AppendElement(maskFrame);
+    }
+  }
+
+  return state;
+}
+
 SVGGeometryElement*
 SVGObserverUtils::GetTextPathsReferencedPath(nsIFrame* aTextPathFrame)
 {
   SVGTextPathObserver* property =
     aTextPathFrame->GetProperty(SVGObserverUtils::HrefAsTextPathProperty());
 
   if (!property) {
     nsIContent* content = aTextPathFrame->GetContent();
@@ -833,17 +888,17 @@ SVGObserverUtils::GetTextPathsReferenced
 
 void
 SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame)
 {
   // We create observer objects and attach them to aFrame, but we do not
   // make aFrame start observing the referenced frames.
   Unused << GetOrCreateFilterObserverListForCSS(aFrame);
   Unused << GetOrCreateClipPathObserver(aFrame);
-  Unused << GetEffectProperties(aFrame);
+  Unused << GetOrCreateMaskObserverList(aFrame);
 }
 
 void
 SVGObserverUtils::RemoveTextPathObserver(nsIFrame* aTextPathFrame)
 {
   aTextPathFrame->DeleteProperty(HrefAsTextPathProperty());
 }
 
@@ -914,31 +969,16 @@ SVGObserverUtils::GetPaintingPropertyFor
   if (!prop) {
     bool watchImage = aProperty == SVGObserverUtils::BackgroundImageProperty();
     prop = new nsSVGPaintingProperty(aURI, aFrame, watchImage);
     hashtable->Put(aURI, prop);
   }
   return prop;
 }
 
-SVGObserverUtils::EffectProperties
-SVGObserverUtils::GetEffectProperties(nsIFrame* aFrame)
-{
-  NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
-
-  EffectProperties result;
-  const nsStyleSVGReset *style = aFrame->StyleSVGReset();
-
-  MOZ_ASSERT(style->mMask.mImageCount > 0);
-  result.mMaskObservers = style->HasMask()
-                          ? GetOrCreateMaskProperty(aFrame) : nullptr;
-
-  return result;
-}
-
 nsSVGPaintServerFrame *
 SVGObserverUtils::GetPaintServer(nsIFrame* aTargetFrame,
                                  nsStyleSVGPaint nsStyleSVG::* aPaint)
 {
   // If we're looking at a frame within SVG text, then we need to look up
   // to find the right frame to get the painting property off.  We should at
   // least look up past a text frame, and if the text frame's parent is the
   // anonymous block frame, then we look up to its parent (the SVGTextFrame).
@@ -973,70 +1013,16 @@ SVGObserverUtils::GetPaintServer(nsIFram
   if (type != LayoutFrameType::SVGLinearGradient &&
       type != LayoutFrameType::SVGRadialGradient &&
       type != LayoutFrameType::SVGPattern)
     return nullptr;
 
   return static_cast<nsSVGPaintServerFrame*>(result);
 }
 
-nsTArray<nsSVGMaskFrame *>
-SVGObserverUtils::EffectProperties::GetMaskFrames()
-{
-  nsTArray<nsSVGMaskFrame *> result;
-  if (!mMaskObservers) {
-    return result;
-  }
-
-  bool ok = true;
-  const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
-    mMaskObservers->GetObservers();
-  for (size_t i = 0; i < observers.Length(); i++) {
-    nsSVGMaskFrame* maskFrame = static_cast<nsSVGMaskFrame*>(
-      observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask, &ok));
-    MOZ_ASSERT(!maskFrame || ok);
-    if (!ok) {
-      // We can not find the specific SVG mask resource in the downloaded SVG
-      // document. There are two possibilities:
-      // 1. The given resource id is invalid.
-      // 2. The given resource id refers to a viewbox.
-      //
-      // Hand it over to the style image.
-      mMaskObservers->ResolveImage(i);
-    }
-    result.AppendElement(maskFrame);
-  }
-
-  return result;
-}
-
-bool
-SVGObserverUtils::EffectProperties::HasNoOrValidEffects()
-{
-  return HasNoOrValidMask();
-}
-
-bool
-SVGObserverUtils::EffectProperties::HasNoOrValidMask()
-{
-  if (mMaskObservers) {
-    bool ok = true;
-    const nsTArray<RefPtr<nsSVGPaintingProperty>>& observers =
-      mMaskObservers->GetObservers();
-    for (size_t i = 0; i < observers.Length(); i++) {
-      observers[i]->GetAndObserveReferencedFrame(LayoutFrameType::SVGMask, &ok);
-      if (!ok) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
 void
 SVGObserverUtils::UpdateEffects(nsIFrame* aFrame)
 {
   NS_ASSERTION(aFrame->GetContent()->IsElement(),
                "aFrame's content should be an element");
 
   aFrame->DeleteProperty(FilterProperty());
   aFrame->DeleteProperty(MaskProperty());
--- a/layout/svg/SVGObserverUtils.h
+++ b/layout/svg/SVGObserverUtils.h
@@ -576,56 +576,16 @@ public:
   NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, SVGMarkerObserver)
   NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, nsSVGPaintingProperty)
   NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, nsSVGPaintingProperty)
   NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty,
                                        SVGTextPathObserver)
   NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty,
                                       URIObserverHashtable)
 
-  struct EffectProperties {
-    SVGMaskObserverList* mMaskObservers;
-
-    /**
-     * @return an array which contains all SVG mask frames.
-     */
-    nsTArray<nsSVGMaskFrame*> GetMaskFrames();
-
-    /*
-     * @return true if all effects we have are valid or we have no effect
-     * at all.
-     */
-    bool HasNoOrValidEffects();
-
-    /*
-     * @return true if we have any invalid effect.
-     */
-    bool HasInvalidEffects() {
-      return !HasNoOrValidEffects();
-    }
-
-    /*
-     * @return true if we either do not have mask or all masks we have
-     * are valid.
-     */
-    bool HasNoOrValidMask();
-
-    /*
-     * @return true if we have an invalid mask.
-     */
-    bool HasInvalidMask() {
-      return !HasNoOrValidMask();
-    }
-  };
-
-  /**
-   * @param aFrame should be the first continuation
-   */
-  static EffectProperties GetEffectProperties(nsIFrame* aFrame);
-
   /**
    * Ensures that that if the given frame requires any resources that are in
    * SVG resource documents that the loading of those documents is initiated.
    * This does not make aFrame start to observe any elements that it
    * references.
    */
   static void InitiateResourceDocLoads(nsIFrame* aFrame);
 
@@ -791,16 +751,32 @@ public:
    * is not invalid for clip-path or mask.  We will return eHasNoRefs in that
    * case.
    */
   static ReferenceState
   GetAndObserveClipPath(nsIFrame* aClippedFrame,
                         nsSVGClipPathFrame** aClipPathFrame);
 
   /**
+   * If masking is applied to aMaskedFrame, gets an array of any SVG masks
+   * that are referenced, setting up aMaskFrames as a rendering observer of
+   * those masks (if any).
+   *
+   * NOTE! A return value of eHasNoRefs does NOT mean that there are no masks
+   * to be applied, only that there are no references to SVG mask elements.
+   *
+   * Note that, unlike for filters, a reference to an ID that doesn't exist
+   * is not invalid for clip-path or mask.  We will return eHasNoRefs in that
+   * case.
+   */
+  static ReferenceState
+  GetAndObserveMasks(nsIFrame* aMaskedFrame,
+                     nsTArray<nsSVGMaskFrame*>* aMaskFrames);
+
+  /**
    * Get the SVGGeometryElement that is referenced by aTextPathFrame, and make
    * aTextPathFrame start observing rendering changes to that element.
    */
   static SVGGeometryElement*
   GetTextPathsReferencedPath(nsIFrame* aTextPathFrame);
 
   /**
    * Make aTextPathFrame stop observing rendering changes to the
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -298,17 +298,17 @@ nsRect
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   // Note: we do not return here for eHasNoRefs since we must still handle any
   // CSS filter functions.
   // TODO: We currently pass nullptr instead of an nsTArray* here, but we
   // actually should get the filter frames and then pass them into
   // GetPostFilterBounds below!  See bug 1494263.
   // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
   // in that case we disable painting of the element.
-  if (SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
+  if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
         SVGObserverUtils::eHasRefsSomeInvalid) {
     return aPreEffectsOverflowRect;
   }
 
   // Create an override bbox - see comment above:
   nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
   // overrideBBox is in "user space", in _CSS_ pixels:
   // XXX Why are we rounding out to pixel boundaries? We don't do that in
@@ -742,19 +742,20 @@ MoveContextOriginToUserSpace(nsIFrame* a
   return offset;
 }
 
 bool
 nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
 {
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
-  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+  nsTArray<nsSVGMaskFrame*> maskFrames;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
   const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
 
   for (uint32_t i = 0; i < maskFrames.Length(); i++) {
     // Refers to a valid SVG mask.
     if (maskFrames[i]) {
       continue;
     }
 
@@ -798,37 +799,37 @@ nsSVGIntegrationUtils::PaintMask(const P
   }
 
   nsIFrame* frame = aParams.frame;
   if (!ValidateSVGFrame(frame)) {
     return false;
   }
 
   gfxContext& ctx = aParams.ctx;
-  nsIFrame* firstFrame =
-    nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
-
   RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
 
   if (maskUsage.shouldGenerateMaskLayer &&
       (maskUsage.shouldGenerateClipMaskLayer ||
        maskUsage.shouldApplyClipPath)) {
     // We will paint both mask of positioned mask and clip-path into
     // maskTarget.
     //
     // Create one extra draw target for drawing positioned mask, so that we do
     // not have to copy the content of maskTarget before painting
     // clip-path into it.
     maskTarget = maskTarget->CreateSimilarDrawTarget(maskTarget->GetSize(),
                                                      SurfaceFormat::A8);
   }
 
-  nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+  nsTArray<nsSVGMaskFrame*> maskFrames;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
   AutoPopGroup autoPop;
   bool shouldPushOpacity = (maskUsage.opacity != 1.0) &&
                            (maskFrames.Length() != 1);
   if (shouldPushOpacity) {
     ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
     autoPop.SetContext(&ctx);
   }
 
@@ -919,29 +920,28 @@ void PaintMaskAndClipPathInternal(const 
 
   if (maskUsage.opacity == 0.0f) {
     return;
   }
 
   gfxContext& context = aParams.ctx;
   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
 
-  /* Properties are added lazily and may have been removed by a restyle,
-     so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
 
   nsSVGClipPathFrame* clipPathFrame;
   // XXX check return value?
   SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
 
+  nsTArray<nsSVGMaskFrame*> maskFrames;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
   gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
-  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
   bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
                              maskUsage.shouldGenerateClipMaskLayer ||
                              maskUsage.shouldGenerateMaskLayer);
   bool shouldPushMask = false;
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -482,22 +482,21 @@ void
 nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
                                MaskUsage& aUsage)
 {
   aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
 
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
 
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(firstFrame);
   const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
 
-  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
-
+  nsTArray<nsSVGMaskFrame*> maskFrames;
+  // XXX check return value?
+  SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
   aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
 
   nsSVGClipPathFrame* clipPathFrame;
   // XXX check return value?
   SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
   MOZ_ASSERT(!clipPathFrame ||
              svgReset->mClipPath.GetType() == StyleShapeSourceType::URL);
 
@@ -709,32 +708,31 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
    *   clip region).
    *f
    * + Merge opacity and masking if both used together.
    */
 
   /* Properties are added lazily and may have been removed by a restyle,
      so make sure all applicable ones are set again. */
   nsSVGClipPathFrame* clipPathFrame;
-  SVGObserverUtils::EffectProperties effectProperties =
-    SVGObserverUtils::GetEffectProperties(aFrame);
+  nsTArray<nsSVGMaskFrame*> maskFrames;
   // TODO: We currently pass nullptr instead of an nsTArray* here, but we
   // actually should get the filter frames and then pass them into
   // PaintFilteredFrame below!  See bug 1494263.
-  if (effectProperties.HasInvalidEffects() ||
-      SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
+  if (SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
         SVGObserverUtils::eHasRefsSomeInvalid ||
       SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
+        SVGObserverUtils::eHasRefsSomeInvalid ||
+      SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) ==
         SVGObserverUtils::eHasRefsSomeInvalid) {
     // Some resource is invalid. We shouldn't paint anything.
     return;
   }
 
-  nsTArray<nsSVGMaskFrame*> masks = effectProperties.GetMaskFrames();
-  nsSVGMaskFrame *maskFrame = masks.IsEmpty() ? nullptr : masks[0];
+  nsSVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
 
   MixModeBlender blender(aFrame, &aContext);
   gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
                        ? blender.CreateBlendTarget(aTransform) : &aContext;
 
   if (!target) {
     return;
   }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..baf8166dae9868aadc93f29ab487a9813fdab311
GIT binary patch
literal 35
kc${<hbhEHbWMp7uXkcVu_|E_YIv@fh!obAj!pL9^09(id-2eap
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..48f97f74bd47cfa51c13c579c8ecb0a01bf2202f
GIT binary patch
literal 35
jc${<hbhEHbWMp7uXkcXc&j12CAOa-9z{KRj$Y2csT*m|5
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js
@@ -0,0 +1,86 @@
+"use strict";
+
+const server = createHttpServer({hosts: ["green.example.com", "red.example.com"]});
+
+server.registerDirectory("/data/", do_get_file("data"));
+
+server.registerPathHandler("/pixel.html", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  response.write(`<!DOCTYPE html>
+    <script>
+      function readByWeb() {
+        let ctx = document.querySelector("canvas").getContext("2d");
+        let {data} = ctx.getImageData(0, 0, 1, 1);
+        return data.slice(0, 3).join();
+      }
+    </script>
+  `);
+});
+
+add_task(async function test_contentscript_canvas_tainting() {
+  async function contentScript() {
+    let canvas = document.createElement("canvas");
+    let ctx = canvas.getContext("2d");
+    document.body.appendChild(canvas);
+
+    function draw(url) {
+      return new Promise(resolve => {
+        let img = document.createElement("img");
+        img.onload = () => {
+          ctx.drawImage(img, 0, 0, 1, 1);
+          resolve();
+        };
+        img.src = url;
+      });
+    }
+
+    function readByExt() {
+      let {data} = ctx.getImageData(0, 0, 1, 1);
+      return data.slice(0, 3).join();
+    }
+
+    let readByWeb = window.wrappedJSObject.readByWeb;
+
+    // Test reading after drawing an image from the same origin as the web page.
+    await draw("http://green.example.com/data/pixel_green.gif");
+    browser.test.assertEq(readByWeb(), "0,255,0", "Content can read same-origin image");
+    browser.test.assertEq(readByExt(), "0,255,0", "Extension can read same-origin image");
+
+    // Test reading after drawing a blue pixel data URI from extension content script.
+    await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
+    browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read extension's image");
+    browser.test.assertEq(readByExt(), "0,0,255", "Extension can read its own image");
+
+    // Test after tainting the canvas with an image from a third party domain.
+    await draw("http://red.example.com/data/pixel_red.gif");
+    browser.test.assertThrows(readByWeb, /operation is insecure/, "Content can't read third party image");
+    browser.test.assertThrows(readByExt, /operation is insecure/, "Extension can't read fully tainted");
+
+    // Test canvas is still fully tainted after drawing extension's data: image again.
+    await draw("data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=");
+    browser.test.assertThrows(readByWeb, /operation is insecure/, "Canvas still fully tainted for content");
+    browser.test.assertThrows(readByExt, /operation is insecure/, "Canvas still fully tainted for extension");
+
+    browser.test.sendMessage("done");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      content_scripts: [{
+        "matches": ["http://green.example.com/pixel.html"],
+        "js": ["cs.js"],
+      }],
+    },
+    files: {
+      "cs.js": contentScript,
+    },
+  });
+
+  await extension.startup();
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://green.example.com/pixel.html");
+  await extension.awaitMessage("done");
+
+  await contentPage.close();
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
@@ -1,13 +1,14 @@
 [test_ext_i18n.js]
 skip-if = os == "android" || (os == "win" && debug) || (os == "linux")
 [test_ext_i18n_css.js]
 [test_ext_contentscript.js]
 [test_ext_contentscript_about_blank_start.js]
+[test_ext_contentscript_canvas_tainting.js]
 [test_ext_contentscript_scriptCreated.js]
 [test_ext_contentscript_triggeringPrincipal.js]
 skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
 [test_ext_contentscript_xrays.js]
 [test_ext_contentScripts_register.js]
 [test_ext_contexts_gc.js]
 [test_ext_adoption_with_xrays.js]
 [test_ext_shadowdom.js]
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -441,23 +441,16 @@ nsChildView::Create(nsIWidget* aParent,
   // we need to provide an autorelease pool to avoid leaking cocoa objects
   // (see bug 559075).
   nsAutoreleasePool localPool;
 
   // See NSView (MethodSwizzling) below.
   if (!gChildViewMethodsSwizzled) {
     nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow),
                               @selector(nsChildView_NSView_mouseDownCanMoveWindow));
-#ifdef __LP64__
-    nsToolkit::SwizzleMethods([NSEvent class], @selector(addLocalMonitorForEventsMatchingMask:handler:),
-                              @selector(nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:handler:),
-                              true);
-    nsToolkit::SwizzleMethods([NSEvent class], @selector(removeMonitor:),
-                              @selector(nsChildView_NSEvent_removeMonitor:), true);
-#endif
     gChildViewMethodsSwizzled = true;
   }
 
   mBounds = aRect;
 
   // Ensure that the toolkit is created.
   nsToolkit::GetToolkit();
 
@@ -7269,53 +7262,8 @@ static const CGEventField kCGWindowNumbe
   NSWindow *ourWindow = [self window];
   NSView *contentView = [ourWindow contentView];
   if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
     return [ourWindow isMovableByWindowBackground];
   return [self nsChildView_NSView_mouseDownCanMoveWindow];
 }
 
 @end
-
-#ifdef __LP64__
-// When using blocks, at least on OS X 10.7, the OS sometimes calls
-// +[NSEvent removeMonitor:] more than once on a single event monitor, which
-// causes crashes.  See bug 678607.  We hook these methods to work around
-// the problem.
-@interface NSEvent (MethodSwizzling)
-+ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block;
-+ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor;
-@end
-
-// This is a local copy of the AppKit frameworks sEventObservers hashtable.
-// It only stores "local monitors".  We use it to ensure that +[NSEvent
-// removeMonitor:] is never called more than once on the same local monitor.
-static NSHashTable *sLocalEventObservers = nil;
-
-@implementation NSEvent (MethodSwizzling)
-
-+ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block
-{
-  if (!sLocalEventObservers) {
-    sLocalEventObservers = [[NSHashTable hashTableWithOptions:
-      NSHashTableStrongMemory | NSHashTableObjectPointerPersonality] retain];
-  }
-  id retval =
-    [self nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:mask handler:block];
-  if (sLocalEventObservers && retval && ![sLocalEventObservers containsObject:retval]) {
-    [sLocalEventObservers addObject:retval];
-  }
-  return retval;
-}
-
-+ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor
-{
-  if (sLocalEventObservers && [eventMonitor isKindOfClass: ::NSClassFromString(@"_NSLocalEventObserver")]) {
-    if (![sLocalEventObservers containsObject:eventMonitor]) {
-      return;
-    }
-    [sLocalEventObservers removeObject:eventMonitor];
-  }
-  [self nsChildView_NSEvent_removeMonitor:eventMonitor];
-}
-
-@end
-#endif // #ifdef __LP64__