Bug 1321428 - Have ScrollTimelines be kept alive by their scrollSource while there are animations associated with them. r=birtles draft
authorBotond Ballo <botond@mozilla.com>
Wed, 23 Nov 2016 12:05:03 -0500
changeset 450526 0ad03ac539be2e6a01de2cb060805e1175e2ca57
parent 450525 1e7f812a94c73afe2fd401d2674d92d3c441f11f
child 450527 44544bd9141cdc58b4aaa07fd82725e6f2ef799f
push id38879
push userbballo@mozilla.com
push dateFri, 16 Dec 2016 21:24:01 +0000
reviewersbirtles
bugs1321428
milestone53.0a1
Bug 1321428 - Have ScrollTimelines be kept alive by their scrollSource while there are animations associated with them. r=birtles Without the scrollSource keeping the ScrollTimeline alive, the ScrollTimeline can get cycle-collected while it's outside of its active range. The mechanism used to achieve this is to give elements an optional DOM property of type ScrollTimelineSet, which keeps a strong reference to ScrollTimelines which have animations associated with them; this is very similar to how EffectSet is used to keep animations alive. MozReview-Commit-ID: FLnXDKoIN1P
dom/animation/ScrollTimeline.cpp
dom/animation/ScrollTimeline.h
dom/animation/ScrollTimelineSet.cpp
dom/animation/ScrollTimelineSet.h
dom/animation/moz.build
dom/base/FragmentOrElement.cpp
dom/base/nsGkAtomList.h
--- a/dom/animation/ScrollTimeline.cpp
+++ b/dom/animation/ScrollTimeline.cpp
@@ -112,26 +112,34 @@ ScrollTimeline::ToTimelineTime(const Tim
                   - timing->GetNavigationStartTimeStamp());
   return result;
 }
 
 // memo: This function is member function of AnimationTimeline.
 void
 ScrollTimeline::NotifyAnimationUpdated(Animation& aAnimation)
 {
+  if (mAnimations.IsEmpty()) {
+    RegisterWithScrollSource();
+  }
+
   // MEMO: Should I pause animation when setting animation?
   AnimationTimeline::NotifyAnimationUpdated(aAnimation);
   CalculateEffectiveTimeRange();
 }
 
 void
 ScrollTimeline::RemoveAnimation(Animation* aAnimation)
 {
   AnimationTimeline::RemoveAnimation(aAnimation);
   CalculateEffectiveTimeRange();
+
+  if (mAnimations.IsEmpty()) {
+    UnregisterFromScrollSource();
+  }
 }
 
 TimeStamp
 ScrollTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
 {
   TimeStamp result;
   RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
   if (MOZ_UNLIKELY(!timing)) {
@@ -270,15 +278,34 @@ ScrollTimeline::ParseProperties()
   if (mSpecifiedTimeRange.IsDouble()) {
     mSpecifiedTimeRangeCooked = Some(StickyTimeDuration::FromMilliseconds(
         mSpecifiedTimeRange.GetAsDouble()));
   }
   // Otherwise it's "auto", and we leave mSpecifiedTimeRangeCooked as None.
 }
 
 void
+ScrollTimeline::RegisterWithScrollSource()
+{
+  ScrollTimelineSet* scrollTimelineSet =
+    ScrollTimelineSet::GetOrCreateScrollTimelineSet(mElement);
+  scrollTimelineSet->AddScrollTimeline(*this);
+}
+
+void ScrollTimeline::UnregisterFromScrollSource()
+{
+  if (ScrollTimelineSet* scrollTimelineSet =
+      ScrollTimelineSet::GetScrollTimelineSet(mElement)) {
+    scrollTimelineSet->RemoveScrollTimeline(*this);
+    if (scrollTimelineSet->IsEmpty()) {
+      ScrollTimelineSet::DestroyScrollTimelineSet(mElement);
+    }
+  }
+}
+
+void
 ScrollPositionListener::ScrollPositionDidChange(nscoord, nscoord)
 {
   mTimeline->NotifyScroll();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/ScrollTimeline.h
+++ b/dom/animation/ScrollTimeline.h
@@ -14,16 +14,17 @@
 #include "mozilla/dom/ScrollTimelineBinding.h"
 #include "AnimationTimeline.h"
 #include "nsCSSValue.h"
 #include "nsIDocument.h"
 #include "nsIScrollPositionListener.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Animation.h"
 #include "mozilla/LinkedList.h"
+#include "mozilla/ScrollTimelineSet.h"
 
 struct JSContext;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount().
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
@@ -155,16 +156,19 @@ public:
   void Teardown();
 
 protected:
   nsIScrollableFrame* GetScrollFrame() const;
 
   void CalculateEffectiveTimeRange();
   void ParseProperties();
 
+  void RegisterWithScrollSource();
+  void UnregisterFromScrollSource();
+
   // Infrastructure
   nsCOMPtr<nsIDocument> mDocument;
   UniquePtr<ScrollPositionListener> mScrollPositionListener;
   bool mNeedsTick;
 
   // Raw versions of specified values of properties.
   RefPtr<Element> mElement;
   nsString mSpecifiedStartScrollOffset;
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineSet.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScrollTimelineSet.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollTimeline.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsGkAtoms.h"
+#include "nsINode.h"
+
+namespace mozilla {
+
+ScrollTimelineSet::ScrollTimelineSet()
+{
+  MOZ_COUNT_CTOR(ScrollTimelineSet);
+}
+
+ScrollTimelineSet::~ScrollTimelineSet()
+{
+  MOZ_COUNT_DTOR(ScrollTimelineSet);
+}
+
+void
+ScrollTimelineSet::Traverse(nsCycleCollectionTraversalCallback& aCallback)
+{
+  for (auto iter = mScrollTimelines.Iter(); !iter.Done(); iter.Next()) {
+    CycleCollectionNoteChild(aCallback, iter.Get()->GetKey(),
+                             "ScrollTimelineSet::mScrollTimelines[]",
+                             aCallback.Flags());
+  }
+}
+
+/* static */ ScrollTimelineSet*
+ScrollTimelineSet::GetScrollTimelineSet(dom::Element* aElement)
+{
+  return static_cast<ScrollTimelineSet*>(
+      aElement->GetProperty(nsGkAtoms::scrollTimelinesProperty));
+}
+
+/* static */ ScrollTimelineSet*
+ScrollTimelineSet::GetOrCreateScrollTimelineSet(dom::Element* aElement)
+{
+  ScrollTimelineSet* scrollTimelineSet = GetScrollTimelineSet(aElement);
+  if (scrollTimelineSet) {
+    return scrollTimelineSet;
+  }
+
+  scrollTimelineSet = new ScrollTimelineSet();
+
+  nsresult rv = aElement->SetProperty(nsGkAtoms::scrollTimelinesProperty,
+                                      scrollTimelineSet,
+                                      nsINode::DeleteProperty<ScrollTimelineSet>,
+                                      true);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("SetProperty failed");
+    delete scrollTimelineSet;
+    return nullptr;
+  }
+
+  return scrollTimelineSet;
+}
+
+/* static */ void
+ScrollTimelineSet::DestroyScrollTimelineSet(dom::Element* aElement)
+{
+  aElement->DeleteProperty(nsGkAtoms::scrollTimelinesProperty);
+}
+
+void
+ScrollTimelineSet::AddScrollTimeline(dom::ScrollTimeline& aScrollTimeline)
+{
+  if (mScrollTimelines.Contains(&aScrollTimeline)) {
+    return;
+  }
+
+  mScrollTimelines.PutEntry(&aScrollTimeline);
+}
+
+void
+ScrollTimelineSet::RemoveScrollTimeline(dom::ScrollTimeline& aScrollTimeline)
+{
+  if (!mScrollTimelines.Contains(&aScrollTimeline)) {
+    return;
+  }
+
+  mScrollTimelines.RemoveEntry(&aScrollTimeline);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineSet.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ScrollTimelineSet_h
+#define mozilla_ScrollTimelineSet_h
+
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+
+class nsPresContext;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+class ScrollTimeline;
+} // namespace dom
+
+// A wrapper around a hashset of ScrollTimeline objects to handle
+// storing the set as a property of an element.
+// The purpose of this class is to keep a ScrollTimeline alive while its
+// scrollSource element is alive and it has animations associated with it;
+// the timeline is added to the scrollSource element's ScrollTimelineSet for
+// the duration when it has animations associated with it.
+class ScrollTimelineSet
+{
+public:
+  ScrollTimelineSet();
+  ~ScrollTimelineSet();
+
+  // Methods for supporting cycle-collection
+  void Traverse(nsCycleCollectionTraversalCallback& aCallback);
+
+  static ScrollTimelineSet* GetScrollTimelineSet(dom::Element* aElement);
+  static ScrollTimelineSet* GetOrCreateScrollTimelineSet(dom::Element* aElement);
+  static void DestroyScrollTimelineSet(dom::Element* aElement);
+
+  void AddScrollTimeline(dom::ScrollTimeline& aScrollTimeline);
+  void RemoveScrollTimeline(dom::ScrollTimeline& aScrollTimeline);
+
+  bool IsEmpty() const { return mScrollTimelines.IsEmpty(); }
+
+private:
+  typedef nsTHashtable<nsRefPtrHashKey<dom::ScrollTimeline>>
+    OwningScrollTimelineSet;
+
+  OwningScrollTimelineSet mScrollTimelines;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollTimelineSet_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -30,16 +30,17 @@ EXPORTS.mozilla += [
     'ComputedTiming.h',
     'ComputedTimingFunction.h',
     'EffectCompositor.h',
     'EffectSet.h',
     'KeyframeEffectParams.h',
     'KeyframeUtils.h',
     'PendingAnimationTracker.h',
     'PseudoElementHashEntry.h',
+    'ScrollTimelineSet.h',
     'TimingParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffectReadOnly.cpp',
     'AnimationEffectTiming.cpp',
     'AnimationEffectTimingReadOnly.cpp',
@@ -53,16 +54,17 @@ UNIFIED_SOURCES += [
     'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
     'KeyframeEffectParams.cpp',
     'KeyframeEffectReadOnly.cpp',
     'KeyframeUtils.cpp',
     'PendingAnimationTracker.cpp',
     'ScrollTimeline.cpp',
+    'ScrollTimelineSet.cpp',
     'ScrollTimelineUtils.cpp',
     'TimingParams.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/layout/base',
     '/layout/style',
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/dom/FragmentOrElement.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/ScrollTimelineSet.h"
 #include "mozilla/dom/Attr.h"
 #include "nsDOMAttributeMap.h"
 #include "nsIAtom.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/Event.h"
 #include "nsIDocumentInlines.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIDOMNodeList.h"
@@ -1348,16 +1349,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Fr
         tmp->DeleteProperty(*props[i]);
       }
       if (tmp->MayHaveAnimations()) {
         nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
         for (uint32_t i = 0; effectProps[i]; ++i) {
           tmp->DeleteProperty(effectProps[i]);
         }
       }
+      tmp->DeleteProperty(nsGkAtoms::scrollTimelinesProperty);
     }
   }
 
   // Unlink child content (and unbind our subtree).
   if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
     uint32_t childCount = tmp->mAttrsAndChildren.ChildCount();
     if (childCount) {
       // Don't allow script to run while we're unbinding everything.
@@ -1929,16 +1931,20 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
         for (uint32_t i = 0; effectProps[i]; ++i) {
           EffectSet* effectSet =
             static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
           if (effectSet) {
             effectSet->Traverse(cb);
           }
         }
       }
+      if (ScrollTimelineSet* scrollTimelineSet =
+          static_cast<ScrollTimelineSet*>(tmp->GetProperty(nsGkAtoms::scrollTimelinesProperty))) {
+        scrollTimelineSet->Traverse(cb);
+      }
     }
   }
 
   // Traverse attribute names and child content.
   {
     uint32_t i;
     uint32_t attrs = tmp->mAttrsAndChildren.AttrCount();
     for (i = 0; i < attrs; i++) {
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2149,16 +2149,17 @@ GK_ATOM(transitionsOfBeforeProperty, "Tr
 GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
 GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
 GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
 GK_ATOM(baseURIProperty, "baseURIProperty")
 GK_ATOM(lockedStyleStates, "lockedStyleStates")
 GK_ATOM(apzCallbackTransform, "apzCallbackTransform")
 GK_ATOM(restylableAnonymousNode, "restylableAnonymousNode")
 GK_ATOM(paintRequestTime, "PaintRequestTime")
+GK_ATOM(scrollTimelinesProperty, "ScrollTimelinesProperty")  // ScrollTimelineSet*
 
 // Languages for lang-specific transforms
 GK_ATOM(Japanese, "ja")
 GK_ATOM(Chinese, "zh-CN")
 GK_ATOM(Taiwanese, "zh-TW")
 GK_ATOM(HongKongChinese, "zh-HK")
 GK_ATOM(Unicode, "x-unicode")