Bug 1404222 Part 3: Block onload when shape-outside images are requested for a frame, and keep it blocked until the frame is removed or reflow is complete. r=dbaron,dholbert
☠☠ backed out by 98749dde9018 ☠ ☠
authorTing-Yu Lin <aethanyc@gmail.com>, Brad Werth <bwerth@mozilla.com>
Thu, 25 Jan 2018 14:56:43 +0800
changeset 464913 f6b9096da915f52a3b34e194beb9121e517bc823
parent 464912 4e0baffdd79bc8aa0f2708b4349115d3a3529fb8
child 464914 59038f2db68a14fbca498f408da8f9650c88efad
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, dholbert
bugs1404222
milestone61.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 1404222 Part 3: Block onload when shape-outside images are requested for a frame, and keep it blocked until the frame is removed or reflow is complete. r=dbaron,dholbert When we finish decoding an image frame, we need to trigger reflow for the frame containing a float with shape-outside: <image>, and delay the firing of the document's onload event until that reflow is complete.
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRenderingBorders.cpp
layout/style/ImageLoader.cpp
layout/style/ImageLoader.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/svg/SVGObserverUtils.cpp
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -101,24 +101,25 @@
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/ServoStyleSet.h"
-#include "mozilla/css/ImageLoader.h"
 #include "mozilla/gfx/Tools.h"
 #include "nsPrintfCString.h"
 #include "ActiveLayerTracker.h"
 
 #include "nsITheme.h"
 #include "nsThemeConstants.h"
 
+#include "ImageLoader.h"
+
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
 
@@ -902,17 +903,17 @@ AddAndRemoveImageAssociations(nsFrame* a
     CompareLayers(aOldLayers, aNewLayers,
       [&imageLoader, aFrame](imgRequestProxy* aReq)
       { imageLoader->DisassociateRequestFromFrame(aReq, aFrame); }
     );
   }
 
   CompareLayers(aNewLayers, aOldLayers,
     [&imageLoader, aFrame](imgRequestProxy* aReq)
-    { imageLoader->AssociateRequestToFrame(aReq, aFrame); }
+    { imageLoader->AssociateRequestToFrame(aReq, aFrame, 0); }
   );
 }
 
 void
 nsIFrame::AddDisplayItem(nsDisplayItem* aItem)
 {
   DisplayItemArray* items = GetProperty(DisplayItems());
   if (!items) {
@@ -1170,17 +1171,34 @@ nsFrame::DidSetComputedStyle(ComputedSty
   // to paint borders with a different style, because they won't have the
   // correct size for the border either.
   if (oldBorderImage != newBorderImage) {
     // stop and restart the image loading/notification
     if (oldBorderImage && HasImageRequest()) {
       imageLoader->DisassociateRequestFromFrame(oldBorderImage, this);
     }
     if (newBorderImage) {
-      imageLoader->AssociateRequestToFrame(newBorderImage, this);
+      imageLoader->AssociateRequestToFrame(newBorderImage, this, 0);
+    }
+  }
+
+  imgIRequest* oldShapeImage =
+      aOldComputedStyle
+    ? aOldComputedStyle->StyleDisplay()->mShapeOutside.GetShapeImageData()
+    : nullptr;
+  imgIRequest* newShapeImage =
+    StyleDisplay()->mShapeOutside.GetShapeImageData();
+
+  if (oldShapeImage != newShapeImage) {
+    if (oldShapeImage && HasImageRequest()) {
+      imageLoader->DisassociateRequestFromFrame(oldShapeImage, this);
+    }
+    if (newShapeImage) {
+      imageLoader->AssociateRequestToFrame(newShapeImage, this,
+        ImageLoader::REQUEST_REQUIRES_REFLOW);
     }
   }
 
   // If the page contains markup that overrides text direction, and
   // does not contain any characters that would activate the Unicode
   // bidi algorithm, we need to call |SetBidiEnabled| on the pres
   // context before reflow starts.  See bug 115921.
   if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
@@ -5215,32 +5233,32 @@ nsIFrame::ContentOffsets nsIFrame::GetCo
 }
 
 nsIFrame::ContentOffsets nsFrame::CalcContentOffsetsFromFramePoint(const nsPoint& aPoint)
 {
   return OffsetsForSingleFrame(this, aPoint);
 }
 
 void
-nsIFrame::AssociateImage(const nsStyleImage& aImage, nsPresContext* aPresContext)
+nsIFrame::AssociateImage(const nsStyleImage& aImage, nsPresContext* aPresContext,
+                         uint32_t aImageLoaderFlags)
 {
   if (aImage.GetType() != eStyleImageType_Image) {
     return;
   }
 
   imgRequestProxy* req = aImage.GetImageData();
   if (!req) {
     return;
   }
-
   mozilla::css::ImageLoader* loader =
     aPresContext->Document()->StyleImageLoader();
 
   // If this fails there's not much we can do ...
-  loader->AssociateRequestToFrame(req, this);
+  loader->AssociateRequestToFrame(req, this, aImageLoaderFlags);
 }
 
 nsresult
 nsFrame::GetCursor(const nsPoint& aPoint,
                    nsIFrame::Cursor& aCursor)
 {
   FillCursorInformationFromStyle(StyleUserInterface(), aCursor);
   if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1950,17 +1950,18 @@ public:
   virtual ContentOffsets GetContentOffsetsFromPointExternal(const nsPoint& aPoint,
                                                             uint32_t aFlags = 0)
   { return GetContentOffsetsFromPoint(aPoint, aFlags); }
 
   /**
    * Ensure that aImage gets notifed when the underlying image request loads
    * or animates.
    */
-  void AssociateImage(const nsStyleImage& aImage, nsPresContext* aPresContext);
+  void AssociateImage(const nsStyleImage& aImage, nsPresContext* aPresContext,
+                      uint32_t aImageLoaderFlags);
 
   /**
    * This structure holds information about a cursor. mContainer represents a
    * loaded image that should be preferred. If it is not possible to use it, or
    * if it is null, mCursor should be used.
    */
   struct MOZ_STACK_CLASS Cursor {
     nsCOMPtr<imgIContainer> mContainer;
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2740,17 +2740,17 @@ nsCSSRendering::PaintStyleImageLayerWith
   // association of the style data with the frame.
   if (aBackgroundSC != aParams.frame->Style()) {
     uint32_t startLayer = drawAllLayers ? layers.mImageCount - 1
                                         : aParams.layer;
     uint32_t count = drawAllLayers ? layers.mImageCount : 1;
     NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, startLayer,
                                                          count) {
       aParams.frame->AssociateImage(layers.mLayers[i].mImage,
-                                    &aParams.presCtx);
+                                    &aParams.presCtx, 0);
     }
   }
 
   // The background color is rendered over the entire dirty area,
   // even if the image isn't.
   if (drawBackgroundColor && !isCanvasFrame) {
     DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
   }
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -3481,17 +3481,17 @@ nsCSSBorderImageRenderer::CreateBorderIm
   }
 
   // Ensure we get invalidated for loads and animations of the image.
   // We need to do this here because this might be the only code that
   // knows about the association of the style data with the frame.
   // XXX We shouldn't really... since if anybody is passing in a
   // different style, they'll potentially have the wrong size for the
   // border too.
-  aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
+  aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext, 0);
 
   nsCSSBorderImageRenderer renderer(aForFrame, aBorderArea,
                                     aStyleBorder, aSkipSides, imgRenderer);
   *aDrawResult = ImgDrawResult::SUCCESS;
   return Some(renderer);
 }
 
 ImgDrawResult
--- a/layout/style/ImageLoader.cpp
+++ b/layout/style/ImageLoader.cpp
@@ -9,16 +9,17 @@
  */
 
 #include "mozilla/css/ImageLoader.h"
 #include "nsAutoPtr.h"
 #include "nsContentUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsError.h"
 #include "nsDisplayList.h"
+#include "nsIFrameInlines.h"
 #include "FrameLayerBuilder.h"
 #include "SVGObserverUtils.h"
 #include "imgIContainer.h"
 #include "Image.h"
 #include "GeckoProfiler.h"
 #include "mozilla/layers/WebRenderUserData.h"
 
 namespace mozilla {
@@ -42,17 +43,18 @@ ImageLoader::DropDocumentReference()
   }
   mImages.Clear();
 
   mDocument = nullptr;
 }
 
 void
 ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
-                                     nsIFrame* aFrame)
+                                     nsIFrame* aFrame,
+                                     FrameFlags aFlags)
 {
   nsCOMPtr<imgINotificationObserver> observer;
   aRequest->GetNotificationObserver(getter_AddRefs(observer));
   if (!observer) {
     // The request has already been canceled, so ignore it.  This is ok because
     // we're not going to get any more notifications from a canceled request.
     return;
   }
@@ -71,26 +73,73 @@ ImageLoader::AssociateRequestToFrame(img
     });
 
   RequestSet* requestSet =
     mFrameToRequestMap.LookupForAdd(aFrame).OrInsert([=]() {
       aFrame->SetHasImageRequest(true);
       return new RequestSet();
     });
 
-  // Add these to the sets, but only if they're not already there.
+  // Add frame to the frameSet, and handle any special processing the
+  // frame might require.
   FrameWithFlags fwf(aFrame);
+  FrameWithFlags* fwfToModify(&fwf);
+
+  // See if the frameSet already has this frame.
   uint32_t i = frameSet->IndexOfFirstElementGt(fwf, FrameOnlyComparator());
+  if (i > 0 && aFrame == frameSet->ElementAt(i-1).mFrame) {
+    // We're already tracking this frame, so prepare to modify the
+    // existing FrameWithFlags object.
+    fwfToModify = &frameSet->ElementAt(i-1);
+  }
+
+  // Check if the frame requires special processing.
+  if (aFlags & REQUEST_REQUIRES_REFLOW) {
+    fwfToModify->mFlags |= REQUEST_REQUIRES_REFLOW;
+
+    // If we weren't already blocking onload, do that now.
+    if ((fwfToModify->mFlags & REQUEST_HAS_BLOCKED_ONLOAD) == 0) {
+      fwfToModify->mFlags |= REQUEST_HAS_BLOCKED_ONLOAD;
+
+      // Block document onload until we either remove the frame in
+      // RemoveRequestToFrameMapping or complete a reflow.
+      mDocument->BlockOnload();
+
+      // We need to stay blocked until we get a reflow. If the first frame
+      // is not yet decoded, we'll trigger that reflow from onFrameComplete.
+      // But if the first frame is already decoded, we need to trigger that
+      // reflow now, because we'll never get a call to onFrameComplete.
+      uint32_t status = 0;
+      if(NS_SUCCEEDED(aRequest->GetImageStatus(&status)) &&
+         status & imgIRequest::STATUS_FRAME_COMPLETE) {
+        RequestReflowOnFrame(aFrame, aRequest);
+      }
+    }
+  }
+
+  // Do some sanity checking to ensure that we only add to one mapping
+  // iff we also add to the other mapping.
+  DebugOnly<bool> didAddToFrameSet(false);
+  DebugOnly<bool> didAddToRequestSet(false);
+
+  // If we weren't already tracking this frame, add it to the frameSet.
   if (i == 0 || aFrame != frameSet->ElementAt(i-1).mFrame) {
     frameSet->InsertElementAt(i, fwf);
+    didAddToFrameSet = true;
   }
+
+  // Add request to the request set if it wasn't already there.
   i = requestSet->IndexOfFirstElementGt(aRequest);
   if (i == 0 || aRequest != requestSet->ElementAt(i-1)) {
     requestSet->InsertElementAt(i, aRequest);
+    didAddToRequestSet = true;
   }
+
+  MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet,
+             "We should only add to one map iff we also add to the other map.");
 }
 
 void
 ImageLoader::MaybeRegisterCSSImage(ImageLoader::Image* aImage)
 {
   NS_ASSERTION(aImage, "This should never be null!");
 
   bool found = false;
@@ -135,18 +184,31 @@ ImageLoader::RemoveRequestToFrameMapping
     aRequest->GetNotificationObserver(getter_AddRefs(observer));
     MOZ_ASSERT(!observer || observer == this);
   }
 #endif
 
   if (auto entry = mRequestToFrameMap.Lookup(aRequest)) {
     FrameSet* frameSet = entry.Data();
     MOZ_ASSERT(frameSet, "This should never be null");
-    frameSet->RemoveElementSorted(FrameWithFlags(aFrame),
-                                  FrameOnlyComparator());
+
+    // Before we remove aFrame from the frameSet, unblock onload if needed.
+    uint32_t i = frameSet->IndexOfFirstElementGt(FrameWithFlags(aFrame),
+                                                 FrameOnlyComparator());
+
+    if (i > 0 && aFrame == frameSet->ElementAt(i-1).mFrame) {
+      FrameWithFlags& fwf = frameSet->ElementAt(i-1);
+      if (fwf.mFlags & REQUEST_HAS_BLOCKED_ONLOAD) {
+        mDocument->UnblockOnload(false);
+        // We're about to remove fwf from the frameSet, so we don't bother
+        // updating the flag.
+      }
+      frameSet->RemoveElementAt(i-1);
+    }
+
     if (frameSet->IsEmpty()) {
       nsPresContext* presContext = GetPresContext();
       if (presContext) {
         nsLayoutUtils::DeregisterImageRequest(presContext, aRequest, nullptr);
       }
       entry.Remove();
     }
   }
@@ -385,16 +447,69 @@ ImageLoader::DoRedraw(FrameSet* aFrameSe
         if (aForcePaint) {
           frame->SchedulePaint();
         }
       }
     }
   }
 }
 
+void
+ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame, imgIRequest* aRequest)
+{
+  MOZ_ASSERT(aFrame);
+  MOZ_ASSERT(aRequest);
+
+  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+  if (!frameSet) {
+    return;
+  }
+
+  size_t i = frameSet->BinaryIndexOf(FrameWithFlags(aFrame),
+                                     FrameOnlyComparator());
+  if (i != FrameSet::NoIndex) {
+    FrameWithFlags& fwf = frameSet->ElementAt(i);
+    if (fwf.mFlags & REQUEST_HAS_BLOCKED_ONLOAD) {
+      mDocument->UnblockOnload(false);
+      fwf.mFlags &= ~REQUEST_HAS_BLOCKED_ONLOAD;
+    }
+  }
+}
+
+void
+ImageLoader::RequestReflowIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest)
+{
+  MOZ_ASSERT(aFrameSet);
+
+  for (FrameWithFlags& fwf : *aFrameSet) {
+    nsIFrame* frame = fwf.mFrame;
+    if (fwf.mFlags & REQUEST_REQUIRES_REFLOW) {
+      // Tell the container of the float to reflow because the
+      // shape-outside: <image> has finished decoding its first frame.
+      RequestReflowOnFrame(frame, aRequest);
+    }
+  }
+}
+
+void
+ImageLoader::RequestReflowOnFrame(nsIFrame* aFrame, imgIRequest* aRequest)
+{
+  // Actually request the reflow.
+  nsIFrame* floatContainer = aFrame->GetInFlowParent();
+  floatContainer->PresShell()->FrameNeedsReflow(
+    floatContainer, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+
+  // We'll respond to the reflow events by unblocking onload, regardless
+  // of whether the reflow was completed or cancelled. The callback will
+  // also delete itself when it is called.
+  ImageReflowCallback* unblocker = new ImageReflowCallback(this, aFrame,
+                                                           aRequest);
+  floatContainer->PresShell()->PostReflowCallback(unblocker);
+}
+
 NS_IMPL_ADDREF(ImageLoader)
 NS_IMPL_RELEASE(ImageLoader)
 
 NS_INTERFACE_MAP_BEGIN(ImageLoader)
   NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
@@ -498,16 +613,19 @@ ImageLoader::OnFrameComplete(imgIRequest
     return NS_OK;
   }
 
   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
   if (!frameSet) {
     return NS_OK;
   }
 
+  // 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).
   DoRedraw(frameSet, /* aForcePaint = */ true);
 
   return NS_OK;
 }
 
@@ -540,10 +658,41 @@ ImageLoader::FlushUseCounters()
     nsCOMPtr<imgIContainer> container;
     request->GetImage(getter_AddRefs(container));
     if (container) {
       static_cast<image::Image*>(container.get())->ReportUseCounters();
     }
   }
 }
 
+bool
+ImageLoader::ImageReflowCallback::ReflowFinished()
+{
+  // Check that the frame is still valid. If it isn't, then onload was
+  // unblocked when the frame was removed from the FrameSet in
+  // RemoveRequestToFrameMapping.
+  if (mFrame.IsAlive()) {
+    mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
+  }
+
+  // Get rid of this callback object.
+  delete this;
+
+  // We don't need to trigger layout.
+  return false;
+}
+
+void
+ImageLoader::ImageReflowCallback::ReflowCallbackCanceled()
+{
+  // Check that the frame is still valid. If it isn't, then onload was
+  // unblocked when the frame was removed from the FrameSet in
+  // RemoveRequestToFrameMapping.
+  if (mFrame.IsAlive()) {
+    mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
+  }
+
+  // Get rid of this callback object.
+  delete this;
+}
+
 } // namespace css
 } // namespace mozilla
--- a/layout/style/ImageLoader.h
+++ b/layout/style/ImageLoader.h
@@ -5,19 +5,21 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // A class that handles style system image loads (other image loads are handled
 // by the nodes in the content tree).
 
 #ifndef mozilla_css_ImageLoader_h___
 #define mozilla_css_ImageLoader_h___
 
-#include "CORSMode.h"
+#include "mozilla/CORSMode.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
+#include "nsIFrame.h"
+#include "nsIReflowCallback.h"
 #include "nsTArray.h"
 #include "imgIRequest.h"
 #include "imgINotificationObserver.h"
 #include "mozilla/Attributes.h"
 
 class imgIContainer;
 class nsIFrame;
 class nsIDocument;
@@ -28,16 +30,24 @@ class nsIPrincipal;
 namespace mozilla {
 namespace css {
 
 struct ImageValue;
 
 class ImageLoader final : public imgINotificationObserver
 {
 public:
+  // We also associate flags alongside frames in the request-to-frames hashmap.
+  // These are used for special handling of events for requests.
+  typedef uint32_t FrameFlags;
+  enum {
+    REQUEST_REQUIRES_REFLOW    = 1u << 0,
+    REQUEST_HAS_BLOCKED_ONLOAD = 1u << 1
+  };
+
   typedef mozilla::css::ImageValue Image;
 
   explicit ImageLoader(nsIDocument* aDocument)
   : mDocument(aDocument),
     mInClone(false)
   {
     MOZ_ASSERT(mDocument);
   }
@@ -46,17 +56,18 @@ public:
   NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void DropDocumentReference();
 
   void MaybeRegisterCSSImage(Image* aImage);
   void DeregisterCSSImage(Image* aImage);
 
   void AssociateRequestToFrame(imgIRequest* aRequest,
-                               nsIFrame* aFrame);
+                               nsIFrame* aFrame,
+                               FrameFlags aFlags);
 
   void DisassociateRequestFromFrame(imgIRequest* aRequest,
                                     nsIFrame* aFrame);
 
   void DropRequestsForFrame(nsIFrame* aFrame);
 
   void SetAnimationMode(uint16_t aMode);
 
@@ -68,27 +79,43 @@ public:
   void LoadImage(nsIURI* aURI, nsIPrincipal* aPrincipal, nsIURI* aReferrer,
                  Image* aCSSValue, CORSMode aCorsMode);
 
   void DestroyRequest(imgIRequest* aRequest);
 
   void FlushUseCounters();
 
 private:
+  // This callback is used to unblock document onload after a reflow
+  // triggered from an image load.
+  struct ImageReflowCallback final : public nsIReflowCallback
+  {
+    RefPtr<ImageLoader> mLoader;
+    WeakFrame mFrame;
+    nsCOMPtr<imgIRequest> const mRequest;
+
+    ImageReflowCallback(ImageLoader* aLoader,
+                        nsIFrame* aFrame,
+                        imgIRequest* aRequest)
+    : mLoader(aLoader)
+    , mFrame(aFrame)
+    , mRequest(aRequest)
+    {}
+
+    bool ReflowFinished() override;
+    void ReflowCallbackCanceled() override;
+  };
+
   ~ImageLoader() {}
 
   // We need to be able to look up the frames associated with a request (for
   // delivering notifications) and the requests associated with a frame (when
   // the frame goes away). Thus we maintain hashtables going both ways.  These
   // should always be in sync.
 
-  // We also associate flags alongside frames in the request-to-frames hashmap.
-  // These are used for special handling of events for requests.
-  typedef uint32_t FrameFlags;
-
   struct FrameWithFlags {
     FrameWithFlags(nsIFrame* aFrame)
     : mFrame(aFrame),
       mFlags(0)
     {
       MOZ_ASSERT(mFrame);
     }
     nsIFrame* const mFrame;
@@ -117,16 +144,19 @@ private:
                            RequestSet> FrameToRequestMap;
 
   void AddImage(Image* aCSSImage);
   void RemoveImage(Image* aCSSImage);
 
   nsPresContext* GetPresContext();
 
   void DoRedraw(FrameSet* aFrameSet, bool aForcePaint);
+  void UnblockOnloadIfNeeded(nsIFrame* aFrame, imgIRequest* aRequest);
+  void RequestReflowIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest);
+  void RequestReflowOnFrame(nsIFrame* aFrame, imgIRequest* aRequest);
 
   nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnFrameComplete(imgIRequest* aRequest);
   nsresult OnImageIsAnimated(imgIRequest* aRequest);
   nsresult OnFrameUpdate(imgIRequest* aRequest);
 
   // Helpers for DropRequestsForFrame / DisassociateRequestFromFrame above.
   void RemoveRequestToFrameMapping(imgIRequest* aRequest, nsIFrame* aFrame);
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1064,16 +1064,28 @@ StyleShapeSource::SetURL(css::URLValue* 
 void
 StyleShapeSource::SetShapeImage(UniquePtr<nsStyleImage> aShapeImage)
 {
   MOZ_ASSERT(aShapeImage);
   mShapeImage = Move(aShapeImage);
   mType = StyleShapeSourceType::Image;
 }
 
+imgIRequest*
+StyleShapeSource::GetShapeImageData() const
+{
+  if (mType != StyleShapeSourceType::Image) {
+    return nullptr;
+  }
+  if (mShapeImage->GetType() != eStyleImageType_Image) {
+    return nullptr;
+  }
+  return mShapeImage->GetImageData();
+}
+
 void
 StyleShapeSource::SetBasicShape(UniquePtr<StyleBasicShape> aBasicShape,
                                 StyleGeometryBox aReferenceBox)
 {
   MOZ_ASSERT(aBasicShape);
   mBasicShape = Move(aBasicShape);
   mReferenceBox = aReferenceBox;
   mType = StyleShapeSourceType::Shape;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2140,16 +2140,21 @@ struct StyleShapeSource final
   void SetURL(css::URLValue* aValue);
 
   const UniquePtr<nsStyleImage>& GetShapeImage() const
   {
     MOZ_ASSERT(mType == StyleShapeSourceType::Image, "Wrong shape source type!");
     return mShapeImage;
   }
 
+  // Iff we have "shape-outside:<image>" with an image URI (not a gradient),
+  // this method returns the corresponding imgIRequest*. Else, returns
+  // null.
+  imgIRequest* GetShapeImageData() const;
+
   void SetShapeImage(UniquePtr<nsStyleImage> aShapeImage);
 
   const UniquePtr<StyleBasicShape>& GetBasicShape() const
   {
     MOZ_ASSERT(mType == StyleShapeSourceType::Shape, "Wrong shape source type!");
     return mBasicShape;
   }
 
--- a/layout/svg/SVGObserverUtils.cpp
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -418,17 +418,17 @@ nsSVGMaskProperty::ResolveImage(uint32_t
 
   if (!image.IsResolved()) {
     MOZ_ASSERT(image.GetType() == nsStyleImageType::eStyleImageType_Image);
     image.ResolveImage(mFrame->PresContext(), nullptr);
 
     mozilla::css::ImageLoader* imageLoader =
       mFrame->PresContext()->Document()->StyleImageLoader();
     if (imgRequestProxy* req = image.GetImageData()) {
-      imageLoader->AssociateRequestToFrame(req, mFrame);
+      imageLoader->AssociateRequestToFrame(req, mFrame, 0);
     }
   }
 }
 
 bool
 nsSVGTextPathProperty::TargetIsValid()
 {
   Element* target = GetTarget();