Files
palemoon27/dom/smil/nsSMILAnimationController.cpp
T
roytam1 55e2238bc1 import changes from `dev' branch of rmottola/Arctic-Fox:
- bug 1212906 - don't handle windows messages while waiting for a sync a11y ipc message r=billm (c9297e58f4)
- Bug 1183431 - Tests for hypothetical box computation (to determine static position of abspos element) where orthogonal writing modes are involved. r=dholbert (87b22c5cf5)
- Bug 1221112: When checking for ::before/::after while sorting flex items by DOM order, dig past placeholder to out-of-flow frame. r=mats (b2e252fa86)
- Bug 1169420: add crashtests. (no review) (f7484702a2)
- Bug 1183431 - Ensure hypothetical box has a writing mode with the same block direction as the absolute containing block. r=dholbert (9106e022d1)
- Partial - No bug - Various crashtest manifest cleanups. (f2d0568f2d)
- Bug 1221874 - Crashtest for overzealous writing-mode check (currently asserts). r=smontagu (ef6685f606)
- Bug 1221874 - Ignore the sideways-orientation flag when logical-coordinate classes are checking writing-mode compatibility. r=smontagu (9617a761d6)
- Bug 1193488 - patch 2 - Update the writing-mode representation printed by debugging code such as DumpFrameTree to reflect the new property values. r=dholbert (b6b3fdda58)
- Bug 1208593 - Make the WritingMode::DebugString() method available even in non-DEBUG compilation (needed by --enable-dump-painting builds, for example). r=mchang (d6b46fde86)
- Bug 1122918 - Part 1 - Add parsing for logical inline-start/end keywords to the 'float' and 'clear' properties. r=heycam (20f9dc4d11)
- Bug 1122918 - Part 2 - Provide accessors that return logical 'float' and 'clear' values resolved to their physical equivalents. r=heycam (9380f050d6)
- Bug 1108055 - Part 1: Add ComputedTimingProperties dictionary. r=smaug (ac88430cfe)
- Bug 1108055 - Part 2: Refine ComputedTiming. r=birtles (3c25124b5f)
- Bug 1108055 - Part 3: Implement GetComputedTiming method. r=birtles (d9fe931cc5)
- Bug 1108055 - Part 4: Add ComputedTiming mochitests. r=birtles (1687824926)
- Bug 1195180 part 1 - Add Animation::NeedsTicks; r=heycam (9ac3bbda87)
- Bug 1195180 part 2 - Make DocumentTimeline inherit from nsARefreshObserver; r=heycam (292f9d154d)
- Bug 1195180 part 3 - Add flag to record if DocumentTimeline is observing the refresh driver; r=heycam (3d90d3f149)
- Bug 1195180 part 4 - Unregister from refresh observer when there are no animations needing ticks; r=heycam (ad76c3ddd5)
- Bug 1195180 part 5 - Synchronize refresh driver observing with presshell creation/teardown; r=heycam (94b7d9176f)
- Bug 1195180 part 6 - Lazily remove animations from timelines; r=heycam (bb3beaa264)
- Bug 1195180 part 7 - Store animations in an array; r=heycam (199756e7d6)
- Bug 1195180 part 8 - Tick animations from their timeline; r=heycam (12942bdc45)
- Bug 1195180 part 9 - Add test for seeking finished -> paused; r=heycam (0b42afcd26)
- Bug 1195180 part 10 - Remove refresh driver observing machinery from CommonAnimationManager; r=heycam (bf233d32ac)
- Bug 1208938 part 1 - Rename AnimationCollection::mNeedsRefreshes to mStyleChanging; r=heycam (679bdf0770)
- Bug 1208938 part 2 - Remove Animation::HasEndEventToQueue; r=heycam (c75bdb64b7)
- Bug 1208938 part 3 - Update pending finishing handling; r=heycam (c9f4c1aace)
- Bug 1196379 - Fix incorrect assertion checking for sorted arrays in nsDocumentRuleResultCacheKey::Matches. r=dbaron (a12db544ef)
- Bug 978833 patch 3 - Make css::Declaration implement nsIStyleRule. r=heycam (3b79410f06)
- Bug 978833 patch 4 - Add pointer back from css::Declaration to css::Rule. r=heycam (2f86331573)
- Bug 978833 patch 5 - Rename ImportantRule to ImportantStyleData. r=heycam (e8471fc026)
- Bug 978833 patch 6 - Move ImportantStyleData from StyleRule.{h,cpp} to Declaration.{h,cpp} r=heycam (612875a128)
- Bug 978833 patch 7 - Fuse allocation of ImportantStyleData with Declaration. r=heycam (a8e9e99a35)
- Bug 978833 patch 8 - Call SetImmutable for declarations of @page and keyframe rules. r=heycam (6b04b5a81e)
- Bug 978833 patch 9 - Move keyframe !important data assertion to where it will continue to happen. r=heycam (862c6df232)
- Bug 978833 patch 10 - Raise consistent exceptions so that the exception doesn't depend on rule destruction timing. r=bzbarsky (8bd58857e9)
- Bug 978833 patch 11 - Add missing #includes in preparation for nsRuleWalker.h #include change in following patch. r=heycam (25a6357983)
- Bug 1180118 - Part 6: Return eRestyle_SomeDescendants from HasAttributeDependentStyle where appropriate. r=bzbarsky (21d88cc938)
- Bug 1202512 - Part 2: Make SelectorMatchesTree take a flags argument. r=bzbarsky (b660a417f8)
- Bug 978833 patch 12 - Use the css::Declaration instead of the css::StyleRule as the matching rule. r=heycam (dd6a7914a6)
- Bug 978833 patch 13 - Remove important rule creation from css::StyleRule. r=heycam (4766c9a635)
- Bug 978833 patch 14 - Pass mozilla::css::Rule instead of nsIStyleRule to nsIDocument/nsIDocumentObserver style rule methods. r=heycam (a632b828b9)
- Bug 978833 patch 15 - Make css::Rule no longer inherit from nsIStyleRule. r=heycam (a975922671)
- Bug 978833 patch 16 - Always call Declaration::SetImmutable when we match a rule. r=heycam (0f0c89aede)
- Bug 978833 patch 17 - Remove Rule::mWasMatched. r=heycam (d0bb9bfef1)
- Bug 978833 patch 18 - Eliminate StyleRule::RuleMatched and call Declaration::SetImmutable directly for style rules (like for @page and keyframe rules).  r=heycam (70d5e45a7c)
- Bug 978833 patch 19 - Fix ResolvedStyleCache to use Declaration rather than nsCSSKeyframeRule as keys. r=heycam (223cf85e8b)
- Bug 978833 patch 20 - Rev IIDs that I should have revised in Bug 978833 patch 14. (c6421cbca8)
- Bug 1214936 - Make the ArrayBuffer constructor throw if invoked without 'new'. (r=evilpie) (ec76f43c26)
- Bug 1198826 - Rename obj parameter to buffer to improve readability, r=terrence (3b1d280a76)
- pointer style (1f9d6a7d22)
- Bug 1218111 - Fix property enumeration order of unboxed objects with expando properties. r=bhackett (a7fdc9482d)
- Bug 980945 - Throw when invoking a typed array constructor without new. r=efaust (4f80026991)
- Bug 1192572 - Add id to each opcode section in SpiderMonkey Bytecode documentation. r=Waldo (bfd9d4c165)
- Bug 1219363 - Fix sort of indexed properties to not include properties already in the Vector. r=jorendorff (77edd4d779)
- Bug 1217593 - Fix compilation of LComputeThis now this can be undefined r=shu (d227ddf666)
- Bug 1212343 - Propagate OOM from SafepointWriter r=nbp (d5a79b7d61)
- remove core files (e008661015)
- Bug 1215937 - Remove use of expression closure from js/xpconnect/. r=bholley (e928296111)
- Bug 1196975 - part1: shimwaiver. r=billm (40bc720f37)
- Bug 1196975 - part2: shimwaiver applications. r=mossop (60cf9a9342)
- Bug 1196975 - part3: GetTabForContentWindowNoShim. r=billm (05d2af3c9a)
- Bug 1196975 - part4: waiveInterposition. r=billm (31ecb441b1)
- Bug 1196975 - part5: waiveInterposition for childloader. r=mossop (fe30dddc85)
- Bug 1196975 - part6: SDK Worker should not support CPOWs. r=mossop (7e59947069)
- Bug 1129662: sdk/page-worker should use a remote page. r=krizsa (ef63bafbe4)
- Bug 1216157 - Check for OOM when generating Ion RegExp stubs. r=jonco (690229c6d7)
- Bug 1221436 patch 5 - Stop generating new style rules in DeclarationChanged (and rename it to SetDeclaration), since we no longer need a new style rule for nsIStyleRule identity rules. r=heycam (98d860d06e)
- Bug 1220516 - Devirtualize CSSStyleSheet::GetOriginalURI. r=bzbarsky (6669f8ef70)
- Fix formatting of some macros; no bug. (fe337a3e91)
- Bug 1165538 part 1 - Use delegated constructor for nsStyleFont to simplify code and remove the need of nsStyleFont::Init. r=heycam (521e59852a)
- Bug 1165538 part 2 - Add -moz-min-font-size-ratio internal property. r=heycam (a16ef2d609)
- Bug 1165538 part 3 - Apply -moz-min-font-size-ratio to rt elements and add test. r=heycam (363684724b)
- Bug 1035091 part 1: change CSS parser and loader APIs to distinguish UA, user, and author sheets instead of just UA vs everyone else. r=heycam (01decf75df)
- Bug 1221436 patch 1 - Have a pointer back from the css::Declaration for style attributes to the nsHTMLCSSStyleSheet. r=heycam (308bf9701e)
- Bug 1221436 patch 2 - For style attributes, only store a css::Declaration instead of a css::StyleRule. r=heycam (f068adb5e9)
- Bug 1167588 - Mark Rule::mParentRule as MOZ_NON_OWNING_REF. r=dbaron (bdb2bfe4a7)
- Bug 1221436 patch 3 - Remove Rule::SetHTMLCSSStyleSheet and related code, now unused. r=heycam (d4fd6fca7c)
- Bug 1221436 patch 4 - Use the same pointer to store the owning rule and the nsHTMLCSSStyleSheet on css::Declaration, since we never need both. r=heycam (1a949ec7dc)
- Bug 1221436 patch 6 - Remove mechanism for replacing style rules. r=heycam (870539dcca)
- Bug 1221436 patch 7 - Remove ReplaceStyleRule/ReplaceRuleInGroup mechanism. r=heycam (a8a58186c1)
- Bug 1186768 patch 1 - Avoid setting different font-size conditions due to MathML font size adjustments. r=heycam (5b48d02031)
- Bug 1146101 - Test. r=dbaron a=abillings (b214ab1529)
- Bug 1161366 - Correctly set referrer when loading fonts with |new FontFace()|. r=dbaron (52a59ad6a4)
- Bug 1206105 - Use CheckedInt for an+b selector matching. r=bzbarsky (739875148c)
- Bug 1163446 - Drop reference to FontFace once a FontFaceBufferSource is loaded. r=jdaggett (9fd3a219c6)
- Bug 1164813 - Make nsAnimationManager update cached in-effect status even when there is no frame; r=dbaron (6f94c96764)
- Bug 1186768 patch 2 - Crashtest. r=heycam (a566cfef9b)
- Bug 1186768 patch 3 - Compute affectedByScriptMinSize sooner to avoid asserting or doing extra work. r=heycam (30dac96a78)
- Bug 1038663 (part 1) - Make nsStyleText::mWordSpacing an nsStyleCoord. r=heycam. (42a6a2398a)
- Bug 1038663 (part 2, attempt 2) - Move GetFirstFontMetrics() up. r=heycam. (5992325377)
- Bug 1038663 (part 3, attempt 2) - Factor out space width computation. r=heycam. (92281609f0)
- Bug 1038663 (part 4, attempt 2) - Inline StyleToCoord(). r=heycam. (f40476c105)
- Bug 1038663 (part 5, attempt 2) - Change GetSpacingFlags(). r=heycam (e1fe5ecdf0)
- Bug 1038663 (part 6, attempt 2) - Allow percentage values for 'word-spacing'. r=heycam. (433fee69f6)
- Bug 1038663 (part 7, attempt 3) - Add test for percentage values for 'word-spacing'. r=heycam. (72df555579)
- Bug 1216747 - Record dependency on writing mode in nsRuleNode::ComputeStylePosition. r=heycam (8604ceff20)
- Bug 1216747 followup - Use the correct value when calling SetWritingModeDependency. (b39a82cbf7)
- Bug 1216747 - Cleanup after test fix. r=heycam (e99ec5989a)
- Bug 1170781 - Patch 1: Implement CSS 'contain: paint'. r=dholbert (3b2f65288f)
- Bug 1170781 - Patch 2: Test CSS 'contain: paint'. r=dholbert (ee83c7c0fd)
2022-11-16 08:06:33 +08:00

783 lines
27 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 "nsSMILAnimationController.h"
#include "nsSMILCompositor.h"
#include "nsSMILCSSProperty.h"
#include "nsCSSProps.h"
#include "nsITimer.h"
#include "mozilla/dom/Element.h"
#include "nsIDocument.h"
#include "mozilla/dom/SVGAnimationElement.h"
#include "nsSMILTimedElement.h"
#include <algorithm>
#include "mozilla/AutoRestore.h"
#include "RestyleTracker.h"
using namespace mozilla;
using namespace mozilla::dom;
//----------------------------------------------------------------------
// nsSMILAnimationController implementation
//----------------------------------------------------------------------
// ctors, dtors, factory methods
nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc)
: mAvgTimeBetweenSamples(0),
mResampleNeeded(false),
mDeferredStartSampling(false),
mRunningSample(false),
mRegisteredWithRefreshDriver(false),
mMightHavePendingStyleUpdates(false),
mDocument(aDoc)
{
MOZ_ASSERT(aDoc, "need a non-null document");
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (refreshDriver) {
mStartTime = refreshDriver->MostRecentRefresh();
} else {
mStartTime = mozilla::TimeStamp::Now();
}
mCurrentSampleTime = mStartTime;
Begin();
}
nsSMILAnimationController::~nsSMILAnimationController()
{
NS_ASSERTION(mAnimationElementTable.Count() == 0,
"Animation controller shouldn't be tracking any animation"
" elements when it dies");
NS_ASSERTION(!mRegisteredWithRefreshDriver,
"Leaving stale entry in refresh driver's observer list");
}
void
nsSMILAnimationController::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 & nsSMILTimeContainer::PAUSE_PAGEHIDE,
"Expecting to be paused for pagehide before disconnect");
StopSampling(GetRefreshDriver());
mDocument = nullptr; // (raw pointer)
}
//----------------------------------------------------------------------
// nsSMILTimeContainer methods:
void
nsSMILAnimationController::Pause(uint32_t aType)
{
nsSMILTimeContainer::Pause(aType);
if (mPauseState) {
mDeferredStartSampling = false;
StopSampling(GetRefreshDriver());
}
}
void
nsSMILAnimationController::Resume(uint32_t aType)
{
bool wasPaused = (mPauseState != 0);
// Update mCurrentSampleTime so that calls to GetParentTime--used for
// calculating parent offsets--are accurate
mCurrentSampleTime = mozilla::TimeStamp::Now();
nsSMILTimeContainer::Resume(aType);
if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
MaybeStartSampling(GetRefreshDriver());
Sample(); // Run the first sample manually
}
}
nsSMILTime
nsSMILAnimationController::GetParentTime() const
{
return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
}
//----------------------------------------------------------------------
// nsARefreshObserver methods:
NS_IMPL_ADDREF(nsSMILAnimationController)
NS_IMPL_RELEASE(nsSMILAnimationController)
// nsRefreshDriver Callback function
void
nsSMILAnimationController::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;
nsSMILTime elapsedTime =
(nsSMILTime)(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 =
(nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
}
mCurrentSampleTime = aTime;
Sample();
}
//----------------------------------------------------------------------
// Animation element registration methods:
void
nsSMILAnimationController::RegisterAnimationElement(
SVGAnimationElement* aAnimationElement)
{
mAnimationElementTable.PutEntry(aAnimationElement);
if (mDeferredStartSampling) {
mDeferredStartSampling = false;
if (mChildContainerTable.Count()) {
// mAnimationElementTable was empty, but now we've added its 1st element
MOZ_ASSERT(mAnimationElementTable.Count() == 1,
"we shouldn't have deferred sampling if we already had "
"animations registered");
StartSampling(GetRefreshDriver());
Sample(); // Run the first sample manually
} // else, don't sample until a time container is registered (via AddChild)
}
}
void
nsSMILAnimationController::UnregisterAnimationElement(
SVGAnimationElement* aAnimationElement)
{
mAnimationElementTable.RemoveEntry(aAnimationElement);
}
//----------------------------------------------------------------------
// Page show/hide
void
nsSMILAnimationController::OnPageShow()
{
Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE);
}
void
nsSMILAnimationController::OnPageHide()
{
Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
}
//----------------------------------------------------------------------
// Cycle-collection support
void
nsSMILAnimationController::Traverse(
nsCycleCollectionTraversalCallback* aCallback)
{
// Traverse last compositor table
if (mLastCompositorTable) {
for (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
nsSMILCompositor* compositor = iter.Get();
compositor->Traverse(aCallback);
}
}
}
void
nsSMILAnimationController::Unlink()
{
mLastCompositorTable = nullptr;
}
//----------------------------------------------------------------------
// Refresh driver lifecycle related methods
void
nsSMILAnimationController::NotifyRefreshDriverCreated(
nsRefreshDriver* aRefreshDriver)
{
if (!mPauseState) {
MaybeStartSampling(aRefreshDriver);
}
}
void
nsSMILAnimationController::NotifyRefreshDriverDestroying(
nsRefreshDriver* aRefreshDriver)
{
if (!mPauseState && !mDeferredStartSampling) {
StopSampling(aRefreshDriver);
}
}
//----------------------------------------------------------------------
// Timer-related implementation helpers
void
nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver)
{
NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
NS_ASSERTION(!mDeferredStartSampling,
"Started sampling but the deferred start flag is still set");
if (aRefreshDriver) {
MOZ_ASSERT(!mRegisteredWithRefreshDriver,
"Redundantly registering with refresh driver");
MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
"Starting sampling with wrong refresh driver");
// 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();
aRefreshDriver->AddRefreshObserver(this, Flush_Style);
mRegisteredWithRefreshDriver = true;
}
}
void
nsSMILAnimationController::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, Flush_Style);
mRegisteredWithRefreshDriver = false;
}
}
void
nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver)
{
if (mDeferredStartSampling) {
// We've received earlier 'MaybeStartSampling' calls, and we're
// deferring until we get a registered animation.
return;
}
if (mAnimationElementTable.Count()) {
StartSampling(aRefreshDriver);
} else {
mDeferredStartSampling = true;
}
}
//----------------------------------------------------------------------
// Sample-related methods and callbacks
void
nsSMILAnimationController::DoSample()
{
DoSample(true); // Skip unchanged time containers
}
void
nsSMILAnimationController::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 (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
nsSMILTimeContainer* container = iter.Get()->GetKey();
if (!container) {
continue;
}
if (!container->IsPausedByType(nsSMILTimeContainer::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 (nsSMILAnimationFunction).
//
// (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
nsAutoPtr<nsSMILCompositorTable>
currentCompositorTable(new nsSMILCompositorTable(0));
for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
SVGAnimationElement* animElem = iter.Get()->GetKey();
SampleTimedElement(animElem, &activeContainers);
AddAnimationToCompositorTable(animElem, currentCompositorTable);
}
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 (auto iter = currentCompositorTable->Iter();
!iter.Done();
iter.Next()) {
nsSMILCompositor* compositor = iter.Get();
nsSMILCompositor* lastCompositor =
mLastCompositorTable->GetEntry(compositor->GetKey());
if (lastCompositor) {
compositor->StealCachedBaseValue(lastCompositor);
}
}
// * 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 (auto iter = currentCompositorTable->Iter();
!iter.Done();
iter.Next()) {
mLastCompositorTable->RemoveEntry(iter.Get()->GetKey());
}
// * 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 (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
iter.Get()->ClearAnimationEffects();
}
}
// return early if there are no active animations to avoid a style flush
if (currentCompositorTable->Count() == 0) {
mLastCompositorTable = nullptr;
return;
}
nsCOMPtr<nsIDocument> kungFuDeathGrip(mDocument); // keeps 'this' alive too
mDocument->FlushPendingNotifications(Flush_Style);
// WARNING:
// WARNING: the above flush may have destroyed the pres shell and/or
// WARNING: frames and other layout related objects.
// WARNING:
// 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)
for (auto iter = currentCompositorTable->Iter(); !iter.Done(); iter.Next()) {
iter.Get()->ComposeAttribute();
}
// Update last compositor table
mLastCompositorTable = currentCompositorTable.forget();
mMightHavePendingStyleUpdates = true;
NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
}
void
nsSMILAnimationController::RewindElements()
{
bool rewindNeeded = false;
for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
nsSMILTimeContainer* container = iter.Get()->GetKey();
if (container->NeedsRewind()) {
rewindNeeded = true;
break;
}
}
if (!rewindNeeded)
return;
for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
SVGAnimationElement* animElem = iter.Get()->GetKey();
nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
if (timeContainer && timeContainer->NeedsRewind()) {
animElem->TimedElement().Rewind();
}
}
for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
iter.Get()->GetKey()->ClearNeedsRewind();
}
}
void
nsSMILAnimationController::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.
nsSMILTime sampleTime = INT64_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.
nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true);
for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
nsSMILTimeContainer* container = iter.Get()->GetKey();
if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) {
continue;
}
nsSMILMilestone thisMilestone;
bool didGetMilestone =
container->GetNextMilestoneInParentTime(thisMilestone);
if (didGetMilestone && thisMilestone < nextMilestone) {
nextMilestone = thisMilestone;
}
}
if (nextMilestone.mTime > GetCurrentTime()) {
break;
}
nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elements;
for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
nsSMILTimeContainer* container = iter.Get()->GetKey();
if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) {
continue;
}
container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
}
uint32_t length = elements.Length();
// 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 (uint32_t i = 0; i < length; ++i) {
SVGAnimationElement* elem = elements[i].get();
MOZ_ASSERT(elem, "nullptr animation element in list");
nsSMILTimeContainer* container = elem->GetTimeContainer();
if (!container)
// The container may be nullptr if the element has been detached from its
// parent since registering a milestone.
continue;
nsSMILTimeValue containerTimeValue =
container->ParentToContainerTime(sampleTime);
if (!containerTimeValue.IsDefinite())
continue;
// Clamp the converted container time to non-negative values.
nsSMILTime containerTime = std::max<nsSMILTime>(0, containerTimeValue.GetMillis());
if (nextMilestone.mIsEnd) {
elem->TimedElement().SampleEndAt(containerTime);
} else {
elem->TimedElement().SampleAt(containerTime);
}
}
}
}
/*static*/ void
nsSMILAnimationController::SampleTimedElement(
SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers)
{
nsSMILTimeContainer* 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;
nsSMILTime containerTime = timeContainer->GetCurrentTime();
MOZ_ASSERT(!timeContainer->IsSeeking(),
"Doing a regular sample but the time container is still seeking");
aElement->TimedElement().SampleAt(containerTime);
}
/*static*/ void
nsSMILAnimationController::AddAnimationToCompositorTable(
SVGAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable)
{
// Add a compositor to the hash table if there's not already one there
nsSMILTargetIdentifier key;
if (!GetTargetIdentifierForAnimation(aElement, key))
// Something's wrong/missing about animation's target; skip this animation
return;
nsSMILAnimationFunction& 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.
nsSMILCompositor* 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.
nsSMILCompositor* 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, nsIAtom *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 nsSMILTargetIdentifier
// for this target.
/*static*/ bool
nsSMILAnimationController::GetTargetIdentifierForAnimation(
SVGAnimationElement* aAnimElem, nsSMILTargetIdentifier& 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.
nsCOMPtr<nsIAtom> 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;
// Look up target (animated) attribute-type
nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
// Check if an 'auto' attributeType refers to a CSS property or XML attribute.
// Note that SMIL requires we search for CSS properties first. So if they
// overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
bool isCSS = false;
if (attributeType == eSMILTargetAttrType_auto) {
if (attributeNamespaceID == kNameSpaceID_None) {
// width/height are special as they may be attributes or for
// outer-<svg> elements, mapped into style.
if (attributeName == nsGkAtoms::width ||
attributeName == nsGkAtoms::height) {
isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
} else {
nsCSSProperty prop =
nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
nsCSSProps::eEnabledForAllContent);
isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
}
}
} else {
isCSS = (attributeType == eSMILTargetAttrType_CSS);
}
// Construct the key
aResult.mElement = targetElem;
aResult.mAttributeName = attributeName;
aResult.mAttributeNamespaceID = attributeNamespaceID;
aResult.mIsCSS = isCSS;
return true;
}
void
nsSMILAnimationController::AddStyleUpdatesTo(RestyleTracker& aTracker)
{
MOZ_ASSERT(mMightHavePendingStyleUpdates,
"Should only add style updates when we think we might have some");
for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
SVGAnimationElement* animElement = iter.Get()->GetKey();
nsSMILTargetIdentifier key;
if (!GetTargetIdentifierForAnimation(animElement, key)) {
// Something's wrong/missing about animation's target; skip this animation
continue;
}
// mIsCSS true means that the rules are the ones returned from
// Element::GetSMILOverrideStyleDeclaration (via nsSMILCSSProperty objects),
// and mIsCSS false means the rules are nsSMILMappedAttribute objects
// returned from nsSVGElement::GetAnimatedContentStyleRule.
nsRestyleHint rshint = key.mIsCSS ? eRestyle_StyleAttribute_Animations
: eRestyle_SVGAttrAnimations;
aTracker.AddPendingRestyle(key.mElement, rshint, nsChangeHint(0));
}
mMightHavePendingStyleUpdates = false;
}
//----------------------------------------------------------------------
// Add/remove child time containers
nsresult
nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
{
TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
if (!mPauseState && mChildContainerTable.Count() == 1) {
MaybeStartSampling(GetRefreshDriver());
Sample(); // Run the first sample manually
}
return NS_OK;
}
void
nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild)
{
mChildContainerTable.RemoveEntry(&aChild);
if (!mPauseState && mChildContainerTable.Count() == 0) {
StopSampling(GetRefreshDriver());
}
}
// Helper method
nsRefreshDriver*
nsSMILAnimationController::GetRefreshDriver()
{
if (!mDocument) {
NS_ERROR("Requesting refresh driver after document has disconnected!");
return nullptr;
}
nsIPresShell* shell = mDocument->GetShell();
if (!shell) {
return nullptr;
}
nsPresContext* context = shell->GetPresContext();
return context ? context->RefreshDriver() : nullptr;
}
void
nsSMILAnimationController::FlagDocumentNeedsFlush()
{
mDocument->SetNeedStyleFlush();
}