gecko-dev/dom/animation/ScrollTimeline.cpp

344 строки
12 KiB
C++
Исходник Ответственный История

Этот файл содержит неоднозначные символы Юникода!

Этот файл содержит неоднозначные символы Юникода, которые могут быть перепутаны с другими в текущей локали. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы подсветить эти символы.

/* -*- 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()),
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 StyleScrollAxis ToStyleScrollAxis(
const StyleScrollDirection aDirection) {
switch (aDirection) {
// The spec defines auto, but there is a spec issue:
// "ISSUE 5 Define these values." in this section. The DOM interface removed
// auto and use block as default value, so we treat auto as block now.
// https://drafts.csswg.org/scroll-animations-1/#descdef-scroll-timeline-orientation
case StyleScrollDirection::Auto:
case StyleScrollDirection::Block:
return StyleScrollAxis::Block;
case StyleScrollDirection::Inline:
return StyleScrollAxis::Inline;
case StyleScrollDirection::Horizontal:
return StyleScrollAxis::Horizontal;
case StyleScrollDirection::Vertical:
return StyleScrollAxis::Vertical;
}
MOZ_ASSERT_UNREACHABLE("Unsupported StyleScrollDirection");
return StyleScrollAxis::Block;
}
/* static */
already_AddRefed<ScrollTimeline> ScrollTimeline::FromRule(
const RawServoScrollTimelineRule& aRule, Document* aDocument,
const NonOwningAnimationTarget& aTarget) {
// Note: If the rules changes after we build the scroll-timeline rule, we
// rebuild all CSS animtions, and then try to look up the scroll-timeline by
// the new source and the new direction. If we cannot find a specific
// timeline, we create one, and the unused scroll-timeline object will be
// dropped automatically becuase no animation owns it and its ref-count
// becomes zero.
StyleScrollAxis axis =
ToStyleScrollAxis(Servo_ScrollTimelineRule_GetOrientation(&aRule));
auto autoScroller = Scroller::Root(aTarget.mElement->OwnerDoc());
return GetOrCreateScrollTimeline(aDocument, autoScroller, axis);
}
/* 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 elements descendants
// 3. that elements 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