--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -327,16 +327,17 @@ public:
* updated in |aComposeResult|.
*/
template<typename ComposeAnimationResult>
void ComposeStyle(ComposeAnimationResult&& aComposeResult,
const nsCSSPropertyIDSet& aPropertiesToSkip);
void NotifyEffectTimingUpdated();
void NotifyGeometricAnimationsStartingThisFrame();
+ StickyTimeDuration EffectEnd() const;
/**
* Used by subclasses to synchronously queue a cancel event in situations
* where the Animation may have been cancelled.
*
* We need to do this synchronously because after a CSS animation/transition
* is canceled, it will be released by its owning element and may not still
* exist when we would normally go to queue events on the next tick.
@@ -410,17 +411,16 @@ protected:
* useful because in some cases animations that are painted together
* may need to be synchronized.
*/
bool IsNewlyStarted() const {
return mPendingState == PendingState::PlayPending &&
mPendingReadyTime.IsNull();
}
bool IsPossiblyOrphanedPendingAnimation() const;
- StickyTimeDuration EffectEnd() const;
nsIDocument* GetRenderedDocument() const;
RefPtr<AnimationTimeline> mTimeline;
RefPtr<AnimationEffectReadOnly> mEffect;
// The beginning of the delay period.
Nullable<TimeDuration> mStartTime; // Timeline timescale
Nullable<TimeDuration> mHoldTime; // Animation timescale
--- a/dom/animation/AnimationTimeline.cpp
+++ b/dom/animation/AnimationTimeline.cpp
@@ -10,22 +10,22 @@
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(AnimationTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationTimeline)
tmp->mAnimationOrder.clear();
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mAnimations)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimations)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationTimeline)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mAnimations)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimations)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AnimationTimeline)
NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationTimeline)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationTimeline)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationTimeline)
--- a/dom/animation/AnimationTimeline.h
+++ b/dom/animation/AnimationTimeline.h
@@ -22,40 +22,37 @@
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
namespace mozilla {
namespace dom {
class Animation;
+class ScrollTimeline;
class AnimationTimeline
: public nsISupports
, public nsWrapperCache
{
public:
- explicit AnimationTimeline(nsIGlobalObject* aWindow)
- : mWindow(aWindow)
+ AnimationTimeline()
{
- MOZ_ASSERT(mWindow);
}
protected:
virtual ~AnimationTimeline()
{
mAnimationOrder.clear();
}
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationTimeline)
- nsIGlobalObject* GetParentObject() const { return mWindow; }
-
// AnimationTimeline methods
virtual Nullable<TimeDuration> GetCurrentTime() const = 0;
// Wrapper functions for AnimationTimeline DOM methods when called from
// script.
Nullable<double> GetCurrentTimeAsDouble() const {
return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
}
@@ -99,16 +96,19 @@ public:
* time.
*/
bool HasAnimations() const {
return !mAnimations.IsEmpty();
}
virtual void RemoveAnimation(Animation* aAnimation);
+ virtual ScrollTimeline* AsScrollTimeline() { return nullptr; }
+ virtual const ScrollTimeline* AsScrollTimeline() const { return nullptr; }
+
protected:
nsCOMPtr<nsIGlobalObject> mWindow;
// Animations observing this timeline
//
// We store them in (a) a hashset for quick lookup, and (b) an array
// to maintain a fixed sampling order.
//
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -19,21 +19,21 @@ namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
AnimationTimeline)
tmp->UnregisterFromRefreshDriver();
if (tmp->isInList()) {
tmp->remove();
}
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentTimeline,
AnimationTimeline)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
AnimationTimeline)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocumentTimeline)
NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline)
--- a/dom/animation/DocumentTimeline.h
+++ b/dom/animation/DocumentTimeline.h
@@ -28,17 +28,18 @@ namespace dom {
class DocumentTimeline final
: public AnimationTimeline
, public nsARefreshObserver
, public LinkedListElement<DocumentTimeline>
{
public:
DocumentTimeline(nsIDocument* aDocument, const TimeDuration& aOriginTime)
- : AnimationTimeline(aDocument->GetParentObject())
+ : AnimationTimeline()
+ , mWindow(aDocument->GetParentObject())
, mDocument(aDocument)
, mIsObservingRefreshDriver(false)
, mOriginTime(aOriginTime)
{
if (mDocument) {
mDocument->Timelines().insertBack(this);
}
}
@@ -53,16 +54,18 @@ protected:
}
}
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline,
AnimationTimeline)
+ nsIGlobalObject* GetParentObject() { return mWindow; }
+
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<DocumentTimeline>
Constructor(const GlobalObject& aGlobal,
const DocumentTimelineOptions& aOptions,
ErrorResult& aRv);
@@ -89,16 +92,17 @@ public:
void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
protected:
TimeStamp GetCurrentTimeStamp() const;
nsRefreshDriver* GetRefreshDriver() const;
void UnregisterFromRefreshDriver();
+ nsCOMPtr<nsIGlobalObject> mWindow;
nsCOMPtr<nsIDocument> mDocument;
// The most recently used refresh driver time. This is used in cases where
// we don't have a refresh driver (e.g. because we are in a display:none
// iframe).
mutable TimeStamp mLastRefreshDriverTime;
bool mIsObservingRefreshDriver;
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimeline.cpp
@@ -0,0 +1,381 @@
+/* -*- 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 "ScrollTimeline.h"
+#include "mozilla/dom/ScrollTimelineBinding.h"
+#include "mozilla/dom/ScrollTimelineUtils.h"
+#include "mozilla/MathAlgorithms.h" // for Clamp()
+#include "AnimationUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSSParser.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIPresShell.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ScrollTimeline)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScrollTimeline,
+ AnimationTimeline)
+ tmp->Teardown();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollSource)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ScrollTimeline,
+ AnimationTimeline)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ScrollTimeline,
+ AnimationTimeline)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ScrollTimeline)
+NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline)
+
+NS_IMPL_ADDREF_INHERITED(ScrollTimeline, AnimationTimeline)
+NS_IMPL_RELEASE_INHERITED(ScrollTimeline, AnimationTimeline)
+
+void
+ScrollTimeline::RemoveFromList()
+{
+ if (isInList()) {
+ remove();
+ }
+}
+
+void
+ScrollTimeline::Teardown()
+{
+ // Note: This function is required to be idempotent, as it can be called from
+ // both cycleCollection::Unlink() and ~ScrollTimeline(). When modifying this
+ // function, be sure to preserve this property.
+ if (mScrollSource) {
+ UnregisterFromScrollSource();
+ }
+ if (nsIScrollableFrame* scrollFrame = GetScrollFrame()) {
+ scrollFrame->RemoveScrollPositionListener(mScrollPositionListener.get());
+ }
+ RemoveFromList();
+}
+
+JSObject*
+ScrollTimeline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ScrollTimelineBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<ScrollTimeline>
+ScrollTimeline::Constructor(const GlobalObject& aGlobal,
+ const ScrollTimelineOptions& aOptions,
+ ErrorResult& aRv)
+{
+ nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ Element* scrollSource = aOptions.mScrollSource.WasPassed()
+ ? &aOptions.mScrollSource.Value()
+ : doc->GetDocumentElement();
+ if (!scrollSource) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<ScrollTimeline> timeline = new ScrollTimeline(scrollSource,
+ aOptions.mOrientation,
+ aOptions.mStartScrollOffset,
+ aOptions.mEndScrollOffset,
+ aOptions.mTimeRange,
+ aOptions.mFillMode);
+ return timeline.forget();
+}
+
+Nullable<TimeDuration>
+ScrollTimeline::GetCurrentTime() const
+{
+ if (!mHaveFrame) {
+ return Nullable<TimeDuration>{};
+ }
+ return ScrollTimelineUtils::CalculateCurrentTime(mCurrentScroll,
+ mMinScroll, mMaxScroll, TimeDuration(mEffectiveTimeRange), mFillMode);
+}
+
+Nullable<TimeDuration>
+ScrollTimeline::ToTimelineTime(const TimeStamp& aTimeStamp) const
+{
+ return Nullable<TimeDuration>{};
+}
+
+// memo: This function is member function of AnimationTimeline.
+void
+ScrollTimeline::NotifyAnimationUpdated(Animation& aAnimation)
+{
+ if (mAnimations.IsEmpty()) {
+ RegisterWithScrollSourceAsOwned();
+ }
+
+ // MEMO: Should I pause animation when setting animation?
+ AnimationTimeline::NotifyAnimationUpdated(aAnimation);
+ CalculateEffectiveTimeRange();
+}
+
+void
+ScrollTimeline::RemoveAnimation(Animation* aAnimation)
+{
+ AnimationTimeline::RemoveAnimation(aAnimation);
+ CalculateEffectiveTimeRange();
+
+ if (mAnimations.IsEmpty()) {
+ UnregisterFromScrollSourceAsOwned();
+ }
+}
+
+TimeStamp
+ScrollTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
+{
+ return TimeStamp{};
+}
+
+void
+ScrollTimeline::CalculateEffectiveTimeRange()
+{
+ if (mSpecifiedTimeRangeCooked) {
+ mEffectiveTimeRange = mSpecifiedTimeRangeCooked.ref();
+ } else {
+ mEffectiveTimeRange = 0;
+ for (Animation* animation : mAnimationOrder) {
+ StickyTimeDuration endTime = animation->EffectEnd();
+ if (endTime == StickyTimeDuration::Forever()) {
+ mEffectiveTimeRange = 0;
+ break;
+ }
+ mEffectiveTimeRange = StickyTimeDuration::Max(endTime, mEffectiveTimeRange);
+ }
+ }
+}
+
+static nscoord
+InterpretValue(const nsCSSValue& aValue,
+ nscoord aMin,
+ nscoord aMax,
+ nscoord aDefault)
+{
+ switch (aValue.GetUnit())
+ {
+ case eCSSUnit_Auto:
+ return aDefault;
+ case eCSSUnit_Pixel:
+ return Clamp(aValue.GetPixelLength(), aMin, aMax);
+ case eCSSUnit_Percent:
+ return aMin + ((aMax - aMin) * aValue.GetPercentValue());
+ default:
+ NS_WARNING("Unhandled CSS value type");
+ return aDefault;
+ }
+}
+
+static void
+FormatValue(const nsCSSValue& aValue, nsString& aOutResult)
+{
+ switch (aValue.GetUnit())
+ {
+ case eCSSUnit_Auto:
+ aOutResult.AppendPrintf("auto");
+ break;
+ case eCSSUnit_Pixel:
+ aOutResult.AppendPrintf("%d px", aValue.GetPixelLength());
+ break;
+ case eCSSUnit_Percent:
+ aOutResult.AppendPrintf("%f %%", aValue.GetPercentValue());
+ break;
+ default:
+ NS_WARNING("Unhandled CSS value type");
+ }
+}
+
+void
+ScrollTimeline::GetStartScrollOffset(nsString& aOutResult) const {
+ FormatValue(mComputedStartScrollOffset, aOutResult);
+}
+
+void
+ScrollTimeline::GetEndScrollOffset(nsString& aOutResult) const {
+ FormatValue(mComputedEndScrollOffset, aOutResult);
+}
+
+void
+ScrollTimeline::QueryScrollValues()
+{
+ nsIScrollableFrame* frame = GetScrollFrame();
+ mHaveFrame = (frame != nullptr);
+ if (!mHaveFrame)
+ return;
+
+ nsPoint scrollOffset = frame->GetScrollPosition();
+ nsRect scrollRange = frame->GetScrollRange();
+ nscoord min, max;
+ if (mComputedOrientation == ScrollDirection::Horizontal) {
+ mCurrentScroll = scrollOffset.x;
+ min = scrollRange.x;
+ max = scrollRange.XMost();
+ } else {
+ mCurrentScroll = scrollOffset.y;
+ min = scrollRange.y;
+ max = scrollRange.YMost();
+ }
+ mMinScroll = InterpretValue(mComputedStartScrollOffset, min, max, min);
+ mMaxScroll = InterpretValue(mComputedEndScrollOffset, min, max, max);
+}
+
+void
+ScrollTimeline::NotifyScroll()
+{
+ mNeedsTick = true;
+}
+
+auto
+ScrollTimeline::GetScrollRange() -> ScrollRange
+{
+ // Call QueryScrollValues() to pick up updated min and max values
+ // if the scroll frame has been reflowed since they were last queried.
+ QueryScrollValues();
+ return {mMinScroll, mMaxScroll};
+}
+
+void
+ScrollTimeline::Tick()
+{
+ if (!mNeedsTick) {
+ return;
+ }
+
+ QueryScrollValues();
+
+ nsTArray<Animation*> animationsToRemove(mAnimations.Count());
+
+ nsAutoAnimationMutationBatch mb(mScrollSource->OwnerDoc());
+
+ for (Animation* animation = mAnimationOrder.getFirst(); animation;
+ animation = animation->getNext()) {
+ // Skip any animations that are longer need associated with this timeline.
+ if (animation->GetTimeline() != this) {
+ // If animation has some other timeline, it better not be also in the
+ // animation list of this timeline object!
+ MOZ_ASSERT(!animation->GetTimeline());
+ animationsToRemove.AppendElement(animation);
+ continue;
+ }
+
+ animation->Tick();
+ }
+
+ for (Animation* animation : animationsToRemove) {
+ RemoveAnimation(animation);
+ }
+
+ mNeedsTick = false;
+}
+
+nsIScrollableFrame*
+ScrollTimeline::GetScrollFrame() const
+{
+ if (!mScrollSource) {
+ return nullptr;
+ }
+ return nsLayoutUtils::FindScrollableFrameFor(
+ nsLayoutUtils::FindOrCreateIDFor(mScrollSource.get()));
+}
+
+void
+ScrollTimeline::ParseProperties(const nsString& aSpecifiedStartScrollOffset,
+ const nsString& aSpecifiedEndScrollOffset)
+{
+ // Parse scroll offsets
+ nsIDocument* doc = mScrollSource->OwnerDoc();
+ nsCSSParser parser(doc->CSSLoader());
+ // Allow the same formats as for the "width" property.
+ parser.ParseLonghandProperty(nsCSSPropertyID::eCSSProperty_width,
+ aSpecifiedStartScrollOffset,
+ doc->GetDocumentURI(),
+ doc->GetDocumentURI(),
+ doc->NodePrincipal(),
+ mComputedStartScrollOffset);
+ parser.ParseLonghandProperty(nsCSSPropertyID::eCSSProperty_width,
+ aSpecifiedEndScrollOffset,
+ doc->GetDocumentURI(),
+ doc->GetDocumentURI(),
+ doc->NodePrincipal(),
+ mComputedEndScrollOffset);
+
+ // Parse time range
+ 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(mScrollSource);
+ scrollTimelineSet->AddScrollTimeline(*this);
+}
+
+void
+ScrollTimeline::UnregisterFromScrollSource()
+{
+ if (ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetScrollTimelineSet(mScrollSource)) {
+ scrollTimelineSet->RemoveScrollTimeline(*this);
+ if (scrollTimelineSet->IsEmpty()) {
+ ScrollTimelineSet::DestroyScrollTimelineSet(mScrollSource);
+ }
+ }
+}
+
+void
+ScrollTimeline::RegisterWithScrollSourceAsOwned()
+{
+ ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetOrCreateScrollTimelineSet(mScrollSource);
+ scrollTimelineSet->AddOwnedScrollTimeline(*this);
+}
+
+void
+ScrollTimeline::UnregisterFromScrollSourceAsOwned()
+{
+ if (ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetScrollTimelineSet(mScrollSource)) {
+ scrollTimelineSet->RemoveOwnedScrollTimeline(*this);
+ if (scrollTimelineSet->IsEmpty()) {
+ ScrollTimelineSet::DestroyScrollTimelineSet(mScrollSource);
+ }
+ }
+}
+
+void
+ScrollTimeline::RegisterWithScrollFrame(nsIScrollableFrame* aScrollFrame)
+{
+ aScrollFrame->AddScrollPositionListener(mScrollPositionListener.get());
+}
+
+void
+ScrollPositionListener::ScrollPositionDidChange(nscoord, nscoord)
+{
+ mTimeline->NotifyScroll();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimeline.h
@@ -0,0 +1,211 @@
+/* -*- 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_dom_ScrollTimeline_h
+#define mozilla_dom_ScrollTimeline_h
+
+#include "AnimationTimeline.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/ScrollTimelineSet.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollTimelineBinding.h"
+#include "nsCSSValue.h"
+#include "nsIDocument.h"
+#include "nsIScrollPositionListener.h"
+
+struct JSContext;
+
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount().
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
+namespace mozilla {
+namespace dom {
+
+class ScrollPositionListener : public nsIScrollPositionListener {
+public:
+ explicit ScrollPositionListener(ScrollTimeline* aTimeline) {
+ mTimeline = aTimeline;
+ }
+ virtual ~ScrollPositionListener() {}
+
+ void ScrollPositionWillChange(nscoord, nscoord) override {}
+ void ScrollPositionDidChange(nscoord, nscoord) override;
+private:
+ ScrollTimeline* mTimeline;
+};
+
+/**
+ * Implementation notes
+ * --------------------
+ *
+ * ScrollTimelines do not observe refreshes the way DocumentTimelines do.
+ * This is because the refresh driver keeps ticking while it has registered
+ * refresh observers. For a DocumentTimeline, it's appropriate to keep the
+ * refresh driver ticking as long as there are active animations, since the
+ * animations need to be sampled on every frame. Scroll-driven animations,
+ * however, only need to be sampled when scrolling has occurred, so keeping
+ * the refresh driver ticking is wasteful.
+ *
+ * As a result, we have the refresh driver call ScrollTimeline::Tick()
+ * directly whenever it ticks (without causing ticks itself). Additionally,
+ * in Tick() we only actually sample the animation if scrolling has occurred
+ * (which the scroll frame notifies us of by calling NotifyScroll()).
+ *
+ */
+class ScrollTimeline final
+ : public AnimationTimeline
+ , public LinkedListElement<ScrollTimeline>
+{
+public:
+ ScrollTimeline(Element* aScrollSource,
+ const Optional<ScrollDirection>& aOrientation,
+ const nsString& aStartScrollOffset,
+ const nsString& aEndScrollOffset,
+ const OwningDoubleOrScrollTimelineAutoKeyword& aTimeRange,
+ FillMode aFillMode)
+ : mScrollPositionListener(MakeUnique<ScrollPositionListener>(this))
+ , mNeedsTick(false)
+ , mHaveFrame(false)
+ , mScrollSource(aScrollSource)
+ , mSpecifiedTimeRange(aTimeRange)
+ , mFillMode(aFillMode)
+ // TODO: If no orientation was passed, compute it based on what direction
+ // |mElement| is scrollable in.
+ , mComputedOrientation(aOrientation.WasPassed()
+ ? aOrientation.Value()
+ : ScrollDirection::Vertical)
+ , mCurrentScroll(0)
+ , mMinScroll(0)
+ , mMaxScroll(0)
+ {
+ MOZ_ASSERT(mScrollSource);
+ mScrollSource->OwnerDoc()->ScrollTimelines().insertBack(this);
+ RegisterWithScrollSource();
+ if (nsIScrollableFrame* scrollFrame = GetScrollFrame()) {
+ RegisterWithScrollFrame(scrollFrame);
+ }
+ ParseProperties(aStartScrollOffset, aEndScrollOffset);
+ CalculateEffectiveTimeRange();
+ QueryScrollValues();
+ }
+
+protected:
+ virtual ~ScrollTimeline()
+ {
+ Teardown();
+ }
+
+public:
+ // Plumbing
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ScrollTimeline,
+ AnimationTimeline)
+ nsIGlobalObject* GetParentObject() const { return mScrollSource->GetOwnerGlobal(); }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // AnimationTimeline methods
+ virtual Nullable<TimeDuration> GetCurrentTime() const override;
+ bool TracksWallclockTime() const override {
+ return false;
+ }
+ Nullable<TimeDuration> ToTimelineTime(const TimeStamp& aTimeStamp) const
+ override;
+ TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override;
+ void NotifyAnimationUpdated(Animation& aAnimation) override;
+ void RemoveAnimation(Animation* aAnimation) override;
+ ScrollTimeline* AsScrollTimeline() override { return this; }
+ const ScrollTimeline* AsScrollTimeline() const override { return this; }
+
+ // WebIDL constructor
+ static already_AddRefed<ScrollTimeline>
+ Constructor(const GlobalObject& aGlobal,
+ const ScrollTimelineOptions& aOptions,
+ ErrorResult& aRv);
+
+ // WebIDL properties
+ Element* SourceElement() const { return mScrollSource; }
+ ScrollDirection Orientation() const { return mComputedOrientation; }
+ void GetStartScrollOffset(nsString& aOutResult) const;
+ void GetEndScrollOffset(nsString& aOutResult) const;
+ void GetTimeRange(OwningDoubleOrScrollTimelineAutoKeyword& aOutResult) const {
+ aOutResult = mSpecifiedTimeRange;
+ }
+ FillMode Fill() const {
+ return mFillMode;
+ }
+
+ // Types used in computed properties
+ struct ScrollRange {
+ nscoord mMinScroll;
+ nscoord mMaxScroll;
+ };
+
+ // Computed properties
+ const StickyTimeDuration& GetEffectiveTimeRange() const {
+ return mEffectiveTimeRange;
+ }
+ ScrollRange GetScrollRange();
+
+ // Other methods
+ void Tick();
+ void NotifyScroll();
+
+ void RegisterWithScrollFrame(nsIScrollableFrame* aScrollFrame);
+
+ void RemoveFromList();
+ void Teardown();
+
+protected:
+ nsIScrollableFrame* GetScrollFrame() const;
+
+ void CalculateEffectiveTimeRange();
+ void ParseProperties(const nsString& aSpecifiedStartScrollOffset,
+ const nsString& aSpecifiedEndScrollOffset);
+ void QueryScrollValues();
+
+ void RegisterWithScrollSource();
+ void UnregisterFromScrollSource();
+
+ void RegisterWithScrollSourceAsOwned();
+ void UnregisterFromScrollSourceAsOwned();
+
+ // Infrastructure
+ nsCOMPtr<nsIDocument> mDocument;
+ UniquePtr<ScrollPositionListener> mScrollPositionListener;
+ bool mNeedsTick : 1;
+ bool mHaveFrame : 1; // whether scrollSource currently has a scroll frame
+
+ // Raw versions of specified values of properties.
+ RefPtr<Element> mScrollSource;
+ // We don't save the specified start and end scroll offset; instead, we
+ // synthesize them from the computed offsets when necessary.
+ OwningDoubleOrScrollTimelineAutoKeyword mSpecifiedTimeRange;
+ FillMode mFillMode;
+
+ // Cooked versions of specified values of properties
+ Maybe<StickyTimeDuration> mSpecifiedTimeRangeCooked;
+
+ // Computed values of properties
+ ScrollDirection mComputedOrientation;
+ nsCSSValue mComputedStartScrollOffset;
+ nsCSSValue mComputedEndScrollOffset;
+ StickyTimeDuration mEffectiveTimeRange;
+ // These values are only valid if mHaveFrame is true.
+ nscoord mCurrentScroll, mMinScroll, mMaxScroll;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScrollTimeline_h
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineSet.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 = mOwnedScrollTimelines.Iter(); !iter.Done(); iter.Next()) {
+ CycleCollectionNoteChild(aCallback, iter.Get()->GetKey(),
+ "ScrollTimelineSet::mOwnedScrollTimelines[]",
+ 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 (mAllScrollTimelines.Contains(&aScrollTimeline)) {
+ return;
+ }
+
+ mAllScrollTimelines.PutEntry(&aScrollTimeline);
+}
+
+void
+ScrollTimelineSet::RemoveScrollTimeline(dom::ScrollTimeline& aScrollTimeline)
+{
+ if (!mAllScrollTimelines.Contains(&aScrollTimeline)) {
+ return;
+ }
+
+ mAllScrollTimelines.RemoveEntry(&aScrollTimeline);
+}
+
+void
+ScrollTimelineSet::AddOwnedScrollTimeline(dom::ScrollTimeline& aScrollTimeline)
+{
+ if (mOwnedScrollTimelines.Contains(&aScrollTimeline)) {
+ return;
+ }
+
+ mOwnedScrollTimelines.PutEntry(&aScrollTimeline);
+}
+
+void
+ScrollTimelineSet::RemoveOwnedScrollTimeline(dom::ScrollTimeline& aScrollTimeline)
+{
+ if (!mOwnedScrollTimelines.Contains(&aScrollTimeline)) {
+ return;
+ }
+
+ mOwnedScrollTimelines.RemoveEntry(&aScrollTimeline);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineSet.h
@@ -0,0 +1,76 @@
+/* -*- 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.
+// This class has two purposes:
+// - To allow iterating over all of the ScrollTimelines that gave a
+// given element as their scrollSource.
+// This is the purpose of mAllScrollTimelines.
+// - To keep a ScrollTimeline alive while its scrollSource element is
+// alive and it has animations associated with it.
+// This is the purpose of mOwnedScrollTimelines.
+// TODO: Since the timelines in mOwnedScrollTimelines are a subset of the
+// timelines in mAllScrollTimelines, we are storing the timelines in
+// mOwnedScrollTimelines redundantly. Perhaps we should just store
+// (plain pointer, is owned?) pairs, and call AddRef() and Release()
+// directly where appropriate?
+class ScrollTimelineSet
+{
+private:
+ typedef nsTHashtable<nsRefPtrHashKey<dom::ScrollTimeline>>
+ OwningScrollTimelineSet;
+ typedef nsTHashtable<nsPtrHashKey<dom::ScrollTimeline>>
+ NonOwningScrollTimelineSet;
+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);
+
+ const NonOwningScrollTimelineSet& GetAllScrollTimelines() const {
+ return mAllScrollTimelines;
+ }
+
+ void AddOwnedScrollTimeline(dom::ScrollTimeline& aScrollTimeline);
+ void RemoveOwnedScrollTimeline(dom::ScrollTimeline& aScrollTimeline);
+
+ bool IsEmpty() const {
+ return mOwnedScrollTimelines.IsEmpty() && mAllScrollTimelines.IsEmpty();
+ }
+
+private:
+ OwningScrollTimelineSet mOwnedScrollTimelines;
+ NonOwningScrollTimelineSet mAllScrollTimelines;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollTimelineSet_h
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineUtils.cpp
@@ -0,0 +1,43 @@
+/* -*- 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 "ScrollTimelineUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+Nullable<TimeDuration>
+ScrollTimelineUtils::CalculateCurrentTime(nscoord aCurrentScroll,
+ nscoord aMinScroll,
+ nscoord aMaxScroll,
+ const TimeDuration& aEffectiveTimeRange,
+ FillMode aFillMode)
+{
+ Nullable<TimeDuration> result; // Initializes to null
+ if (aCurrentScroll < aMinScroll) {
+ if (aFillMode == FillMode::Both || aFillMode == FillMode::Backwards) {
+ result.SetValue(TimeDuration());
+ }
+ return result;
+ }
+ if (aCurrentScroll >= aMaxScroll) {
+ if (aFillMode == FillMode::Both || aFillMode == FillMode::Forwards) {
+ result.SetValue(aEffectiveTimeRange);
+ }
+ return result;
+ }
+ double scrollPosition = CSSPixel::FromAppUnits(aCurrentScroll - aMinScroll);
+ double scrollRange = CSSPixel::FromAppUnits(aMaxScroll - aMinScroll);
+ MOZ_ASSERT(aEffectiveTimeRange != TimeDuration::Forever());
+ if (scrollRange != 0) {
+ result.SetValue(
+ TimeDuration(aEffectiveTimeRange.MultDouble(scrollPosition / scrollRange)));
+ }
+ return result;
+}
+
+} // namespace dom
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/animation/ScrollTimelineUtils.h
@@ -0,0 +1,42 @@
+/* -*- 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_dom_ScrollTimelineUtils_h
+#define mozilla_dom_ScrollTimelineUtils_h
+
+#include "Units.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/Nullable.h"
+
+// The purpose of this class to house helper functions that are shared
+// by the main-thread implementation of scroll timelines (ScrollTimeline.cpp),
+// and the OMTA implementation (AsyncCompositionManager.cpp).
+
+namespace mozilla {
+namespace dom {
+
+struct ScrollTimelineUtils {
+ /**
+ * Calculate the current time of a scroll-driven animation.
+ * @param aCurrentScroll the current scroll offset, in app units
+ * @param aMinScroll the start scroll offset, in app units
+ * @param aMaxScroll the end scroll offset, in app units
+ * @param aEffectiveTimeRange the scroll timeline's effective time range
+ * @param aFillMode the scroll timeline's fill mode
+ * @return the calculated current time
+ */
+ Nullable<TimeDuration>
+ static CalculateCurrentTime(nscoord aCurrentScroll,
+ nscoord aMinScroll,
+ nscoord aMaxScroll,
+ const TimeDuration& aEffectiveTimeRange,
+ FillMode aFillMode);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScrollTimelineUtils_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -15,16 +15,18 @@ EXPORTS.mozilla.dom += [
'AnimationEffectReadOnly.h',
'AnimationEffectTiming.h',
'AnimationEffectTimingReadOnly.h',
'AnimationTimeline.h',
'CSSPseudoElement.h',
'DocumentTimeline.h',
'KeyframeEffect.h',
'KeyframeEffectReadOnly.h',
+ 'ScrollTimeline.h',
+ 'ScrollTimelineUtils.h',
]
EXPORTS.mozilla += [
'AnimationComparator.h',
'AnimationPerformanceWarning.h',
'AnimationTarget.h',
'AnimationUtils.h',
'AnimValuesStyleRule.h',
@@ -32,16 +34,17 @@ EXPORTS.mozilla += [
'ComputedTimingFunction.h',
'EffectCompositor.h',
'EffectSet.h',
'Keyframe.h',
'KeyframeEffectParams.h',
'KeyframeUtils.h',
'PendingAnimationTracker.h',
'PseudoElementHashEntry.h',
+ 'ScrollTimelineSet.h',
'TimingParams.h',
]
UNIFIED_SOURCES += [
'Animation.cpp',
'AnimationEffectReadOnly.cpp',
'AnimationEffectTiming.cpp',
'AnimationEffectTimingReadOnly.cpp',
@@ -54,16 +57,19 @@ UNIFIED_SOURCES += [
'DocumentTimeline.cpp',
'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/ServoRestyleManager.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"
@@ -1369,16 +1370,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.
@@ -1954,16 +1956,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/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -213,16 +213,17 @@
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/ImageTracker.h"
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/OwningNonNull.h"
+#include "mozilla/dom/ScrollTimeline.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/WebComponentsBinding.h"
#include "mozilla/dom/CustomElementRegistryBinding.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "nsFrame.h"
#include "nsDOMCaretPosition.h"
#include "nsIDOMHTMLTextAreaElement.h"
#include "nsViewportInfo.h"
@@ -1553,16 +1554,17 @@ nsDocument::~nsDocument()
mStyleSheetSetList->Disconnect();
}
if (mAnimationController) {
mAnimationController->Disconnect();
}
MOZ_ASSERT(mTimelines.isEmpty());
+ MOZ_ASSERT(mScrollTimelines.isEmpty());
mParentDocument = nullptr;
// Kill the subdocument map, doing this will release its strong
// references, if any.
delete mSubDocuments;
mSubDocuments = nullptr;
@@ -3179,16 +3181,24 @@ nsDocument::Timeline()
if (!mDocumentTimeline) {
mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
}
return mDocumentTimeline;
}
void
+nsDocument::TickScrollTimelines()
+{
+ for (ScrollTimeline* timeline : mScrollTimelines) {
+ timeline->Tick();
+ }
+}
+
+void
nsDocument::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations)
{
// Hold a strong ref for the root element since Element::GetAnimations() calls
// FlushPendingNotifications() which may destroy the element.
RefPtr<Element> root = GetRootElement();
if (!root) {
return;
}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -608,16 +608,21 @@ public:
static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
virtual mozilla::dom::DocumentTimeline* Timeline() override;
virtual void GetAnimations(
nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) override;
mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() override
{
return mTimelines;
}
+ mozilla::LinkedList<mozilla::dom::ScrollTimeline>& ScrollTimelines() override
+ {
+ return mScrollTimelines;
+ }
+ void TickScrollTimelines() override;
virtual nsresult SetSubDocumentFor(Element* aContent,
nsIDocument* aSubDoc) override;
virtual nsIDocument* GetSubDocumentFor(nsIContent* aContent) const override;
virtual Element* FindContentForSubDocument(nsIDocument *aDocument) const override;
virtual Element* GetRootElementInternal() const override;
virtual void EnsureOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet) override;
@@ -1617,16 +1622,17 @@ private:
uint8_t mScrolledToRefAlready : 1;
uint8_t mChangeScrollPosWhenScrollingToRef : 1;
// Tracking for plugins in the document.
nsTHashtable< nsPtrHashKey<nsIObjectLoadingContent> > mPlugins;
RefPtr<mozilla::dom::DocumentTimeline> mDocumentTimeline;
mozilla::LinkedList<mozilla::dom::DocumentTimeline> mTimelines;
+ mozilla::LinkedList<mozilla::dom::ScrollTimeline> mScrollTimelines;
enum ViewportType {
DisplayWidthHeight,
Specified,
Unknown
};
ViewportType mViewportType;
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2153,16 +2153,17 @@ GK_ATOM(transitionsOfBeforeProperty, "Tr
GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
GK_ATOM(lockedStyleStates, "lockedStyleStates")
GK_ATOM(apzCallbackTransform, "apzCallbackTransform")
GK_ATOM(restylableAnonymousNode, "restylableAnonymousNode")
GK_ATOM(paintRequestTime, "PaintRequestTime")
GK_ATOM(pseudoProperty, "PseudoProperty") // CSSPseudoElementType
+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")
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -149,16 +149,17 @@ class Link;
class Location;
class MediaQueryList;
class GlobalObject;
class NodeFilter;
class NodeIterator;
enum class OrientationType : uint8_t;
class ProcessingInstruction;
class Promise;
+class ScrollTimeline;
class StyleSheetList;
class SVGDocument;
class SVGSVGElement;
class Touch;
class TouchList;
class TreeWalker;
class XPathEvaluator;
class XPathExpression;
@@ -2409,16 +2410,19 @@ public:
* elements set using mozSetImageElement have higher priority.
* @param aId the ID associated the element we want to lookup
* @return the element associated with |aId|
*/
virtual Element* LookupImageElement(const nsAString& aElementId) = 0;
virtual mozilla::dom::DocumentTimeline* Timeline() = 0;
virtual mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() = 0;
+ virtual mozilla::LinkedList<mozilla::dom::ScrollTimeline>& ScrollTimelines() = 0;
+
+ virtual void TickScrollTimelines() = 0;
virtual void GetAnimations(
nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) = 0;
mozilla::dom::SVGSVGElement* GetSVGRootElement() const;
nsresult ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback,
int32_t *aHandle);
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -25,16 +25,17 @@
#include "nsBindingManager.h"
#include "nsGenericHTMLElement.h"
#include "mozilla/AnimationTarget.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "mozilla/dom/ScrollTimeline.h"
#include "nsWrapperCacheInlines.h"
#include "nsObjectLoadingContent.h"
#include "nsDOMMutationObserver.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/ShadowRoot.h"
using namespace mozilla;
@@ -565,16 +566,29 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
if (oldDoc != newDoc && oldDoc->MayHaveDOMMutationObservers()) {
newDoc->SetMayHaveDOMMutationObservers();
}
if (oldDoc != newDoc && oldDoc->MayHaveAnimationObservers()) {
newDoc->SetMayHaveAnimationObservers();
}
+ if (oldDoc != newDoc && aNode->IsElement()) {
+ // Transfer element's scroll timelines from old document to new document.
+ if (ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetScrollTimelineSet(aNode->AsElement())) {
+ const auto& scrollTimelines = scrollTimelineSet->GetAllScrollTimelines();
+ for (auto iter = scrollTimelines.ConstIter(); !iter.Done(); iter.Next()) {
+ ScrollTimeline* timeline= iter.Get()->GetKey();
+ timeline->RemoveFromList();
+ newDoc->ScrollTimelines().insertBack(timeline);
+ }
+ }
+ }
+
if (elem) {
elem->RecompileScriptEventListeners();
}
if (aReparentScope) {
AutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx);
if ((wrapper = aNode->GetWrapper())) {
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ScrollTimeline.webidl
@@ -0,0 +1,35 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://wicg.github.io/scroll-animations/#scrolltimeline
+ *
+ * Copyright © 2016 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+enum ScrollDirection { "vertical", "horizontal" };
+
+enum ScrollTimelineAutoKeyword { "auto" };
+
+dictionary ScrollTimelineOptions {
+ Element scrollSource;
+ ScrollDirection orientation;
+ DOMString startScrollOffset = "auto";
+ DOMString endScrollOffset = "auto";
+ (double or ScrollTimelineAutoKeyword) timeRange = "auto";
+ FillMode fillMode = "none";
+};
+
+[Func="nsDocument::IsWebAnimationsEnabled",
+ Constructor(optional ScrollTimelineOptions options)]
+interface ScrollTimeline : AnimationTimeline {
+ readonly attribute Element sourceElement;
+ readonly attribute ScrollDirection orientation;
+ readonly attribute DOMString startScrollOffset;
+ readonly attribute DOMString endScrollOffset;
+ readonly attribute (double or ScrollTimelineAutoKeyword) timeRange;
+ readonly attribute FillMode fill;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -762,16 +762,17 @@ WEBIDL_FILES = [
'Response.webidl',
'RGBColor.webidl',
'RTCStatsReport.webidl',
'Screen.webidl',
'ScreenOrientation.webidl',
'ScriptProcessorNode.webidl',
'ScrollAreaEvent.webidl',
'ScrollBoxObject.webidl',
+ 'ScrollTimeline.webidl',
'Selection.webidl',
'ServiceWorker.webidl',
'ServiceWorkerContainer.webidl',
'ServiceWorkerGlobalScope.webidl',
'ServiceWorkerRegistration.webidl',
'SettingChangeNotification.webidl',
'ShadowRoot.webidl',
'SharedWorker.webidl',
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1616,16 +1616,42 @@ CollectDocuments(nsIDocument* aDocument,
{
static_cast<AutoTArray<nsCOMPtr<nsIDocument>, 32>*>(aDocArray)->
AppendElement(aDocument);
aDocument->EnumerateSubDocuments(CollectDocuments, aDocArray);
return true;
}
void
+nsRefreshDriver::TickScrollTimelines()
+{
+ if (!mPresContext) {
+ return;
+ }
+
+ nsCOMArray<nsIDocument> documents;
+ CollectDocuments(mPresContext->Document(), &documents);
+
+ for (int32_t i = 0; i < documents.Count(); ++i) {
+ nsIDocument* doc = documents[i];
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ continue;
+ }
+
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+ if (!context || context->RefreshDriver() != this) {
+ continue;
+ }
+
+ doc->TickScrollTimelines();
+ }
+}
+
+void
nsRefreshDriver::DispatchAnimationEvents()
{
if (!mPresContext) {
return;
}
AutoTArray<nsCOMPtr<nsIDocument>, 32> documents;
CollectDocuments(mPresContext->Document(), &documents);
@@ -1835,16 +1861,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
StopTimer();
return;
}
}
if (i == 0) {
// This is the FlushType::Style case.
+ TickScrollTimelines();
DispatchAnimationEvents();
DispatchPendingEvents();
RunFrameRequestCallbacks(aNowTime);
if (mPresContext && mPresContext->GetPresShell()) {
Maybe<GeckoProfilerTracingRAII> tracingStyleFlush;
AutoTArray<nsIPresShell*, 16> observers;
observers.AppendElements(mStyleFlushObservers);
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -369,16 +369,17 @@ private:
{
}
mozilla::Maybe<mozilla::TimeStamp> mStartTime;
RequestTable mEntries;
};
typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
+ void TickScrollTimelines();
void DispatchPendingEvents();
void DispatchAnimationEvents();
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
enum EnsureTimerStartedFlags {
eNone = 0,
eForceAdjustTimer = 1 << 0,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -34,17 +34,19 @@
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsBidiPresUtils.h"
#include "nsBidiUtils.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
+#include "mozilla/ScrollTimelineSet.h"
#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollTimeline.h"
#include <stdint.h>
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Telemetry.h"
#include "FrameLayerBuilder.h"
#include "nsSMILKeySpline.h"
#include "nsSubDocumentFrame.h"
#include "nsSVGOuterSVGFrame.h"
#include "nsIObjectLoadingContent.h"
@@ -2071,16 +2073,34 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
mOuter->PresContext()->PresShell(),
ScreenMargin(),
0,
nsLayoutUtils::RepaintMode::DoNotRepaint);
nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
mOuter, nsLayoutUtils::RepaintMode::DoNotRepaint);
}
+ // Register any scroll timelines associated with our content element, with
+ // this scroll frame. The timeline registers itself when it's constructed,
+ // but the frame can be reconstructed over the lifetime of the content
+ // element.
+ nsIScrollableFrame* sf = do_QueryFrame(mOuter);
+ MOZ_ASSERT(sf);
+ if (nsIContent* content = mOuter->GetContent()) {
+ if (content->IsElement()) {
+ if (ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetScrollTimelineSet(content->AsElement())) {
+ const auto& scrollTimelines = scrollTimelineSet->GetAllScrollTimelines();
+ for (auto iter = scrollTimelines.ConstIter(); !iter.Done(); iter.Next()) {
+ ScrollTimeline* timeline= iter.Get()->GetKey();
+ timeline->RegisterWithScrollFrame(sf);
+ }
+ }
+ }
+ }
}
ScrollFrameHelper::~ScrollFrameHelper()
{
}
/*
* Callback function from AsyncSmoothMSDScroll, used in ScrollFrameHelper::ScrollTo