Bug 1676791 - Part 2: Implement the infrastructure of scroll-linked animations. r=emilio,hiro
The basic part of the infrastructure of scroll-linked animations. This is designed based on scroll-linked animation generated by CSS. We will implement the timing computation and hook the ScrollTimeline to the CSS animations in the following patches. All the tests are in the patch that hooks ScrollTimeline to CSS Animation. Differential Revision: https://phabricator.services.mozilla.com/D129100
This commit is contained in:
@@ -34,6 +34,41 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationTimeline)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
bool AnimationTimeline::Tick() {
|
||||
bool needsTicks = false;
|
||||
|
||||
nsTArray<Animation*> animationsToRemove;
|
||||
|
||||
for (Animation* animation = mAnimationOrder.getFirst(); animation;
|
||||
animation =
|
||||
static_cast<LinkedListElement<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;
|
||||
}
|
||||
|
||||
needsTicks |= animation->NeedsTicks();
|
||||
// Even if |animation| doesn't need future ticks, we should still
|
||||
// Tick it this time around since it might just need a one-off tick in
|
||||
// order to dispatch events.
|
||||
animation->Tick();
|
||||
|
||||
if (!animation->NeedsTicks()) {
|
||||
animationsToRemove.AppendElement(animation);
|
||||
}
|
||||
}
|
||||
|
||||
for (Animation* animation : animationsToRemove) {
|
||||
RemoveAnimation(animation);
|
||||
}
|
||||
|
||||
return needsTicks;
|
||||
}
|
||||
|
||||
void AnimationTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
|
||||
if (mAnimations.EnsureInserted(&aAnimation)) {
|
||||
if (aAnimation.GetTimeline() && aAnimation.GetTimeline() != this) {
|
||||
|
||||
@@ -32,6 +32,10 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
||||
protected:
|
||||
virtual ~AnimationTimeline();
|
||||
|
||||
// Tick animations and may remove them from the list if we don't need to
|
||||
// tick it. Return true if any animations need to be ticked.
|
||||
bool Tick();
|
||||
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationTimeline)
|
||||
|
||||
@@ -166,39 +166,10 @@ void DocumentTimeline::MostRecentRefreshTimeUpdated() {
|
||||
MOZ_ASSERT(GetRefreshDriver(),
|
||||
"Should be able to reach refresh driver from within WillRefresh");
|
||||
|
||||
bool needsTicks = false;
|
||||
nsTArray<Animation*> animationsToRemove(mAnimations.Count());
|
||||
|
||||
nsAutoAnimationMutationBatch mb(mDocument);
|
||||
|
||||
for (Animation* animation = mAnimationOrder.getFirst(); animation;
|
||||
animation =
|
||||
static_cast<LinkedListElement<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;
|
||||
}
|
||||
|
||||
needsTicks |= animation->NeedsTicks();
|
||||
// Even if |animation| doesn't need future ticks, we should still
|
||||
// Tick it this time around since it might just need a one-off tick in
|
||||
// order to dispatch events.
|
||||
animation->Tick();
|
||||
|
||||
if (!animation->NeedsTicks()) {
|
||||
animationsToRemove.AppendElement(animation);
|
||||
}
|
||||
}
|
||||
|
||||
for (Animation* animation : animationsToRemove) {
|
||||
RemoveAnimation(animation);
|
||||
}
|
||||
|
||||
if (!needsTicks) {
|
||||
bool ticked = Tick();
|
||||
if (!ticked) {
|
||||
// We already assert that GetRefreshDriver() is non-null at the beginning
|
||||
// of this function but we check it again here to be sure that ticking
|
||||
// animations does not have any side effects that cause us to lose the
|
||||
|
||||
@@ -11,9 +11,14 @@
|
||||
|
||||
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)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
@@ -33,9 +38,81 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ScrollTimeline,
|
||||
ScrollTimeline::ScrollTimeline(Document* aDocument, Element* aScroller)
|
||||
: AnimationTimeline(aDocument->GetParentObject()),
|
||||
mDocument(aDocument),
|
||||
// FIXME: Bug 1737918: We may have to udpate the constructor arguments
|
||||
// because this can be nearest, root, or a specific container. For now,
|
||||
// the input is a source element directly and it is the root element.
|
||||
//
|
||||
// FIXME: it seems we cannot use Document->GetScrollingElement() in the
|
||||
// caller (i.e. nsAnimationManager::BuildAnimation()) because it flushes
|
||||
// the layout, Perhaps we have to define a variant version, like
|
||||
// GetScrollingElementNoLayout(), which doesn't do layout. So this causes
|
||||
// a bug on quirks mode.
|
||||
mSource(aScroller),
|
||||
mDirection(StyleScrollDirection::Auto) {
|
||||
// TODO: Register the timeline to the soruce element in the next patch.
|
||||
MOZ_ASSERT(aDocument);
|
||||
|
||||
RegisterWithScrollSource();
|
||||
}
|
||||
|
||||
void ScrollTimeline::RegisterWithScrollSource() {
|
||||
if (!mSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ScrollTimelineSet* scrollTimelineSet =
|
||||
ScrollTimelineSet::GetOrCreateScrollTimelineSet(mSource)) {
|
||||
scrollTimelineSet->AddScrollTimeline(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollTimeline::UnregisterFromScrollSource() {
|
||||
if (!mSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ScrollTimelineSet* scrollTimelineSet =
|
||||
ScrollTimelineSet::GetScrollTimelineSet(mSource)) {
|
||||
scrollTimelineSet->RemoveScrollTimeline(*this);
|
||||
if (scrollTimelineSet->IsEmpty()) {
|
||||
ScrollTimelineSet::DestroyScrollTimelineSet(mSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Methods of ScrollTimelineSet
|
||||
// ---------------------------------
|
||||
|
||||
/* static */ ScrollTimelineSet* ScrollTimelineSet::GetScrollTimelineSet(
|
||||
Element* aElement) {
|
||||
MOZ_ASSERT(aElement);
|
||||
return static_cast<ScrollTimelineSet*>(
|
||||
aElement->GetProperty(nsGkAtoms::scrollTimelinesProperty));
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
@@ -8,11 +8,53 @@
|
||||
#define mozilla_dom_ScrollTimeline_h
|
||||
|
||||
#include "mozilla/dom/AnimationTimeline.h"
|
||||
#include "mozilla/HashTable.h"
|
||||
#include "mozilla/ServoStyleConsts.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class Document;
|
||||
class Element;
|
||||
|
||||
/**
|
||||
* 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-linked animations,
|
||||
* however, only need to be sampled when scrolling has occurred, so keeping
|
||||
* the refresh driver ticking is wasteful.
|
||||
*
|
||||
* As a result, we schedule an animation restyle when
|
||||
* 1) there are any scroll offsets updated (from APZ or script), via
|
||||
* nsIScrollableFrame, or
|
||||
* 2) there are any possible scroll range updated during the frame reflow.
|
||||
*
|
||||
* -------------
|
||||
* | Animation |
|
||||
* -------------
|
||||
* ^
|
||||
* | Call Animation::Tick() if there are any scroll updates.
|
||||
* |
|
||||
* ------------------
|
||||
* | ScrollTimeline |
|
||||
* ------------------
|
||||
* ^
|
||||
* | Try schedule the scroll-linked animations, if there are any scroll
|
||||
* | offsets changed or the scroll range changed [1].
|
||||
* |
|
||||
* ----------------------
|
||||
* | nsIScrollableFrame |
|
||||
* ----------------------
|
||||
*
|
||||
* [1] nsIScrollableFrame uses its associated dom::Element to lookup the
|
||||
* ScrollTimelineSet, and iterates the set to schedule the animations
|
||||
* linked to the ScrollTimelines.
|
||||
*/
|
||||
class ScrollTimeline final : public AnimationTimeline {
|
||||
public:
|
||||
ScrollTimeline() = delete;
|
||||
@@ -47,17 +89,85 @@ class ScrollTimeline final : public AnimationTimeline {
|
||||
}
|
||||
Document* GetDocument() const override { return mDocument; }
|
||||
|
||||
void ScheduleAnimations() {
|
||||
// FIXME: Bug 1737927: Need to check the animation mutation observers for
|
||||
// animations with scroll timelines.
|
||||
// nsAutoAnimationMutationBatch mb(mDocument);
|
||||
|
||||
Tick();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~ScrollTimeline() = default;
|
||||
virtual ~ScrollTimeline() { Teardown(); }
|
||||
|
||||
private:
|
||||
RefPtr<Document> mDocument;
|
||||
// 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.
|
||||
void Teardown() { UnregisterFromScrollSource(); }
|
||||
|
||||
// FIXME: We will use these data members in the following patches.
|
||||
// Register/Unregister this scroll timeline to the element property.
|
||||
void RegisterWithScrollSource();
|
||||
void UnregisterFromScrollSource();
|
||||
|
||||
RefPtr<Document> mDocument;
|
||||
RefPtr<Element> mSource;
|
||||
StyleScrollDirection mDirection;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* A wrapper around a hashset of ScrollTimeline objects to handle
|
||||
* storing the set as a property of an element (i.e. source).
|
||||
* This makes use easier to look up a ScrollTimeline from the element.
|
||||
*
|
||||
* Note:
|
||||
* 1. "source" is the element which the ScrollTimeline hooks.
|
||||
* Each ScrollTimeline hooks an dom::Element, and a dom::Element may be
|
||||
* registered by multiple ScrollTimelines.
|
||||
* 2. Element holds the ScrollTimelineSet as an element property. Also, the
|
||||
* owner document of this Element keeps a linked list of ScrollTimelines
|
||||
* (instead of ScrollTimelineSet).
|
||||
*/
|
||||
class ScrollTimelineSet {
|
||||
public:
|
||||
using NonOwningScrollTimelineSet = HashSet<ScrollTimeline*>;
|
||||
|
||||
~ScrollTimelineSet() = default;
|
||||
|
||||
static ScrollTimelineSet* GetScrollTimelineSet(Element* aElement);
|
||||
static ScrollTimelineSet* GetOrCreateScrollTimelineSet(Element* aElement);
|
||||
static void DestroyScrollTimelineSet(Element* aElement);
|
||||
|
||||
void AddScrollTimeline(ScrollTimeline& aScrollTimeline) {
|
||||
Unused << mScrollTimelines.put(&aScrollTimeline);
|
||||
}
|
||||
void RemoveScrollTimeline(ScrollTimeline& aScrollTimeline) {
|
||||
mScrollTimelines.remove(&aScrollTimeline);
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return mScrollTimelines.empty(); }
|
||||
|
||||
void ScheduleAnimations() const {
|
||||
for (auto iter = mScrollTimelines.iter(); !iter.done(); iter.next()) {
|
||||
iter.get()->ScheduleAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ScrollTimelineSet() = default;
|
||||
|
||||
// ScrollTimelineSet doesn't own ScrollTimeline. We let Animations own its
|
||||
// scroll timeline. (Note: one ScrollTimeline could be owned by multiple
|
||||
// associated Animations.)
|
||||
// The ScrollTimeline is generated only by CSS, so if all the associated
|
||||
// Animations are gone, we don't need the ScrollTimeline anymore, so
|
||||
// ScrollTimelineSet doesn't have to keep it for the source element.
|
||||
// FIXME: Bug 1676794: We may have to update here if it's possible to create
|
||||
// ScrollTimeline via script.
|
||||
NonOwningScrollTimelineSet mScrollTimelines;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/HTMLMarqueeElement.h"
|
||||
#include "mozilla/dom/ScrollTimeline.h"
|
||||
#include <stdint.h>
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
@@ -2269,6 +2270,7 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter, bool aIsRoot)
|
||||
mProcessingScrollEvent(false),
|
||||
mApzAnimationRequested(false),
|
||||
mReclampVVOffsetInReflowFinished(false),
|
||||
mMayScheduleScrollAnimations(false),
|
||||
mVelocityQueue(aOuter->PresContext()) {
|
||||
AppendScrollUpdate(ScrollPositionUpdate::NewScrollframe(nsPoint()));
|
||||
|
||||
@@ -3239,6 +3241,11 @@ void ScrollFrameHelper::ScrollToImpl(
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule the scroll-timelines linked to its scrollable frame.
|
||||
// if `pt == curPos`, we early return, so the position must be changed at
|
||||
// this moment. Therefore, we can schedule scroll animations directly.
|
||||
ScheduleScrollAnimations();
|
||||
|
||||
// notify the listeners.
|
||||
for (uint32_t i = 0; i < mListeners.Length(); i++) {
|
||||
mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
|
||||
@@ -6411,6 +6418,8 @@ void ScrollFrameHelper::UpdateMinimumScaleSize(
|
||||
bool ScrollFrameHelper::ReflowFinished() {
|
||||
mPostedReflowCallback = false;
|
||||
|
||||
TryScheduleScrollAnimations();
|
||||
|
||||
if (mIsRoot) {
|
||||
if (mMinimumScaleSizeChanged &&
|
||||
mOuter->PresShell()->UsesMobileViewportSizing() &&
|
||||
@@ -6659,7 +6668,14 @@ void ScrollFrameHelper::UpdateSticky() {
|
||||
}
|
||||
|
||||
void ScrollFrameHelper::UpdatePrevScrolledRect() {
|
||||
mPrevScrolledRect = GetScrolledRect();
|
||||
// The layout scroll range is determinated by the scrolled rect and the scroll
|
||||
// port, so if the scrolled rect is updated, we may have to schedule the
|
||||
// associated scroll-linked animations' restyles.
|
||||
nsRect currScrolledRect = GetScrolledRect();
|
||||
if (!currScrolledRect.IsEqualEdges(mPrevScrolledRect)) {
|
||||
mMayScheduleScrollAnimations = true;
|
||||
}
|
||||
mPrevScrolledRect = currScrolledRect;
|
||||
}
|
||||
|
||||
void ScrollFrameHelper::AdjustScrollbarRectForResizer(
|
||||
@@ -7959,3 +7975,19 @@ void ScrollFrameHelper::AppendScrollUpdate(
|
||||
mScrollGeneration = aUpdate.GetGeneration();
|
||||
mScrollUpdates.AppendElement(aUpdate);
|
||||
}
|
||||
|
||||
void ScrollFrameHelper::ScheduleScrollAnimations() {
|
||||
MOZ_ASSERT(mOuter);
|
||||
nsIContent* content = mOuter->GetContent();
|
||||
MOZ_ASSERT(content && content->IsElement(),
|
||||
"The nsIScrollableFrame should have the element.");
|
||||
|
||||
const auto* set =
|
||||
ScrollTimelineSet::GetScrollTimelineSet(content->AsElement());
|
||||
if (!set) {
|
||||
// We don't have scroll timelines associated with this frame.
|
||||
return;
|
||||
}
|
||||
|
||||
set->ScheduleAnimations();
|
||||
}
|
||||
|
||||
@@ -206,6 +206,9 @@ class ScrollFrameHelper : public nsIReflowCallback {
|
||||
|
||||
const nsRect& ScrollPort() const { return mScrollPort; }
|
||||
void SetScrollPort(const nsRect& aNewScrollPort) {
|
||||
if (!mScrollPort.IsEqualEdges(aNewScrollPort)) {
|
||||
mMayScheduleScrollAnimations = true;
|
||||
}
|
||||
mScrollPort = aNewScrollPort;
|
||||
}
|
||||
|
||||
@@ -399,6 +402,16 @@ class ScrollFrameHelper : public nsIReflowCallback {
|
||||
|
||||
bool NeedsScrollSnap() const;
|
||||
|
||||
// Schedule the scroll-linked animations.
|
||||
void ScheduleScrollAnimations();
|
||||
void TryScheduleScrollAnimations() {
|
||||
if (!mMayScheduleScrollAnimations) {
|
||||
return;
|
||||
}
|
||||
ScheduleScrollAnimations();
|
||||
mMayScheduleScrollAnimations = false;
|
||||
}
|
||||
|
||||
public:
|
||||
bool IsScrollbarOnRight() const;
|
||||
bool IsScrollingActive() const;
|
||||
@@ -750,6 +763,9 @@ class ScrollFrameHelper : public nsIReflowCallback {
|
||||
// Whether we need to reclamp the visual viewport offset in ReflowFinished.
|
||||
bool mReclampVVOffsetInReflowFinished : 1;
|
||||
|
||||
// Whether we need to schedule the scroll-linked animations.
|
||||
bool mMayScheduleScrollAnimations : 1;
|
||||
|
||||
mozilla::layout::ScrollVelocityQueue mVelocityQueue;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -2134,6 +2134,7 @@ STATIC_ATOMS = [
|
||||
Atom("docLevelNativeAnonymousContent", "docLevelNativeAnonymousContent"), # bool
|
||||
Atom("paintRequestTime", "PaintRequestTime"),
|
||||
Atom("pseudoProperty", "PseudoProperty"), # PseudoStyleType
|
||||
Atom("scrollTimelinesProperty", "SrollTimelinesProperty"), # ScrollTimelineSet*
|
||||
Atom("manualNACProperty", "ManualNACProperty"), # ManualNAC*
|
||||
Atom("markerPseudoProperty", "markerPseudoProperty"), # nsXMLElement*
|
||||
# Languages for lang-specific transforms
|
||||
|
||||
Reference in New Issue
Block a user