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
--- 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")