Bug 1404222 Part 2: Block onload when shape-outside images are first decoded, and keep it blocked until a second reflow is complete. draft
authorTing-Yu Lin <aethanyc@gmail.com>
Thu, 25 Jan 2018 14:56:43 +0800
changeset 764852 77377091968df8d0155e4d8ee4352e5eb1e1a15d
parent 764851 c31e515a27d87f7d7d9c9555284b727a77c588f5
child 764853 3fdfa2cc3bb0f57e2e950ecfa4e26a6c224ae1aa
push id101877
push userbwerth@mozilla.com
push dateThu, 08 Mar 2018 15:00:07 +0000
bugs1404222
milestone60.0a1
Bug 1404222 Part 2: Block onload when shape-outside images are first decoded, and keep it blocked until a second reflow is complete. When we finish decoding an image frame, we need to trigger reflow for the frame containing a float with shape-outside: <image>. MozReview-Commit-ID: 2eNsXsw4kxM
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
layout/base/PresShell.cpp
layout/generic/nsFrame.cpp
layout/style/ImageLoader.cpp
layout/style/ImageLoader.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8403,16 +8403,49 @@ nsDocument::UnblockOnload(bool aFireSync
                                  NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),
                                  false,
                                  false);
       asyncDispatcher->PostDOMEvent();
     }
   }
 }
 
+void
+nsDocument::BlockOnloadOnResource(void* aResource)
+{
+  MOZ_ASSERT(aResource, "Can't block on an empty resource.");
+  if (mOnloadBlockingResources.empty()) {
+    BlockOnload();
+  }
+
+  mOnloadBlockingResources.insert(aResource);
+}
+
+void
+nsDocument::UnblockOnloadOnResource(void* aResource)
+{
+  MOZ_ASSERT(aResource, "Can't unblock on an empty resource.");
+  if (!mOnloadBlockingResources.empty()) {
+    mOnloadBlockingResources.erase(aResource);
+
+    if (mOnloadBlockingResources.empty()) {
+      UnblockOnload(false);
+    }
+  }
+}
+
+void
+nsDocument::ForgetAllBlockingResources()
+{
+  if (!mOnloadBlockingResources.empty()) {
+    mOnloadBlockingResources.clear();
+    UnblockOnload(false);
+  }
+}
+
 class nsUnblockOnloadEvent : public Runnable {
 public:
   explicit nsUnblockOnloadEvent(nsDocument* aDoc)
     : mozilla::Runnable("nsUnblockOnloadEvent")
     , mDoc(aDoc)
   {
   }
   NS_IMETHOD Run() override {
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -66,16 +66,17 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
 #include "jsfriendapi.h"
 #include "mozilla/LinkedList.h"
 #include "CustomElementRegistry.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/Maybe.h"
 #include "nsIURIClassifier.h"
+#include <unordered_set>
 
 #define XML_DECLARATION_BITS_DECLARATION_EXISTS   (1 << 0)
 #define XML_DECLARATION_BITS_ENCODING_EXISTS      (1 << 1)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS    (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES       (1 << 3)
 
 
 class nsDOMStyleSheetSetList;
@@ -610,16 +611,20 @@ public:
   virtual bool CanSavePresentation(nsIRequest *aNewRequest) override;
   virtual void Destroy() override;
   virtual void RemovedFromDocShell() override;
   virtual already_AddRefed<nsILayoutHistoryState> GetLayoutHistoryState() const override;
 
   virtual void BlockOnload() override;
   virtual void UnblockOnload(bool aFireSync) override;
 
+  virtual void BlockOnloadOnResource(void* aResource) override;
+  virtual void UnblockOnloadOnResource(void* aResource) override;
+  virtual void ForgetAllBlockingResources() override;
+
   virtual void AddStyleRelevantLink(mozilla::dom::Link* aLink) override;
   virtual void ForgetLink(mozilla::dom::Link* aLink) override;
 
   virtual void ClearBoxObjectFor(nsIContent* aContent) override;
 
   virtual already_AddRefed<mozilla::dom::BoxObject>
   GetBoxObjectFor(mozilla::dom::Element* aElement,
                   mozilla::ErrorResult& aRv) override;
@@ -1098,16 +1103,20 @@ private:
   nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
 
   // Currently active onload blockers
   uint32_t mOnloadBlockCount;
   // Onload blockers which haven't been activated yet
   uint32_t mAsyncOnloadBlockCount;
   nsCOMPtr<nsIRequest> mOnloadBlocker;
 
+  // Resources that we are tracking which block onload.
+  typedef std::unordered_set<void*> resourceSet;
+  resourceSet mOnloadBlockingResources;
+
   // A hashtable of styled links keyed by address pointer.
   nsTHashtable<nsPtrHashKey<mozilla::dom::Link> > mStyledLinks;
 #ifdef DEBUG
   // Indicates whether mStyledLinks was cleared or not.  This is used to track
   // state so we can provide useful assertions to consumers of ForgetLink and
   // AddStyleRelevantLink.
   bool mStyledLinksCleared;
 #endif
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2006,16 +2006,33 @@ public:
   /**
    * @param aFireSync whether to fire onload synchronously.  If false,
    * onload will fire asynchronously after all onload blocks have been
    * removed.  It will NOT fire from inside UnblockOnload.  If true,
    * onload may fire from inside UnblockOnload.
    */
   virtual void UnblockOnload(bool aFireSync) = 0;
 
+  /**
+   * Methods that can be used to prevent onload firing while some resources
+   * are being waited for. These methods will call BlockOnload when the first
+   * resource is passed to BlockOnloadOnResource, and then when that resource
+   * and all further supplied resources have been passed to
+   * UnblockOnloadOnResource, will call UnblockOnload triggering an async
+   * onload firing.
+   */
+  virtual void BlockOnloadOnResource(void* aResource) = 0;
+  virtual void UnblockOnloadOnResource(void* aResource) = 0;
+
+  /**
+   * Forget all blocking resources and unblock the onload if there were
+   * blocking resources.
+   */
+  virtual void ForgetAllBlockingResources() = 0;
+
   void BlockDOMContentLoaded()
   {
     ++mBlockDOMContentLoaded;
   }
 
   virtual void UnblockDOMContentLoaded() = 0;
 
   /**
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4203,16 +4203,17 @@ PresShell::DoFlushPendingNotifications(m
       if (!mIsDestroying) {
         nsAutoScriptBlocker scriptBlocker;
 #ifdef MOZ_GECKO_PROFILER
         AutoProfilerTracing tracingStyleFlush("Paint", "Styles",
                                               Move(mStyleCause));
         mStyleCause = nullptr;
 #endif
 
+        mDocument->ForgetAllBlockingResources();
         mPresContext->RestyleManager()->ProcessPendingRestyles();
       }
     }
 
     // Process whatever XBL constructors those restyles queued up.  This
     // ensures that onload doesn't fire too early and that we won't do extra
     // reflows after those constructors run.
     if (!mIsDestroying) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1204,16 +1204,32 @@ nsFrame::DidSetStyleContext(nsStyleConte
     if (oldBorderImage && HasImageRequest()) {
       imageLoader->DisassociateRequestFromFrame(oldBorderImage, this);
     }
     if (newBorderImage) {
       imageLoader->AssociateRequestToFrame(newBorderImage, this);
     }
   }
 
+  imgRequestProxy* oldShapeImage =
+    aOldStyleContext
+    ? aOldStyleContext->StyleDisplay()->mShapeOutside.GetShapeImageData()
+    : nullptr;
+  imgRequestProxy* newShapeImage =
+    StyleDisplay()->mShapeOutside.GetShapeImageData();
+
+  if (oldShapeImage != newShapeImage) {
+    if (newShapeImage) {
+      imageLoader->AssociateRequestToFrame(newShapeImage, this);
+    }
+    if (oldShapeImage && HasImageRequest()) {
+      imageLoader->DisassociateRequestFromFrame(oldShapeImage, this);
+    }
+  }
+
   // 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) {
     PresContext()->SetBidiEnabled();
   }
 
--- 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"
 
 namespace mozilla {
 namespace css {
@@ -380,16 +381,70 @@ ImageLoader::DoRedraw(FrameSet* aFrameSe
         if (aForcePaint) {
           frame->SchedulePaint();
         }
       }
     }
   }
 }
 
+void
+ImageLoader::BlockOnloadIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest)
+{
+  MOZ_ASSERT(aFrameSet);
+
+  for (nsIFrame* frame : *aFrameSet) {
+    const nsStyleDisplay* display = frame->StyleDisplay();
+    const imgIRequest* shapeOutsideRequest =
+      display->mShapeOutside.GetShapeImageData();
+    if (display->mFloat != StyleFloat::None &&
+        shapeOutsideRequest == aRequest) {
+      // Tell the document to block onloading for this shape image. It will
+      // get unblocked through a later call to RequestReflowIfNeeded().
+      frame->PresShell()->GetDocument()->BlockOnloadOnResource(
+        display->mShapeOutside.GetShapeImage().get());
+    }
+  }
+}
+
+
+bool
+ImageLoader::RequestReflowIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest)
+{
+  MOZ_ASSERT(aFrameSet);
+
+  bool reflowRequested = false;
+  for (nsIFrame* frame : *aFrameSet) {
+    const nsStyleDisplay* display = frame->StyleDisplay();
+    const imgIRequest* shapeOutsideRequest =
+      display->mShapeOutside.GetShapeImageData();
+    if (display->mFloat != StyleFloat::None &&
+        shapeOutsideRequest == aRequest) {
+      // Tell the container of the float to reflow because the
+      // shape-outside: <image> has finished decoding its first frame.
+
+      // Actually request the reflow.
+      nsIFrame* floatContainer = frame->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(display->mShapeOutside.GetShapeImage().get(),
+                                floatContainer->PresShell()->GetDocument());
+      floatContainer->PresShell()->PostReflowCallback(unblocker);
+
+      reflowRequested = true;
+    }
+  }
+  return reflowRequested;
+}
+
 NS_IMPL_ADDREF(ImageLoader)
 NS_IMPL_RELEASE(ImageLoader)
 
 NS_INTERFACE_MAP_BEGIN(ImageLoader)
   NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
@@ -453,16 +508,23 @@ ImageLoader::OnSizeAvailable(imgIRequest
   }
 
   for (nsIFrame* frame : *frameSet) {
     if (frame->StyleVisibility()->IsVisible()) {
       frame->MarkNeedsDisplayItemRebuild();
     }
   }
 
+  // Check if this request is for a shape-outside: image, in which
+  // case we need to block the document from firing an onload event
+  // until we've fully decoded that image and have done another
+  // reflow with the full image data available. This is triggered in
+  // RequestReflowIfNeeded().
+  BlockOnloadIfNeeded(frameSet, aRequest);
+
   return NS_OK;
 }
 
 nsresult
 ImageLoader::OnImageIsAnimated(imgIRequest* aRequest)
 {
   if (!mDocument) {
     return NS_OK;
@@ -492,16 +554,30 @@ 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).
+  bool reflowRequested = RequestReflowIfNeeded(frameSet, aRequest);
+  if (reflowRequested) {
+    // Currently, the only reason we would request reflow is because the
+    // image is used by shape-outside, which means that we don't want
+    // to get any more of these events. Deregister with the refresh driver.
+    nsPresContext* presContext = GetPresContext();
+    if (presContext) {
+      nsLayoutUtils::DeregisterImageRequest(presContext,
+                                            aRequest,
+                                            nullptr);
+    }
+  }
+
   // 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;
 }
 
@@ -534,10 +610,33 @@ ImageLoader::FlushUseCounters()
     nsCOMPtr<imgIContainer> container;
     request->GetImage(getter_AddRefs(container));
     if (container) {
       static_cast<image::Image*>(container.get())->ReportUseCounters();
     }
   }
 }
 
+bool
+ImageLoader::ImageReflowCallback::ReflowFinished()
+{
+  MOZ_ASSERT(mDocument, "This should only be triggered with a document.");
+  mDocument->UnblockOnloadOnResource(mImage);
+
+  // Get rid of this callback object.
+  delete this;
+
+  // We don't need to trigger layout.
+  return false;
+}
+
+void
+ImageLoader::ImageReflowCallback::ReflowCallbackCanceled()
+{
+  MOZ_ASSERT(mDocument, "This should only be triggered with a document.");
+  mDocument->UnblockOnloadOnResource(mImage);
+
+  // Get rid of this callback object.
+  delete this;
+}
+
 } // namespace css
 } // namespace mozilla
--- a/layout/style/ImageLoader.h
+++ b/layout/style/ImageLoader.h
@@ -8,16 +8,17 @@
 // by the nodes in the content tree).
 
 #ifndef mozilla_css_ImageLoader_h___
 #define mozilla_css_ImageLoader_h___
 
 #include "CORSMode.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
+#include "nsIReflowCallback.h"
 #include "nsTArray.h"
 #include "imgIRequest.h"
 #include "imgINotificationObserver.h"
 #include "mozilla/Attributes.h"
 
 class imgIContainer;
 class nsIFrame;
 class nsIDocument;
@@ -68,16 +69,32 @@ public:
   void LoadImage(nsIURI* aURI, nsIPrincipal* aPrincipal, nsIURI* aReferrer,
                  Image* aCSSValue, CORSMode aCorsMode);
 
   void DestroyRequest(imgIRequest* aRequest);
 
   void FlushUseCounters();
 
 private:
+  struct ImageReflowCallback : public nsIReflowCallback
+  {
+    nsStyleImage* mImage;
+    nsIDocument* mDocument;
+
+    ImageReflowCallback(nsStyleImage* aImage,
+                        nsIDocument* aDocument)
+    : mImage(aImage)
+    , mDocument(aDocument)
+    {}
+    virtual ~ImageReflowCallback() {}
+
+    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.
 
   typedef nsTArray<nsIFrame*> FrameSet;
@@ -89,16 +106,18 @@ private:
                            RequestSet> FrameToRequestMap;
 
   void AddImage(Image* aCSSImage);
   void RemoveImage(Image* aCSSImage);
 
   nsPresContext* GetPresContext();
 
   void DoRedraw(FrameSet* aFrameSet, bool aForcePaint);
+  void BlockOnloadIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest);
+  bool RequestReflowIfNeeded(FrameSet* aFrameSet, 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
@@ -1095,16 +1095,28 @@ StyleShapeSource::SetURL(css::URLValue* 
 void
 StyleShapeSource::SetShapeImage(UniquePtr<nsStyleImage> aShapeImage)
 {
   MOZ_ASSERT(aShapeImage);
   mShapeImage = Move(aShapeImage);
   mType = StyleShapeSourceType::Image;
 }
 
+imgRequestProxy*
+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
@@ -2436,16 +2436,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 imgRequestProxy*. Else, returns
+  // null.
+  imgRequestProxy* GetShapeImageData() const;
+
   void SetShapeImage(UniquePtr<nsStyleImage> aShapeImage);
 
   const UniquePtr<StyleBasicShape>& GetBasicShape() const
   {
     MOZ_ASSERT(mType == StyleShapeSourceType::Shape, "Wrong shape source type!");
     return mBasicShape;
   }