Bug 1527085 - Ensure we invalidate when the image request changes. r=jrmuizel
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 13 Feb 2019 14:13:55 -0500
changeset 459482 43678f936e37673d325bd3bf3d300ea69aad5bae
parent 459481 80e1aa2d745901c8825b9bb33402b087c1648218
child 459483 3f188359a08cd8b1775881cfcc9b61eaa8f82356
child 459552 8961019ee4c6fe27e6319d9cf9ea6867b4f3c7bb
push id111958
push useraosmond@gmail.com
push dateFri, 15 Feb 2019 14:24:39 +0000
treeherdermozilla-inbound@43678f936e37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1527085
milestone67.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 1527085 - Ensure we invalidate when the image request changes. r=jrmuizel When the underlying image request (imgIRequest) changes for an image, we need to ensure that we invalidate the cached WebRenderImageData such that the image container stored therein is updated to be for the correct image. This gets a little tricky because some display items store both the current and previous images, and choose to display the latter if the former is not yet ready. We also don't know what image the image container belongs to. As such, we now compare the producer ID of the current frame in the image container, to the expected producer ID of the current image request. If they don't match, we must regenerate the display list. Differential Revision: https://phabricator.services.mozilla.com/D19699
gfx/layers/ImageContainer.h
gfx/layers/ImageTypes.h
gfx/layers/ipc/SharedSurfacesChild.cpp
gfx/layers/ipc/SharedSurfacesChild.h
gfx/layers/wr/WebRenderUserData.cpp
gfx/layers/wr/WebRenderUserData.h
image/DynamicImage.cpp
image/Image.h
image/ImageWrapper.cpp
image/RasterImage.cpp
image/VectorImage.cpp
image/imgIContainer.idl
image/imgIRequest.idl
image/imgRequestProxy.cpp
layout/generic/nsImageFrame.cpp
layout/style/ImageLoader.cpp
layout/style/ImageLoader.h
layout/xul/nsImageBoxFrame.cpp
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -382,18 +382,18 @@ class ImageContainer final : public Supp
 
   /**
    * Create ImageContainer just to hold another ASYNCHRONOUS ImageContainer's
    * async container ID.
    * @param aAsyncContainerID async container ID for which we are a proxy
    */
   explicit ImageContainer(const CompositableHandle& aHandle);
 
-  typedef uint32_t FrameID;
-  typedef uint32_t ProducerID;
+  typedef ContainerFrameID FrameID;
+  typedef ContainerProducerID ProducerID;
 
   RefPtr<PlanarYCbCrImage> CreatePlanarYCbCrImage();
 
   // Factory methods for shared image types.
   RefPtr<SharedRGBImage> CreateSharedRGBImage();
 
   struct NonOwningImage {
     explicit NonOwningImage(Image* aImage = nullptr,
--- a/gfx/layers/ImageTypes.h
+++ b/gfx/layers/ImageTypes.h
@@ -2,16 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_IMAGETYPES_H
 #define GFX_IMAGETYPES_H
 
+#include <stdint.h>     // for uint32_t
+
 namespace mozilla {
 
 enum class ImageFormat {
   /**
    * The PLANAR_YCBCR format creates a PlanarYCbCrImage. All backends should
    * support this format, because the Ogg video decoder depends on it.
    * The maximum image width and height is 16384.
    */
@@ -101,11 +103,21 @@ enum class StereoMode {
 
 enum class YUVColorSpace {
   BT601,
   BT709,
   // This represents the unknown format.
   UNKNOWN,
 };
 
+namespace layers {
+
+typedef uint32_t ContainerFrameID;
+constexpr ContainerFrameID kContainerFrameID_Invalid = 0;
+
+typedef uint32_t ContainerProducerID;
+constexpr ContainerProducerID kContainerProducerID_Invalid = 0;
+
+}  // namespace layers
+
 }  // namespace mozilla
 
 #endif
--- a/gfx/layers/ipc/SharedSurfacesChild.cpp
+++ b/gfx/layers/ipc/SharedSurfacesChild.cpp
@@ -320,31 +320,41 @@ SharedSurfacesChild::AsSourceSurfaceShar
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   return Share(sharedSurface, aManager, aResources, aKey);
 }
 
 /* static */ nsresult SharedSurfacesChild::Share(
     ImageContainer* aContainer, RenderRootStateManager* aManager,
-    wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) {
+    wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey,
+    ContainerProducerID aProducerId) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aContainer);
   MOZ_ASSERT(aManager);
 
   if (aContainer->IsAsync()) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   AutoTArray<ImageContainer::OwningImage, 4> images;
   aContainer->GetCurrentImages(&images);
   if (images.IsEmpty()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (aProducerId != kContainerProducerID_Invalid &&
+      images[0].mProducerID != aProducerId) {
+    // If the producer ID of the surface in the container does not match the
+    // expected producer ID, then we do not want to proceed with sharing. This
+    // is useful for when callers are unsure if given container is for the same
+    // producer / underlying image request.
+    return NS_ERROR_FAILURE;
+  }
+
   RefPtr<gfx::SourceSurface> surface = images[0].mImage->GetAsSourceSurface();
   if (!surface) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   auto sharedSurface = AsSourceSurfaceSharedData(surface);
   if (!sharedSurface) {
     return NS_ERROR_NOT_IMPLEMENTED;
--- a/gfx/layers/ipc/SharedSurfacesChild.h
+++ b/gfx/layers/ipc/SharedSurfacesChild.h
@@ -10,16 +10,17 @@
 #include <stdint.h>                            // for uint32_t, uint64_t
 #include "mozilla/Attributes.h"                // for override
 #include "mozilla/Maybe.h"                     // for Maybe
 #include "mozilla/RefPtr.h"                    // for already_AddRefed
 #include "mozilla/StaticPtr.h"                 // for StaticRefPtr
 #include "mozilla/gfx/UserData.h"              // for UserDataKey
 #include "mozilla/webrender/WebRenderTypes.h"  // for wr::ImageKey
 #include "nsTArray.h"                          // for AutoTArray
+#include "ImageTypes.h"                        // for ContainerProducerID
 
 namespace mozilla {
 namespace layers {
 class AnimationImageKeyData;
 }  // namespace layers
 }  // namespace mozilla
 
 template <>
@@ -86,17 +87,17 @@ class SharedSurfacesChild final {
    * mapped into the compositor thread's memory space, and a valid ImageKey be
    * generated for it for use with WebRender. If a different method should be
    * used to share the image data for this particular container, it will return
    * NS_ERROR_NOT_IMPLEMENTED. This must be called from the main thread.
    */
   static nsresult Share(ImageContainer* aContainer,
                         RenderRootStateManager* aManager,
                         wr::IpcResourceUpdateQueue& aResources,
-                        wr::ImageKey& aKey);
+                        wr::ImageKey& aKey, ContainerProducerID aProducerId);
 
   /**
    * Get the external ID, if any, bound to the shared surface. Used for memory
    * reporting purposes.
    */
   static Maybe<wr::ExternalImageId> GetExternalId(
       const gfx::SourceSurfaceSharedData* aSurface);
 
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -34,17 +34,17 @@ void WebRenderBackgroundData::AddWebRend
   if (data) {
     return data->IsAsync();
   }
 
   return false;
 }
 
 /* static */ bool WebRenderUserData::ProcessInvalidateForImage(
-    nsIFrame* aFrame, DisplayItemType aType) {
+    nsIFrame* aFrame, DisplayItemType aType, ContainerProducerID aProducerId) {
   MOZ_ASSERT(aFrame);
 
   if (!aFrame->HasProperty(WebRenderUserDataProperty::Key())) {
     return false;
   }
 
   auto type = static_cast<uint32_t>(aType);
   RefPtr<WebRenderFallbackData> fallback =
@@ -52,17 +52,17 @@ void WebRenderBackgroundData::AddWebRend
   if (fallback) {
     fallback->SetInvalid(true);
     aFrame->SchedulePaint();
     return true;
   }
 
   RefPtr<WebRenderImageData> image =
       GetWebRenderUserData<WebRenderImageData>(aFrame, type);
-  if (image && image->UsingSharedSurface()) {
+  if (image && image->UsingSharedSurface(aProducerId)) {
     return true;
   }
 
   aFrame->SchedulePaint();
   return false;
 }
 
 WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager,
@@ -102,27 +102,28 @@ WebRenderImageData::WebRenderImageData(R
 WebRenderImageData::~WebRenderImageData() {
   ClearImageKey();
 
   if (mPipelineId) {
     mManager->RemovePipelineIdForCompositable(mPipelineId.ref());
   }
 }
 
-bool WebRenderImageData::UsingSharedSurface() const {
+bool WebRenderImageData::UsingSharedSurface(
+    ContainerProducerID aProducerId) const {
   if (!mContainer || !mKey || mOwnsKey) {
     return false;
   }
 
   // If this is just an update with the same image key, then we know that the
   // share request initiated an asynchronous update so that we don't need to
   // rebuild the scene.
   wr::ImageKey key;
   nsresult rv = SharedSurfacesChild::Share(
-      mContainer, mManager, mManager->AsyncResourceUpdates(), key);
+      mContainer, mManager, mManager->AsyncResourceUpdates(), key, aProducerId);
   return NS_SUCCEEDED(rv) && mKey.ref() == key;
 }
 
 void WebRenderImageData::ClearImageKey() {
   if (mKey) {
     // If we don't own the key, then the owner is responsible for discarding the
     // key when appropriate.
     if (mOwnsKey) {
@@ -144,18 +145,18 @@ Maybe<wr::ImageKey> WebRenderImageData::
   MOZ_ASSERT(aContainer);
 
   if (mContainer != aContainer) {
     mContainer = aContainer;
   }
 
   wr::WrImageKey key;
   if (!aFallback) {
-    nsresult rv =
-        SharedSurfacesChild::Share(aContainer, mManager, aResources, key);
+    nsresult rv = SharedSurfacesChild::Share(aContainer, mManager, aResources,
+                                             key, kContainerProducerID_Invalid);
     if (NS_SUCCEEDED(rv)) {
       // Ensure that any previously owned keys are released before replacing. We
       // don't own this key, the surface itself owns it, so that it can be
       // shared across multiple elements.
       ClearImageKey();
       mKey = Some(key);
       return mKey;
     }
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -8,16 +8,17 @@
 #define GFX_WEBRENDERUSERDATA_H
 
 #include <vector>
 #include "BasicLayers.h"  // for BasicLayerManager
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/layers/AnimationInfo.h"
 #include "nsIFrame.h"
+#include "ImageTypes.h"
 
 class nsDisplayItemGeometry;
 
 namespace mozilla {
 namespace wr {
 class IpcResourceUpdateQueue;
 }
 
@@ -52,18 +53,18 @@ class WebRenderBackgroundData {
 /// to an nsFrame.
 class WebRenderUserData {
  public:
   typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData>>
       WebRenderUserDataRefTable;
 
   static bool SupportsAsyncUpdate(nsIFrame* aFrame);
 
-  static bool ProcessInvalidateForImage(nsIFrame* aFrame,
-                                        DisplayItemType aType);
+  static bool ProcessInvalidateForImage(nsIFrame* aFrame, DisplayItemType aType,
+                                        ContainerProducerID aProducerId);
 
   NS_INLINE_DECL_REFCOUNTING(WebRenderUserData)
 
   WebRenderUserData(RenderRootStateManager* aManager, nsDisplayItem* aItem);
   WebRenderUserData(RenderRootStateManager* aManager, uint32_t mDisplayItemKey, nsIFrame* aFrame);
 
   virtual WebRenderImageData* AsImageData() { return nullptr; }
   virtual WebRenderFallbackData* AsFallbackData() { return nullptr; }
@@ -146,17 +147,17 @@ class WebRenderImageData : public WebRen
       const LayoutDeviceRect& aSCBounds, const gfx::Matrix4x4& aSCTransform,
       const gfx::MaybeIntSize& aScaleToSize, const wr::ImageRendering& aFilter,
       const wr::MixBlendMode& aMixBlendMode, bool aIsBackfaceVisible);
 
   void CreateImageClientIfNeeded();
 
   bool IsAsync() { return mPipelineId.isSome(); }
 
-  bool UsingSharedSurface() const;
+  bool UsingSharedSurface(ContainerProducerID aProducerId) const;
 
   void ClearImageKey();
 
  protected:
   Maybe<wr::ImageKey> mKey;
   RefPtr<TextureClient> mTextureOfImage;
   RefPtr<ImageClient> mImageClient;
   Maybe<wr::PipelineId> mPipelineId;
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -116,16 +116,22 @@ DynamicImage::GetOrientation() { return 
 
 NS_IMETHODIMP
 DynamicImage::GetType(uint16_t* aType) {
   *aType = imgIContainer::TYPE_RASTER;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+DynamicImage::GetProducerId(uint32_t* aId) {
+  *aId = 0;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DynamicImage::GetAnimated(bool* aAnimated) {
   *aAnimated = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 DynamicImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
   IntSize size(mDrawable->Size());
--- a/image/Image.h
+++ b/image/Image.h
@@ -266,16 +266,20 @@ class ImageResource : public Image {
    * Illegal to use off-main-thread.
    */
   nsIURI* GetURI() const override { return mURI; }
 
  protected:
   explicit ImageResource(nsIURI* aURI);
   ~ImageResource();
 
+  layers::ContainerProducerID GetImageProducerId() const {
+    return mImageProducerID;
+  }
+
   bool GetSpecTruncatedTo1k(nsCString& aSpec) const;
 
   // Shared functionality for implementors of imgIContainer. Every
   // implementation of attribute animationMode should forward here.
   nsresult GetAnimationModeInternal(uint16_t* aAnimationMode);
   nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
 
   /**
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -127,16 +127,21 @@ ImageWrapper::GetIntrinsicRatio(nsSize* 
 
 NS_IMETHODIMP_(Orientation)
 ImageWrapper::GetOrientation() { return mInnerImage->GetOrientation(); }
 
 NS_IMETHODIMP
 ImageWrapper::GetType(uint16_t* aType) { return mInnerImage->GetType(aType); }
 
 NS_IMETHODIMP
+ImageWrapper::GetProducerId(uint32_t* aId) {
+  return mInnerImage->GetProducerId(aId);
+}
+
+NS_IMETHODIMP
 ImageWrapper::GetAnimated(bool* aAnimated) {
   return mInnerImage->GetAnimated(aAnimated);
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 ImageWrapper::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
   return mInnerImage->GetFrame(aWhichFrame, aFlags);
 }
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -271,16 +271,24 @@ RasterImage::GetOrientation() { return m
 NS_IMETHODIMP
 RasterImage::GetType(uint16_t* aType) {
   NS_ENSURE_ARG_POINTER(aType);
 
   *aType = imgIContainer::TYPE_RASTER;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+RasterImage::GetProducerId(uint32_t* aId) {
+  NS_ENSURE_ARG_POINTER(aId);
+
+  *aId = ImageResource::GetImageProducerId();
+  return NS_OK;
+}
+
 LookupResult RasterImage::LookupFrameInternal(const IntSize& aSize,
                                               uint32_t aFlags,
                                               PlaybackType aPlaybackType,
                                               bool aMarkUsed) {
   if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) {
     MOZ_ASSERT(mFrameAnimator);
     MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
                "Can't composite frames with non-default surface flags");
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -658,16 +658,25 @@ VectorImage::GetType(uint16_t* aType) {
   NS_ENSURE_ARG_POINTER(aType);
 
   *aType = imgIContainer::TYPE_VECTOR;
   return NS_OK;
 }
 
 //******************************************************************************
 NS_IMETHODIMP
+VectorImage::GetProducerId(uint32_t* aId) {
+  NS_ENSURE_ARG_POINTER(aId);
+
+  *aId = ImageResource::GetImageProducerId();
+  return NS_OK;
+}
+
+//******************************************************************************
+NS_IMETHODIMP
 VectorImage::GetAnimated(bool* aAnimated) {
   if (mError || !mIsFullyLoaded) {
     return NS_ERROR_FAILURE;
   }
 
   *aAnimated = mSVGDocumentWrapper->IsAnimated();
   return NS_OK;
 }
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -138,16 +138,21 @@ interface imgIContainer : nsISupports
    * Whether this image is animated. You can only be guaranteed that querying
    * this will not throw if STATUS_DECODE_COMPLETE is set on the imgIRequest.
    *
    * @throws NS_ERROR_NOT_AVAILABLE if the animated state cannot be determined.
    */
   readonly attribute boolean animated;
 
   /**
+   * Producer ID for image containers created by this image.
+   */
+  [infallible] readonly attribute unsigned long producerId;
+
+  /**
    * Flags for imgIContainer operations.
    *
    * Meanings:
    *
    * FLAG_NONE: Lack of flags.
    *
    * FLAG_SYNC_DECODE: Forces synchronous/non-progressive decode of all
    * available data before the call returns.
--- a/image/imgIRequest.idl
+++ b/image/imgIRequest.idl
@@ -25,16 +25,21 @@ interface imgIRequest : nsIRequest
   /**
    * the image container...
    * @return the image object associated with the request.
    * @attention NEED DOCS
    */
   readonly attribute imgIContainer image;
 
   /**
+   * Producer ID for image containers created by this image.
+   */
+  [infallible] readonly attribute unsigned long producerId;
+
+  /**
    * Bits set in the return value from imageStatus
    * @name statusflags
    *
    * Meanings:
    *
    * STATUS_NONE: Nothing to report.
    *
    * STATUS_SIZE_AVAILABLE: We received enough image data
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "imgRequestProxy.h"
 
 #include "ImageLogging.h"
 #include "imgLoader.h"
 #include "Image.h"
 #include "ImageOps.h"
+#include "ImageTypes.h"
 #include "nsError.h"
 #include "nsCRTGlue.h"
 #include "imgINotificationObserver.h"
 #include "mozilla/dom/TabGroup.h"  // for TabGroup
 #include "mozilla/dom/DocGroup.h"  // for DocGroup
 #include "mozilla/Move.h"
 #include "mozilla/Telemetry.h"  // for Telemetry
 
@@ -658,16 +659,31 @@ imgRequestProxy::GetImage(imgIContainer*
   }
 
   imageToReturn.swap(*aImage);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+imgRequestProxy::GetProducerId(uint32_t* aId) {
+  NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
+
+  nsCOMPtr<imgIContainer> image;
+  nsresult rv = GetImage(getter_AddRefs(image));
+  if (NS_SUCCEEDED(rv)) {
+    *aId = image->GetProducerId();
+  } else {
+    *aId = layers::kContainerProducerID_Invalid;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
   if (IsValidating()) {
     // We are currently validating the image, and so our status could revert if
     // we discard the cache. We should also be deferring notifications, such
     // that the caller will be notified when validation completes. Rather than
     // risk misleading the caller, return nothing.
     *aStatus = imgIRequest::STATUS_NONE;
   } else {
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -735,17 +735,19 @@ nsresult nsImageFrame::OnFrameUpdate(img
   return NS_OK;
 }
 
 void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
                                   const nsRect* aFrameInvalidRect) {
   // Check if WebRender has interacted with this frame. If it has
   // we need to let it know that things have changed.
   const auto type = DisplayItemType::TYPE_IMAGE;
-  if (WebRenderUserData::ProcessInvalidateForImage(this, type)) {
+  const auto producerId =
+      mImage ? mImage->GetProducerId() : kContainerProducerID_Invalid;
+  if (WebRenderUserData::ProcessInvalidateForImage(this, type, producerId)) {
     return;
   }
 
   InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect);
 
   if (!mFirstFrameComplete) {
     InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
                     aFrameInvalidRect);
--- a/layout/style/ImageLoader.cpp
+++ b/layout/style/ImageLoader.cpp
@@ -476,17 +476,17 @@ nsPresContext* ImageLoader::GetPresConte
 }
 
 static bool IsRenderNoImages(uint32_t aDisplayItemKey) {
   DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
   uint8_t flags = GetDisplayItemFlagsForType(type);
   return flags & TYPE_RENDERS_NO_IMAGES;
 }
 
-static void InvalidateImages(nsIFrame* aFrame) {
+static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest) {
   bool invalidateFrame = false;
   const SmallPointerArray<DisplayItemData>& array = aFrame->DisplayItemData();
   for (uint32_t i = 0; i < array.Length(); i++) {
     DisplayItemData* data =
         DisplayItemData::AssertDisplayItemData(array.ElementAt(i));
     uint32_t displayItemKey = data->GetDisplayItemKey();
     if (displayItemKey != 0 && !IsRenderNoImages(displayItemKey)) {
       if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
@@ -511,46 +511,48 @@ static void InvalidateImages(nsIFrame* a
             static_cast<layers::WebRenderFallbackData*>(data.get())
                 ->SetInvalid(true);
           }
           // XXX: handle Blob data
           invalidateFrame = true;
           break;
         case layers::WebRenderUserData::UserDataType::eImage:
           if (static_cast<layers::WebRenderImageData*>(data.get())
-                  ->UsingSharedSurface()) {
+                  ->UsingSharedSurface(aRequest->GetProducerId())) {
             break;
           }
           MOZ_FALLTHROUGH;
         default:
           invalidateFrame = true;
           break;
       }
     }
   }
 
   if (invalidateFrame) {
     aFrame->SchedulePaint();
   }
 }
 
-void ImageLoader::RequestPaintIfNeeded(FrameSet* aFrameSet, bool aForcePaint) {
+void ImageLoader::RequestPaintIfNeeded(FrameSet* aFrameSet,
+                                       imgIRequest* aRequest,
+                                       bool aForcePaint) {
   NS_ASSERTION(aFrameSet, "Must have a frame set");
   NS_ASSERTION(mDocument, "Should have returned earlier!");
 
   for (FrameWithFlags& fwf : *aFrameSet) {
     nsIFrame* frame = fwf.mFrame;
     if (frame->StyleVisibility()->IsVisible()) {
       if (frame->IsFrameOfType(nsIFrame::eTablePart)) {
         // Tables don't necessarily build border/background display items
         // for the individual table part frames, so IterateRetainedDataFor
         // might not find the right display item.
         frame->InvalidateFrame();
       } else {
-        InvalidateImages(frame);
+        InvalidateImages(frame, aRequest);
 
         // Update ancestor rendering observers (-moz-element etc)
         nsIFrame* f = frame;
         while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
           SVGObserverUtils::InvalidateDirectRenderingObservers(f);
           f = nsLayoutUtils::GetCrossDocParentFrame(f);
         }
 
@@ -725,32 +727,32 @@ nsresult ImageLoader::OnFrameComplete(im
   }
 
   // We may need reflow (for example if the image is from shape-outside).
   RequestReflowIfNeeded(frameSet, aRequest);
 
   // Since we just finished decoding a frame, we always want to paint, in case
   // we're now able to paint an image that we couldn't paint before (and hence
   // that we don't have retained data for).
-  RequestPaintIfNeeded(frameSet, /* aForcePaint = */ true);
+  RequestPaintIfNeeded(frameSet, aRequest, /* aForcePaint = */ true);
 
   return NS_OK;
 }
 
 nsresult ImageLoader::OnFrameUpdate(imgIRequest* aRequest) {
   if (!mDocument || mInClone) {
     return NS_OK;
   }
 
   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
   if (!frameSet) {
     return NS_OK;
   }
 
-  RequestPaintIfNeeded(frameSet, /* aForcePaint = */ false);
+  RequestPaintIfNeeded(frameSet, aRequest, /* aForcePaint = */ false);
 
   return NS_OK;
 }
 
 nsresult ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
   if (!mDocument || mInClone) {
     return NS_OK;
   }
--- a/layout/style/ImageLoader.h
+++ b/layout/style/ImageLoader.h
@@ -138,17 +138,18 @@ class ImageLoader final : public imgINot
   typedef nsTArray<FrameWithFlags> FrameSet;
   typedef nsTArray<nsCOMPtr<imgIRequest>> RequestSet;
   typedef nsClassHashtable<nsISupportsHashKey, FrameSet> RequestToFrameMap;
   typedef nsClassHashtable<nsPtrHashKey<nsIFrame>, RequestSet>
       FrameToRequestMap;
 
   nsPresContext* GetPresContext();
 
-  void RequestPaintIfNeeded(FrameSet* aFrameSet, bool aForcePaint);
+  void RequestPaintIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest,
+                            bool aForcePaint);
   void UnblockOnloadIfNeeded(nsIFrame* aFrame, imgIRequest* aRequest);
   void RequestReflowIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest);
   void RequestReflowOnFrame(FrameWithFlags* aFwf, imgIRequest* aRequest);
 
   nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnFrameComplete(imgIRequest* aRequest);
   nsresult OnImageIsAnimated(imgIRequest* aRequest);
   nsresult OnFrameUpdate(imgIRequest* aRequest);
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -819,17 +819,18 @@ nsresult nsImageBoxFrame::OnImageIsAnima
 nsresult nsImageBoxFrame::OnFrameUpdate(imgIRequest* aRequest) {
   if ((0 == mRect.width) || (0 == mRect.height)) {
     return NS_OK;
   }
 
   // Check if WebRender has interacted with this frame. If it has
   // we need to let it know that things have changed.
   const auto type = DisplayItemType::TYPE_XUL_IMAGE;
-  if (WebRenderUserData::ProcessInvalidateForImage(this, type)) {
+  const auto producerId = aRequest->GetProducerId();
+  if (WebRenderUserData::ProcessInvalidateForImage(this, type, producerId)) {
     return NS_OK;
   }
 
   InvalidateLayer(type);
 
   return NS_OK;
 }