Merge graphics to m-c a=merge a=release
authorWes Kocher <wkocher@mozilla.com>
Fri, 19 May 2017 14:27:32 -0700
changeset 359354 979f11deabd01d6cb897d064d3d9ed11ea8baa93
parent 359291 5a8f2dcbeac0677b5aae0f9fd43d6a33309b4a6b (current diff)
parent 359353 bf1f2aaf7668a3cdbdeae8d2192d59abdc9f15f8 (diff)
child 359376 8d60d0f825110cfb646ac31dc16dc011708bcf34
push id31852
push userkwierso@gmail.com
push dateFri, 19 May 2017 21:47:27 +0000
treeherdermozilla-central@979f11deabd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, release
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge graphics to m-c a=merge a=release IGNORE BAD COMMIT MESSAGES because something landed and was backed out for no bug number
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_border.glsl
gfx/webrender/res/ps_border.vs.glsl
third_party/rust/gamma-lut/src/main.rs
third_party/rust/threadpool/.cargo-checksum.json
third_party/rust/threadpool/.cargo-ok
third_party/rust/threadpool/.gitignore
third_party/rust/threadpool/.travis.yml
third_party/rust/threadpool/CHANGES.md
third_party/rust/threadpool/Cargo.toml
third_party/rust/threadpool/LICENSE-APACHE
third_party/rust/threadpool/LICENSE-MIT
third_party/rust/threadpool/README.md
third_party/rust/threadpool/lib.rs
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -333,16 +333,17 @@ nsDOMWindowUtils::UpdateLayerTree()
 {
   if (nsIPresShell* presShell = GetPresShell()) {
     presShell->FlushPendingNotifications(FlushType::Display);
     RefPtr<nsViewManager> vm = presShell->GetViewManager();
     nsView* view = vm->GetRootView();
     if (view) {
       presShell->Paint(view, view->GetBounds(),
           nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
+      presShell->GetLayerManager()->WaitOnTransactionProcessed();
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
 {
--- a/gfx/2d/DrawTargetCapture.cpp
+++ b/gfx/2d/DrawTargetCapture.cpp
@@ -199,16 +199,17 @@ DrawTargetCaptureImpl::ReplayToDrawTarge
 
 bool
 DrawTargetCaptureImpl::ContainsOnlyColoredGlyphs(RefPtr<ScaledFont>& aScaledFont,
                                                  Color& aColor,
                                                  std::vector<Glyph>& aGlyphs)
 {
   uint8_t* start = &mDrawCommandStorage.front();
   uint8_t* current = start;
+  bool result = false;
 
   while (current < start + mDrawCommandStorage.size()) {
     DrawingCommand* command =
       reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t));
     current += *(uint32_t*)current;
 
     if (command->GetType() != CommandType::FILLGLYPHS &&
         command->GetType() != CommandType::SETTRANSFORM) {
@@ -246,14 +247,15 @@ DrawTargetCaptureImpl::ContainsOnlyColor
       return false;
     }
 
     //TODO: Deal with AA on the DrawOptions, and the GlyphRenderingOptions
 
     aGlyphs.insert(aGlyphs.end(),
                    fillGlyphs->mGlyphs.begin(),
                    fillGlyphs->mGlyphs.end());
+    result = true;
   }
-  return true;
+  return result;
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/2d/MacIOSurface.cpp
+++ b/gfx/2d/MacIOSurface.cpp
@@ -6,16 +6,17 @@
 
 #include "MacIOSurface.h"
 #include <OpenGL/gl.h>
 #include <QuartzCore/QuartzCore.h>
 #include <dlfcn.h>
 #include "mozilla/RefPtr.h"
 #include "mozilla/Assertions.h"
 #include "GLConsts.h"
+#include "GLContextCGL.h"
 
 using namespace mozilla;
 // IOSurface signatures
 #define IOSURFACE_FRAMEWORK_PATH \
   "/System/Library/Frameworks/IOSurface.framework/IOSurface"
 #define OPENGL_FRAMEWORK_PATH \
   "/System/Library/Frameworks/OpenGL.framework/OpenGL"
 #define COREGRAPHICS_FRAMEWORK_PATH \
@@ -505,56 +506,91 @@ MacIOSurface::GetReadFormat()
   } else if (pixelFormat == '2vuy') {
     return SurfaceFormat::R8G8B8X8;
   } else  {
     return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
   }
 }
 
 CGLError
-MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, size_t plane)
+MacIOSurface::CGLTexImageIOSurface2D(mozilla::gl::GLContext* aGL,
+                                     CGLContextObj ctx,
+                                     size_t plane,
+                                     mozilla::gfx::SurfaceFormat* aOutReadFormat)
 {
   MOZ_ASSERT(plane >= 0);
+  bool isCompatibilityProfile = aGL->IsCompatibilityProfile();
   OSType pixelFormat = GetPixelFormat();
 
   GLenum internalFormat;
   GLenum format;
   GLenum type;
   if (pixelFormat == '420v') {
     MOZ_ASSERT(GetPlaneCount() == 2);
     MOZ_ASSERT(plane < 2);
 
+    // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated
+    // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile.
+    // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats
     if (plane == 0) {
-      internalFormat = format = GL_LUMINANCE;
+      internalFormat = format = (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE)
+                                                          : (LOCAL_GL_RED);
     } else {
-      internalFormat = format = GL_LUMINANCE_ALPHA;
+      internalFormat = format = (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA)
+                                                          : (LOCAL_GL_RG);
     }
-    type = GL_UNSIGNED_BYTE;
+    type = LOCAL_GL_UNSIGNED_BYTE;
+    if (aOutReadFormat) {
+      *aOutReadFormat = mozilla::gfx::SurfaceFormat::NV12;
+    }
   } else if (pixelFormat == '2vuy') {
     MOZ_ASSERT(plane == 0);
-
-    internalFormat = GL_RGB;
-    format = LOCAL_GL_YCBCR_422_APPLE;
-    type = GL_UNSIGNED_SHORT_8_8_APPLE;
+    // The YCBCR_422_APPLE ext is only available in compatibility profile. So,
+    // we should use RGB_422_APPLE for core profile. The difference between
+    // YCBCR_422_APPLE and RGB_422_APPLE is that the YCBCR_422_APPLE converts
+    // the YCbCr value to RGB with REC 601 conversion. But the RGB_422_APPLE
+    // doesn't contain color conversion. You should do the color conversion by
+    // yourself for RGB_422_APPLE.
+    //
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_ycbcr_422.txt
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    if (isCompatibilityProfile) {
+      format = LOCAL_GL_YCBCR_422_APPLE;
+      if (aOutReadFormat) {
+        *aOutReadFormat = mozilla::gfx::SurfaceFormat::R8G8B8X8;
+      }
+    } else {
+      format = LOCAL_GL_RGB_422_APPLE;
+      if (aOutReadFormat) {
+        *aOutReadFormat = mozilla::gfx::SurfaceFormat::YUV422;
+      }
+    }
+    internalFormat = LOCAL_GL_RGB;
+    type = LOCAL_GL_UNSIGNED_SHORT_8_8_APPLE;
   } else  {
     MOZ_ASSERT(plane == 0);
 
-    internalFormat = HasAlpha() ? GL_RGBA : GL_RGB;
-    format = GL_BGRA;
-    type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    internalFormat = HasAlpha() ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
+    format = LOCAL_GL_BGRA;
+    type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
+    if (aOutReadFormat) {
+      *aOutReadFormat = HasAlpha() ? mozilla::gfx::SurfaceFormat::R8G8B8A8
+                                  : mozilla::gfx::SurfaceFormat::R8G8B8X8;
+    }
   }
-  CGLError temp =  MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
-                                                GL_TEXTURE_RECTANGLE_ARB,
-                                                internalFormat,
-                                                GetDevicePixelWidth(plane),
-                                                GetDevicePixelHeight(plane),
-                                                format,
-                                                type,
-                                                mIOSurfacePtr, plane);
-  return temp;
+
+  return MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
+                                                 LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+                                                 internalFormat,
+                                                 GetDevicePixelWidth(plane),
+                                                 GetDevicePixelHeight(plane),
+                                                 format,
+                                                 type,
+                                                 mIOSurfacePtr,
+                                                 plane);
 }
 
 static
 CGColorSpaceRef CreateSystemColorSpace() {
   CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
   if (!cspace) {
     cspace = ::CGColorSpaceCreateDeviceRGB();
   }
@@ -591,25 +627,22 @@ already_AddRefed<MacIOSurface> MacIOSurf
   RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
   if (!ioSurface) {
     ::CFRelease(surfaceRef);
     return nullptr;
   }
   return ioSurface.forget();
 }
 
-
 CGContextType GetContextType(CGContextRef ref)
 {
   if (!MacIOSurfaceLib::isInit() || !MacIOSurfaceLib::sCGContextGetTypePtr)
     return CG_CONTEXT_TYPE_UNKNOWN;
 
   unsigned int type = MacIOSurfaceLib::sCGContextGetTypePtr(ref);
   if (type == CG_CONTEXT_TYPE_BITMAP) {
     return CG_CONTEXT_TYPE_BITMAP;
   } else if (type == CG_CONTEXT_TYPE_IOSURFACE) {
     return CG_CONTEXT_TYPE_IOSURFACE;
   } else {
     return CG_CONTEXT_TYPE_UNKNOWN;
   }
 }
-
-
--- a/gfx/2d/MacIOSurface.h
+++ b/gfx/2d/MacIOSurface.h
@@ -6,16 +6,22 @@
 
 #ifndef MacIOSurface_h__
 #define MacIOSurface_h__
 #ifdef XP_DARWIN
 #include <QuartzCore/QuartzCore.h>
 #include <CoreVideo/CoreVideo.h>
 #include <dlfcn.h>
 
+namespace mozilla {
+namespace gl {
+class GLContext;
+}
+}
+
 struct _CGLContextObject;
 
 typedef _CGLContextObject* CGLContextObj;
 typedef struct CGContext* CGContextRef;
 typedef struct CGImage* CGImageRef;
 typedef uint32_t IOSurfaceID;
 
 #ifdef XP_IOS
@@ -115,17 +121,20 @@ public:
   void IncrementUseCount();
   void DecrementUseCount();
   bool HasAlpha() { return mHasAlpha; }
   mozilla::gfx::SurfaceFormat GetFormat();
   mozilla::gfx::SurfaceFormat GetReadFormat();
 
   // We would like to forward declare NSOpenGLContext, but it is an @interface
   // and this file is also used from c++, so we use a void *.
-  CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, size_t plane = 0);
+  CGLError CGLTexImageIOSurface2D(mozilla::gl::GLContext* aGL,
+                                  CGLContextObj ctxt,
+                                  size_t plane,
+                                  mozilla::gfx::SurfaceFormat* aOutReadFormat = nullptr);
   already_AddRefed<SourceSurface> GetAsSurface();
   CGContextRef CreateIOSurfaceContext();
 
   // FIXME This doesn't really belong here
   static CGImageRef CreateImageFromIOSurfaceContext(CGContextRef aContext);
   static already_AddRefed<MacIOSurface> IOSurfaceContextGetSurface(CGContextRef aContext,
                                                                         double aContentsScaleFactor = 1.0,
                                                                         bool aHasAlpha = true);
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -207,16 +207,23 @@ ScaledFontBase::CopyGlyphsToBuilder(cons
     cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs);
 
     RefPtr<PathCairo> cairoPath = new PathCairo(ctx);
     cairo_destroy(ctx);
 
     cairoPath->AppendPathToBuilder(builder);
     return;
   }
+  if (backendType == BackendType::RECORDING) {
+    SkPath skPath = GetSkiaPathForGlyphs(aBuffer);
+    RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING);
+    path->StreamToSink(aBuilder);
+    return;
+  }
+  MOZ_ASSERT(false, "Path not being copied");
 #endif
 }
 
 void
 ScaledFontBase::GetGlyphDesignMetrics(const uint16_t* aGlyphs, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics)
 {
 #ifdef USE_CAIRO_SCALED_FONT
   if (mScaledFont) {
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -51,16 +51,18 @@ enum class SurfaceFormat : int8_t {
   // The _UINT16 suffix here indicates that the name reflects the layout when
   // viewed as a uint16_t value. In memory these values are stored using native
   // endianness.
   R5G6B5_UINT16,                    // 0bRRRRRGGGGGGBBBBB
 
   // This one is a single-byte, so endianness isn't an issue.
   A8,
 
+  R8G8,
+
   // These ones are their own special cases.
   YUV,
   NV12,
   YUV422,
   HSV,
   Lab,
   Depth,
 
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 8516d6c04235e684d9bf9c783ba4fc99dab3bf02
+Latest Commit: 102603520d52f335f152ab74b6bcfdae061b6bc8
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -773,24 +773,28 @@ GLBlitHelper::BlitMacIOSurfaceImage(laye
 
     GLuint textures[2];
     mGL->fGenTextures(2, textures);
 
     mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
     mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[0]);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 0);
+    surf->CGLTexImageIOSurface2D(mGL,
+                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
+                                 0);
     mGL->fUniform2f(mYTexScaleLoc, surf->GetWidth(0), surf->GetHeight(0));
 
     mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
     mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[1]);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
     mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 1);
+    surf->CGLTexImageIOSurface2D(mGL,
+                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
+                                 1);
     mGL->fUniform2f(mCbCrTexScaleLoc, surf->GetWidth(1), surf->GetHeight(1));
 
     mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
     for (int i = 0; i < 2; i++) {
         mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
         mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
     }
 
--- a/gfx/gl/SharedSurfaceIO.cpp
+++ b/gfx/gl/SharedSurfaceIO.cpp
@@ -137,17 +137,17 @@ BackTextureWithIOSurf(GLContext* gl, GLu
                         LOCAL_GL_CLAMP_TO_EDGE);
     gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                         LOCAL_GL_TEXTURE_WRAP_T,
                         LOCAL_GL_CLAMP_TO_EDGE);
 
     CGLContextObj cgl = GLContextCGL::Cast(gl)->GetCGLContext();
     MOZ_ASSERT(cgl);
 
-    ioSurf->CGLTexImageIOSurface2D(cgl);
+    ioSurf->CGLTexImageIOSurface2D(gl, cgl, 0);
 }
 
 SharedSurface_IOSurface::SharedSurface_IOSurface(const RefPtr<MacIOSurface>& ioSurf,
                                                  GLContext* gl,
                                                  const gfx::IntSize& size,
                                                  bool hasAlpha)
   : SharedSurface(SharedSurfaceType::IOSurface,
                   AttachmentType::GLTexture,
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -591,16 +591,21 @@ public:
    * Make sure that the previous transaction has been entirely
    * completed.
    *
    * Note: This may sychronously wait on a remote compositor
    * to complete rendering.
    */
   virtual void FlushRendering() { }
 
+  /**
+   * Make sure that the previous transaction has been
+   * received. This will synchronsly wait on a remote compositor. */
+  virtual void WaitOnTransactionProcessed() { }
+
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) {}
 
   /**
    * Checks if we need to invalidate the OS widget to trigger
    * painting when updating this layer manager.
    */
   virtual bool NeedsWidgetInvalidation() { return true; }
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/mozalloc.h"           // for operator new
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "TreeTraversal.h"              // for ForEachNode, BreadthFirstSearch, etc
 #include "LayersLogging.h"              // for Stringify
 #include "Units.h"                      // for ParentlayerPixel
 #include "GestureEventListener.h"       // for GestureEventListener::setLongTapEnabled
@@ -359,16 +360,70 @@ APZCTreeManager::UpdateHitTestingTree(ui
                                       uint64_t aOriginatingLayersId,
                                       uint32_t aPaintSequenceNumber)
 {
   WebRenderScrollDataWrapper wrapper(&aScrollData);
   UpdateHitTestingTreeImpl(aRootLayerTreeId, wrapper, aIsFirstPaint,
                            aOriginatingLayersId, aPaintSequenceNumber);
 }
 
+bool
+APZCTreeManager::PushStateToWR(wr::WebRenderAPI* aWrApi,
+                               const TimeStamp& aSampleTime)
+{
+  APZThreadUtils::AssertOnCompositorThread();
+  MOZ_ASSERT(aWrApi);
+
+  MutexAutoLock lock(mTreeLock);
+
+  bool activeAnimations = false;
+  uint64_t lastLayersId = -1;
+  WrPipelineId lastPipelineId;
+
+  // We iterate backwards here because the HitTestingTreeNode is optimized
+  // for backwards iteration. The equivalent code in AsyncCompositionManager
+  // iterates forwards, but the direction shouldn't really matter in practice
+  // so we do what's faster. In the future, if we need to start doing the
+  // equivalent of AlignFixedAndStickyLayers here, then the order will become
+  // important and we'll need to take that into consideration.
+  ForEachNode<ReverseIterator>(mRootNode.get(),
+      [&](HitTestingTreeNode* aNode)
+      {
+        if (!aNode->IsPrimaryHolder()) {
+          return;
+        }
+        AsyncPanZoomController* apzc = aNode->GetApzc();
+        MOZ_ASSERT(apzc);
+
+        if (aNode->GetLayersId() != lastLayersId) {
+          // If we walked into or out of a subtree, we need to get the new
+          // pipeline id.
+          lastLayersId = aNode->GetLayersId();
+          const LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(lastLayersId);
+          MOZ_ASSERT(state && state->mWrBridge);
+          lastPipelineId = state->mWrBridge->PipelineId();
+        }
+
+        ParentLayerPoint layerTranslation = apzc->GetCurrentAsyncTransform(
+            AsyncPanZoomController::RESPECT_FORCE_DISABLE).mTranslation;
+        // The positive translation means the painted content is supposed to
+        // move down (or to the right), and that corresponds to a reduction in
+        // the scroll offset. Since we are effectively giving WR the async
+        // scroll delta here, we want to negate the translation.
+        ParentLayerPoint asyncScrollDelta = -layerTranslation;
+        aWrApi->UpdateScrollPosition(lastPipelineId, apzc->GetGuid().mScrollId,
+            wr::ToWrPoint(asyncScrollDelta));
+
+        apzc->ReportCheckerboard(aSampleTime);
+        activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
+      });
+
+  return activeAnimations;
+}
+
 // Compute the clip region to be used for a layer with an APZC. This function
 // is only called for layers which actually have scrollable metrics and an APZC.
 template<class ScrollNode> static ParentLayerIntRegion
 ComputeClipRegion(GeckoContentController* aController,
                   const ScrollNode& aLayer)
 {
   ParentLayerIntRegion clipRegion;
   if (aLayer.GetClipRect()) {
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -22,16 +22,20 @@
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 
 namespace mozilla {
 class MultiTouchInput;
 
+namespace wr {
+class WebRenderAPI;
+}
+
 namespace layers {
 
 class Layer;
 class AsyncPanZoomController;
 class APZCTreeManagerParent;
 class CompositorBridgeParent;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
@@ -140,16 +144,28 @@ public:
    */
   void UpdateHitTestingTree(uint64_t aRootLayerTreeId,
                             const WebRenderScrollData& aScrollData,
                             bool aIsFirstPaint,
                             uint64_t aOriginatingLayersId,
                             uint32_t aPaintSequenceNumber);
 
   /**
+   * Called when webrender is enabled, from the compositor thread. This function
+   * walks through the tree of APZC instances and tells webrender about the
+   * async scroll position. It also advances APZ animations to the specified
+   * sample time. In effect it is the webrender equivalent of (part of) the
+   * code in AsyncCompositionManager.
+   * Returns true if any APZ animations are in progress and we need to keep
+   * compositing.
+   */
+  bool PushStateToWR(wr::WebRenderAPI* aWrApi,
+                     const TimeStamp& aSampleTime);
+
+  /**
    * Walk the tree of APZCs and flushes the repaint requests for all the APZCS
    * corresponding to the given layers id. Finally, sends a flush complete
    * notification to the GeckoContentController for the layers id.
    */
   void FlushApzRepaints(uint64_t aLayersId);
 
   /**
    * General handler for incoming input events. Manipulates the frame metrics
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -652,16 +652,24 @@ ClientLayerManager::FlushRendering()
       } else {
         remoteRenderer->SendFlushRenderingAsync();
       }
     }
   }
 }
 
 void
+ClientLayerManager::WaitOnTransactionProcessed()
+{
+  CompositorBridgeChild* remoteRenderer = GetCompositorBridgeChild();
+  if (remoteRenderer) {
+    remoteRenderer->SendWaitOnTransactionProcessed();
+  }
+}
+void
 ClientLayerManager::UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier,
                                                    uint64_t aDeviceResetSeqNo)
 {
   MOZ_ASSERT_IF(XRE_IsContentProcess(),
                 aDeviceResetSeqNo == CompositorBridgeChild::Get()->DeviceResetSequenceNumber());
 
   mForwarder->IdentifyTextureHost(aNewIdentifier);
   mDeviceResetSequenceNumber = aDeviceResetSeqNo;
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -109,16 +109,17 @@ public:
   virtual void UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier,
 											  uint64_t aDeviceResetSeqNo) override;
   virtual TextureFactoryIdentifier GetTextureFactoryIdentifier() override
   {
     return AsShadowForwarder()->GetTextureFactoryIdentifier();
   }
 
   virtual void FlushRendering() override;
+  virtual void WaitOnTransactionProcessed() override;
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) override;
 
   virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize) override;
 
   virtual void StopFrameTimeRecording(uint32_t         aStartIndex,
                                       nsTArray<float>& aFrameIntervals) override;
 
   virtual bool NeedsWidgetInvalidation() override { return false; }
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor, etc
 #include "mozilla/layers/TextureHostBasic.h"
 #include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/TextureClient.h"
 #include "mozilla/layers/GPUVideoTextureHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "nsAString.h"
 #include "mozilla/RefPtr.h"                   // for nsRefPtr
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "mozilla/layers/PTextureParent.h"
 #include "mozilla/Unused.h"
 #include <limits>
 #include "../opengl/CompositorOGL.h"
 #include "gfxPrefs.h"
@@ -551,16 +552,43 @@ BufferTextureHost::Lock()
 void
 BufferTextureHost::Unlock()
 {
   MOZ_ASSERT(mLocked);
   mLocked = false;
 }
 
 void
+BufferTextureHost::AddWRImage(wr::WebRenderAPI* aAPI,
+                              Range<const wr::ImageKey>& aImageKeys,
+                              const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT(aImageKeys.length() == 1);
+  // XXX handling YUV
+  gfx::SurfaceFormat wrFormat =
+      (GetFormat() == gfx::SurfaceFormat::YUV) ? gfx::SurfaceFormat::B8G8R8A8
+                                               : GetFormat();
+  gfx::SurfaceFormat format = GetFormat();
+  uint32_t wrStride = 0;
+
+  if (format == gfx::SurfaceFormat::YUV) {
+    // XXX this stride is used until yuv image rendering by webrender is used.
+    // Software converted RGB buffers strides are aliened to 16
+    wrStride = gfx::GetAlignedStride<16>(GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8));
+  } else {
+    wrStride = ImageDataSerializer::ComputeRGBStride(format, GetSize().width);
+  }
+
+  wr::ImageDescriptor descriptor(GetSize(), wrStride, wrFormat);
+  aAPI->AddExternalImageBuffer(aImageKeys[0],
+                               descriptor,
+                               aExtID);
+}
+
+void
 TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc,
                                  ISurfaceAllocator* aAllocator)
 {
   RefPtr<TextureReadLock> lock = TextureReadLock::Deserialize(aDesc, aAllocator);
   if (!lock) {
     return;
   }
 
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -15,32 +15,37 @@
 #include "mozilla/gfx/2D.h"             // for DataSourceSurface
 #include "mozilla/gfx/Point.h"          // for IntSize, IntPoint
 #include "mozilla/gfx/Types.h"          // for SurfaceFormat, etc
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"  // for TextureFlags, etc
 #include "mozilla/layers/LayersTypes.h"  // for LayerRenderState, etc
 #include "mozilla/layers/LayersSurfaces.h"
 #include "mozilla/mozalloc.h"           // for operator delete
+#include "mozilla/Range.h"
 #include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 #include "nscore.h"                     // for nsACString
 #include "mozilla/layers/AtomicRefCountedWithFinalize.h"
 #include "mozilla/gfx/Rect.h"
 
 namespace mozilla {
 namespace ipc {
 class Shmem;
 } // namespace ipc
 
+namespace wr {
+class WebRenderAPI;
+}
+
 namespace layers {
 
 class BufferDescriptor;
 class BufferTextureHost;
 class Compositor;
 class CompositableParentManager;
 class ReadLockDescriptor;
 class CompositorBridgeParent;
@@ -586,16 +591,25 @@ public:
   void SetReadLock(TextureReadLock* aReadLock);
 
   TextureReadLock* GetReadLock() { return mReadLock; }
 
   virtual BufferTextureHost* AsBufferTextureHost() { return nullptr; }
   virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() { return nullptr; }
   virtual WebRenderTextureHost* AsWebRenderTextureHost() { return nullptr; }
 
+  // Add all necessary textureHost informations to WebrenderAPI. Then, WR could
+  // use these informations to compose this textureHost.
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID)
+  {
+    MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this TextureHost type.");
+  }
+
 protected:
   void ReadUnlock();
 
   void RecycleTexture(TextureFlags aFlags);
 
   virtual void UpdatedInternal(const nsIntRegion *Region) {}
 
   /**
@@ -673,16 +687,20 @@ public:
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
 
   virtual bool HasIntermediateBuffer() const override { return mHasIntermediateBuffer; }
 
   virtual BufferTextureHost* AsBufferTextureHost() override { return this; }
 
   const BufferDescriptor& GetBufferDescriptor() const { return mDescriptor; }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   bool Upload(nsIntRegion *aRegion = nullptr);
   bool MaybeUpload(nsIntRegion *aRegion = nullptr);
   bool EnsureWrappingTextureSource();
 
   virtual void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override;
 
   BufferDescriptor mDescriptor;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -9,16 +9,17 @@
 #include "Effects.h"
 #include "gfxWindowsPlatform.h"
 #include "gfx2DGlue.h"
 #include "gfxPrefs.h"
 #include "ReadbackManagerD3D11.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
 static const GUID sD3D11TextureUsage =
@@ -849,16 +850,24 @@ DXGITextureHostD3D11::BindTextureSource(
 {
   MOZ_ASSERT(mIsLocked);
   // If Lock was successful we must have a valid TextureSource.
   MOZ_ASSERT(mTextureSource);
   aTexture = mTextureSource;
   return !!aTexture;
 }
 
+void
+DXGITextureHostD3D11::AddWRImage(wr::WebRenderAPI* aAPI,
+                                 Range<const wr::ImageKey>& aImageKeys,
+                                 const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this DXGITextureHostD3D11 type.");
+}
+
 DXGIYCbCrTextureHostD3D11::DXGIYCbCrTextureHostD3D11(TextureFlags aFlags,
   const SurfaceDescriptorDXGIYCbCr& aDescriptor)
   : TextureHost(aFlags)
   , mSize(aDescriptor.size())
   , mIsLocked(false)
 {
   mHandles[0] = aDescriptor.handleY();
   mHandles[1] = aDescriptor.handleCb();
@@ -982,16 +991,24 @@ DXGIYCbCrTextureHostD3D11::BindTextureSo
 {
   MOZ_ASSERT(mIsLocked);
   // If Lock was successful we must have a valid TextureSource.
   MOZ_ASSERT(mTextureSources[0] && mTextureSources[1] && mTextureSources[2]);
   aTexture = mTextureSources[0].get();
   return !!aTexture;
 }
 
+void
+DXGIYCbCrTextureHostD3D11::AddWRImage(wr::WebRenderAPI* aAPI,
+                                      Range<const wr::ImageKey>& aImageKeys,
+                                      const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT_UNREACHABLE("No AddWRImage() implementation for this DXGIYCbCrTextureHostD3D11 type.");
+}
+
 bool
 DataTextureSourceD3D11::Update(DataSourceSurface* aSurface,
                                nsIntRegion* aDestRegion,
                                IntPoint* aSrcOffset)
 {
   // Incremental update with a source offset is only used on Mac so it is not
   // clear that we ever will need to support it for D3D.
   MOZ_ASSERT(!aSrcOffset);
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -320,16 +320,20 @@ public:
 
   virtual gfx::IntSize GetSize() const override { return mSize; }
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
   {
     return nullptr;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   bool LockInternal();
   void UnlockInternal();
 
   RefPtr<ID3D11Device> GetDevice();
 
   bool OpenSharedHandle();
 
@@ -365,16 +369,20 @@ public:
 
   virtual gfx::IntSize GetSize() const override { return mSize; }
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
   {
     return nullptr;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   RefPtr<ID3D11Device> GetDevice();
 
   bool OpenSharedHandle();
 
   RefPtr<ID3D11Texture2D> mTextures[3];
   RefPtr<DataTextureSourceD3D11> mTextureSources[3];
 
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1172,15 +1172,15 @@ CompositorBridgeChild::DeallocPWebRender
 wr::MaybeExternalImageId
 CompositorBridgeChild::GetNextExternalImageId()
 {
   static uint32_t sNextID = 1;
   ++sNextID;
   MOZ_RELEASE_ASSERT(sNextID != UINT32_MAX);
 
   uint64_t imageId = mNamespace;
-  imageId = imageId << 32 | sNextID;
+  imageId = (imageId << 32) | sNextID;
   return Some(wr::ToExternalImageId(imageId));
 }
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -519,16 +519,22 @@ CompositorBridgeParent::RecvMakeSnapshot
     // of error to the child process and let it deal with it...
     return IPC_FAIL_NO_REASON(this);
   }
   ForceComposeToTarget(target, &aRect);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+CompositorBridgeParent::RecvWaitOnTransactionProcessed()
+{
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvFlushRendering()
 {
   if (mCompositorScheduler->NeedsComposite()) {
     CancelCurrentCompositeTask();
     ForceComposeToTarget(nullptr);
   }
   return IPC_OK();
 }
@@ -1590,17 +1596,18 @@ CompositorBridgeParent::AllocPWebRenderB
   MOZ_ASSERT(!mCompositor);
   MOZ_ASSERT(!mCompositorScheduler);
 
 
   MOZ_ASSERT(mWidget);
   RefPtr<widget::CompositorWidget> widget = mWidget;
   RefPtr<wr::WebRenderAPI> api = wr::WebRenderAPI::Create(
     gfxPrefs::WebRenderProfilerEnabled(), this, Move(widget), aSize);
-  RefPtr<WebRenderCompositableHolder> holder = new WebRenderCompositableHolder();
+  RefPtr<WebRenderCompositableHolder> holder =
+    new WebRenderCompositableHolder(WebRenderBridgeParent::AllocIdNameSpace());
   MOZ_ASSERT(api); // TODO have a fallback
   api->SetRootPipeline(aPipelineId);
   mWrBridge = new WebRenderBridgeParent(this, aPipelineId, mWidget, nullptr, Move(api), Move(holder));
   *aIdNamespace = mWrBridge->GetIdNameSpace();
 
   mCompositorScheduler = mWrBridge->CompositorScheduler();
   MOZ_ASSERT(mCompositorScheduler);
   mWrBridge.get()->AddRef(); // IPDL reference
@@ -1626,16 +1633,22 @@ CompositorBridgeParent::DeallocPWebRende
     if (it != sIndirectLayerTrees.end()) {
       it->second.mWrBridge = nullptr;
     }
   }
   parent->Release(); // IPDL reference
   return true;
 }
 
+RefPtr<WebRenderBridgeParent>
+CompositorBridgeParent::GetWebRenderBridgeParent() const
+{
+  return mWrBridge;
+}
+
 void
 CompositorBridgeParent::SetWebRenderProfilerEnabled(bool aEnabled)
 {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); it++) {
     LayerTreeState* state = &it->second;
     if (state->mWrBridge) {
       state->mWrBridge->SetWebRenderProfilerEnabled(aEnabled);
@@ -1829,22 +1842,24 @@ CompositorBridgeParent::DidComposite(Tim
     NotifyDidComposite(mPendingTransaction, aCompositeStart, aCompositeEnd);
     mPendingTransaction = 0;
   }
 }
 
 void
 CompositorBridgeParent::NotifyDidCompositeToPipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd)
 {
+  if (!mWrBridge) {
+    return;
+  }
+  mWrBridge->CompositableHolder()->Update(aPipelineId, aEpoch);
+
   if (mPaused) {
     return;
   }
-  MOZ_ASSERT(mWrBridge);
-
-  mWrBridge->CompositableHolder()->Update(aPipelineId, aEpoch);
 
   if (mWrBridge->PipelineId() == aPipelineId) {
     uint64_t transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch);
     Unused << SendDidComposite(0, transactionId, aCompositeStart, aCompositeEnd);
 
     nsTArray<ImageCompositeNotificationInfo> notifications;
     mWrBridge->ExtractImageCompositeNotifications(&notifications);
     if (!notifications.IsEmpty()) {
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -194,16 +194,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvNotifyChildCreated(const uint64_t& child, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvMapAndNotifyChildCreated(const uint64_t& child, const base::ProcessId& pid, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvNotifyChildRecreated(const uint64_t& child, CompositorOptions* aOptions) override;
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const uint64_t& child) override;
   virtual mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 const gfx::IntRect& aRect) override;
   virtual mozilla::ipc::IPCResult RecvFlushRendering() override;
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() override;
+  virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override;
   virtual mozilla::ipc::IPCResult RecvForcePresent() override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override;
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override;
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override;
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
@@ -442,21 +443,28 @@ public:
   bool DeallocPAPZParent(PAPZParent* aActor) override;
 
   RefPtr<APZCTreeManager> GetAPZCTreeManager();
 
   CompositorOptions GetOptions() const {
     return mOptions;
   }
 
+  TimeDuration GetVsyncInterval() const {
+    // the variable is called "rate" but really it's an interval
+    return mVsyncRate;
+  }
+
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                       uint32_t* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
+  RefPtr<WebRenderBridgeParent> GetWebRenderBridgeParent() const;
+
   static void SetWebRenderProfilerEnabled(bool aEnabled);
 
   static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId(const uint64_t& aLayersId);
 
 #if defined(MOZ_WIDGET_ANDROID)
   gfx::IntSize GetEGLSurfaceSize() {
     return mEGLSurfaceSize;
   }
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -55,16 +55,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvNotifyChildRecreated(const uint64_t& child, CompositorOptions* aOptions) override { return IPC_FAIL_NO_REASON(this); }
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const uint64_t& child) override { return IPC_FAIL_NO_REASON(this); }
   virtual mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 const gfx::IntRect& aRect) override
   { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvFlushRendering() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvForcePresent() override { return IPC_OK(); }
+  virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override { return IPC_OK(); }
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override  { return IPC_OK(); }
 
   virtual mozilla::ipc::IPCResult RecvClearApproximatelyVisibleRegions(const uint64_t& aLayersId,
                                                     const uint32_t& aPresShellId) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyApproximatelyVisibleRegion(const ScrollableLayerGuid& aGuid,
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -189,16 +189,19 @@ parent:
   // Make sure any pending composites are started immediately and
   // block until they are completed.
   sync FlushRendering();
 
   // Same as FlushRendering, but asynchronous, since not all platforms require
   // synchronous repaints on resize.
   async FlushRenderingAsync();
 
+  // Make sure any pending composites have been received.
+  sync WaitOnTransactionProcessed();
+
   // Force an additional frame presentation to be executed. This is used to
   // work around a windows presentation bug (See Bug 1232042)
   async ForcePresent();
 
   sync StartFrameTimeRecording(int32_t bufferSize)
     returns (uint32_t startIndex);
 
   sync StopFrameTimeRecording(uint32_t startIndex)
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -16,17 +16,17 @@ include protocol PTexture;
 
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
 using WrBuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
-using WrAuxiliaryListsDescriptor from "mozilla/webrender/webrender_ffi.h";
+using WrSize from "mozilla/webrender/webrender_ffi.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 
 namespace mozilla {
 namespace layers {
 
 sync protocol PWebRenderBridge
 {
   manager PCompositorBridge;
@@ -48,20 +48,20 @@ parent:
   sync UpdateImage(ImageKey aImageKey, IntSize aSize,
                    SurfaceFormat aFormat, ByteBuffer aBytes);
   sync DeleteImage(ImageKey aImageKey);
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
   async DeleteFont(FontKey aFontKey);
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
-              ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc, ByteBuffer aAux, WrAuxiliaryListsDescriptor aAuxDesc,
+              WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
               WebRenderScrollData aScrollData);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
-                 ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc, ByteBuffer aAux, WrAuxiliaryListsDescriptor aAuxDesc,
+                 WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
                  WebRenderScrollData aScrollData);
   sync DPGetSnapshot(PTexture texture);
   async AddExternalImageId(ExternalImageId aImageId, CompositableHandle aHandle);
   async AddExternalImageIdForCompositable(ExternalImageId aImageId, CompositableHandle aHandle);
   async RemoveExternalImageId(ExternalImageId aImageId);
   async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -26,22 +26,28 @@ union OptionalOpacity {
   void_t;
 };
 
 struct OpAddExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
 };
 
+struct OpAddExternalVideoImage {
+  ExternalImageId externalImageId;
+  ImageKey[] keys;
+};
+
 struct OpAddCompositorAnimations {
   CompositorAnimations data;
   OptionalTransform transform;
   OptionalOpacity opacity;
 };
 
 union WebRenderParentCommand {
   OpAddExternalImage;
+  OpAddExternalVideoImage;
   CompositableOperation;
   OpAddCompositorAnimations;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -197,16 +197,17 @@ EXPORTS.mozilla.layers += [
     'PersistentBufferProvider.h',
     'RenderTrace.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
     'UpdateImageHelper.h',
+    'wr/ScrollingLayersHelper.h',
     'wr/StackingContextHelper.h',
     'wr/WebRenderBridgeChild.h',
     'wr/WebRenderBridgeParent.h',
     'wr/WebRenderCompositableHolder.h',
     'wr/WebRenderDisplayItemLayer.h',
     'wr/WebRenderImageHost.h',
     'wr/WebRenderLayer.h',
     'wr/WebRenderLayerManager.h',
@@ -392,16 +393,17 @@ UNIFIED_SOURCES += [
     'ReadbackProcessor.cpp',
     'RenderTrace.cpp',
     'RotatedBuffer.cpp',
     'ShareableCanvasLayer.cpp',
     'SourceSurfaceSharedData.cpp',
     'SourceSurfaceVolatileData.cpp',
     'TextureSourceProvider.cpp',
     'TextureWrapperImage.cpp',
+    'wr/ScrollingLayersHelper.cpp',
     'wr/StackingContextHelper.cpp',
     'wr/WebRenderBridgeChild.cpp',
     'wr/WebRenderBridgeParent.cpp',
     'wr/WebRenderCanvasLayer.cpp',
     'wr/WebRenderColorLayer.cpp',
     'wr/WebRenderCompositableHolder.cpp',
     'wr/WebRenderContainerLayer.cpp',
     'wr/WebRenderDisplayItemLayer.cpp',
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MacIOSurfaceTextureHostOGL.h"
 #include "mozilla/gfx/MacIOSurface.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 #include "GLContextCGL.h"
 
 namespace mozilla {
 namespace layers {
 
 MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
                                                        const SurfaceDescriptorMacIOSurface& aDescriptor)
   : TextureHost(aFlags)
@@ -30,17 +31,23 @@ MacIOSurfaceTextureHostOGL::CreateTextur
 {
   GLuint textureHandle;
   gl::GLContext* gl = mProvider->GetGLContext();
   gl->fGenTextures(1, &textureHandle);
   gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textureHandle);
   gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
   gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
 
-  mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext(), aPlane);
+  gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN;
+  mSurface->CGLTexImageIOSurface2D(gl,
+                                   gl::GLContextCGL::Cast(gl)->GetCGLContext(),
+                                   aPlane,
+                                   &readFormat);
+  // With compositorOGL, we doesn't support the yuv interleaving format yet.
+  MOZ_ASSERT(readFormat != gfx::SurfaceFormat::YUV422);
 
   return new GLTextureSource(mProvider, textureHandle, LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                              gfx::IntSize(mSurface->GetDevicePixelWidth(aPlane),
                                           mSurface->GetDevicePixelHeight(aPlane)),
                              // XXX: This isn't really correct (but isn't used), we should be using the
                              // format of the individual plane, not of the whole buffer.
                              mSurface->GetFormat());
 }
@@ -102,76 +109,70 @@ MacIOSurfaceTextureHostOGL::GetSize() co
 }
 
 gl::GLContext*
 MacIOSurfaceTextureHostOGL::gl() const
 {
   return mProvider ? mProvider->GetGLContext() : nullptr;
 }
 
-MacIOSurfaceTextureSourceOGL::MacIOSurfaceTextureSourceOGL(
-                                CompositorOGL* aCompositor,
-                                MacIOSurface* aSurface)
-  : mCompositor(aCompositor)
-  , mSurface(aSurface)
-{
-  MOZ_ASSERT(aCompositor);
-  MOZ_COUNT_CTOR(MacIOSurfaceTextureSourceOGL);
-}
-
-MacIOSurfaceTextureSourceOGL::~MacIOSurfaceTextureSourceOGL()
+void
+MacIOSurfaceTextureHostOGL::AddWRImage(wr::WebRenderAPI* aAPI,
+                                       Range<const wr::ImageKey>& aImageKeys,
+                                       const wr::ExternalImageId& aExtID)
 {
-  MOZ_COUNT_DTOR(MacIOSurfaceTextureSourceOGL);
-}
-
-gfx::IntSize
-MacIOSurfaceTextureSourceOGL::GetSize() const
-{
-  return gfx::IntSize(mSurface->GetDevicePixelWidth(),
-                      mSurface->GetDevicePixelHeight());
-}
-
-gfx::SurfaceFormat
-MacIOSurfaceTextureSourceOGL::GetFormat() const
-{
-  return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
-                              : gfx::SurfaceFormat::R8G8B8X8;
-}
+  MOZ_ASSERT(mSurface);
 
-void
-MacIOSurfaceTextureSourceOGL::BindTexture(GLenum aTextureUnit,
-                                          gfx::SamplingFilter aSamplingFilter)
-{
-  gl::GLContext* gl = this->gl();
-  if (!gl || !gl->MakeCurrent()) {
-    NS_WARNING("Trying to bind a texture without a working GLContext");
-    return;
+  switch (GetFormat()) {
+    case gfx::SurfaceFormat::R8G8B8X8:
+    case gfx::SurfaceFormat::R8G8B8A8: {
+      MOZ_ASSERT(aImageKeys.length() == 1);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
+      wr::ImageDescriptor descriptor(GetSize(), GetFormat());
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      break;
+    }
+    case gfx::SurfaceFormat::YUV422: {
+      // This is the special buffer format. The buffer contents could be a
+      // converted RGB interleaving data or a YCbCr interleaving data depending
+      // on the different platform setting. (e.g. It will be RGB at OpenGL 2.1
+      // and YCbCr at OpenGL 3.1)
+      MOZ_ASSERT(aImageKeys.length() == 1);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
+      wr::ImageDescriptor descriptor(GetSize(), gfx::SurfaceFormat::R8G8B8X8);
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      break;
+    }
+    case gfx::SurfaceFormat::NV12: {
+      MOZ_ASSERT(aImageKeys.length() == 2);
+      MOZ_ASSERT(mSurface->GetPlaneCount() == 2);
+      wr::ImageDescriptor descriptor0(gfx::IntSize(mSurface->GetDevicePixelWidth(0), mSurface->GetDevicePixelHeight(0)),
+                                      gfx::SurfaceFormat::A8);
+      wr::ImageDescriptor descriptor1(gfx::IntSize(mSurface->GetDevicePixelWidth(1), mSurface->GetDevicePixelHeight(1)),
+                                      gfx::SurfaceFormat::R8G8);
+      aAPI->AddExternalImage(aImageKeys[0],
+                             descriptor0,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             0);
+      aAPI->AddExternalImage(aImageKeys[1],
+                             descriptor1,
+                             aExtID,
+                             WrExternalImageBufferType::TextureRectHandle,
+                             1);
+      break;
+    }
+    default: {
+      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+    }
   }
-  GLuint tex = mCompositor->GetTemporaryTexture(GetTextureTarget(), aTextureUnit);
-
-  gl->fActiveTexture(aTextureUnit);
-  gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, tex);
-  mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext());
-  ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
-}
-
-void
-MacIOSurfaceTextureSourceOGL::SetTextureSourceProvider(TextureSourceProvider* aProvider)
-{
-  CompositorOGL* ogl = nullptr;
-  if (Compositor* compositor = aProvider->AsCompositor()) {
-    ogl = compositor->AsCompositorOGL();
-  }
-
-  mCompositor = ogl;
-  if (mCompositor && mNextSibling) {
-    mNextSibling->SetTextureSourceProvider(aProvider);
-  }
-}
-
-gl::GLContext*
-MacIOSurfaceTextureSourceOGL::gl() const
-{
-  return mCompositor ? mCompositor->gl() : nullptr;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
@@ -10,59 +10,16 @@
 #include "mozilla/layers/TextureHostOGL.h"
 
 class MacIOSurface;
 
 namespace mozilla {
 namespace layers {
 
 /**
- * A texture source meant for use with MacIOSurfaceTextureHostOGL.
- *
- * It does not own any GL texture, and attaches its shared handle to one of
- * the compositor's temporary textures when binding.
- */
-class MacIOSurfaceTextureSourceOGL : public TextureSource
-                                   , public TextureSourceOGL
-{
-public:
-  MacIOSurfaceTextureSourceOGL(CompositorOGL* aCompositor,
-                               MacIOSurface* aSurface);
-  virtual ~MacIOSurfaceTextureSourceOGL();
-
-  virtual const char* Name() const override { return "MacIOSurfaceTextureSourceOGL"; }
-
-  virtual TextureSourceOGL* AsSourceOGL() override { return this; }
-
-  virtual void BindTexture(GLenum activetex,
-                           gfx::SamplingFilter aSamplingFilter) override;
-
-  virtual bool IsValid() const override { return !!gl(); }
-
-  virtual gfx::IntSize GetSize() const override;
-
-  virtual gfx::SurfaceFormat GetFormat() const override;
-
-  virtual GLenum GetTextureTarget() const override { return LOCAL_GL_TEXTURE_RECTANGLE_ARB; }
-
-  virtual GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; }
-
-  // MacIOSurfaceTextureSourceOGL doesn't own any gl texture
-  virtual void DeallocateDeviceData() override {}
-
-  virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
-
-  gl::GLContext* gl() const;
-
-protected:
-  RefPtr<CompositorOGL> mCompositor;
-  RefPtr<MacIOSurface> mSurface;
-};
-
-/**
  * A TextureHost for shared MacIOSurface
  *
  * Most of the logic actually happens in MacIOSurfaceTextureSourceOGL.
  */
 class MacIOSurfaceTextureHostOGL : public TextureHost
 {
 public:
   MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
@@ -100,16 +57,20 @@ public:
 
   virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() override { return this; }
 
   MacIOSurface* GetMacIOSurface()
   {
     return mSurface;
   }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   GLTextureSource* CreateTextureSourceForPlane(size_t aPlane);
 
   RefPtr<GLTextureSource> mTextureSource;
   RefPtr<MacIOSurface> mSurface;
 };
 
 } // namespace layers
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/ScrollingLayersHelper.h"
+
+#include "FrameMetrics.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayer.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "UnitTransforms.h"
+
+namespace mozilla {
+namespace layers {
+
+ScrollingLayersHelper::ScrollingLayersHelper(WebRenderLayer* aLayer,
+                                             wr::DisplayListBuilder& aBuilder,
+                                             const StackingContextHelper& aStackingContext)
+  : mLayer(aLayer)
+  , mBuilder(&aBuilder)
+{
+  if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
+    // If APZ is disabled then we don't need to push the scrolling clips
+    return;
+  }
+
+  Layer* layer = mLayer->GetLayer();
+  for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; i--) {
+    const FrameMetrics& fm = layer->GetFrameMetrics(i - 1);
+    if (!fm.IsScrollable()) {
+      return;
+    }
+    LayoutDeviceRect contentRect = fm.GetExpandedScrollableRect()
+        * fm.GetDevPixelsPerCSSPixel();
+    // TODO: check coordinate systems are sane here
+    LayerRect clipBounds = ViewAs<LayerPixel>(
+        fm.GetCompositionBounds(),
+        PixelCastJustification::MovingDownToChildren);
+    mBuilder->PushScrollLayer(fm.GetScrollId(),
+        aStackingContext.ToRelativeWrRect(contentRect),
+        aStackingContext.ToRelativeWrRect(clipBounds));
+  }
+}
+
+ScrollingLayersHelper::~ScrollingLayersHelper()
+{
+  if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
+    return;
+  }
+
+  Layer* layer = mLayer->GetLayer();
+  for (int32_t i = layer->GetScrollMetadataCount(); i > 0; i--) {
+    const FrameMetrics& fm = layer->GetFrameMetrics(i - 1);
+    if (!fm.IsScrollable()) {
+      return;
+    }
+    mBuilder->PopScrollLayer();
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/ScrollingLayersHelper.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#ifndef GFX_SCROLLINGLAYERSHELPER_H
+#define GFX_SCROLLINGLAYERSHELPER_H
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+namespace wr {
+class DisplayListBuilder;
+}
+
+namespace layers {
+
+class StackingContextHelper;
+class WebRenderLayer;
+
+class MOZ_RAII ScrollingLayersHelper
+{
+public:
+  ScrollingLayersHelper(WebRenderLayer* aLayer,
+                        wr::DisplayListBuilder& aBuilder,
+                        const StackingContextHelper& aSc);
+  ~ScrollingLayersHelper();
+
+private:
+  WebRenderLayer* mLayer;
+  wr::DisplayListBuilder* mBuilder;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -19,18 +19,16 @@ StackingContextHelper::StackingContextHe
 
 StackingContextHelper::StackingContextHelper(const StackingContextHelper& aParentSC,
                                              wr::DisplayListBuilder& aBuilder,
                                              WebRenderLayer* aLayer,
                                              const Maybe<gfx::Matrix4x4>& aTransform)
   : mBuilder(&aBuilder)
 {
   WrRect scBounds = aParentSC.ToRelativeWrRect(aLayer->BoundsForStackingContext());
-  mOffsetToParent.x = scBounds.x;
-  mOffsetToParent.y = scBounds.y;
   Layer* layer = aLayer->GetLayer();
   mTransform = aTransform.valueOr(layer->GetTransform());
   mBuilder->PushStackingContext(scBounds,
                                 1.0f,
                                 mTransform,
                                 wr::ToWrMixBlendMode(layer->GetMixBlendMode()));
   mOrigin = aLayer->Bounds().TopLeft();
 }
@@ -39,18 +37,16 @@ StackingContextHelper::StackingContextHe
                                              wr::DisplayListBuilder& aBuilder,
                                              WebRenderLayer* aLayer,
                                              uint64_t aAnimationsId,
                                              float* aOpacityPtr,
                                              gfx::Matrix4x4* aTransformPtr)
   : mBuilder(&aBuilder)
 {
   WrRect scBounds = aParentSC.ToRelativeWrRect(aLayer->BoundsForStackingContext());
-  mOffsetToParent.x = scBounds.x;
-  mOffsetToParent.y = scBounds.y;
   if (aTransformPtr) {
     mTransform = *aTransformPtr;
   }
   mBuilder->PushStackingContext(scBounds,
                                 aAnimationsId,
                                 aOpacityPtr,
                                 aTransformPtr,
                                 wr::ToWrMixBlendMode(aLayer->GetLayer()->GetMixBlendMode()));
@@ -84,17 +80,17 @@ StackingContextHelper::ToRelativeWrPoint
 
 WrRect
 StackingContextHelper::ToRelativeWrRectRounded(const LayoutDeviceRect& aRect) const
 {
   return wr::ToWrRect(RoundedToInt(ViewAs<LayerPixel>(aRect, PixelCastJustification::WebRenderHasUnitResolution) - mOrigin));
 }
 
 gfx::Matrix4x4
-StackingContextHelper::TransformToParentSC() const
+StackingContextHelper::TransformToRoot() const
 {
   gfx::Matrix4x4 inv = mTransform.Inverse();
-  inv.PostTranslate(-mOffsetToParent.x, -mOffsetToParent.y, 0);
+  inv.PostTranslate(-mOrigin.x, -mOrigin.y, 0);
   return inv;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -60,22 +60,21 @@ public:
   WrRect ToRelativeWrRect(const LayerRect& aRect) const;
   WrRect ToRelativeWrRect(const LayoutDeviceRect& aRect) const;
   // Same but for points
   WrPoint ToRelativeWrPoint(const LayerPoint& aPoint) const;
   // Same but rounds the rectangle to ints after transforming.
   WrRect ToRelativeWrRectRounded(const LayoutDeviceRect& aRect) const;
 
   // Produce a transform that converts points from the coordinate space of this
-  // stacking context to the coordinate space of the parent stacking context.
-  gfx::Matrix4x4 TransformToParentSC() const;
+  // stacking context to the coordinate space of the root of the layer tree.
+  gfx::Matrix4x4 TransformToRoot() const;
 
 private:
   wr::DisplayListBuilder* mBuilder;
   LayerPoint mOrigin;
-  WrPoint mOffsetToParent;
   gfx::Matrix4x4 mTransform;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* GFX_STACKINGCONTEXTHELPER_H */
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -12,16 +12,18 @@
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/PTextureChild.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 namespace layers {
 
+using namespace mozilla::gfx;
+
 WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
   : mReadLockSequenceNumber(0)
   , mIsInTransaction(false)
   , mIdNamespace(0)
   , mResourceId(0)
   , mPipelineId(aPipelineId)
   , mIPCOpen(false)
   , mDestroyed(false)
@@ -95,26 +97,27 @@ WebRenderBridgeChild::DPEnd(wr::DisplayL
                             const gfx::IntSize& aSize,
                             bool aIsSync,
                             uint64_t aTransactionId,
                             const WebRenderScrollData& aScrollData)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
-  wr::BuiltDisplayList dl = aBuilder.Finalize();
+  wr::BuiltDisplayList dl;
+  WrSize contentSize;
+  aBuilder.Finalize(contentSize, dl);
   ByteBuffer dlData(Move(dl.dl));
-  ByteBuffer auxData(Move(dl.aux));
 
   if (aIsSync) {
     this->SendDPSyncEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                        dlData, dl.dl_desc, auxData, dl.aux_desc, aScrollData);
+                        contentSize, dlData, dl.dl_desc, aScrollData);
   } else {
     this->SendDPEnd(aSize, mParentCommands, mDestroyedActors, GetFwdTransactionId(), aTransactionId,
-                    dlData, dl.dl_desc, auxData, dl.aux_desc, aScrollData);
+                    contentSize, dlData, dl.dl_desc, aScrollData);
   }
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 wr::ExternalImageId
@@ -183,30 +186,30 @@ WebRenderBridgeChild::PushGlyphs(wr::Dis
                                  const LayerRect& aBounds, const LayerRect& aClip)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
   WrFontKey key = GetFontKeyForScaledFont(aFont);
   MOZ_ASSERT(key.mNamespace && key.mHandle);
 
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(aSc.ToRelativeWrRect(aClip));
-
   for (size_t i = 0; i < aGlyphs.Length(); i++) {
     GlyphArray glyph_array = aGlyphs[i];
     nsTArray<gfx::Glyph>& glyphs = glyph_array.glyphs();
 
     nsTArray<WrGlyphInstance> wr_glyph_instances;
     wr_glyph_instances.SetLength(glyphs.Length());
 
     for (size_t j = 0; j < glyphs.Length(); j++) {
       wr_glyph_instances[j].index = glyphs[j].mIndex;
       wr_glyph_instances[j].point = aSc.ToRelativeWrPoint(
               LayerPoint::FromUnknownPoint(glyphs[j].mPosition));
     }
+
+    WrClipRegionToken clipRegion = aBuilder.PushClipRegion(aSc.ToRelativeWrRect(aClip));
     aBuilder.PushText(aSc.ToRelativeWrRect(aBounds),
                       clipRegion,
                       glyph_array.color().value(),
                       key,
                       Range<const WrGlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
                       aFont->GetSize());
 
   }
@@ -318,16 +321,21 @@ WebRenderBridgeChild::AddOpDestroy(const
 
   mDestroyedActors.AppendElement(aOp);
   return true;
 }
 
 void
 WebRenderBridgeChild::ReleaseCompositable(const CompositableHandle& aHandle)
 {
+  if (!IPCOpen()) {
+    // This can happen if the IPC connection was torn down, because, e.g.
+    // the GPU process died.
+    return;
+  }
   if (!DestroyInTransaction(aHandle)) {
     SendReleaseCompositable(aHandle);
   }
   mCompositables.Remove(aHandle.Value());
 }
 
 bool
 WebRenderBridgeChild::DestroyInTransaction(PTextureChild* aTexture)
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -114,19 +114,20 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mPipelineId(aPipelineId)
   , mWidget(aWidget)
   , mApi(aApi)
   , mCompositableHolder(aHolder)
   , mCompositorScheduler(aScheduler)
   , mChildLayerObserverEpoch(0)
   , mParentLayerObserverEpoch(0)
   , mWrEpoch(0)
-  , mIdNameSpace(++sIdNameSpace)
+  , mIdNameSpace(AllocIdNameSpace())
   , mPaused(false)
   , mDestroyed(false)
+  , mIsSnapshotting(false)
 {
   MOZ_ASSERT(mCompositableHolder);
   mCompositableHolder->AddPipeline(mPipelineId);
   if (mWidget) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
 }
@@ -152,22 +153,20 @@ WebRenderBridgeParent::RecvCreate(const 
 #endif
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvShutdown()
 {
-  if (mDestroyed) {
-    return IPC_OK();
-  }
   Destroy();
+  IProtocol* mgr = Manager();
   if (!Send__delete__(this)) {
-    return IPC_FAIL_NO_REASON(this);
+    return IPC_FAIL_NO_REASON(mgr);
   }
   return IPC_OK();
 }
 
 void
 WebRenderBridgeParent::Destroy()
 {
   if (mDestroyed) {
@@ -299,20 +298,19 @@ WebRenderBridgeParent::RecvDPBegin(const
 }
 
 void
 WebRenderBridgeParent::HandleDPEnd(const gfx::IntSize& aSize,
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
+                                 const WrSize& aContentSize,
                                  const ByteBuffer& dl,
                                  const WrBuiltDisplayListDescriptor& dlDesc,
-                                 const ByteBuffer& aux,
-                                 const WrAuxiliaryListsDescriptor& auxDesc,
                                  const WebRenderScrollData& aScrollData)
 {
   UpdateFwdTransactionId(aFwdTransactionId);
   AutoClearReadLocks clearLocks(mReadLocks);
 
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
@@ -320,174 +318,217 @@ WebRenderBridgeParent::HandleDPEnd(const
     return;
   }
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvDPEnd without doing so.
   AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy);
 
   ++mWrEpoch; // Update webrender epoch
   ProcessWebRenderCommands(aSize, aCommands, wr::NewEpoch(mWrEpoch),
-                           dl, dlDesc, aux, auxDesc);
+                           aContentSize, dl, dlDesc);
   HoldPendingTransactionId(mWrEpoch, aTransactionId);
 
   mScrollData = aScrollData;
   UpdateAPZ();
 }
 
-void
-WebRenderBridgeParent::UpdateAPZ()
+CompositorBridgeParent*
+WebRenderBridgeParent::GetRootCompositorBridgeParent() const
 {
   if (!mCompositorBridge) {
-    return;
+    return nullptr;
   }
 
-  CompositorBridgeParent* cbp;
-  uint64_t rootLayersId;
-  WebRenderBridgeParent* rootWrbp;
   if (mWidget) {
     // This WebRenderBridgeParent is attached to the root
     // CompositorBridgeParent.
-    cbp = static_cast<CompositorBridgeParent*>(mCompositorBridge);
-    rootLayersId = wr::AsUint64(mPipelineId);
-    rootWrbp = this;
-  } else {
-    // This WebRenderBridgeParent is attached to a
-    // CrossProcessCompositorBridgeParent so we have an extra level of
-    // indirection to unravel.
-    uint64_t layersId = wr::AsUint64(mPipelineId);
-    CompositorBridgeParent::LayerTreeState* lts =
-        CompositorBridgeParent::GetIndirectShadowTree(layersId);
-    MOZ_ASSERT(lts);
-    cbp = lts->mParent;
-    rootLayersId = cbp->RootLayerTreeId();
-    lts = CompositorBridgeParent::GetIndirectShadowTree(rootLayersId);
-    MOZ_ASSERT(lts);
-    rootWrbp = lts->mWrBridge.get();
+    return static_cast<CompositorBridgeParent*>(mCompositorBridge);
   }
 
-  MOZ_ASSERT(cbp);
+  // Otherwise, this WebRenderBridgeParent is attached to a
+  // CrossProcessCompositorBridgeParent so we have an extra level of
+  // indirection to unravel.
+  uint64_t layersId = wr::AsUint64(mPipelineId);
+  CompositorBridgeParent::LayerTreeState* lts =
+      CompositorBridgeParent::GetIndirectShadowTree(layersId);
+  MOZ_ASSERT(lts);
+  return lts->mParent;
+}
+
+void
+WebRenderBridgeParent::UpdateAPZ()
+{
+  CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+  if (!cbp) {
+    return;
+  }
+  uint64_t rootLayersId = cbp->RootLayerTreeId();
+  RefPtr<WebRenderBridgeParent> rootWrbp = cbp->GetWebRenderBridgeParent();
   if (!rootWrbp) {
     return;
   }
   if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
     apzc->UpdateHitTestingTree(rootLayersId, rootWrbp->GetScrollData(),
         mScrollData.IsFirstPaint(), wr::AsUint64(mPipelineId),
         /* TODO: propagate paint sequence number */ 0);
   }
 }
 
+bool
+WebRenderBridgeParent::PushAPZStateToWR()
+{
+  CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+  if (!cbp) {
+    return false;
+  }
+  if (RefPtr<APZCTreeManager> apzc = cbp->GetAPZCTreeManager()) {
+    TimeStamp animationTime = mCompositorScheduler->GetLastComposeTime();
+    TimeDuration frameInterval = cbp->GetVsyncInterval();
+    // As with the non-webrender codepath in AsyncCompositionManager, we want to
+    // use the timestamp for the next vsync when advancing animations.
+    if (frameInterval != TimeDuration::Forever()) {
+      animationTime += frameInterval;
+    }
+    return apzc->PushStateToWR(mApi, animationTime);
+  }
+  return false;
+}
+
 const WebRenderScrollData&
 WebRenderBridgeParent::GetScrollData() const
 {
   MOZ_ASSERT(mozilla::layers::CompositorThreadHolder::IsInCompositorThread());
   return mScrollData;
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPEnd(const gfx::IntSize& aSize,
                                  InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                  InfallibleTArray<OpDestroy>&& aToDestroy,
                                  const uint64_t& aFwdTransactionId,
                                  const uint64_t& aTransactionId,
+                                 const WrSize& aContentSize,
                                  const ByteBuffer& dl,
                                  const WrBuiltDisplayListDescriptor& dlDesc,
-                                 const ByteBuffer& aux,
-                                 const WrAuxiliaryListsDescriptor& auxDesc,
                                  const WebRenderScrollData& aScrollData)
 {
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              dl, dlDesc, aux, auxDesc, aScrollData);
+              aContentSize, dl, dlDesc, aScrollData);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvDPSyncEnd(const gfx::IntSize &aSize,
                                      InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                      InfallibleTArray<OpDestroy>&& aToDestroy,
                                      const uint64_t& aFwdTransactionId,
                                      const uint64_t& aTransactionId,
+                                     const WrSize& aContentSize,
                                      const ByteBuffer& dl,
                                      const WrBuiltDisplayListDescriptor& dlDesc,
-                                     const ByteBuffer& aux,
-                                     const WrAuxiliaryListsDescriptor& auxDesc,
                                      const WebRenderScrollData& aScrollData)
 {
   HandleDPEnd(aSize, Move(aCommands), Move(aToDestroy), aFwdTransactionId, aTransactionId,
-              dl, dlDesc, aux, auxDesc, aScrollData);
+              aContentSize, dl, dlDesc, aScrollData);
   return IPC_OK();
 }
 
 void
 WebRenderBridgeParent::ProcessWebRenderCommands(const gfx::IntSize &aSize,
                                                 InfallibleTArray<WebRenderParentCommand>& aCommands, const wr::Epoch& aEpoch,
-                                                const ByteBuffer& dl,
-                                                const WrBuiltDisplayListDescriptor& dlDesc,
-                                                const ByteBuffer& aux,
-                                                const WrAuxiliaryListsDescriptor& auxDesc)
+                                                const WrSize& aContentSize, const ByteBuffer& dl,
+                                                const WrBuiltDisplayListDescriptor& dlDesc)
 {
   mCompositableHolder->SetCompositionTime(TimeStamp::Now());
 
   for (InfallibleTArray<WebRenderParentCommand>::index_type i = 0; i < aCommands.Length(); ++i) {
     const WebRenderParentCommand& cmd = aCommands[i];
     switch (cmd.type()) {
       case WebRenderParentCommand::TOpAddExternalImage: {
         const OpAddExternalImage& op = cmd.get_OpAddExternalImage();
-        wr::ImageKey key = op.key();
+        Range<const wr::ImageKey> keys(&op.key(), 1);
         MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
-        MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(key), nullptr));
-        mActiveKeys.Put(wr::AsUint64(key), key);
+        MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(keys[0]), nullptr));
+        mActiveKeys.Put(wr::AsUint64(keys[0]), keys[0]);
 
         RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
         if (!host) {
           NS_ERROR("CompositableHost does not exist");
           break;
         }
         // XXX select Texture for video in CompositeToTarget().
         TextureHost* texture = host->GetAsTextureHostForComposite();
         if (!texture) {
           NS_ERROR("TextureHost does not exist");
           break;
         }
         WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
         if (wrTexture) {
-          if (wrTexture->IsWrappingNativeHandle()) {
-            // XXX only for MacIOSurface right now.
-            // XXX remove the redundant codes for both native handle and yuv case.
-            wr::ImageDescriptor descriptor(wrTexture->GetSize(), wrTexture->GetReadFormat());
-            mApi->AddExternalImageHandle(key,
-                                         descriptor,
-                                         wrTexture->GetExternalImageKey());
-            mCompositableHolder->HoldExternalImage(mPipelineId, aEpoch, texture->AsWebRenderTextureHost());
-          } else {
-            // XXX handling YUV
-            gfx::SurfaceFormat format =
-              wrTexture->GetFormat() == SurfaceFormat::YUV ? SurfaceFormat::B8G8R8A8 : wrTexture->GetFormat();
-            wr::ImageDescriptor descriptor(wrTexture->GetSize(), wrTexture->GetRGBStride(), format);
-            mApi->AddExternalImageBuffer(key,
-                                         descriptor,
-                                         wrTexture->GetExternalImageKey());
-            mCompositableHolder->HoldExternalImage(mPipelineId, aEpoch, texture->AsWebRenderTextureHost());
-          }
-
+          wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
           break;
         }
         RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
         if (!dSurf) {
           break;
         }
 
         DataSourceSurface::MappedSurface map;
         if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
           break;
         }
 
         IntSize size = dSurf->GetSize();
         wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
         auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
-        mApi->AddImage(key, descriptor, slice);
+        mApi->AddImage(keys[0], descriptor, slice);
+
+        dSurf->Unmap();
+        break;
+      }
+      case WebRenderParentCommand::TOpAddExternalVideoImage: {
+        const OpAddExternalVideoImage& op = cmd.get_OpAddExternalVideoImage();
+        MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
+        MOZ_ASSERT(op.keys().Length() > 0);
+        Range<const wr::ImageKey> keys(&(op.keys())[0], op.keys().Length());
+        for (auto key : keys) {
+          MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(key), nullptr));
+          mActiveKeys.Put(wr::AsUint64(key), key);
+        }
+
+        RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
+        if (!host) {
+          NS_ERROR("CompositableHost does not exist");
+          break;
+        }
+        // XXX select Texture for video in CompositeToTarget().
+        TextureHost* texture = host->GetAsTextureHostForComposite();
+        if (!texture) {
+          NS_ERROR("TextureHost does not exist");
+          break;
+        }
+        WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
+        if (wrTexture) {
+          wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
+          break;
+        }
+
+        MOZ_ASSERT(keys.length() == 1);
+        RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
+        if (!dSurf) {
+          break;
+        }
+        DataSourceSurface::MappedSurface map;
+        if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+          break;
+        }
+
+        IntSize size = dSurf->GetSize();
+        wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+        auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
+        mApi->AddImage(keys[0], descriptor, slice);
 
         dSurf->Unmap();
         break;
       }
       case WebRenderParentCommand::TCompositableOperation: {
         if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) {
           NS_ERROR("ReceiveCompositableUpdate failed");
         }
@@ -521,19 +562,18 @@ WebRenderBridgeParent::ProcessWebRenderC
       }
     }
   }
   if (mWidget) {
     LayoutDeviceIntSize size = mWidget->GetClientSize();
     mApi->SetWindowParameters(size);
   }
   mApi->SetRootDisplayList(gfx::Color(0.3f, 0.f, 0.f, 1.f), aEpoch, LayerSize(aSize.width, aSize.height),
-                           mPipelineId,
-                           dlDesc, dl.mData, dl.mLength,
-                           auxDesc, aux.mData, aux.mLength);
+                           mPipelineId, aContentSize,
+                           dlDesc, dl.mData, dl.mLength);
 
   ScheduleComposition();
   DeleteOldImages();
 
   if (ShouldParentObserveEpoch()) {
     mCompositorBridge->ObserveLayerUpdate(wr::AsUint64(mPipelineId), GetChildLayerObserverEpoch(), true);
   }
 }
@@ -572,23 +612,27 @@ WebRenderBridgeParent::RecvDPGetSnapshot
   // We only support B8G8R8A8 for now.
   MOZ_ASSERT(buffer);
   MOZ_ASSERT(bufferTexture->GetFormat() == SurfaceFormat::B8G8R8A8);
   uint32_t buffer_size = size.width * size.height * 4;
 
   // Assert the stride of the buffer is what webrender expects
   MOZ_ASSERT((uint32_t)(size.width * 4) == stride);
 
+  mIsSnapshotting = true;
+
   if (mCompositorScheduler->NeedsComposite()) {
     mCompositorScheduler->CancelCurrentCompositeTask();
     mCompositorScheduler->ForceComposeToTarget(nullptr, nullptr);
   }
 
   mApi->Readback(size, buffer, buffer_size);
 
+  mIsSnapshotting = false;
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvAddExternalImageId(const ExternalImageId& aImageId,
                                               const CompositableHandle& aHandle)
 {
   if (mDestroyed) {
@@ -608,17 +652,17 @@ WebRenderBridgeParent::RecvAddExternalIm
   }
   MOZ_ASSERT(host->AsWebRenderImageHost());
   WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
   if (!wrHost) {
     NS_ERROR("Incompatible CompositableHost");
     return IPC_OK();
   }
 
-  wrHost->SetWrCompositableHolder(mCompositableHolder);
+  wrHost->SetWrBridge(this);
   mExternalImageIds.Put(wr::AsUint64(aImageId), wrHost);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                              const CompositableHandle& aHandle)
@@ -631,32 +675,32 @@ WebRenderBridgeParent::RecvAddExternalIm
   RefPtr<CompositableHost> host = FindCompositable(aHandle);
   MOZ_ASSERT(host->AsWebRenderImageHost());
   WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
   if (!wrHost) {
     NS_ERROR("Incompatible CompositableHost");
     return IPC_OK();
   }
 
-  wrHost->SetWrCompositableHolder(mCompositableHolder);
+  wrHost->SetWrBridge(this);
   mExternalImageIds.Put(wr::AsUint64(aImageId), wrHost);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvRemoveExternalImageId(const ExternalImageId& aImageId)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(aImageId)).get());
   WebRenderImageHost* wrHost = mExternalImageIds.Get(wr::AsUint64(aImageId)).get();
   if (wrHost) {
-    wrHost->SetWrCompositableHolder(nullptr);
+    wrHost->ClearWrBridge();
   }
   mExternalImageIds.Remove(wr::AsUint64(aImageId));
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch)
@@ -718,16 +762,29 @@ WebRenderBridgeParent::SampleAnimations(
 
 void
 WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   if (mPaused) {
     return;
   }
 
+  const uint32_t maxPendingFrameCount = 2;
+
+  if (!mIsSnapshotting &&
+      wr::RenderThread::Get()->GetPendingFrameCount(mApi->GetId()) > maxPendingFrameCount) {
+    // Render thread is busy, try next time.
+    ScheduleComposition();
+    return;
+  }
+
+  if (PushAPZStateToWR()) {
+    ScheduleComposition();
+  }
+
   if (gfxPrefs::WebRenderOMTAEnabled()) {
     nsTArray<WrOpacityProperty> opacityArray;
     nsTArray<WrTransformProperty> transformArray;
     SampleAnimations(opacityArray, transformArray);
 
     if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
       mApi->GenerateFrame(opacityArray, transformArray);
       ScheduleComposition();
@@ -842,36 +899,36 @@ WebRenderBridgeParent::Resume()
 #endif
   mPaused = false;
   return true;
 }
 
 void
 WebRenderBridgeParent::ClearResources()
 {
-  if (mApi) {
-    ++mWrEpoch; // Update webrender epoch
-    mApi->ClearRootDisplayList(wr::NewEpoch(mWrEpoch), mPipelineId);
-    for (auto iter = mActiveKeys.Iter(); !iter.Done(); iter.Next()) {
-      mKeysToDelete.push_back(iter.Data());
-      iter.Remove();
-    }
-    if (!mKeysToDelete.empty()) {
-      // XXX Sync wait.
-      mApi->WaitFlushed();
-      DeleteOldImages();
-    }
+  if (!mApi) {
+    return;
   }
-  mCompositableHolder->RemovePipeline(mPipelineId);
+
+  ++mWrEpoch; // Update webrender epoch
+  mApi->ClearRootDisplayList(wr::NewEpoch(mWrEpoch), mPipelineId);
+  // Schedule composition to clean up Pipeline
+  mCompositorScheduler->ScheduleComposition();
+  for (auto iter = mActiveKeys.Iter(); !iter.Done(); iter.Next()) {
+    mKeysToDelete.push_back(iter.Data());
+    iter.Remove();
+  }
+  DeleteOldImages();
   for (auto iter = mExternalImageIds.Iter(); !iter.Done(); iter.Next()) {
-    iter.Data()->SetWrCompositableHolder(nullptr);
+    iter.Data()->ClearWrBridge();
   }
   mExternalImageIds.Clear();
+  mCompositableHolder->RemovePipeline(mPipelineId, wr::NewEpoch(mWrEpoch));
 
-  if (mWidget && mCompositorScheduler) {
+  if (mWidget) {
     mCompositorScheduler->Destroy();
   }
   mCompositorScheduler = nullptr;
   mApi = nullptr;
   mCompositorBridge = nullptr;
 }
 
 bool
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -48,16 +48,17 @@ public:
                         const wr::PipelineId& aPipelineId,
                         widget::CompositorWidget* aWidget,
                         CompositorVsyncScheduler* aScheduler,
                         RefPtr<wr::WebRenderAPI>&& aApi,
                         RefPtr<WebRenderCompositableHolder>&& aHolder);
 
   wr::PipelineId PipelineId() { return mPipelineId; }
   wr::WebRenderAPI* GetWebRenderAPI() { return mApi; }
+  wr::Epoch WrEpoch() { return wr::NewEpoch(mWrEpoch); }
   WebRenderCompositableHolder* CompositableHolder() { return mCompositableHolder; }
   CompositorVsyncScheduler* CompositorScheduler() { return mCompositorScheduler.get(); }
 
   mozilla::ipc::IPCResult RecvNewCompositable(const CompositableHandle& aHandle,
                                               const TextureInfo& aInfo) override;
   mozilla::ipc::IPCResult RecvReleaseCompositable(const CompositableHandle& aHandle) override;
 
   mozilla::ipc::IPCResult RecvInitReadLocks(ReadLockArray&& aReadLocks) override;
@@ -85,30 +86,28 @@ public:
                                          const uint32_t& aFontIndex) override;
   mozilla::ipc::IPCResult RecvDeleteFont(const wr::FontKey& aFontKey) override;
   mozilla::ipc::IPCResult RecvDPBegin(const gfx::IntSize& aSize) override;
   mozilla::ipc::IPCResult RecvDPEnd(const gfx::IntSize& aSize,
                                     InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                     InfallibleTArray<OpDestroy>&& aToDestroy,
                                     const uint64_t& aFwdTransactionId,
                                     const uint64_t& aTransactionId,
+                                    const WrSize& aContentSize,
                                     const ByteBuffer& dl,
                                     const WrBuiltDisplayListDescriptor& dlDesc,
-                                    const ByteBuffer& aux,
-                                    const WrAuxiliaryListsDescriptor& auxDesc,
                                     const WebRenderScrollData& aScrollData) override;
   mozilla::ipc::IPCResult RecvDPSyncEnd(const gfx::IntSize& aSize,
                                         InfallibleTArray<WebRenderParentCommand>&& aCommands,
                                         InfallibleTArray<OpDestroy>&& aToDestroy,
                                         const uint64_t& aFwdTransactionId,
                                         const uint64_t& aTransactionId,
+                                        const WrSize& aContentSize,
                                         const ByteBuffer& dl,
                                         const WrBuiltDisplayListDescriptor& dlDesc,
-                                        const ByteBuffer& aux,
-                                        const WrAuxiliaryListsDescriptor& auxDesc,
                                         const WebRenderScrollData& aScrollData) override;
   mozilla::ipc::IPCResult RecvDPGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvAddExternalImageId(const ExternalImageId& aImageId,
                                                  const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                                 const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvRemoveExternalImageId(const ExternalImageId& aImageId) override;
@@ -160,43 +159,53 @@ public:
   uint32_t GetIdNameSpace()
   {
     return mIdNameSpace;
   }
 
   void UpdateAPZ();
   const WebRenderScrollData& GetScrollData() const;
 
+  static uint32_t AllocIdNameSpace() {
+    return ++sIdNameSpace;
+  }
+
 private:
   virtual ~WebRenderBridgeParent();
 
   void DeleteOldImages();
-  void ProcessWebRenderCommands(const gfx::IntSize &aSize, InfallibleTArray<WebRenderParentCommand>& commands, const wr::Epoch& aEpoch,
-                                    const ByteBuffer& dl,
-                                    const WrBuiltDisplayListDescriptor& dlDesc,
-                                    const ByteBuffer& aux,
-                                    const WrAuxiliaryListsDescriptor& auxDesc);
+  void ProcessWebRenderCommands(const gfx::IntSize &aSize,
+                                InfallibleTArray<WebRenderParentCommand>& commands,
+                                const wr::Epoch& aEpoch,
+                                const WrSize& aContentSize,
+                                const ByteBuffer& dl,
+                                const WrBuiltDisplayListDescriptor& dlDesc);
   void ScheduleComposition();
   void ClearResources();
   uint64_t GetChildLayerObserverEpoch() const { return mChildLayerObserverEpoch; }
   bool ShouldParentObserveEpoch();
   void HandleDPEnd(const gfx::IntSize& aSize,
                    InfallibleTArray<WebRenderParentCommand>&& aCommands,
                    InfallibleTArray<OpDestroy>&& aToDestroy,
                    const uint64_t& aFwdTransactionId,
                    const uint64_t& aTransactionId,
+                   const WrSize& aContentSize,
                    const ByteBuffer& dl,
                    const WrBuiltDisplayListDescriptor& dlDesc,
-                   const ByteBuffer& aux,
-                   const WrAuxiliaryListsDescriptor& auxDesc,
                    const WebRenderScrollData& aScrollData);
 
   void SampleAnimations(nsTArray<WrOpacityProperty>& aOpacityArray,
                         nsTArray<WrTransformProperty>& aTransformArray);
 
+  CompositorBridgeParent* GetRootCompositorBridgeParent() const;
+
+  // Have APZ push the async scroll state to WR. Returns true if an APZ
+  // animation is in effect and we need to schedule another composition.
+  bool PushAPZStateToWR();
+
 private:
   struct PendingTransactionId {
     PendingTransactionId(wr::Epoch aEpoch, uint64_t aId)
       : mEpoch(aEpoch)
       , mId(aId)
     {}
     wr::Epoch mEpoch;
     uint64_t mId;
@@ -222,16 +231,17 @@ private:
   uint64_t mParentLayerObserverEpoch;
 
   std::queue<PendingTransactionId> mPendingTransactionIds;
   uint32_t mWrEpoch;
   uint32_t mIdNameSpace;
 
   bool mPaused;
   bool mDestroyed;
+  bool mIsSnapshotting;
 
   // Can only be accessed on the compositor thread.
   WebRenderScrollData mScrollData;
 
   static uint32_t sIdNameSpace;
 };
 
 } // namespace layers
--- a/gfx/layers/wr/WebRenderCanvasLayer.cpp
+++ b/gfx/layers/wr/WebRenderCanvasLayer.cpp
@@ -7,16 +7,17 @@
 
 #include "AsyncCanvasRenderer.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "LayersLogging.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/TextureClientSharedSurface.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "PersistentBufferProvider.h"
 #include "SharedSurface.h"
 #include "SharedSurfaceGL.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
@@ -57,24 +58,25 @@ WebRenderCanvasLayer::RenderLayer(wr::Di
   }
 
   Maybe<gfx::Matrix4x4> transform;
   const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
   if (needsYFlip) {
     transform = Some(GetTransform().PreTranslate(0, mBounds.height, 0).PreScale(1, -1, 1));
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this, transform);
 
   LayerRect rect(0, 0, mBounds.width, mBounds.height);
   DumpLayerInfo("CanvasLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
 
   if (gfxPrefs::LayersDump()) {
     printf_stderr("CanvasLayer %p texture-filter=%s\n",
                   this->GetLayer(),
--- a/gfx/layers/wr/WebRenderColorLayer.cpp
+++ b/gfx/layers/wr/WebRenderColorLayer.cpp
@@ -4,36 +4,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderColorLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 void
 WebRenderColorLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("ColorLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   aBuilder.PushRect(sc.ToRelativeWrRect(rect), clip, wr::ToWrColor(mColor));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderCompositableHolder.cpp
+++ b/gfx/layers/wr/WebRenderCompositableHolder.cpp
@@ -3,74 +3,87 @@
  * 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 "WebRenderCompositableHolder.h"
 
 #include "CompositableHost.h"
 #include "mozilla/layers/WebRenderImageHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
-WebRenderCompositableHolder::WebRenderCompositableHolder()
+WebRenderCompositableHolder::WebRenderCompositableHolder(uint32_t aIdNamespace)
+ : mIdNamespace(aIdNamespace)
+ , mResourceId(0)
 {
   MOZ_COUNT_CTOR(WebRenderCompositableHolder);
 }
 
 WebRenderCompositableHolder::~WebRenderCompositableHolder()
 {
   MOZ_COUNT_DTOR(WebRenderCompositableHolder);
-  MOZ_ASSERT(mPipelineTexturesHolders.IsEmpty());
 }
 
 void
 WebRenderCompositableHolder::AddPipeline(const wr::PipelineId& aPipelineId)
 {
   uint64_t id = wr::AsUint64(aPipelineId);
 
   MOZ_ASSERT(!mPipelineTexturesHolders.Get(id));
   PipelineTexturesHolder* holder = new PipelineTexturesHolder();
   mPipelineTexturesHolders.Put(id, holder);
 }
 
 void
-WebRenderCompositableHolder::RemovePipeline(const wr::PipelineId& aPipelineId)
+WebRenderCompositableHolder::RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
-  uint64_t id = wr::AsUint64(aPipelineId);
-  if (mPipelineTexturesHolders.Get(id)) {
-    mPipelineTexturesHolders.Remove(id);
+  PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
+  MOZ_ASSERT(holder);
+  if (!holder) {
+    return;
   }
+  MOZ_ASSERT(holder->mDestroyedEpoch.isNothing());
+  holder->mDestroyedEpoch = Some(aEpoch);
 }
 
 void
 WebRenderCompositableHolder::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture)
 {
   MOZ_ASSERT(aTexture);
+
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     return;
   }
   // Hold WebRenderTextureHost until end of its usage on RenderThread
   holder->mTextureHosts.push(ForwardingTextureHost(aEpoch, aTexture));
 }
 
 void
 WebRenderCompositableHolder::Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
-  if (!holder || holder->mTextureHosts.empty()) {
+  if (!holder) {
     return;
   }
 
+  // Remove Pipeline
+  if (holder->mDestroyedEpoch.isSome() && holder->mDestroyedEpoch.ref() <= aEpoch) {
+    mPipelineTexturesHolders.Remove(wr::AsUint64(aPipelineId));
+    return;
+  }
+
+  // Release TextureHosts based on Epoch
   while (!holder->mTextureHosts.empty()) {
     if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
       break;
     }
     holder->mTextureHosts.pop();
   }
 }
 
--- a/gfx/layers/wr/WebRenderCompositableHolder.h
+++ b/gfx/layers/wr/WebRenderCompositableHolder.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 #define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 
 #include <queue>
 
 #include "mozilla/layers/TextureHost.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsClassHashtable.h"
 
 namespace mozilla {
 
 namespace wr {
 class WebRenderAPI;
 }
@@ -23,24 +24,24 @@ namespace layers {
 class CompositableHost;
 class WebRenderTextureHost;
 
 class WebRenderCompositableHolder final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderCompositableHolder)
 
-  explicit WebRenderCompositableHolder();
+  explicit WebRenderCompositableHolder(uint32_t aIdNamespace);
 
 protected:
   ~WebRenderCompositableHolder();
 
 public:
   void AddPipeline(const wr::PipelineId& aPipelineId);
-  void RemovePipeline(const wr::PipelineId& aPipelineId);
+  void RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
   void HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture);
   void Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
 
   TimeStamp GetCompositionTime() const {
     return mCompositionTime;
   }
   void SetCompositionTime(TimeStamp aTimeStamp) {
     mCompositionTime = aTimeStamp;
@@ -54,32 +55,45 @@ public:
         mCompositeUntilTime < aTimeStamp) {
       mCompositeUntilTime = aTimeStamp;
     }
   }
   TimeStamp GetCompositeUntilTime() const {
     return mCompositeUntilTime;
   }
 
+  uint32_t GetNextResourceId() { return ++mResourceId; }
+  uint32_t GetNamespace() { return mIdNamespace; }
+  wr::ImageKey GetImageKey()
+  {
+    wr::ImageKey key;
+    key.mNamespace = GetNamespace();
+    key.mHandle = GetNextResourceId();
+    return key;
+  }
+
 private:
 
   struct ForwardingTextureHost {
     ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
       : mEpoch(aEpoch)
       , mTexture(aTexture)
     {}
     wr::Epoch mEpoch;
     CompositableTextureHostRef mTexture;
   };
 
   struct PipelineTexturesHolder {
     // Holds forwarding WebRenderTextureHosts.
     std::queue<ForwardingTextureHost> mTextureHosts;
+    Maybe<wr::Epoch> mDestroyedEpoch;
   };
 
+  uint32_t mIdNamespace;
+  uint32_t mResourceId;
   nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder> mPipelineTexturesHolders;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
--- a/gfx/layers/wr/WebRenderContainerLayer.cpp
+++ b/gfx/layers/wr/WebRenderContainerLayer.cpp
@@ -3,16 +3,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 "WebRenderContainerLayer.h"
 
 #include <inttypes.h>
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
 namespace layers {
 
@@ -84,16 +85,17 @@ WebRenderContainerLayer::RenderLayer(wr:
 
     animationsId = GetCompositorAnimationsId();
     OpAddCompositorAnimations
       anim(CompositorAnimations(GetAnimations(), animationsId),
            transformForCompositor, opacityForCompositor);
     WrBridge()->AddWebRenderParentCommand(anim);
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this, animationsId, opacityForSC, transformForSC);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("ContainerLayer", rect);
 
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
   aBuilder.PushClip(sc.ToRelativeWrRect(rect), mask.ptrOr(nullptr));
 
@@ -105,25 +107,27 @@ WebRenderContainerLayer::RenderLayer(wr:
   }
   aBuilder.PopClip();
 }
 
 void
 WebRenderRefLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
   ParentLayerRect bounds = GetLocalTransformTyped().TransformBounds(Bounds());
   // As with WebRenderTextLayer, because we don't push a stacking context for
   // this layer, WR doesn't know about the transform on this layer. Therefore
   // we need to apply that transform to the bounds before we pass it on to WR.
   // The conversion from ParentLayerPixel to LayerPixel below is a result of
   // changing the reference layer from "this layer" to the "the layer that
   // created aSc".
   LayerRect rect = ViewAs<LayerPixel>(bounds,
       PixelCastJustification::MovingDownToChildren);
   DumpLayerInfo("RefLayer", rect);
 
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(aSc.ToRelativeWrRect(rect));
+  WrClipRegionToken clipRegion = aBuilder.PushClipRegion(aSc.ToRelativeWrRect(rect));
   aBuilder.PushIFrame(aSc.ToRelativeWrRect(rect), clipRegion, wr::AsPipelineId(mId));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
+++ b/gfx/layers/wr/WebRenderDisplayItemLayer.cpp
@@ -3,16 +3,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 "WebRenderDisplayItemLayer.h"
 
 #include "LayersLogging.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "nsDisplayList.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
@@ -32,45 +33,51 @@ WebRenderDisplayItemLayer::~WebRenderDis
 void
 WebRenderDisplayItemLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc)
 {
   if (mVisibleRegion.IsEmpty()) {
     return;
   }
 
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
   Maybe<WrImageMask> mask = BuildWrMaskLayer(nullptr);
   WrImageMask* imageMask = mask.ptrOr(nullptr);
-  if (imageMask) {
-    ParentLayerRect clip = GetLocalTransformTyped().TransformBounds(Bounds());
-    // As with WebRenderTextLayer, I'm not 100% sure this is correct, but I
-    // think it is. Because we don't push a stacking context for this layer,
-    // WR doesn't know about the transform on this layer. The display items
-    // that we push as part of this layer already take the transform into
-    // account. When we set the clip rect we also need to explicitly apply
-    // the transform to make sure it gets taken into account.
-    // In a sense this is the opposite of what WebRenderLayer::ClipRect() does,
-    // because there we remove the transform from the clip rect to bring it
-    // into the coordinate space of the local stacking context, but here we
-    // need to apply the transform to the bounds to take it into the coordinate
-    // space of the enclosing stacking context.
-    // The conversion from ParentLayerPixel to LayerPixel below is a result of
-    // changing the reference layer from "this layer" to the "the layer that
-    // created aSc".
-    LayerRect clipInParentLayerSpace = ViewAs<LayerPixel>(clip,
-        PixelCastJustification::MovingDownToChildren);
-    aBuilder.PushClip(aSc.ToRelativeWrRect(clipInParentLayerSpace), imageMask);
+
+  ParentLayerRect clip = GetLocalTransformTyped().TransformBounds(Bounds());
+  if (GetClipRect()) {
+    clip = ParentLayerRect(GetClipRect().ref());
   }
 
+  // As with WebRenderTextLayer, I'm not 100% sure this is correct, but I
+  // think it is. Because we don't push a stacking context for this layer,
+  // WR doesn't know about the transform on this layer. The display items
+  // that we push as part of this layer already take the transform into
+  // account. When we set the clip rect we also need to explicitly apply
+  // the transform to make sure it gets taken into account.
+  // In a sense this is the opposite of what WebRenderLayer::ClipRect() does,
+  // because there we remove the transform from the clip rect to bring it
+  // into the coordinate space of the local stacking context, but here we
+  // need to apply the transform to the bounds to take it into the coordinate
+  // space of the enclosing stacking context.
+  // The conversion from ParentLayerPixel to LayerPixel below is a result of
+  // changing the reference layer from "this layer" to the "the layer that
+  // created aSc".
+  LayerRect clipInParentLayerSpace = ViewAs<LayerPixel>(clip,
+      PixelCastJustification::MovingDownToChildren);
+  aBuilder.PushClip(aSc.ToRelativeWrRect(clipInParentLayerSpace), imageMask);
+
   if (mItem) {
-    wr::DisplayListBuilder builder(WrBridge()->GetPipeline());
+    WrSize contentSize; // this won't actually be used by anything
+    wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
     // We might have recycled this layer. Throw away the old commands.
     mParentCommands.Clear();
     mItem->CreateWebRenderCommands(builder, aSc, mParentCommands, this);
-    mBuiltDisplayList = builder.Finalize();
+    builder.Finalize(contentSize, mBuiltDisplayList);
   } else {
     // else we have an empty transaction and just use the
     // old commands.
     WebRenderLayerManager* manager = WrManager();
     MOZ_ASSERT(manager);
 
     // Since our recording relies on our parent layer's transform and stacking context
     // If this layer or our parent changed, this empty transaction won't work.
@@ -78,19 +85,17 @@ WebRenderDisplayItemLayer::RenderLayer(w
       manager->SetTransactionIncomplete();
       return;
     }
   }
 
   aBuilder.PushBuiltDisplayList(Move(mBuiltDisplayList));
   WrBridge()->AddWebRenderParentCommands(mParentCommands);
 
-  if (imageMask) {
-    aBuilder.PopClip();
-  }
+  aBuilder.PopClip();
 }
 
 Maybe<wr::ImageKey>
 WebRenderDisplayItemLayer::SendImageContainer(ImageContainer* aContainer,
                                               nsTArray<layers::WebRenderParentCommand>& aParentCommands)
 {
   MOZ_ASSERT(aContainer);
 
@@ -161,17 +166,17 @@ WebRenderDisplayItemLayer::PushItemAsBlo
   }
 
   wr::ByteBuffer bytes;
   bytes.Allocate(recorder->RecordingSize());
   DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
   MOZ_ASSERT(ok);
 
   WrRect dest = aSc.ToRelativeWrRect(imageRect + offset);
-  WrClipRegion clipRegion = aBuilder.BuildClipRegion(dest);
+  WrClipRegionToken clipRegion = aBuilder.PushClipRegion(dest);
   WrImageKey key = GetImageKey();
   WrBridge()->SendAddBlobImage(key, imageSize.ToUnknownSize(), imageSize.width * 4, dt->GetFormat(), bytes);
   WrManager()->AddImageKeyForDiscard(key);
 
   aBuilder.PushImage(dest,
                      clipRegion,
                      wr::ImageRendering::Auto,
                      key);
--- a/gfx/layers/wr/WebRenderImageHost.cpp
+++ b/gfx/layers/wr/WebRenderImageHost.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderImageHost.h"
 
 #include "LayersLogging.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
 #include "mozilla/layers/LayerManagerComposite.h"     // for TexturedEffect, Effect, etc
+#include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/layers/WebRenderCompositableHolder.h"
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_WARNING, NS_ASSERTION
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsString.h"                   // for nsAutoCString
 
 namespace mozilla {
 
@@ -21,22 +22,23 @@ using namespace gfx;
 
 namespace layers {
 
 class ISurfaceAllocator;
 
 WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo)
   : CompositableHost(aTextureInfo)
   , ImageComposite()
-  , mWrCompositableHolder(nullptr)
+  , mWrBridge(nullptr)
+  , mWrBridgeBindings(0)
 {}
 
 WebRenderImageHost::~WebRenderImageHost()
 {
-  MOZ_ASSERT(!mWrCompositableHolder);
+  MOZ_ASSERT(!mWrBridge);
 }
 
 void
 WebRenderImageHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures)
 {
   CompositableHost::UseTextureHost(aTextures);
   MOZ_ASSERT(aTextures.Length() >= 1);
 
@@ -65,22 +67,23 @@ WebRenderImageHost::UseTextureHost(const
 
   mImages.SwapElements(newImages);
   newImages.Clear();
 
   // Video producers generally send replacement images with the same frameID but
   // slightly different timestamps in order to sync with the audio clock. This
   // means that any CompositeUntil() call we made in Composite() may no longer
   // guarantee that we'll composite until the next frame is ready. Fix that here.
-  if (mWrCompositableHolder && mLastFrameID >= 0) {
+  if (mWrBridge && mLastFrameID >= 0) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
     for (size_t i = 0; i < mImages.Length(); ++i) {
       bool frameComesAfter = mImages[i].mFrameID > mLastFrameID ||
                              mImages[i].mProducerID != mLastProducerID;
       if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) {
-        mWrCompositableHolder->CompositeUntil(mImages[i].mTimeStamp +
+        mWrBridge->CompositableHolder()->CompositeUntil(mImages[i].mTimeStamp +
                            TimeDuration::FromMilliseconds(BIAS_TIME_MS));
         break;
       }
     }
   }
 }
 
 void
@@ -91,16 +94,17 @@ WebRenderImageHost::UseComponentAlphaTex
 }
 
 void
 WebRenderImageHost::CleanupResources()
 {
   nsTArray<TimedImage> newImages;
   mImages.SwapElements(newImages);
   newImages.Clear();
+  SetCurrentTextureHost(nullptr);
 }
 
 void
 WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
   CompositableHost::RemoveTextureHost(aTexture);
 
   for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
@@ -110,18 +114,19 @@ WebRenderImageHost::RemoveTextureHost(Te
     }
   }
 }
 
 TimeStamp
 WebRenderImageHost::GetCompositionTime() const
 {
   TimeStamp time;
-  if (mWrCompositableHolder) {
-    time = mWrCompositableHolder->GetCompositionTime();
+  if (mWrBridge) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    time = mWrBridge->CompositableHolder()->GetCompositionTime();
   }
   return time;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHost(IntRect* aPictureRect)
 {
   TimedImage* img = ChooseImage();
@@ -131,30 +136,56 @@ WebRenderImageHost::GetAsTextureHost(Int
   return nullptr;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHostForComposite()
 {
   int imageIndex = ChooseImageIndex();
   if (imageIndex < 0) {
+    SetCurrentTextureHost(nullptr);
     return nullptr;
   }
 
-  if (mWrCompositableHolder && uint32_t(imageIndex) + 1 < mImages.Length()) {
-    mWrCompositableHolder->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+  if (mWrBridge && uint32_t(imageIndex) + 1 < mImages.Length()) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    mWrBridge->CompositableHolder()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
   }
 
   TimedImage* img = &mImages[imageIndex];
 
   if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) {
     mLastFrameID = img->mFrameID;
     mLastProducerID = img->mProducerID;
   }
-  return img->mTextureHost;
+  SetCurrentTextureHost(img->mTextureHost);
+  return mCurrentTextureHost;
+}
+
+void
+WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture)
+{
+  if (aTexture == mCurrentTextureHost.get()) {
+    return;
+  }
+
+  if (mWrBridge &&
+      !!mCurrentTextureHost &&
+      mCurrentTextureHost != aTexture &&
+      mCurrentTextureHost->AsWebRenderTextureHost()) {
+    MOZ_ASSERT(mWrBridge->CompositableHolder());
+    wr::PipelineId piplineId = mWrBridge->PipelineId();
+    wr::Epoch epoch = mWrBridge->WrEpoch();
+    mWrBridge->CompositableHolder()->HoldExternalImage(
+      piplineId,
+      epoch,
+      mCurrentTextureHost->AsWebRenderTextureHost());
+  }
+
+  mCurrentTextureHost = aTexture;
 }
 
 void WebRenderImageHost::Attach(Layer* aLayer,
                        TextureSourceProvider* aProvider,
                        AttachFlags aFlags)
 {
   MOZ_ASSERT_UNREACHABLE("unexpected to be called");
 }
@@ -241,10 +272,34 @@ WebRenderImageHost::GetImageSize() const
 {
   const TimedImage* img = ChooseImage();
   if (img) {
     return IntSize(img->mPictureRect.width, img->mPictureRect.height);
   }
   return IntSize();
 }
 
+void
+WebRenderImageHost::SetWrBridge(WebRenderBridgeParent* aWrBridge)
+{
+  // For image hosts created through ImageBridgeParent, there may be multiple
+  // references to it due to the order of creation and freeing of layers by
+  // the layer tree. However this should be limited to things such as video
+  // which will not be reused across different WebRenderBridgeParent objects.
+  MOZ_ASSERT(aWrBridge);
+  MOZ_ASSERT(!mWrBridge || mWrBridge == aWrBridge);
+  mWrBridge = aWrBridge;
+  ++mWrBridgeBindings;
+}
+
+void
+WebRenderImageHost::ClearWrBridge()
+{
+  MOZ_ASSERT(mWrBridgeBindings > 0);
+  --mWrBridgeBindings;
+  if (mWrBridgeBindings == 0) {
+    SetCurrentTextureHost(nullptr);
+    mWrBridge = nullptr;
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderImageHost.h
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -7,17 +7,17 @@
 #define MOZILLA_GFX_WEBRENDERIMAGEHOST_H
 
 #include "CompositableHost.h"           // for CompositableHost
 #include "mozilla/layers/ImageComposite.h"  // for ImageComposite
 
 namespace mozilla {
 namespace layers {
 
-class WebRenderCompositableHolder;
+class WebRenderBridgeParent;
 
 /**
  * ImageHost. Works with ImageClientSingle and ImageClientBuffered
  */
 class WebRenderImageHost : public CompositableHost,
                            public ImageComposite
 {
 public:
@@ -64,24 +64,29 @@ public:
   virtual void Unlock() override;
 
   virtual void CleanupResources() override;
 
   virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; }
 
   TextureHost* GetAsTextureHostForComposite();
 
-  void SetWrCompositableHolder(WebRenderCompositableHolder* aWrCompositableHolder)
-  {
-    mWrCompositableHolder = aWrCompositableHolder;
-  }
+  void SetWrBridge(WebRenderBridgeParent* aWrBridge);
+
+  void ClearWrBridge();
 
 protected:
   // ImageComposite
   virtual TimeStamp GetCompositionTime() const override;
 
-  WebRenderCompositableHolder* MOZ_NON_OWNING_REF mWrCompositableHolder;
+  void SetCurrentTextureHost(TextureHost* aTexture);
+
+  WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge;
+
+  uint32_t mWrBridgeBindings;
+
+  CompositableTextureHostRef mCurrentTextureHost;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_WEBRENDERIMAGEHOST_H
--- a/gfx/layers/wr/WebRenderImageLayer.cpp
+++ b/gfx/layers/wr/WebRenderImageLayer.cpp
@@ -2,17 +2,19 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderImageLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "mozilla/layers/TextureWrapperImage.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace layers {
@@ -25,19 +27,24 @@ WebRenderImageLayer::WebRenderImageLayer
 {
   MOZ_COUNT_CTOR(WebRenderImageLayer);
 }
 
 WebRenderImageLayer::~WebRenderImageLayer()
 {
   MOZ_COUNT_DTOR(WebRenderImageLayer);
   mPipelineIdRequest.DisconnectIfExists();
+
+  for (auto key : mVideoKeys) {
+    WrManager()->AddImageKeyForDiscard(key);
+  }
   if (mKey.isSome()) {
     WrManager()->AddImageKeyForDiscard(mKey.value());
   }
+
   if (mExternalImageId.isSome()) {
     WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
   }
 }
 
 CompositableType
 WebRenderImageLayer::GetImageClientType()
 {
@@ -79,16 +86,27 @@ void
 WebRenderImageLayer::ClearCachedResources()
 {
   if (mImageClient) {
     mImageClient->ClearCachedResources();
   }
 }
 
 void
+WebRenderImageLayer::AddWRVideoImage(size_t aChannelNumber)
+{
+  for (size_t i = 0; i < aChannelNumber; ++i) {
+    WrImageKey key = GetImageKey();
+    WrManager()->AddImageKeyForDiscard(key);
+    mVideoKeys.AppendElement(key);
+  }
+  WrBridge()->AddWebRenderParentCommand(OpAddExternalVideoImage(mExternalImageId.value(), mVideoKeys));
+}
+
+void
 WebRenderImageLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc)
 {
   if (!mContainer) {
      return;
   }
 
   CompositableType type = GetImageClientType();
@@ -141,60 +159,107 @@ WebRenderImageLayer::RenderLayer(wr::Dis
   // XXX Not good for async ImageContainer case.
   AutoLockImage autoLock(mContainer);
   Image* image = autoLock.GetImage();
   if (!image) {
     return;
   }
   gfx::IntSize size = image->GetSize();
 
-  if (GetImageClientType() == CompositableType::IMAGE_BRIDGE) {
-    // Always allocate key
-    WrImageKey key = GetImageKey();
-    WrBridge()->AddWebRenderParentCommand(OpAddExternalImage(mExternalImageId.value(), key));
-    WrManager()->AddImageKeyForDiscard(key);
-    mKey = Some(key);
-  } else {
+  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
     // Handle CompositableType::IMAGE case
     MOZ_ASSERT(mImageClient->AsImageClientSingle());
     mKey = UpdateImageKey(mImageClient->AsImageClientSingle(),
                           mContainer,
                           mKey,
                           mExternalImageId.ref());
+    if (mKey.isNothing()) {
+      return;
+    }
+  } else {
+    // Always allocate key.
+    mVideoKeys.Clear();
+
+    // XXX (Jerry): Remove the hardcode image format setting.
+#if defined(XP_WIN)
+    // Use libyuv to convert the buffer to rgba format. So, use 1 image key here.
+    AddWRVideoImage(1);
+#elif defined(XP_MACOSX)
+    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
+      // Use the hardware MacIOSurface with YCbCr interleaved format. It uses 1
+      // image key.
+      AddWRVideoImage(1);
+    } else {
+      // Use libyuv.
+      AddWRVideoImage(1);
+    }
+#elif defined(MOZ_WIDGET_GTK)
+    // Use libyuv.
+    AddWRVideoImage(1);
+#elif defined(ANDROID)
+    // Use libyuv.
+    AddWRVideoImage(1);
+#endif
   }
 
-  if (mKey.isNothing()) {
-    return;
-  }
-
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect(0, 0, size.width, size.height);
   if (mScaleMode != ScaleMode::SCALE_NONE) {
     NS_ASSERTION(mScaleMode == ScaleMode::STRETCH,
                  "No other scalemodes than stretch and none supported yet.");
     rect = LayerRect(0, 0, mScaleToSize.width, mScaleToSize.height);
   }
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
 
   DumpLayerInfo("Image Layer", rect);
   if (gfxPrefs::LayersDump()) {
     printf_stderr("ImageLayer %p texture-filter=%s \n",
                   GetLayer(),
                   Stringify(filter).c_str());
   }
 
-  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
+  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
+  } else {
+    // XXX (Jerry): Remove the hardcode image format setting. The format of
+    // textureClient could change from time to time. So, we just set the most
+    // usable format here.
+#if defined(XP_WIN)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#elif defined(XP_MACOSX)
+    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
+      // Use the hardware MacIOSurface with YCbCr interleaved format.
+      MOZ_ASSERT(mVideoKeys.Length() == 1);
+      aBuilder.PushYCbCrInterleavedImage(sc.ToRelativeWrRect(rect), clip, mVideoKeys[0], WrYuvColorSpace::Rec601);
+    } else {
+      // Use libyuv to convert the buffer to rgba format.
+      MOZ_ASSERT(mVideoKeys.Length() == 1);
+      aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+    }
+#elif defined(MOZ_WIDGET_GTK)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#elif defined(ANDROID)
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(mVideoKeys.Length() == 1);
+    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
+#endif
+  }
 }
 
 Maybe<WrImageMask>
 WebRenderImageLayer::RenderMaskLayer(const gfx::Matrix4x4& aTransform)
 {
   if (!mContainer) {
      return Nothing();
   }
--- a/gfx/layers/wr/WebRenderImageLayer.h
+++ b/gfx/layers/wr/WebRenderImageLayer.h
@@ -31,27 +31,32 @@ public:
   Layer* GetLayer() override { return this; }
   void RenderLayer(wr::DisplayListBuilder& aBuilder,
                    const StackingContextHelper& aSc) override;
   Maybe<WrImageMask> RenderMaskLayer(const gfx::Matrix4x4& aTransform) override;
 
 protected:
   CompositableType GetImageClientType();
 
+  void AddWRVideoImage(size_t aChannelNumber);
+
   class Holder {
   public:
     explicit Holder(WebRenderImageLayer* aLayer)
       : mLayer(aLayer)
     {}
     WebRenderImageLayer* operator ->() const { return mLayer; }
   private:
     WebRenderImageLayer* mLayer;
   };
 
   wr::MaybeExternalImageId mExternalImageId;
+  // Some video image format contains multiple channel data.
+  nsTArray<wr::ImageKey> mVideoKeys;
+  // The regular single channel image.
   Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   CompositableType mImageClientTypeContainer;
   Maybe<wr::PipelineId> mPipelineId;
   MozPromiseRequestHolder<PipelineIdPromise> mPipelineIdRequest;
 };
 
 } // namespace layers
--- a/gfx/layers/wr/WebRenderLayer.cpp
+++ b/gfx/layers/wr/WebRenderLayer.cpp
@@ -47,17 +47,17 @@ WebRenderLayer::BuildWrMaskLayer(const S
     WebRenderLayer* maskLayer = ToWebRenderLayer(GetLayer()->GetMaskLayer());
 
     // If |this| layer is pushing a stacking context, that should be passed in
     // as |aUnapplySc|. We need to unapply the transform from that stacking
     // context because the mask layer (according to WR) is outside that stacking
     // context.
     gfx::Matrix4x4 transform = maskLayer->GetLayer()->GetTransform();
     if (aUnapplySc) {
-      transform = transform * aUnapplySc->TransformToParentSC();
+      transform = transform * aUnapplySc->TransformToRoot();
     }
 
     return maskLayer->RenderMaskLayer(transform);
   }
 
   return Nothing();
 }
 
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -102,17 +102,17 @@ WebRenderLayerManager::~WebRenderLayerMa
 {
   Destroy();
   MOZ_COUNT_DTOR(WebRenderLayerManager);
 }
 
 CompositorBridgeChild*
 WebRenderLayerManager::GetCompositorBridgeChild()
 {
-  return mWidget ? mWidget->GetRemoteRenderer() : nullptr;
+  return WrBridge()->GetCompositorBridgeChild();
 }
 
 int32_t
 WebRenderLayerManager::GetMaxTextureSize() const
 {
   return WrBridge()->GetMaxTextureSize();
 }
 
@@ -187,30 +187,31 @@ WebRenderLayerManager::EndTransactionInt
   LayoutDeviceIntSize size = mWidget->GetClientSize();
   if (!WrBridge()->DPBegin(size.ToUnknownSize())) {
     return false;
   }
   DiscardCompositorAnimations();
   mRoot->StartPendingAnimations(mAnimationReadyTime);
 
   StackingContextHelper sc;
-  wr::DisplayListBuilder builder(WrBridge()->GetPipeline());
+  WrSize contentSize { (float)size.width, (float)size.height };
+  wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
   WebRenderLayer::ToWebRenderLayer(mRoot)->RenderLayer(builder, sc);
   WrBridge()->ClearReadLocks();
 
   // We can't finish this transaction so return. This usually
   // happens in an empty transaction where we can't repaint a painted layer.
   // In this case, leave the transaction open and let a full transaction happen.
   if (mTransactionIncomplete) {
     DiscardLocalImages();
     return false;
   }
 
   WebRenderScrollData scrollData;
-  if (mWidget->AsyncPanZoomEnabled()) {
+  if (AsyncPanZoomEnabled()) {
     if (mIsFirstPaint) {
       scrollData.SetIsFirstPaint();
       mIsFirstPaint = false;
     }
     if (mRoot) {
       PopulateScrollData(scrollData, mRoot.get());
     }
   }
@@ -228,16 +229,22 @@ WebRenderLayerManager::EndTransactionInt
   // this may result in Layers being deleted, which results in
   // PLayer::Send__delete__() and DeallocShmem()
   mKeepAlive.Clear();
   ClearMutatedLayers();
 
   return true;
 }
 
+bool
+WebRenderLayerManager::AsyncPanZoomEnabled() const
+{
+  return mWidget->AsyncPanZoomEnabled();
+}
+
 void
 WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize)
 {
   if (!mTarget || aSize.IsEmpty()) {
     return;
   }
 
   // XXX Add other TextureData supports.
@@ -299,38 +306,38 @@ void
 WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
 {
   mImageKeys.push_back(key);
 }
 
 void
 WebRenderLayerManager::DiscardImages()
 {
-  if (!WrBridge()->IsDestroyed()) {
+  if (WrBridge()->IPCOpen()) {
     for (auto key : mImageKeys) {
       WrBridge()->SendDeleteImage(key);
     }
   }
   mImageKeys.clear();
 }
 
 void
 WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
   mDiscardedCompositorAnimationsIds.AppendElement(aId);
 }
 
 void
 WebRenderLayerManager::DiscardCompositorAnimations()
 {
-  if (!mDiscardedCompositorAnimationsIds.IsEmpty()) {
+  if (WrBridge()->IPCOpen() && !mDiscardedCompositorAnimationsIds.IsEmpty()) {
     WrBridge()->
       SendDeleteCompositorAnimations(mDiscardedCompositorAnimationsIds);
-    mDiscardedCompositorAnimationsIds.Clear();
   }
+  mDiscardedCompositorAnimationsIds.Clear();
 }
 
 void
 WebRenderLayerManager::DiscardLocalImages()
 {
   // Removes images but doesn't tell the parent side about them
   // This is useful in empty / failed transactions where we created
   // image keys but didn't tell the parent about them yet.
@@ -461,16 +468,25 @@ WebRenderLayerManager::FlushRendering()
 {
   CompositorBridgeChild* bridge = GetCompositorBridgeChild();
   if (bridge) {
     bridge->SendFlushRendering();
   }
 }
 
 void
+WebRenderLayerManager::WaitOnTransactionProcessed()
+{
+  CompositorBridgeChild* bridge = GetCompositorBridgeChild();
+  if (bridge) {
+    bridge->SendWaitOnTransactionProcessed();
+  }
+}
+
+void
 WebRenderLayerManager::SendInvalidRegion(const nsIntRegion& aRegion)
 {
   // XXX Webrender does not support invalid region yet.
 }
 
 void
 WebRenderLayerManager::Composite()
 {
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -83,28 +83,31 @@ public:
 
   virtual void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override
   { mTransactionIdAllocator = aAllocator; }
 
   virtual void AddDidCompositeObserver(DidCompositeObserver* aObserver) override;
   virtual void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override;
 
   virtual void FlushRendering() override;
+  virtual void WaitOnTransactionProcessed() override;
 
   virtual void SendInvalidRegion(const nsIntRegion& aRegion) override;
 
   virtual void Composite() override;
 
   virtual void SetNeedsComposite(bool aNeedsComposite) override
   {
     mNeedsComposite = aNeedsComposite;
   }
   virtual bool NeedsComposite() const override { return mNeedsComposite; }
   virtual void SetIsFirstPaint() override { mIsFirstPaint = true; }
 
+  bool AsyncPanZoomEnabled() const override;
+
   DrawPaintedLayerCallback GetPaintedLayerCallback() const
   { return mPaintedLayerCallback; }
 
   void* GetPaintedLayerCallbackData() const
   { return mPaintedLayerCallbackData; }
 
   // adds an imagekey to a list of keys that will be discarded on the next
   // transaction or destruction
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -187,33 +187,11 @@ struct ParamTraits<WrBuiltDisplayListDes
   Read(const Message* aMsg, PickleIterator* aIter, WrBuiltDisplayListDescriptor* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->display_list_items_size)
         && ReadParam(aMsg, aIter, &aResult->builder_start_time)
         && ReadParam(aMsg, aIter, &aResult->builder_finish_time);
   }
 };
 
-template<>
-struct ParamTraits<WrAuxiliaryListsDescriptor>
-{
-  static void
-  Write(Message* aMsg, const WrAuxiliaryListsDescriptor& aParam)
-  {
-    WriteParam(aMsg, aParam.gradient_stops_size);
-    WriteParam(aMsg, aParam.complex_clip_regions_size);
-    WriteParam(aMsg, aParam.filters_size);
-    WriteParam(aMsg, aParam.glyph_instances_size);
-  }
-
-  static bool
-  Read(const Message* aMsg, PickleIterator* aIter, WrAuxiliaryListsDescriptor* aResult)
-  {
-    return ReadParam(aMsg, aIter, &aResult->gradient_stops_size)
-        && ReadParam(aMsg, aIter, &aResult->complex_clip_regions_size)
-        && ReadParam(aMsg, aIter, &aResult->filters_size)
-        && ReadParam(aMsg, aIter, &aResult->glyph_instances_size);
-  }
-};
-
 } // namespace IPC
 
 #endif // GFX_WEBRENDERMESSAGEUTILS_H
--- a/gfx/layers/wr/WebRenderPaintedLayer.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayer.cpp
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderPaintedLayer.h"
 
 #include "LayersLogging.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/UpdateImageHelper.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 
 namespace mozilla {
@@ -87,24 +88,25 @@ WebRenderPaintedLayer::UpdateImageClient
 
   return true;
 }
 
 void
 WebRenderPaintedLayer::CreateWebRenderDisplayList(wr::DisplayListBuilder& aBuilder,
                                                   const StackingContextHelper& aSc)
 {
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("PaintedLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
   WrImageKey key = GetImageKey();
   WrBridge()->AddWebRenderParentCommand(OpAddExternalImage(mExternalImageId.value(), key));
   WrManager()->AddImageKeyForDiscard(key);
 
   aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, key);
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
@@ -5,16 +5,17 @@
 
 #include "WebRenderPaintedLayerBlob.h"
 
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "LayersLogging.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/UpdateImageHelper.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace layers {
 
@@ -41,59 +42,60 @@ WebRenderPaintedLayerBlob::RenderLayer(w
   // We have something to paint but can't. This usually happens only in
   // empty transactions
   if (!regionToPaint.IsEmpty() && !WrManager()->GetPaintedLayerCallback()) {
     WrManager()->SetTransactionIncomplete();
     return;
   }
 
   IntSize imageSize(size.ToUnknownSize());
-  RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
-  RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, imageSize, gfx::SurfaceFormat::B8G8R8X8);
-  RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt);
+  if (!regionToPaint.IsEmpty() && WrManager()->GetPaintedLayerCallback()) {
+    RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
+    RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, imageSize, gfx::SurfaceFormat::B8G8R8X8);
+    RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt);
 
-  if (!regionToPaint.IsEmpty() && WrManager()->GetPaintedLayerCallback()) {
     dt->ClearRect(Rect(0, 0, imageSize.width, imageSize.height));
     dt->SetTransform(Matrix().PreTranslate(-bounds.x, -bounds.y));
     RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(dt);
     MOZ_ASSERT(ctx); // already checked the target above
 
     WrManager()->GetPaintedLayerCallback()(this,
                                            ctx,
                                            visibleRegion.ToUnknownRegion(), visibleRegion.ToUnknownRegion(),
                                            DrawRegionClip::DRAW, nsIntRegion(), WrManager()->GetPaintedLayerCallbackData());
 
     if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
       dt->SetTransform(Matrix());
       dt->FillRect(Rect(0, 0, imageSize.width, imageSize.height), ColorPattern(Color(1.0, 0.0, 0.0, 0.5)));
     }
 
+    wr::ByteBuffer bytes;
+    bytes.Allocate(recorder->RecordingSize());
+    DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
+    MOZ_ASSERT(ok);
+
+    //XXX: We should switch to updating the blob image instead of adding a new one
+    //     That will get rid of this discard bit
+    if (mImageKey.isSome()) {
+      WrManager()->AddImageKeyForDiscard(mImageKey.value());
+    }
+    mImageKey = Some(GetImageKey());
+    WrBridge()->SendAddBlobImage(mImageKey.value(), imageSize, size.width * 4, dt->GetFormat(), bytes);
   } else {
-    // we need to reuse the blob image
-    MOZ_ASSERT(mExternalImageId);
-    MOZ_ASSERT(mImageContainer->HasCurrentImage());
     MOZ_ASSERT(GetInvalidRegion().IsEmpty());
   }
 
-  wr::ByteBuffer bytes;
-  bytes.Allocate(recorder->RecordingSize());
-  DebugOnly<bool> ok = recorder->CopyRecording((char*)bytes.AsSlice().begin().get(), bytes.AsSlice().length());
-  MOZ_ASSERT(ok);
-
+  ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
   LayerRect rect = Bounds();
   DumpLayerInfo("PaintedLayer", rect);
 
   LayerRect clipRect = ClipRect().valueOr(rect);
   Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
-  WrClipRegion clip = aBuilder.BuildClipRegion(
+  WrClipRegionToken clip = aBuilder.PushClipRegion(
       sc.ToRelativeWrRect(clipRect),
       mask.ptrOr(nullptr));
 
-  WrImageKey key = GetImageKey();
-  WrBridge()->SendAddBlobImage(key, imageSize, size.width * 4, dt->GetFormat(), bytes);
-  WrManager()->AddImageKeyForDiscard(key);
-
-  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, key);
+  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, wr::ImageRendering::Auto, mImageKey.value());
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.h
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.h
@@ -30,16 +30,19 @@ public:
 
 protected:
   virtual ~WebRenderPaintedLayerBlob()
   {
     MOZ_COUNT_DTOR(WebRenderPaintedLayerBlob);
     if (mExternalImageId.isSome()) {
       WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
     }
+    if (mImageKey.isSome()) {
+      WrManager()->AddImageKeyForDiscard(mImageKey.value());
+    }
   }
 
   wr::MaybeExternalImageId mExternalImageId;
 
 public:
   virtual void InvalidateRegion(const nsIntRegion& aRegion) override
   {
     mInvalidRegion.Add(aRegion);
@@ -47,15 +50,15 @@ public:
   }
 
   Layer* GetLayer() override { return this; }
   void RenderLayer(wr::DisplayListBuilder& aBuilder,
                    const StackingContextHelper& aSc) override;
 private:
   RefPtr<ImageContainer> mImageContainer;
   RefPtr<ImageClient> mImageClient;
-
+  Maybe<WrImageKey> mImageKey;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // GFX_WEBRENDERPAINTEDLAYERBLOB_H
--- a/gfx/layers/wr/WebRenderTextLayer.cpp
+++ b/gfx/layers/wr/WebRenderTextLayer.cpp
@@ -3,16 +3,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 "WebRenderTextLayer.h"
 
 #include "gfxPrefs.h"
 #include "LayersLogging.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 
 #include "mozilla/gfx/2D.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
@@ -20,16 +21,18 @@ using namespace mozilla::gfx;
 void
 WebRenderTextLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                 const StackingContextHelper& aSc)
 {
     if (mBounds.IsEmpty()) {
         return;
     }
 
+    ScrollingLayersHelper scroller(this, aBuilder, aSc);
+
     LayerRect rect = LayerRect::FromUnknownRect(
         // I am not 100% sure this is correct, but it probably is. Because:
         // the bounds are in layer space, and when gecko composites layers it
         // applies the transform to the layer before compositing. However with
         // WebRender compositing, we don't pass the transform on this layer to
         // WR, so WR has no way of knowing about the transformed bounds unless
         // we apply it here. The glyphs that we push to WR should already be
         // taking the transform into account.
--- a/gfx/layers/wr/WebRenderTextureHost.cpp
+++ b/gfx/layers/wr/WebRenderTextureHost.cpp
@@ -151,10 +151,21 @@ WebRenderTextureHost::GetRGBStride()
   if (GetFormat() == gfx::SurfaceFormat::YUV) {
     // XXX this stride is used until yuv image rendering by webrender is used.
     // Software converted RGB buffers strides are aliened to 16
     return gfx::GetAlignedStride<16>(GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8));
   }
   return ImageDataSerializer::ComputeRGBStride(format, GetSize().width);
 }
 
+void
+WebRenderTextureHost::AddWRImage(wr::WebRenderAPI* aAPI,
+                                 Range<const wr::ImageKey>& aImageKeys,
+                                 const wr::ExternalImageId& aExtID)
+{
+  MOZ_ASSERT(mWrappedTextureHost);
+  MOZ_ASSERT(mExternalImageId == aExtID);
+
+  mWrappedTextureHost->AddWRImage(aAPI, aImageKeys, aExtID);
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderTextureHost.h
+++ b/gfx/layers/wr/WebRenderTextureHost.h
@@ -60,16 +60,20 @@ public:
   virtual WebRenderTextureHost* AsWebRenderTextureHost() override { return this; }
 
   wr::ExternalImageId GetExternalImageKey() { return mExternalImageId; }
 
   int32_t GetRGBStride();
 
   bool IsWrappingNativeHandle() { return mIsWrappingNativeHandle; }
 
+  virtual void AddWRImage(wr::WebRenderAPI* aAPI,
+                          Range<const wr::ImageKey>& aImageKeys,
+                          const wr::ExternalImageId& aExtID) override;
+
 protected:
   void CreateRenderTextureHost(const SurfaceDescriptor& aDesc, TextureHost* aTexture);
 
   RefPtr<TextureHost> mWrappedTextureHost;
   wr::ExternalImageId mExternalImageId;
 
   bool mIsWrappingNativeHandle;
 };
--- a/gfx/thebes/gfxPrefs.cpp
+++ b/gfx/thebes/gfxPrefs.cpp
@@ -306,8 +306,13 @@ void gfxPrefs::CopyPrefValue(const GfxPr
 {
   *aOutValue = aValue->get_nsCString().get();
 }
 
 bool gfxPrefs::OverrideBase_WebRender()
 {
   return gfx::gfxVars::UseWebRender();
 }
+
+bool gfxPrefs::OverrideBase_WebRendest()
+{
+  return gfx::gfxVars::UseWebRender() && gfxPrefs::WebRendestEnabled();
+}
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -454,16 +454,17 @@ private:
   DECL_GFX_PREF(Once, "gfx.touch.resample.min-delta",          TouchResampleMinDelta, int32_t, 2);
   DECL_GFX_PREF(Once, "gfx.touch.resample.old-touch-threshold",TouchResampleOldTouchThreshold, int32_t, 17);
   DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust",       TouchVsyncSampleAdjust, int32_t, 5);
 
   DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   DECL_OVERRIDE_PREF(Live, "gfx.webrender.omta.enabled",       WebRenderOMTAEnabled, gfxPrefs::OverrideBase_WebRender());
   DECL_GFX_PREF(Live, "gfx.webrender.profiler.enable",         WebRenderProfilerEnabled, bool, false);
+  DECL_GFX_PREF(Live, "gfx.webrendest.enabled",                WebRendestEnabled, bool, false);
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
   DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled",          ScreenMirroringEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist",       IgnoreDXInterop2Blacklist, bool, false);
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 #if defined(XP_MACOSX)
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
@@ -485,32 +486,33 @@ private:
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabledDoNotUseDirectly, bool, false);
-  DECL_GFX_PREF(Live, "layers.advanced.background-color",      LayersAllowBackgroundColorLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-image", LayersAllowBackgroundImage, false);
-  DECL_GFX_PREF(Live, "layers.advanced.basic-layer.enabled",   LayersAdvancedBasicLayerEnabled, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.border-layers",    LayersAllowBorderLayers, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-inset-layers", LayersAllowInsetBoxShadow, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers", LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.bullet-layers",         LayersAllowBulletLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-color",        LayersAllowBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.background-image",        LayersAllowBackgroundImage, gfxPrefs::OverrideBase_WebRendest());
+  DECL_GFX_PREF(Live, "layers.advanced.basic-layer.enabled",          LayersAdvancedBasicLayerEnabled, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.border-layers",           LayersAllowBorderLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-inset-layers",  LayersAllowInsetBoxShadow, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers",  LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.bullet-layers",           LayersAllowBulletLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.button-foreground-layers", LayersAllowButtonForegroundLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",     LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.columnRule-layers",     LayersAllowColumnRuleLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",            LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers",       LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.displaybuttonborder-layers", LayersAllowDisplayButtonBorder, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.image-layers",          LayersAllowImageLayers, bool, false);
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",   LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",      LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_GFX_PREF(Live, "layers.advanced.text-layers",           LayersAllowTextLayers, bool, false);
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.image-layers",            LayersAllowImageLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",          LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",             LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.table",                   LayersAllowTable, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.text-layers",             LayersAllowTextLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
   DECL_GFX_PREF(Live, "layers.bench.enabled",                  LayersBenchEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.bufferrotation.enabled",         BufferRotationEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.child-process-shutdown",         ChildProcessShutdown, bool, true);
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
@@ -752,16 +754,17 @@ private:
   static void CopyPrefValue(const GfxPrefValue* aValue, std::string* aOutValue);
 
   static void AssertMainThread();
 
   // Some wrapper functions for the DECL_OVERRIDE_PREF prefs' base values, so
   // that we don't to include all sorts of header files into this gfxPrefs.h
   // file.
   static bool OverrideBase_WebRender();
+  static bool OverrideBase_WebRendest();
 
   gfxPrefs();
   ~gfxPrefs();
   gfxPrefs(const gfxPrefs&) = delete;
   gfxPrefs& operator=(const gfxPrefs&) = delete;
 };
 
 #undef DECL_GFX_PREF /* Don't need it outside of this file */
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.36.0"
+version = "0.39.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
@@ -20,22 +20,22 @@ byteorder = "1.0"
 euclid = "0.11.2"
 fnv = "1.0"
 gleam = "0.4.3"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
 offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
-threadpool = "1.3.2"
+rayon = {version = "0.7", features = ["unstable"]}
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
-gamma-lut = "0.1"
+gamma-lut = "0.2"
 thread_profiler = "0.1.1"
-plane-split = "0.2.1"
+plane-split = "0.3"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 servo-glutin = "0.10.1"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
--- a/gfx/webrender/doc/CLIPPING.md
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -7,17 +7,17 @@ and can be reused between items as well 
 
 ## Clips
 
 Clips are defined using the ClipRegion in both cases.
 
 ```rust
 pub struct ClipRegion {
     pub main: LayoutRect,
-    pub complex: ItemRange,
+    pub complex: ItemRange<ComplexClip>,
     pub image_mask: Option<ImageMask>,
 }
 ```
 
 `main` defines a rectangular clip, while the other members make that rectangle
 smaller. `complex`, if it is not empty, defines the boundaries of a rounded
 rectangle. While `image_mask` defines the positioning, repetition, and data of
 a masking image.
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -12,20 +12,20 @@ extern crate webrender_traits;
 use app_units::Au;
 use gleam::gl;
 use glutin::TouchPhase;
 use std::collections::HashMap;
 use std::env;
 use std::fs::File;
 use std::io::Read;
 use std::path::PathBuf;
-use webrender_traits::{ColorF, Epoch, GlyphInstance};
+use webrender_traits::{ClipRegionToken, ColorF, DisplayListBuilder, Epoch, GlyphInstance};
 use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
-use webrender_traits::{PipelineId, TransformStyle, BoxShadowClipMode};
+use webrender_traits::{PipelineId, RenderApi, TransformStyle, BoxShadowClipMode};
 
 #[derive(Debug)]
 enum Gesture {
     None,
     Pan,
     Zoom,
 }
 
@@ -186,16 +186,36 @@ impl webrender_traits::RenderNotifier fo
     }
 
     fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
         #[cfg(not(target_os = "android"))]
         self.window_proxy.wakeup_event_loop();
     }
 }
 
+fn push_sub_clip(api: &RenderApi, builder: &mut DisplayListBuilder, bounds: &LayoutRect)
+                 -> ClipRegionToken {
+    let mask_image = api.generate_image_key();
+    api.add_image(mask_image,
+                  ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+                  ImageData::new(vec![0, 80, 180, 255]),
+                  None);
+    let mask = webrender_traits::ImageMask {
+        image: mask_image,
+        rect: LayoutRect::new(LayoutPoint::new(75.0, 75.0), LayoutSize::new(100.0, 100.0)),
+        repeat: false,
+    };
+    let complex = webrender_traits::ComplexClipRegion::new(
+        LayoutRect::new(LayoutPoint::new(50.0, 50.0), LayoutSize::new(100.0, 100.0)),
+        webrender_traits::BorderRadius::uniform(20.0));
+
+    builder.push_clip_region(bounds, vec![complex], Some(mask))
+}
+
+
 fn main() {
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
     } else {
         None
     };
 
@@ -237,51 +257,36 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
-    let sub_clip = {
-        let mask_image = api.generate_image_key();
-        api.add_image(
-            mask_image,
-            ImageDescriptor::new(2, 2, ImageFormat::A8, true),
-            ImageData::new(vec![0, 80, 180, 255]),
-            None,
-        );
-        let mask = webrender_traits::ImageMask {
-            image: mask_image,
-            rect: LayoutRect::new(LayoutPoint::new(75.0, 75.0), LayoutSize::new(100.0, 100.0)),
-            repeat: false,
-        };
-        let complex = webrender_traits::ComplexClipRegion::new(
-            LayoutRect::new(LayoutPoint::new(50.0, 50.0), LayoutSize::new(100.0, 100.0)),
-            webrender_traits::BorderRadius::uniform(20.0));
 
-        builder.new_clip_region(&bounds, vec![complex], Some(mask))
-    };
-
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_rect(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                      sub_clip,
+                      clip,
                       ColorF::new(0.0, 1.0, 0.0, 1.0));
+
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_rect(LayoutRect::new(LayoutPoint::new(250.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                      sub_clip,
+                      clip,
                       ColorF::new(0.0, 1.0, 0.0, 1.0));
     let border_side = webrender_traits::BorderSide {
         color: ColorF::new(0.0, 0.0, 1.0, 1.0),
         style: webrender_traits::BorderStyle::Groove,
     };
     let border_widths = webrender_traits::BorderWidths {
         top: 10.0,
         left: 10.0,
@@ -290,18 +295,20 @@ fn main() {
     };
     let border_details = webrender_traits::BorderDetails::Normal(webrender_traits::NormalBorder {
         top: border_side,
         right: border_side,
         bottom: border_side,
         left: border_side,
         radius: webrender_traits::BorderRadius::uniform(20.0),
     });
+
+    let clip = push_sub_clip(&api, &mut builder, &bounds);
     builder.push_border(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
-                        sub_clip,
+                        clip,
                         border_widths,
                         border_details);
 
 
     if false { // draw text?
         let font_key = api.generate_font_key();
         let font_bytes = load_file("res/FreeSans.ttf");
         api.add_raw_font(font_key, font_bytes, 0);
@@ -354,37 +361,38 @@ fn main() {
                 point: LayoutPoint::new(600.0, 100.0),
             },
             GlyphInstance {
                 index: 17,
                 point: LayoutPoint::new(650.0, 100.0),
             },
         ];
 
+        let clip = builder.push_clip_region(&bounds, Vec::new(), None);
         builder.push_text(text_bounds,
-                          webrender_traits::ClipRegion::simple(&bounds),
+                          clip,
                           &glyphs,
                           font_key,
                           ColorF::new(1.0, 1.0, 0.0, 1.0),
                           Au::from_px(32),
-                          Au::from_px(0),
+                          0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
         let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
                                                 LayoutSize::new(50.0, 50.0));
         let offset = LayoutPoint::new(10.0, 10.0);
         let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
         let blur_radius = 0.0;
         let spread_radius = 0.0;
         let simple_border_radius = 8.0;
         let box_shadow_type = BoxShadowClipMode::Inset;
-        let full_screen_clip = builder.new_clip_region(&bounds, Vec::new(), None);
+        let full_screen_clip = builder.push_clip_region(&bounds, Vec::new(), None);
 
         builder.push_box_shadow(rect,
                                 full_screen_clip,
                                 simple_box_bounds,
                                 offset,
                                 color,
                                 blur_radius,
                                 spread_radius,
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -3,21 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate app_units;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
+extern crate rayon;
 
 use gleam::gl;
+use rayon::ThreadPool;
+use rayon::Configuration as ThreadPoolConfig;
 use std::collections::HashMap;
+use std::collections::hash_map::Entry;
+use std::sync::Arc;
+use std::sync::mpsc::{channel, Sender, Receiver};
 use webrender_traits::{BlobImageData, BlobImageDescriptor, BlobImageError, BlobImageRenderer, BlobImageRequest};
-use webrender_traits::{BlobImageResult, ImageStore, ClipRegion, ColorF, ColorU, Epoch};
+use webrender_traits::{BlobImageResult, TileOffset, ImageStore, ColorF, ColorU, Epoch};
 use webrender_traits::{DeviceUintSize, DeviceUintRect, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageRendering, ImageKey, TileSize};
 use webrender_traits::{PipelineId, RasterizedBlobImage, TransformStyle};
 
 // This example shows how to implement a very basic BlobImageRenderer that can only render
 // a checkerboard pattern.
 
 // The deserialized command list internally used by this example is just a color.
@@ -34,110 +40,181 @@ fn deserialize_blob(blob: &[u8]) -> Resu
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
         (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(ColorU::new(r, g, b, a)),
         (Some(&a), None, None, None) => Ok(ColorU::new(a, a, a, a)),
         _ => Err(()),
     }
 }
 
+// This is the function that applies the deserialized drawing commands and generates
+// actual image data.
+fn render_blob(
+    commands: Arc<ImageRenderingCommands>,
+    descriptor: &BlobImageDescriptor,
+    tile: Option<TileOffset>,
+) -> BlobImageResult {
+    let color = *commands;
+
+    // Allocate storage for the result. Right now the resource cache expects the
+    // tiles to have have no stride or offset.
+    let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+
+    // Generate a per-tile pattern to see it in the demo. For a real use case it would not
+    // make sense for the rendered content to depend on its tile.
+    let tile_checker = match tile {
+        Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
+        None => true,
+    };
+
+    for y in 0..descriptor.height {
+        for x in 0..descriptor.width {
+            // Apply the tile's offset. This is important: all drawing commands should be
+            // translated by this offset to give correct results with tiled blob images.
+            let x2 = x + descriptor.offset.x as u32;
+            let y2 = y + descriptor.offset.y as u32;
+
+            // Render a simple checkerboard pattern
+            let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 };
+            // ..nested in the per-tile cherkerboard pattern
+            let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
+
+            match descriptor.format {
+                ImageFormat::RGBA8 => {
+                    texels.push(color.b * checker + tc);
+                    texels.push(color.g * checker + tc);
+                    texels.push(color.r * checker + tc);
+                    texels.push(color.a * checker + tc);
+                }
+                ImageFormat::A8 => {
+                    texels.push(color.a * checker + tc);
+                }
+                _ => {
+                    return Err(BlobImageError::Other(format!(
+                        "Usupported image format {:?}",
+                        descriptor.format
+                    )));
+                }
+            }
+        }
+    }
+
+    Ok(RasterizedBlobImage {
+        data: texels,
+        width: descriptor.width,
+        height: descriptor.height,
+    })
+}
+
 struct CheckerboardRenderer {
+    // We are going to defer the rendering work to worker threads.
+    // Using a pre-built Arc<ThreadPool> rather than creating our own threads
+    // makes it possible to share the same thread pool as the glyph renderer (if we
+    // want to).
+    workers: Arc<ThreadPool>,
+
+    // the workers will use an mpsc channel to communicate the result.
+    tx: Sender<(BlobImageRequest, BlobImageResult)>,
+    rx: Receiver<(BlobImageRequest, BlobImageResult)>,
+
     // The deserialized drawing commands.
-    image_cmds: HashMap<ImageKey, ImageRenderingCommands>,
+    // In this example we store them in Arcs. This isn't necessary since in this simplified
+    // case the command list is a simple 32 bits value and would be cheap to clone before sending
+    // to the workers. But in a more realistic scenario the commands would typically be bigger
+    // and more expensive to clone, so let's pretend it is also the case here.
+    image_cmds: HashMap<ImageKey, Arc<ImageRenderingCommands>>,
 
-    // The images rendered in the current frame (not kept here between frames)
-    rendered_images: HashMap<BlobImageRequest, BlobImageResult>,
+    // The images rendered in the current frame (not kept here between frames).
+    rendered_images: HashMap<BlobImageRequest, Option<BlobImageResult>>,
 }
 
 impl CheckerboardRenderer {
-    fn new() -> Self {
+    fn new(workers: Arc<ThreadPool>) -> Self {
+        let (tx, rx) = channel();
         CheckerboardRenderer {
             image_cmds: HashMap::new(),
             rendered_images: HashMap::new(),
+            workers: workers,
+            tx: tx,
+            rx: rx,
         }
     }
 }
 
 impl BlobImageRenderer for CheckerboardRenderer {
     fn add(&mut self, key: ImageKey, cmds: BlobImageData, _: Option<TileSize>) {
-        self.image_cmds.insert(key, deserialize_blob(&cmds[..]).unwrap());
+        self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
     fn update(&mut self, key: ImageKey, cmds: BlobImageData) {
         // Here, updating is just replacing the current version of the commands with
-        // the new one (no incremental updates)
-        self.image_cmds.insert(key, deserialize_blob(&cmds[..]).unwrap());
+        // the new one (no incremental updates).
+        self.image_cmds.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
     }
 
     fn delete(&mut self, key: ImageKey) {
         self.image_cmds.remove(&key);
     }
 
     fn request(&mut self,
                request: BlobImageRequest,
                descriptor: &BlobImageDescriptor,
                _dirty_rect: Option<DeviceUintRect>,
                _images: &ImageStore) {
-        let color = self.image_cmds.get(&request.key).unwrap().clone();
-
-        // Allocate storage for the result. Right now the resource cache expects the
-        // tiles to have have no stride or offset.
-        let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+        // This method is where we kick off our rendering jobs.
+        // It should avoid doing work on the calling thread as much as possible.
+        // In this example we will use the thread pool to render individual tiles.
 
-        // Generate a per-tile pattern to see it in the demo. For a real use case it would not
-        // make sense for the rendered content to depend on its tile.
-        let tile_checker = match request.tile {
-            Some(tile) => (tile.x % 2 == 0) != (tile.y % 2 == 0),
-            None => true,
-        };
+        // Gather the input data to send to a worker thread.
+        let cmds = Arc::clone(&self.image_cmds.get(&request.key).unwrap());
+        let tx = self.tx.clone();
+        let descriptor = descriptor.clone();
 
-        for y in 0..descriptor.height {
-            for x in 0..descriptor.width {
-                // Apply the tile's offset. This is important: all drawing commands should be
-                // translated by this offset to give correct results with tiled blob images.
-                let x2 = x + descriptor.offset.x as u32;
-                let y2 = y + descriptor.offset.y as u32;
+        self.workers.spawn_async(move || {
+            let result = render_blob(cmds, &descriptor, request.tile);
+            tx.send((request, result)).unwrap();
+        });
 
-                // Render a simple checkerboard pattern
-                let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { 1 } else { 0 };
-                // ..nested in the per-tile cherkerboard pattern
-                let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
+        // Add None in the map of rendered images. This makes it possible to differentiate
+        // between commands that aren't finished yet (entry in the map is equal to None) and
+        // keys that have never been requested (entry not in the map), which would cause deadlocks
+        // if we were to block upon receing their result in resolve!
+        self.rendered_images.insert(request, None);
+    }
+
+    fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
+        // In this method we wait until the work is complete on the worker threads and
+        // gather the results.
 
-                match descriptor.format {
-                    ImageFormat::RGBA8 => {
-                        texels.push(color.b * checker + tc);
-                        texels.push(color.g * checker + tc);
-                        texels.push(color.r * checker + tc);
-                        texels.push(color.a * checker + tc);
-                    }
-                    ImageFormat::A8 => {
-                        texels.push(color.a * checker + tc);
-                    }
-                    _ => {
-                        self.rendered_images.insert(request,
-                            Err(BlobImageError::Other(format!(
-                                "Usupported image format {:?}",
-                                descriptor.format
-                            )))
-                        );
-                        return;
-                    }
+        // First look at whether we have already received the rendered image
+        // that we are looking for.
+        match self.rendered_images.entry(request) {
+            Entry::Vacant(_) => {
+                return Err(BlobImageError::InvalidKey);
+            }
+            Entry::Occupied(entry) => {
+                // None means we haven't yet received the result.
+                if entry.get().is_some() {
+                    let result = entry.remove();
+                    return result.unwrap();
                 }
             }
         }
 
-        self.rendered_images.insert(request, Ok(RasterizedBlobImage {
-            data: texels,
-            width: descriptor.width,
-            height: descriptor.height,
-        }));
-    }
+        // We haven't received it yet, pull from the channel until we receive it.
+        while let Ok((req, result)) = self.rx.recv() {
+            if req == request {
+                // There it is!
+                return result
+            }
+            self.rendered_images.insert(req, Some(result));
+        }
 
-    fn resolve(&mut self, request: BlobImageRequest) -> BlobImageResult {
-        self.rendered_images.remove(&request).unwrap_or(Err(BlobImageError::InvalidKey))
+        // If we break out of the loop above it means the channel closed unexpectedly.
+        Err(BlobImageError::Other("Channel closed".into()))
     }
 }
 
 fn main() {
     let window = glutin::WindowBuilder::new()
                 .with_title("WebRender Sample (BlobImageRenderer)")
                 .with_multitouch()
                 .with_gl(glutin::GlRequest::GlThenGles {
@@ -155,19 +232,28 @@ fn main() {
         gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
         gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
     };
 
     println!("OpenGL version {}", gl.get_string(gl::VERSION));
 
     let (width, height) = window.get_inner_size_pixels().unwrap();
 
+    let worker_config = ThreadPoolConfig::new().thread_name(|idx|{
+        format!("WebRender:Worker#{}", idx)
+    });
+
+    let workers = Arc::new(ThreadPool::new(worker_config).unwrap());
+
     let opts = webrender::RendererOptions {
         debug: true,
-        blob_image_renderer: Some(Box::new(CheckerboardRenderer::new())),
+        workers: Some(Arc::clone(&workers)),
+        // Register our blob renderer, so that WebRender integrates it in the resource cache..
+        // Share the same pool of worker threads between WebRender and our blob renderer.
+        blob_image_renderer: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))),
         device_pixel_ratio: window.hidpi_factor(),
         .. Default::default()
     };
 
     let size = DeviceUintSize::new(width, height);
     let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
     let api = sender.create_api();
 
@@ -189,38 +275,42 @@ fn main() {
     api.add_image(
         blob_img2,
         ImageDescriptor::new(200, 200, ImageFormat::RGBA8, true),
         ImageData::new_blob_image(serialize_blob(ColorU::new(50, 150, 50, 255))),
         None,
     );
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
+
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_image(
         LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutSize::new(500.0, 500.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         LayoutSize::new(500.0, 500.0),
         LayoutSize::new(0.0, 0.0),
         ImageRendering::Auto,
         blob_img1,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_image(
         LayoutRect::new(LayoutPoint::new(600.0, 60.0), LayoutSize::new(200.0, 200.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         LayoutSize::new(200.0, 200.0),
         LayoutSize::new(0.0, 0.0),
         ImageRendering::Auto,
         blob_img2,
     );
 
     builder.pop_stacking_context();
 
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -5,17 +5,17 @@
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
 
 use gleam::gl;
 use std::env;
 use std::path::PathBuf;
-use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
+use webrender_traits::{ClipId, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
 use webrender_traits::{LayoutSize, PipelineId, ScrollEventPhase, ScrollLocation, TransformStyle};
 use webrender_traits::WorldPoint;
 
 struct Notifier {
     window_proxy: glutin::WindowProxy,
 }
 
 impl Notifier {
@@ -95,19 +95,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -118,57 +119,65 @@ fn main() {
                                       LayoutRect::new(LayoutPoint::new(10.0, 10.0),
                                                       LayoutSize::zero()),
                                       None,
                                       TransformStyle::Flat,
                                       None,
                                       webrender_traits::MixBlendMode::Normal,
                                       Vec::new());
         // set the scrolling clip
+        let clip = builder.push_clip_region(&scrollbox, vec![], None);
         let clip_id = builder.define_clip((0, 0).to(1000, 1000),
-                                          ClipRegion::simple(&scrollbox),
+                                          clip,
                                           Some(ClipId::new(42, pipeline_id)));
         builder.push_clip_id(clip_id);
         // now put some content into it.
         // start with a white background
+        let clip = builder.push_clip_region(&(0, 0).to(1000, 1000), vec![], None);
         builder.push_rect((0, 0).to(500, 500),
-                          ClipRegion::simple(&(0, 0).to(1000, 1000)),
+                          clip,
                           ColorF::new(1.0, 1.0, 1.0, 1.0));
         // let's make a 50x50 blue square as a visual reference
+        let clip = builder.push_clip_region(&(0, 0).to(50, 50), vec![], None);
         builder.push_rect((0, 0).to(50, 50),
-                          ClipRegion::simple(&(0, 0).to(50, 50)),
+                          clip,
                           ColorF::new(0.0, 0.0, 1.0, 1.0));
         // and a 50x50 green square next to it with an offset clip
         // to see what that looks like
+        let clip = builder.push_clip_region(&(60, 10).to(110, 60), vec![], None);
         builder.push_rect((50, 0).to(100, 50),
-                          ClipRegion::simple(&(60, 10).to(110, 60)),
+                          clip,
                           ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         // Below the above rectangles, set up a nested scrollbox. It's still in
         // the same stacking context, so note that the rects passed in need to
         // be relative to the stacking context.
+        let clip = builder.push_clip_region(&(0, 100).to(200, 300), vec![], None);
         let nested_clip_id = builder.define_clip((0, 100).to(300, 400),
-                                                 ClipRegion::simple(&(0, 100).to(200, 300)),
+                                                 clip,
                                                  Some(ClipId::new(43, pipeline_id)));
         builder.push_clip_id(nested_clip_id);
         // give it a giant gray background just to distinguish it and to easily
         // visually identify the nested scrollbox
+        let clip = builder.push_clip_region(&(-1000, -1000).to(5000, 5000), vec![], None);
         builder.push_rect((-1000, -1000).to(5000, 5000),
-                          ClipRegion::simple(&(-1000, -1000).to(5000, 5000)),
+                          clip,
                           ColorF::new(0.5, 0.5, 0.5, 1.0));
         // add a teal square to visualize the scrolling/clipping behaviour
         // as you scroll the nested scrollbox with WASD keys
+        let clip = builder.push_clip_region(&(0, 100).to(50, 150), vec![], None);
         builder.push_rect((0, 100).to(50, 150),
-                          ClipRegion::simple(&(0, 100).to(50, 150)),
+                          clip,
                           ColorF::new(0.0, 1.0, 1.0, 1.0));
         // just for good measure add another teal square in the bottom-right
         // corner of the nested scrollframe content, which can be scrolled into
         // view by the user
+        let clip = builder.push_clip_region(&(250, 350).to(300, 400), vec![], None);
         builder.push_rect((250, 350).to(300, 400),
-                          ClipRegion::simple(&(250, 350).to(300, 400)),
+                          clip,
                           ColorF::new(0.0, 1.0, 1.0, 1.0));
         builder.pop_clip_id(); // nested_clip_id
 
         builder.pop_clip_id(); // clip_id
         builder.pop_stacking_context();
     }
 
     builder.pop_stacking_context();
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -9,17 +9,17 @@ extern crate glutin;
 extern crate webrender;
 extern crate webrender_traits;
 
 use gleam::gl;
 use glutin::TouchPhase;
 use std::collections::HashMap;
 use std::env;
 use std::path::PathBuf;
-use webrender_traits::{ClipRegion, ColorF, Epoch};
+use webrender_traits::{ColorF, Epoch};
 use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
 use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
 use webrender_traits::{PipelineId, TransformStyle};
 use webrender_traits::{YuvColorSpace, YuvData};
 
 #[derive(Debug)]
 enum Gesture {
     None,
@@ -229,19 +229,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -270,26 +271,28 @@ fn main() {
     );
     api.add_image(
         yuv_chanel3,
         ImageDescriptor::new(100, 100, ImageFormat::A8, true),
         ImageData::new(vec![127; 100 * 100]),
         None,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_yuv_image(
         LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         YuvData::NV12(yuv_chanel1, yuv_chanel2),
         YuvColorSpace::Rec601,
     );
 
+    let clip = builder.push_clip_region(&bounds, vec![], None);
     builder.push_yuv_image(
         LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)),
-        ClipRegion::simple(&bounds),
+        clip,
         YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
         YuvColorSpace::Rec601,
     );
 
     builder.pop_stacking_context();
 
     api.set_display_list(
         Some(root_background_color),
--- a/gfx/webrender/res/cs_clip_border.fs.glsl
+++ b/gfx/webrender/res/cs_clip_border.fs.glsl
@@ -16,15 +16,25 @@ void main(void) {
     float d1 = distance_to_line(vPoint_Tangent1.xy,
                                 vPoint_Tangent1.zw,
                                 clip_relative_pos);
 
     // Get AA widths based on zoom / scale etc.
     vec2 fw = fwidth(local_pos);
     float afwidth = length(fw);
 
+    // SDF subtract edges for dash clip
+    float dash_distance = max(d0, -d1);
+
+    // Get distance from dot.
+    float dot_distance = distance(clip_relative_pos, vDotParams.xy) - vDotParams.z;
+
+    // Select between dot/dash clip based on mode.
+    float d = mix(dash_distance, dot_distance, vAlphaMask.x);
+
     // Apply AA over half a device pixel for the clip.
-    float d = smoothstep(-0.5 * afwidth,
-                         0.5 * afwidth,
-                         max(d0, -d1));
+    d = 1.0 - smoothstep(0.0, 0.5 * afwidth, d);
+
+    // Completely mask out clip if zero'ing out the rect.
+    d = d * vAlphaMask.y;
 
     oFragColor = vec4(d, 0.0, 0.0, 1.0);
 }
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -5,8 +5,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
+flat varying vec3 vDotParams;
+flat varying vec2 vAlphaMask;
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -1,52 +1,112 @@
 #line 1
 /* 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/. */
 
+// Matches BorderCorner enum in border.rs
+#define CORNER_TOP_LEFT     0
+#define CORNER_TOP_RIGHT    1
+#define CORNER_BOTTOM_LEFT  2
+#define CORNER_BOTTOM_RIGHT 3
+
+// Matches BorderCornerClipKind enum in border.rs
+#define CLIP_MODE_DASH      0
+#define CLIP_MODE_DOT       1
+
 // Header for a border corner clip.
 struct BorderCorner {
     RectWithSize rect;
     vec2 clip_center;
-    vec2 sign_modifier;
+    int corner;
+    int clip_mode;
 };
 
 BorderCorner fetch_border_corner(int index) {
     vec4 data[2] = fetch_data_2(index);
     return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
                         data[1].xy,
-                        data[1].zw);
+                        int(data[1].z),
+                        int(data[1].w));
 }
 
 // Per-dash clip information.
-// TODO: Expand this to handle dots in the future!
-struct BorderClip {
+struct BorderClipDash {
     vec4 point_tangent_0;
     vec4 point_tangent_1;
 };
 
-BorderClip fetch_border_clip(int index) {
+BorderClipDash fetch_border_clip_dash(int index) {
     vec4 data[2] = fetch_data_2(index);
-    return BorderClip(data[0], data[1]);
+    return BorderClipDash(data[0], data[1]);
+}
+
+// Per-dot clip information.
+struct BorderClipDot {
+    vec3 center_radius;
+};
+
+BorderClipDot fetch_border_clip_dot(int index) {
+    vec4 data[2] = fetch_data_2(index);
+    return BorderClipDot(data[0].xyz);
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
 
     // Fetch the header information for this corner clip.
     BorderCorner corner = fetch_border_corner(cci.data_index);
     vClipCenter = corner.clip_center;
 
-    // Fetch the information about this particular dash.
-    BorderClip clip = fetch_border_clip(cci.data_index + cci.segment_index + 1);
-    vPoint_Tangent0 = clip.point_tangent_0 * corner.sign_modifier.xyxy;
-    vPoint_Tangent1 = clip.point_tangent_1 * corner.sign_modifier.xyxy;
+    if (cci.segment_index == 0) {
+        // The first segment is used to zero out the border corner.
+        vAlphaMask = vec2(0.0);
+        vDotParams = vec3(0.0);
+        vPoint_Tangent0 = vec4(1.0);
+        vPoint_Tangent1 = vec4(1.0);
+    } else {
+        vec2 sign_modifier;
+        switch (corner.corner) {
+            case CORNER_TOP_LEFT:
+                sign_modifier = vec2(-1.0);
+                break;
+            case CORNER_TOP_RIGHT:
+                sign_modifier = vec2(1.0, -1.0);
+                break;
+            case CORNER_BOTTOM_RIGHT:
+                sign_modifier = vec2(1.0);
+                break;
+            case CORNER_BOTTOM_LEFT:
+                sign_modifier = vec2(-1.0, 1.0);
+                break;
+        };
+
+        switch (corner.clip_mode) {
+            case CLIP_MODE_DASH: {
+                // Fetch the information about this particular dash.
+                BorderClipDash dash = fetch_border_clip_dash(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
+                vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
+                vDotParams = vec3(0.0);
+                vAlphaMask = vec2(0.0, 1.0);
+                break;
+            }
+            case CLIP_MODE_DOT: {
+                BorderClipDot cdot = fetch_border_clip_dot(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = vec4(1.0);
+                vPoint_Tangent1 = vec4(1.0);
+                vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
+                vAlphaMask = vec2(1.0, 1.0);
+                break;
+            }
+        }
+    }
 
     // Get local vertex position for the corner rect.
     // TODO(gw): We could reduce the number of pixels written here
     // by calculating a tight fitting bounding box of the dash itself.
     vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size;
 
     // Transform to world pos
     vec4 world_pos = layer.transform * vec4(pos, 0.0, 1.0);
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.fs.glsl
@@ -1,41 +1,57 @@
 /* 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/. */
 
-float rounded_rect(vec2 pos) {
-    vec2 ref_tl = vClipRect.xy + vec2( vClipRadius.x,  vClipRadius.x);
-    vec2 ref_tr = vClipRect.zy + vec2(-vClipRadius.y,  vClipRadius.y);
-    vec2 ref_br = vClipRect.zw + vec2(-vClipRadius.z, -vClipRadius.z);
-    vec2 ref_bl = vClipRect.xw + vec2( vClipRadius.w, -vClipRadius.w);
+float clip_against_ellipse_if_needed(vec2 pos,
+                                     float current_distance,
+                                     vec4 ellipse_center_radius,
+                                     vec2 sign_modifier,
+                                     float afwidth) {
+    float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
+                                                 ellipse_center_radius.zw);
 
-    float d_tl = distance(pos, ref_tl);
-    float d_tr = distance(pos, ref_tr);
-    float d_br = distance(pos, ref_br);
-    float d_bl = distance(pos, ref_bl);
+    return mix(current_distance,
+               ellipse_distance + afwidth,
+               all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
+}
 
-    float pixels_per_fragment = length(fwidth(pos.xy));
-    float nudge = 0.5 * pixels_per_fragment;
-    vec4 distances = vec4(d_tl, d_tr, d_br, d_bl) - vClipRadius + nudge;
+float rounded_rect(vec2 pos) {
+    float current_distance = 0.0;
+
+    // Apply AA
+    float afwidth = 0.5 * length(fwidth(pos));
 
-    bvec4 is_out = bvec4(pos.x < ref_tl.x && pos.y < ref_tl.y,
-                         pos.x > ref_tr.x && pos.y < ref_tr.y,
-                         pos.x > ref_br.x && pos.y > ref_br.y,
-                         pos.x < ref_bl.x && pos.y > ref_bl.y);
+    // Clip against each ellipse.
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TL,
+                                                      vec2(1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TR,
+                                                      vec2(-1.0, 1.0),
+                                                      afwidth);
 
-    float distance_from_border = dot(vec4(is_out),
-                                     max(vec4(0.0, 0.0, 0.0, 0.0), distances));
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BR,
+                                                      vec2(-1.0),
+                                                      afwidth);
 
-    // Move the distance back into pixels.
-    distance_from_border /= pixels_per_fragment;
-    // Apply a more gradual fade out to transparent.
-    //distance_from_border -= 0.5;
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BL,
+                                                      vec2(1.0, -1.0),
+                                                      afwidth);
 
-    return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
+    return smoothstep(0.0, afwidth, 1.0 - current_distance);
 }
 
 
 void main(void) {
     float alpha = 1.f;
     vec2 local_pos = init_transform_fs(vPos, alpha);
 
     float clip_alpha = rounded_rect(local_pos);
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,10 +1,12 @@
 #line 1
 
 /* 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/. */
 
 varying vec3 vPos;
-flat varying vec4 vClipRect;
-flat varying vec4 vClipRadius;
 flat varying float vClipMode;
+flat varying vec4 vClipCenter_Radius_TL;
+flat varying vec4 vClipCenter_Radius_TR;
+flat varying vec4 vClipCenter_Radius_BL;
+flat varying vec4 vClipCenter_Radius_BR;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -52,14 +52,25 @@ void main(void) {
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cci.segment_index);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
-    vClipRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
-    vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
-                       clip.top_right.outer_inner_radius.x,
-                       clip.bottom_right.outer_inner_radius.x,
-                       clip.bottom_left.outer_inner_radius.x);
+
+    RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
+
+    vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
+                                 clip.top_left.outer_inner_radius.xy);
+
+    vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
+                                 clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
+                                 clip.top_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
+                                 clip.bottom_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
+                                 clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
+                                 clip.bottom_left.outer_inner_radius.xy);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -257,16 +257,33 @@ AlphaBatchTask fetch_alpha_batch_task(in
     task.render_target_origin = data.data0.xy;
     task.size = data.data0.zw;
     task.screen_space_origin = data.data1.xy;
     task.render_target_layer_index = data.data1.z;
 
     return task;
 }
 
+struct ReadbackTask {
+    vec2 render_target_origin;
+    vec2 size;
+    float render_target_layer_index;
+};
+
+ReadbackTask fetch_readback_task(int index) {
+    RenderTaskData data = fetch_render_task(index);
+
+    ReadbackTask task;
+    task.render_target_origin = data.data0.xy;
+    task.size = data.data0.zw;
+    task.render_target_layer_index = data.data1.x;
+
+    return task;
+}
+
 struct ClipArea {
     vec4 task_bounds;
     vec4 screen_origin_target_index;
     vec4 inner_rect;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
@@ -319,18 +336,18 @@ RadialGradient fetch_radial_gradient(int
 
 struct Border {
     vec4 style;
     vec4 widths;
     vec4 colors[4];
     vec4 radii[2];
 };
 
-vec4 get_effective_border_widths(Border border) {
-    switch (int(border.style.x)) {
+vec4 get_effective_border_widths(Border border, int style) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE:
             // Calculate the width of a border segment in a style: double
             // border. Round to the nearest CSS pixel.
 
             // The CSS spec doesn't define what width each of the segments
             // in a style: double border should be. It only says that the
             // sum of the segments should be equal to the total border
             // width. We pick to make the segments (almost) equal thirds
@@ -795,31 +812,21 @@ float signed_distance_rect(vec2 pos, vec
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
 vec2 init_transform_fs(vec3 local_pos, out float fragment_alpha) {
     fragment_alpha = 1.0;
     vec2 pos = local_pos.xy / local_pos.z;
 
-    // Because the local rect is placed on whole coordinates, but the interpolation
-    // occurs at pixel centers, we need to offset the signed distance by that amount.
-    // In the simple case of no zoom, and no transform, this is 0.5. However, we
-    // need to scale this by the amount that the local rect is changing by per
-    // fragment, based on the current zoom and transform.
-    vec2 fw = fwidth(pos.xy);
-    vec2 dxdy = 0.5 * fw;
-
-    // Now get the actual signed distance. Inset the local rect by the offset amount
-    // above to get correct distance values. This ensures that we only apply
-    // anti-aliasing when the fragment has partial coverage.
-    float d = signed_distance_rect(pos, vLocalBounds.xy + dxdy, vLocalBounds.zw - dxdy);
+    // Now get the actual signed distance.
+    float d = signed_distance_rect(pos, vLocalBounds.xy, vLocalBounds.zw);
 
     // Find the appropriate distance to apply the AA smoothstep over.
-    float afwidth = 0.5 / length(fw);
+    float afwidth = 0.5 * length(fwidth(pos.xy));
 
     // Only apply AA to fragments outside the signed distance field.
     fragment_alpha = 1.0 - smoothstep(0.0, afwidth, d);
 
     return pos;
 }
 #endif //WR_FEATURE_TRANSFORM
 
@@ -845,30 +852,99 @@ vec4 dither(vec4 color) {
 }
 #else
 vec4 dither(vec4 color) {
     return color;
 }
 #endif //WR_FEATURE_DITHERING
 
 vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) {
-    // Either saturate or modulo the offset depending on repeat mode
-    float x = mix(clamp(offset, 0.0, 1.0), fract(offset), gradient_repeat);
+    // Modulo the offset if the gradient repeats. We don't need to clamp non-repeating
+    // gradients because the gradient data texture is bound with CLAMP_TO_EDGE, and the
+    // first and last color entries are filled with the first and last stop colors
+    float x = mix(offset, fract(offset), gradient_repeat);
 
-    // Scale to the number of gradient color entries (texture width / 2).
-    x = x * 0.5 * gradient_size.x;
+    // Calculate the color entry index to use for this offset:
+    //     offsets < 0 use the first color entry, 0
+    //     offsets from [0, 1) use the color entries in the range of [1, N-1)
+    //     offsets >= 1 use the last color entry, N-1
+    //     so transform the range [0, 1) -> [1, N-1)
+    float gradient_entries = 0.5 * gradient_size.x;
+    x = x * (gradient_entries - 2.0) + 1.0;
 
     // Calculate the texel to index into the gradient color entries:
     //     floor(x) is the gradient color entry index
     //     fract(x) is the linear filtering factor between start and end
     //     so, 2 * floor(x) + 0.5 is the center of the start color
     //     finally, add floor(x) to interpolate to end
     x = 2.0 * floor(x) + 0.5 + fract(x);
 
     // Gradient color entries are encoded with high bits in one row and low bits in the next
     // So use linear filtering to mix (gradient_index + 1) with (gradient_index)
     float y = gradient_index * 2.0 + 0.5 + 1.0 / 256.0;
 
     // Finally sample and apply dithering
     return dither(texture(sGradients, vec2(x, y) / gradient_size));
 }
 
+//
+// Signed distance to an ellipse.
+// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+// Note that this fails for exact circles.
+//
+float sdEllipse( vec2 p, in vec2 ab ) {
+    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
+    float l = ab.y*ab.y - ab.x*ab.x;
+
+    float m = ab.x*p.x/l;
+    float n = ab.y*p.y/l;
+    float m2 = m*m;
+    float n2 = n*n;
+
+    float c = (m2 + n2 - 1.0)/3.0;
+    float c3 = c*c*c;
+
+    float q = c3 + m2*n2*2.0;
+    float d = c3 + m2*n2;
+    float g = m + m*n2;
+
+    float co;
+
+    if( d<0.0 )
+    {
+        float p = acos(q/c3)/3.0;
+        float s = cos(p);
+        float t = sin(p)*sqrt(3.0);
+        float rx = sqrt( -c*(s + t + 2.0) + m2 );
+        float ry = sqrt( -c*(s - t + 2.0) + m2 );
+        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+    }
+    else
+    {
+        float h = 2.0*m*n*sqrt( d );
+        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
+        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
+        float rx = -s - u - c*4.0 + 2.0*m2;
+        float ry = (s - u)*sqrt(3.0);
+        float rm = sqrt( rx*rx + ry*ry );
+        float p = ry/sqrt(rm-rx);
+        co = (p + 2.0*g/rm - m)/2.0;
+    }
+
+    float si = sqrt( 1.0 - co*co );
+
+    vec2 r = vec2( ab.x*co, ab.y*si );
+
+    return length(r - p ) * sign(p.y-r.y);
+}
+
+float distance_to_ellipse(vec2 p, vec2 radii) {
+    // sdEllipse fails on exact circles, so handle equal
+    // radii here. The branch coherency should make this
+    // a performance win for the circle case too.
+    if (radii.x == radii.y) {
+        return length(p) - radii.x;
+    } else {
+        return sdEllipse(p, radii);
+    }
+}
+
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -12,18 +12,20 @@ void main(void) {
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
                          aPosition.xy);
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-    vec2 st0 = src_task.render_target_origin / texture_size;
-    vec2 st1 = (src_task.render_target_origin + src_task.size) / texture_size;
-    vUv = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
-    vUvBounds = vec4(st0 + 0.5 / texture_size, st1 - 0.5 / texture_size);
+    vec2 st0 = src_task.render_target_origin;
+    vec2 st1 = src_task.render_target_origin + src_task.size;
+
+    vec2 uv = src_task.render_target_origin + aPosition.xy * src_task.size;
+    vUv = vec3(uv / texture_size, src_task.render_target_layer_index);
+    vUvBounds = vec4(st0 + 0.5, st1 - 0.5) / texture_size.xyxy;
 
     vOp = pi.sub_index;
     vAmount = float(pi.user_data.y) / 65535.0;
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
 }
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ /dev/null
@@ -1,434 +0,0 @@
-#line 1
-
-/* 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/. */
-
-void discard_pixels_in_rounded_borders(vec2 local_pos) {
-  float distanceFromRef = distance(vRefPoint, local_pos);
-  if (vRadii.x > 0.0 && (distanceFromRef > vRadii.x || distanceFromRef < vRadii.z)) {
-      discard;
-  }
-}
-
-vec4 get_fragment_color(float distanceFromMixLine, float pixelsPerFragment) {
-  // Here we are mixing between the two border colors. We need to convert
-  // distanceFromMixLine it to pixel space to properly anti-alias and then push
-  // it between the limits accepted by `mix`.
-  float colorMix = min(max(distanceFromMixLine / pixelsPerFragment, -0.5), 0.5) + 0.5;
-  return mix(vHorizontalColor, vVerticalColor, colorMix);
-}
-
-float alpha_for_solid_border(float distance_from_ref,
-                             float inner_radius,
-                             float outer_radius,
-                             float pixels_per_fragment) {
-  // We want to start anti-aliasing one pixel in from the border.
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  if (distance_from_ref < outer_radius && distance_from_ref > inner_radius) {
-    return 1.0;
-  }
-
-  float distance_from_border = max(distance_from_ref - outer_radius,
-                                   inner_radius - distance_from_ref);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  // Apply a more gradual fade out to transparent.
-  // distance_from_border -= 0.5;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_ellipse_border(vec2 local_pos,
-                                     vec2 inner_radius,
-                                     vec2 outer_radius,
-                                     float pixels_per_fragment) {
-  vec2 distance_from_ref = local_pos - vRefPoint;
-
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  float inner_ellipse = distance_from_ref.x * distance_from_ref.x / inner_radius.x / inner_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / inner_radius.y / inner_radius.y;
-  float outer_ellipse = distance_from_ref.x * distance_from_ref.x / outer_radius.x / outer_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / outer_radius.y / outer_radius.y;
-  if (inner_ellipse > 1.0 && outer_ellipse < 1.0) {
-      return 1.0;
-  }
-
-  vec2 offset = step(inner_radius.yx, inner_radius.xy) *
-                (sqrt(abs(inner_radius.x * inner_radius.x - inner_radius.y * inner_radius.y)));
-  vec2 focus1 = vRefPoint + offset;
-  vec2 focus2 = vRefPoint - offset;
-
-  float inner_distance_from_border = max(inner_radius.x, inner_radius.y) -
-                                     (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0;
-
-  offset = step(outer_radius.yx, outer_radius.xy) *
-           (sqrt(abs(outer_radius.x * outer_radius.x - outer_radius.y * outer_radius.y)));
-  focus1 = vRefPoint + offset;
-  focus2 = vRefPoint - offset;
-  float outer_distance_from_border = (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0 -
-                                     max(outer_radius.x, outer_radius.y);
-
-  float distance_from_border = max(inner_distance_from_border, outer_distance_from_border);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_border_corner(vec2 local_pos,
-                                    vec2 inner_radius,
-                                    vec2 outer_radius,
-                                    float pixels_per_fragment) {
-  if (inner_radius.x == inner_radius.y && outer_radius.x == outer_radius.y) {
-    float distance_from_ref = distance(vRefPoint, local_pos);
-    return alpha_for_solid_border(distance_from_ref, inner_radius.x, outer_radius.x, pixels_per_fragment);
-  } else {
-    return alpha_for_solid_ellipse_border(local_pos, inner_radius, outer_radius, pixels_per_fragment);
-  }
-}
-
-vec4 draw_dotted_edge(vec2 local_pos, vec4 piece_rect, float pixels_per_fragment) {
-  // We don't use pixels_per_fragment here, since it can change along the edge
-  // of a transformed border edge. We want this calculation to be consistent
-  // across the entire edge so that the positioning of the dots stays the same.
-  float two_pixels = 2.0 * length(fwidth(vLocalPos.xy));
-
-  // Circle diameter is stroke width, minus a couple pixels to account for anti-aliasing.
-  float circle_diameter = max(piece_rect.z - two_pixels, min(piece_rect.z, two_pixels));
-
-  // We want to spread the circles across the edge, but keep one circle diameter at the end
-  // reserved for two half-circles which connect to the corners.
-  float edge_available = piece_rect.w - (circle_diameter * 2.0);
-  float number_of_circles = floor(edge_available / (circle_diameter * 2.0));
-
-  // Here we are initializing the distance from the y coordinate of the center of the circle to
-  // the closest end half-circle.
-  vec2 relative_pos = local_pos - piece_rect.xy;
-  float y_distance = min(relative_pos.y, piece_rect.w - relative_pos.y);
-
-  if (number_of_circles > 0.0) {
-    // Spread the circles throughout the edge, to distribute the extra space evenly. We want
-    // to ensure that we have at last two pixels of space for each circle so that they aren't
-    // touching.
-    float space_for_each_circle = ceil(max(edge_available / number_of_circles, two_pixels));
-
-    float first_half_circle_space = circle_diameter;
-
-    float circle_index = (relative_pos.y - first_half_circle_space) / space_for_each_circle;
-    circle_index = floor(clamp(circle_index, 0.0, number_of_circles - 1.0));
-
-    float circle_y_pos =
-      circle_index * space_for_each_circle + (space_for_each_circle / 2.0) + circle_diameter;
-    y_distance = min(abs(circle_y_pos - relative_pos.y), y_distance);
-  }
-
-  float distance_from_circle_center = length(vec2(relative_pos.x - (piece_rect.z / 2.0), y_distance));
-  float distance_from_circle_edge = distance_from_circle_center - (circle_diameter / 2.0);
-
-  // Don't anti-alias if the circle diameter is small to avoid a blur of color.
-  if (circle_diameter < two_pixels && distance_from_circle_edge > 0.0)
-    return vec4(0.0);
-
-  // Move the distance back into pixels.
-  distance_from_circle_edge /= pixels_per_fragment;
-
-  float alpha = 1.0 - smoothstep(0.0, 1.0, min(1.0, max(0.0, distance_from_circle_edge)));
-  return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_dashed_edge(float position, float border_width, float pixels_per_fragment) {
-  // TODO: Investigate exactly what FF does.
-  float size = border_width * 3.0;
-  float segment = floor(position / size);
-
-  float alpha = alpha_for_solid_border(position,
-                                       segment * size,
-                                       (segment + 1.0) * size,
-                                       pixels_per_fragment);
-
-  if (mod(segment + 2.0, 2.0) == 0.0) {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, 1.0 - alpha);
-  } else {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-  }
-}
-
-vec4 draw_dashed_or_dotted_border(vec2 local_pos, float distance_from_mix_line) {
-  // This is the conversion factor for transformations and device pixel scaling.
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-    {
-      vec4 color = get_fragment_color(distance_from_mix_line, pixels_per_fragment);
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(local_pos,
-                                                 vRadii.zw,
-                                                 vRadii.xy,
-                                                 pixels_per_fragment);
-      }
-      return color;
-    }
-    case PST_BOTTOM:
-    case PST_TOP: {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.x - vPieceRect.x, vPieceRect.w, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.yx, vPieceRect.yxwz, pixels_per_fragment);
-    }
-    case PST_LEFT:
-    case PST_RIGHT:
-    {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.y - vPieceRect.y, vPieceRect.z, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.xy, vPieceRect.xyzw, pixels_per_fragment);
-    }
-  }
-
-  return vec4(0.0);
-}
-
-vec4 draw_double_edge(float pos,
-                      float len,
-                      float distance_from_mix_line,
-                      float pixels_per_fragment) {
-  float total_border_width = len;
-  float one_third_width = total_border_width / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border(pos,
-                                       total_border_width - one_third_width,
-                                       total_border_width,
-                                       pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border(pos, 0.0, one_third_width, pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_vertical(vec2 local_pos,
-                               float distance_from_mix_line,
-                               float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.x - vRefPoint.x);
-  return draw_double_edge(position, abs(vPieceRect.z), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_horizontal(vec2 local_pos,
-                                 float distance_from_mix_line,
-                                 float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.y - vRefPoint.y);
-  return draw_double_edge(position, abs(vPieceRect.w), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_corner_with_radius(vec2 local_pos,
-                                         float distance_from_mix_line,
-                                         float pixels_per_fragment) {
-  float total_border_width = vRadii.x - vRadii.z;
-  float one_third_width = total_border_width / 3.0;
-  float total_border_height = vRadii.y - vRadii.w;
-  float one_third_height = total_border_height / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border_corner(local_pos,
-                                              vec2(vRadii.x - one_third_width,
-                                                   vRadii.y - one_third_height),
-                                              vec2(vRadii.x, vRadii.y),
-                                              pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border_corner(local_pos,
-                                         vec2(vRadii.z, vRadii.w),
-                                         vec2(vRadii.z + one_third_width, vRadii.w + one_third_height),
-                                         pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_corner(vec2 local_pos,
-                             float distance_from_mix_line,
-                             float pixels_per_fragment) {
-  if (vRadii.x > 0.0) {
-      return draw_double_edge_corner_with_radius(local_pos,
-                                                 distance_from_mix_line,
-                                                 pixels_per_fragment);
-  }
-
-  bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distance_from_mix_line < 0.0 :
-                                                     distance_from_mix_line >= 0.0;
-  if (is_vertical) {
-    return draw_double_edge_vertical(local_pos, distance_from_mix_line, pixels_per_fragment);
-  } else {
-    return draw_double_edge_horizontal(local_pos, distance_from_mix_line, pixels_per_fragment);
-  }
-}
-
-vec4 draw_double_border(float distance_from_mix_line, vec2 local_pos) {
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-      return draw_double_edge_corner(local_pos, distance_from_mix_line, pixels_per_fragment);
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_double_edge_horizontal(local_pos,
-                                         distance_from_mix_line,
-                                         pixels_per_fragment);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_double_edge_vertical(local_pos,
-                                       distance_from_mix_line,
-                                       pixels_per_fragment);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_solid_border(float distanceFromMixLine, vec2 localPos) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(localPos, vRadii.zw, vRadii.xy, pixelsPerFragment);
-      }
-
-      return color;
-    }
-    default:
-      discard_pixels_in_rounded_borders(localPos);
-      return vHorizontalColor;
-  }
-}
-
-vec4 draw_mixed_edge(float distance, float border_len, vec4 color, vec2 brightness_mod) {
-  float modulator = distance / border_len > 0.5 ? brightness_mod.x : brightness_mod.y;
-  return vec4(color.xyz * modulator, color.a);
-}
-
-vec4 draw_mixed_border(float distanceFromMixLine, float distanceFromMiddle, vec2 localPos, vec2 brightness_mod) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        float distance = distance(vRefPoint, localPos) - vRadii.z;
-        float length = vRadii.x - vRadii.z;
-        if (distanceFromMiddle < 0.0) {
-          distance = length - distance;
-        }
-
-        return 0.0 <= distance && distance <= length ?
-          draw_mixed_edge(distance, length, color, brightness_mod) :
-          vec4(0.0, 0.0, 0.0, 0.0);
-      }
-
-      bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distanceFromMixLine < 0.0 :
-                                                         distanceFromMixLine >= 0.0;
-      float distance = is_vertical ? abs(localPos.x - vRefPoint.x) : abs(localPos.y - vRefPoint.y);
-      float length = is_vertical ? abs(vPieceRect.z) : abs(vPieceRect.w);
-      if (distanceFromMiddle > 0.0) {
-          distance = length - distance;
-      }
-
-      return 0.0 <= distance && distance <= length ?
-        draw_mixed_edge(distance, length, color, brightness_mod) :
-        vec4(0.0, 0.0, 0.0, 0.0);
-    }
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_mixed_edge(localPos.y - vPieceRect.y, vPieceRect.w, vVerticalColor, brightness_mod);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_mixed_edge(localPos.x - vPieceRect.x, vPieceRect.z, vHorizontalColor, brightness_mod);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_complete_border(vec2 local_pos, float distance_from_mix_line, float distance_from_middle) {
-  vec2 brightness_mod = vec2(0.7, 1.3);
-
-  // Note: we can't pass-through in the following cases,
-  // because Angle doesn't support it and fails to compile the shaders.
-  switch (vBorderStyle) {
-    case BORDER_STYLE_DASHED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOTTED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOUBLE:
-      return draw_double_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_OUTSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_INSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_SOLID:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_NONE:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_GROOVE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.yx);
-    case BORDER_STYLE_RIDGE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.xy);
-    case BORDER_STYLE_HIDDEN:
-    default:
-      break;
-  }
-
-  // Note: Workaround for Angle on Windows,
-  // because non-empty case statements must have break or return.
-  discard;
-  return vec4(0.0);
-}
-
-// TODO: Investigate performance of this shader and see
-//       if it's worthwhile splitting it / removing branches etc.
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    float alpha = 1.0;
-    vec2 local_pos = vLocalPos;
-#endif
-
-#ifdef WR_FEATURE_TRANSFORM
-    // TODO(gw): Support other border styles for transformed elements.
-    float distance_from_mix_line = (local_pos.x - vPieceRect.x) * vPieceRect.w -
-                                   (local_pos.y - vPieceRect.y) * vPieceRect.z;
-    distance_from_mix_line /= vPieceRectHypotenuseLength;
-    float distance_from_middle = (local_pos.x - vBorderRect.x) +
-                                 (local_pos.y - vBorderRect.y) -
-                                 0.5 * (vBorderRect.z + vBorderRect.w);
-#else
-    float distance_from_mix_line = vDistanceFromMixLine;
-    float distance_from_middle = vDistanceFromMiddle;
-#endif
-
-    oFragColor = draw_complete_border(local_pos, distance_from_mix_line, distance_from_middle);
-    oFragColor.a *= min(alpha, do_clip());
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.glsl
+++ /dev/null
@@ -1,33 +0,0 @@
-#line 1
-
-/* 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/. */
-
-// These are not changing.
-flat varying vec4 vVerticalColor;      // The vertical color, e.g. top/bottom
-flat varying vec4 vHorizontalColor;    // The horizontal color e.g. left/right
-flat varying vec4 vRadii;              // The border radius from CSS border-radius
-flat varying vec4 vBorderRect;         // The rect of the border in local space.
-
-// for corners, this is the beginning of the corner.
-// For the lines, this is the top left of the line.
-flat varying vec2 vRefPoint;
-flat varying int vBorderStyle;
-flat varying int vBorderPart; // Which part of the border we're drawing.
-
-flat varying vec4 vPieceRect;
-
-// These are in device space
-#ifdef WR_FEATURE_TRANSFORM
-varying vec3 vLocalPos;     // The clamped position in local space.
-flat varying float vPieceRectHypotenuseLength;
-#else
-varying vec2 vLocalPos;     // The clamped position in local space.
-
-// These two are interpolated
-varying float vDistanceFromMixLine;  // This is the distance from the line where two colors
-                                     // meet in border corners.
-varying float vDistanceFromMiddle;   // This is the distance from the line between the top
-                                     // left corner and the bottom right.
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ /dev/null
@@ -1,190 +0,0 @@
-#line 1
-/* 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/. */
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.prim_index);
-    int sub_part = prim.sub_index;
-    vBorderRect = vec4(prim.local_rect.p0, prim.local_rect.size);
-
-    vec2 tl_outer = vBorderRect.xy;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(vBorderRect.x,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    RectWithSize segment_rect;
-    switch (sub_part) {
-        case PST_TOP_LEFT:
-            segment_rect.p0 = tl_outer;
-            segment_rect.size = tl_inner - tl_outer;
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(border.radii[0].xy,
-                          border.radii[0].xy - border.widths.xy);
-            break;
-        case PST_TOP_RIGHT:
-            segment_rect.p0 = vec2(tr_inner.x, tr_outer.y);
-            segment_rect.size = vec2(tr_outer.x - tr_inner.x, tr_inner.y - tr_outer.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[0].zw,
-                          border.radii[0].zw - border.widths.zy);
-            break;
-        case PST_BOTTOM_RIGHT:
-            segment_rect.p0 = br_inner;
-            segment_rect.size = br_outer - br_inner;
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[1].xy,
-                          border.radii[1].xy - border.widths.zw);
-            break;
-        case PST_BOTTOM_LEFT:
-            segment_rect.p0 = vec2(bl_outer.x, bl_inner.y);
-            segment_rect.size = vec2(bl_inner.x - bl_outer.x, bl_outer.y - bl_inner.y);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(border.radii[1].zw,
-                          border.radii[1].zw - border.widths.xw);
-            break;
-        case PST_LEFT:
-            segment_rect.p0 = vec2(tl_outer.x, tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, bl_inner.y - tl_inner.y);
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_RIGHT:
-            segment_rect.p0 = vec2(tr_outer.x - border.widths.z, tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, br_inner.y - tr_inner.y);
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_RIGHT];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_BOTTOM:
-            segment_rect.p0 = vec2(bl_inner.x, bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(br_inner.x - bl_inner.x, border.widths.w);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_BOTTOM];
-            vRadii = vec4(0.0);
-            break;
-        case PST_TOP:
-            segment_rect.p0 = vec2(tl_inner.x, tl_outer.y);
-            segment_rect.size = vec2(tr_inner.x - tl_inner.x, border.widths.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(0.0);
-            break;
-    }
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect.p0);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect.p0);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    float x0, y0, x1, y1;
-    switch (sub_part) {
-        // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-        case PST_TOP_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            // These are width / heights
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-
-            // The radius here is the border-radius. This is 0, so vRefPoint will
-            // just be the top left (x,y) corner.
-            vRefPoint = vec2(x0, y0) + vRadii.xy;
-            break;
-        case PST_TOP_RIGHT:
-            x0 = segment_rect.p0.x + segment_rect.size.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x0, y0) + vec2(-vRadii.x, vRadii.y);
-            break;
-        case PST_BOTTOM_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y + segment_rect.size.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y;
-            vRefPoint = vec2(x0, y0) + vec2(vRadii.x, -vRadii.y);
-            break;
-        case PST_BOTTOM_RIGHT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x1, y1) + vec2(-vRadii.x, -vRadii.y);
-            break;
-        case PST_TOP:
-        case PST_LEFT:
-        case PST_BOTTOM:
-        case PST_RIGHT:
-            vRefPoint = segment_rect.p0.xy;
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            break;
-    }
-
-    // y1 - y0 is the height of the corner / line
-    // x1 - x0 is the width of the corner / line.
-    float width = x1 - x0;
-    float height = y1 - y0;
-
-    vBorderPart = sub_part;
-    vPieceRect = vec4(x0, y0, width, height);
-
-    // The fragment shader needs to calculate the distance from the bisecting line
-    // to properly mix border colors. For transformed borders, we calculate this distance
-    // in the fragment shader itself. For non-transformed borders, we can use the
-    // interpolator.
-#ifdef WR_FEATURE_TRANSFORM
-    vPieceRectHypotenuseLength = sqrt(pow(width, 2.0) + pow(height, 2.0));
-#else
-    vDistanceFromMixLine = (vi.local_pos.x - x0) * height -
-                           (vi.local_pos.y - y0) * width;
-    vDistanceFromMiddle = (vi.local_pos.x - vBorderRect.x) +
-                          (vi.local_pos.y - vBorderRect.y) -
-                          0.5 * (vBorderRect.z + vBorderRect.w);
-#endif
-}
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -1,75 +1,13 @@
 #line 1
 /* 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/. */
 
-//
-// Signed distance to an ellipse.
-// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
-// Note that this fails for exact circles.
-//
-float sdEllipse( vec2 p, in vec2 ab ) {
-    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
-    float l = ab.y*ab.y - ab.x*ab.x;
-
-    float m = ab.x*p.x/l;
-    float n = ab.y*p.y/l;
-    float m2 = m*m;
-    float n2 = n*n;
-
-    float c = (m2 + n2 - 1.0)/3.0;
-    float c3 = c*c*c;
-
-    float q = c3 + m2*n2*2.0;
-    float d = c3 + m2*n2;
-    float g = m + m*n2;
-
-    float co;
-
-    if( d<0.0 )
-    {
-        float p = acos(q/c3)/3.0;
-        float s = cos(p);
-        float t = sin(p)*sqrt(3.0);
-        float rx = sqrt( -c*(s + t + 2.0) + m2 );
-        float ry = sqrt( -c*(s - t + 2.0) + m2 );
-        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
-    }
-    else
-    {
-        float h = 2.0*m*n*sqrt( d );
-        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
-        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
-        float rx = -s - u - c*4.0 + 2.0*m2;
-        float ry = (s - u)*sqrt(3.0);
-        float rm = sqrt( rx*rx + ry*ry );
-        float p = ry/sqrt(rm-rx);
-        co = (p + 2.0*g/rm - m)/2.0;
-    }
-
-    float si = sqrt( 1.0 - co*co );
-
-    vec2 r = vec2( ab.x*co, ab.y*si );
-
-    return length(r - p ) * sign(p.y-r.y);
-}
-
-float distance_to_ellipse(vec2 p, vec2 radii) {
-    // sdEllipse fails on exact circles, so handle equal
-    // radii here. The branch coherency should make this
-    // a performance win for the circle case too.
-    if (radii.x == radii.y) {
-        return length(p) - radii.x;
-    } else {
-        return sdEllipse(p, radii);
-    }
-}
-
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #else
     vec2 local_pos = vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -1,29 +1,34 @@
 #line 1
 /* 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/. */
 
+// Matches BorderCornerSide enum in border.rs
+#define SIDE_BOTH       0
+#define SIDE_FIRST      1
+#define SIDE_SECOND     2
+
 vec2 get_radii(vec2 radius, vec2 invalid) {
     if (all(greaterThan(radius, vec2(0.0)))) {
         return radius;
     }
 
     return invalid;
 }
 
-void set_radii(float style,
+void set_radii(int style,
                vec2 radii,
                vec2 widths,
                vec2 adjusted_widths) {
     vRadii0.xy = get_radii(radii, 2.0 * widths);
     vRadii0.zw = get_radii(radii - widths, -widths);
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_RIDGE:
         case BORDER_STYLE_GROOVE:
             vRadii1.xy = radii - adjusted_widths;
             vRadii1.zw = -widths;
             break;
         case BORDER_STYLE_DOUBLE:
             vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
             vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
@@ -37,17 +42,17 @@ void set_radii(float style,
 
 void set_edge_line(vec2 border_width,
                    vec2 outer_corner,
                    vec2 gradient_sign) {
     vec2 gradient = border_width * gradient_sign;
     vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
 }
 
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta) {
+void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
     vec4 modulate;
 
     switch (style) {
         case BORDER_STYLE_GROOVE:
             modulate = vec4(1.0 - 0.3 * delta.x,
                             1.0 + 0.3 * delta.x,
                             1.0 - 0.3 * delta.y,
                             1.0 + 0.3 * delta.y);
@@ -59,150 +64,201 @@ void write_color(vec4 color0, vec4 color
                             1.0 + 0.3 * delta.y,
                             1.0 - 0.3 * delta.y);
             break;
         default:
             modulate = vec4(1.0);
             break;
     }
 
+    // Optionally mask out one side of the border corner,
+    // depending on the instance kind.
+    switch (instance_kind) {
+        case SIDE_FIRST:
+            color0.a = 0.0;
+            break;
+        case SIDE_SECOND:
+            color1.a = 0.0;
+            break;
+    }
+
     vColor00 = vec4(color0.rgb * modulate.x, color0.a);
     vColor01 = vec4(color0.rgb * modulate.y, color0.a);
     vColor10 = vec4(color1.rgb * modulate.z, color1.a);
     vColor11 = vec4(color1.rgb * modulate.w, color1.a);
 }
 
+int select_style(int color_select, vec2 fstyle) {
+    ivec2 style = ivec2(fstyle);
+
+    switch (color_select) {
+        case SIDE_BOTH:
+            // TODO(gw): A temporary hack! While we don't support
+            //           border corners that have dots or dashes
+            //           with another style, pretend they are solid
+            //           border corners.
+            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
+                            style.y == BORDER_STYLE_DOTTED;
+            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
+                              style.y == BORDER_STYLE_DASHED;
+            if (style.x != style.y && (has_dots || has_dashes))
+                return BORDER_STYLE_SOLID;
+            return style.x;
+        case SIDE_FIRST:
+            return style.x;
+        case SIDE_SECOND:
+            return style.y;
+    }
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
 
-    vec4 adjusted_widths = get_effective_border_widths(border);
-    vec4 inv_adjusted_widths = border.widths - adjusted_widths;
     vec2 p0, p1;
 
     // TODO(gw): We'll need to pass through multiple styles
     //           once we support style transitions per corner.
     int style;
     vec4 edge_distances;
     vec4 color0, color1;
     vec2 color_delta;
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
     switch (sub_part) {
         case 0: {
             p0 = corners.tl_outer;
             p1 = corners.tl_inner;
             color0 = border.colors[0];
             color1 = border.colors[1];
             vClipCenter = corners.tl_outer + border.radii[0].xy;
             vClipSign = vec2(1.0);
-            set_radii(border.style.x,
+            style = select_style(prim.user_data.x, border.style.yx);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].xy,
                       border.widths.xy,
                       adjusted_widths.xy);
             set_edge_line(border.widths.xy,
                           corners.tl_outer,
                           vec2(1.0, 1.0));
-            style = int(border.style.x);
             edge_distances = vec4(p0 + adjusted_widths.xy,
                                   p0 + inv_adjusted_widths.xy);
             color_delta = vec2(1.0);
             break;
         }
         case 1: {
             p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
             p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
             color0 = border.colors[1];
             color1 = border.colors[2];
             vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
             vClipSign = vec2(-1.0, 1.0);
-            set_radii(border.style.y,
+            style = select_style(prim.user_data.x, border.style.zy);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].zw,
                       border.widths.zy,
                       adjusted_widths.zy);
             set_edge_line(border.widths.zy,
                           corners.tr_outer,
                           vec2(-1.0, 1.0));
-            style = int(border.style.y);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p0.y + adjusted_widths.y,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p0.y + inv_adjusted_widths.y);
             color_delta = vec2(1.0, -1.0);
             break;
         }
         case 2: {
             p0 = corners.br_inner;
             p1 = corners.br_outer;
             color0 = border.colors[2];
             color1 = border.colors[3];
             vClipCenter = corners.br_outer - border.radii[1].xy;
             vClipSign = vec2(-1.0, -1.0);
-            set_radii(border.style.z,
+            style = select_style(prim.user_data.x, border.style.wz);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].xy,
                       border.widths.zw,
                       adjusted_widths.zw);
             set_edge_line(border.widths.zw,
                           corners.br_outer,
                           vec2(-1.0, -1.0));
-            style = int(border.style.z);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p1.y - adjusted_widths.w,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0);
             break;
         }
         case 3: {
             p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
             p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
             color0 = border.colors[3];
             color1 = border.colors[0];
             vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
             vClipSign = vec2(1.0, -1.0);
-            set_radii(border.style.w,
+            style = select_style(prim.user_data.x, border.style.xw);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].zw,
                       border.widths.xw,
                       adjusted_widths.xw);
             set_edge_line(border.widths.xw,
                           corners.bl_outer,
                           vec2(1.0, -1.0));
-            style = int(border.style.w);
             edge_distances = vec4(p0.x + adjusted_widths.x,
                                   p1.y - adjusted_widths.w,
                                   p0.x + inv_adjusted_widths.x,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0, 1.0);
             break;
         }
     }
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE: {
             vEdgeDistance = edge_distances;
             vAlphaSelect = 0.0;
             vSDFSelect = 0.0;
             break;
         }
         case BORDER_STYLE_GROOVE:
         case BORDER_STYLE_RIDGE:
             vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 1.0;
             break;
+        case BORDER_STYLE_DOTTED:
+            // Disable normal clip radii for dotted corners, since
+            // all the clipping is handled by the clip mask.
+            vClipSign = vec2(0.0);
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
         default: {
             vEdgeDistance = vec4(0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 0.0;
             break;
         }
     }
 
-    write_color(color0, color1, style, color_delta);
+    write_color(color0, color1, style, color_delta, prim.user_data.x);
 
     RectWithSize segment_rect;
     segment_rect.p0 = p0;
     segment_rect.size = p1 - p0;
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -12,16 +12,17 @@ void main(void) {
 #else
     vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // Find the appropriate distance to apply the step over.
     vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
 
     // Applies the math necessary to draw a style: double
     // border. In the case of a solid border, the vertex
     // shader sets interpolator values that make this have
     // no effect.
 
     // Select the x/y coord, depending on which axis this edge is.
     vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
@@ -37,13 +38,28 @@ void main(void) {
     // No AA here, since we know we're on a straight edge
     // and the width is rounded to a whole CSS pixel.
     alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
 
     // Mix color based on first distance.
     // TODO(gw): Support AA for groove/ridge border edge with transforms.
     vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
 
-    // Apply dashing parameters.
-    alpha = min(alpha, step(mod(pos.y - vDashParams.x, vDashParams.y), vDashParams.z));
+    // Apply dashing / dotting parameters.
+
+    // Get the main-axis position relative to closest dot or dash.
+    float x = mod(pos.y - vClipParams.x, vClipParams.y);
+
+    // Calculate dash alpha (on/off) based on dash length
+    float dash_alpha = step(x, vClipParams.z);
+
+    // Get the dot alpha
+    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
+    float dot_distance = length(dot_relative_pos) - vClipParams.z;
+    float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
+                                        0.5 * afwidth,
+                                        dot_distance);
+
+    // Select between dot/dash alpha based on clip mode.
+    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
 
     oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -2,15 +2,16 @@
  * 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/. */
 
 flat varying vec4 vColor0;
 flat varying vec4 vColor1;
 flat varying vec2 vEdgeDistance;
 flat varying float vAxisSelect;
 flat varying float vAlphaSelect;
-flat varying vec3 vDashParams;
+flat varying vec4 vClipParams;
+flat varying float vClipSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -51,84 +51,134 @@ void write_color(vec4 color, float style
             modulate = vec2(1.0);
             break;
     }
 
     vColor0 = vec4(color.rgb * modulate.x, color.a);
     vColor1 = vec4(color.rgb * modulate.y, color.a);
 }
 
-void write_dash_params(float style,
+void write_clip_params(float style,
                        float border_width,
                        float edge_length,
-                       float edge_offset) {
+                       float edge_offset,
+                       float center_line) {
     // x = offset
     // y = dash on + off length
     // z = dash length
+    // w = center line of edge cross-axis (for dots only)
     switch (int(style)) {
         case BORDER_STYLE_DASHED: {
             float desired_dash_length = border_width * 3.0;
             // Consider half total length since there is an equal on/off for each dash.
             float dash_count = ceil(0.5 * edge_length / desired_dash_length);
             float dash_length = 0.5 * edge_length / dash_count;
-            vDashParams = vec3(edge_offset - 0.5 * dash_length,
+            vClipParams = vec4(edge_offset - 0.5 * dash_length,
                                2.0 * dash_length,
-                               dash_length);
+                               dash_length,
+                               0.0);
+            vClipSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_DOTTED: {
+            float diameter = border_width;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * edge_length / diameter);
+            float empty_space = edge_length - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            vClipParams = vec4(edge_offset - radius,
+                               distance_between_centers,
+                               radius,
+                               center_line);
+            vClipSelect = 1.0;
             break;
         }
         default:
-            vDashParams = vec3(1.0);
+            vClipParams = vec4(1.0);
+            vClipSelect = 0.0;
             break;
     }
 }
 
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 adjusted_widths = get_effective_border_widths(border);
     vec4 color = border.colors[sub_part];
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
+    float style;
+    bool color_flip;
+
     RectWithSize segment_rect;
     switch (sub_part) {
-        case 0:
+        case 0: {
             segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
             segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
             write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            write_alpha_select(border.style.x);
-            write_color(color, border.style.x, false);
-            write_dash_params(border.style.x, border.widths.x, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.x;
+            color_flip = false;
+            write_clip_params(border.style.x,
+                              border.widths.x,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 1:
+        }
+        case 1: {
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
             write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            write_alpha_select(border.style.y);
-            write_color(color, border.style.y, false);
-            write_dash_params(border.style.y, border.widths.y, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.y;
+            color_flip = false;
+            write_clip_params(border.style.y,
+                              border.widths.y,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
-        case 2:
+        }
+        case 2: {
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
             write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            write_alpha_select(border.style.z);
-            write_color(color, border.style.z, true);
-            write_dash_params(border.style.z, border.widths.z, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.z;
+            color_flip = true;
+            write_clip_params(border.style.z,
+                              border.widths.z,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 3:
+        }
+        case 3: {
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
             write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            write_alpha_select(border.style.w);
-            write_color(color, border.style.w, true);
-            write_dash_params(border.style.w, border.widths.w, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.w;
+            color_flip = true;
+            write_clip_params(border.style.w,
+                              border.widths.w,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
+        }
     }
 
+    write_alpha_select(style);
+    write_color(color, style, color_flip);
+
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
 #else
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ b/gfx/webrender/res/ps_composite.vs.glsl
@@ -1,17 +1,17 @@
 #line 1
 /* 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/. */
 
 void main(void) {
     PrimitiveInstance pi = fetch_prim_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(pi.render_task_index);
-    AlphaBatchTask backdrop_task = fetch_alpha_batch_task(pi.user_data.x);
+    ReadbackTask backdrop_task = fetch_readback_task(pi.user_data.x);
     AlphaBatchTask src_task = fetch_alpha_batch_task(pi.user_data.y);
 
     vec2 dest_origin = dest_task.render_target_origin -
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
--- a/gfx/webrender/res/ps_split_composite.fs.glsl
+++ b/gfx/webrender/res/ps_split_composite.fs.glsl
@@ -1,9 +1,15 @@
 #line 1
 /* 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/. */
 
 void main(void) {
-    vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-    oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
+                                 vec4(vUv.xy, vUvTaskBounds.zw));
+    if (all(inside)) {
+        vec2 uv = clamp(vUv.xy, vUvSampleBounds.xy, vUvSampleBounds.zw);
+        oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    } else {
+        oFragColor = vec4(0.0);
+    }
 }
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -1,7 +1,8 @@
 #line 1
 /* 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/. */
 
 varying vec3 vUv;
-flat varying vec4 vUvBounds;
+flat varying vec4 vUvTaskBounds;
+flat varying vec4 vUvSampleBounds;
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -11,20 +11,22 @@ struct SplitGeometry {
 
 SplitGeometry fetch_split_geometry(int index) {
     ivec2 uv = get_fetch_uv(index, VECS_PER_SPLIT_GEOM);
 
     vec4 data0 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(0, 0));
     vec4 data1 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(1, 0));
     vec4 data2 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(2, 0));
 
-    return SplitGeometry(vec3[4](
+    SplitGeometry geo;
+    geo.points = vec3[4](
         data0.xyz, vec3(data0.w, data1.xy),
         vec3(data1.zw, data2.x), data2.yzw
-    ));
+    );
+    return geo;
 }
 
 vec3 bilerp(vec3 a, vec3 b, vec3 c, vec3 d, float s, float t) {
     vec3 x = mix(a, b, t);
     vec3 y = mix(c, d, t);
     return mix(x, y, s);
 }
 
@@ -35,14 +37,15 @@ void main(void) {
 
     vec3 world_pos = bilerp(geometry.points[0], geometry.points[1],
                             geometry.points[3], geometry.points[2],
                             aPosition.y, aPosition.x);
     vec4 final_pos = vec4(world_pos.xy * uDevicePixelRatio, pi.z, 1.0);
 
     gl_Position = uTransform * final_pos;
 
-    vec2 uv_origin = src_task.render_target_origin - src_task.screen_space_origin;
-    vec2 uv_pos = uv_origin + world_pos.xy;
+    vec2 uv_origin = src_task.render_target_origin;
+    vec2 uv_pos = uv_origin + world_pos.xy - src_task.screen_space_origin;
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vUv = vec3(uv_pos / texture_size, src_task.render_target_layer_index);
-    vUvBounds = vec4((uv_origin + 0.5) / texture_size, (uv_origin + src_task.size - 0.5) / texture_size);
+    vUvTaskBounds = vec4(uv_origin, uv_origin + src_task.size) / texture_size.xyxy;
+    vUvSampleBounds = vec4(uv_origin + 0.5, uv_origin + src_task.size - 0.5) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -10,32 +10,36 @@
 // The constants added to the Y, U and V components are applied in the fragment shader.
 #if defined(WR_FEATURE_YUV_REC601)
 // From Rec601:
 // [R]   [1.1643835616438356,  0.0,                 1.5960267857142858   ]   [Y -  16]
 // [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708   ] x [U - 128]
 // [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [V - 128]
 //
 // For the range [0,1] instead of [0,255].
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.59603,
-    1.16438, -0.39176, -0.81297,
-    1.16438,  2.01723,  0.0
+    1.16438,  1.16438, 1.16438,
+    0.0,     -0.39176, 2.01723,
+    1.59603, -0.81297, 0.0
 );
 #elif defined(WR_FEATURE_YUV_REC709)
 // From Rec709:
 // [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]   [Y -  16]
 // [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444 ] x [U - 128]
 // [B]   [1.1643835616438356,  2.1124017857142854,     0.0               ]   [V - 128]
 //
 // For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.79274,
-    1.16438, -0.21325, -0.53291,
-    1.16438,  2.11240,  0.0
+    1.16438,  1.16438,  1.16438,
+    0.0    , -0.21325,  2.11240,
+    1.79274, -0.53291,  0.0
 );
 #endif
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
@@ -49,26 +53,33 @@ void main(void) {
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
     // in order to avoid sampling outside of the texture area.
     vec2 st_y = vTextureOffsetY + clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeY,
         vHalfTexelY, vTextureSizeY - vHalfTexelY);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vec2 uv_offset = clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeUv,
         vHalfTexelUv, vTextureSizeUv - vHalfTexelUv);
     // NV12 only uses 2 textures. The sColor0 is for y and sColor1 is for uv.
     // The texture coordinates of u and v are the same. So, we could skip the
     // st_v if the format is NV12.
     vec2 st_u = vTextureOffsetU + uv_offset;
+#endif
 
     vec3 yuv_value;
-#ifdef WR_FEATURE_NV12
+#ifdef WR_FEATURE_INTERLEAVED_Y_CB_CR
+    // "The Y, Cb and Cr color channels within the 422 data are mapped into
+    // the existing green, blue and red color channels."
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    yuv_value = TEX_SAMPLE(sColor0, st_y).gbr;
+#elif defined(WR_FEATURE_NV12)
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.yz = TEX_SAMPLE(sColor1, st_u).rg;
 #else
     // The yuv_planar format should have this third texture coordinate.
     vec2 st_v = vTextureOffsetV + uv_offset;
 
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.y = TEX_SAMPLE(sColor1, st_u).r;
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -21,34 +21,37 @@ void main(void) {
                                  prim.task,
                                  prim.local_rect.p0);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     ResourceRect y_rect = fetch_resource_rect(prim.user_data.x);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR  // only 1 channel
     ResourceRect u_rect = fetch_resource_rect(prim.user_data.x + 1);
-#ifndef WR_FEATURE_NV12
+#ifndef WR_FEATURE_NV12 // 2 channel
     ResourceRect v_rect = fetch_resource_rect(prim.user_data.x + 2);
 #endif
+#endif
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 y_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 y_texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
 #endif
     vec2 y_st0 = y_rect.uv_rect.xy / y_texture_size_normalization_factor;
     vec2 y_st1 = y_rect.uv_rect.zw / y_texture_size_normalization_factor;
 
     vTextureSizeY = y_st1 - y_st0;
     vTextureOffsetY = y_st0;
 
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     // This assumes the U and V surfaces have the same size.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 uv_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 uv_texture_size_normalization_factor = vec2(textureSize(sColor1, 0));
 #endif
     vec2 u_st0 = u_rect.uv_rect.xy / uv_texture_size_normalization_factor;
     vec2 u_st1 = u_rect.uv_rect.zw / uv_texture_size_normalization_factor;
@@ -57,15 +60,18 @@ void main(void) {
     vec2 v_st0 = v_rect.uv_rect.xy / uv_texture_size_normalization_factor;
 #endif
 
     vTextureSizeUv = u_st1 - u_st0;
     vTextureOffsetU = u_st0;
 #ifndef WR_FEATURE_NV12
     vTextureOffsetV = v_st0;
 #endif
+#endif
 
     YuvImage image = fetch_yuv_image(prim.prim_index);
     vStretchSize = image.size;
 
     vHalfTexelY = vec2(0.5) / y_texture_size_normalization_factor;
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vHalfTexelUv = vec2(0.5) / uv_texture_size_normalization_factor;
+#endif
 }
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -2,42 +2,104 @@
  * 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 ellipse::Ellipse;
 use frame_builder::FrameBuilder;
 use mask_cache::{ClipSource};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, GpuBlock32, PrimitiveContainer};
 use tiling::PrimitiveFlags;
-use util::pack_as_float;
+use util::{lerp, pack_as_float};
 use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ClipRegion};
 use webrender_traits::{ColorF, LayerPoint, LayerRect, LayerSize, NormalBorder};
 
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum BorderCornerInstance {
+    Single,     // Single instance needed - corner styles are same or similar.
+    Double,     // Different corner styles. Draw two instances, one per style.
+}
+
+#[repr(C)]
+pub enum BorderCornerSide {
+    Both,
+    First,
+    Second,
+}
+
+#[repr(C)]
 enum BorderCorner {
     TopLeft,
     TopRight,
     BottomLeft,
     BottomRight,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
-    Clip,
-    Mask(BorderCornerClipData, LayerSize, LayerSize),
-    Unhandled,
+    Clip(BorderCornerInstance),
+    Mask(BorderCornerClipData, LayerSize, LayerSize, BorderCornerClipKind),
+}
+
+impl BorderCornerKind {
+    fn new_mask(kind: BorderCornerClipKind,
+                width0: f32,
+                width1: f32,
+                corner: BorderCorner,
+                radius: LayerSize,
+                border_rect: LayerRect) -> BorderCornerKind {
+        let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
+        let (origin, clip_center) = match corner {
+            BorderCorner::TopLeft => {
+                let origin = border_rect.origin;
+                let clip_center = origin + size;
+                (origin, clip_center)
+            }
+            BorderCorner::TopRight => {
+                let origin = LayerPoint::new(border_rect.origin.x +
+                                             border_rect.size.width -
+                                             size.width,
+                                             border_rect.origin.y);
+                let clip_center = origin + LayerSize::new(0.0, size.height);
+                (origin, clip_center)
+            }
+            BorderCorner::BottomRight => {
+                let origin = border_rect.origin + (border_rect.size - size);
+                let clip_center = origin;
+                (origin, clip_center)
+            }
+            BorderCorner::BottomLeft => {
+                let origin = LayerPoint::new(border_rect.origin.x,
+                                             border_rect.origin.y +
+                                             border_rect.size.height -
+                                             size.height);
+                let clip_center = origin + LayerSize::new(size.width, 0.0);
+                (origin, clip_center)
+            }
+        };
+        let clip_data = BorderCornerClipData {
+            corner_rect: LayerRect::new(origin, size),
+            clip_center: clip_center,
+            corner: pack_as_float(corner as u32),
+            kind: pack_as_float(kind as u32),
+        };
+        BorderCornerKind::Mask(clip_data,
+                               radius,
+                               LayerSize::new(width0, width1),
+                               kind)
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
-    Unhandled,
 }
 
 trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
@@ -75,77 +137,66 @@ impl NormalBorderHelpers for NormalBorde
             (BorderStyle::Hidden, _) | (_, BorderStyle::Hidden) => BorderCornerKind::None,
 
             // If both borders are solid, we can draw them with a simple rectangle if
             // both the colors match and there is no radius.
             (BorderStyle::Solid, BorderStyle::Solid) => {
                 if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
                     BorderCornerKind::Solid
                 } else {
-                    BorderCornerKind::Clip
+                    BorderCornerKind::Clip(BorderCornerInstance::Single)
                 }
             }
 
             // Inset / outset borders just modtify the color of edges, so can be
             // drawn with the normal border corner shader.
             (BorderStyle::Outset, BorderStyle::Outset) |
             (BorderStyle::Inset, BorderStyle::Inset) |
             (BorderStyle::Double, BorderStyle::Double) |
             (BorderStyle::Groove, BorderStyle::Groove) |
-            (BorderStyle::Ridge, BorderStyle::Ridge) => BorderCornerKind::Clip,
-
-            // Dashed border corners get drawn into a clip mask.
-            (BorderStyle::Dashed, BorderStyle::Dashed) => {
-                let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
-                let (origin, clip_center, sign_modifier) = match corner {
-                    BorderCorner::TopLeft => {
-                        let origin = border_rect.origin;
-                        let clip_center = origin + size;
-                        (origin, clip_center, LayerPoint::new(-1.0, -1.0))
-                    }
-                    BorderCorner::TopRight => {
-                        let origin = LayerPoint::new(border_rect.origin.x +
-                                                     border_rect.size.width -
-                                                     size.width,
-                                                     border_rect.origin.y);
-                        let clip_center = origin + LayerSize::new(0.0, size.height);
-                        (origin, clip_center, LayerPoint::new(1.0, -1.0))
-                    }
-                    BorderCorner::BottomRight => {
-                        let origin = border_rect.origin + (border_rect.size - size);
-                        let clip_center = origin;
-                        (origin, clip_center, LayerPoint::new(1.0, 1.0))
-                    }
-                    BorderCorner::BottomLeft => {
-                        let origin = LayerPoint::new(border_rect.origin.x,
-                                                     border_rect.origin.y +
-                                                     border_rect.size.height -
-                                                     size.height);
-                        let clip_center = origin + LayerSize::new(size.width, 0.0);
-                        (origin, clip_center, LayerPoint::new(-1.0, 1.0))
-                    }
-                };
-                let clip_data = BorderCornerClipData {
-                    corner_rect: LayerRect::new(origin, size),
-                    clip_center: clip_center,
-                    sign_modifier: sign_modifier,
-                };
-                BorderCornerKind::Mask(clip_data, *radius, LayerSize::new(width0, width1))
+            (BorderStyle::Ridge, BorderStyle::Ridge) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
             }
 
-            // Assume complex for these cases.
-            // TODO(gw): There are some cases in here that can be handled with a fast path.
-            // For example, with inset/outset borders, two of the four corners are solid.
-            (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Unhandled,
-            (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Unhandled,
-            (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Unhandled,
-            (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Unhandled,
-            (BorderStyle::Ridge, _) | (_, BorderStyle::Ridge) => BorderCornerKind::Unhandled,
-            (BorderStyle::Outset, _) | (_, BorderStyle::Outset) => BorderCornerKind::Unhandled,
-            (BorderStyle::Inset, _) | (_, BorderStyle::Inset) => BorderCornerKind::Unhandled,
+            // Dashed and dotted border corners get drawn into a clip mask.
+            (BorderStyle::Dashed, BorderStyle::Dashed) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dash,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+            (BorderStyle::Dotted, BorderStyle::Dotted) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dot,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+
+            // Draw border transitions with dots and/or dashes as
+            // solid segments. The old border path didn't support
+            // this anyway, so we might as well start using the new
+            // border path here, since the dashing in the edges is
+            // much higher quality anyway.
+            (BorderStyle::Dotted, _) |
+            (_, BorderStyle::Dotted) |
+            (BorderStyle::Dashed, _) |
+            (_, BorderStyle::Dashed) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
+            }
+
+            // Everything else can be handled by drawing the corner twice,
+            // where the shader outputs zero alpha for the side it's not
+            // drawing. This is somewhat inefficient in terms of pixels
+            // written, but it's a fairly rare case, and we can optimize
+            // this case later.
+            _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
         }
     }
 
     fn get_edge(&self,
                 edge: &BorderSide,
                 width: f32) -> (BorderEdgeKind, f32) {
         if width == 0.0 {
             return (BorderEdgeKind::None, 0.0);
@@ -157,46 +208,45 @@ impl NormalBorderHelpers for NormalBorde
 
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
             BorderStyle::Groove |
             BorderStyle::Ridge |
-            BorderStyle::Dashed => (BorderEdgeKind::Clip, width),
-
-            BorderStyle::Dotted => (BorderEdgeKind::Unhandled, width),
+            BorderStyle::Dashed |
+            BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
         }
     }
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
                                    clip_region: &ClipRegion,
-                                   use_new_border_path: bool,
+                                   corner_instances: [BorderCornerInstance; 4],
                                    extra_clips: &[ClipSource]) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
         let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
 
         let prim_cpu = BorderPrimitiveCpu {
-            use_new_border_path: use_new_border_path,
+            corner_instances: corner_instances,
         };
 
         let prim_gpu = BorderPrimitiveGpu {
             colors: [ left_color, top_color, right_color, bottom_color ],
             widths: [ widths.left,
                       widths.top,
                       widths.right,
                       widths.bottom ],
@@ -272,52 +322,28 @@ impl FrameBuilder {
                               widths.left,
                               bottom,
                               widths.bottom,
                               &radius.bottom_left,
                               BorderCorner::BottomLeft,
                               rect),
         ];
 
-        // If any of the corners are unhandled, fall back to slow path for now.
-        if corners.iter().any(|c| *c == BorderCornerKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         let (left_edge, left_len) = border.get_edge(left, widths.left);
         let (top_edge, top_len) = border.get_edge(top, widths.top);
         let (right_edge, right_len) = border.get_edge(right, widths.right);
         let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
 
         let edges = [
             left_edge,
             top_edge,
             right_edge,
             bottom_edge,
         ];
 
-        // If any of the edges are unhandled, fall back to slow path for now.
-        if edges.iter().any(|e| *e == BorderEdgeKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         // Use a simple rectangle case when all edges and corners are either
         // solid or none.
         let all_corners_simple = corners.iter().all(|c| {
             *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
         });
         let all_edges_simple = edges.iter().all(|e| {
             *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
         });
@@ -361,32 +387,40 @@ impl FrameBuilder {
                                                          LayerSize::new(rect_width, bottom_len)),
                                          clip_region,
                                          &border.bottom.color,
                                          PrimitiveFlags::None);
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
+            let mut corner_instances = [BorderCornerInstance::Single; 4];
 
-            for corner in corners.iter() {
-                if let &BorderCornerKind::Mask(corner_data, corner_radius, widths) = corner {
-                    let clip_source = BorderCornerClipSource::new(corner_data,
-                                                                  corner_radius,
-                                                                  widths);
-                    extra_clips.push(ClipSource::BorderCorner(clip_source));
+            for (i, corner) in corners.iter().enumerate() {
+                match corner {
+                    &BorderCornerKind::Mask(corner_data, corner_radius, widths, kind) => {
+                        let clip_source = BorderCornerClipSource::new(corner_data,
+                                                                      corner_radius,
+                                                                      widths,
+                                                                      kind);
+                        extra_clips.push(ClipSource::BorderCorner(clip_source));
+                    }
+                    &BorderCornerKind::Clip(instance_kind) => {
+                        corner_instances[i] = instance_kind;
+                    }
+                    _ => {}
                 }
             }
 
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              clip_region,
-                                             true,
+                                             corner_instances,
                                              &extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
@@ -416,105 +450,217 @@ impl BorderSideHelpers for BorderSide {
                     ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
                 }
             }
             _ => self.color,
         }
     }
 }
 
+/// The kind of border corner clip.
+#[repr(C)]
+#[derive(Copy, Debug, Clone, PartialEq)]
+pub enum BorderCornerClipKind {
+    Dash,
+    Dot,
+}
+
 /// The source data for a border corner clip mask.
 #[derive(Debug, Clone)]
 pub struct BorderCornerClipSource {
     pub corner_data: BorderCornerClipData,
-    pub dash_count: usize,
-    dash_arc_length: f32,
+    pub max_clip_count: usize,
+    pub actual_clip_count: usize,
+    kind: BorderCornerClipKind,
+    widths: LayerSize,
     ellipse: Ellipse,
 }
 
 impl BorderCornerClipSource {
     pub fn new(corner_data: BorderCornerClipData,
                corner_radius: LayerSize,
-               widths: LayerSize) -> BorderCornerClipSource {
-        let ellipse = Ellipse::new(corner_radius);
-
+               widths: LayerSize,
+               kind: BorderCornerClipKind) -> BorderCornerClipSource {
         // Work out a dash length (and therefore dash count)
         // based on the width of the border edges. The "correct"
         // dash length is not mentioned in the CSS borders
         // spec. The calculation below is similar, but not exactly
         // the same as what Gecko uses.
         // TODO(gw): Iterate on this to get it closer to what Gecko
         //           uses for dash length.
 
-        // Approximate the total arc length of the quarter ellipse.
-        let total_arc_length = ellipse.get_quarter_arc_length();
+        let (ellipse, max_clip_count) = match kind {
+            BorderCornerClipKind::Dash => {
+                let ellipse = Ellipse::new(corner_radius);
 
-        // The desired dash length is ~3x the border width.
-        let average_border_width = 0.5 * (widths.width + widths.height);
-        let desired_dash_arc_length = average_border_width * 3.0;
+                // The desired dash length is ~3x the border width.
+                let average_border_width = 0.5 * (widths.width + widths.height);
+                let desired_dash_arc_length = average_border_width * 3.0;
+
+                // Get the ideal number of dashes for that arc length.
+                // This is scaled by 0.5 since there is an on/off length
+                // for each dash.
+                let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
-        // Get the ideal number of dashes for that arc length.
-        // This is scaled by 0.5 since there is an on/off length
-        // for each dash.
-        let desired_count = 0.5 * total_arc_length / desired_dash_arc_length;
+                // Round that up to the nearest integer, so that the dash length
+                // doesn't exceed the ratio above. Add one extra dash to cover
+                // the last half-dash of the arc.
+                (ellipse, 1 + desired_count.ceil() as usize)
+            }
+            BorderCornerClipKind::Dot => {
+                // The centers of dots follow an ellipse along the middle of the
+                // border radius.
+                let inner_radius = corner_radius - widths * 0.5;
+                let ellipse = Ellipse::new(inner_radius);
 
-        // Round that up to the nearest integer, so that the dash length
-        // doesn't exceed the ratio above.
-        let actual_count = desired_count.ceil();
+                // Allocate a "worst case" number of dot clips. This can be
+                // calculated by taking the minimum edge radius, since that
+                // will result in the maximum number of dots along the path.
+                let min_diameter = widths.width.min(widths.height);
 
-        // Get the correct dash arc length.
-        let dash_arc_length = 0.5 * total_arc_length / actual_count;
+                // Get the number of circles (assuming spacing of one diameter
+                // between dots).
+                let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
 
-        // Get the number of dashes we'll need to fit.
-        let dash_count = actual_count as usize;
+                // Add space for one extra dot since they are centered at the
+                // start of the arc.
+                (ellipse, 1 + max_dot_count.ceil() as usize)
+            }
+        };
 
         BorderCornerClipSource {
+            kind: kind,
             corner_data: corner_data,
-            dash_count: dash_count,
+            max_clip_count: max_clip_count,
+            actual_clip_count: 0,
             ellipse: ellipse,
-            dash_arc_length: dash_arc_length,
+            widths: widths,
         }
     }
 
-    pub fn populate_gpu_data(&self, slice: &mut [GpuBlock32]) {
-        let (header, dashes) = slice.split_first_mut().unwrap();
+    pub fn populate_gpu_data(&mut self, slice: &mut [GpuBlock32]) {
+        let (header, clips) = slice.split_first_mut().unwrap();
         *header = self.corner_data.into();
 
-        let mut current_arc_length = self.dash_arc_length * 0.5;
-        for dash_index in 0..self.dash_count {
-            let arc_length0 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+        match self.kind {
+            BorderCornerClipKind::Dash => {
+                // Get the correct dash arc length.
+                self.actual_clip_count = self.max_clip_count;
+                let dash_arc_length = 0.5 * self.ellipse.total_arc_length / (self.actual_clip_count - 1) as f32;
+                let mut current_arc_length = -0.5 * dash_arc_length;
+                for dash_index in 0..self.actual_clip_count {
+                    let arc_length0 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let arc_length1 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let dash_data = BorderCornerDashClipData::new(arc_length0,
+                                                                  arc_length1,
+                                                                  &self.ellipse);
+
+                    clips[dash_index] = dash_data.into();
+                }
+            }
+            BorderCornerClipKind::Dot => {
+                let mut forward_dots = Vec::new();
+                let mut back_dots = Vec::new();
+                let mut leftover_arc_length = 0.0;
+
+                // Alternate between adding dots at the start and end of the
+                // ellipse arc. This ensures that we always end up with an exact
+                // half dot at each end of the arc, to match up with the edges.
+                forward_dots.push(DotInfo::new(0.0, self.widths.width));
+                back_dots.push(DotInfo::new(self.ellipse.total_arc_length, self.widths.height));
+
+                for dot_index in 0..self.max_clip_count {
+                    let prev_forward_pos = *forward_dots.last().unwrap();
+                    let prev_back_pos = *back_dots.last().unwrap();
+
+                    // Select which end of the arc to place a dot from.
+                    // This just alternates between the start and end of
+                    // the arc, which ensures that there is always an
+                    // exact half-dot at each end of the ellipse.
+                    let going_forward = dot_index & 1 == 0;
+
+                    let (next_dot_pos, leftover) = if going_forward {
+                        let next_dot_pos = prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter;
+                        (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos)
+                    } else {
+                        let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter;
+                        (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos)
+                    };
 
-            let arc_length1 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+                    // Use a lerp between each edge's dot
+                    // diameter, based on the linear distance
+                    // along the arc to get the diameter of the
+                    // dot at this arc position.
+                    let t = next_dot_pos / self.ellipse.total_arc_length;
+                    let dot_diameter = lerp(self.widths.width, self.widths.height, t);
+
+                    // If we can't fit a dot, bail out.
+                    if leftover < dot_diameter {
+                        leftover_arc_length = leftover;
+                        break;
+                    }
+
+                    // We can place a dot!
+                    let dot = DotInfo::new(next_dot_pos, dot_diameter);
+                    if going_forward {
+                        forward_dots.push(dot);
+                    } else {
+                        back_dots.push(dot);
+                    }
+                }
 
-            let dash_data = BorderCornerDashClipData::new(arc_length0,
-                                                          arc_length1,
-                                                          &self.ellipse);
-            dashes[dash_index] = dash_data.into();
+                // Now step through the dots, and distribute any extra
+                // leftover space on the arc between them evenly. Once
+                // the final arc position is determined, generate the correct
+                // arc positions and angles that get passed to the clip shader.
+                self.actual_clip_count = 0;
+                let dot_count = forward_dots.len() + back_dots.len();
+                let extra_space_per_dot = leftover_arc_length / (dot_count - 1) as f32;
+
+                for (i, dot) in forward_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos + extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+
+                for (i, dot) in back_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos - extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+            }
         }
     }
 }
 
 /// Represents the common GPU data for writing a
 /// clip mask for a border corner.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 pub struct BorderCornerClipData {
     /// Local space rect of the border corner.
     corner_rect: LayerRect,
     /// Local space point that is the center of the
     /// circle or ellipse that we are clipping against.
     clip_center: LayerPoint,
-    /// A constant that flips the local space points
-    /// and tangents of the ellipse for this specific
-    /// corner. This is used since the ellipse points
-    /// and tangents are always generated for a single
-    /// quadrant only.
-    sign_modifier: LayerPoint,
+    /// The shader needs to know which corner, to
+    /// be able to flip the dash tangents to the
+    /// right orientation.
+    corner: f32,        // Of type BorderCorner enum
+    kind: f32,          // Of type BorderCornerClipKind enum
 }
 
 /// Represents the GPU data for drawing a single dash
 /// to a clip mask. A dash clip is defined by two lines.
 /// We store a point on the ellipse curve, and a tangent
 /// to that point, which allows for efficient line-distance
 /// calculations in the fragment shader.
 #[derive(Debug, Clone)]
@@ -539,8 +685,48 @@ impl BorderCornerDashClipData {
         BorderCornerDashClipData {
             point0: p0,
             tangent0: t0,
             point1: p1,
             tangent1: t1,
         }
     }
 }
+
+/// Represents the GPU data for drawing a single dot
+/// to a clip mask.
+#[derive(Debug, Clone)]
+#[repr(C)]
+pub struct BorderCornerDotClipData {
+    pub center: LayerPoint,
+    pub radius: f32,
+    pub padding: [f32; 5],
+}
+
+impl BorderCornerDotClipData {
+    pub fn new(arc_length: f32,
+               radius: f32,
+               ellipse: &Ellipse) -> BorderCornerDotClipData {
+        let theta = ellipse.find_angle_for_arc_length(arc_length);
+        let (center, _) = ellipse.get_point_and_tangent(theta);
+
+        BorderCornerDotClipData {
+            center: center,
+            radius: radius,
+            padding: [0.0; 5],
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+struct DotInfo {
+    arc_pos: f32,
+    diameter: f32,
+}
+
+impl DotInfo {
+    fn new(arc_pos: f32, diameter: f32) -> DotInfo {
+        DotInfo {
+            arc_pos: arc_pos,
+            diameter: diameter,
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -5,20 +5,20 @@
 use euclid::Point3D;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRectKind;
-use webrender_traits::{ClipId, ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
-use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform, PipelineId};
-use webrender_traits::{ScrollEventPhase, ScrollLayerRect, ScrollLocation, WorldPoint};
-use webrender_traits::{DeviceIntRect, WorldPoint4D};
+use webrender_traits::{ClipId, ClipRegion, DeviceIntRect, LayerPixel, LayerPoint, LayerRect};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayerToWorldTransform, PipelineId};
+use webrender_traits::{ScrollClamping, ScrollEventPhase, ScrollLayerRect, ScrollLocation};
+use webrender_traits::{WorldPoint, WorldPoint4D};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
@@ -193,34 +193,42 @@ impl ClipScrollNode {
             -scrollable_height - self.scrolling.offset.y
         } else {
             0.0
         };
 
         LayerSize::new(overscroll_x, overscroll_y)
     }
 
-    pub fn set_scroll_origin(&mut self, origin: &LayerPoint) -> bool {
+    pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
         match self.node_type {
             NodeType::ReferenceFrame(_) => {
                 warn!("Tried to scroll a reference frame.");
                 return false;
             }
             NodeType::Clip(_) => {}
         };
 
 
         let scrollable_height = self.scrollable_height();
         let scrollable_width = self.scrollable_width();
-        if scrollable_height <= 0. && scrollable_width <= 0. {
-            return false;
-        }
+
+        let new_offset = match clamp {
+            ScrollClamping::ToContentBounds => {
+                if scrollable_height <= 0. && scrollable_width <= 0. {
+                    return false;
+                }
 
-        let new_offset = LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
-                                         (-origin.y).max(-scrollable_height).min(0.0).round());
+                let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
+                LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
+                                (-origin.y).max(-scrollable_height).min(0.0).round())
+            }
+            ScrollClamping::NoClamping => LayerPoint::zero() - *origin,
+        };
+
         if new_offset == self.scrolling.offset {
             return false;
         }
 
         self.scrolling.offset = new_offset;
         self.scrolling.bouncing_back = false;
         self.scrolling.started_bouncing_back = false;
         true
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,25 +1,27 @@
 /* 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 clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use fnv::FnvHasher;
+use print_tree::PrintTree;
 use std::collections::{HashMap, HashSet};
 use std::hash::BuildHasherDefault;
 use webrender_traits::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
-use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerRect};
-use webrender_traits::{ScrollLayerState, ScrollLocation, WorldPoint, as_scroll_parent_rect};
+use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
+use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, WorldPoint};
+use webrender_traits::as_scroll_parent_rect;
 
 pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
 
 pub struct ClipScrollTree {
     pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<ClipId, LayerPoint>,
+    pub pending_scroll_offsets: HashMap<ClipId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current reference frame id, used for giving a unique id to all new
     /// reference frames. The ClipScrollTree increments this by one every time a
     /// reference frame is created.
@@ -126,33 +128,32 @@ impl ClipScrollTree {
                 scroll_states.insert(layer_id, old_node.scrolling);
             }
         }
 
         self.pipelines_to_discard.clear();
         scroll_states
     }
 
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
         if id.is_reference_frame() {
             warn!("Tried to scroll a reference frame.");
             return false;
         }
 
         if self.nodes.is_empty() {
-            self.pending_scroll_offsets.insert(id, origin);
+            self.pending_scroll_offsets.insert(id, (origin, clamp));
             return false;
         }
 
-        let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
         if let Some(node) = self.nodes.get_mut(&id) {
-            return node.set_scroll_origin(&origin);
+            return node.set_scroll_origin(&origin, clamp);
         }
 
-        self.pending_scroll_offsets.insert(id, origin);
+        self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
@@ -303,18 +304,18 @@ impl ClipScrollTree {
         for (clip_id, node) in &mut self.nodes {
             let scrolling_state = match old_states.get(clip_id) {
                 Some(old_scrolling_state) => *old_scrolling_state,
                 None => ScrollingState::new(),
             };
 
             node.finalize(&scrolling_state);
 
-            if let Some(pending_offset) = self.pending_scroll_offsets.remove(clip_id) {
-                node.set_scroll_origin(&pending_offset);
+            if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
+                node.set_scroll_origin(&pending_offset, clamping);
             }
         }
 
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
@@ -349,10 +350,52 @@ impl ClipScrollTree {
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
 
         match self.currently_scrolling_node_id {
             Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
+
+    fn print_node(&self, id: &ClipId, pt: &mut PrintTree) {
+        let node = self.nodes.get(id).unwrap();
+
+        match node.node_type {
+            NodeType::Clip(ref info) => {
+                pt.new_level("Clip".to_owned());
+                pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
+
+                pt.new_level(format!("Clip Sources [{}]", info.clip_sources.len()));
+                for source in &info.clip_sources {
+                    pt.add_item(format!("{:?}", source));
+                }
+                pt.end_level();
+            }
+            NodeType::ReferenceFrame(ref transform) => {
+                pt.new_level(format!("ReferenceFrame {:?}", transform));
+            }
+        }
+
+        pt.add_item(format!("content_size: {:?}", node.content_size));
+        pt.add_item(format!("scroll.offset: {:?}", node.scrolling.offset));
+        pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
+        pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
+        pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
+        pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
+        pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
+
+        for child_id in &node.children {
+            self.print_node(child_id, pt);
+        }
+
+        pt.end_level();
+    }
+
+    #[allow(dead_code)]
+    pub fn print(&self) {
+        if !self.nodes.is_empty() {
+            let mut pt = PrintTree::new("clip_scroll tree");
+            self.print_node(&self.root_reference_frame_id, &mut pt);
+        }
+    }
 }
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -40,16 +40,17 @@ const SHADER_VERSION_GL: &'static str = 
 
 const SHADER_VERSION_GLES: &'static str = "#version 300 es\n";
 
 static SHADER_PREAMBLE: &'static str = "shared";
 
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
+    LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureTarget {
     Default,
     Array,
     Rect,
     External,
@@ -443,20 +444,20 @@ impl Program {
                 self.gl.bind_attrib_location(self.id, ClipAttribute::DataIndex as gl::GLuint, "aClipDataIndex");
                 self.gl.bind_attrib_location(self.id, ClipAttribute::SegmentIndex as gl::GLuint, "aClipSegmentIndex");
             }
         }
 
         self.gl.link_program(self.id);
         if self.gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
             let error_log = self.gl.get_program_info_log(self.id);
-            println!("Failed to link shader program: {}", error_log);
+            println!("Failed to link shader program: {:?}\n{}", self.name, error_log);
             self.gl.detach_shader(self.id, vs_id);
             self.gl.detach_shader(self.id, fs_id);
-            return Err(ShaderError::Link(error_log));
+            return Err(ShaderError::Link(self.name.clone(), error_log));
         }
 
         Ok(())
     }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
@@ -854,17 +855,17 @@ impl FileWatcherThread {
 pub struct Capabilities {
     pub max_ubo_size: usize,
     pub supports_multisampling: bool,
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error mssage
-    Link(String), // error message
+    Link(String, String), // name, error message
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [TextureId; 16],
     bound_program: ProgramId,
     bound_vao: VAOId,
@@ -1707,22 +1708,17 @@ impl Device {
                           data: &[u8]) {
         debug_assert!(self.inside_frame);
 
         let mut expanded_data = Vec::new();
 
         let (gl_format, bpp, data) = match self.textures.get(&texture_id).unwrap().format {
             ImageFormat::A8 => {
                 if cfg!(any(target_arch="arm", target_arch="aarch64")) {
-                    for byte in data {
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                    }
+                    expanded_data.extend(data.iter().flat_map(|byte| repeat(*byte).take(4)));
                     (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice())
                 } else {
                     (GL_FORMAT_A, 1, data)
                 }
             }
             ImageFormat::RGB8 => (gl::RGB, 3, data),
             ImageFormat::RGBA8 => (get_gl_format_bgra(self.gl()), 4, data),
             ImageFormat::RG8 => (gl::RG, 2, data),
@@ -1732,18 +1728,16 @@ impl Device {
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
         // Take the stride into account for all rows, except the last one.
         let len = bpp * row_length * (height - 1)
                 + width * bpp;
-
-        assert!(data.len() as u32 >= len);
         let data = &data[0..len as usize];
 
         if let Some(..) = stride {
             self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture_id);
         self.update_image_for_2d_texture(texture_id.target,
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -7,88 +7,94 @@ use std::f32::consts::FRAC_PI_2;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
 pub struct Ellipse {
     pub radius: LayerSize,
+    pub total_arc_length: f32,
 }
 
 impl Ellipse {
     pub fn new(radius: LayerSize) -> Ellipse {
+        // Approximate the total length of the first quadrant of this ellipse.
+        let total_arc_length = get_simpson_length(FRAC_PI_2,
+                                                  radius.width,
+                                                  radius.height);
+
         Ellipse {
             radius: radius,
+            total_arc_length: total_arc_length,
         }
     }
 
-    /// Use Simpsons rule to approximate the arc length of
-    /// part of an ellipse. Note that this only works over
-    /// the range of [0, pi/2].
-    // TODO(gw): This is a simplistic way to estimate the
-    // arc length of an ellipse segment. We can probably use
-    // a faster / more accurate method!
-    fn get_simpson_length(&self, theta: f32) -> f32 {
-        let df = theta / STEP_COUNT as f32;
-        let mut sum = 0.0;
-
-        for i in 0..(STEP_COUNT+1) {
-            let (sin_theta, cos_theta) = (i as f32 * df).sin_cos();
-            let a = self.radius.width * sin_theta;
-            let b = self.radius.height * cos_theta;
-            let y = (a*a + b*b).sqrt();
-            let q = if i == 0 || i == STEP_COUNT {
-                1.0
-            } else if i % 2 == 0 {
-                2.0
-            } else {
-                4.0
-            };
-
-            sum += q * y;
-        }
-
-        (df / 3.0) * sum
-    }
-
     /// Binary search to estimate the angle of an ellipse
     /// for a given arc length. This only searches over the
     /// first quadrant of an ellipse.
     pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 {
+        // Clamp arc length to [0, pi].
+        let arc_length = arc_length.max(0.0).min(self.total_arc_length);
+
         let epsilon = 0.01;
         let mut low = 0.0;
         let mut high = FRAC_PI_2;
         let mut theta = 0.0;
 
         while low <= high {
             theta = 0.5 * (low + high);
-            let length = self.get_simpson_length(theta);
+            let length = get_simpson_length(theta,
+                                            self.radius.width,
+                                            self.radius.height);
 
             if (length - arc_length).abs() < epsilon {
                 break;
             } else if length < arc_length {
                 low = theta;
             } else {
                 high = theta;
             }
         }
 
         theta
     }
 
-    /// Approximate the total length of the first quadrant of
-    /// this ellipse.
-    pub fn get_quarter_arc_length(&self) -> f32 {
-        self.get_simpson_length(FRAC_PI_2)
-    }
-
     /// Get a point and tangent on this ellipse from a given angle.
     /// This only works for the first quadrant of the ellipse.
     pub fn get_point_and_tangent(&self, theta: f32) -> (LayerPoint, LayerPoint) {
         let (sin_theta, cos_theta) = theta.sin_cos();
         let point = LayerPoint::new(self.radius.width * cos_theta,
                                     self.radius.height * sin_theta);
         let tangent = LayerPoint::new(-self.radius.width * sin_theta,
                                        self.radius.height * cos_theta);
         (point, tangent)
     }
 }
+
+/// Use Simpsons rule to approximate the arc length of
+/// part of an ellipse. Note that this only works over
+/// the range of [0, pi/2].
+// TODO(gw): This is a simplistic way to estimate the
+// arc length of an ellipse segment. We can probably use
+// a faster / more accurate method!
+fn get_simpson_length(theta: f32, rx: f32, ry: f32) -> f32 {
+    let df = theta / STEP_COUNT as f32;
+    let mut sum = 0.0;
+
+    for i in 0..(STEP_COUNT+1) {
+        let (sin_theta, cos_theta) = (i as f32 * df).sin_cos();
+        let a = rx * sin_theta;
+        let b = ry * cos_theta;
+        let y = (a*a + b*b).sqrt();
+        let q = if i == 0 || i == STEP_COUNT {
+            1.0
+        } else if i % 2 == 0 {
+            2.0
+        } else {
+            4.0
+        };
+
+        sum += q * y;
+    }
+
+    (df / 3.0) * sum
+}
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -10,24 +10,25 @@ use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use profiler::TextureCacheProfileCounters;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
-use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
+use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
-use webrender_traits::{AuxiliaryLists, ClipAndScrollInfo, ClipDisplayItem, ClipId, ClipRegion};
-use webrender_traits::{ColorF, DeviceUintRect, DeviceUintSize, DisplayItem, Epoch, FilterOp};
-use webrender_traits::{ImageDisplayItem, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
-use webrender_traits::{LayoutRect, LayoutTransform, MixBlendMode, PipelineId, ScrollEventPhase};
+use webrender_traits::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipDisplayItem};
+use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintRect, DeviceUintSize, DisplayItemRef};
+use webrender_traits::{Epoch, FilterOp, ImageDisplayItem, ItemRange, LayerPoint, LayerRect};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayoutSize, LayoutTransform};
+use webrender_traits::{MixBlendMode, PipelineId, ScrollClamping, ScrollEventPhase};
 use webrender_traits::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
-use webrender_traits::{StackingContext, TileOffset, WorldPoint};
+use webrender_traits::{StackingContext, TileOffset, TransformStyle, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
@@ -61,52 +62,39 @@ impl<'a> FlattenContext<'a> {
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
-trait DisplayListHelpers {
-    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a LayoutRect)>;
-}
-
-impl DisplayListHelpers for Vec<DisplayItem> {
-    fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a LayoutRect)> {
-        self.first().and_then(|item| match item.item {
-            SpecificDisplayItem::PushStackingContext(ref specific_item) => {
-                Some((&specific_item.stacking_context, &item.rect))
-            },
-            _ => None,
-        })
-    }
-}
-
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(&self,
-                                  auxiliary_lists: &AuxiliaryLists,
+                                  display_list: &BuiltDisplayList,
+                                  input_filters: ItemRange<FilterOp>,
                                   properties: &SceneProperties) -> Vec<LowLevelFilterOp>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
     }
 
     fn filter_ops_for_compositing(&self,
-                                  auxiliary_lists: &AuxiliaryLists,
+                                  display_list: &BuiltDisplayList,
+                                  input_filters: ItemRange<FilterOp>,
                                   properties: &SceneProperties) -> Vec<LowLevelFilterOp> {
         let mut filters = vec![];
-        for filter in auxiliary_lists.filters(&self.filters) {
-            match *filter {
+        for filter in display_list.get(input_filters) {
+            match filter {
                 FilterOp::Blur(radius) => {
                     filters.push(LowLevelFilterOp::Blur(
                         radius,
                         AxisDirection::Horizontal));
                     filters.push(LowLevelFilterOp::Blur(
                         radius,
                         AxisDirection::Vertical));
                 }
@@ -145,81 +133,26 @@ impl StackingContextHelpers for Stacking
                             LowLevelFilterOp::Sepia(Au::from_f32_px(amount)));
                 }
             }
         }
         filters
     }
 }
 
-struct DisplayListTraversal<'a> {
-    pub display_list: &'a [DisplayItem],
-    pub next_item_index: usize,
-}
-
-impl<'a> DisplayListTraversal<'a> {
-    pub fn new_skipping_first(display_list: &'a Vec<DisplayItem>) -> DisplayListTraversal {
-        DisplayListTraversal {
-            display_list: display_list,
-            next_item_index: 1,
-        }
-    }
-
-    pub fn skip_current_stacking_context(&mut self) {
-        let mut depth = 0;
-        for item in self {
-            match item.item {
-                SpecificDisplayItem::PushStackingContext(..) => depth += 1,
-                SpecificDisplayItem::PopStackingContext if depth == 0 => return,
-                SpecificDisplayItem::PopStackingContext => depth -= 1,
-                _ => {}
-            }
-            debug_assert!(depth >= 0);
-        }
-    }
-
-    pub fn current_stacking_context_empty(&self) -> bool {
-        match self.peek() {
-            Some(item) => item.item == SpecificDisplayItem::PopStackingContext,
-            None => true,
-        }
-    }
-
-    fn peek(&self) -> Option<&'a DisplayItem> {
-        if self.next_item_index >= self.display_list.len() {
-            return None
-        }
-        Some(&self.display_list[self.next_item_index])
-    }
-}
-
-impl<'a> Iterator for DisplayListTraversal<'a> {
-    type Item = &'a DisplayItem;
-
-    fn next(&mut self) -> Option<&'a DisplayItem> {
-        if self.next_item_index >= self.display_list.len() {
-            return None
-        }
-
-        let item = &self.display_list[self.next_item_index];
-        self.next_item_index += 1;
-        Some(item)
-    }
-}
-
 fn clip_intersection(original_rect: &LayerRect,
                      region: &ClipRegion,
-                     aux_lists: &AuxiliaryLists)
+                     display_list: &BuiltDisplayList)
                      -> Option<LayerRect> {
     if region.image_mask.is_some() {
         return None;
     }
-    let clips = aux_lists.complex_clip_regions(&region.complex);
+    let clips = display_list.get(region.complex_clips);
     let base_rect = region.main.intersection(original_rect);
-    clips.iter().fold(base_rect, |inner_combined, ccr| {
+    clips.fold(base_rect, |inner_combined, ccr| {
         inner_combined.and_then(|combined| {
             ccr.get_inner_rect_full().and_then(|ir| ir.intersection(&combined))
         })
     })
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
@@ -240,19 +173,19 @@ impl Frame {
 
         self.clip_scroll_tree.drain()
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
-    /// Returns true if any nodes actually changed position or false otherwise.
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
-        self.clip_scroll_tree.scroll_nodes(origin, id)
+    /// Returns true if the node actually changed position or false otherwise.
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
+        self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
@@ -292,59 +225,46 @@ impl Frame {
         if window_size.width == 0 || window_size.height == 0 {
             error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
         }
 
         let old_scrolling_states = self.reset();
 
         self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch);
 
-        let (root_stacking_context, root_bounds) = match display_list.starting_stacking_context() {
-            Some(some) => some,
-            None => {
-                warn!("Pipeline display list does not start with a stacking context.");
-                return;
-            }
-        };
-
         let background_color = root_pipeline.background_color.and_then(|color| {
             if color.a > 0.0 {
                 Some(color)
             } else {
                 None
             }
         });
 
         let mut frame_builder = FrameBuilder::new(self.frame_builder.take(),
                                                   window_size,
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache);
 
-            let clip_id = context.builder.push_root(root_pipeline_id,
-                                                    &root_pipeline.viewport_size,
-                                                    &root_bounds.size,
-                                                    &mut self.clip_scroll_tree);
+            context.builder.push_root(root_pipeline_id,
+                                      &root_pipeline.viewport_size,
+                                      &root_pipeline.content_size,
+                                      &mut self.clip_scroll_tree);
 
             context.builder.setup_viewport_offset(window_size,
                                                   inner_rect,
                                                   device_pixel_ratio,
                                                   &mut self.clip_scroll_tree);
 
-            let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
-            self.flatten_stacking_context(&mut traversal,
-                                          root_pipeline_id,
-                                          &mut context,
-                                          clip_id,
-                                          LayerPoint::zero(),
-                                          0,
-                                          root_bounds,
-                                          root_stacking_context);
+            self.flatten_root(&mut display_list.iter(),
+                              root_pipeline_id,
+                              &mut context,
+                              &root_pipeline.content_size);
         }
 
         self.frame_builder = Some(frame_builder);
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
@@ -358,36 +278,37 @@ impl Frame {
                                              pipeline_id,
                                              &content_rect,
                                              clip,
                                              &mut self.clip_scroll_tree);
 
     }
 
     fn flatten_stacking_context<'a>(&mut self,
-                                    traversal: &mut DisplayListTraversal<'a>,
+                                    traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
                                     mut reference_frame_relative_offset: LayerPoint,
-                                    level: i32,
                                     bounds: &LayerRect,
-                                    stacking_context: &StackingContext) {
+                                    stacking_context: &StackingContext,
+                                    filters: ItemRange<FilterOp>) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
-            let auxiliary_lists = context.scene.pipeline_auxiliary_lists
-                                               .get(&pipeline_id)
-                                               .expect("No auxiliary lists?!");
+            // TODO(optimization?): self.traversal.display_list()
+            let display_list = context.scene.display_lists
+                                      .get(&pipeline_id)
+                                      .expect("No display list?!");
             CompositeOps::new(
-                stacking_context.filter_ops_for_compositing(auxiliary_lists, &context.scene.properties),
+                stacking_context.filter_ops_for_compositing(display_list, filters, &context.scene.properties),
                 stacking_context.mix_blend_mode_for_compositing())
         };
 
         if composition_operations.will_make_invisible() {
             traversal.skip_current_stacking_context();
             return;
         }
 
@@ -424,56 +345,26 @@ impl Frame {
             context.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerPoint::zero();
         } else {
             reference_frame_relative_offset = LayerPoint::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
-        // TODO(gw): Int with overflow etc
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
-                                              level == 0,
                                               composition_operations,
                                               *bounds,
                                               stacking_context.transform_style);
 
-        // For the root pipeline, there's no need to add a full screen rectangle
-        // here, as it's handled by the framebuffer clear.
-        if level == 0 && context.scene.root_pipeline_id.unwrap() != pipeline_id {
-            if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
-                if let Some(bg_color) = pipeline.background_color {
-                    // Note: we don't use the original clip region here,
-                    // it's already processed by the node we just pushed.
-                    let background_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
-                    context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
-                                                        bounds,
-                                                        &ClipRegion::simple(&background_rect),
-                                                        &bg_color,
-                                                        PrimitiveFlags::None);
-                }
-            }
-        }
-
         self.flatten_items(traversal,
                            pipeline_id,
                            context,
-                           reference_frame_relative_offset,
-                           level);
-
-        if level == 0 && self.frame_builder_config.enable_scrollbars {
-            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
-            context.builder.add_solid_rectangle(
-                ClipAndScrollInfo::simple(clip_id),
-                &scrollbar_rect,
-                &ClipRegion::simple(&scrollbar_rect),
-                &DEFAULT_SCROLLBAR_COLOR,
-                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
-        }
+                           reference_frame_relative_offset);
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             context.replacements.pop();
         }
 
         if is_reference_frame {
             context.replacements.pop();
             context.builder.pop_reference_frame();
@@ -494,222 +385,295 @@ impl Frame {
         };
 
         let display_list = context.scene.display_lists.get(&pipeline_id);
         let display_list = match display_list {
             Some(display_list) => display_list,
             None => return,
         };
 
-        let (iframe_stacking_context,
-             iframe_stacking_context_bounds) = match display_list.starting_stacking_context() {
-            Some(some) => some,
-            None => {
-                warn!("Pipeline display list does not start with a stacking context.");
-                return;
-            }
-        };
-
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = LayerToScrollTransform::create_translation(
             reference_frame_relative_offset.x + bounds.origin.x,
             reference_frame_relative_offset.y + bounds.origin.y,
             0.0);
 
         let iframe_reference_frame_id =
             context.builder.push_reference_frame(Some(parent_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
-        let iframe_clip_id = ClipId::root_scroll_node(pipeline_id);
         context.builder.add_clip_scroll_node(
-            iframe_clip_id,
+            ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
-            &LayerRect::new(LayerPoint::zero(), iframe_stacking_context_bounds.size),
+            &LayerRect::new(LayerPoint::zero(), pipeline.content_size),
             &ClipRegion::simple(&iframe_rect),
             &mut self.clip_scroll_tree);
 
-        let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
-        self.flatten_stacking_context(&mut traversal,
-                                      pipeline_id,
-                                      context,
-                                      iframe_clip_id,
-                                      LayerPoint::zero(),
-                                      0,
-                                      iframe_stacking_context_bounds,
-                                      iframe_stacking_context);
+        self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
 
         context.builder.pop_reference_frame();
     }
 
-    fn flatten_items<'a>(&mut self,
-                         traversal: &mut DisplayListTraversal<'a>,
-                         pipeline_id: PipelineId,
-                         context: &mut FlattenContext,
-                         reference_frame_relative_offset: LayerPoint,
-                         level: i32) {
-        while let Some(item) = traversal.next() {
-            let mut clip_and_scroll = item.clip_and_scroll;
-            clip_and_scroll.scroll_node_id =
-                context.clip_id_with_replacement(clip_and_scroll.scroll_node_id);
+    fn flatten_item<'a, 'b>(&mut self,
+                            item: DisplayItemRef<'a, 'b>,
+                            pipeline_id: PipelineId,
+                            context: &mut FlattenContext,
+                            reference_frame_relative_offset: LayerPoint)
+                            -> Option<BuiltDisplayListIter<'a>> {
+        let mut clip_and_scroll = item.clip_and_scroll();
+        clip_and_scroll.scroll_node_id =
+            context.clip_id_with_replacement(clip_and_scroll.scroll_node_id);
 
-            match item.item {
-                SpecificDisplayItem::WebGL(ref info) => {
-                    context.builder.add_webgl_rectangle(clip_and_scroll,
-                                                        item.rect,
-                                                        &item.clip,
-                                                        info.context_id);
-                }
-                SpecificDisplayItem::Image(ref info) => {
-                    let image = context.resource_cache.get_image_properties(info.image_key);
-                    if let Some(tile_size) = image.tiling {
-                        // The image resource is tiled. We have to generate an image primitive
-                        // for each tile.
-                        let image_size = DeviceUintSize::new(image.descriptor.width, image.descriptor.height);
-                        self.decompose_image(clip_and_scroll,
-                                             context,
-                                             &item.rect,
-                                             &item.clip,
-                                             info,
-                                             image_size,
-                                             tile_size as u32);
-                    } else {
-                        context.builder.add_image(clip_and_scroll,
-                                                  item.rect,
-                                                  &item.clip,
-                                                  &info.stretch_size,
-                                                  &info.tile_spacing,
-                                                  None,
-                                                  info.image_key,
-                                                  info.image_rendering,
-                                                  None);
-                    }
+        match *item.item() {
+            SpecificDisplayItem::WebGL(ref info) => {
+                context.builder.add_webgl_rectangle(clip_and_scroll,
+                                                    item.rect(),
+                                                    item.clip_region(),
+                                                    info.context_id);
+            }
+            SpecificDisplayItem::Image(ref info) => {
+                let image = context.resource_cache.get_image_properties(info.image_key);
+                if let Some(tile_size) = image.tiling {
+                    // The image resource is tiled. We have to generate an image primitive
+                    // for each tile.
+                    let image_size = DeviceUintSize::new(image.descriptor.width,
+                                                         image.descriptor.height);
+                    self.decompose_image(clip_and_scroll,
+                                         context,
+                                         &item.rect(),
+                                         item.clip_region(),
+                                         info,
+                                         image_size,
+                                         tile_size as u32);
+                } else {
+                    context.builder.add_image(clip_and_scroll,
+                                              item.rect(),
+                                              item.clip_region(),
+                                              &info.stretch_size,
+                                              &info.tile_spacing,
+                                              None,
+                                              info.image_key,
+                                              info.image_rendering,
+                                              None);
                 }
-                SpecificDisplayItem::YuvImage(ref info) => {
-                    context.builder.add_yuv_image(clip_and_scroll,
-                                                  item.rect,
-                                                  &item.clip,
-                                                  info.yuv_data,
-                                                  info.color_space);
-                }
-                SpecificDisplayItem::Text(ref text_info) => {
-                    context.builder.add_text(clip_and_scroll,
-                                             item.rect,
-                                             &item.clip,
-                                             text_info.font_key,
-                                             text_info.size,
-                                             text_info.blur_radius,
-                                             &text_info.color,
-                                             text_info.glyphs,
-                                             text_info.glyph_options);
-                }
-                SpecificDisplayItem::Rectangle(ref info) => {
-                    let auxiliary_lists = context.scene.pipeline_auxiliary_lists
-                                                       .get(&pipeline_id)
-                                                       .expect("No auxiliary lists?!");
-                    // Try to extract the opaque inner rectangle out of the clipped primitive.
-                    if let Some(opaque_rect) = clip_intersection(&item.rect, &item.clip, auxiliary_lists) {
-                        let mut results = Vec::new();
-                        subtract_rect(&item.rect, &opaque_rect, &mut results);
-                        // The inner rectangle is considered opaque within this layer.
-                        // It may still inherit some masking from the clip stack.
+            }
+            SpecificDisplayItem::YuvImage(ref info) => {
+                context.builder.add_yuv_image(clip_and_scroll,
+                                              item.rect(),
+                                              item.clip_region(),
+                                              info.yuv_data,
+                                              info.color_space);
+            }
+            SpecificDisplayItem::Text(ref text_info) => {
+                context.builder.add_text(clip_and_scroll,
+                                         item.rect(),
+                                         item.clip_region(),
+                                         text_info.font_key,
+                                         text_info.size,
+                                         text_info.blur_radius,
+                                         &text_info.color,
+                                         item.glyphs(),
+                                         item.display_list().get(item.glyphs()).count(),
+                                         text_info.glyph_options);
+            }
+            SpecificDisplayItem::Rectangle(ref info) => {
+                let display_list = context.scene.display_lists
+                                          .get(&pipeline_id)
+                                          .expect("No display list?!");
+                // Try to extract the opaque inner rectangle out of the clipped primitive.
+                if let Some(opaque_rect) = clip_intersection(&item.rect(),
+                                                             item.clip_region(),
+                                                             display_list) {
+                    let mut results = Vec::new();
+                    subtract_rect(&item.rect(), &opaque_rect, &mut results);
+                    // The inner rectangle is considered opaque within this layer.
+                    // It may still inherit some masking from the clip stack.
+                    context.builder.add_solid_rectangle(clip_and_scroll,
+                                                        &opaque_rect,
+                                                        &ClipRegion::simple(&item.clip_region().main),
+                                                        &info.color,
+                                                        PrimitiveFlags::None);
+                    for transparent_rect in &results {
                         context.builder.add_solid_rectangle(clip_and_scroll,
-                                                            &opaque_rect,
-                                                            &ClipRegion::simple(&item.clip.main),
-                                                            &info.color,
-                                                            PrimitiveFlags::None);
-                        for transparent_rect in &results {
-                            context.builder.add_solid_rectangle(clip_and_scroll,
-                                                                transparent_rect,
-                                                                &item.clip,
-                                                                &info.color,
-                                                                PrimitiveFlags::None);
-                        }
-                    } else {
-                        context.builder.add_solid_rectangle(clip_and_scroll,
-                                                            &item.rect,
-                                                            &item.clip,
+                                                            transparent_rect,
+                                                            item.clip_region(),
                                                             &info.color,
                                                             PrimitiveFlags::None);
                     }
-                }
-                SpecificDisplayItem::Gradient(ref info) => {
-                    context.builder.add_gradient(clip_and_scroll,
-                                                 item.rect,
-                                                 &item.clip,
-                                                 info.gradient.start_point,
-                                                 info.gradient.end_point,
-                                                 info.gradient.stops,
-                                                 info.gradient.extend_mode,
-                                                 info.tile_size,
-                                                 info.tile_spacing);
-                }
-                SpecificDisplayItem::RadialGradient(ref info) => {
-                    context.builder.add_radial_gradient(clip_and_scroll,
-                                                        item.rect,
-                                                        &item.clip,
-                                                        info.gradient.start_center,
-                                                        info.gradient.start_radius,
-                                                        info.gradient.end_center,
-                                                        info.gradient.end_radius,
-                                                        info.gradient.ratio_xy,
-                                                        info.gradient.stops,
-                                                        info.gradient.extend_mode,
-                                                        info.tile_size,
-                                                        info.tile_spacing);
+                } else {
+                    context.builder.add_solid_rectangle(clip_and_scroll,
+                                                        &item.rect(),
+                                                        item.clip_region(),
+                                                        &info.color,
+                                                        PrimitiveFlags::None);
                 }
-                SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
-                    context.builder.add_box_shadow(clip_and_scroll,
-                                                   &box_shadow_info.box_bounds,
-                                                   &item.clip,
-                                                   &box_shadow_info.offset,
-                                                   &box_shadow_info.color,
-                                                   box_shadow_info.blur_radius,
-                                                   box_shadow_info.spread_radius,
-                                                   box_shadow_info.border_radius,
-                                                   box_shadow_info.clip_mode);
-                }
-                SpecificDisplayItem::Border(ref info) => {
-                    context.builder.add_border(clip_and_scroll,
-                                               item.rect,
-                                               &item.clip,
-                                               info);
+            }
+            SpecificDisplayItem::Gradient(ref info) => {
+                context.builder.add_gradient(clip_and_scroll,
+                                             item.rect(),
+                                             item.clip_region(),
+                                             info.gradient.start_point,
+                                             info.gradient.end_point,
+                                             item.gradient_stops(),
+                                             item.display_list()
+                                                 .get(item.gradient_stops()).count(),
+                                             info.gradient.extend_mode,
+                                             info.tile_size,
+                                             info.tile_spacing);
+            }
+            SpecificDisplayItem::RadialGradient(ref info) => {
+                context.builder.add_radial_gradient(clip_and_scroll,
+                                                    item.rect(),
+                                                    item.clip_region(),
+                                                    info.gradient.start_center,
+                                                    info.gradient.start_radius,
+                                                    info.gradient.end_center,
+                                                    info.gradient.end_radius,
+                                                    info.gradient.ratio_xy,
+                                                    item.gradient_stops(),
+                                                    info.gradient.extend_mode,
+                                                    info.tile_size,
+                                                    info.tile_spacing);
+            }
+            SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+                context.builder.add_box_shadow(clip_and_scroll,
+                                               &box_shadow_info.box_bounds,
+                                               item.clip_region(),
+                                               &box_shadow_info.offset,
+                                               &box_shadow_info.color,
+                                               box_shadow_info.blur_radius,
+                                               box_shadow_info.spread_radius,
+                                               box_shadow_info.border_radius,
+                                               box_shadow_info.clip_mode);
+            }
+            SpecificDisplayItem::Border(ref info) => {
+                context.builder.add_border(clip_and_scroll,
+                                           item.rect(),
+                                           item.clip_region(),
+                                           info,
+                                           item.gradient_stops(),
+                                           item.display_list()
+                                               .get(item.gradient_stops()).count());
+            }
+            SpecificDisplayItem::PushStackingContext(ref info) => {
+                let mut subtraversal = item.sub_iter();
+                self.flatten_stacking_context(&mut subtraversal,
+                                              pipeline_id,
+                                              context,
+                                              item.clip_and_scroll().scroll_node_id,
+                                              reference_frame_relative_offset,
+                                              &item.rect(),
+                                              &info.stacking_context,
+                                              item.filters());
+                return Some(subtraversal);
+            }
+            SpecificDisplayItem::Iframe(ref info) => {
+                self.flatten_iframe(info.pipeline_id,
+                                    clip_and_scroll.scroll_node_id,
+                                    &item.rect(),
+                                    context,
+                                    reference_frame_relative_offset);
+            }
+            SpecificDisplayItem::Clip(ref info) => {
+                let content_rect = &item.rect().translate(&reference_frame_relative_offset);
+                self.flatten_clip(context,
+                                  pipeline_id,
+                                  clip_and_scroll.scroll_node_id,
+                                  &info,
+                                  &content_rect,
+                                  item.clip_region());
+            }
+
+            // Do nothing; these are dummy items for the display list parser
+            SpecificDisplayItem::SetGradientStops | SpecificDisplayItem::SetClipRegion(_) => { }
+
+            SpecificDisplayItem::PopStackingContext =>
+                unreachable!("Should have returned in parent method."),
+        }
+        None
+    }
+
+    fn flatten_root<'a>(&mut self,
+                        traversal: &mut BuiltDisplayListIter<'a>,
+                        pipeline_id: PipelineId,
+                        context: &mut FlattenContext,
+                        content_size: &LayoutSize) {
+        let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size);
+        context.builder.push_stacking_context(&LayerPoint::zero(),
+                                              pipeline_id,
+                                              CompositeOps::default(),
+                                              root_bounds,
+                                              TransformStyle::Flat);
+
+        // We do this here, rather than above because we want any of the top-level
+        // stacking contexts in the display list to be treated like root stacking contexts.
+        // FIXME(mrobinson): Currently only the first one will, which for the moment is
+        // sufficient for all our use cases.
+        context.builder.notify_waiting_for_root_stacking_context();
+
+        // For the root pipeline, there's no need to add a full screen rectangle
+        // here, as it's handled by the framebuffer clear.
+        let clip_id = ClipId::root_scroll_node(pipeline_id);
+        if context.scene.root_pipeline_id.unwrap() != pipeline_id {
+            if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
+                if let Some(bg_color) = pipeline.background_color {
+                    context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
+                                                        &root_bounds,
+                                                        &ClipRegion::simple(&root_bounds),
+                                                        &bg_color,
+                                                        PrimitiveFlags::None);
                 }
-                SpecificDisplayItem::PushStackingContext(ref info) => {
-                    self.flatten_stacking_context(traversal,
-                                                  pipeline_id,
-                                                  context,
-                                                  item.clip_and_scroll.scroll_node_id,
-                                                  reference_frame_relative_offset,
-                                                  level + 1,
-                                                  &item.rect,
-                                                  &info.stacking_context);
+            }
+        }
+
+
+        self.flatten_items(traversal, pipeline_id, context, LayerPoint::zero());
+
+        if self.frame_builder_config.enable_scrollbars {
+            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
+            context.builder.add_solid_rectangle(
+                ClipAndScrollInfo::simple(clip_id),
+                &scrollbar_rect,
+                &ClipRegion::simple(&scrollbar_rect),
+                &DEFAULT_SCROLLBAR_COLOR,
+                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
+        }
+
+        context.builder.pop_stacking_context();
+    }
+
+    fn flatten_items<'a>(&mut self,
+                         traversal: &mut BuiltDisplayListIter<'a>,
+                         pipeline_id: PipelineId,
+                         context: &mut FlattenContext,
+                         reference_frame_relative_offset: LayerPoint) {
+        loop {
+            let subtraversal = {
+                let item = match traversal.next() {
+                    Some(item) => item,
+                    None => break,
+                };
+
+                if SpecificDisplayItem::PopStackingContext == *item.item() {
+                    return;
                 }
-                SpecificDisplayItem::Iframe(ref info) => {
-                    self.flatten_iframe(info.pipeline_id,
-                                        clip_and_scroll.scroll_node_id,
-                                        &item.rect,
-                                        context,
-                                        reference_frame_relative_offset);
-                }
-                SpecificDisplayItem::Clip(ref info) => {
-                    let content_rect = &item.rect.translate(&reference_frame_relative_offset);
-                    self.flatten_clip(context,
-                                      pipeline_id,
-                                      clip_and_scroll.scroll_node_id,
-                                      &info,
-                                      &content_rect,
-                                      &item.clip);
-                }
-                SpecificDisplayItem::PopStackingContext => return,
+
+                self.flatten_item(item, pipeline_id, context, reference_frame_relative_offset)
+            };
+
+            // If flatten_item created a sub-traversal, we need `traversal` to have the
+            // same state as the completed subtraversal, so we reinitialize it here.
+            if let Some(subtraversal) = subtraversal {
+                *traversal = subtraversal;
             }
         }
     }
 
     /// Decomposes an image display item that is repeated into an image per individual repetition.
     /// We need to do this when we are unable to perform the repetition in the shader,
     /// for example if the image is tiled.
     ///
@@ -817,17 +781,17 @@ impl Frame {
         // We need to do this because the image is broken up into smaller tiles in the texture
         // cache and the image shader is not able to work with this type of sparse representation.
 
         // The tiling logic works as follows:
         //
         //  ###################-+  -+
         //  #    |    |    |//# |   | image size
         //  #    |    |    |//# |   |
-        //  #----+----+----+--#-+   |  -+ 
+        //  #----+----+----+--#-+   |  -+
         //  #    |    |    |//# |   |   | regular tile size
         //  #    |    |    |//# |   |   |
         //  #----+----+----+--#-+   |  -+-+
         //  #////|////|////|//# |   |     | "leftover" height
         //  ################### |  -+  ---+
         //  #----+----+----+----+
         //
         // In the ascii diagram above, a large image is plit into tiles of almost regular size.
@@ -997,42 +961,42 @@ impl Frame {
                                       info.image_key,
                                       info.image_rendering,
                                       Some(tile_offset));
         }
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
-                 auxiliary_lists_map: &AuxiliaryListsMap,
+                 display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
                  pan: LayerPoint,
                  texture_cache_profile: &mut TextureCacheProfileCounters)
                  -> RendererFrame {
         self.clip_scroll_tree.update_all_node_transforms(pan);
         let frame = self.build_frame(resource_cache,
-                                     auxiliary_lists_map,
+                                     display_lists,
                                      device_pixel_ratio,
                                      texture_cache_profile);
         resource_cache.expire_old_resources(self.id);
         frame
     }
 
     fn build_frame(&mut self,
                    resource_cache: &mut ResourceCache,
-                   auxiliary_lists_map: &AuxiliaryListsMap,
+                   display_lists: &DisplayListMap,
                    device_pixel_ratio: f32,
                    texture_cache_profile: &mut TextureCacheProfileCounters)
                    -> RendererFrame {
         let mut frame_builder = self.frame_builder.take();
         let frame = frame_builder.as_mut().map(|builder|
             builder.build(resource_cache,
                           self.id,
                           &mut self.clip_scroll_tree,
-                          auxiliary_lists_map,
+                          display_lists,
                           device_pixel_ratio,
                           texture_cache_profile)
         );
         self.frame_builder = frame_builder;
 
         let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
         RendererFrame::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
     }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -10,34 +10,36 @@ use mask_cache::{ClipMode, ClipSource, M
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, SplitGeometry, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
 use prim_store::{BoxShadowPrimitiveGpu, TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
 use profiler::{FrameProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
-use render_task::RenderTaskLocation;
+use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
+use std::collections::HashMap;
 use euclid::{SideOffsets2D, TypedPoint3D};
 use tiling::{ContextIsolation, StackingContextIndex};
-use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
+use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::RectHelpers;
 use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo};
 use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceUintRect, DeviceUintSize, ExtendMode, FontKey, FontRenderMode};
-use webrender_traits::{GlyphOptions, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect};
-use webrender_traits::{LayerSize, LayerToScrollTransform, PipelineId, RepeatMode, TileOffset};
-use webrender_traits::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use webrender_traits::{GlyphInstance, GlyphOptions, GradientStop, ImageKey, ImageRendering};
+use webrender_traits::{ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
+use webrender_traits::{PipelineId, RepeatMode, TileOffset, TransformStyle, WebGLContextId};
+use webrender_traits::{WorldPixel, YuvColorSpace, YuvData};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
     tile_spacing: LayerSize,
 }
@@ -82,36 +84,41 @@ impl ImageBorderSegment {
     }
 }
 
 /// Construct a polygon from stacking context boundaries.
 /// `anchor` here is an index that's going to be preserved in all the
 /// splits of the polygon.
 fn make_polygon(sc: &StackingContext, node: &ClipScrollNode, anchor: usize)
                 -> Polygon<f32, WorldPixel> {
-    let mut bounds = sc.local_bounds;
-    bounds.origin = bounds.origin + sc.reference_frame_offset;
+    //TODO: only work with `sc.local_bounds` worth of space
+    // This can be achieved by moving the `sc.local_bounds.origin` shift
+    // from the primitive local coordinates into the layer transformation.
+    // Which in turn needs it to be a render task property obeyed by all primitives
+    // upon rendering, possibly not limited to `write_*_vertex` implementations.
+    let size = sc.local_bounds.bottom_right();
+    let bounds = LayerRect::new(sc.reference_frame_offset, LayerSize::new(size.x, size.y));
     Polygon::from_transformed_rect(bounds, node.world_content_transform, anchor)
 }
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
-    pub enable_subpixel_aa: bool,
+    pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
 
 impl FrameBuilderConfig {
     pub fn new(enable_scrollbars: bool,
-               enable_subpixel_aa: bool,
+               default_font_render_mode: FontRenderMode,
                debug: bool)
                -> FrameBuilderConfig {
         FrameBuilderConfig {
             enable_scrollbars: enable_scrollbars,
-            enable_subpixel_aa: enable_subpixel_aa,
+            default_font_render_mode: default_font_render_mode,
             debug: debug,
         }
     }
 }
 
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
@@ -127,16 +134,20 @@ pub struct FrameBuilder {
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
+
+    /// Whether or not we've pushed a root stacking context for the current pipeline.
+    has_root_stacking_context: bool,
+
 }
 
 impl FrameBuilder {
     pub fn new(previous: Option<FrameBuilder>,
                screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
@@ -148,31 +159,33 @@ impl FrameBuilder {
                     packed_layers: recycle_vec(prev.packed_layers),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
                     screen_size: screen_size,
                     background_color: background_color,
                     config: config,
+                    has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size: screen_size,
                     background_color: background_color,
                     config: config,
+                    has_root_stacking_context: false,
                 }
             }
         }
     }
 
     pub fn create_clip_scroll_group_if_necessary(&mut self,
                                                  stacking_context_index: StackingContextIndex,
                                                  info: ClipAndScrollInfo) {
@@ -244,20 +257,23 @@ impl FrameBuilder {
             clip_node_id: info.clip_node_id(),
             packed_layer_index: packed_layer_index,
             screen_bounding_rect: None,
          });
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
 
+    pub fn notify_waiting_for_root_stacking_context(&mut self) {
+        self.has_root_stacking_context = false;
+    }
+
     pub fn push_stacking_context(&mut self,
                                  reference_frame_offset: &LayerPoint,
                                  pipeline_id: PipelineId,
-                                 is_page_root: bool,
                                  composite_ops: CompositeOps,
                                  local_bounds: LayerRect,
                                  transform_style: TransformStyle) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
                 // the parent stacking context of a stacking context with mix-blend-mode
@@ -270,21 +286,22 @@ impl FrameBuilder {
                 *isolation = ContextIsolation::Full;
             }
         }
 
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
         let reference_frame_id = self.current_reference_frame_id();
         self.stacking_context_store.push(StackingContext::new(pipeline_id,
                                                               *reference_frame_offset,
-                                                              is_page_root,
+                                                              !self.has_root_stacking_context,
                                                               reference_frame_id,
                                                               local_bounds,
                                                               transform_style,
                                                               composite_ops));
+        self.has_root_stacking_context = true;
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
     }
@@ -414,17 +431,19 @@ impl FrameBuilder {
             }
         }
     }
 
     pub fn add_border(&mut self,
                       clip_and_scroll: ClipAndScrollInfo,
                       rect: LayerRect,
                       clip_region: &ClipRegion,
-                      border_item: &BorderDisplayItem) {
+                      border_item: &BorderDisplayItem,
+                      gradient_stops: ItemRange<GradientStop>,
+                      gradient_stops_count: usize) {
         let create_segments = |outset: SideOffsets2D<f32>| {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayerPoint::new(rect.origin.x - outset.left,
                                          rect.origin.y - outset.top);
             let size = LayerSize::new(rect.size.width + outset.left + outset.right,
                                       rect.size.height + outset.top + outset.bottom);
             let rect = LayerRect::new(origin, size);
 
@@ -574,17 +593,18 @@ impl FrameBuilder {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
 
                     self.add_gradient(clip_and_scroll,
                                       segment,
                                       clip_region,
                                       border.gradient.start_point - segment_rel,
                                       border.gradient.end_point - segment_rel,
-                                      border.gradient.stops,
+                                      gradient_stops,
+                                      gradient_stops_count,
                                       border.gradient.extend_mode,
                                       segment.size,
                                       LayerSize::zero());
                 }
             }
             BorderDetails::RadialGradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
@@ -592,32 +612,33 @@ impl FrameBuilder {
                     self.add_radial_gradient(clip_and_scroll,
                                              segment,
                                              clip_region,
                                              border.gradient.start_center - segment_rel,
                                              border.gradient.start_radius,
                                              border.gradient.end_center - segment_rel,
                                              border.gradient.end_radius,
                                              border.gradient.ratio_xy,
-                                             border.gradient.stops,
+                                             gradient_stops,
                                              border.gradient.extend_mode,
                                              segment.size,
                                              LayerSize::zero());
                 }
             }
         }
     }
 
     pub fn add_gradient(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
                         rect: LayerRect,
                         clip_region: &ClipRegion,
                         start_point: LayerPoint,
                         end_point: LayerPoint,
-                        stops: ItemRange,
+                        stops: ItemRange<GradientStop>,
+                        stops_count: usize,
                         extend_mode: ExtendMode,
                         tile_size: LayerSize,
                         tile_spacing: LayerSize) {
         let tile_repeat = tile_size + tile_spacing;
         let is_not_tiled = tile_repeat.width >= rect.size.width &&
                            tile_repeat.height >= rect.size.height;
 
         let aligned_and_fills_rect = (start_point.x == end_point.x &&
@@ -638,16 +659,17 @@ impl FrameBuilder {
         // reversing in that case.
         let reverse_stops = !aligned &&
                             (start_point.x > end_point.x ||
                              (start_point.x == end_point.x &&
                               start_point.y > end_point.y));
 
         let gradient_cpu = GradientPrimitiveCpu {
             stops_range: stops,
+            stops_count: stops_count,
             extend_mode: extend_mode,
             reverse_stops: reverse_stops,
             cache_dirty: true,
         };
 
         // To get reftests exactly matching with reverse start/end
         // points, it's necessary to reverse the gradient
         // line in some cases.
@@ -679,17 +701,17 @@ impl FrameBuilder {
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: LayerRect,
                                clip_region: &ClipRegion,
                                start_center: LayerPoint,
                                start_radius: f32,
                                end_center: LayerPoint,
                                end_radius: f32,
                                ratio_xy: f32,
-                               stops: ItemRange,
+                               stops: ItemRange<GradientStop>,
                                extend_mode: ExtendMode,
                                tile_size: LayerSize,
                                tile_spacing: LayerSize) {
         let radial_gradient_cpu = RadialGradientPrimitiveCpu {
             stops_range: stops,
             extend_mode: extend_mode,
             cache_dirty: true,
         };
@@ -714,43 +736,61 @@ impl FrameBuilder {
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     rect: LayerRect,
                     clip_region: &ClipRegion,
                     font_key: FontKey,
                     size: Au,
-                    blur_radius: Au,
+                    blur_radius: f32,
                     color: &ColorF,
-                    glyph_range: ItemRange,
+                    glyph_range: ItemRange<GlyphInstance>,
+                    glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
         if color.a == 0.0 {
             return
         }
 
         if size.0 <= 0 {
             return
         }
 
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
-        let render_mode = if blur_radius == Au(0) &&
-                             self.config.enable_subpixel_aa {
-            FontRenderMode::Subpixel
-        } else {
-            FontRenderMode::Alpha
-        };
+        let mut render_mode = self.config.default_font_render_mode;
+
+        // There are some conditions under which we can't use
+        // subpixel text rendering, even if enabled.
+        if render_mode == FontRenderMode::Subpixel {
+            // text-blur shadow needs to force alpha AA.
+            if blur_radius != 0.0 {
+                render_mode = FontRenderMode::Alpha;
+            }
+
+            // text on a stacking context that has filters
+            // (e.g. opacity) can't use sub-pixel.
+            // TODO(gw): It's possible we can relax this in
+            //           the future, if we modify the way
+            //           we handle subpixel blending.
+            if let Some(sc_index) = self.stacking_context_stack.last() {
+                let stacking_context = &self.stacking_context_store[sc_index.0];
+                if stacking_context.composite_ops.count() > 0 {
+                    render_mode = FontRenderMode::Alpha;
+                }
+            }
+        }
 
         let prim_cpu = TextRunPrimitiveCpu {
             font_key: font_key,
             logical_font_size: size,
             blur_radius: blur_radius,
             glyph_range: glyph_range,
+            glyph_count: glyph_count,
             cache_dirty: true,
             glyph_instances: Vec::new(),
             color_texture_id: SourceTexture::Invalid,
             color: *color,
             render_mode: render_mode,
             glyph_options: glyph_options,
             resource_address: GpuStoreAddress(0),
         };
@@ -837,17 +877,17 @@ impl FrameBuilder {
             self.add_solid_rectangle(clip_and_scroll,
                                      box_bounds,
                                      clip_region,
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
-        if blur_radius == 0.0 && spread_radius == 0.0 && border_radius != 0.0 {
+        if blur_radius == 0.0 && border_radius != 0.0 {
             self.fill_box_shadow_rect(clip_and_scroll,
                                       box_bounds,
                                       bs_rect,
                                       clip_region,
                                       color,
                                       border_radius,
                                       clip_mode);
             return;
@@ -920,16 +960,17 @@ impl FrameBuilder {
                     self.add_solid_rectangle(clip_and_scroll,
                                              rect,
                                              clip_region,
                                              color,
                                              PrimitiveFlags::None)
                 }
             }
             BoxShadowKind::Shadow(rects) => {
+                assert!(blur_radius > 0.0);
                 if clip_mode == BoxShadowClipMode::Inset {
                     self.fill_box_shadow_rect(clip_and_scroll,
                                               box_bounds,
                                               bs_rect,
                                               clip_region,
                                               color,
                                               border_radius,
                                               clip_mode);
@@ -1035,16 +1076,18 @@ impl FrameBuilder {
                          clip_region: &ClipRegion,
                          yuv_data: YuvData,
                          color_space: YuvColorSpace) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
             YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
                 [plane_0, plane_1, plane_2],
+            YuvData::InterleavedYCbCr(plane_0) =>
+                [plane_0, ImageKey::new(0, 0), ImageKey::new(0, 0)],
         };
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key: yuv_key,
             yuv_texture_id: [SourceTexture::Invalid, SourceTexture::Invalid, SourceTexture::Invalid],
             yuv_resource_address: GpuStoreAddress(0),
             format: format,
             color_space: color_space,
@@ -1059,25 +1102,25 @@ impl FrameBuilder {
                            PrimitiveContainer::YuvImage(prim_cpu, prim_gpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
-                                                auxiliary_lists_map: &AuxiliaryListsMap,
+                                                display_lists: &DisplayListMap,
                                                 resource_cache: &mut ResourceCache,
                                                 profile_counters: &mut FrameProfileCounters,
                                                 device_pixel_ratio: f32) {
         profile_scope!("cull");
         LayerRectCalculationAndCullingPass::create_and_run(self,
                                                            screen_rect,
                                                            clip_scroll_tree,
-                                                           auxiliary_lists_map,
+                                                           display_lists,
                                                            resource_cache,
                                                            profile_counters,
                                                            device_pixel_ratio);
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree) {
         let distance_from_edge = 8.0;
 
@@ -1125,124 +1168,95 @@ impl FrameBuilder {
 
     fn build_render_task(&mut self, clip_scroll_tree: &ClipScrollTree)
                          -> (RenderTask, usize) {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
         let mut next_task_index = RenderTaskIndex(0);
 
-        let mut sc_stack = Vec::new();
+        let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
         let mut current_task = RenderTask::new_alpha_batch(next_task_index,
                                                            DeviceIntPoint::zero(),
                                                            RenderTaskLocation::Fixed);
         next_task_index.0 += 1;
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
-        // The stack of "preserve-3d" contexts. We are baking these into render targets
-        // and onlu compositing once we are out of "preserve-3d" hierarchy.
-        // That is why we stack those contexts.
-        let mut preserve_3d_stack = Vec::new();
+        // A map of "preserve-3d" contexts. We are baking these into render targets
+        // and only compositing once we are out of "preserve-3d" hierarchy.
+        // The stacking contexts that fall into this category are
+        //  - ones with `ContextIsolation::Items`, for their actual items to be backed
+        //  - immediate children of `ContextIsolation::Items`
+        let mut preserve_3d_map: HashMap<StackingContextIndex, RenderTask> = HashMap::new();
         // The plane splitter, using a simple BSP tree.
         let mut splitter = BspSplitter::new();
 
         self.prim_store.gpu_split_geometry.clear();
+        debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
+                    let parent_isolation = sc_stack.last()
+                                                   .map(|index| self.stacking_context_store[index.0].isolation);
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
                     sc_stack.push(stacking_context_index);
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
+                    debug!("\tpush {:?} {:?}", stacking_context_index, stacking_context.isolation);
+
                     let stacking_context_rect = &stacking_context.screen_bounds;
                     let composite_count = stacking_context.composite_ops.count();
 
-                    let new_task = match stacking_context.isolation {
-                        ContextIsolation::Items => Some(
-                            RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect)
-                        ),
-                        ContextIsolation::Full if composite_count == 0 => Some(
-                            RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect)
-                        ),
-                        ContextIsolation::Full | ContextIsolation::None => None,
-                    };
+                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
+                        next_task_index.0 += 1;
+                    }
 
-                    if let Some(task) = new_task {
+                    if parent_isolation == Some(ContextIsolation::Items) ||
+                       stacking_context.isolation == ContextIsolation::Items {
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
                         next_task_index.0 += 1;
-                        let prev_task = mem::replace(&mut current_task, task);
-                        alpha_task_stack.push(prev_task);
+                        //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
+                        // that have no child items (only other stacking contexts). However, we don't know if
+                        // there are any items at this time (in `PushStackingContext`).
+                        //Note: the reason we add the polygon for splitting during `Push*` as opposed to `Pop*`
+                        // is because we need to preserve the order of drawing for planes that match together.
+                        let scroll_node = clip_scroll_tree.nodes.get(&stacking_context.reference_frame_id).unwrap();
+                        let sc_polygon = make_polygon(stacking_context, scroll_node, stacking_context_index.0);
+                        debug!("\tadd {:?} -> {:?}", stacking_context_index, sc_polygon);
+                        splitter.add(sc_polygon);
                     }
 
                     for _ in 0..composite_count {
-                        let new_task = RenderTask::new_dynamic_alpha_batch(next_task_index,
-                                                                           stacking_context_rect);
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
                         next_task_index.0 += 1;
-                        let prev_task = mem::replace(&mut current_task, new_task);
-                        alpha_task_stack.push(prev_task);
                     }
                 }
                 PrimitiveRunCmd::PopStackingContext => {
                     let stacking_context_index = sc_stack.pop().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
+                    let composite_count = stacking_context.composite_ops.count();
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
-                    // Handle the `Item` isolation type first. Once we are out of an isolated
-                    // sub-tree of stacking contexts, we do plane splitting and compositing.
-                    match stacking_context.isolation {
-                        ContextIsolation::Items => {
-                            let prev_task = alpha_task_stack.pop().unwrap();
-                            let mut old_current = mem::replace(&mut current_task, prev_task);
-                            // If there are any items or non-preserve-3d sub-contexts, we have the
-                            // contents to bake and plane split. Note: if the `old_current` is also preserve-3d,
-                            // means we are inside the `ContextIsolation::Items` sub-tree, which only
-                            // gets added as task dependencies on exit (see the next match arm).
-                            if !old_current.as_alpha_batch().items.is_empty() || !old_current.children.is_empty() {
-                                let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-                                let scroll_node = clip_scroll_tree.nodes.get(&stacking_context.reference_frame_id).unwrap();
-                                let sc_polygon = make_polygon(stacking_context, scroll_node, preserve_3d_stack.len());
-                                splitter.add(sc_polygon);
-                                preserve_3d_stack.push((stacking_context_index, old_current));
-                            } else if !old_current.children.is_empty() {
-                                current_task.children.push(old_current);
-                            }
-                        },
-                        ContextIsolation::None | ContextIsolation::Full => {
-                            // We are back from a "preserve-3d" sub-domain.
-                            // Time to split those stacking context planes.
-                            current_task.children.extend(preserve_3d_stack.iter().map(|&(_, ref task)| task.clone()));
-                            for poly in splitter.sort(TypedPoint3D::new(0.0, 0.0, -1.0)) {
-                                let (sc_index, ref task) = preserve_3d_stack[poly.anchor];
-                                let pp = &poly.points;
-                                let split_geo = SplitGeometry {
-                                    data: [pp[0].x, pp[0].y, pp[0].z,
-                                           pp[1].x, pp[1].y, pp[1].z,
-                                           pp[2].x, pp[2].y, pp[2].z,
-                                           pp[3].x, pp[3].y, pp[3].z],
-                                };
-                                let gpu_index = self.prim_store.gpu_split_geometry.push(split_geo);
-                                let item = AlphaRenderItem::SplitComposite(sc_index, task.id, gpu_index, next_z);
-                                current_task.as_alpha_batch().items.push(item);
-                            }
-                            splitter.reset();
-                            preserve_3d_stack.clear();
-                            next_z += 1;
-                        },
-                    }
+                    debug!("\tpop {:?}", stacking_context_index);
+                    let parent_isolation = sc_stack.last()
+                                                   .map(|index| self.stacking_context_store[index.0].isolation);
 
-                    let composite_count = stacking_context.composite_ops.count();
-
-                    if composite_count == 0 && stacking_context.isolation == ContextIsolation::Full {
+                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::HardwareComposite(stacking_context_index,
                                                                       current_task.id,
                                                                       HardwareCompositeOp::PremultipliedAlpha,
                                                                       next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
@@ -1273,32 +1287,69 @@ impl FrameBuilder {
                                                               mix_blend_mode,
                                                               next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         prev_task.children.push(readback_task);
                         current_task = prev_task;
                     }
+
+                    if parent_isolation == Some(ContextIsolation::Items) ||
+                       stacking_context.isolation == ContextIsolation::Items {
+                        //Note: we don't register the dependent tasks here. It's only done
+                        // when we are out of the `preserve-3d` branch (see the code below),
+                        // since this is only where the parent task is known.
+                        preserve_3d_map.insert(stacking_context_index, current_task);
+                        current_task = alpha_task_stack.pop().unwrap();
+                    }
+
+                    if !preserve_3d_map.is_empty() && parent_isolation != Some(ContextIsolation::Items) {
+                        // Flush the accumulated plane splits onto the task tree.
+                        // Notice how this is done before splitting in order to avoid duplicate tasks.
+                        current_task.children.extend(preserve_3d_map.values().cloned());
+                        debug!("\tplane splitting in {:?}", current_task.id);
+                        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
+                        for poly in splitter.sort(TypedPoint3D::new(0.0, 0.0, 1.0)) {
+                            let sc_index = StackingContextIndex(poly.anchor);
+                            let task_id = preserve_3d_map[&sc_index].id;
+                            debug!("\t\tproduce {:?} -> {:?} for {:?}", sc_index, poly, task_id);
+                            let pp = &poly.points;
+                            let split_geo = SplitGeometry {
+                                data: [pp[0].x, pp[0].y, pp[0].z,
+                                       pp[1].x, pp[1].y, pp[1].z,
+                                       pp[2].x, pp[2].y, pp[2].z,
+                                       pp[3].x, pp[3].y, pp[3].z],
+                            };
+                            let gpu_index = self.prim_store.gpu_split_geometry.push(split_geo);
+                            let item = AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_index, next_z);
+                            current_task.as_alpha_batch().items.push(item);
+                        }
+                        splitter.reset();
+                        preserve_3d_map.clear();
+                        next_z += 1;
+                    }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let group_index = self.stacking_context_store[stacking_context_index.0]
                                           .clip_scroll_group(clip_and_scroll);
                     if self.clip_scroll_group_store[group_index.0].screen_bounding_rect.is_none() {
                         continue
                     }
 
+                    debug!("\trun of {} items into {:?}", prim_count, current_task.id);
+
                     for i in 0..prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
                         if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
                             let prim_metadata = self.prim_store.get_metadata(prim_index);
 
                             // Add any dynamic render tasks needed to render this primitive
                             if let Some(ref render_task) = prim_metadata.render_task {
@@ -1313,25 +1364,26 @@ impl FrameBuilder {
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
-        debug_assert!(preserve_3d_stack.is_empty());
+        debug_assert!(preserve_3d_map.is_empty());
+        debug_assert_eq!(current_task.id, RenderTaskId::Static(RenderTaskIndex(0)));
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
-                 auxiliary_lists_map: &AuxiliaryListsMap,
+                 display_lists: &DisplayListMap,
                  device_pixel_ratio: f32,
                  texture_cache_profile: &mut TextureCacheProfileCounters)
                  -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters.total_primitives.set(self.prim_store.prim_count());
 
@@ -1348,17 +1400,17 @@ impl FrameBuilder {
         // covers the entire screen).
         let cache_size = DeviceUintSize::new(cmp::max(1024, screen_rect.size.width as u32),
                                              cmp::max(1024, screen_rect.size.height as u32));
 
         self.update_scroll_bars(clip_scroll_tree);
 
         self.build_layer_screen_rects_and_cull_layers(&screen_rect,
                                                       clip_scroll_tree,
-                                                      auxiliary_lists_map,
+                                                      display_lists,
                                                       resource_cache,
                                                       &mut profile_counters,
                                                       device_pixel_ratio);
 
         let (main_render_task, static_render_task_count) = self.build_render_task(clip_scroll_tree);
         let mut render_tasks = RenderTaskCollection::new(static_render_task_count);
 
         let mut required_pass_count = 0;
@@ -1428,17 +1480,17 @@ impl FrameBuilder {
     }
 
 }
 
 struct LayerRectCalculationAndCullingPass<'a> {
     frame_builder: &'a mut FrameBuilder,
     screen_rect: &'a DeviceIntRect,
     clip_scroll_tree: &'a mut ClipScrollTree,
-    auxiliary_lists_map: &'a AuxiliaryListsMap,
+    display_lists: &'a DisplayListMap,
     resource_cache: &'a mut ResourceCache,
     profile_counters: &'a mut FrameProfileCounters,
     device_pixel_ratio: f32,
     stacking_context_stack: Vec<StackingContextIndex>,
 
     /// A cached clip info stack, which should handle the most common situation,
     /// which is that we are using the same clip info stack that we were using
     /// previously.
@@ -1448,26 +1500,26 @@ struct LayerRectCalculationAndCullingPas
     /// to recalculate it for every primitive.
     current_clip_info: Option<(ClipId, Option<DeviceIntRect>)>
 }
 
 impl<'a> LayerRectCalculationAndCullingPass<'a> {
     fn create_and_run(frame_builder: &'a mut FrameBuilder,
                       screen_rect: &'a DeviceIntRect,
                       clip_scroll_tree: &'a mut ClipScrollTree,
-                      auxiliary_lists_map: &'a AuxiliaryListsMap,
+                      display_lists: &'a DisplayListMap,
                       resource_cache: &'a mut ResourceCache,
                       profile_counters: &'a mut FrameProfileCounters,
                       device_pixel_ratio: f32) {
 
         let mut pass = LayerRectCalculationAndCullingPass {
             frame_builder: frame_builder,
             screen_rect: screen_rect,
             clip_scroll_tree: clip_scroll_tree,
-            auxiliary_lists_map: auxiliary_lists_map,
+            display_lists: display_lists,
             resource_cache: resource_cache,
             profile_counters: profile_counters,
             device_pixel_ratio: device_pixel_ratio,
             stacking_context_stack: Vec::new(),
             current_clip_stack: Vec::new(),
             current_clip_info: None,
         };
         pass.run();
@@ -1520,24 +1572,24 @@ impl<'a> LayerRectCalculationAndCullingP
                                                                         self.screen_rect,
                                                                         self.device_pixel_ratio);
 
             let mask_info = match node_clip_info.mask_cache_info {
                 Some(ref mut mask_info) => mask_info,
                 _ => continue,
             };
 
-            let auxiliary_lists = self.auxiliary_lists_map.get(&node.pipeline_id)
-                                                          .expect("No auxiliary lists?");
+            let display_list = self.display_lists.get(&node.pipeline_id)
+                                                 .expect("No display list?");
 
             mask_info.update(&node_clip_info.clip_sources,
                              &packed_layer.transform,
                              &mut self.frame_builder.prim_store.gpu_data32,
                              self.device_pixel_ratio,
-                             auxiliary_lists);
+                             display_list);
 
             for clip_source in &node_clip_info.clip_sources {
                 if let Some(mask) = clip_source.image_mask() {
                     // We don't add the image mask for resolution, because
                     // layer masks are resolved later.
                     self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
                 }
             }
@@ -1688,30 +1740,30 @@ impl<'a> LayerRectCalculationAndCullingP
             self.rebuild_clip_info_stack_if_necessary(clip_and_scroll.clip_node_id());
         if clip_bounds.map_or(false, |bounds| bounds.is_empty()) {
             return;
         }
 
         let stacking_context =
             &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
         let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
-        let auxiliary_lists = self.auxiliary_lists_map.get(&pipeline_id)
-                                                      .expect("No auxiliary lists?");
+        let display_list = self.display_lists.get(&pipeline_id)
+                                             .expect("No display list?");
         for i in 0..prim_count {
             let prim_index = PrimitiveIndex(prim_index.0 + i);
             if self.frame_builder.prim_store.build_bounding_rect(prim_index,
                                                                  self.screen_rect,
                                                                  &packed_layer.transform,
                                                                  &packed_layer.local_clip_rect,
                                                                  self.device_pixel_ratio) {
                 if self.frame_builder.prim_store.prepare_prim_for_render(prim_index,
                                                                          self.resource_cache,
                                                                          &packed_layer.transform,
                                                                          self.device_pixel_ratio,
-                                                                         auxiliary_lists) {
+                                                                         display_list) {
                     self.frame_builder.prim_store.build_bounding_rect(prim_index,
                                                                       self.screen_rect,
                                                                       &packed_layer.transform,
                                                                       &packed_layer.local_clip_rect,
                                                                       self.device_pixel_ratio);
                 }
 
                 // If the primitive is visible, consider culling it via clip rect(s).
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -56,16 +56,17 @@ mod ellipse;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod gpu_store;
 mod internal_types;
 mod mask_cache;
 mod prim_store;
+mod print_tree;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
 mod resource_cache;
 mod scene;
 mod spring;
 mod texture_cache;
@@ -127,16 +128,16 @@ extern crate fnv;
 extern crate gleam;
 extern crate num_traits;
 //extern crate notify;
 extern crate time;
 extern crate webrender_traits;
 #[cfg(feature = "webgl")]
 extern crate offscreen_gl_context;
 extern crate byteorder;
-extern crate threadpool;
+extern crate rayon;
 extern crate plane_split;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
 pub use renderer::{Renderer, RendererOptions};
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use border::BorderCornerClipSource;
 use gpu_store::GpuStoreAddress;
 use prim_store::{ClipData, GpuBlock32, PrimitiveStore};
 use prim_store::{CLIP_DATA_GPU_SIZE, MASK_DATA_GPU_SIZE};
 use renderer::VertexDataStore;
 use util::{ComplexClipRegionHelpers, MatrixHelpers, TransformedRect};
-use webrender_traits::{AuxiliaryLists, BorderRadius, ClipRegion, ComplexClipRegion, ImageMask};
+use webrender_traits::{BorderRadius, BuiltDisplayList, ClipRegion, ComplexClipRegion, ImageMask};
 use webrender_traits::{DeviceIntRect, LayerToWorldTransform};
 use webrender_traits::{LayerRect, LayerPoint, LayerSize};
 use std::ops::Not;
 
 const MAX_CLIP: f32 = 1000000.0;
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq)]
@@ -149,26 +149,25 @@ impl MaskCacheInfo {
                 ClipSource::Complex(..) => {
                     complex_clip_count += 1;
                 }
                 ClipSource::Region(ref region, region_mode) => {
                     if let Some(info) = region.image_mask {
                         debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
                         image = Some((info, clip_store.alloc(MASK_DATA_GPU_SIZE)));
                     }
-
-                    complex_clip_count += region.complex.length;
+                    complex_clip_count += region.complex_clip_count;
                     if region_mode == RegionMode::IncludeRect {
                         complex_clip_count += 1;
                     }
                 }
                 ClipSource::BorderCorner(ref source) => {
                     // One block for the corner header, plus one
                     // block per dash to clip out.
-                    let gpu_address = clip_store.alloc(1 + source.dash_count);
+                    let gpu_address = clip_store.alloc(1 + source.max_clip_count);
                     border_corners.push((source.clone(), gpu_address));
                 }
             }
         }
 
         let complex_clip_range = ClipAddressRange {
             start: if complex_clip_count > 0 {
                 clip_store.alloc(CLIP_DATA_GPU_SIZE * complex_clip_count)
@@ -188,18 +187,18 @@ impl MaskCacheInfo {
         })
     }
 
     pub fn update(&mut self,
                   sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
                   clip_store: &mut VertexDataStore<GpuBlock32>,
                   device_pixel_ratio: f32,
-                  aux_lists: &AuxiliaryLists) {
-        let is_aligned = transform.can_losslessly_transform_and_perspective_project_a_2d_rect();
+                  display_list: &BuiltDisplayList) {
+        let is_aligned = transform.preserves_2d_axis_alignment();
 
         // If we haven't cached this info, or if the transform type has changed
         // we need to re-calculate the number of clips.
         if self.bounds.is_none() || self.is_aligned != is_aligned {
             let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
                                                      LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
             let mut local_inner: Option<LayerRect> = None;
             let mut has_clip_out = false;
@@ -233,48 +232,48 @@ impl MaskCacheInfo {
                             Some(ref mask) if !mask.repeat => {
                                 local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
                                 None
                             },
                             Some(_) => None,
                             None => local_rect,
                         };
 
-                        let clips = aux_lists.complex_clip_regions(&region.complex);
+                        let clips = display_list.get(region.complex_clips);
                         if !self.is_aligned && region_mode == RegionMode::IncludeRect {
                             // we have an extra clip rect coming from the transformed layer
                             debug_assert!(self.effective_complex_clip_count < self.complex_clip_range.item_count);
                             let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE;
                             self.effective_complex_clip_count += 1;
 
                             let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE);
                             PrimitiveStore::populate_clip_data(slice, ClipData::uniform(region.main, 0.0, ClipMode::Clip));
                         }
 
                         debug_assert!(self.effective_complex_clip_count + clips.len() <= self.complex_clip_range.item_count);
                         let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE;
                         self.effective_complex_clip_count += clips.len();
 
                         let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE * clips.len());
-                        for (clip, chunk) in clips.iter().zip(slice.chunks_mut(CLIP_DATA_GPU_SIZE)) {
-                            let data = ClipData::from_clip_region(clip);
+                        for (clip, chunk) in clips.zip(slice.chunks_mut(CLIP_DATA_GPU_SIZE)) {
+                            let data = ClipData::from_clip_region(&clip);
                             PrimitiveStore::populate_clip_data(chunk, data);
                             local_rect = local_rect.and_then(|r| r.intersection(&clip.rect));
                             local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
                                                                        .and_then(|ref inner| r.intersection(inner)));
                         }
                     }
                     ClipSource::BorderCorner{..} => {}
                 }
             }
 
-            for &(ref source, gpu_address) in &self.border_corners {
+            for &mut (ref mut source, gpu_address) in &mut self.border_corners {
                 has_border_clip = true;
                 let slice = clip_store.get_slice_mut(gpu_address,
-                                                     1 + source.dash_count);
+                                                     1 + source.max_clip_count);
                 source.populate_gpu_data(slice);
             }
 
             // Work out the type of mask geometry we have, based on the
             // list of clip sources above.
             if has_clip_out || has_border_clip {
                 // For clip-out, the mask rect is not known.
                 self.bounds = Some(MaskBounds::None);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -239,148 +239,147 @@ impl FontContext {
         }
     }
 
     pub fn rasterize_glyph(&mut self,
                            key: &GlyphKey,
                            render_mode: FontRenderMode,
                            _glyph_options: Option<GlyphOptions>)
                            -> Option<RasterizedGlyph> {
-        match self.get_ct_font(key.font_key, key.size) {
-            Some(ref ct_font) => {
-                let glyph = key.index as CGGlyph;
-                let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
-                if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
-                    return Some(RasterizedGlyph::blank())
-                }
+
+        let ct_font = match self.get_ct_font(key.font_key, key.size) {
+            Some(font) => font,
+            None => return Some(RasterizedGlyph::blank())
+        };
+
+        let glyph = key.index as CGGlyph;
+        let metrics = get_glyph_metrics(&ct_font, glyph, &key.subpixel_point);
+        if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
+            return Some(RasterizedGlyph::blank())
+        }
 
-                let context_flags = match render_mode {
-                    FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
-                    FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
-                };
+        let context_flags = match render_mode {
+            FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
+            FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
+        };
 
-                let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
-                                                                      metrics.rasterized_height as usize,
-                                                                      8,
-                                                                      metrics.rasterized_width as usize * 4,
-                                                                      &CGColorSpace::create_device_rgb(),
-                                                                      context_flags);
+        let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
+                                                              metrics.rasterized_height as usize,
+                                                              8,
+                                                              metrics.rasterized_width as usize * 4,
+                                                              &CGColorSpace::create_device_rgb(),
+                                                              context_flags);
 
 
-                // Tested on mac OS Sierra, 10.12
-                // For Mono + alpha, the only values that matter are the alpha values.
-                // For subpixel, we need each individual rgb channel.
-                // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
-                // 1) black text on white opaque background
-                // 2) white text on black opaque background
-                // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
-                // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
-                //
-                // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
-                // 1) black text on transparent white - only alpha values filled
-                // 2) white text on transparent black - channels == alpha
-                //
-                // If we draw grayscale/mono on an opaque background
-                // the RGB channels are the alpha values from transparent backgrounds
-                // with the alpha set as opaque.
-                // At the end of all this, WR expects individual RGB channels and ignores alpha
-                // for subpixel AA.
-                // For alpha/mono, WR ignores all channels other than alpha.
-                // Also note that WR expects text to be black bg with white text, so invert
-                // when we draw the glyphs.
-                let (antialias, smooth) = match render_mode {
-                    FontRenderMode::Subpixel => (true, true),
-                    FontRenderMode::Alpha => (true, false),
-                    FontRenderMode::Mono => (false, false),
-                };
+        // Tested on mac OS Sierra, 10.12
+        // For Mono + alpha, the only values that matter are the alpha values.
+        // For subpixel, we need each individual rgb channel.
+        // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
+        // 1) black text on white opaque background
+        // 2) white text on black opaque background
+        // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
+        // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
+        //
+        // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
+        // 1) black text on transparent white - only alpha values filled
+        // 2) white text on transparent black - channels == alpha
+        //
+        // If we draw grayscale/mono on an opaque background
+        // the RGB channels are the alpha values from transparent backgrounds
+        // with the alpha set as opaque.
+        // At the end of all this, WR expects individual RGB channels and ignores alpha
+        // for subpixel AA.
+        // For alpha/mono, WR ignores all channels other than alpha.
+        // Also note that WR expects text to be black bg with white text, so invert
+        // when we draw the glyphs.
+        let (antialias, smooth) = match render_mode {
+            FontRenderMode::Subpixel => (true, true),
+            FontRenderMode::Alpha => (true, false),
+            FontRenderMode::Mono => (false, false),
+        };
 
-                // These are always true in Gecko, even for non-AA fonts
-                cg_context.set_allows_font_subpixel_positioning(true);
-                cg_context.set_should_subpixel_position_fonts(true);
+        // These are always true in Gecko, even for non-AA fonts
+        cg_context.set_allows_font_subpixel_positioning(true);
+        cg_context.set_should_subpixel_position_fonts(true);
 
-                // Don't quantize because we're doing it already.
-                cg_context.set_allows_font_subpixel_quantization(false);
-                cg_context.set_should_subpixel_quantize_fonts(false);
+        // Don't quantize because we're doing it already.
+        cg_context.set_allows_font_subpixel_quantization(false);
+        cg_context.set_should_subpixel_quantize_fonts(false);
 
-                cg_context.set_allows_font_smoothing(smooth);
-                cg_context.set_should_smooth_fonts(smooth);
-                cg_context.set_allows_antialiasing(antialias);
-                cg_context.set_should_antialias(antialias);
+        cg_context.set_allows_font_smoothing(smooth);
+        cg_context.set_should_smooth_fonts(smooth);
+        cg_context.set_allows_antialiasing(antialias);
+        cg_context.set_should_antialias(antialias);
 
-                let (x_offset, y_offset) = key.subpixel_point.to_f64();
+        let (x_offset, y_offset) = key.subpixel_point.to_f64();
 
-                // CG Origin is bottom left, WR is top left. Need -y offset
-                let rasterization_origin = CGPoint {
-                    x: -metrics.rasterized_left as f64 + x_offset,
-                    y: metrics.rasterized_descent as f64 - y_offset,
-                };
+        // CG Origin is bottom left, WR is top left. Need -y offset
+        let rasterization_origin = CGPoint {
+            x: -metrics.rasterized_left as f64 + x_offset,
+            y: metrics.rasterized_descent as f64 - y_offset,
+        };
 
-                // Always draw black text on a white background
-                // Fill the background
-                cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
-                let rect = CGRect {
-                    origin: CGPoint {
-                        x: 0.0,
-                        y: 0.0,
-                    },
-                    size: CGSize {
-                        width: metrics.rasterized_width as f64,
-                        height: metrics.rasterized_height as f64,
-                    }
-                };
-                cg_context.fill_rect(rect);
+        // Always draw black text on a white background
+        // Fill the background
+        cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+        let rect = CGRect {
+            origin: CGPoint {
+                x: 0.0,
+                y: 0.0,
+            },
+            size: CGSize {
+                width: metrics.rasterized_width as f64,
+                height: metrics.rasterized_height as f64,
+            }
+        };
+        cg_context.fill_rect(rect);
 
-                // Set the text color
-                cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
-                cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
-                ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
+        // Set the text color
+        cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
+        cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
+        ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
 
-                let mut rasterized_pixels = cg_context.data().to_vec();
+        let mut rasterized_pixels = cg_context.data().to_vec();
 
-                // Convert to linear space for subpixel AA.
-                // We explicitly do not do this for grayscale AA
-                if render_mode == FontRenderMode::Subpixel {
-                    self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
-                                                                       metrics.rasterized_width as usize,
-                                                                       metrics.rasterized_height as usize);
-                }
+        // Convert to linear space for subpixel AA.
+        // We explicitly do not do this for grayscale AA
+        if render_mode == FontRenderMode::Subpixel {
+            self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
+                                                               metrics.rasterized_width as usize,
+                                                               metrics.rasterized_height as usize);
+        }
 
-                // We need to invert the pixels back since right now
-                // transparent pixels are actually opaque white.
-                for i in 0..metrics.rasterized_height {
-                    let current_height = (i * metrics.rasterized_width * 4) as usize;