Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "TimelineManager.h"
#include "mozilla/ElementAnimationData.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ScrollTimeline.h"
#include "mozilla/dom/ViewTimeline.h"
#include "nsPresContext.h"
namespace mozilla {
using dom::Element;
using dom::ScrollTimeline;
using dom::ViewTimeline;
template <typename TimelineType>
static void TryDestroyTimeline(Element* aElement,
const PseudoStyleRequest& aPseudoRequest) {
auto* collection =
TimelineCollection<TimelineType>::Get(aElement, aPseudoRequest);
if (!collection) {
return;
}
collection->Destroy();
}
void TimelineManager::UpdateTimelines(Element* aElement,
const PseudoStyleRequest& aPseudoRequest,
const ComputedStyle* aComputedStyle,
ProgressTimelineType aType) {
MOZ_ASSERT(
aElement->IsInComposedDoc(),
"No need to update timelines that are not attached to the document tree");
// If we are in a display:none subtree we will have no computed values.
// However, if we are on the root of display:none subtree, the computed values
// might not have been cleared yet. In either case, since CSS animations
// should not run in display:none subtrees, so we don't need timeline, either.
const bool shouldDestroyTimelines =
!aComputedStyle ||
aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None;
switch (aType) {
case ProgressTimelineType::Scroll:
if (shouldDestroyTimelines) {
TryDestroyTimeline<ScrollTimeline>(aElement, aPseudoRequest);
return;
}
DoUpdateTimelines<StyleScrollTimeline, ScrollTimeline>(
mPresContext, aElement, aPseudoRequest,
aComputedStyle->StyleUIReset()->mScrollTimelines,
aComputedStyle->StyleUIReset()->mScrollTimelineNameCount);
break;
case ProgressTimelineType::View:
if (shouldDestroyTimelines) {
TryDestroyTimeline<ViewTimeline>(aElement, aPseudoRequest);
return;
}
DoUpdateTimelines<StyleViewTimeline, ViewTimeline>(
mPresContext, aElement, aPseudoRequest,
aComputedStyle->StyleUIReset()->mViewTimelines,
aComputedStyle->StyleUIReset()->mViewTimelineNameCount);
break;
}
}
template <typename TimelineType>
static already_AddRefed<TimelineType> PopExistingTimeline(
nsAtom* aName, TimelineCollection<TimelineType>* aCollection) {
if (!aCollection) {
return nullptr;
}
return aCollection->Extract(aName);
}
template <typename StyleType, typename TimelineType>
static auto BuildTimelines(nsPresContext* aPresContext, Element* aElement,
const PseudoStyleRequest& aPseudoRequest,
const nsStyleAutoArray<StyleType>& aTimelines,
size_t aTimelineCount,
TimelineCollection<TimelineType>* aCollection) {
typename TimelineCollection<TimelineType>::TimelineMap result;
// If multiple timelines are attempting to modify the same property, then the
// timeline closest to the end of the list of names wins.
// The spec doesn't mention this specifically for scroll-timeline-name and
// view-timeline-name, so we follow the same rule with animation-name.
for (size_t idx = 0; idx < aTimelineCount; ++idx) {
const StyleType& timeline = aTimelines[idx];
if (timeline.GetName() == nsGkAtoms::_empty) {
continue;
}
RefPtr<TimelineType> dest =
PopExistingTimeline(timeline.GetName(), aCollection);
if (dest) {
dest->ReplacePropertiesWith(aElement, aPseudoRequest, timeline);
} else {
dest = TimelineType::MakeNamed(aPresContext->Document(), aElement,
aPseudoRequest, timeline);
}
MOZ_ASSERT(dest);
// Override the previous one if it is duplicated.
Unused << result.InsertOrUpdate(timeline.GetName(), dest);
}
return result;
}
template <typename TimelineType>
static TimelineCollection<TimelineType>& EnsureTimelineCollection(
Element& aElement, const PseudoStyleRequest& aPseudoRequest);
template <>
ScrollTimelineCollection& EnsureTimelineCollection<ScrollTimeline>(
Element& aElement, const PseudoStyleRequest& aPseudoRequest) {
return aElement.EnsureAnimationData().EnsureScrollTimelineCollection(
aElement, aPseudoRequest);
}
template <>
ViewTimelineCollection& EnsureTimelineCollection<ViewTimeline>(
Element& aElement, const PseudoStyleRequest& aPseudoRequest) {
return aElement.EnsureAnimationData().EnsureViewTimelineCollection(
aElement, aPseudoRequest);
}
template <typename StyleType, typename TimelineType>
void TimelineManager::DoUpdateTimelines(
nsPresContext* aPresContext, Element* aElement,
const PseudoStyleRequest& aPseudoRequest,
const nsStyleAutoArray<StyleType>& aStyleTimelines, size_t aTimelineCount) {
auto* collection =
TimelineCollection<TimelineType>::Get(aElement, aPseudoRequest);
if (!collection && aTimelineCount == 1 &&
aStyleTimelines[0].GetName() == nsGkAtoms::_empty) {
return;
}
// We create a new timeline list based on its computed style and the existing
// timelines.
auto newTimelines = BuildTimelines<StyleType, TimelineType>(
aPresContext, aElement, aPseudoRequest, aStyleTimelines, aTimelineCount,
collection);
if (newTimelines.IsEmpty()) {
if (collection) {
collection->Destroy();
}
return;
}
if (!collection) {
collection =
&EnsureTimelineCollection<TimelineType>(*aElement, aPseudoRequest);
if (!collection->isInList()) {
AddTimelineCollection(collection);
}
}
// Replace unused timeline with new ones.
collection->Swap(newTimelines);
// FIXME: Bug 1774060. We may have to restyle the animations which use the
// dropped timelines. Or rely on restyling the subtree and the following
// siblings when mutating {scroll|view}-timeline-name.
}
void TimelineManager::UpdateHiddenByContentVisibilityForAnimations() {
for (auto* scrollTimelineCollection : mScrollTimelineCollections) {
for (ScrollTimeline* timeline :
scrollTimelineCollection->Timelines().Values()) {
timeline->UpdateHiddenByContentVisibility();
}
}
for (auto* viewTimelineCollection : mViewTimelineCollections) {
for (ViewTimeline* timeline :
viewTimelineCollection->Timelines().Values()) {
timeline->UpdateHiddenByContentVisibility();
}
}
}
} // namespace mozilla