mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
cf4401d376
Basically, we'd like to separate the originating element and its pseudo element request in Animation code (for better compatibility with Web Animations APIs and other pseudo types), and store the animations and keyframe effects in the origninating element as well. In this patch, we reuse `Element::GetPseudoElement()`, and update the function signatures in EffectCompositor and RestyleManager. All the following patches will replace more places with `PseudoStyleRequest`. Differential Revision: https://phabricator.services.mozilla.com/D228223
679 lines
25 KiB
C++
679 lines
25 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 "SMILAnimationController.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "mozilla/AutoRestore.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/PresShellInlines.h"
|
|
#include "mozilla/RestyleManager.h"
|
|
#include "mozilla/SMILTimedElement.h"
|
|
#include "mozilla/dom/DocumentInlines.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/SVGAnimationElement.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsRefreshDriver.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "SMILCompositor.h"
|
|
#include "SMILCSSProperty.h"
|
|
|
|
using namespace mozilla::dom;
|
|
|
|
namespace mozilla {
|
|
|
|
//----------------------------------------------------------------------
|
|
// SMILAnimationController implementation
|
|
|
|
//----------------------------------------------------------------------
|
|
// ctors, dtors, factory methods
|
|
|
|
SMILAnimationController::SMILAnimationController(Document* aDoc)
|
|
: mDocument(aDoc) {
|
|
MOZ_ASSERT(aDoc, "need a non-null document");
|
|
|
|
if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
|
|
mStartTime = refreshDriver->MostRecentRefresh();
|
|
} else {
|
|
mStartTime = mozilla::TimeStamp::Now();
|
|
}
|
|
mCurrentSampleTime = mStartTime;
|
|
|
|
Begin();
|
|
}
|
|
|
|
SMILAnimationController::~SMILAnimationController() {
|
|
NS_ASSERTION(mAnimationElementTable.IsEmpty(),
|
|
"Animation controller shouldn't be tracking any animation"
|
|
" elements when it dies");
|
|
MOZ_RELEASE_ASSERT(!mRegisteredWithRefreshDriver,
|
|
"Leaving stale entry in refresh driver's observer list");
|
|
}
|
|
|
|
void SMILAnimationController::Disconnect() {
|
|
MOZ_ASSERT(mDocument, "disconnecting when we weren't connected...?");
|
|
MOZ_ASSERT(mRefCnt.get() == 1,
|
|
"Expecting to disconnect when doc is sole remaining owner");
|
|
NS_ASSERTION(mPauseState & SMILTimeContainer::PAUSE_PAGEHIDE,
|
|
"Expecting to be paused for pagehide before disconnect");
|
|
|
|
StopSampling(GetRefreshDriver());
|
|
|
|
mDocument = nullptr; // (raw pointer)
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// SMILTimeContainer methods:
|
|
|
|
void SMILAnimationController::Pause(uint32_t aType) {
|
|
SMILTimeContainer::Pause(aType);
|
|
UpdateSampling();
|
|
}
|
|
|
|
void SMILAnimationController::Resume(uint32_t aType) {
|
|
bool wasPaused = !!mPauseState;
|
|
// Update mCurrentSampleTime so that calls to GetParentTime--used for
|
|
// calculating parent offsets--are accurate
|
|
mCurrentSampleTime = mozilla::TimeStamp::Now();
|
|
|
|
SMILTimeContainer::Resume(aType);
|
|
|
|
if (wasPaused && !mPauseState) {
|
|
UpdateSampling();
|
|
}
|
|
}
|
|
|
|
SMILTime SMILAnimationController::GetParentTime() const {
|
|
return (SMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// nsARefreshObserver methods:
|
|
NS_IMPL_ADDREF(SMILAnimationController)
|
|
NS_IMPL_RELEASE(SMILAnimationController)
|
|
|
|
// nsRefreshDriver Callback function
|
|
void SMILAnimationController::WillRefresh(mozilla::TimeStamp aTime) {
|
|
// Although we never expect aTime to go backwards, when we initialise the
|
|
// animation controller, if we can't get hold of a refresh driver we
|
|
// initialise mCurrentSampleTime to Now(). It may be possible that after
|
|
// doing so we get sampled by a refresh driver whose most recent refresh time
|
|
// predates when we were initialised, so to be safe we make sure to take the
|
|
// most recent time here.
|
|
aTime = std::max(mCurrentSampleTime, aTime);
|
|
|
|
// Sleep detection: If the time between samples is a whole lot greater than we
|
|
// were expecting then we assume the computer went to sleep or someone's
|
|
// messing with the clock. In that case, fiddle our parent offset and use our
|
|
// average time between samples to calculate the new sample time. This
|
|
// prevents us from hanging while trying to catch up on all the missed time.
|
|
|
|
// Smoothing of coefficient for the average function. 0.2 should let us track
|
|
// the sample rate reasonably tightly without being overly affected by
|
|
// occasional delays.
|
|
static const double SAMPLE_DUR_WEIGHTING = 0.2;
|
|
// If the elapsed time exceeds our expectation by this number of times we'll
|
|
// initiate special behaviour to basically ignore the intervening time.
|
|
static const double SAMPLE_DEV_THRESHOLD = 200.0;
|
|
|
|
SMILTime elapsedTime =
|
|
(SMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
|
|
if (mAvgTimeBetweenSamples == 0) {
|
|
// First sample.
|
|
mAvgTimeBetweenSamples = elapsedTime;
|
|
} else {
|
|
if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
|
|
// Unexpectedly long delay between samples.
|
|
NS_WARNING(
|
|
"Detected really long delay between samples, continuing from "
|
|
"previous sample");
|
|
mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
|
|
}
|
|
// Update the moving average. Due to truncation here the average will
|
|
// normally be a little less than it should be but that's probably ok.
|
|
mAvgTimeBetweenSamples =
|
|
(SMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
|
|
mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
|
|
}
|
|
mCurrentSampleTime = aTime;
|
|
|
|
Sample();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Animation element registration methods:
|
|
|
|
void SMILAnimationController::RegisterAnimationElement(
|
|
SVGAnimationElement* aAnimationElement) {
|
|
const bool wasEmpty = mAnimationElementTable.IsEmpty();
|
|
mAnimationElementTable.PutEntry(aAnimationElement);
|
|
if (wasEmpty) {
|
|
UpdateSampling();
|
|
}
|
|
}
|
|
|
|
void SMILAnimationController::UnregisterAnimationElement(
|
|
SVGAnimationElement* aAnimationElement) {
|
|
mAnimationElementTable.RemoveEntry(aAnimationElement);
|
|
if (mAnimationElementTable.IsEmpty()) {
|
|
UpdateSampling();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Page show/hide
|
|
|
|
void SMILAnimationController::OnPageShow() {
|
|
Resume(SMILTimeContainer::PAUSE_PAGEHIDE);
|
|
}
|
|
|
|
void SMILAnimationController::OnPageHide() {
|
|
Pause(SMILTimeContainer::PAUSE_PAGEHIDE);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Cycle-collection support
|
|
|
|
void SMILAnimationController::Traverse(
|
|
nsCycleCollectionTraversalCallback* aCallback) {
|
|
// Traverse last compositor table
|
|
if (mLastCompositorTable) {
|
|
for (SMILCompositor& compositor : *mLastCompositorTable) {
|
|
compositor.Traverse(aCallback);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SMILAnimationController::Unlink() { mLastCompositorTable = nullptr; }
|
|
|
|
//----------------------------------------------------------------------
|
|
// Refresh driver lifecycle related methods
|
|
|
|
void SMILAnimationController::NotifyRefreshDriverCreated(
|
|
nsRefreshDriver* aRefreshDriver) {
|
|
UpdateSampling();
|
|
}
|
|
|
|
void SMILAnimationController::NotifyRefreshDriverDestroying(
|
|
nsRefreshDriver* aRefreshDriver) {
|
|
StopSampling(aRefreshDriver);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Timer-related implementation helpers
|
|
|
|
bool SMILAnimationController::ShouldSample() const {
|
|
return !mPauseState && !mAnimationElementTable.IsEmpty() &&
|
|
!mChildContainerTable.IsEmpty();
|
|
}
|
|
|
|
void SMILAnimationController::UpdateSampling() {
|
|
const bool shouldSample = ShouldSample();
|
|
const bool isSampling = mRegisteredWithRefreshDriver;
|
|
if (shouldSample == isSampling) {
|
|
return;
|
|
}
|
|
|
|
nsRefreshDriver* driver = GetRefreshDriver();
|
|
if (!driver) {
|
|
return;
|
|
}
|
|
|
|
if (shouldSample) {
|
|
// We're effectively resuming from a pause so update our current sample time
|
|
// or else it will confuse our "average time between samples" calculations.
|
|
mCurrentSampleTime = mozilla::TimeStamp::Now();
|
|
driver->AddRefreshObserver(this, FlushType::Style, "SMIL animations");
|
|
mRegisteredWithRefreshDriver = true;
|
|
Sample(); // Run the first sample manually.
|
|
} else {
|
|
StopSampling(driver);
|
|
}
|
|
}
|
|
|
|
void SMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver) {
|
|
if (aRefreshDriver && mRegisteredWithRefreshDriver) {
|
|
// NOTE: The document might already have been detached from its PresContext
|
|
// (and RefreshDriver), which would make GetRefreshDriver() return null.
|
|
MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
|
|
"Stopping sampling with wrong refresh driver");
|
|
aRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
|
|
mRegisteredWithRefreshDriver = false;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Sample-related methods and callbacks
|
|
|
|
void SMILAnimationController::DoSample() {
|
|
DoSample(true); // Skip unchanged time containers
|
|
}
|
|
|
|
void SMILAnimationController::DoSample(bool aSkipUnchangedContainers) {
|
|
if (!mDocument) {
|
|
NS_ERROR("Shouldn't be sampling after document has disconnected");
|
|
return;
|
|
}
|
|
if (mRunningSample) {
|
|
NS_ERROR("Shouldn't be recursively sampling");
|
|
return;
|
|
}
|
|
|
|
mResampleNeeded = false;
|
|
|
|
// Set running sample flag -- do this before flushing styles so that when we
|
|
// flush styles we don't end up requesting extra samples
|
|
AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
|
|
mRunningSample = true;
|
|
|
|
// STEP 1: Bring model up to date
|
|
// (i) Rewind elements where necessary
|
|
// (ii) Run milestone samples
|
|
RewindElements();
|
|
DoMilestoneSamples();
|
|
|
|
// STEP 2: Sample the child time containers
|
|
//
|
|
// When we sample the child time containers they will simply record the sample
|
|
// time in document time.
|
|
TimeContainerHashtable activeContainers(mChildContainerTable.Count());
|
|
for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
|
|
if (!container) {
|
|
continue;
|
|
}
|
|
|
|
if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) &&
|
|
(container->NeedsSample() || !aSkipUnchangedContainers)) {
|
|
container->ClearMilestones();
|
|
container->Sample();
|
|
container->MarkSeekFinished();
|
|
activeContainers.PutEntry(container);
|
|
}
|
|
}
|
|
|
|
// STEP 3: (i) Sample the timed elements AND
|
|
// (ii) Create a table of compositors
|
|
//
|
|
// (i) Here we sample the timed elements (fetched from the
|
|
// SVGAnimationElements) which determine from the active time if the
|
|
// element is active and what its simple time etc. is. This information is
|
|
// then passed to its time client (SMILAnimationFunction).
|
|
//
|
|
// (ii) During the same loop we also build up a table that contains one
|
|
// compositor for each animated attribute and which maps animated elements to
|
|
// the corresponding compositor for their target attribute.
|
|
//
|
|
// Note that this compositor table needs to be allocated on the heap so we can
|
|
// store it until the next sample. This lets us find out which elements were
|
|
// animated in sample 'n-1' but not in sample 'n' (and hence need to have
|
|
// their animation effects removed in sample 'n').
|
|
//
|
|
// Parts (i) and (ii) are not functionally related but we combine them here to
|
|
// save iterating over the animation elements twice.
|
|
|
|
// Create the compositor table
|
|
UniquePtr<SMILCompositorTable> currentCompositorTable(
|
|
new SMILCompositorTable(0));
|
|
nsTArray<RefPtr<SVGAnimationElement>> animElems(
|
|
mAnimationElementTable.Count());
|
|
|
|
for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
|
|
SampleTimedElement(animElem, &activeContainers);
|
|
AddAnimationToCompositorTable(animElem, currentCompositorTable.get());
|
|
animElems.AppendElement(animElem);
|
|
}
|
|
activeContainers.Clear();
|
|
|
|
// STEP 4: Compare previous sample's compositors against this sample's.
|
|
// (Transfer cached base values across, & remove animation effects from
|
|
// no-longer-animated targets.)
|
|
if (mLastCompositorTable) {
|
|
// * Transfer over cached base values, from last sample's compositors
|
|
for (SMILCompositor& compositor : *currentCompositorTable) {
|
|
SMILCompositor* lastCompositor =
|
|
mLastCompositorTable->GetEntry(compositor.GetKey());
|
|
|
|
if (lastCompositor) {
|
|
compositor.StealCachedBaseValue(lastCompositor);
|
|
if (!lastCompositor->HasSameNumberOfAnimationFunctionsAs(compositor)) {
|
|
// If we have multiple animations on the same element, they share a
|
|
// compositor. If an active animation ends, it will no longer be in
|
|
// the compositor table. We need to force compositing to ensure we
|
|
// render the element with any remaining frozen animations even though
|
|
// they would not normally trigger compositing.
|
|
compositor.ToggleForceCompositing();
|
|
}
|
|
}
|
|
}
|
|
|
|
// * For each compositor in current sample's hash table, remove entry from
|
|
// prev sample's hash table -- we don't need to clear animation
|
|
// effects of those compositors, since they're still being animated.
|
|
for (const auto& key : currentCompositorTable->Keys()) {
|
|
mLastCompositorTable->RemoveEntry(key);
|
|
}
|
|
|
|
// * For each entry that remains in prev sample's hash table (i.e. for
|
|
// every target that's no longer animated), clear animation effects.
|
|
for (SMILCompositor& compositor : *mLastCompositorTable) {
|
|
compositor.ClearAnimationEffects();
|
|
}
|
|
}
|
|
|
|
// return early if there are no active animations to avoid a style flush
|
|
if (currentCompositorTable->IsEmpty()) {
|
|
mLastCompositorTable = nullptr;
|
|
return;
|
|
}
|
|
|
|
// STEP 5: Compose currently-animated attributes.
|
|
// XXXdholbert: This step traverses our animation targets in an effectively
|
|
// random order. For animation from/to 'inherit' values to work correctly
|
|
// when the inherited value is *also* being animated, we really should be
|
|
// traversing our animated nodes in an ancestors-first order (bug 501183)
|
|
bool mightHavePendingStyleUpdates = false;
|
|
for (auto& compositor : *currentCompositorTable) {
|
|
compositor.ComposeAttribute(mightHavePendingStyleUpdates);
|
|
}
|
|
|
|
// Update last compositor table
|
|
mLastCompositorTable = std::move(currentCompositorTable);
|
|
mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates;
|
|
|
|
NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
|
|
}
|
|
|
|
void SMILAnimationController::RewindElements() {
|
|
const bool rewindNeeded = std::any_of(
|
|
mChildContainerTable.Keys().cbegin(), mChildContainerTable.Keys().cend(),
|
|
[](SMILTimeContainer* container) { return container->NeedsRewind(); });
|
|
|
|
if (!rewindNeeded) return;
|
|
|
|
for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
|
|
SMILTimeContainer* timeContainer = animElem->GetTimeContainer();
|
|
if (timeContainer && timeContainer->NeedsRewind()) {
|
|
animElem->TimedElement().Rewind();
|
|
}
|
|
}
|
|
|
|
for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
|
|
container->ClearNeedsRewind();
|
|
}
|
|
}
|
|
|
|
void SMILAnimationController::DoMilestoneSamples() {
|
|
// We need to sample the timing model but because SMIL operates independently
|
|
// of the frame-rate, we can get one sample at t=0s and the next at t=10min.
|
|
//
|
|
// In between those two sample times a whole string of significant events
|
|
// might be expected to take place: events firing, new interdependencies
|
|
// between animations resolved and dissolved, etc.
|
|
//
|
|
// Furthermore, at any given time, we want to sample all the intervals that
|
|
// end at that time BEFORE any that begin. This behaviour is implied by SMIL's
|
|
// endpoint-exclusive timing model.
|
|
//
|
|
// So we have the animations (specifically the timed elements) register the
|
|
// next significant moment (called a milestone) in their lifetime and then we
|
|
// step through the model at each of these moments and sample those animations
|
|
// registered for those times. This way events can fire in the correct order,
|
|
// dependencies can be resolved etc.
|
|
|
|
SMILTime sampleTime = std::numeric_limits<SMILTime>::min();
|
|
|
|
while (true) {
|
|
// We want to find any milestones AT OR BEFORE the current sample time so we
|
|
// initialise the next milestone to the moment after (1ms after, to be
|
|
// precise) the current sample time and see if there are any milestones
|
|
// before that. Any other milestones will be dealt with in a subsequent
|
|
// sample.
|
|
SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
|
|
for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
|
|
if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
|
|
continue;
|
|
}
|
|
SMILMilestone thisMilestone;
|
|
bool didGetMilestone =
|
|
container->GetNextMilestoneInParentTime(thisMilestone);
|
|
if (didGetMilestone && thisMilestone < nextMilestone) {
|
|
nextMilestone = thisMilestone;
|
|
}
|
|
}
|
|
|
|
if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) {
|
|
break;
|
|
}
|
|
|
|
nsTArray<RefPtr<dom::SVGAnimationElement>> elements;
|
|
for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
|
|
if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
|
|
continue;
|
|
}
|
|
container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
|
|
}
|
|
|
|
// During the course of a sampling we don't want to actually go backwards.
|
|
// Due to negative offsets, early ends and the like, a timed element might
|
|
// register a milestone that is actually in the past. That's fine, but it's
|
|
// still only going to get *sampled* with whatever time we're up to and no
|
|
// earlier.
|
|
//
|
|
// Because we're only performing this clamping at the last moment, the
|
|
// animations will still all get sampled in the correct order and
|
|
// dependencies will be appropriately resolved.
|
|
sampleTime = std::max(nextMilestone.mTime, sampleTime);
|
|
|
|
for (RefPtr<dom::SVGAnimationElement>& elem : elements) {
|
|
MOZ_ASSERT(elem, "nullptr animation element in list");
|
|
SMILTimeContainer* container = elem->GetTimeContainer();
|
|
if (!container)
|
|
// The container may be nullptr if the element has been detached from
|
|
// its parent since registering a milestone.
|
|
continue;
|
|
|
|
SMILTimeValue containerTimeValue =
|
|
container->ParentToContainerTime(sampleTime);
|
|
if (!containerTimeValue.IsDefinite()) continue;
|
|
|
|
// Clamp the converted container time to non-negative values.
|
|
SMILTime containerTime =
|
|
std::max<SMILTime>(0, containerTimeValue.GetMillis());
|
|
|
|
if (nextMilestone.mIsEnd) {
|
|
elem->TimedElement().SampleEndAt(containerTime);
|
|
} else {
|
|
elem->TimedElement().SampleAt(containerTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*static*/
|
|
void SMILAnimationController::SampleTimedElement(
|
|
SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) {
|
|
SMILTimeContainer* timeContainer = aElement->GetTimeContainer();
|
|
if (!timeContainer) return;
|
|
|
|
// We'd like to call timeContainer->NeedsSample() here and skip all timed
|
|
// elements that belong to paused time containers that don't need a sample,
|
|
// but that doesn't work because we've already called Sample() on all the time
|
|
// containers so the paused ones don't need a sample any more and they'll
|
|
// return false.
|
|
//
|
|
// Instead we build up a hashmap of active time containers during the previous
|
|
// step (SampleTimeContainer) and then test here if the container for this
|
|
// timed element is in the list.
|
|
if (!aActiveContainers->GetEntry(timeContainer)) return;
|
|
|
|
SMILTime containerTime = timeContainer->GetCurrentTimeAsSMILTime();
|
|
|
|
MOZ_ASSERT(!timeContainer->IsSeeking(),
|
|
"Doing a regular sample but the time container is still seeking");
|
|
aElement->TimedElement().SampleAt(containerTime);
|
|
}
|
|
|
|
/*static*/
|
|
void SMILAnimationController::AddAnimationToCompositorTable(
|
|
SVGAnimationElement* aElement, SMILCompositorTable* aCompositorTable) {
|
|
// Add a compositor to the hash table if there's not already one there
|
|
SMILTargetIdentifier key;
|
|
if (!GetTargetIdentifierForAnimation(aElement, key))
|
|
// Something's wrong/missing about animation's target; skip this animation
|
|
return;
|
|
|
|
SMILAnimationFunction& func = aElement->AnimationFunction();
|
|
|
|
// Only add active animation functions. If there are no active animations
|
|
// targeting an attribute, no compositor will be created and any previously
|
|
// applied animations will be cleared.
|
|
if (func.IsActiveOrFrozen()) {
|
|
// Look up the compositor for our target, & add our animation function
|
|
// to its list of animation functions.
|
|
SMILCompositor* result = aCompositorTable->PutEntry(key);
|
|
result->AddAnimationFunction(&func);
|
|
|
|
} else if (func.HasChanged()) {
|
|
// Look up the compositor for our target, and force it to skip the
|
|
// "nothing's changed so don't bother compositing" optimization for this
|
|
// sample. |func| is inactive, but it's probably *newly* inactive (since
|
|
// it's got HasChanged() == true), so we need to make sure to recompose
|
|
// its target.
|
|
SMILCompositor* result = aCompositorTable->PutEntry(key);
|
|
result->ToggleForceCompositing();
|
|
|
|
// We've now made sure that |func|'s inactivity will be reflected as of
|
|
// this sample. We need to clear its HasChanged() flag so that it won't
|
|
// trigger this same clause in future samples (until it changes again).
|
|
func.ClearHasChanged();
|
|
}
|
|
}
|
|
|
|
static inline bool IsTransformAttribute(int32_t aNamespaceID,
|
|
nsAtom* aAttributeName) {
|
|
return aNamespaceID == kNameSpaceID_None &&
|
|
(aAttributeName == nsGkAtoms::transform ||
|
|
aAttributeName == nsGkAtoms::patternTransform ||
|
|
aAttributeName == nsGkAtoms::gradientTransform);
|
|
}
|
|
|
|
// Helper function that, given a SVGAnimationElement, looks up its target
|
|
// element & target attribute and populates a SMILTargetIdentifier
|
|
// for this target.
|
|
/*static*/
|
|
bool SMILAnimationController::GetTargetIdentifierForAnimation(
|
|
SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) {
|
|
// Look up target (animated) element
|
|
Element* targetElem = aAnimElem->GetTargetElementContent();
|
|
if (!targetElem)
|
|
// Animation has no target elem -- skip it.
|
|
return false;
|
|
|
|
// Look up target (animated) attribute
|
|
// SMILANIM section 3.1, attributeName may
|
|
// have an XMLNS prefix to indicate the XML namespace.
|
|
RefPtr<nsAtom> attributeName;
|
|
int32_t attributeNamespaceID;
|
|
if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
|
|
getter_AddRefs(attributeName)))
|
|
// Animation has no target attr -- skip it.
|
|
return false;
|
|
|
|
// animateTransform can only animate transforms, conversely transforms
|
|
// can only be animated by animateTransform
|
|
if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
|
|
(aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)))
|
|
return false;
|
|
|
|
// Construct the key
|
|
aResult.mElement = targetElem;
|
|
aResult.mAttributeName = attributeName;
|
|
aResult.mAttributeNamespaceID = attributeNamespaceID;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SMILAnimationController::PreTraverse() { PreTraverseInSubtree(nullptr); }
|
|
|
|
void SMILAnimationController::PreTraverseInSubtree(Element* aRoot) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mMightHavePendingStyleUpdates) {
|
|
return;
|
|
}
|
|
|
|
nsPresContext* context = mDocument->GetPresContext();
|
|
if (!context) {
|
|
return;
|
|
}
|
|
|
|
for (SVGAnimationElement* animElement : mAnimationElementTable.Keys()) {
|
|
SMILTargetIdentifier key;
|
|
if (!GetTargetIdentifierForAnimation(animElement, key)) {
|
|
// Something's wrong/missing about animation's target; skip this animation
|
|
continue;
|
|
}
|
|
|
|
// Ignore restyles that aren't in the flattened tree subtree rooted at
|
|
// aRoot.
|
|
if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
|
|
key.mElement, aRoot)) {
|
|
continue;
|
|
}
|
|
|
|
context->RestyleManager()->PostRestyleEventForAnimations(
|
|
key.mElement, PseudoStyleRequest::NotPseudo(),
|
|
RestyleHint::RESTYLE_SMIL);
|
|
}
|
|
|
|
// Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
|
|
// all restyles.
|
|
if (!aRoot) {
|
|
mMightHavePendingStyleUpdates = false;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Add/remove child time containers
|
|
|
|
nsresult SMILAnimationController::AddChild(SMILTimeContainer& aChild) {
|
|
const bool wasEmpty = mChildContainerTable.IsEmpty();
|
|
TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
|
|
NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
|
|
if (wasEmpty) {
|
|
UpdateSampling();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) {
|
|
mChildContainerTable.RemoveEntry(&aChild);
|
|
if (mChildContainerTable.IsEmpty()) {
|
|
UpdateSampling();
|
|
}
|
|
}
|
|
|
|
// Helper method
|
|
nsRefreshDriver* SMILAnimationController::GetRefreshDriver() {
|
|
if (!mDocument) {
|
|
NS_ERROR("Requesting refresh driver after document has disconnected!");
|
|
return nullptr;
|
|
}
|
|
|
|
nsPresContext* context = mDocument->GetPresContext();
|
|
return context ? context->RefreshDriver() : nullptr;
|
|
}
|
|
|
|
void SMILAnimationController::FlagDocumentNeedsFlush() {
|
|
if (PresShell* presShell = mDocument->GetPresShell()) {
|
|
presShell->SetNeedStyleFlush();
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla
|