mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 03:49:42 +00:00

`self` keyword specifies to use the element’s own principal box as the scroll container. If the principal box is not a scroll container, then the scroll progress timeline is inactive. Differential Revision: https://phabricator.services.mozilla.com/D175707
296 lines
10 KiB
C++
296 lines
10 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/ElementAnimationData.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_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);
|
|
RegisterWithScrollSource();
|
|
}
|
|
|
|
/* static */ std::pair<const Element*, PseudoStyleType>
|
|
ScrollTimeline::FindNearestScroller(Element* aSubject,
|
|
PseudoStyleType aPseudoType) {
|
|
MOZ_ASSERT(aSubject);
|
|
Element* subject =
|
|
AnimationUtils::GetElementForRestyle(aSubject, aPseudoType);
|
|
|
|
Element* curr = subject->GetFlattenedTreeParentElement();
|
|
Element* root = subject->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.
|
|
if (!curr) {
|
|
return {root, PseudoStyleType::NotPseudo};
|
|
}
|
|
return AnimationUtils::GetElementPseudoPair(curr);
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<ScrollTimeline> ScrollTimeline::MakeAnonymous(
|
|
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: {
|
|
auto [element, pseudo] =
|
|
FindNearestScroller(aTarget.mElement, aTarget.mPseudoType);
|
|
scroller = Scroller::Nearest(const_cast<Element*>(element), pseudo);
|
|
break;
|
|
}
|
|
case StyleScroller::SelfElement:
|
|
scroller = Scroller::Self(aTarget.mElement, aTarget.mPseudoType);
|
|
break;
|
|
}
|
|
|
|
// Each use of scroll() corresponds to its own instance of ScrollTimeline in
|
|
// the Web Animations API, even if multiple elements use scroll() to refer to
|
|
// the same scroll container with the same arguments.
|
|
// https://drafts.csswg.org/scroll-animations-1/#scroll-notation
|
|
return MakeAndAddRef<ScrollTimeline>(aDocument, scroller, aAxis);
|
|
}
|
|
|
|
/* static*/ already_AddRefed<ScrollTimeline> ScrollTimeline::MakeNamed(
|
|
Document* aDocument, Element* aReferenceElement,
|
|
PseudoStyleType aPseudoType, const StyleScrollTimeline& aStyleTimeline) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
Scroller scroller = Scroller::Named(aReferenceElement, aPseudoType);
|
|
return MakeAndAddRef<ScrollTimeline>(aDocument, std::move(scroller),
|
|
aStyleTimeline.GetAxis());
|
|
}
|
|
|
|
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 there is no scrollable overflow, then the ScrollTimeline is inactive.
|
|
// https://drafts.csswg.org/scroll-animations-1/#scrolltimeline-interface
|
|
if (!scrollFrame->GetAvailableScrollingDirections().contains(orientation)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const bool isHorizontal = orientation == layers::ScrollDirection::eHorizontal;
|
|
const nsPoint& scrollPosition = scrollFrame->GetScrollPosition();
|
|
const Maybe<ScrollOffsets>& offsets =
|
|
ComputeOffsets(scrollFrame, orientation);
|
|
if (!offsets) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: For RTL, scrollPosition.x or scrollPosition.y may be negative,
|
|
// e.g. the range of its value is [0, -range], so we have to use the
|
|
// absolute value.
|
|
nscoord position =
|
|
std::abs(isHorizontal ? scrollPosition.x : scrollPosition.y);
|
|
double progress = static_cast<double>(position - offsets->mStart) /
|
|
static_cast<double>(offsets->mEnd - offsets->mStart);
|
|
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::ReplacePropertiesWith(const Element* aReferenceElement,
|
|
PseudoStyleType aPseudoType,
|
|
const StyleScrollTimeline& aNew) {
|
|
MOZ_ASSERT(aReferenceElement == mSource.mElement &&
|
|
aPseudoType == mSource.mPseudoType);
|
|
mAxis = aNew.GetAxis();
|
|
|
|
for (auto* anim = mAnimationOrder.getFirst(); anim;
|
|
anim = static_cast<LinkedListElement<Animation>*>(anim)->getNext()) {
|
|
MOZ_ASSERT(anim->GetTimeline() == this);
|
|
// Set this so we just PostUpdate() for this animation.
|
|
anim->SetTimeline(this);
|
|
}
|
|
}
|
|
|
|
Maybe<ScrollTimeline::ScrollOffsets> ScrollTimeline::ComputeOffsets(
|
|
const nsIScrollableFrame* aScrollFrame,
|
|
layers::ScrollDirection aOrientation) const {
|
|
const nsRect& scrollRange = aScrollFrame->GetScrollRange();
|
|
nscoord range = aOrientation == layers::ScrollDirection::eHorizontal
|
|
? scrollRange.width
|
|
: scrollRange.height;
|
|
MOZ_ASSERT(range > 0);
|
|
return Some(ScrollOffsets{0, range});
|
|
}
|
|
|
|
void ScrollTimeline::RegisterWithScrollSource() {
|
|
if (!mSource) {
|
|
return;
|
|
}
|
|
|
|
auto& scheduler =
|
|
ProgressTimelineScheduler::Ensure(mSource.mElement, mSource.mPseudoType);
|
|
scheduler.AddTimeline(this);
|
|
}
|
|
|
|
void ScrollTimeline::UnregisterFromScrollSource() {
|
|
if (!mSource) {
|
|
return;
|
|
}
|
|
|
|
auto* scheduler =
|
|
ProgressTimelineScheduler::Get(mSource.mElement, mSource.mPseudoType);
|
|
if (!scheduler) {
|
|
return;
|
|
}
|
|
|
|
scheduler->RemoveTimeline(this);
|
|
if (scheduler->IsEmpty()) {
|
|
ProgressTimelineScheduler::Destroy(mSource.mElement, mSource.mPseudoType);
|
|
}
|
|
}
|
|
|
|
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:
|
|
case Scroller::Type::Self:
|
|
return nsLayoutUtils::FindScrollableFrameFor(mSource.mElement);
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported scroller type");
|
|
return nullptr;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// Methods of ProgressTimelineScheduler
|
|
// ------------------------------------
|
|
/* static */ ProgressTimelineScheduler* ProgressTimelineScheduler::Get(
|
|
const Element* aElement, PseudoStyleType aPseudoType) {
|
|
MOZ_ASSERT(aElement);
|
|
auto* data = aElement->GetAnimationData();
|
|
if (!data) {
|
|
return nullptr;
|
|
}
|
|
|
|
return data->GetProgressTimelineScheduler(aPseudoType);
|
|
}
|
|
|
|
/* static */ ProgressTimelineScheduler& ProgressTimelineScheduler::Ensure(
|
|
Element* aElement, PseudoStyleType aPseudoType) {
|
|
MOZ_ASSERT(aElement);
|
|
return aElement->EnsureAnimationData().EnsureProgressTimelineScheduler(
|
|
*aElement, aPseudoType);
|
|
}
|
|
|
|
/* static */
|
|
void ProgressTimelineScheduler::Destroy(const Element* aElement,
|
|
PseudoStyleType aPseudoType) {
|
|
auto* data = aElement->GetAnimationData();
|
|
MOZ_ASSERT(data);
|
|
data->ClearProgressTimelineScheduler(aPseudoType);
|
|
}
|
|
|
|
} // namespace mozilla::dom
|