DocumentTimelinesController.cpp [plain text]
#include "config.h"
#include "DocumentTimelinesController.h"
#include "AnimationEventBase.h"
#include "CSSTransition.h"
#include "DOMWindow.h"
#include "Document.h"
#include "DocumentTimeline.h"
#include "EventLoop.h"
#include "Logging.h"
#include "Page.h"
#include "Settings.h"
#include "WebAnimation.h"
#include "WebAnimationTypes.h"
#include <JavaScriptCore/VM.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
DocumentTimelinesController::DocumentTimelinesController(Document& document)
: m_document(document)
{
if (auto* page = document.page()) {
if (page->settings().hiddenPageCSSAnimationSuspensionEnabled() && !page->isVisible())
suspendAnimations();
}
}
DocumentTimelinesController::~DocumentTimelinesController()
{
}
void DocumentTimelinesController::addTimeline(DocumentTimeline& timeline)
{
m_timelines.add(timeline);
if (m_isSuspended)
timeline.suspendAnimations();
else
timeline.resumeAnimations();
}
void DocumentTimelinesController::removeTimeline(DocumentTimeline& timeline)
{
m_timelines.remove(timeline);
}
void DocumentTimelinesController::detachFromDocument()
{
m_currentTimeClearingTaskQueue.close();
while (!m_timelines.computesEmpty())
m_timelines.begin()->detachFromDocument();
}
void DocumentTimelinesController::updateAnimationsAndSendEvents(ReducedResolutionSeconds timestamp)
{
LOG_WITH_STREAM(Animations, stream << "DocumentTimelinesController::updateAnimationsAndSendEvents for time " << timestamp);
ASSERT(!m_timelines.hasNullReferences());
Vector<Ref<DocumentTimeline>> protectedTimelines;
for (auto& timeline : m_timelines)
protectedTimelines.append(timeline);
if (!m_isSuspended)
cacheCurrentTime(timestamp);
Vector<Ref<DocumentTimeline>> timelinesToUpdate;
Vector<Ref<WebAnimation>> animationsToRemove;
Vector<Ref<CSSTransition>> completedTransitions;
for (auto& timeline : protectedTimelines) {
auto shouldUpdateAnimationsAndSendEvents = timeline->documentWillUpdateAnimationsAndSendEvents();
if (shouldUpdateAnimationsAndSendEvents == DocumentTimeline::ShouldUpdateAnimationsAndSendEvents::No)
continue;
timelinesToUpdate.append(timeline.copyRef());
for (auto& animation : copyToVector(timeline->relevantAnimations())) {
if (animation->timeline() != timeline.ptr()) {
ASSERT(!animation->timeline());
continue;
}
animation->tick();
if (!animation->isRelevant() && !animation->needsTick())
animationsToRemove.append(*animation);
if (is<CSSTransition>(*animation)) {
auto& transition = downcast<CSSTransition>(*animation);
if (!transition.needsTick() && transition.playState() == WebAnimation::PlayState::Finished && transition.owningElement())
completedTransitions.append(transition);
}
}
}
if (timelinesToUpdate.isEmpty())
return;
for (auto& timeline : protectedTimelines)
timeline->removeReplacedAnimations();
makeRef(m_document)->eventLoop().performMicrotaskCheckpoint();
AnimationEvents events;
for (auto& timeline : timelinesToUpdate)
events.appendVector(timeline->prepareForPendingAnimationEventsDispatch());
std::stable_sort(events.begin(), events.end(), [] (const Ref<AnimationEventBase>& lhs, const Ref<AnimationEventBase>& rhs) {
return lhs->timelineTime() < rhs->timelineTime();
});
for (auto& event : events)
event->target()->dispatchEvent(event);
for (auto& animation : animationsToRemove) {
if (auto timeline = animation->timeline()) {
if (!animation->isRelevant() && !animation->needsTick())
timeline->removeAnimation(animation);
}
}
for (auto& completedTransition : completedTransitions) {
if (auto timeline = completedTransition->timeline())
downcast<DocumentTimeline>(*timeline).transitionDidComplete(WTFMove(completedTransition));
}
for (auto& timeline : timelinesToUpdate)
timeline->documentDidUpdateAnimationsAndSendEvents();
}
void DocumentTimelinesController::suspendAnimations()
{
if (m_isSuspended)
return;
if (!m_cachedCurrentTime)
m_cachedCurrentTime = liveCurrentTime();
for (auto& timeline : m_timelines)
timeline.suspendAnimations();
m_isSuspended = true;
}
void DocumentTimelinesController::resumeAnimations()
{
if (!m_isSuspended)
return;
m_cachedCurrentTime = WTF::nullopt;
m_isSuspended = false;
for (auto& timeline : m_timelines)
timeline.resumeAnimations();
}
bool DocumentTimelinesController::animationsAreSuspended() const
{
return m_isSuspended;
}
ReducedResolutionSeconds DocumentTimelinesController::liveCurrentTime() const
{
return m_document.domWindow()->nowTimestamp();
}
Optional<Seconds> DocumentTimelinesController::currentTime()
{
if (!m_document.domWindow())
return WTF::nullopt;
if (!m_cachedCurrentTime)
cacheCurrentTime(liveCurrentTime());
return *m_cachedCurrentTime;
}
void DocumentTimelinesController::cacheCurrentTime(ReducedResolutionSeconds newCurrentTime)
{
m_cachedCurrentTime = newCurrentTime;
m_waitingOnVMIdle = true;
if (!m_currentTimeClearingTaskQueue.hasPendingTasks())
m_currentTimeClearingTaskQueue.enqueueTask(std::bind(&DocumentTimelinesController::maybeClearCachedCurrentTime, this));
m_document.vm().whenIdle([this, protectedDocument = makeRefPtr(m_document)]() {
m_waitingOnVMIdle = false;
maybeClearCachedCurrentTime();
});
}
void DocumentTimelinesController::maybeClearCachedCurrentTime()
{
if (!m_isSuspended && !m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks())
m_cachedCurrentTime = WTF::nullopt;
}
}