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
--- 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;
}