Bug 1404222 Part 3: Block onload when shape-outside images are requested for a frame, and keep it blocked until the frame is removed, the image fails to load, or reflow is complete. r=dbaron,dholbert
authorTing-Yu Lin <aethanyc@gmail.com>, Brad Werth <bwerth@mozilla.com>
Thu, 25 Jan 2018 14:56:43 +0800
changeset 776865 501cc7513f9d73fa1400d85bbfc00b73911e8e1a
parent 776864 9f645937f6c00768586d5260c8575a0b9e7fd15c
child 776866 bd731b1a68618f1cbce43ce8f46711fafe08fb6e
push id105022
push userdholbert@mozilla.com
push dateTue, 03 Apr 2018 21:03:05 +0000
reviewersdbaron, dholbert
bugs1404222
milestone61.0a1
Bug 1404222 Part 3: Block onload when shape-outside images are requested for a frame, and keep it blocked until the frame is removed, the image fails to load, 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 onLoadComplete, 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(fwfToModify, 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,74 @@ 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) {
+    if (fwf.mFlags & REQUEST_REQUIRES_REFLOW) {
+      // Tell the container of the frame to reflow because the
+      // image request has finished decoding its first frame.
+      RequestReflowOnFrame(&fwf, aRequest);
+    }
+  }
+}
+
+void
+ImageLoader::RequestReflowOnFrame(FrameWithFlags* aFwf, imgIRequest* aRequest)
+{
+  // Set the flag indicating that we've requested reflow. This flag will never
+  // be unset.
+  aFwf->mFlags |= REQUEST_HAS_REQUESTED_REFLOW;
+
+  nsIFrame* frame = aFwf->mFrame;
+
+  // Actually request the reflow.
+  nsIFrame* parent = frame->GetInFlowParent();
+  parent->PresShell()->FrameNeedsReflow(parent, 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, frame,
+                                                           aRequest);
+  parent->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
@@ -434,16 +554,20 @@ ImageLoader::Notify(imgIRequest* aReques
   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
     nsCOMPtr<imgIContainer> image;
     aRequest->GetImage(getter_AddRefs(image));
     if (image && mDocument) {
       image->PropagateUseCounters(mDocument);
     }
   }
 
+  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+    return OnLoadComplete(aRequest);
+  }
+
   return NS_OK;
 }
 
 nsresult
 ImageLoader::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
 {
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
@@ -498,16 +622,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;
 }
 
@@ -523,16 +650,47 @@ ImageLoader::OnFrameUpdate(imgIRequest* 
     return NS_OK;
   }
 
   DoRedraw(frameSet, /* aForcePaint = */ false);
 
   return NS_OK;
 }
 
+nsresult
+ImageLoader::OnLoadComplete(imgIRequest* aRequest)
+{
+  if (!mDocument || mInClone) {
+    return NS_OK;
+  }
+
+  FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
+  if (!frameSet) {
+    return NS_OK;
+  }
+
+  // This may be called for a request that never sent a complete frame.
+  // This is what happens in a CORS mode violation, and may happen during
+  // other network events. We check for any frames that have blocked
+  // onload but haven't requested reflow. In such a case, we unblock
+  // onload here, since onFrameComplete will not be called for this request.
+  FrameFlags flagsToCheck(REQUEST_HAS_BLOCKED_ONLOAD |
+                          REQUEST_HAS_REQUESTED_REFLOW);
+  for (FrameWithFlags& fwf : *frameSet) {
+    if ((fwf.mFlags & flagsToCheck) == REQUEST_HAS_BLOCKED_ONLOAD) {
+      // We've blocked onload but haven't requested reflow. Unblock onload
+      // and clear the flag.
+      mDocument->UnblockOnload(false);
+      fwf.mFlags &= ~REQUEST_HAS_BLOCKED_ONLOAD;
+    }
+  }
+
+  return NS_OK;
+}
+
 void
 ImageLoader::FlushUseCounters()
 {
   for (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) {
     nsPtrHashKey<Image>* key = iter.Get();
     ImageLoader::Image* image = key->GetKey();
 
     imgIRequest* request = image->mRequests.GetWeak(mDocument);
@@ -540,10 +698,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,25 @@ 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_REQUESTED_REFLOW = 1u << 1,
+    REQUEST_HAS_BLOCKED_ONLOAD   = 1u << 2
+  };
+
   typedef mozilla::css::ImageValue Image;
 
   explicit ImageLoader(nsIDocument* aDocument)
   : mDocument(aDocument),
     mInClone(false)
   {
     MOZ_ASSERT(mDocument);
   }
@@ -46,17 +57,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 +80,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 {
     explicit FrameWithFlags(nsIFrame* aFrame)
     : mFrame(aFrame),
       mFlags(0)
     {
       MOZ_ASSERT(mFrame);
     }
     nsIFrame* const mFrame;
@@ -117,21 +145,25 @@ 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(FrameWithFlags* aFwf, imgIRequest* aRequest);
 
   nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnFrameComplete(imgIRequest* aRequest);
   nsresult OnImageIsAnimated(imgIRequest* aRequest);
   nsresult OnFrameUpdate(imgIRequest* aRequest);
+  nsresult OnLoadComplete(imgIRequest* aRequest);
 
   // Helpers for DropRequestsForFrame / DisassociateRequestFromFrame above.
   void RemoveRequestToFrameMapping(imgIRequest* aRequest, nsIFrame* aFrame);
   void RemoveFrameToRequestMapping(imgIRequest* aRequest, nsIFrame* aFrame);
 
   // A map of imgIRequests to the nsIFrames that are using them.
   RequestToFrameMap mRequestToFrameMap;
 
--- 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();