summaryrefslogtreecommitdiffstats
path: root/dom/animation/ScrollTimeline.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/animation/ScrollTimeline.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/animation/ScrollTimeline.cpp')
-rw-r--r--dom/animation/ScrollTimeline.cpp304
1 files changed, 304 insertions, 0 deletions
diff --git a/dom/animation/ScrollTimeline.cpp b/dom/animation/ScrollTimeline.cpp
new file mode 100644
index 0000000000..b4aa8fbcd6
--- /dev/null
+++ b/dom/animation/ScrollTimeline.cpp
@@ -0,0 +1,304 @@
+/* -*- 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/Animation.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/PresShell.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla::dom {
+
+// ---------------------------------
+// Methods of ScrollTimeline
+// ---------------------------------
+
+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(mSource.mElement)
+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(mSource.mElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ScrollTimeline,
+ AnimationTimeline)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ScrollTimeline,
+ AnimationTimeline)
+
+ScrollTimeline::ScrollTimeline(Document* aDocument, const Scroller& aScroller,
+ StyleScrollAxis aAxis)
+ : AnimationTimeline(aDocument->GetParentObject(),
+ aDocument->GetScopeObject()->GetRTPCallerType()),
+ mDocument(aDocument),
+ mSource(aScroller),
+ mAxis(aAxis) {
+ MOZ_ASSERT(aDocument);
+}
+
+/* static */
+already_AddRefed<ScrollTimeline> ScrollTimeline::GetOrCreateScrollTimeline(
+ Document* aDocument, const Scroller& aScroller,
+ const StyleScrollAxis& aAxis) {
+ MOZ_ASSERT(aScroller);
+
+ RefPtr<ScrollTimeline> timeline;
+ auto* set =
+ ScrollTimelineSet::GetOrCreateScrollTimelineSet(aScroller.mElement);
+ auto key = ScrollTimelineSet::Key{aScroller.mType, aAxis};
+ auto p = set->LookupForAdd(key);
+ if (!p) {
+ timeline = new ScrollTimeline(aDocument, aScroller, aAxis);
+ set->Add(p, key, timeline);
+ } else {
+ timeline = p->value();
+ }
+ return timeline.forget();
+}
+
+/* static */
+already_AddRefed<ScrollTimeline> ScrollTimeline::FromAnonymousScroll(
+ Document* aDocument, const NonOwningAnimationTarget& aTarget,
+ StyleScrollAxis aAxis, StyleScroller aScroller) {
+ MOZ_ASSERT(aTarget);
+ Scroller scroller;
+ switch (aScroller) {
+ case StyleScroller::Root:
+ scroller = Scroller::Root(aTarget.mElement->OwnerDoc());
+ break;
+ case StyleScroller::Nearest: {
+ Element* curr = aTarget.mElement->GetFlattenedTreeParentElement();
+ Element* root = aTarget.mElement->OwnerDoc()->GetDocumentElement();
+ while (curr && curr != root) {
+ const ComputedStyle* style = Servo_Element_GetMaybeOutOfDateStyle(curr);
+ MOZ_ASSERT(style, "The ancestor should be styled.");
+ if (style->StyleDisplay()->IsScrollableOverflow()) {
+ break;
+ }
+ curr = curr->GetFlattenedTreeParentElement();
+ }
+ // If there is no scroll container, we use root.
+ scroller = Scroller::Nearest(curr ? curr : root);
+ }
+ }
+ return GetOrCreateScrollTimeline(aDocument, scroller, aAxis);
+}
+
+/* static*/ already_AddRefed<ScrollTimeline> ScrollTimeline::FromNamedScroll(
+ Document* aDocument, const NonOwningAnimationTarget& aTarget,
+ const nsAtom* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTarget);
+
+ // A named scroll progress timeline is referenceable in animation-timeline by:
+ // 1. the declaring element itself
+ // 2. that element’s descendants
+ // 3. that element’s following siblings and their descendants
+ // https://drafts.csswg.org/scroll-animations-1/rewrite#timeline-scope
+ //
+ // Note: It's unclear to us about the scope of scroll-timeline, so we
+ // intentionally don't let it cross the shadow dom boundary for now.
+ //
+ // FIXME: We may have to support global scope. This depends on the result of
+ // this spec issue: https://github.com/w3c/csswg-drafts/issues/7047
+ Element* result = nullptr;
+ StyleScrollAxis axis = StyleScrollAxis::Block;
+ for (Element* curr = aTarget.mElement; curr;
+ curr = curr->GetParentElement()) {
+ // If multiple elements have declared the same timeline name, the matching
+ // timeline is the one declared on the nearest element in tree order, which
+ // considers siblings closer than parents.
+ // Note: This should be fine for parallel traversal because we update
+ // animations by SequentialTask.
+ for (Element* e = curr; e; e = e->GetPreviousElementSibling()) {
+ const ComputedStyle* style = Servo_Element_GetMaybeOutOfDateStyle(e);
+ // The elements in the shadow dom might not be in the flat tree.
+ if (!style) {
+ continue;
+ }
+
+ const nsStyleUIReset* styleUIReset = style->StyleUIReset();
+ if (styleUIReset->mScrollTimelineName._0.AsAtom() == aName) {
+ result = e;
+ axis = styleUIReset->mScrollTimelineAxis;
+ break;
+ }
+ }
+
+ if (result) {
+ break;
+ }
+ }
+
+ // If we cannot find a matched scroll-timeline-name, this animation is not
+ // associated with a timeline.
+ // https://drafts.csswg.org/css-animations-2/#typedef-timeline-name
+ if (!result) {
+ return nullptr;
+ }
+ Scroller scroller = Scroller::Named(result);
+ return GetOrCreateScrollTimeline(aDocument, scroller, axis);
+}
+
+Nullable<TimeDuration> ScrollTimeline::GetCurrentTimeAsDuration() const {
+ // If no layout box, this timeline is inactive.
+ if (!mSource || !mSource.mElement->GetPrimaryFrame()) {
+ return nullptr;
+ }
+
+ // if this is not a scroller container, this timeline is inactive.
+ const nsIScrollableFrame* scrollFrame = GetScrollFrame();
+ if (!scrollFrame) {
+ return nullptr;
+ }
+
+ const auto orientation = Axis();
+
+ // If this orientation is not ready for scrolling (i.e. the scroll range is
+ // not larger than or equal to one device pixel), we make it 100%.
+ if (!scrollFrame->GetAvailableScrollingDirections().contains(orientation)) {
+ return TimeDuration::FromMilliseconds(PROGRESS_TIMELINE_DURATION_MILLISEC);
+ }
+
+ const nsPoint& scrollOffset = scrollFrame->GetScrollPosition();
+ const nsRect& scrollRange = scrollFrame->GetScrollRange();
+ const bool isHorizontal = orientation == layers::ScrollDirection::eHorizontal;
+
+ // Note: For RTL, scrollOffset.x or scrollOffset.y may be negative, e.g. the
+ // range of its value is [0, -range], so we have to use the absolute value.
+ double position = std::abs(isHorizontal ? scrollOffset.x : scrollOffset.y);
+ double range = isHorizontal ? scrollRange.width : scrollRange.height;
+ MOZ_ASSERT(range > 0.0);
+ // Use the definition of interval progress to compute the progress.
+ // Note: We simplify the scroll offsets to [0%, 100%], so offset weight and
+ // offset index are ignored here.
+ // https://drafts.csswg.org/scroll-animations-1/#progress-calculation-algorithm
+ double progress = position / range;
+ return TimeDuration::FromMilliseconds(progress *
+ PROGRESS_TIMELINE_DURATION_MILLISEC);
+}
+
+layers::ScrollDirection ScrollTimeline::Axis() const {
+ MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame());
+
+ const WritingMode wm = mSource.mElement->GetPrimaryFrame()->GetWritingMode();
+ return mAxis == StyleScrollAxis::Horizontal ||
+ (!wm.IsVertical() && mAxis == StyleScrollAxis::Inline) ||
+ (wm.IsVertical() && mAxis == StyleScrollAxis::Block)
+ ? layers::ScrollDirection::eHorizontal
+ : layers::ScrollDirection::eVertical;
+}
+
+StyleOverflow ScrollTimeline::SourceScrollStyle() const {
+ MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame());
+
+ const nsIScrollableFrame* scrollFrame = GetScrollFrame();
+ MOZ_ASSERT(scrollFrame);
+
+ const ScrollStyles scrollStyles = scrollFrame->GetScrollStyles();
+
+ return Axis() == layers::ScrollDirection::eHorizontal
+ ? scrollStyles.mHorizontal
+ : scrollStyles.mVertical;
+}
+
+bool ScrollTimeline::APZIsActiveForSource() const {
+ MOZ_ASSERT(mSource);
+ return gfxPlatform::AsyncPanZoomEnabled() &&
+ !nsLayoutUtils::ShouldDisableApzForElement(mSource.mElement) &&
+ DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(mSource.mElement);
+}
+
+bool ScrollTimeline::ScrollingDirectionIsAvailable() const {
+ const nsIScrollableFrame* scrollFrame = GetScrollFrame();
+ MOZ_ASSERT(scrollFrame);
+ return scrollFrame->GetAvailableScrollingDirections().contains(Axis());
+}
+
+void ScrollTimeline::UnregisterFromScrollSource() {
+ if (!mSource) {
+ return;
+ }
+
+ if (ScrollTimelineSet* scrollTimelineSet =
+ ScrollTimelineSet::GetScrollTimelineSet(mSource.mElement)) {
+ scrollTimelineSet->Remove(ScrollTimelineSet::Key{mSource.mType, mAxis});
+ if (scrollTimelineSet->IsEmpty()) {
+ ScrollTimelineSet::DestroyScrollTimelineSet(mSource.mElement);
+ }
+ }
+}
+
+const nsIScrollableFrame* ScrollTimeline::GetScrollFrame() const {
+ if (!mSource) {
+ return nullptr;
+ }
+
+ switch (mSource.mType) {
+ case Scroller::Type::Root:
+ if (const PresShell* presShell =
+ mSource.mElement->OwnerDoc()->GetPresShell()) {
+ return presShell->GetRootScrollFrameAsScrollable();
+ }
+ return nullptr;
+ case Scroller::Type::Nearest:
+ case Scroller::Type::Name:
+ return nsLayoutUtils::FindScrollableFrameFor(mSource.mElement);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unsupported scroller type");
+ return nullptr;
+}
+
+// ---------------------------------
+// Methods of ScrollTimelineSet
+// ---------------------------------
+
+/* static */ ScrollTimelineSet* ScrollTimelineSet::GetScrollTimelineSet(
+ Element* aElement) {
+ return aElement ? static_cast<ScrollTimelineSet*>(aElement->GetProperty(
+ nsGkAtoms::scrollTimelinesProperty))
+ : nullptr;
+}
+
+/* static */ ScrollTimelineSet* ScrollTimelineSet::GetOrCreateScrollTimelineSet(
+ Element* aElement) {
+ MOZ_ASSERT(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(
+ Element* aElement) {
+ aElement->RemoveProperty(nsGkAtoms::scrollTimelinesProperty);
+}
+
+} // namespace mozilla::dom