Bug 847223. Part 6. Use the first reflow of relevant image frames to add/remove them from the visible list. r=mats
authorTimothy Nikkel <tnikkel@gmail.com>
Sat, 14 Sep 2013 19:05:05 -0500
changeset 147216 c6dcd4d77085
parent 147215 f824604ba8dc
child 147217 8f08b1cb5fba
push id33809
push usertnikkel@gmail.com
push dateSun, 15 Sep 2013 00:05:34 +0000
treeherdermozilla-inbound@daa9550337ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs847223
milestone26.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 847223. Part 6. Use the first reflow of relevant image frames to add/remove them from the visible list. r=mats
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/svg/nsSVGImageFrame.cpp
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -5455,8 +5455,58 @@ nsLayoutUtils::GetBoxShadowRectForFrame(
     tmpRect.Inflate(shadow->mSpread);
     tmpRect.Inflate(
       nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D));
     shadows.UnionRect(shadows, tmpRect);
   }
   return shadows;
 }
 
+/* static */ void
+nsLayoutUtils::UpdateImageVisibilityForFrame(nsIFrame* aImageFrame)
+{
+#ifdef DEBUG
+  nsIAtom* type = aImageFrame->GetType();
+  MOZ_ASSERT(type == nsGkAtoms::imageFrame ||
+             type == nsGkAtoms::imageControlFrame ||
+             type == nsGkAtoms::svgImageFrame, "wrong type of frame");
+
+  NS_ASSERTION(!aImageFrame->PresContext()->PresShell()->AssumeAllImagesVisible(),
+               "shouldn't be in a doc where we assume all images are visible");
+#endif
+
+  nsCOMPtr<nsIImageLoadingContent> content = do_QueryInterface(aImageFrame->GetContent());
+  if (!content) {
+    return;
+  }
+
+  bool visible = true;
+  nsIFrame* f = aImageFrame->GetParent();
+  nsRect rect = aImageFrame->GetContentRectRelativeToSelf();
+  nsIFrame* rectFrame = aImageFrame;
+  while (f) {
+    nsIScrollableFrame* sf = do_QueryFrame(f);
+    if (sf) {
+      nsRect transformedRect =
+        nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f);
+      if (!sf->IsRectNearlyVisible(transformedRect)) {
+        visible = false;
+        break;
+      }
+      rect = sf->GetScrollPortRect();
+      rectFrame = f;
+    }
+    nsIFrame* parent = f->GetParent();
+    if (!parent) {
+      parent = nsLayoutUtils::GetCrossDocParentFrame(f);
+      if (parent && parent->PresContext()->IsChrome()) {
+        break;
+      }
+    }
+    f = parent;
+  }
+
+  if (visible) {
+    aImageFrame->PresContext()->PresShell()->EnsureImageInVisibleList(content);
+  } else {
+    aImageFrame->PresContext()->PresShell()->RemoveImageFromVisibleList(content);
+  }
+}
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1879,16 +1879,24 @@ public:
   /**
    * Assert that the frame tree rooted at |aSubtreeRoot| is empty, i.e.,
    * that it contains no first-in-flows.
    */
   static void
   AssertTreeOnlyEmptyNextInFlows(nsIFrame *aSubtreeRoot);
 #endif
 
+  /**
+   * Determine if aImageFrame (which is an nsImageFrame, nsImageControlFrame, or
+   * nsSVGImageFrame) is visible or close to being visible via scrolling and
+   * update the presshell with this knowledge.
+   */
+  static void
+  UpdateImageVisibilityForFrame(nsIFrame* aImageFrame);
+
 private:
   // Helper-functions for SortFrameList():
   template<bool IsLessThanOrEqual(nsIFrame*, nsIFrame*)>
   static nsIFrame* SortedMerge(nsIFrame *aLeft, nsIFrame *aRight);
 
   template<bool IsLessThanOrEqual(nsIFrame*, nsIFrame*)>
   static nsIFrame* MergeSort(nsIFrame *aSource);
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -127,17 +127,18 @@ NS_NewImageFrame(nsIPresShell* aPresShel
 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
 
 
 nsImageFrame::nsImageFrame(nsStyleContext* aContext) :
   ImageFrameSuper(aContext),
   mComputedSize(0, 0),
   mIntrinsicRatio(0, 0),
   mDisplayingIcon(false),
-  mFirstFrameComplete(false)
+  mFirstFrameComplete(false),
+  mReflowCallbackPosted(false)
 {
   // We assume our size is not constrained and we haven't gotten an
   // initial reflow yet, so don't touch those flags.
   mIntrinsicSize.width.SetCoordValue(0);
   mIntrinsicSize.height.SetCoordValue(0);
 }
 
 nsImageFrame::~nsImageFrame()
@@ -175,16 +176,21 @@ nsImageFrame::DisconnectMap()
   }
 #endif
   }
 }
 
 void
 nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
+  if (mReflowCallbackPosted) {
+    PresContext()->PresShell()->CancelReflowCallback(this);
+    mReflowCallbackPosted = false;
+  }
+
   // Tell our image map, if there is one, to clean up
   // This causes the nsImageMap to unregister itself as
   // a DOM listener.
   DisconnectMap();
 
   // set the frame to null so we don't send messages to a dead object.
   if (mListener) {
     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
@@ -886,23 +892,47 @@ nsImageFrame::Reflow(nsPresContext*     
     // scrollable overflow, since it doesn't really need to be scrolled to
     // outside the image.
     static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?");
     nsRect& visualOverflow = aMetrics.VisualOverflow();
     visualOverflow.UnionRect(visualOverflow, altFeedbackSize);
   }
   FinishAndStoreOverflow(&aMetrics);
 
+  if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
+    nsIPresShell* shell = PresContext()->PresShell();
+    if (shell && !shell->AssumeAllImagesVisible()) {
+      mReflowCallbackPosted = true;
+      shell->PostReflowCallback(this);
+    }
+  }
+
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                   ("exit nsImageFrame::Reflow: size=%d,%d",
                   aMetrics.width, aMetrics.height));
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
   return NS_OK;
 }
 
+bool
+nsImageFrame::ReflowFinished()
+{
+  mReflowCallbackPosted = false;
+
+  nsLayoutUtils::UpdateImageVisibilityForFrame(this);
+
+  return false;
+}
+
+void
+nsImageFrame::ReflowCallbackCanceled()
+{
+  mReflowCallbackPosted = false;
+}
+
 // Computes the width of the specified string. aMaxWidth specifies the maximum
 // width available. Once this limit is reached no more characters are measured.
 // The number of characters that fit within the maximum width are returned in
 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
 // into the rendering context before this is called (for performance). MMP
 nscoord
 nsImageFrame::MeasureString(const PRUnichar*     aString,
                             int32_t              aLength,
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -12,16 +12,17 @@
 #include "nsIIOService.h"
 #include "nsIObserver.h"
 
 #include "imgINotificationObserver.h"
 
 #include "nsDisplayList.h"
 #include "imgIContainer.h"
 #include "mozilla/Attributes.h"
+#include "nsIReflowCallback.h"
 
 class nsImageMap;
 class nsIURI;
 class nsILoadGroup;
 struct nsHTMLReflowState;
 struct nsHTMLReflowMetrics;
 class nsDisplayImage;
 class nsPresContext;
@@ -52,17 +53,18 @@ private:
   nsImageFrame *mFrame;
 };
 
 #define IMAGE_SIZECONSTRAINED       NS_FRAME_STATE_BIT(20)
 #define IMAGE_GOTINITIALREFLOW      NS_FRAME_STATE_BIT(21)
 
 #define ImageFrameSuper nsSplittableFrame
 
-class nsImageFrame : public ImageFrameSuper {
+class nsImageFrame : public ImageFrameSuper,
+                     public nsIReflowCallback {
 public:
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::layers::ImageLayer ImageLayer;
   typedef mozilla::layers::LayerManager LayerManager;
 
   NS_DECL_FRAMEARENA_HELPERS
 
   nsImageFrame(nsStyleContext* aContext);
@@ -161,16 +163,21 @@ public:
 
   nsImageMap* GetImageMap();
   nsImageMap* GetExistingImageMap() const { return mImageMap; }
 
   virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext,
                                  InlineMinWidthData *aData);
 
   void DisconnectMap();
+
+  // nsIReflowCallback
+  virtual bool ReflowFinished() MOZ_OVERRIDE;
+  virtual void ReflowCallbackCanceled() MOZ_OVERRIDE;
+
 protected:
   virtual ~nsImageFrame();
 
   void EnsureIntrinsicSizeAndRatio(nsPresContext* aPresContext);
 
   virtual nsSize ComputeSize(nsRenderingContext *aRenderingContext,
                              nsSize aCBSize, nscoord aAvailableWidth,
                              nsSize aMargin, nsSize aBorder, nsSize aPadding,
@@ -279,16 +286,17 @@ private:
 
   nsCOMPtr<imgIContainer> mImage;
   nsSize mComputedSize;
   nsIFrame::IntrinsicSize mIntrinsicSize;
   nsSize mIntrinsicRatio;
 
   bool mDisplayingIcon;
   bool mFirstFrameComplete;
+  bool mReflowCallbackPosted;
 
   static nsIIOService* sIOService;
   
   /* loading / broken image icon support */
 
   // XXXbz this should be handled by the prescontext, I think; that
   // way we would have a single iconload per mozilla session instead
   // of one per document...
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -15,16 +15,17 @@
 #include "nsSVGEffects.h"
 #include "nsSVGPathGeometryFrame.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "nsSVGUtils.h"
 #include "SVGContentUtils.h"
 #include "SVGImageContext.h"
 #include "mozilla/dom/SVGImageElement.h"
 #include "nsContentUtils.h"
+#include "nsIReflowCallback.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class nsSVGImageFrame;
 
 class nsSVGImageListener MOZ_FINAL : public imgINotificationObserver
 {
@@ -37,23 +38,25 @@ public:
   void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; }
 
 private:
   nsSVGImageFrame *mFrame;
 };
 
 typedef nsSVGPathGeometryFrame nsSVGImageFrameBase;
 
-class nsSVGImageFrame : public nsSVGImageFrameBase
+class nsSVGImageFrame : public nsSVGImageFrameBase,
+                        public nsIReflowCallback
 {
   friend nsIFrame*
   NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 protected:
-  nsSVGImageFrame(nsStyleContext* aContext) : nsSVGImageFrameBase(aContext) {}
+  nsSVGImageFrame(nsStyleContext* aContext) : nsSVGImageFrameBase(aContext),
+                                              mReflowCallbackPosted(false) {}
   virtual ~nsSVGImageFrame();
 
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect,
                       nsIFrame* aTransformRoot) MOZ_OVERRIDE;
@@ -81,30 +84,36 @@ public:
 
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult);
   }
 #endif
 
+  // nsIReflowCallback
+  virtual bool ReflowFinished() MOZ_OVERRIDE;
+  virtual void ReflowCallbackCanceled() MOZ_OVERRIDE;
+
 private:
   gfxMatrix GetRasterImageTransform(int32_t aNativeWidth,
                                     int32_t aNativeHeight,
                                     uint32_t aFor,
                                     nsIFrame* aTransformRoot = nullptr);
   gfxMatrix GetVectorImageTransform(uint32_t aFor,
                                     nsIFrame* aTransformRoot = nullptr);
   bool TransformContextForPainting(gfxContext* aGfxContext,
                                    nsIFrame* aTransformRoot);
 
   nsCOMPtr<imgINotificationObserver> mListener;
 
   nsCOMPtr<imgIContainer> mImageContainer;
 
+  bool mReflowCallbackPosted;
+
   friend class nsSVGImageListener;
 };
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsIFrame*
 NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
@@ -148,16 +157,21 @@ nsSVGImageFrame::Init(nsIContent* aConte
   imageLoader->FrameCreated(this);
 
   imageLoader->AddObserver(mListener);
 }
 
 /* virtual */ void
 nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
+  if (mReflowCallbackPosted) {
+    PresContext()->PresShell()->CancelReflowCallback(this);
+    mReflowCallbackPosted = false;
+  }
+
   nsCOMPtr<nsIImageLoadingContent> imageLoader =
     do_QueryInterface(nsFrame::mContent);
 
   if (imageLoader) {
     imageLoader->FrameDestroyed(this);
   }
 
   nsFrame::DestroyFrom(aDestructRoot);
@@ -486,32 +500,56 @@ nsSVGImageFrame::ReflowSVG()
     mRect.SetEmpty();
   }
 
   if (mState & NS_FRAME_FIRST_REFLOW) {
     // Make sure we have our filter property (if any) before calling
     // FinishAndStoreOverflow (subsequent filter changes are handled off
     // nsChangeHint_UpdateEffects):
     nsSVGEffects::UpdateEffects(this);
+
+    if (!mReflowCallbackPosted) {
+      nsIPresShell* shell = PresContext()->PresShell();
+      if (shell && !shell->AssumeAllImagesVisible()) {
+        mReflowCallbackPosted = true;
+        shell->PostReflowCallback(this);
+      }
+    }
   }
 
   nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
   nsOverflowAreas overflowAreas(overflow, overflow);
   FinishAndStoreOverflow(overflowAreas, mRect.Size());
 
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
 
   // Invalidate, but only if this is not our first reflow (since if it is our
   // first reflow then we haven't had our first paint yet).
   if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
     InvalidateFrame();
   }
 }
 
+bool
+nsSVGImageFrame::ReflowFinished()
+{
+  mReflowCallbackPosted = false;
+
+  nsLayoutUtils::UpdateImageVisibilityForFrame(this);
+
+  return false;
+}
+
+void
+nsSVGImageFrame::ReflowCallbackCanceled()
+{
+  mReflowCallbackPosted = false;
+}
+
 uint16_t
 nsSVGImageFrame::GetHitTestFlags()
 {
   uint16_t flags = 0;
 
   switch(StyleVisibility()->mPointerEvents) {
     case NS_STYLE_POINTER_EVENTS_NONE:
       break;