Bug 539356 - Correctly invalidate SVG observers. r=roc
authorMatt Woodrow <mwoodrow@mozilla.com>
Wed, 29 Aug 2012 17:49:16 +1200
changeset 108570 5947680feefbd34a97d7a52187a4ebf6c9e9f150
parent 108569 b55adbbe23bcb8e4851c4a3bcc5c8f2c524f0777
child 108571 b3c0862a729ab37ec9e748cb2c9733af56d858dd
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersroc
bugs539356
milestone18.0a1
Bug 539356 - Correctly invalidate SVG observers. r=roc
layout/base/nsPresShell.cpp
layout/generic/nsFrame.cpp
layout/svg/nsSVGEffects.cpp
layout/svg/nsSVGEffects.h
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -114,16 +114,18 @@
 #include "nsStyleSheetService.h"
 #include "gfxImageSurface.h"
 #include "gfxContext.h"
 #ifdef MOZ_MEDIA
 #include "nsHTMLMediaElement.h"
 #endif
 #include "nsSMILAnimationController.h"
 #include "SVGContentUtils.h"
+#include "nsSVGUtils.h"
+#include "nsSVGEffects.h"
 #include "SVGFragmentIdentifier.h"
 
 #include "nsRefreshDriver.h"
 
 // Drag & Drop, Clipboard
 #include "nsWidgetsCID.h"
 #include "nsIClipboard.h"
 #include "nsIClipboardHelper.h"
@@ -7329,16 +7331,21 @@ PresShell::ScheduleReflowOffTimer()
   }
   return true;
 }
 
 bool
 PresShell::DoReflow(nsIFrame* target, bool aInterruptible)
 {
   target->SchedulePaint();
+  nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(target);
+  while (parent) {
+    nsSVGEffects::InvalidateDirectRenderingObservers(parent);
+    parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
+  }
 
   nsAutoCString docURL("N/A");
   nsIURI *uri = mDocument->GetDocumentURI();
   if (uri)
     uri->GetSpec(docURL);
   SAMPLE_LABEL_PRINTF("layout", "DoReflow", "(%s)", docURL.get());
 
   if (mReflowContinueTimer) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4148,16 +4148,18 @@ nsFrame::WillReflow(nsPresContext* aPres
 NS_IMETHODIMP
 nsFrame::DidReflow(nsPresContext*           aPresContext,
                    const nsHTMLReflowState*  aReflowState,
                    nsDidReflowStatus         aStatus)
 {
   NS_FRAME_TRACE_MSG(NS_FRAME_TRACE_CALLS,
                      ("nsFrame::DidReflow: aStatus=%d", aStatus));
 
+  nsSVGEffects::InvalidateDirectRenderingObservers(this, nsSVGEffects::INVALIDATE_REFLOW);
+
   if (NS_FRAME_REFLOW_FINISHED == aStatus) {
     mState &= ~(NS_FRAME_IN_REFLOW | NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
                 NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
   // Notify the percent height observer if there is a percent height.
   // The observer may be able to initiate another reflow with a computed
   // height. This happens in the case where a table cell has no computed
@@ -4792,19 +4794,21 @@ nsIFrame::ClearInvalidationStateBits()
   RemoveStateBits(NS_FRAME_NEEDS_PAINT | 
                   NS_FRAME_DESCENDANT_NEEDS_PAINT | 
                   NS_FRAME_ALL_DESCENDANTS_NEED_PAINT);
 }
 
 static void InvalidateFrameInternal(nsIFrame *aFrame)
 {
   aFrame->AddStateBits(NS_FRAME_NEEDS_PAINT);
+  nsSVGEffects::InvalidateDirectRenderingObservers(aFrame);
   nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
   while (parent && !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
     parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT);
+    nsSVGEffects::InvalidateDirectRenderingObservers(parent);
     parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
   }
   if (!parent) {
     aFrame->SchedulePaint();
   }
   if (aFrame->HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
     aFrame->Properties().Delete(nsIFrame::InvalidationRect());
     aFrame->RemoveStateBits(NS_FRAME_HAS_INVALID_RECT);
@@ -6899,16 +6903,19 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
       // The nsChangeHint_RepaintFrame for the style change will only
       // repaint the old overflow area, so if the overflow area has
       // changed (in particular, if it grows), we have to repaint the
       // new area here.
       InvalidateFrame();
     }
   }
 
+  if (anyOverflowChanged) {
+    nsSVGEffects::InvalidateDirectRenderingObservers(this);
+  }
   return anyOverflowChanged;
 }
 
 void
 nsIFrame::RecomputePerspectiveChildrenOverflow(const nsStyleContext* aStartStyle, const nsRect* aBounds)
 {
   // Children may check our size when getting our transform, make sure it's valid.
   nsSize oldSize = GetSize();
--- a/layout/svg/nsSVGEffects.cpp
+++ b/layout/svg/nsSVGEffects.cpp
@@ -9,16 +9,17 @@
 // Keep others in (case-insensitive) order:
 #include "nsCSSFrameConstructor.h"
 #include "nsISupportsImpl.h"
 #include "nsSVGClipPathFrame.h"
 #include "nsSVGPaintServerFrame.h"
 #include "nsSVGFilterFrame.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGTextPathFrame.h"
+#include "nsIReflowCallback.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // nsSVGRenderingObserver impl
 NS_IMPL_ISUPPORTS1(nsSVGRenderingObserver, nsIMutationObserver)
 
 void
@@ -279,28 +280,59 @@ nsSVGMarkerProperty::DoUpdate()
     // XXXjwatt: We need to unify SVG into standard reflow so we can just use
     // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here.
     nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
   }
   mFramePresShell->FrameConstructor()->PostRestyleEvent(
     mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
 }
 
+class nsAsyncNotifyGlyphMetricsChange MOZ_FINAL : public nsIReflowCallback
+{
+public:
+    nsAsyncNotifyGlyphMetricsChange(nsIFrame* aFrame) : mWeakFrame(aFrame)
+    {
+    }
+
+    virtual bool ReflowFinished()
+    {
+        nsSVGTextPathFrame* frame =
+            static_cast<nsSVGTextPathFrame*>(mWeakFrame.GetFrame());
+        if (frame) {
+            frame->NotifyGlyphMetricsChange();
+        }
+        delete this;
+        return true;
+    }
+
+    virtual void ReflowCallbackCanceled()
+    {
+        delete this;
+    }
+
+    nsWeakFrame mWeakFrame;
+};
+
 void
 nsSVGTextPathProperty::DoUpdate()
 {
   nsSVGIDRenderingObserver::DoUpdate();
   if (!mFrame)
     return;
 
   NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
 
   if (mFrame->GetType() == nsGkAtoms::svgTextPathFrame) {
-    nsSVGTextPathFrame* textPathFrame = static_cast<nsSVGTextPathFrame*>(mFrame);
-    textPathFrame->NotifyGlyphMetricsChange();
+    if (mFrame->PresContext()->PresShell()->IsReflowLocked()) {
+      nsIReflowCallback* cb = new nsAsyncNotifyGlyphMetricsChange(mFrame);
+      mFrame->PresContext()->PresShell()->PostReflowCallback(cb);
+    } else {
+      nsSVGTextPathFrame* textPathFrame = static_cast<nsSVGTextPathFrame*>(mFrame);
+      textPathFrame->NotifyGlyphMetricsChange();
+    }
   }
 }
 
 void
 nsSVGPaintingProperty::DoUpdate()
 {
   nsSVGIDRenderingObserver::DoUpdate();
   if (!mFrame)
@@ -524,16 +556,30 @@ GatherEnumerator(nsPtrHashKey<nsSVGRende
 {
   nsTArray<nsSVGRenderingObserver*>* array =
     static_cast<nsTArray<nsSVGRenderingObserver*>*>(aArg);
   array->AppendElement(aEntry->GetKey());
           
   return PL_DHASH_REMOVE;
 }
 
+static PLDHashOperator
+GatherEnumeratorForReflow(nsPtrHashKey<nsSVGRenderingObserver>* aEntry, void* aArg)
+{
+  if (!aEntry->GetKey()->ObservesReflow()) {
+    return PL_DHASH_NEXT;
+  }
+
+  nsTArray<nsSVGRenderingObserver*>* array =
+    static_cast<nsTArray<nsSVGRenderingObserver*>*>(aArg);
+  array->AppendElement(aEntry->GetKey());
+          
+  return PL_DHASH_REMOVE;
+}
+
 void
 nsSVGRenderingObserverList::InvalidateAll()
 {
   if (mObservers.Count() == 0)
     return;
 
   nsAutoTArray<nsSVGRenderingObserver*,10> observers;
 
@@ -541,16 +587,32 @@ nsSVGRenderingObserverList::InvalidateAl
   mObservers.EnumerateEntries(GatherEnumerator, &observers);
 
   for (uint32_t i = 0; i < observers.Length(); ++i) {
     observers[i]->InvalidateViaReferencedElement();
   }
 }
 
 void
+nsSVGRenderingObserverList::InvalidateAllForReflow()
+{
+  if (mObservers.Count() == 0)
+    return;
+
+  nsAutoTArray<nsSVGRenderingObserver*,10> observers;
+
+  // The PL_DHASH_REMOVE in GatherEnumerator drops all our observers here:
+  mObservers.EnumerateEntries(GatherEnumeratorForReflow, &observers);
+
+  for (uint32_t i = 0; i < observers.Length(); ++i) {
+    observers[i]->InvalidateViaReferencedElement();
+  }
+}
+
+void
 nsSVGRenderingObserverList::RemoveAll()
 {
   nsAutoTArray<nsSVGRenderingObserver*,10> observers;
 
   // The PL_DHASH_REMOVE in GatherEnumerator drops all our observers here:
   mObservers.EnumerateEntries(GatherEnumerator, &observers);
 
   // Our list is now cleared.  We need to notify the observers we've removed,
@@ -630,25 +692,29 @@ nsSVGEffects::InvalidateRenderingObserve
         observerList->InvalidateAll();
         return;
       }
     }
   }
 }
 
 void
-nsSVGEffects::InvalidateDirectRenderingObservers(Element *aElement)
+nsSVGEffects::InvalidateDirectRenderingObservers(Element *aElement, uint32_t aFlags /* = 0 */)
 {
   if (aElement->HasRenderingObservers()) {
     nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
     if (observerList) {
-      observerList->InvalidateAll();
+      if (aFlags & INVALIDATE_REFLOW) {
+        observerList->InvalidateAllForReflow();
+      } else {
+        observerList->InvalidateAll();
+      }
     }
   }
 }
 
 void
-nsSVGEffects::InvalidateDirectRenderingObservers(nsIFrame *aFrame)
+nsSVGEffects::InvalidateDirectRenderingObservers(nsIFrame *aFrame, uint32_t aFlags /* = 0 */)
 {
   if (aFrame->GetContent() && aFrame->GetContent()->IsElement()) {
-    InvalidateDirectRenderingObservers(aFrame->GetContent()->AsElement());
+    InvalidateDirectRenderingObservers(aFrame->GetContent()->AsElement(), aFlags);
   }
 }
--- a/layout/svg/nsSVGEffects.h
+++ b/layout/svg/nsSVGEffects.h
@@ -73,16 +73,18 @@ public:
   /**
    * @param aOK this is only for the convenience of callers. We set *aOK to false
    * if the frame is the wrong type
    */
   nsIFrame* GetReferencedFrame(nsIAtom* aFrameType, bool* aOK);
 
   Element* GetReferencedElement();
 
+  virtual bool ObservesReflow() { return true; }
+
 protected:
   // Non-virtual protected methods
   void StartListening();
   void StopListening();
 
   // Virtual protected methods
   virtual void DoUpdate() = 0; // called when the referenced resource changes.
 
@@ -180,16 +182,18 @@ protected:
   virtual void DoUpdate() MOZ_OVERRIDE;
 };
 
 class nsSVGTextPathProperty : public nsSVGIDRenderingObserver {
 public:
   nsSVGTextPathProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
     : nsSVGIDRenderingObserver(aURI, aFrame, aReferenceImage) {}
 
+  virtual bool ObservesReflow() MOZ_OVERRIDE { return false; }
+
 protected:
   virtual void DoUpdate() MOZ_OVERRIDE;
 };
  
 class nsSVGPaintingProperty : public nsSVGIDRenderingObserver {
 public:
   nsSVGPaintingProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
     : nsSVGIDRenderingObserver(aURI, aFrame, aReferenceImage) {}
@@ -239,16 +243,22 @@ public:
 
   /**
    * Drop all our observers, and notify them that we have changed and dropped
    * our reference to them.
    */
   void InvalidateAll();
 
   /**
+   * Drop all observers that observe reflow, and notify them that we have changed and dropped
+   * our reference to them.
+   */
+  void InvalidateAllForReflow();
+
+  /**
    * Drop all our observers, and notify them that we have dropped our reference
    * to them.
    */
   void RemoveAll();
 
 private:
   nsTHashtable<nsPtrHashKey<nsSVGRenderingObserver> > mObservers;
 };
@@ -367,22 +377,27 @@ public:
    * node) invalidating all mutation observers (not just
    * nsSVGRenderingObservers) on all nodes along the way (not just the first
    * node it finds with observers). In other words, by doing all the
    * things in parentheses in the preceding sentence, this method uses
    * knowledge about our implementation and what can be affected by SVG effects
    * to make invalidation relatively lightweight when an SVG effect changes.
    */
   static void InvalidateRenderingObservers(nsIFrame *aFrame);
+
+  enum {
+    INVALIDATE_REFLOW = 1
+  };
+
   /**
    * This can be called on any element or frame. Only direct observers of this
    * (frame's) element, if any, are invalidated.
    */
-  static void InvalidateDirectRenderingObservers(Element *aElement);
-  static void InvalidateDirectRenderingObservers(nsIFrame *aFrame);
+  static void InvalidateDirectRenderingObservers(Element *aElement, uint32_t aFlags = 0);
+  static void InvalidateDirectRenderingObservers(nsIFrame *aFrame, uint32_t aFlags = 0);
 
   /**
    * Get an nsSVGMarkerProperty for the frame, creating a fresh one if necessary
    */
   static nsSVGMarkerProperty *
   GetMarkerProperty(nsIURI *aURI, nsIFrame *aFrame,
                     const FramePropertyDescriptor *aProperty);
   /**