Bug 1494403 - Separate the Blob related apis. r=jrmuizel
authorNicolas Silva <nsilva@mozilla.com>
Fri, 23 Nov 2018 23:33:49 +0000
changeset 447896 df30b0f614c904678b72d66616a65b989f9a87e9
parent 447895 b75eb61d2048a0a0dabeb08ad390a0286358b55e
child 447897 d4db66eb83f9bae1b88fa3a900ea8159f25b41be
push id35092
push userdluca@mozilla.com
push dateSat, 24 Nov 2018 09:44:31 +0000
treeherdermozilla-central@d4db66eb83f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1494403
milestone65.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
Bug 1494403 - Separate the Blob related apis. r=jrmuizel This commit contains the Gecko-side changes from WebRender PR#3277: - Dedicated DirtyRect type. - Separate the blob image APIs from regular image ones. Differential Revision: https://phabricator.services.mozilla.com/D12463
gfx/layers/ipc/WebRenderMessages.ipdlh
gfx/layers/wr/IpcResourceUpdateQueue.cpp
gfx/layers/wr/IpcResourceUpdateQueue.h
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderCommandBuilder.cpp
gfx/layers/wr/WebRenderLayerManager.cpp
gfx/layers/wr/WebRenderLayerManager.h
gfx/layers/wr/WebRenderMessageUtils.h
gfx/layers/wr/WebRenderUserData.cpp
gfx/layers/wr/WebRenderUserData.h
gfx/webrender_bindings/Moz2DImageRenderer.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/WebRenderTypes.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/src/moz2d_renderer.rs
gfx/webrender_bindings/webrender_ffi.h
gfx/webrender_bindings/webrender_ffi_generated.h
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -17,16 +17,17 @@ using mozilla::wr::ImageDescriptor from 
 using mozilla::wr::ImageRendering from "mozilla/webrender/webrender_ffi.h";
 using mozilla::wr::MixBlendMode from "mozilla/webrender/webrender_ffi.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::MaybeFontInstanceOptions from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::BlobImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h";
 using mozilla::LayoutDeviceRect from "Units.h";
 using mozilla::ImageIntRect from "Units.h";
 using mozilla::gfx::Rect from "mozilla/gfx/Rect.h";
 using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
@@ -112,47 +113,51 @@ struct OpAddImage {
   uint16_t tiling;
   ImageKey key;
 };
 
 struct OpAddBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   uint16_t tiling;
-  ImageKey key;
+  BlobImageKey key;
 };
 
 struct OpUpdateImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
   ImageKey key;
 };
 
 struct OpUpdateBlobImage {
   ImageDescriptor descriptor;
   OffsetRange bytes;
-  ImageKey key;
+  BlobImageKey key;
   ImageIntRect dirtyRect;
 };
 
 struct OpSetImageVisibleArea {
   ImageIntRect area;
-  ImageKey key;
+  BlobImageKey key;
 };
 
 struct OpUpdateExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
   ImageIntRect dirtyRect;
 };
 
 struct OpDeleteImage {
   ImageKey key;
 };
 
+struct OpDeleteBlobImage {
+  BlobImageKey key;
+};
+
 struct OpAddRawFont {
   OffsetRange bytes;
   uint32_t fontIndex;
   FontKey key;
 };
 
 struct OpAddFontDescriptor {
   OffsetRange bytes;
@@ -179,16 +184,17 @@ struct OpDeleteFontInstance {
 
 union OpUpdateResource {
   OpAddImage;
   OpAddBlobImage;
   OpUpdateImage;
   OpUpdateBlobImage;
   OpSetImageVisibleArea;
   OpDeleteImage;
+  OpDeleteBlobImage;
   OpAddRawFont;
   OpAddFontDescriptor;
   OpDeleteFont;
   OpAddFontInstance;
   OpDeleteFontInstance;
   OpAddExternalImage;
   OpPushExternalImageForTexture;
   OpUpdateExternalImage;
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -295,17 +295,17 @@ IpcResourceUpdateQueue::AddImage(ImageKe
   if (!bytes.length()) {
     return false;
   }
   mUpdates.AppendElement(layers::OpAddImage(aDescriptor, bytes, 0, key));
   return true;
 }
 
 bool
-IpcResourceUpdateQueue::AddBlobImage(ImageKey key, const ImageDescriptor& aDescriptor,
+IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor,
                                      Range<uint8_t> aBytes)
 {
   MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0);
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
   mUpdates.AppendElement(layers::OpAddBlobImage(aDescriptor, bytes, 0, key));
@@ -339,17 +339,17 @@ IpcResourceUpdateQueue::UpdateImageBuffe
   if (!bytes.length()) {
     return false;
   }
   mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey));
   return true;
 }
 
 bool
-IpcResourceUpdateQueue::UpdateBlobImage(ImageKey aKey,
+IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
                                         const ImageDescriptor& aDescriptor,
                                         Range<uint8_t> aBytes,
                                         ImageIntRect aDirtyRect)
 {
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
@@ -361,28 +361,34 @@ void
 IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
                                             wr::ImageKey aKey,
                                             ImageIntRect aDirtyRect)
 {
   mUpdates.AppendElement(layers::OpUpdateExternalImage(aExtId, aKey, aDirtyRect));
 }
 
 void
-IpcResourceUpdateQueue::SetImageVisibleArea(ImageKey aKey,
-                                            const ImageIntRect& aArea)
+IpcResourceUpdateQueue::SetBlobImageVisibleArea(wr::BlobImageKey aKey,
+                                                const ImageIntRect& aArea)
 {
   mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
 }
 
 void
 IpcResourceUpdateQueue::DeleteImage(ImageKey aKey)
 {
   mUpdates.AppendElement(layers::OpDeleteImage(aKey));
 }
 
+void
+IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey)
+{
+  mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey));
+}
+
 bool
 IpcResourceUpdateQueue::AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex)
 {
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
   mUpdates.AppendElement(layers::OpAddRawFont(bytes, aIndex, aKey));
--- a/gfx/layers/wr/IpcResourceUpdateQueue.h
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.h
@@ -86,44 +86,46 @@ public:
 
   IpcResourceUpdateQueue(const IpcResourceUpdateQueue& aOther) = delete;
   IpcResourceUpdateQueue& operator=(const IpcResourceUpdateQueue& aOther) = delete;
 
   bool AddImage(wr::ImageKey aKey,
                 const ImageDescriptor& aDescriptor,
                 Range<uint8_t> aBytes);
 
-  bool AddBlobImage(wr::ImageKey aKey,
+  bool AddBlobImage(wr::BlobImageKey aKey,
                     const ImageDescriptor& aDescriptor,
                     Range<uint8_t> aBytes);
 
   void AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey);
 
   void PushExternalImageForTexture(wr::ExternalImageId aExtId,
                                    wr::ImageKey aKey,
                                    layers::TextureClient* aTexture,
                                    bool aIsUpdate);
 
   bool UpdateImageBuffer(wr::ImageKey aKey,
                          const ImageDescriptor& aDescriptor,
                          Range<uint8_t> aBytes);
 
-  bool UpdateBlobImage(wr::ImageKey aKey,
+  bool UpdateBlobImage(wr::BlobImageKey aKey,
                        const ImageDescriptor& aDescriptor,
                        Range<uint8_t> aBytes,
                        ImageIntRect aDirtyRect);
 
   void UpdateExternalImage(ExternalImageId aExtID,
                            ImageKey aKey,
                            ImageIntRect aDirtyRect);
 
-  void SetImageVisibleArea(ImageKey aKey, const ImageIntRect& aArea);
+  void SetBlobImageVisibleArea(BlobImageKey aKey, const ImageIntRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
+  void DeleteBlobImage(wr::BlobImageKey aKey);
+
   bool AddRawFont(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
 
   bool AddFontDescriptor(wr::FontKey aKey, Range<uint8_t> aBytes, uint32_t aIndex);
 
   void DeleteFont(wr::FontKey aKey);
 
   void AddFontInstance(wr::FontInstanceKey aKey,
                        wr::FontKey aFontKey,
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -456,17 +456,17 @@ WebRenderBridgeParent::UpdateResources(c
         break;
       }
       case OpUpdateResource::TOpUpdateBlobImage: {
         const auto& op = cmd.get_OpUpdateBlobImage();
         wr::Vec<uint8_t> bytes;
         if (!reader.Read(op.bytes(), bytes)) {
           return false;
         }
-        aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToDeviceIntRect(op.dirtyRect()));
+        aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, wr::ToLayoutIntRect(op.dirtyRect()));
         break;
       }
       case OpUpdateResource::TOpSetImageVisibleArea: {
         const auto& op = cmd.get_OpSetImageVisibleArea();
         wr::DeviceIntRect area;
         area.origin.x = op.area().x;
         area.origin.y = op.area().y;
         area.size.width = op.area().width;
@@ -529,16 +529,21 @@ WebRenderBridgeParent::UpdateResources(c
                                  variations);
         break;
       }
       case OpUpdateResource::TOpDeleteImage: {
         const auto& op = cmd.get_OpDeleteImage();
         DeleteImage(op.key(), aUpdates);
         break;
       }
+      case OpUpdateResource::TOpDeleteBlobImage: {
+        const auto& op = cmd.get_OpDeleteBlobImage();
+        aUpdates.DeleteBlobImage(op.key());
+        break;
+      }
       case OpUpdateResource::TOpDeleteFont: {
         const auto& op = cmd.get_OpDeleteFont();
         aUpdates.DeleteFont(op.key());
         break;
       }
       case OpUpdateResource::TOpDeleteFontInstance: {
         const auto& op = cmd.get_OpDeleteFontInstance();
         aUpdates.DeleteFontInstance(op.key());
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -336,17 +336,17 @@ struct DIGroup
   int32_t mAppUnitsPerDevPixel;
   gfx::Size mScale;
   ScrollableLayerGuid::ViewID mScrollId;
   LayerPoint mResidualOffset;
   LayerIntRect mLayerBounds;
   // The current bounds of the blob image, relative to
   // the top-left of the mLayerBounds.
   IntRect mImageBounds;
-  Maybe<wr::ImageKey> mKey;
+  Maybe<wr::BlobImageKey> mKey;
   std::vector<RefPtr<SourceSurface>> mExternalSurfaces;
   std::vector<RefPtr<ScaledFont>> mFonts;
 
   DIGroup()
     : mAppUnitsPerDevPixel(0)
     , mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID)
   {
   }
@@ -373,17 +373,17 @@ struct DIGroup
       delete data;
     }
   }
 
   void ClearImageKey(WebRenderLayerManager* aManager, bool aForce = false)
   {
     if (mKey) {
       MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty());
-      aManager->AddImageKeyForDiscard(mKey.value());
+      aManager->AddBlobImageKeyForDiscard(mKey.value());
       mKey = Nothing();
     }
     mFonts.clear();
   }
 
   static IntRect
   ToDeviceSpace(nsRect aBounds, Matrix& aMatrix, int32_t aAppUnitsPerDevPixel, LayerIntPoint aOffset)
   {
@@ -629,17 +629,17 @@ struct DIGroup
     LayerIntRect layerBounds = mLayerBounds;
     IntSize dtSize = layerBounds.Size().ToUnknownSize();
     LayoutDeviceRect bounds = (LayerRect(layerBounds) - mResidualOffset) / scale;
 
     if (mInvalidRect.IsEmpty()) {
       GP("Not repainting group because it's empty\n");
       GP("End EndGroup\n");
       if (mKey) {
-        aResources.SetImageVisibleArea(
+        aResources.SetBlobImageVisibleArea(
           mKey.value(),
           ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
         PushImage(aBuilder, bounds);
       }
       return;
     }
 
     gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
@@ -683,18 +683,18 @@ struct DIGroup
 
     TakeExternalSurfaces(recorder, mExternalSurfaces, aWrManager, aResources);
     bool hasItems = recorder->Finish();
     GP("%d Finish\n", hasItems);
     Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
     if (!mKey) {
       if (!hasItems) // we don't want to send a new image that doesn't have any items in it
         return;
-      wr::ImageKey key = aWrManager->WrBridge()->GetNextImageKey();
-      GP("No previous key making new one %d\n", key.mHandle);
+      wr::BlobImageKey key = wr::BlobImageKey { aWrManager->WrBridge()->GetNextImageKey() };
+      GP("No previous key making new one %d\n", key._0.mHandle);
       wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
       MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
       if (!aResources.AddBlobImage(key, descriptor, bytes)) {
         return;
       }
       mKey = Some(key);
     } else {
       wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
@@ -703,17 +703,17 @@ struct DIGroup
       MOZ_RELEASE_ASSERT(bottomRight.x <= dtSize.width && bottomRight.y <= dtSize.height);
       GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
       if (!aResources.UpdateBlobImage(mKey.value(), descriptor, bytes, ViewAs<ImagePixel>(mInvalidRect))) {
         return;
       }
     }
     mFonts = std::move(fonts);
     mInvalidRect.SetEmpty();
-    aResources.SetImageVisibleArea(
+    aResources.SetBlobImageVisibleArea(
       mKey.value(),
       ViewAs<ImagePixel>(mPaintRect, PixelCastJustification::LayerIsImage));
     PushImage(aBuilder, bounds);
     GP("End EndGroup\n\n");
   }
 
   void PushImage(wr::DisplayListBuilder& aBuilder, const LayoutDeviceRect& bounds)
   {
@@ -728,17 +728,17 @@ struct DIGroup
 
     // XXX - clipping the item against the paint rect breaks some content.
     // cf. Bug 1455422.
     //wr::LayoutRect clip = wr::ToLayoutRect(bounds.Intersect(mPaintRect));
 
     aBuilder.SetHitTestInfo(mScrollId, hitInfo);
     aBuilder.PushImage(dest, dest, !backfaceHidden,
                        wr::ToImageRendering(sampleFilter),
-                       mKey.value());
+                       wr::AsImageKey(mKey.value()));
     aBuilder.ClearHitTestInfo();
   }
 
   void PaintItemRange(Grouper* aGrouper,
                       nsDisplayItem* aStartItem,
                       nsDisplayItem* aEndItem,
                       gfxContext* aContext,
                       WebRenderDrawEventRecorder* aRecorder) {
@@ -1597,23 +1597,23 @@ WebRenderCommandBuilder::PushOverrideFor
 void
 WebRenderCommandBuilder::PopOverrideForASR(const ActiveScrolledRoot* aASR)
 {
   mClipManager.PopOverrideForASR(aASR);
 }
 
 Maybe<wr::ImageKey>
 WebRenderCommandBuilder::CreateImageKey(nsDisplayItem* aItem,
-                                        ImageContainer* aContainer,
-                                        mozilla::wr::DisplayListBuilder& aBuilder,
-                                        mozilla::wr::IpcResourceUpdateQueue& aResources,
-                                        mozilla::wr::ImageRendering aRendering,
-                                        const StackingContextHelper& aSc,
-                                        gfx::IntSize& aSize,
-                                        const Maybe<LayoutDeviceRect>& aAsyncImageBounds)
+                                            ImageContainer* aContainer,
+                                            mozilla::wr::DisplayListBuilder& aBuilder,
+                                            mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                            mozilla::wr::ImageRendering aRendering,
+                                            const StackingContextHelper& aSc,
+                                            gfx::IntSize& aSize,
+                                            const Maybe<LayoutDeviceRect>& aAsyncImageBounds)
 {
   RefPtr<WebRenderImageData> imageData = CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
   MOZ_ASSERT(imageData);
 
   if (aContainer->IsAsync()) {
     MOZ_ASSERT(aAsyncImageBounds);
 
     LayoutDeviceRect rect = aAsyncImageBounds.value();
@@ -1948,17 +1948,17 @@ WebRenderCommandBuilder::GenerateFallbac
       if (!lastBounds.IsEqualInterior(paintBounds)) {
         invalidRegion.OrWith(lastBounds);
         invalidRegion.OrWith(paintBounds);
       }
     }
     needPaint = !invalidRegion.IsEmpty();
   }
 
-  if (needPaint || !fallbackData->GetKey()) {
+  if (needPaint || !fallbackData->GetImageKey()) {
     nsAutoPtr<nsDisplayItemGeometry> newGeometry;
     newGeometry = aItem->AllocateGeometry(aDisplayListBuilder);
     fallbackData->SetGeometry(std::move(newGeometry));
 
     gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
                                                       gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
     if (useBlobImage) {
       bool snapped;
@@ -1991,27 +1991,27 @@ WebRenderCommandBuilder::GenerateFallbac
       bool isInvalidated = PaintItemByDrawTarget(aItem, dt, offset, aDisplayListBuilder,
                                                  fallbackData->mBasicLayerManager, scale, highlight);
       recorder->FlushItem(IntRect({ 0, 0 }, dtSize.ToUnknownSize()));
       TakeExternalSurfaces(recorder, fallbackData->mExternalSurfaces, mManager, aResources);
       recorder->Finish();
 
       if (isInvalidated) {
         Range<uint8_t> bytes((uint8_t *)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
-        wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
+        wr::BlobImageKey key = wr::BlobImageKey { mManager->WrBridge()->GetNextImageKey() };
         wr::ImageDescriptor descriptor(dtSize.ToUnknownSize(), 0, dt->GetFormat(), opacity);
         if (!aResources.AddBlobImage(key, descriptor, bytes)) {
           return nullptr;
         }
-        fallbackData->SetKey(key);
+        fallbackData->SetBlobImageKey(key);
         fallbackData->SetFonts(fonts);
       } else {
         // If there is no invalidation region and we don't have a image key,
         // it means we don't need to push image for the item.
-        if (!fallbackData->GetKey().isSome()) {
+        if (!fallbackData->GetBlobImageKey().isSome()) {
           return nullptr;
         }
       }
     } else {
       fallbackData->CreateImageClientIfNeeded();
       RefPtr<ImageClient> imageClient = fallbackData->GetImageClient();
       RefPtr<ImageContainer> imageContainer = LayerManager::CreateImageContainer();
       bool isInvalidated = false;
@@ -2035,17 +2035,17 @@ WebRenderCommandBuilder::GenerateFallbac
         if (isInvalidated) {
           // Update image if there it's invalidated.
           if (!helper.UpdateImage()) {
             return nullptr;
           }
         } else {
           // If there is no invalidation region and we don't have a image key,
           // it means we don't need to push image for the item.
-          if (!fallbackData->GetKey().isSome()) {
+          if (!fallbackData->GetImageKey().isSome()) {
             return nullptr;
           }
         }
       }
 
       // Force update the key in fallback data since we repaint the image in this path.
       // If not force update, fallbackData may reuse the original key because it
       // doesn't know UpdateImageHelper already updated the image container.
@@ -2056,17 +2056,17 @@ WebRenderCommandBuilder::GenerateFallbac
 
     fallbackData->SetScale(scale);
     fallbackData->SetInvalid(false);
   }
 
   // Update current bounds to fallback data
   fallbackData->SetBounds(paintBounds);
 
-  MOZ_ASSERT(fallbackData->GetKey());
+  MOZ_ASSERT(fallbackData->GetImageKey());
 
   return fallbackData.forget();
 }
 
 Maybe<wr::WrImageMask>
 WebRenderCommandBuilder::BuildWrMaskImage(nsDisplayItem* aItem,
                                           wr::DisplayListBuilder& aBuilder,
                                           wr::IpcResourceUpdateQueue& aResources,
@@ -2078,17 +2078,17 @@ WebRenderCommandBuilder::BuildWrMaskImag
   RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(aItem, aBuilder, aResources,
                                                                     aSc, aDisplayListBuilder,
                                                                     imageRect);
   if (!fallbackData) {
     return Nothing();
   }
 
   wr::WrImageMask imageMask;
-  imageMask.image = fallbackData->GetKey().value();
+  imageMask.image = fallbackData->GetImageKey().value();
   imageMask.rect = wr::ToRoundedLayoutRect(imageRect);
   imageMask.repeat = false;
   return Some(imageMask);
 }
 
 bool
 WebRenderCommandBuilder::PushItemAsImage(nsDisplayItem* aItem,
                                          wr::DisplayListBuilder& aBuilder,
@@ -2105,17 +2105,17 @@ WebRenderCommandBuilder::PushItemAsImage
   }
 
   wr::LayoutRect dest = wr::ToRoundedLayoutRect(imageRect);
   gfx::SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
   aBuilder.PushImage(dest,
                      dest,
                      !aItem->BackfaceIsHidden(),
                      wr::ToImageRendering(sampleFilter),
-                     fallbackData->GetKey().value());
+                     fallbackData->GetImageKey().value());
   return true;
 }
 
 void
 WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData()
 {
   for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) {
     WebRenderUserData* data = iter.Get()->GetKey();
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -103,17 +103,17 @@ WebRenderLayerManager::DoDestroy(bool aI
   if (IsDestroyed()) {
     return;
   }
 
   LayerManager::Destroy();
 
   if (WrBridge()) {
     // Just clear ImageKeys, they are deleted during WebRenderAPI destruction.
-    mImageKeysToDelete.Clear();
+    DiscardLocalImages();
     // CompositorAnimations are cleared by WebRenderBridgeParent.
     mDiscardedCompositorAnimationsIds.Clear();
     WrBridge()->Destroy(aIsSync);
   }
 
   // Clear this before calling RemoveUnusedAndResetWebRenderUserData(),
   // otherwise that function might destroy some WebRenderAnimationData instances
   // which will put stuff back into mDiscardedCompositorAnimationsIds. If
@@ -364,21 +364,17 @@ WebRenderLayerManager::EndTransactionWit
       // If we can't just swap the queue, we need to take the slow path and
       // send the update as a separate message. We don't need to schedule a
       // composite however because that will happen with EndTransaction.
       WrBridge()->UpdateResources(mAsyncResourceUpdates.ref());
     }
     mAsyncResourceUpdates.reset();
   }
 
-  for (const auto& key : mImageKeysToDelete) {
-    resourceUpdates.DeleteImage(key);
-  }
-  mImageKeysToDelete.Clear();
-
+  DiscardImagesInTransaction(resourceUpdates);
   WrBridge()->RemoveExpiredFontKeys(resourceUpdates);
 
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
@@ -478,23 +474,39 @@ WebRenderLayerManager::MakeSnapshotIfReq
 
 void
 WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
 {
   mImageKeysToDelete.AppendElement(key);
 }
 
 void
+WebRenderLayerManager::AddBlobImageKeyForDiscard(wr::BlobImageKey key)
+{
+  mBlobImageKeysToDelete.AppendElement(key);
+}
+
+void
+WebRenderLayerManager::DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResources)
+{
+  for (const auto& key : mImageKeysToDelete) {
+    aResources.DeleteImage(key);
+  }
+  for (const auto& key : mBlobImageKeysToDelete) {
+    aResources.DeleteBlobImage(key);
+  }
+  mImageKeysToDelete.Clear();
+  mBlobImageKeysToDelete.Clear();
+}
+
+void
 WebRenderLayerManager::DiscardImages()
 {
   wr::IpcResourceUpdateQueue resources(WrBridge());
-  for (const auto& key : mImageKeysToDelete) {
-    resources.DeleteImage(key);
-  }
-  mImageKeysToDelete.Clear();
+  DiscardImagesInTransaction(resources);
   WrBridge()->UpdateResources(resources);
 }
 
 void
 WebRenderLayerManager::AddActiveCompositorAnimationId(uint64_t aId)
 {
   // In layers-free mode we track the active compositor animation ids on the
   // client side so that we don't try to discard the same animation id multiple
@@ -527,16 +539,17 @@ WebRenderLayerManager::DiscardCompositor
 
 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.
   mImageKeysToDelete.Clear();
+  mBlobImageKeysToDelete.Clear();
 }
 
 void
 WebRenderLayerManager::SetLayersObserverEpoch(LayersObserverEpoch aEpoch)
 {
   if (WrBridge()->IPCOpen()) {
     WrBridge()->SendSetLayersObserverEpoch(aEpoch);
   }
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -129,17 +129,19 @@ public:
   virtual already_AddRefed<PersistentBufferProvider>
   CreatePersistentBufferProvider(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override;
 
   bool AsyncPanZoomEnabled() const override;
 
   // adds an imagekey to a list of keys that will be discarded on the next
   // transaction or destruction
   void AddImageKeyForDiscard(wr::ImageKey);
+  void AddBlobImageKeyForDiscard(wr::BlobImageKey);
   void DiscardImages();
+  void DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResourceUpdates);
   void DiscardLocalImages();
 
   wr::IpcResourceUpdateQueue& AsyncResourceUpdates();
   void FlushAsyncResourceUpdates();
 
   void RegisterAsyncAnimation(const wr::ImageKey& aKey, SharedSurfacesAnimation* aAnimation);
   void DeregisterAsyncAnimation(const wr::ImageKey& aKey);
   void ClearAsyncAnimations();
@@ -184,16 +186,17 @@ private:
    * Take a snapshot of the parent context, and copy
    * it into mTarget.
    */
   void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize);
 
 private:
   nsIWidget* MOZ_NON_OWNING_REF mWidget;
   nsTArray<wr::ImageKey> mImageKeysToDelete;
+  nsTArray<wr::BlobImageKey> mBlobImageKeysToDelete;
 
   // Set of compositor animation ids for which there are active animations (as
   // of the last transaction) on the compositor side.
   std::unordered_set<uint64_t> mActiveCompositorAnimationIds;
   // Compositor animation ids for animations that are done now and that we want
   // the compositor to discard information for.
   nsTArray<uint64_t> mDiscardedCompositorAnimationsIds;
 
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -71,16 +71,22 @@ struct ParamTraits<mozilla::wr::IdNamesp
 
 template<>
 struct ParamTraits<mozilla::wr::ImageKey>
   : public PlainOldDataSerializer<mozilla::wr::ImageKey>
 {
 };
 
 template<>
+struct ParamTraits<mozilla::wr::BlobImageKey>
+  : public PlainOldDataSerializer<mozilla::wr::BlobImageKey>
+{
+};
+
+template<>
 struct ParamTraits<mozilla::wr::FontKey>
   : public PlainOldDataSerializer<mozilla::wr::FontKey>
 {
 };
 
 template<>
 struct ParamTraits<mozilla::wr::FontInstanceKey>
   : public PlainOldDataSerializer<mozilla::wr::FontInstanceKey>
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -212,25 +212,16 @@ WebRenderImageData::UpdateImageKey(Image
   }
 
   mTextureOfImage = currentTexture;
   mOwnsKey = true;
 
   return mKey;
 }
 
-void
-WebRenderImageData::SetKey(const wr::ImageKey& aKey)
-{
-  MOZ_ASSERT_IF(mKey, mKey.value() != aKey);
-  ClearImageKey();
-  mKey = Some(aKey);
-  mOwnsKey = true;
-}
-
 already_AddRefed<ImageClient>
 WebRenderImageData::GetImageClient()
 {
   RefPtr<ImageClient> imageClient = mImageClient;
   return imageClient.forget();
 }
 
 void
@@ -314,16 +305,45 @@ WebRenderFallbackData::GetGeometry()
 }
 
 void
 WebRenderFallbackData::SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
 {
   mGeometry = aGeometry;
 }
 
+void
+WebRenderFallbackData::SetBlobImageKey(const wr::BlobImageKey& aKey)
+{
+  ClearImageKey();
+  mBlobKey = Some(aKey);
+  mOwnsKey = true;
+}
+
+Maybe<wr::ImageKey>
+WebRenderFallbackData::GetImageKey()
+{
+  if (mBlobKey) {
+    return Some(wr::AsImageKey(mBlobKey.value()));
+  }
+
+  return mKey;
+}
+
+void
+WebRenderFallbackData::ClearImageKey()
+{
+  if (mBlobKey && mOwnsKey) {
+    mWRManager->AddBlobImageKeyForDiscard(mBlobKey.value());
+  }
+  mBlobKey.reset();
+
+  WebRenderImageData::ClearImageKey();
+}
+
 WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
   : WebRenderUserData(aWRManager, aItem)
 {
 }
 
 WebRenderAnimationData::~WebRenderAnimationData()
 {
   // It may be the case that nsDisplayItem that created this WebRenderUserData
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -111,27 +111,29 @@ struct WebRenderUserDataKey {
   }
 
   uint32_t mFrameKey;
   WebRenderUserData::UserDataType mType;
 };
 
 typedef nsRefPtrHashtable<nsGenericHashKey<mozilla::layers::WebRenderUserDataKey>, WebRenderUserData> WebRenderUserDataTable;
 
+/// Holds some data used to share TextureClient/ImageClient with the parent
+/// process except if used with blob images (watch your step).
 class WebRenderImageData : public WebRenderUserData
 {
 public:
   WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderImageData();
 
   virtual WebRenderImageData* AsImageData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eImage; }
   static UserDataType Type() { return UserDataType::eImage; }
-  Maybe<wr::ImageKey> GetKey() { return mKey; }
-  void SetKey(const wr::ImageKey& aKey);
+  virtual Maybe<wr::ImageKey> GetImageKey() { return mKey; }
+  void SetImageKey(const wr::ImageKey& aKey);
   already_AddRefed<ImageClient> GetImageClient();
 
   Maybe<wr::ImageKey> UpdateImageKey(ImageContainer* aContainer,
                                      wr::IpcResourceUpdateQueue& aResources,
                                      bool aFallback = false);
 
   void CreateAsyncImageWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                          ImageContainer* aContainer,
@@ -149,26 +151,34 @@ public:
   bool IsAsync()
   {
     return mPipelineId.isSome();
   }
 
   bool IsAsyncAnimatedImage() const;
 
 protected:
-  void ClearImageKey();
+  virtual void ClearImageKey();
 
   RefPtr<TextureClient> mTextureOfImage;
   Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   Maybe<wr::PipelineId> mPipelineId;
   RefPtr<ImageContainer> mContainer;
   bool mOwnsKey;
 };
 
+/// Used for fallback rendering.
+///
+/// In most cases this uses blob images but it can also render on the content side directly into
+/// a texture.
+///
+/// TODO(nical) It would be much better to separate the two use cases into separate classes and
+/// not have the blob image related code inherit from WebRenderImageData (the current code only
+/// works if we carefully use a subset of the parent code).
 class WebRenderFallbackData : public WebRenderImageData
 {
 public:
   WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderFallbackData();
 
   virtual WebRenderFallbackData* AsFallbackData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eFallback; }
@@ -177,20 +187,26 @@ public:
   void SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
   nsRect GetBounds() { return mBounds; }
   void SetBounds(const nsRect& aRect) { mBounds = aRect; }
   void SetInvalid(bool aInvalid) { mInvalid = aInvalid; }
   void SetScale(gfx::Size aScale) { mScale = aScale; }
   gfx::Size GetScale() { return mScale; }
   bool IsInvalid() { return mInvalid; }
   void SetFonts(const std::vector<RefPtr<gfx::ScaledFont>>& aFonts) { mFonts = aFonts; }
+  Maybe<wr::BlobImageKey> GetBlobImageKey() { return mBlobKey; }
+  virtual Maybe<wr::ImageKey> GetImageKey() override;
+  void SetBlobImageKey(const wr::BlobImageKey& aKey);
 
   RefPtr<BasicLayerManager> mBasicLayerManager;
   std::vector<RefPtr<gfx::SourceSurface>> mExternalSurfaces;
 protected:
+  virtual void ClearImageKey() override;
+
+  Maybe<wr::BlobImageKey> mBlobKey;
   nsAutoPtr<nsDisplayItemGeometry> mGeometry;
   nsRect mBounds;
   bool mInvalid;
   gfx::Size mScale;
   std::vector<RefPtr<gfx::ScaledFont>> mFonts;
 };
 
 class WebRenderAnimationData : public WebRenderUserData
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -318,17 +318,17 @@ GetScaledFont(Translator* aTranslator, W
   return data.mScaledFont;
 }
 
 static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
                                 gfx::IntSize aSize,
                                 gfx::SurfaceFormat aFormat,
                                 const uint16_t *aTileSize,
                                 const mozilla::wr::TileOffset *aTileOffset,
-                                const mozilla::wr::DeviceIntRect *aDirtyRect,
+                                const mozilla::wr::LayoutIntRect *aDirtyRect,
                                 Range<uint8_t> aOutput)
 {
   AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob");
   MOZ_ASSERT(aSize.width > 0 && aSize.height > 0);
   if (aSize.width <= 0 || aSize.height <= 0) {
     return false;
   }
 
@@ -481,17 +481,17 @@ static bool Moz2DRenderCallback(const Ra
 
 extern "C" {
 
 bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
                         int32_t width, int32_t height,
                         mozilla::wr::ImageFormat aFormat,
                         const uint16_t *aTileSize,
                         const mozilla::wr::TileOffset *aTileOffset,
-                        const mozilla::wr::DeviceIntRect *aDirtyRect,
+                        const mozilla::wr::LayoutIntRect *aDirtyRect,
                         mozilla::wr::MutByteSlice output)
 {
   return mozilla::wr::Moz2DRenderCallback(mozilla::wr::ByteSliceToRange(blob),
                                           mozilla::gfx::IntSize(width, height),
                                           mozilla::wr::ImageFormatToSurfaceFormat(aFormat),
                                           aTileSize,
                                           aTileOffset,
                                           aDirtyRect,
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -631,17 +631,17 @@ TransactionBuilder::AddImage(ImageKey ke
 {
   wr_resource_updates_add_image(mTxn,
                                 key,
                                 &aDescriptor,
                                 &aBytes.inner);
 }
 
 void
-TransactionBuilder::AddBlobImage(ImageKey key, const ImageDescriptor& aDescriptor,
+TransactionBuilder::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor,
                                  wr::Vec<uint8_t>& aBytes)
 {
   wr_resource_updates_add_blob_image(mTxn,
                                      key,
                                      &aDescriptor,
                                      &aBytes.inner);
 }
 
@@ -678,20 +678,20 @@ TransactionBuilder::UpdateImageBuffer(Im
 {
   wr_resource_updates_update_image(mTxn,
                                    aKey,
                                    &aDescriptor,
                                    &aBytes.inner);
 }
 
 void
-TransactionBuilder::UpdateBlobImage(ImageKey aKey,
+TransactionBuilder::UpdateBlobImage(BlobImageKey aKey,
                                     const ImageDescriptor& aDescriptor,
                                     wr::Vec<uint8_t>& aBytes,
-                                    const wr::DeviceIntRect& aDirtyRect)
+                                    const wr::LayoutIntRect& aDirtyRect)
 {
   wr_resource_updates_update_blob_image(mTxn,
                                         aKey,
                                         &aDescriptor,
                                         &aBytes.inner,
                                         aDirtyRect);
 }
 
@@ -723,29 +723,35 @@ TransactionBuilder::UpdateExternalImageW
                                                             &aDescriptor,
                                                             aExtID,
                                                             aBufferType,
                                                             aChannelIndex,
                                                             aDirtyRect);
 }
 
 void
-TransactionBuilder::SetImageVisibleArea(ImageKey aKey,
+TransactionBuilder::SetImageVisibleArea(BlobImageKey aKey,
                                         const wr::DeviceIntRect& aArea)
 {
-  wr_resource_updates_set_image_visible_area(mTxn, aKey, &aArea);
+  wr_resource_updates_set_blob_image_visible_area(mTxn, aKey, &aArea);
 }
 
 void
 TransactionBuilder::DeleteImage(ImageKey aKey)
 {
   wr_resource_updates_delete_image(mTxn, aKey);
 }
 
 void
+TransactionBuilder::DeleteBlobImage(BlobImageKey aKey)
+{
+  wr_resource_updates_delete_blob_image(mTxn, aKey);
+}
+
+void
 TransactionBuilder::AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex)
 {
   wr_resource_updates_add_raw_font(mTxn, aKey, &aBytes.inner, aIndex);
 }
 
 void
 TransactionBuilder::AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex)
 {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -102,17 +102,17 @@ public:
                             const wr::LayoutPoint& aScrollPosition);
 
   bool IsEmpty() const;
 
   void AddImage(wr::ImageKey aKey,
                 const ImageDescriptor& aDescriptor,
                 wr::Vec<uint8_t>& aBytes);
 
-  void AddBlobImage(wr::ImageKey aKey,
+  void AddBlobImage(wr::BlobImageKey aKey,
                     const ImageDescriptor& aDescriptor,
                     wr::Vec<uint8_t>& aBytes);
 
   void AddExternalImageBuffer(ImageKey key,
                               const ImageDescriptor& aDescriptor,
                               ExternalImageId aHandle);
 
   void AddExternalImage(ImageKey key,
@@ -120,38 +120,40 @@ public:
                         ExternalImageId aExtID,
                         wr::WrExternalImageBufferType aBufferType,
                         uint8_t aChannelIndex = 0);
 
   void UpdateImageBuffer(wr::ImageKey aKey,
                          const ImageDescriptor& aDescriptor,
                          wr::Vec<uint8_t>& aBytes);
 
-  void UpdateBlobImage(wr::ImageKey aKey,
+  void UpdateBlobImage(wr::BlobImageKey aKey,
                        const ImageDescriptor& aDescriptor,
                        wr::Vec<uint8_t>& aBytes,
-                       const wr::DeviceIntRect& aDirtyRect);
+                       const wr::LayoutIntRect& aDirtyRect);
 
   void UpdateExternalImage(ImageKey aKey,
                            const ImageDescriptor& aDescriptor,
                            ExternalImageId aExtID,
                            wr::WrExternalImageBufferType aBufferType,
                            uint8_t aChannelIndex = 0);
 
   void UpdateExternalImageWithDirtyRect(ImageKey aKey,
                                         const ImageDescriptor& aDescriptor,
                                         ExternalImageId aExtID,
                                         wr::WrExternalImageBufferType aBufferType,
                                         const wr::DeviceIntRect& aDirtyRect,
                                         uint8_t aChannelIndex = 0);
 
-  void SetImageVisibleArea(ImageKey aKey, const wr::DeviceIntRect& aArea);
+  void SetImageVisibleArea(BlobImageKey aKey, const wr::DeviceIntRect& aArea);
 
   void DeleteImage(wr::ImageKey aKey);
 
+  void DeleteBlobImage(wr::BlobImageKey aKey);
+
   void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
 
   void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
 
   void DeleteFont(wr::FontKey aKey);
 
   void AddFontInstance(wr::FontInstanceKey aKey,
                        wr::FontKey aFontKey,
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -358,16 +358,27 @@ static inline wr::DeviceIntRect ToDevice
   wr::DeviceIntRect r;
   r.origin.x = rect.X();
   r.origin.y = rect.Y();
   r.size.width = rect.Width();
   r.size.height = rect.Height();
   return r;
 }
 
+// TODO: should be const LayoutDeviceIntRect instead of ImageIntRect
+static inline wr::LayoutIntRect ToLayoutIntRect(const mozilla::ImageIntRect& rect)
+{
+  wr::LayoutIntRect r;
+  r.origin.x = rect.X();
+  r.origin.y = rect.Y();
+  r.size.width = rect.Width();
+  r.size.height = rect.Height();
+  return r;
+}
+
 static inline wr::LayoutRect ToLayoutRect(const mozilla::LayoutDeviceIntRect& rect)
 {
   return ToLayoutRect(IntRectToRect(rect));
 }
 
 static inline wr::LayoutRect ToRoundedLayoutRect(const mozilla::LayoutDeviceRect& aRect) {
   auto rect = aRect;
   rect.Round();
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1415,24 +1415,24 @@ pub extern "C" fn wr_resource_updates_ad
         ImageData::new(bytes.flush_into_vec()),
         None
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_add_blob_image(
     txn: &mut Transaction,
-    image_key: WrImageKey,
+    image_key: BlobImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
 ) {
-    txn.add_image(
+    txn.add_blob_image(
         image_key,
         descriptor.into(),
-        ImageData::new_blob_image(bytes.flush_into_vec()),
+        Arc::new(bytes.flush_into_vec()),
         if descriptor.format == ImageFormat::BGRA8 { Some(256) } else { None }
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_add_external_image(
     txn: &mut Transaction,
     image_key: WrImageKey,
@@ -1461,27 +1461,27 @@ pub extern "C" fn wr_resource_updates_up
     key: WrImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
 ) {
     txn.update_image(
         key,
         descriptor.into(),
         ImageData::new(bytes.flush_into_vec()),
-        None
+        &DirtyRect::All,
     );
 }
 
 #[no_mangle]
-pub extern "C" fn wr_resource_updates_set_image_visible_area(
+pub extern "C" fn wr_resource_updates_set_blob_image_visible_area(
     txn: &mut Transaction,
-    key: WrImageKey,
+    key: BlobImageKey,
     area: &DeviceIntRect,
 ) {
-    txn.set_image_visible_area(key, *area);
+    txn.set_blob_image_visible_area(key, *area);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_external_image(
     txn: &mut Transaction,
     key: WrImageKey,
     descriptor: &WrImageDescriptor,
     external_image_id: WrExternalImageId,
@@ -1493,17 +1493,17 @@ pub extern "C" fn wr_resource_updates_up
         descriptor.into(),
         ImageData::External(
             ExternalImageData {
                 id: external_image_id.into(),
                 channel_index,
                 image_type: image_type.to_wr(),
             }
         ),
-        None
+        &DirtyRect::All,
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_external_image_with_dirty_rect(
     txn: &mut Transaction,
     key: WrImageKey,
     descriptor: &WrImageDescriptor,
@@ -1517,45 +1517,53 @@ pub extern "C" fn wr_resource_updates_up
         descriptor.into(),
         ImageData::External(
             ExternalImageData {
                 id: external_image_id.into(),
                 channel_index,
                 image_type: image_type.to_wr(),
             }
         ),
-        Some(dirty_rect)
+        &DirtyRect::Partial(dirty_rect)
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_update_blob_image(
     txn: &mut Transaction,
-    image_key: WrImageKey,
+    image_key: BlobImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
-    dirty_rect: DeviceIntRect,
+    dirty_rect: LayoutIntRect,
 ) {
-    txn.update_image(
+    txn.update_blob_image(
         image_key,
         descriptor.into(),
-        ImageData::new_blob_image(bytes.flush_into_vec()),
-        Some(dirty_rect)
+        Arc::new(bytes.flush_into_vec()),
+        &DirtyRect::Partial(dirty_rect)
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_resource_updates_delete_image(
     txn: &mut Transaction,
     key: WrImageKey
 ) {
     txn.delete_image(key);
 }
 
 #[no_mangle]
+pub extern "C" fn wr_resource_updates_delete_blob_image(
+    txn: &mut Transaction,
+    key: BlobImageKey
+) {
+    txn.delete_blob_image(key);
+}
+
+#[no_mangle]
 pub extern "C" fn wr_api_send_transaction(
     dh: &mut DocumentHandle,
     transaction: &mut Transaction,
     is_async: bool
 ) {
     if transaction.is_empty() {
         return;
     }
@@ -2675,17 +2683,17 @@ pub unsafe extern "C" fn wr_dec_ref_arc(
 extern "C" {
      // TODO: figure out the API for tiled blob images.
      pub fn wr_moz2d_render_cb(blob: ByteSlice,
                                width: i32,
                                height: i32,
                                format: ImageFormat,
                                tile_size: Option<&u16>,
                                tile_offset: Option<&TileOffset>,
-                               dirty_rect: Option<&DeviceIntRect>,
+                               dirty_rect: Option<&LayoutIntRect>,
                                output: MutByteSlice)
                                -> bool;
 }
 
 #[no_mangle]
 pub extern "C" fn wr_root_scroll_node_id() -> usize {
     // The PipelineId doesn't matter here, since we just want the numeric part of the id
     // produced for any given root reference frame.
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -14,16 +14,17 @@ use rayon::prelude::*;
 use std::collections::hash_map::HashMap;
 use std::collections::hash_map;
 use std::collections::btree_map::BTreeMap;
 use std::collections::Bound::Included;
 use std::mem;
 use std::os::raw::{c_void, c_char};
 use std::ptr;
 use std::sync::Arc;
+use std::i32;
 use std;
 
 #[cfg(target_os = "windows")]
 use dwrote;
 
 #[cfg(target_os = "macos")]
 use foreign_types::ForeignType;
 
@@ -63,17 +64,17 @@ fn dump_index(blob: &[u8]) -> () {
     }
 }
 
 
 
 /// Handles the interpretation and rasterization of gecko-based (moz2d) WR blob images.
 pub struct Moz2dBlobImageHandler {
     workers: Arc<ThreadPool>,
-    blob_commands: HashMap<ImageKey, BlobCommand>,
+    blob_commands: HashMap<BlobImageKey, BlobCommand>,
 }
 
 /// Transmute some bytes into a value.
 ///
 /// Wow this is dangerous if non-POD values are read!
 /// FIXME: kill this with fire and/or do a super robust security audit
 unsafe fn convert_from_bytes<T>(slice: &[u8]) -> T {
     assert!(mem::size_of::<T>() <= slice.len());
@@ -266,22 +267,16 @@ impl Box2d {
     fn contained_by(&self, other: &Box2d) -> bool {
         self.x1 >= other.x1 &&
         self.x2 <= other.x2 &&
         self.y1 >= other.y1 &&
         self.y2 <= other.y2
     }
 }
 
-impl From<DeviceIntRect> for Box2d {
-    fn from(rect: DeviceIntRect) -> Self {
-        Box2d{ x1: rect.min_x(), y1: rect.min_y(), x2: rect.max_x(), y2: rect.max_y() }
-    }
-}
-
 /// Provides an API for looking up the display items in a blob image by bounds, yielding items
 /// with equal bounds in their original relative ordering.
 ///
 /// This is used to implement `merge_blobs_images`.
 ///
 /// We use a BTree as a kind of multi-map, by appending an integer "cache_order" to the key.
 /// This lets us use multiple items with matching bounds in the map and allows
 /// us to fetch and remove them while retaining the ordering of the original list.
@@ -436,26 +431,26 @@ struct BlobCommand {
     /// The size of the tiles to use in rasterization, if tiling should be used.
     tile_size: Option<TileSize>,
 }
 
  struct Job {
     request: BlobImageRequest,
     descriptor: BlobImageDescriptor,
     commands: Arc<BlobImageData>,
-    dirty_rect: Option<DeviceIntRect>,
+    dirty_rect: BlobDirtyRect,
     tile_size: Option<TileSize>,
 }
 
 /// Rasterizes gecko blob images.
 struct Moz2dBlobRasterizer {
     /// Pool of rasterizers.
     workers: Arc<ThreadPool>,
     /// Blobs to rasterize.
-    blob_commands: HashMap<ImageKey, BlobCommand>,
+    blob_commands: HashMap<BlobImageKey, BlobCommand>,
 }
 
 struct GeckoProfilerMarker {
     name: &'static [u8],
 }
 
 impl GeckoProfilerMarker {
     pub fn new(name: &'static [u8]) -> GeckoProfilerMarker {
@@ -509,71 +504,91 @@ impl AsyncBlobImageRasterizer for Moz2dB
         } else {
             requests.into_iter().map(rasterize_blob).collect()
         }
     }
 }
 
 fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
     let descriptor = job.descriptor;
-    let buf_size = (descriptor.size.width
-        * descriptor.size.height
+    let buf_size = (descriptor.rect.size.width
+        * descriptor.rect.size.height
         * descriptor.format.bytes_per_pixel()) as usize;
 
     let mut output = vec![0u8; buf_size];
 
+    let dirty_rect = match job.dirty_rect {
+        DirtyRect::Partial(rect) => Some(rect),
+        DirtyRect::All => None,
+    };
+
     let result = unsafe {
         if wr_moz2d_render_cb(
             ByteSlice::new(&job.commands[..]),
-            descriptor.size.width,
-            descriptor.size.height,
+            descriptor.rect.size.width,
+            descriptor.rect.size.height,
             descriptor.format,
             job.tile_size.as_ref(),
             job.request.tile.as_ref(),
-            job.dirty_rect.as_ref(),
+            dirty_rect.as_ref(),
             MutByteSlice::new(output.as_mut_slice()),
         ) {
+            // We want the dirty rect local to the tile rather than the whole image.
+            // TODO(nical): move that up and avoid recomupting the tile bounds in the callback
+            let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
+            let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
+            let rasterized_rect = tx.transform_rect(&dirty_rect);
+
             Ok(RasterizedBlobImage {
-                rasterized_rect: job.dirty_rect.unwrap_or(
-                    DeviceIntRect {
-                        origin: DeviceIntPoint::origin(),
-                        size: descriptor.size,
-                    }
-                ),
+                rasterized_rect,
                 data: Arc::new(output),
             })
         } else {
             panic!("Moz2D replay problem");
         }
     };
 
     (job.request, result)    
 }
 
 impl BlobImageHandler for Moz2dBlobImageHandler {
-    fn add(&mut self, key: ImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
+    fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
         {
             let index = BlobReader::new(&data);
             assert!(index.reader.has_more());
         }
         self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), tile_size });
     }
 
-    fn update(&mut self, key: ImageKey, data: Arc<BlobImageData>, dirty_rect: Option<DeviceIntRect>) {
+    fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect) {
         match self.blob_commands.entry(key) {
             hash_map::Entry::Occupied(mut e) => {
                 let command = e.get_mut();
-                command.data = Arc::new(merge_blob_images(&command.data, &data,
-                                                       dirty_rect.unwrap().into()));
+                let dirty_rect = if let DirtyRect::Partial(rect) = *dirty_rect {
+                    Box2d {
+                        x1: rect.min_x(),
+                        y1: rect.min_y(),
+                        x2: rect.max_x(),
+                        y2: rect.max_y(),
+                    }
+                } else {
+                    Box2d {
+                        x1: i32::MIN,
+                        y1: i32::MIN,
+                        x2: i32::MAX,
+                        y2: i32::MAX,
+                    }
+                };
+                command.data = Arc::new(merge_blob_images(&command.data, &data, dirty_rect));
             }
             _ => { panic!("missing image key"); }
         }
     }
 
-    fn delete(&mut self, key: ImageKey) {
+    fn delete(&mut self, key: BlobImageKey) {
         self.blob_commands.remove(&key);
     }
 
     fn create_blob_rasterizer(&mut self) -> Box<AsyncBlobImageRasterizer> {
         Box::new(Moz2dBlobRasterizer {
             workers: Arc::clone(&self.workers),
             blob_commands: self.blob_commands.clone(),
         })
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -132,9 +132,20 @@ void apz_deregister_sampler(mozilla::wr:
 #undef WR_DESTRUCTOR_SAFE_FUNC
 
 // More functions invoked from Rust code. These are down here because they
 // refer to data structures from webrender_ffi_generated.h
 extern "C" {
 void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, uint64_t aTimeNs);
 }
 
+namespace mozilla {
+namespace wr {
+
+// Cast a blob image key into a regular image for use in
+// a display item.
+inline ImageKey AsImageKey(BlobImageKey aKey) { return aKey._0; }
+
+} // namespace wr
+} // namespace mozilla
+
+
 #endif // WR_h
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -992,17 +992,17 @@ struct ByteSlice {
   bool operator==(const ByteSlice& aOther) const {
     return buffer == aOther.buffer &&
            len == aOther.len;
   }
 };
 
 using TileOffset = TypedPoint2D<int32_t, TileCoordinate>;
 
-using DeviceIntRect = TypedRect<int32_t, DevicePixel>;
+using LayoutIntRect = TypedRect<int32_t, LayoutPixel>;
 
 struct MutByteSlice {
   uint8_t *buffer;
   uintptr_t len;
 
   bool operator==(const MutByteSlice& aOther) const {
     return buffer == aOther.buffer &&
            len == aOther.len;
@@ -1068,32 +1068,45 @@ struct WrExternalImageHandler {
 
   bool operator==(const WrExternalImageHandler& aOther) const {
     return external_image_obj == aOther.external_image_obj &&
            lock_func == aOther.lock_func &&
            unlock_func == aOther.unlock_func;
   }
 };
 
+// An opaque identifier describing a blob image registered with WebRender.
+// This is used as a handle to reference blob images, and can be used as an
+// image in display items.
+struct BlobImageKey {
+  ImageKey _0;
+
+  bool operator==(const BlobImageKey& aOther) const {
+    return _0 == aOther._0;
+  }
+};
+
 struct WrImageDescriptor {
   ImageFormat format;
   int32_t width;
   int32_t height;
   int32_t stride;
   OpacityType opacity;
 
   bool operator==(const WrImageDescriptor& aOther) const {
     return format == aOther.format &&
            width == aOther.width &&
            height == aOther.height &&
            stride == aOther.stride &&
            opacity == aOther.opacity;
   }
 };
 
+using DeviceIntRect = TypedRect<int32_t, DevicePixel>;
+
 struct WrTransformProperty {
   uint64_t id;
   LayoutTransform transform;
 };
 
 struct WrOpacityProperty {
   uint64_t id;
   float opacity;
@@ -1614,17 +1627,17 @@ uintptr_t wr_dump_display_list(WrState *
 WR_FUNC;
 
 extern bool wr_moz2d_render_cb(ByteSlice aBlob,
                                int32_t aWidth,
                                int32_t aHeight,
                                ImageFormat aFormat,
                                const uint16_t *aTileSize,
                                const TileOffset *aTileOffset,
-                               const DeviceIntRect *aDirtyRect,
+                               const LayoutIntRect *aDirtyRect,
                                MutByteSlice aOutput);
 
 extern void wr_notifier_external_event(WrWindowId aWindowId,
                                        uintptr_t aRawEvent);
 
 extern void wr_notifier_new_frame_ready(WrWindowId aWindowId);
 
 extern void wr_notifier_nop_frame_done(WrWindowId aWindowId);
@@ -1690,17 +1703,17 @@ WR_FUNC;
 
 WR_INLINE
 void wr_renderer_update_program_cache(Renderer *aRenderer,
                                       WrProgramCache *aProgramCache)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_add_blob_image(Transaction *aTxn,
-                                        WrImageKey aImageKey,
+                                        BlobImageKey aImageKey,
                                         const WrImageDescriptor *aDescriptor,
                                         WrVecU8 *aBytes)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_add_external_image(Transaction *aTxn,
                                             WrImageKey aImageKey,
                                             const WrImageDescriptor *aDescriptor,
@@ -1740,42 +1753,47 @@ void wr_resource_updates_add_raw_font(Tr
                                       uint32_t aIndex)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_clear(Transaction *aTxn)
 WR_FUNC;
 
 WR_INLINE
+void wr_resource_updates_delete_blob_image(Transaction *aTxn,
+                                           BlobImageKey aKey)
+WR_FUNC;
+
+WR_INLINE
 void wr_resource_updates_delete_font(Transaction *aTxn,
                                      WrFontKey aKey)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_delete_font_instance(Transaction *aTxn,
                                               WrFontInstanceKey aKey)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_delete_image(Transaction *aTxn,
                                       WrImageKey aKey)
 WR_FUNC;
 
 WR_INLINE
-void wr_resource_updates_set_image_visible_area(Transaction *aTxn,
-                                                WrImageKey aKey,
-                                                const DeviceIntRect *aArea)
+void wr_resource_updates_set_blob_image_visible_area(Transaction *aTxn,
+                                                     BlobImageKey aKey,
+                                                     const DeviceIntRect *aArea)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_update_blob_image(Transaction *aTxn,
-                                           WrImageKey aImageKey,
+                                           BlobImageKey aImageKey,
                                            const WrImageDescriptor *aDescriptor,
                                            WrVecU8 *aBytes,
-                                           DeviceIntRect aDirtyRect)
+                                           LayoutIntRect aDirtyRect)
 WR_FUNC;
 
 WR_INLINE
 void wr_resource_updates_update_external_image(Transaction *aTxn,
                                                WrImageKey aKey,
                                                const WrImageDescriptor *aDescriptor,
                                                WrExternalImageId aExternalImageId,
                                                WrExternalImageBufferType aImageType,