Backed out 5 changesets (bug 1570081) for assertion failure at WebRenderCommandBuilder.cpp on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Fri, 13 Sep 2019 11:24:59 +0300
changeset 493002 c2abef0bfc0b855a8f0b50e96dabe1f6a4973de2
parent 493001 7e3d9c747e6e3b97080ea2ce72b25d00927a6e87
child 493003 058d5d7d5fee11b4f2e1e57355bfd58085a46c6d
push id114081
push userbtara@mozilla.com
push dateFri, 13 Sep 2019 15:49:27 +0000
treeherdermozilla-inbound@6a36994d2e14 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1570081
milestone71.0a1
backs out8d4baf4d063ac13cb7b0ef7e1457d43a91f8fbe5
14a4451b27810f3bffb3c67955efe97f115c968a
ba19640abf33a516cc7fa5ae6e75a369d02c4852
214c815015a862ecdfc86802645861a6678c9c40
548e4395ee1a10700a729f93c0340d44ebe91ac7
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
Backed out 5 changesets (bug 1570081) for assertion failure at WebRenderCommandBuilder.cpp on a CLOSED TREE Backed out changeset 8d4baf4d063a (bug 1570081) Backed out changeset 14a4451b2781 (bug 1570081) Backed out changeset ba19640abf33 (bug 1570081) Backed out changeset 214c815015a8 (bug 1570081) Backed out changeset 548e4395ee1a (bug 1570081)
gfx/layers/wr/IpcResourceUpdateQueue.cpp
gfx/layers/wr/WebRenderCommandBuilder.cpp
gfx/webrender_bindings/Moz2DImageRenderer.cpp
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/src/moz2d_renderer.rs
gfx/wr/webrender/src/clip.rs
gfx/wr/webrender/src/image.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/resource_cache.rs
gfx/wr/wrench/src/rawtest.rs
layout/reftests/border-image/reftest.list
layout/reftests/mathml/reftest.list
layout/reftests/svg/filters/reftest.list
layout/reftests/svg/reftest.list
layout/reftests/text-stroke/reftest.list
testing/web-platform/meta/svg/painting/reftests/markers-orient-001.svg.ini
--- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp
+++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp
@@ -335,18 +335,16 @@ bool IpcResourceUpdateQueue::UpdateImage
   return true;
 }
 
 bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
                                              const ImageDescriptor& aDescriptor,
                                              Range<uint8_t> aBytes,
                                              ImageIntRect aVisibleRect,
                                              ImageIntRect aDirtyRect) {
-  MOZ_ASSERT(aVisibleRect.width > 0 && aVisibleRect.height > 0);
-
   auto bytes = mWriter.Write(aBytes);
   if (!bytes.length()) {
     return false;
   }
   mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey,
                                                    aVisibleRect, aDirtyRect));
   return true;
 }
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -582,20 +582,16 @@ struct DIGroup {
   void EndGroup(WebRenderLayerManager* aWrManager,
                 nsDisplayListBuilder* aDisplayListBuilder,
                 wr::DisplayListBuilder& aBuilder,
                 wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper,
                 nsDisplayItem* aStartItem, nsDisplayItem* aEndItem) {
     GP("\n\n");
     GP("Begin EndGroup\n");
 
-    if (mVisibleRect.IsEmpty()) {
-      return;
-    }
-
     // Invalidate any unused items
     GP("mDisplayItems\n");
     for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
       BlobItemData* data = iter.Get()->GetKey();
       GP("  : %p-%d\n", data->mFrame, data->mDisplayItemKey);
       if (!data->mUsed) {
         GP("Invalidate unused: %p-%d\n", data->mFrame, data->mDisplayItemKey);
         InvalidateRect(data->mRect);
@@ -687,22 +683,19 @@ struct DIGroup {
     GP("%d Finish\n", hasItems);
     if (!validFonts) {
       gfxCriticalNote << "Failed serializing fonts for blob image";
       return;
     }
     Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData,
                          recorder->mOutputStream.mLength);
     if (!mKey) {
-      // we don't want to send a new image that doesn't have any
-      // items in it
-      if (!hasItems || mVisibleRect.IsEmpty()) {
+      if (!hasItems)  // we don't want to send a new image that doesn't have any
+                      // items in it
         return;
-      }
-
       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,
               ViewAs<ImagePixel>(mVisibleRect,
@@ -710,17 +703,18 @@ struct DIGroup {
         return;
       }
       mKey = Some(MakePair(aBuilder.GetRenderRoot(), key));
     } else {
       wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
 
       // Convert mInvalidRect to image space by subtracting the corner of the
       // image bounds
-      auto dirtyRect = ViewAs<ImagePixel>(mInvalidRect);
+      auto dirtyRect = ViewAs<ImagePixel>(
+          mInvalidRect - mVisibleRect.ToUnknownRect().TopLeft());
 
       auto bottomRight = dirtyRect.BottomRight();
       GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y,
          dtSize.width, dtSize.height);
       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);
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -331,64 +331,66 @@ struct Reader {
   int ReadInt() { return Read<int>(); }
 
   IntRectAbsolute ReadBounds() { return Read<IntRectAbsolute>(); }
 
   layers::BlobFont ReadBlobFont() { return Read<layers::BlobFont>(); }
 };
 
 static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
-                                gfx::SurfaceFormat aFormat,
+                                gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                                 const mozilla::wr::DeviceIntRect* aVisibleRect,
-                                const mozilla::wr::LayoutIntRect* aRenderRect,
                                 const uint16_t* aTileSize,
                                 const mozilla::wr::TileOffset* aTileOffset,
                                 const mozilla::wr::LayoutIntRect* aDirtyRect,
                                 Range<uint8_t> aOutput) {
-  IntSize size(aRenderRect->size.width, aRenderRect->size.height);
   AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob", GRAPHICS);
-  MOZ_RELEASE_ASSERT(size.width > 0 && size.height > 0);
-  if (size.width <= 0 || size.height <= 0) {
+  MOZ_RELEASE_ASSERT(aSize.width > 0 && aSize.height > 0);
+  if (aSize.width <= 0 || aSize.height <= 0) {
     return false;
   }
 
-  auto stride = size.width * gfx::BytesPerPixel(aFormat);
+  auto stride = aSize.width * gfx::BytesPerPixel(aFormat);
 
-  if (aOutput.length() < static_cast<size_t>(size.height * stride)) {
+  if (aOutput.length() < static_cast<size_t>(aSize.height * stride)) {
     return false;
   }
 
   // In bindings.rs we allocate a buffer filled with opaque white.
   bool uninitialized = false;
 
   RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
-      gfx::BackendType::SKIA, aOutput.begin().get(), size, stride, aFormat,
+      gfx::BackendType::SKIA, aOutput.begin().get(), aSize, stride, aFormat,
       uninitialized);
 
   if (!dt) {
     return false;
   }
 
   // We try hard to not have empty blobs but we can end up with
   // them because of CompositorHitTestInfo and merging.
   size_t footerSize = sizeof(size_t) + sizeof(IntPoint);
   MOZ_RELEASE_ASSERT(aBlob.length() >= footerSize);
   size_t indexOffset = ConvertFromBytes<size_t>(aBlob.end().get() - footerSize);
-
-  // aRenderRect is the part of the blob that we are currently rendering
-  // (for example a tile) in the same coordinate space as aVisibleRect.
-  IntPoint origin = gfx::IntPoint(aRenderRect->origin.x, aRenderRect->origin.y);
+  IntPoint origin = ConvertFromBytes<IntPoint>(aBlob.end().get() - footerSize +
+                                               sizeof(size_t));
+  // Apply the visibleRect's offset to make (0, 0) in the DT correspond to (0,
+  // 0) in the texture
 
   MOZ_RELEASE_ASSERT(indexOffset <= aBlob.length() - footerSize);
   Reader reader(aBlob.begin().get() + indexOffset,
                 aBlob.length() - footerSize - indexOffset);
 
+  if (aTileOffset) {
+    origin +=
+        gfx::IntPoint(aTileOffset->x * *aTileSize, aTileOffset->y * *aTileSize);
+  }
   dt = gfx::Factory::CreateOffsetDrawTarget(dt, origin);
 
-  auto bounds = gfx::IntRect(origin, size);
+  auto bounds = gfx::IntRect(origin, aSize);
 
   if (aDirtyRect) {
     gfx::Rect dirty(aDirtyRect->origin.x, aDirtyRect->origin.y,
                     aDirtyRect->size.width, aDirtyRect->size.height);
     dt->PushClipRect(dirty);
     bounds = bounds.Intersect(
         IntRect(aDirtyRect->origin.x, aDirtyRect->origin.y,
                 aDirtyRect->size.width, aDirtyRect->size.height));
@@ -429,17 +431,17 @@ static bool Moz2DRenderCallback(const Ra
     offset = extra_end;
   }
 
   if (StaticPrefs::gfx_webrender_blob_paint_flashing()) {
     dt->SetTransform(gfx::Matrix());
     float r = float(rand()) / float(RAND_MAX);
     float g = float(rand()) / float(RAND_MAX);
     float b = float(rand()) / float(RAND_MAX);
-    dt->FillRect(gfx::Rect(origin.x, origin.y, size.width, size.height),
+    dt->FillRect(gfx::Rect(origin.x, origin.y, aSize.width, aSize.height),
                  gfx::ColorPattern(gfx::Color(r, g, b, 0.5)));
   }
 
   if (aDirtyRect) {
     dt->PopClip();
   }
 
 #if 0
@@ -452,24 +454,22 @@ static bool Moz2DRenderCallback(const Ra
   return ret;
 }
 
 }  // namespace wr
 }  // namespace mozilla
 
 extern "C" {
 
-bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
-                        mozilla::wr::ImageFormat aFormat,
-                        const mozilla::wr::LayoutIntRect* aRenderRect,
+bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob, int32_t width,
+                        int32_t height, mozilla::wr::ImageFormat aFormat,
                         const mozilla::wr::DeviceIntRect* aVisibleRect,
                         const uint16_t* aTileSize,
                         const mozilla::wr::TileOffset* aTileOffset,
                         const mozilla::wr::LayoutIntRect* aDirtyRect,
                         mozilla::wr::MutByteSlice output) {
   return mozilla::wr::Moz2DRenderCallback(
-      mozilla::wr::ByteSliceToRange(blob),
-      mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect,
-      aRenderRect, aTileSize, aTileOffset, aDirtyRect,
-      mozilla::wr::MutByteSliceToRange(output));
+      mozilla::wr::ByteSliceToRange(blob), mozilla::gfx::IntSize(width, height),
+      mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect, aTileSize,
+      aTileOffset, aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
 }
 
 }  // extern
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -3231,18 +3231,19 @@ pub unsafe extern "C" fn wr_dec_ref_arc(
 }
 
 // TODO: nical
 // Update for the new blob image interface changes.
 //
 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,
-                               render_rect: &LayoutIntRect,
                                visible_rect: &DeviceIntRect,
                                tile_size: Option<&u16>,
                                tile_offset: Option<&TileOffset>,
                                dirty_rect: Option<&LayoutIntRect>,
                                output: MutByteSlice)
                                -> bool;
 }
 
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -375,31 +375,36 @@ impl<'a> CachedReader<'a> {
 /// to do is walk through the new (partial) list, determine which of the two lists
 /// has the recording for that item, and copy the recording into the result.
 ///
 /// If an item is contained in the dirty rect, then the new list contains the
 /// correct recording for that item, so we always copy it from there. Otherwise, we find
 /// the first not-yet-copied item with those bounds in the old list and copy that.
 /// Any items found in the old list but not the new one can be safely assumed to
 /// have been deleted.
-fn merge_blob_images(old_buf: &[u8], new_buf: &[u8], dirty_rect: Box2d) -> Vec<u8> {
+fn merge_blob_images(old_buf: &[u8], new_buf: &[u8], mut dirty_rect: Box2d) -> Vec<u8> {
 
     let mut result = BlobWriter::new();
     dlog!("dirty rect: {:?}", dirty_rect);
     dlog!("old:");
     dump_bounds(old_buf, dirty_rect);
     dlog!("new:");
     dump_bounds(new_buf, dirty_rect);
 
     let mut old_reader = CachedReader::new(old_buf);
     let mut new_reader = BlobReader::new(new_buf);
 
     // we currently only support merging blobs that have the same origin
     assert_eq!(old_reader.reader.origin, new_reader.origin);
 
+    dirty_rect.x1 += new_reader.origin.x;
+    dirty_rect.y1 += new_reader.origin.y;
+    dirty_rect.x2 += new_reader.origin.x;
+    dirty_rect.y2 += new_reader.origin.y;
+
     // Loop over both new and old entries merging them.
     // Both new and old must have the same number of entries that
     // overlap but are not contained by the dirty rect, and they
     // must be in the same order.
     while new_reader.reader.has_more() {
         let new = new_reader.read_entry();
         dlog!("bounds: {} {} {:?}", new.end, new.extra_end, new.bounds);
         if new.bounds.contained_by(&dirty_rect) {
@@ -543,18 +548,19 @@ fn rasterize_blob(job: Job) -> (BlobImag
         DirtyRect::Partial(rect) => Some(rect),
         DirtyRect::All => None,
     };
     assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height  > 0);
 
     let result = unsafe {
         if wr_moz2d_render_cb(
             ByteSlice::new(&job.commands[..]),
+            descriptor.rect.size.width,
+            descriptor.rect.size.height,
             descriptor.format,
-            &descriptor.rect,
             &job.visible_rect,
             job.tile_size.as_ref(),
             job.request.tile.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
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -375,25 +375,29 @@ impl ClipNodeInfo {
                     };
 
                     let repetitions = image::repetitions(
                         &rect,
                         &visible_rect,
                         rect.size,
                     );
 
+                    // TODO: As a followup, if the image is a tiled blob, the device_image_rect below
+                    // will be set to the blob's visible area.
+                    let device_image_rect = DeviceIntRect::from_size(props.descriptor.size);
+
                     for Repetition { origin, .. } in repetitions {
                         let layout_image_rect = LayoutRect {
                             origin,
                             size: rect.size,
                         };
                         let tiles = image::tiles(
                             &layout_image_rect,
                             &visible_rect,
-                            &props.visible_rect,
+                            &device_image_rect,
                             tile_size as i32,
                         );
                         for tile in tiles {
                             if request_resources {
                                 resource_cache.request_image(
                                     request.with_tile(tile.offset),
                                     gpu_cache,
                                 );
--- a/gfx/wr/webrender/src/image.rs
+++ b/gfx/wr/webrender/src/image.rs
@@ -486,47 +486,16 @@ fn last_tile_size_1d(
             //             .      |      |//#   .      .
             // modulo (m):                ~~>
             m => m,
         },
         image_size,
     )
 }
 
-pub fn compute_tile_rect(
-    image_rect: &DeviceIntRect,
-    regular_tile_size: TileSize,
-    tile: TileOffset,
-) -> DeviceIntRect {
-    let regular_tile_size = regular_tile_size as i32;
-    DeviceIntRect {
-        origin: point2(
-            compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
-            compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
-        ),
-        size: size2(
-            compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
-            compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
-        ),
-    }
-}
-
-fn compute_tile_origin_1d(
-    img_range: Range<i32>,
-    regular_tile_size: i32,
-    tile_offset: i32,
-) -> i32 {
-    let tile_range = tile_range_1d(&img_range, regular_tile_size);
-    if tile_offset == tile_range.start {
-        img_range.start
-    } else {
-        tile_offset * regular_tile_size
-    }
-}
-
 // Compute the width and height in pixels of a tile depending on its position in the image.
 pub fn compute_tile_size(
     image_rect: &DeviceIntRect,
     regular_tile_size: TileSize,
     tile: TileOffset,
 ) -> DeviceIntSize {
     let regular_tile_size = regular_tile_size as i32;
     size2(
@@ -577,57 +546,16 @@ pub fn for_each_tile_in_range(
 ) {
     for y in range.y_range() {
         for x in range.x_range() {
             callback(point2(x, y));
         }
     }
 }
 
-pub fn compute_valid_tiles_if_bounds_change(
-    prev_rect: &DeviceIntRect,
-    new_rect: &DeviceIntRect,
-    tile_size: u16,
-) -> Option<TileRange> {
-    let intersection = prev_rect.intersection(new_rect);
-
-    if intersection.is_none() {
-        return Some(TileRange::zero());
-    }
-
-    let intersection = intersection.unwrap_or(DeviceIntRect::zero());
-
-    let left = prev_rect.min_x() != new_rect.min_x();
-    let right = prev_rect.max_x() != new_rect.max_x();
-    let top = prev_rect.min_y() != new_rect.min_y();
-    let bottom = prev_rect.max_y() != new_rect.max_y();
-
-    if !left && !right && !top && !bottom {
-        // Bounds have not changed.
-        return None;
-    }
-
-    let tw = 1.0 / (tile_size as f32);
-    let th = 1.0 / (tile_size as f32);
-
-    let tiles = intersection
-        .cast::<f32>()
-        .scale(tw, th);
-
-    let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) };
-    let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) };
-    let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) };
-    let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) };
-
-    Some(TileRange {
-        origin: point2(min_x as i32, min_y as i32),
-        size: size2((max_x - min_x) as i32, (max_y - min_y) as i32),
-    })
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
     use std::collections::HashSet;
     use euclid::rect;
 
     // this checks some additional invariants
     fn checked_for_each_tile(
@@ -781,31 +709,9 @@ mod tests {
 
         // One partial tile at positve offset, non-zero origin.
         let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 300..310, 64);
         assert_eq!(result.tile_range.start, 4);
         assert_eq!(result.tile_range.end, 5);
         assert_eq!(result.first_tile_layout_size, 10.0);
         assert_eq!(result.last_tile_layout_size, 10.0);
     }
-
-    #[test]
-    fn smaller_than_tile_size_at_origin() {
-        let r = compute_tile_rect(
-            &rect(0, 0, 80, 80),
-            256,
-            point2(0, 0),
-        );
-
-        assert_eq!(r, rect(0, 0, 80, 80));
-    }
-
-    #[test]
-    fn smaller_than_tile_size_with_offset() {
-        let r = compute_tile_rect(
-            &rect(20, 20, 80, 80),
-            256,
-            point2(0, 0),
-        );
-
-        assert_eq!(r, rect(20, 20, 80, 80));
-    }
 }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -2376,22 +2376,19 @@ impl PrimitiveStore {
                 match image_properties {
                     Some(ImageProperties { tiling: None, .. }) => {
 
                         frame_state.resource_cache.request_image(
                             request,
                             frame_state.gpu_cache,
                         );
                     }
-                    Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => {
+                    Some(ImageProperties { descriptor, tiling: Some(tile_size), .. }) => {
                         image_instance.visible_tiles.clear();
-                        // TODO: rename the blob's visible_rect into something that doesn't conflict
-                        // with the terminology we use during culling since it's not really the same
-                        // thing.
-                        let active_rect = visible_rect;
+                        let device_image_rect = DeviceIntRect::from_size(descriptor.size);
 
                         // Tighten the clip rect because decomposing the repeated image can
                         // produce primitives that are partially covering the original image
                         // rect and we want to clip these extra parts out.
                         let prim_info = &frame_state.scratch.prim_info[prim_instance.visibility_info.0 as usize];
                         let prim_rect = LayoutRect::new(
                             prim_instance.prim_origin,
                             common_data.prim_size,
@@ -2434,17 +2431,17 @@ impl PrimitiveStore {
                             let layout_image_rect = LayoutRect {
                                 origin,
                                 size: image_data.stretch_size,
                             };
 
                             let tiles = crate::image::tiles(
                                 &layout_image_rect,
                                 &visible_rect,
-                                &active_rect,
+                                &device_image_rect,
                                 tile_size as i32,
                             );
 
                             for tile in tiles {
                                 frame_state.resource_cache.request_image(
                                     request.with_tile(tile.offset),
                                     frame_state.gpu_cache,
                                 );
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -19,18 +19,17 @@ use crate::capture::PlainExternalImage;
 use crate::capture::CaptureConfig;
 use crate::device::TextureFilter;
 use euclid::{point2, size2};
 use crate::glyph_cache::GlyphCache;
 use crate::glyph_cache::GlyphCacheEntry;
 use crate::glyph_rasterizer::{BaseFontInstance, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
-use crate::image::{compute_tile_size, compute_tile_rect, compute_tile_range, for_each_tile_in_range};
-use crate::image::compute_valid_tiles_if_bounds_change;
+use crate::image::{compute_tile_size, compute_tile_range, for_each_tile_in_range};
 use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList};
 use crate::profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use crate::render_backend::{FrameId, FrameStamp};
 use crate::render_task_graph::{RenderTaskGraph, RenderTaskId};
 use crate::render_task::{RenderTaskCache, RenderTaskCacheKey};
 use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle};
 use smallvec::SmallVec;
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
@@ -139,19 +138,16 @@ impl CachedImageData {
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
-    // Potentially a subset of the image's total rectangle. This rectangle is what
-    // we map to the (layout space) display item bounds.
-    pub visible_rect: DeviceIntRect,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
@@ -163,34 +159,23 @@ enum RasterizedBlob {
 }
 
 /// Pre scene building state.
 /// We use this to generate the async blob rendering requests.
 struct BlobImageTemplate {
     descriptor: ImageDescriptor,
     tiling: Option<TileSize>,
     dirty_rect: BlobDirtyRect,
-    /// See ImageResource::visible_rect.
-    visible_rect: DeviceIntRect,
-    // If the active rect of the blob changes, this represents the
-    // range of tiles that remain valid. This must be taken into
-    // account in addition to the valid rect when submitting blob
-    // rasterization requests.
-    // `None` means the bounds have not changed (tiles are still valid).
-    // `Some(TileRange::zero())` means all of the tiles are invalid.
-    valid_tiles_after_bounds_change: Option<TileRange>,
+    viewport_tiles: Option<TileRange>,
 }
 
 struct ImageResource {
     data: CachedImageData,
     descriptor: ImageDescriptor,
     tiling: Option<TileSize>,
-    /// This is used to express images that are virtually very large
-    /// but with only a visible sub-set that is valid at a given time.
-    visible_rect: DeviceIntRect,
 }
 
 #[derive(Clone, Debug)]
 pub struct ImageTiling {
     pub image_size: DeviceIntSize,
     pub tile_size: TileSize,
 }
 
@@ -596,60 +581,51 @@ impl ResourceCache {
         // in a way that reduces fragmentation in the atlas).
 
         for update in updates {
             match update {
                 ResourceUpdate::AddImage(img) => {
                     if let ImageData::Raw(ref bytes) = img.data {
                         profile_counters.image_templates.inc(bytes.len());
                     }
-                    self.add_image_template(
-                        img.key,
-                        img.descriptor,
-                        img.data.into(),
-                        &img.descriptor.size.into(),
-                        img.tiling,
-                    );
+                    self.add_image_template(img.key, img.descriptor, img.data.into(), img.tiling);
                 }
                 ResourceUpdate::UpdateImage(img) => {
                     self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect);
                 }
                 ResourceUpdate::AddBlobImage(img) => {
                     self.add_image_template(
                         img.key.as_image(),
                         img.descriptor,
                         CachedImageData::Blob,
-                        &img.visible_rect,
                         img.tiling,
                     );
                 }
                 ResourceUpdate::UpdateBlobImage(img) => {
                     self.update_image_template(
                         img.key.as_image(),
                         img.descriptor,
                         CachedImageData::Blob,
                         &to_image_dirty_rect(
                             &img.dirty_rect
                         ),
                     );
-                    self.discard_tiles_outside_visible_area(img.key, &img.visible_rect); // TODO: remove?
-                    self.set_image_visible_rect(img.key.as_image(), &img.visible_rect);
+                    self.discard_tiles_outside_visible_area(img.key, &img.visible_rect);
                 }
                 ResourceUpdate::DeleteImage(img) => {
                     self.delete_image_template(img);
                 }
                 ResourceUpdate::DeleteFont(font) => {
                     self.delete_font_template(font);
                 }
                 ResourceUpdate::DeleteFontInstance(font) => {
                     self.delete_font_instance(font);
                 }
                 ResourceUpdate::SetBlobImageVisibleArea(key, area) => {
                     self.discard_tiles_outside_visible_area(key, &area);
-                    self.set_image_visible_rect(key.as_image(), &area);
                 }
                 ResourceUpdate::AddFont(_) |
                 ResourceUpdate::AddFontInstance(_) => {
                     // Handled in update_resources_pre_scene_building
                 }
             }
         }
     }
@@ -676,17 +652,22 @@ impl ResourceCache {
                         &img.descriptor,
                         &img.dirty_rect,
                         Arc::clone(&img.data),
                         &img.visible_rect,
                     );
                 }
                 ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => {
                     if let Some(template) = self.blob_image_templates.get_mut(&key) {
-                        template.visible_rect = *area;
+                        if let Some(tile_size) = template.tiling {
+                            template.viewport_tiles = Some(compute_tile_range(
+                                &area,
+                                tile_size,
+                            ));
+                        }
                     }
                 }
                 _ => {}
             }
         }
 
         drain_filter(
             updates,
@@ -781,30 +762,16 @@ impl ResourceCache {
             if let Some(tile) = request.tile {
                 if let RasterizedBlob::NonTiled(..) = *image {
                     *image = RasterizedBlob::Tiled(FastHashMap::default());
                 }
 
                 if let RasterizedBlob::Tiled(ref mut tiles) = *image {
                     tiles.insert(tile, data);
                 }
-
-                match self.cached_images.try_get_mut(&request.key.as_image()) {
-                    Some(&mut ImageResult::Multi(ref mut entries)) => {
-                        let cached_key = CachedImageKey {
-                            rendering: ImageRendering::Auto, // TODO(nical)
-                            tile: Some(tile),
-                        };
-                        if let Some(entry) = entries.try_get_mut(&cached_key) {
-                            entry.dirty_rect = DirtyRect::All;
-                        }
-                    }
-                    _ => {}
-                }
-
             } else {
                 if let RasterizedBlob::NonTiled(ref mut queue) = *image {
                     // If our new rasterized rect overwrites items in the queue, discard them.
                     queue.retain(|img| {
                         !data.rasterized_rect.contains_rect(&img.rasterized_rect)
                     });
 
                     queue.push(data);
@@ -884,30 +851,28 @@ impl ResourceCache {
         instance_map.get(&instance_key).map(|instance| { Arc::clone(instance) })
     }
 
     pub fn add_image_template(
         &mut self,
         image_key: ImageKey,
         descriptor: ImageDescriptor,
         data: CachedImageData,
-        visible_rect: &DeviceIntRect,
         mut tiling: Option<TileSize>,
     ) {
         if tiling.is_none() && Self::should_tile(self.max_texture_size(), &descriptor, &data) {
             // We aren't going to be able to upload a texture this big, so tile it, even
             // if tiling was not requested.
             tiling = Some(DEFAULT_TILE_SIZE);
         }
 
         let resource = ImageResource {
             descriptor,
             data,
             tiling,
-            visible_rect: *visible_rect,
         };
 
         self.resources.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(
         &mut self,
         image_key: ImageKey,
@@ -965,42 +930,42 @@ impl ResourceCache {
         if image.descriptor.format != descriptor.format {
             // could be a stronger warning/error?
             trace!("Format change {:?} -> {:?}", image.descriptor.format, descriptor.format);
         }
         *image = ImageResource {
             descriptor,
             data,
             tiling,
-            visible_rect: descriptor.size.into(),
         };
     }
 
     // Happens before scene building.
     pub fn add_blob_image(
         &mut self,
         key: BlobImageKey,
         descriptor: &ImageDescriptor,
         mut tiling: Option<TileSize>,
         data: Arc<BlobImageData>,
         visible_rect: &DeviceIntRect,
     ) {
         let max_texture_size = self.max_texture_size();
         tiling = get_blob_tiling(tiling, descriptor, max_texture_size);
 
+        let viewport_tiles = tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
+
         self.blob_image_handler.as_mut().unwrap().add(key, data, visible_rect, tiling);
 
         self.blob_image_templates.insert(
             key,
             BlobImageTemplate {
                 descriptor: *descriptor,
                 tiling,
                 dirty_rect: DirtyRect::All,
-                valid_tiles_after_bounds_change: None,
-                visible_rect: *visible_rect,
+                viewport_tiles,
             },
         );
     }
 
     // Happens before scene building.
     pub fn update_blob_image(
         &mut self,
         key: BlobImageKey,
@@ -1014,42 +979,23 @@ impl ResourceCache {
         let max_texture_size = self.max_texture_size();
 
         let image = self.blob_image_templates
             .get_mut(&key)
             .expect("Attempt to update non-existent blob image");
 
         let tiling = get_blob_tiling(image.tiling, descriptor, max_texture_size);
 
-        let mut valid_tiles_after_bounds_change = None;
-
-        if let Some(tile_size) = image.tiling {
-            valid_tiles_after_bounds_change = compute_valid_tiles_if_bounds_change(
-                &image.visible_rect,
-                visible_rect,
-                tile_size,
-            );
-        }
-
-        match (image.valid_tiles_after_bounds_change, valid_tiles_after_bounds_change) {
-            (Some(old), Some(ref mut new)) => {
-                *new = new.intersection(&old).unwrap_or(TileRange::zero());
-            }
-            (Some(old), None) => {
-                valid_tiles_after_bounds_change = Some(old);
-            }
-            _ => {}
-        }
+        let viewport_tiles = image.tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
 
         *image = BlobImageTemplate {
             descriptor: *descriptor,
             tiling,
             dirty_rect: dirty_rect.union(&image.dirty_rect),
-            valid_tiles_after_bounds_change,
-            visible_rect: *visible_rect,
+            viewport_tiles,
         };
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         // Remove the template.
         let value = self.resources.image_templates.remove(image_key);
 
         // Release the corresponding texture cache entry, if any.
@@ -1209,21 +1155,24 @@ impl ResourceCache {
 
             // For some reason the blob image is missing. We'll fall back to
             // rasterizing it on the render backend thread.
             if missing {
                 let descriptor = BlobImageDescriptor {
                     rect: match template.tiling {
                         Some(tile_size) => {
                             let tile = request.tile.unwrap();
-                            blob_rect(compute_tile_rect(
-                                &template.visible_rect,
-                                tile_size,
-                                tile,
-                            ))
+                            LayoutIntRect {
+                                origin: point2(tile.x, tile.y) * tile_size as i32,
+                                size: blob_size(compute_tile_size(
+                                    &template.descriptor.size.into(),
+                                    tile_size,
+                                    tile,
+                                )),
+                            }
                         }
                         None => blob_size(template.descriptor.size).into(),
                     },
                     format: template.descriptor.format,
                 };
 
                 assert!(!descriptor.rect.is_empty());
 
@@ -1253,43 +1202,47 @@ impl ResourceCache {
         let mut blob_tiles_clear_requests = Vec::new();
         let mut blob_request_params = Vec::new();
         for key in keys {
             let template = self.blob_image_templates.get_mut(key).unwrap();
 
             if let Some(tile_size) = template.tiling {
                 // If we know that only a portion of the blob image is in the viewport,
                 // only request these visible tiles since blob images can be huge.
-                let mut tiles = compute_tile_range(
-                    &template.visible_rect,
-                    tile_size,
-                );
+                let mut tiles = template.viewport_tiles.unwrap_or_else(|| {
+                    // Default to requesting the full range of tiles.
+                    compute_tile_range(
+                        &DeviceIntRect {
+                            origin: point2(0, 0),
+                            size: template.descriptor.size,
+                        },
+                        tile_size,
+                    )
+                });
 
                 let image_dirty_rect = to_image_dirty_rect(&template.dirty_rect);
                 // Don't request tiles that weren't invalidated.
-                let dirty_tiles = match image_dirty_rect {
-                    DirtyRect::Partial(dirty_rect) => {
-                        let dirty_rect = DeviceIntRect {
-                            origin: point2(
-                                dirty_rect.origin.x,
-                                dirty_rect.origin.y,
-                            ),
-                            size: size2(
-                                dirty_rect.size.width,
-                                dirty_rect.size.height,
-                            ),
-                        };
+                if let DirtyRect::Partial(dirty_rect) = image_dirty_rect {
+                    let dirty_rect = DeviceIntRect {
+                        origin: point2(
+                            dirty_rect.origin.x,
+                            dirty_rect.origin.y,
+                        ),
+                        size: size2(
+                            dirty_rect.size.width,
+                            dirty_rect.size.height,
+                        ),
+                    };
+                    let dirty_tiles = compute_tile_range(
+                        &dirty_rect,
+                        tile_size,
+                    );
 
-                        compute_tile_range(
-                            &dirty_rect,
-                            tile_size,
-                        )
-                    }
-                    DirtyRect::All => tiles,
-                };
+                    tiles = tiles.intersection(&dirty_tiles).unwrap_or_else(TileRange::zero);
+                }
 
                 let original_tile_range = tiles;
 
                 // This code tries to keep things sane if Gecko sends
                 // nonsensical blob image requests.
                 // Constant here definitely needs to be tweaked.
                 const MAX_TILES_PER_REQUEST: i32 = 512;
                 // For truly nonsensical requests, we might run into overflow
@@ -1320,32 +1273,26 @@ impl ResourceCache {
                     let clear_params = BlobImageClearParams {
                         key: *key,
                         original_tile_range,
                         actual_tile_range: tiles,
                     };
                     blob_tiles_clear_requests.push(clear_params);
                 }
 
-
                 for_each_tile_in_range(&tiles, |tile| {
-                    let still_valid = template.valid_tiles_after_bounds_change
-                        .map(|valid_tiles| valid_tiles.contains(tile))
-                        .unwrap_or(true);
-
-                    if still_valid && !dirty_tiles.contains(tile) {
-                        return;
-                    }
-
                     let descriptor = BlobImageDescriptor {
-                        rect: blob_rect(compute_tile_rect(
-                            &template.visible_rect,
-                            tile_size,
-                            tile,
-                        )),
+                        rect: LayoutIntRect {
+                            origin: point2(tile.x, tile.y) * tile_size as i32,
+                            size: blob_size(compute_tile_size(
+                                &template.descriptor.size.into(),
+                                tile_size,
+                                tile,
+                            )),
+                        },
                         format: template.descriptor.format,
                     };
 
                     assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height > 0);
                     // TODO: We only track dirty rects for non-tiled blobs but we
                     // should also do it with tiled ones unless we settle for a small
                     // tile size.
                     blob_request_params.push(
@@ -1354,27 +1301,25 @@ impl ResourceCache {
                                 key: *key,
                                 tile: Some(tile),
                             },
                             descriptor,
                             dirty_rect: DirtyRect::All,
                         }
                     );
                 });
-
-                template.valid_tiles_after_bounds_change = None;
             } else {
                 let mut needs_upload = match self.cached_images.try_get(&key.as_image()) {
                     Some(&ImageResult::UntiledAuto(ref entry)) => {
                         self.texture_cache.needs_upload(&entry.texture_cache_handle)
                     }
                     _ => true,
                 };
 
-                // If the queue of rasterized updates is growing it probably means that
+                // If the queue of ratserized updates is growing it probably means that
                 // the texture is not getting uploaded because the display item is off-screen.
                 // In that case we are better off
                 // - Either not kicking rasterization for that image (avoid wasted cpu work
                 //   but will jank next time the item is visible because of lazy rasterization.
                 // - Clobber the update queue by pushing an update with a larger dirty rect
                 //   to prevent it from accumulating.
                 //
                 // We do the latter here but it's not ideal and might want to revisit and do
@@ -1461,22 +1406,16 @@ impl ResourceCache {
                     entry.mark_unused(texture_cache);
                     return false;
                 });
             }
             _ => {}
         }
     }
 
-    fn set_image_visible_rect(&mut self, key: ImageKey, rect: &DeviceIntRect) {
-        if let Some(image) = self.resources.image_templates.get_mut(key) {
-            image.visible_rect = * rect;
-        }
-    }
-
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
         glyph_keys: &[GlyphKey],
         gpu_cache: &mut GpuCache,
         render_task_tree: &mut RenderTaskGraph,
     ) {
         debug_assert_eq!(self.state, State::AddResources);
@@ -1605,17 +1544,16 @@ impl ResourceCache {
                 // raw and blob image are all using resource_cache.
                 CachedImageData::Raw(..) | CachedImageData::Blob => None,
             };
 
             ImageProperties {
                 descriptor: image_template.descriptor,
                 external_image,
                 tiling: image_template.tiling,
-                visible_rect: image_template.visible_rect,
             }
         })
     }
 
     pub fn prepare_for_frames(&mut self, time: SystemTime) {
         self.texture_cache.prepare_for_frames(time);
     }
 
@@ -1746,25 +1684,23 @@ impl ResourceCache {
                     ImageResult::Err(_) => panic!("Update requested for invalid entry")
                 };
 
                 let mut descriptor = image_template.descriptor.clone();
                 let mut dirty_rect = entry.dirty_rect.replace_with_empty();
 
                 if let Some(tile) = request.tile {
                     let tile_size = image_template.tiling.unwrap();
-                    let clipped_tile_size = compute_tile_size(&image_template.visible_rect, tile_size, tile);
+                    let clipped_tile_size = compute_tile_size(&descriptor.size.into(), tile_size, tile);
+
                     // The tiled image could be stored on the CPU as one large image or be
                     // already broken up into tiles. This affects the way we compute the stride
                     // and offset.
                     let tiled_on_cpu = image_template.data.is_blob();
                     if !tiled_on_cpu {
-                        // we don't expect to have partial tiles at the top and left of non-blob
-                        // images.
-                        debug_assert_eq!(image_template.visible_rect.origin, point2(0, 0));
                         let bpp = descriptor.format.bytes_per_pixel();
                         let stride = descriptor.compute_stride();
                         descriptor.stride = Some(stride);
                         descriptor.offset +=
                             tile.y as i32 * tile_size as i32 * stride +
                             tile.x as i32 * tile_size as i32 * bpp;
                     }
 
@@ -2333,32 +2269,23 @@ impl ResourceCache {
                     CachedImageData::Raw(arc)
                 }
             };
 
             res.image_templates.images.insert(key, ImageResource {
                 data,
                 descriptor: template.descriptor,
                 tiling: template.tiling,
-                visible_rect: template.descriptor.size.into(),
             });
         }
 
         external_images
     }
 }
 
 /// For now the blob's coordinate space have the same pixel sizes as the
 /// rendered texture's device space (only a translation is applied).
 /// So going from one to the other is only a matter of casting away the unit
 /// for sizes.
 #[inline]
 fn blob_size(device_size: DeviceIntSize) -> LayoutIntSize {
     size2(device_size.width, device_size.height)
 }
-
-#[inline]
-fn blob_rect(device_rect: DeviceIntRect) -> LayoutIntRect {
-    LayoutIntRect {
-        origin: point2(device_rect.origin.x, device_rect.origin.y),
-        size: blob_size(device_rect.size),
-    }
-}
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -34,17 +34,17 @@ impl<'a> RawtestHarness<'a> {
     pub fn run(mut self) {
         self.test_hit_testing();
         self.test_resize_image();
         self.test_retained_blob_images_test();
         self.test_blob_update_test();
         self.test_blob_update_epoch_test();
         self.test_tile_decomposition();
         self.test_very_large_blob();
-        self.test_blob_visible_area();
+        self.test_insufficient_blob_visible_area();
         self.test_offscreen_blob();
         self.test_save_restore();
         self.test_blur_cache();
         self.test_capture();
         self.test_zero_height_window();
         self.test_clear_cache();
     }
 
@@ -296,185 +296,212 @@ impl<'a> RawtestHarness<'a> {
 
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img,
-            ImageDescriptor::new(15000, 15000, ImageFormat::BGRA8, false, false),
+            ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
-            rect(0, 0, 15000, 15000),
-            Some(100),
+            rect(0, 0, 15010, 111256),
+            Some(31),
         );
 
         let called = Arc::new(AtomicIsize::new(0));
         let called_inner = Arc::clone(&called);
 
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
             called_inner.fetch_add(1, Ordering::SeqCst);
         });
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
+        let image_size = size2(1510., 111256.);
+
         let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
         let clip_id = builder.define_clip(
             &root_space_and_clip,
             rect(40., 41., 200., 201.),
             vec![],
             None,
         );
 
         let info = CommonItemProperties {
-            clip_rect: rect(0.0, 0.0, 800.0, 800.0),
+            clip_rect: rect(0., -9600.0, 1510.000031, 111256.),
             clip_id,
             spatial_id: root_space_and_clip.spatial_id,
             is_backface_visible: true,
             hit_info: None,
         };
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
-            size2(15000.0, 15000.0).into(),
-            size2(15000.0, 15000.0),
-            size2(0.0, 0.0),
+            info.clip_rect,
+            image_size * 2.,
+            image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
+        txn.set_blob_image_visible_area(
+            blob_img,
+            DeviceIntRect {
+                origin: point2(0, 111256 / 30),
+                size: size2(1510, 111256 / 30),
+            }
+        );
 
         let mut epoch = Epoch(0);
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
 
         let pixels = self.render_and_get_pixels(window_rect);
 
         // make sure we didn't request too many blobs
         assert!(called.load(Ordering::SeqCst) < 20);
 
-        //use crate::png;
-        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));
-
         // make sure things are in the right spot
-        let w = window_rect.size.width as usize;
-        let h = window_rect.size.height as usize;
-        let p1 = (40 + (h - 100) * w) * 4;
-        assert_eq!(pixels[p1 + 0], 50);
-        assert_eq!(pixels[p1 + 1], 50);
-        assert_eq!(pixels[p1 + 2], 150);
-        assert_eq!(pixels[p1 + 3], 255);
+        assert!(
+            pixels[(148 +
+                (window_rect.size.height as usize - 148) *
+                    window_rect.size.width as usize) * 4] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 1] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 2] == 255 &&
+                pixels[(148 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 3] == 255
+        );
+        assert!(
+            pixels[(132 +
+                (window_rect.size.height as usize - 148) *
+                    window_rect.size.width as usize) * 4] == 50 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 1] == 50 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 2] == 150 &&
+                pixels[(132 +
+                    (window_rect.size.height as usize - 148) *
+                        window_rect.size.width as usize) * 4 + 3] == 255
+        );
 
         // Leaving a tiled blob image in the resource cache
         // confuses the `test_capture`. TODO: remove this
         txn = Transaction::new();
         txn.delete_blob_image(blob_img);
         self.wrench.api.update_resources(txn.resource_updates);
 
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
-    fn test_blob_visible_area(&mut self) {
-        println!("\tblob visible area...");
+    fn test_insufficient_blob_visible_area(&mut self) {
+        println!("\tinsufficient blob visible area...");
 
-        assert_eq!(self.wrench.device_pixel_ratio, 1.0);
+        // This test compares two almost identical display lists containing the a blob
+        // image. The only difference is that one of the display lists specifies a visible
+        // area for its blob image which is too small, causing frame building to run into
+        // missing tiles, and forcing it to exercise the code path where missing tiles are
+        // rendered synchronously on demand.
+
+        assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
-
         let test_size = FramebufferIntSize::new(800, 800);
-
         let window_rect = FramebufferIntRect::new(
-            FramebufferIntPoint::new(0, window_size.height - test_size.height),
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
+        let layout_size = LayoutSize::new(800.0, 800.0);
+        let image_size = size2(800.0, 800.0);
+        let info = self.make_common_properties(rect(0.0, 0.0, 800.0, 800.0));
 
-        let layout_size = LayoutSize::new(800.0, 800.0);
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
 
-        let blob_img = self.wrench.api.generate_blob_image_key();
+        let blob_img1 = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
-            blob_img,
-            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
+            blob_img1,
+            ImageDescriptor::new(
+                image_size.width as i32,
+                image_size.height as i32,
+                ImageFormat::BGRA8,
+                false,
+                false
+            ),
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
-            DeviceIntRect {
-                origin: point2(50, 20),
-                size: size2(400, 400),
-            },
+            rect(0, 0, image_size.width as i32, image_size.height as i32),
             Some(100),
         );
 
-        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
-
-        let image_size = size2(400.0, 400.0);
-
-        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
-        let clip_id = builder.define_clip(
-            &root_space_and_clip,
-            rect(-1000.0, -1000.0, 2000.0, 2000.0),
-            vec![],
-            None,
-        );
-
-        let info = CommonItemProperties {
-            clip_rect: rect(10.0, 10.0, 400.0, 400.0),
-            clip_id,
-            spatial_id: root_space_and_clip.spatial_id,
-            is_backface_visible: true,
-            hit_info: None,
-        };
-
         builder.push_image(
             &info,
             info.clip_rect,
             image_size,
             image_size,
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
-            blob_img.as_image(),
+            blob_img1.as_image(),
             ColorF::WHITE,
         );
-        let mut epoch = Epoch(0);
-
-        self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
-
-        let pixels = self.render_and_get_pixels(window_rect);
-
-        //use super::png;
-        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));
 
-        // make sure things are in the right spot
-        let w = window_rect.size.width as usize;
-        let h = window_rect.size.height as usize;
-        let p1 = (65 + (h - 15) * w) * 4;
-        assert_eq!(pixels[p1 + 0], 255);
-        assert_eq!(pixels[p1 + 1], 255);
-        assert_eq!(pixels[p1 + 2], 255);
-        assert_eq!(pixels[p1 + 3], 255);
+        self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
+        let pixels1 = self.render_and_get_pixels(window_rect);
+
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+        let mut txn = Transaction::new();
 
-        let p3 = (15 + (h - 15) * w) * 4;
-        assert_eq!(pixels[p3 + 0], 50);
-        assert_eq!(pixels[p3 + 1], 50);
-        assert_eq!(pixels[p3 + 2], 150);
-        assert_eq!(pixels[p3 + 3], 255);
+        let blob_img2 = self.wrench.api.generate_blob_image_key();
+        txn.add_blob_image(
+            blob_img2,
+            ImageDescriptor::new(
+                image_size.width as i32,
+                image_size.height as i32,
+                ImageFormat::BGRA8,
+                false,
+                false
+            ),
+            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
+            // Set a visible rectangle that is too small.
+            // This will force sync rasterization of the missing tiles during frame building.
+            DeviceIntRect {
+                origin: point2(200, 200),
+                size: size2(80, 80),
+            },
+            Some(100),
+        );
 
-        let p2 = (25 + (h - 15) * w) * 4;
-        assert_eq!(pixels[p2 + 0], 221);
-        assert_eq!(pixels[p2 + 1], 221);
-        assert_eq!(pixels[p2 + 2], 221);
-        assert_eq!(pixels[p2 + 3], 255);
+        builder.push_image(
+            &info,
+            info.clip_rect,
+            image_size,
+            image_size,
+            ImageRendering::Auto,
+            AlphaType::PremultipliedAlpha,
+            blob_img2.as_image(),
+            ColorF::WHITE,
+        );
 
-        // Leaving a tiled blob image in the resource cache
-        // confuses the `test_capture`. TODO: remove this
+        self.submit_dl(&mut Epoch(1), layout_size, builder, &txn.resource_updates);
+        let pixels2 = self.render_and_get_pixels(window_rect);
+
+        assert!(pixels1 == pixels2);
+
         txn = Transaction::new();
-        txn.delete_blob_image(blob_img);
+        txn.delete_blob_image(blob_img1);
+        txn.delete_blob_image(blob_img2);
         self.wrench.api.update_resources(txn.resource_updates);
-
-        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_offscreen_blob(&mut self) {
         println!("\toffscreen blob update...");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
--- a/layout/reftests/border-image/reftest.list
+++ b/layout/reftests/border-image/reftest.list
@@ -38,48 +38,48 @@ fails-if(Android) fails-if(usesRepeatRes
 == border-image-outset-resize-1.html border-image-outset-resize-1-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,0-140,0-514) fuzzy-if(winWidget,0-144,0-448) == border-image-outset-move-1.html border-image-outset-move-1-ref.html
 == border-image-style-none.html border-image-style-none-ref.html
 == border-image-style-none-length.html border-image-style-none-length-ref.html
 == border-image-style-none-auto.html border-image-style-none-auto-ref.html
 
 # border images with gradients
 fuzzy-if(webrender&&!geckoview,1-3,1488-1804) == border-image-linear-gradient.html border-image-linear-gradient-ref.html
-fuzzy(0-1,0-98) fuzzy-if(skiaContent,0-1,0-350) fuzzy-if(webrender&&!geckoview,1-3,36774-37537) == border-image-linear-gradient-slice-1.html border-image-linear-gradient-slice-1-ref.html
+fuzzy(0-1,0-98) fuzzy-if(skiaContent,0-1,0-350) fuzzy-if(webrender&&!geckoview,1-2,36774-37537) == border-image-linear-gradient-slice-1.html border-image-linear-gradient-slice-1-ref.html
 fuzzy(0-1,0-149) fuzzy-if(OSX,0-1,0-10595) fuzzy-if(webrender&&!geckoview,1-3,24999-25136) == border-image-linear-gradient-slice-2.html border-image-linear-gradient-slice-2-ref.html
 fuzzy(0-1,0-433) fuzzy-if(skiaContent,0-1,0-2500) fuzzy-if(webrender&&!geckoview,1-3,83975-85584) == border-image-linear-gradient-slice-fill-1.html border-image-linear-gradient-slice-fill-1-ref.html
 fuzzy(0-1,0-177) fuzzy-if(OSX,0-1,0-25771) fuzzy-if(skiaContent&&!Android,0-1,0-400) fuzzy-if(Android,0-1,0-6093) fuzzy-if(webrender&&!geckoview,1-3,57249-57480) == border-image-linear-gradient-slice-fill-2.html border-image-linear-gradient-slice-fill-2-ref.html
 fuzzy(0-1,0-48)  fuzzy-if(OSX,0-5,0-1676) fuzzy-if(webrender&&!geckoview,1-1,4464-4537) == border-image-linear-gradient-width.html border-image-linear-gradient-width-ref.html
-fuzzy(0-1,0-5000) fuzzy-if(OSX,0-1,0-15000) fuzzy-if(webrender&&!geckoview,2-2,58625-59025) == border-image-linear-gradient-slice-width.html border-image-linear-gradient-slice-width-ref.html
-fuzzy(0-1,0-3000) fuzzy-if(OSX,0-1,0-6000) fuzzy-if(webrender&&!geckoview,1-3,25920-26413) == border-image-linear-gradient-outset.html border-image-linear-gradient-outset-ref.html
+fuzzy(0-1,0-5000) fuzzy-if(OSX,0-1,0-15000) fuzzy-if(webrender&&!geckoview,2-2,58625-58987) == border-image-linear-gradient-slice-width.html border-image-linear-gradient-slice-width-ref.html
+fuzzy(0-1,0-3000) fuzzy-if(OSX,0-1,0-6000) fuzzy-if(webrender&&!geckoview,1-3,25940-26413) == border-image-linear-gradient-outset.html border-image-linear-gradient-outset-ref.html
 fuzzy(0-1,0-12) fuzzy-if(skiaContent,0-1,0-400) fuzzy-if(webrender&&!geckoview,1-3,25966-26872) == border-image-linear-gradient-repeat-repeat-1.html border-image-linear-gradient-repeat-repeat-1-ref.html
 fuzzy(0-1,0-13) fuzzy-if(skiaContent,0-1,0-300) fuzzy-if(webrender&&!geckoview,1-3,26038-27131) == border-image-linear-gradient-repeat-round-1.html border-image-linear-gradient-repeat-round-1-ref.html
-fuzzy-if(webrender&&!geckoview,1-2,60435-67805) == border-image-linear-gradient-repeat-repeat-2.html border-image-linear-gradient-repeat-repeat-2-ref.html
-fuzzy(0-1,0-576) fuzzy-if(skiaContent,0-1,0-2000) fuzzy-if(webrender&&!geckoview,1-2,60802-68383) == border-image-linear-gradient-repeat-round-2.html border-image-linear-gradient-repeat-round-2-ref.html
+fuzzy-if(webrender&&!geckoview,1-2,61283-67805) == border-image-linear-gradient-repeat-repeat-2.html border-image-linear-gradient-repeat-repeat-2-ref.html
+fuzzy(0-1,0-576) fuzzy-if(skiaContent,0-1,0-2000) fuzzy-if(webrender&&!geckoview,1-2,61764-68383) == border-image-linear-gradient-repeat-round-2.html border-image-linear-gradient-repeat-round-2-ref.html
 fuzzy(0-1,0-8533) fuzzy-if(webrender&&!geckoview,1-3,64622-85925) == border-image-linear-gradient-repeat-repeat-3.html border-image-linear-gradient-repeat-repeat-3-ref.html
-fuzzy(0-1,0-7161) fuzzy-if(webrender&&!geckoview,1-3,84995-86037) == border-image-linear-gradient-repeat-round-3.html border-image-linear-gradient-repeat-round-3-ref.html
+fuzzy(0-1,0-7161) fuzzy-if(webrender&&!geckoview,1-3,85018-86037) == border-image-linear-gradient-repeat-round-3.html border-image-linear-gradient-repeat-round-3-ref.html
 
 fuzzy-if(webrender,0-1,0-2096) == border-image-radial-gradient.html border-image-radial-gradient-ref.html
 fuzzy(0-1,0-42) fuzzy-if(skiaContent,0-2,0-20) fuzzy-if(webrender,0-1,0-37818) == border-image-radial-gradient-slice-1.html border-image-radial-gradient-slice-1-ref.html
 fuzzy(0-1,0-46) fuzzy-if(OSX,0-2,0-4472) fuzzy-if(webrender,0-1,0-26363) == border-image-radial-gradient-slice-2.html border-image-radial-gradient-slice-2-ref.html
 fuzzy(0-1,0-105) fuzzy-if(webrender,0-1,0-90873) == border-image-radial-gradient-slice-fill-1.html border-image-radial-gradient-slice-fill-1-ref.html
 fuzzy(0-1,0-139) fuzzy-if(OSX,0-2,0-4478) fuzzy-if(skiaContent,0-2,0-120) fuzzy-if(webrender,0-1,0-61729) == border-image-radial-gradient-slice-fill-2.html border-image-radial-gradient-slice-fill-2-ref.html
 fuzzy-if(skiaContent,0-1,0-2) fuzzy-if(webrender,0-1,0-4894) == border-image-radial-gradient-width.html border-image-radial-gradient-width-ref.html
 fuzzy(0-1,0-9000) fuzzy-if(webrender,0-3,0-66698) == border-image-radial-gradient-slice-width.html border-image-radial-gradient-slice-width-ref.html
 
 # OS X failures tracked in bug 957025
 fuzzy-if(webrender&&!geckoview,1-5,1766-1824) == border-image-repeating-linear-gradient.html border-image-repeating-linear-gradient-ref.html
 fuzzy(0-1,0-5608) fails-if(OSX) fuzzy-if(skiaContent,0-1,0-6093) fuzzy-if(webrender,0-3,0-95449) == border-image-repeating-linear-gradient-slice-fill-2.html border-image-repeating-linear-gradient-slice-fill-2-ref.html
-fuzzy(0-1,0-19200) fails-if(OSX) fuzzy-if(skiaContent,0-3,0-20000) fuzzy-if(webrender&&!geckoview,1-4,67336-70767) == border-image-repeating-linear-gradient-repeat-round-2.html border-image-repeating-linear-gradient-repeat-round-2-ref.html
+fuzzy(0-1,0-19200) fails-if(OSX) fuzzy-if(skiaContent,0-3,0-20000) fuzzy-if(webrender&&!geckoview,1-4,67400-70767) == border-image-repeating-linear-gradient-repeat-round-2.html border-image-repeating-linear-gradient-repeat-round-2-ref.html
 
 fuzzy(0-1,0-657) fuzzy-if(webrender,0-3,0-3008) == border-image-repeating-radial-gradient.html border-image-repeating-radial-gradient-ref.html
 fuzzy(0-1,0-510) fuzzy-if(skiaContent,0-3,0-362) fuzzy-if(webrender,0-3,0-62078) == border-image-repeating-radial-gradient-slice-1.html border-image-repeating-radial-gradient-slice-1-ref.html
 fuzzy(0-1,0-438) fuzzy-if(skiaContent,0-3,0-437) fuzzy-if(webrender,0-3,0-40536) == border-image-repeating-radial-gradient-slice-2.html border-image-repeating-radial-gradient-slice-2-ref.html
-fuzzy(0-1,0-1357) fuzzy-if(skiaContent,0-3,0-964) fuzzy-if(webrender&&!geckoview,1-4,85710-85915) == border-image-repeating-radial-gradient-slice-fill-1.html border-image-repeating-radial-gradient-slice-fill-1-ref.html
-fuzzy(0-1,0-1058) fails-if(OSX) fuzzy-if(skiaContent,0-3,0-887) fuzzy-if(webrender&&!geckoview,1-4,57065-57318) == border-image-repeating-radial-gradient-slice-fill-2.html border-image-repeating-radial-gradient-slice-fill-2-ref.html
+fuzzy(0-1,0-1357) fuzzy-if(skiaContent,0-3,0-964) fuzzy-if(webrender&&!geckoview,1-4,85720-85915) == border-image-repeating-radial-gradient-slice-fill-1.html border-image-repeating-radial-gradient-slice-fill-1-ref.html
+fuzzy(0-1,0-1058) fails-if(OSX) fuzzy-if(skiaContent,0-3,0-887) fuzzy-if(webrender&&!geckoview,1-4,57067-57318) == border-image-repeating-radial-gradient-slice-fill-2.html border-image-repeating-radial-gradient-slice-fill-2-ref.html
 fuzzy(0-1,0-602) fuzzy-if(webrender,0-3,0-7441) == border-image-repeating-radial-gradient-width.html border-image-repeating-radial-gradient-width-ref.html
 fuzzy(0-3,0-18000) fails-if(OSX) fuzzy-if(skiaContent,0-4,0-16462) fuzzy-if(webrender,0-5,0-99728) == border-image-repeating-radial-gradient-slice-width.html border-image-repeating-radial-gradient-slice-width-ref.html
 fuzzy-if(webrender,0-3,0-117768) == border-image-repeating-radial-gradient-repeat-repeat-2.html border-image-repeating-radial-gradient-repeat-repeat-2-ref.html
 fuzzy(0-1,0-1054) fails-if(OSX) fuzzy-if(skiaContent,0-2,0-952) fuzzy-if(webrender,0-3,0-116185) == border-image-repeating-radial-gradient-repeat-round-2.html border-image-repeating-radial-gradient-repeat-round-2-ref.html
 
 # border-image-source (-moz-)element
 fuzzy(0-125,0-5903) == border-image-element.html border-image-element-ref.html
 
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -18,17 +18,17 @@ fuzzy-if(skiaContent,0-2,0-500) == 39376
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == dir-2.html dir-2-ref.html # Bug 1392106
 random-if(gtkWidget) == dir-3.html dir-3-ref.html # bug 1309426
 == dir-4.html dir-4-ref.html
 == dir-5.html dir-5-ref.html
 fuzzy-if(cocoaWidget,0-135,0-56) == dir-6a.html dir-6a-ref.html
 == css-spacing-1.html css-spacing-1-ref.html
 pref(mathml.disabled,true) == disabled-scriptlevel-1.html disabled-scriptlevel-1-ref.html
 pref(mathml.disabled,true) == disabled-scriptlevel-1.xhtml disabled-scriptlevel-1-ref.xhtml
-random-if(smallScreen&&Android) fuzzy(0-255,0-200) fuzzy-if(webrender&&winWidget,114-255,245-304) fuzzy-if(webrender&&OSX,87-153,242-262) == mirror-op-1.html mirror-op-1-ref.html
+random-if(smallScreen&&Android) fuzzy(0-255,0-200) fuzzy-if(webrender&&winWidget,114-255,265-304) fuzzy-if(webrender&&OSX,87-87,242-242) == mirror-op-1.html mirror-op-1-ref.html
 != mirror-op-2.html mirror-op-2-ref.html
 != mirror-op-3.html mirror-op-3-ref.html
 != mirror-op-4.html mirror-op-4-ref.html
 == dynamic-mi.xhtml dynamic-mi-ref.xhtml
 == mphantom-1.html mphantom-1-ref.html
 == mphantom-2.html mphantom-2-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == mfenced-1.xhtml mfenced-1-ref.xhtml # Bug 1392106
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == mfenced-2a.xhtml mfenced-2-ref.xhtml # Bug 1392106
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -99,17 +99,17 @@ fails == filter-marked-line-01.svg pass.
 fuzzy(0-1,0-26732) == feComposite-paint-01.svg feComposite-paint-01-ref.svg
 fuzzy(0-1,0-10000) == feConvolveMatrix-bias-01.svg feConvolveMatrix-bias-01-ref.svg
 == feConvolveMatrix-order-01.svg feConvolveMatrix-order-01-ref.svg
 
 fuzzy-if(skiaContent,0-1,0-400) == feDisplacementMap-alpha-01.svg pass.svg
 fuzzy(0-2,0-500) == feDisplacementMap-colour-01.svg feDisplacementMap-colour-01-ref.svg
 == feDisplacementMap-scale-01.svg pass.svg
 
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-2,0-25) fuzzy-if(webrender,56-98,14033-15939) == feDropShadow-01.svg feDropShadow-01-ref.svg
+fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-2,0-25) fuzzy-if(webrender,56-57,14033-15586) == feDropShadow-01.svg feDropShadow-01-ref.svg
 
 == feFlood-color-01.svg pass.svg
 
 fuzzy-if(webrender,20-21,5624-5646) == feGaussianBlur-alpha-01.svg feGaussianBlur-alpha-01-ref.svg
 
 == feMorphology-radius-negative-01.svg pass.svg
 == feMorphology-radius-negative-02.svg pass.svg
 == feMorphology-radius-zero-01.svg pass.svg
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -335,32 +335,32 @@ random-if(gtkWidget) == objectBoundingBo
 
 == opacity-and-gradient-01.svg pass.svg
 skip-if(d2d) fuzzy-if(cocoaWidget,0-1,0-99974) fuzzy-if(skiaContent,0-1,0-200000) == opacity-and-gradient-02.svg opacity-and-gradient-02-ref.svg
 == opacity-and-pattern-01.svg pass.svg
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||skiaContent,0-1,0-10000) == opacity-and-transform-01.svg opacity-and-transform-01-ref.svg
 
 fuzzy-if(Android,0-8,0-200) == outer-svg-border-and-padding-01.svg outer-svg-border-and-padding-01-ref.svg
 
-fuzzy(0-16,0-193) fuzzy-if(skiaContent,0-7,0-193) == outline.html outline-ref.html # Bug 1392106, Bug 1503525
+fuzzy(0-16,0-193) fuzzy-if(skiaContent,0-7,0-193) fuzzy-if(webrender,54-54,124-435) == outline.html outline-ref.html # Bug 1392106, Bug 1503525
 
 == overflow-on-outer-svg-01.svg overflow-on-outer-svg-01-ref.svg
 == overflow-on-outer-svg-02a.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02b.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02c.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02d.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-03a.xhtml overflow-on-outer-svg-03-ref.xhtml
 == overflow-on-outer-svg-03b.xhtml overflow-on-outer-svg-03-ref.xhtml
 
 == paint-on-maskLayer-1a.html paint-on-maskLayer-1-ref.html
 == paint-on-maskLayer-1b.html paint-on-maskLayer-1-ref.html
 == paint-on-maskLayer-1c.html paint-on-maskLayer-1-ref.html
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-16,0-5) == paint-order-01.svg paint-order-01-ref.svg
 == paint-order-02.svg paint-order-02-ref.svg
-== paint-order-03.svg paint-order-03-ref.svg
+fuzzy-if(webrender,72-73,96-1804) == paint-order-03.svg paint-order-03-ref.svg
 
 #fuzzy(0-23,0-60) fails-if(d2d) == path-01.svg path-01-ref.svg
 == path-02.svg pass.svg
 == path-04.svg pass.svg
 == path-05.svg pass.svg
 fuzzy-if(skiaContent,0-1,0-400) == path-06.svg path-06-ref.svg
 == path-07.svg path-07-ref.svg
 == path-08.svg pass.svg
--- a/layout/reftests/text-stroke/reftest.list
+++ b/layout/reftests/text-stroke/reftest.list
@@ -1,10 +1,10 @@
 # basic tests for webkit-text-stroke
 # fuzzy is needed here for platform dependent backends
 
 # These fail on Linux without webrender due to lack of antialiasing of the HTML text stroke
-fuzzy(0-64,0-52) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-64,0-770) == webkit-text-stroke-property-001.html webkit-text-stroke-property-001-ref.html
+fuzzy(0-64,0-47) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-64,0-770) == webkit-text-stroke-property-001.html webkit-text-stroke-property-001-ref.html
 fuzzy(0-4,0-24) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-3,0-1476) == webkit-text-stroke-property-002.html webkit-text-stroke-property-002-ref.html
-fuzzy(0-48,0-28) fails-if(gtkWidget&&!webrender) fuzzy-if(webrender,0-63,0-97) == webkit-text-stroke-property-003.html webkit-text-stroke-property-003-ref.html
-fuzzy(0-64,0-33) fails-if(gtkWidget&&!webrender) fuzzy-if(webrender,0-48,0-342) == webkit-text-stroke-property-004.html webkit-text-stroke-property-004-ref.html
-fuzzy(0-64,0-47) fails-if(gtkWidget&&!webrender) fuzzy-if(webrender,0-64,0-776) == webkit-text-stroke-property-005.html webkit-text-stroke-property-005-ref.html
+fuzzy(0-48,0-28) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-63,0-97) == webkit-text-stroke-property-003.html webkit-text-stroke-property-003-ref.html
+fuzzy(0-64,0-33) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-48,0-342) == webkit-text-stroke-property-004.html webkit-text-stroke-property-004-ref.html
+fuzzy(0-64,0-47) fails-if(gtkWidget&&!webrender) fuzzy-if(geckoview&&webrender,0-64,0-776) == webkit-text-stroke-property-005.html webkit-text-stroke-property-005-ref.html
 fuzzy(0-71,0-10) fails-if(gtkWidget&&!webrender) == webkit-text-stroke-property-006.html webkit-text-stroke-property-006-ref.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/svg/painting/reftests/markers-orient-001.svg.ini
@@ -0,0 +1,6 @@
+[markers-orient-001.svg]
+  expected:
+    if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): FAIL
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): FAIL
+    if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): FAIL
+    if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): FAIL