1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/smil/nsSMILAnimationController.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,870 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsSMILAnimationController.h" 1.10 +#include "nsSMILCompositor.h" 1.11 +#include "nsSMILCSSProperty.h" 1.12 +#include "nsCSSProps.h" 1.13 +#include "nsITimer.h" 1.14 +#include "mozilla/dom/Element.h" 1.15 +#include "nsIDocument.h" 1.16 +#include "mozilla/dom/SVGAnimationElement.h" 1.17 +#include "nsSMILTimedElement.h" 1.18 +#include <algorithm> 1.19 +#include "mozilla/AutoRestore.h" 1.20 + 1.21 +using namespace mozilla; 1.22 +using namespace mozilla::dom; 1.23 + 1.24 +//---------------------------------------------------------------------- 1.25 +// nsSMILAnimationController implementation 1.26 + 1.27 +//---------------------------------------------------------------------- 1.28 +// ctors, dtors, factory methods 1.29 + 1.30 +nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc) 1.31 + : mAvgTimeBetweenSamples(0), 1.32 + mResampleNeeded(false), 1.33 + mDeferredStartSampling(false), 1.34 + mRunningSample(false), 1.35 + mRegisteredWithRefreshDriver(false), 1.36 + mDocument(aDoc) 1.37 +{ 1.38 + NS_ABORT_IF_FALSE(aDoc, "need a non-null document"); 1.39 + 1.40 + nsRefreshDriver* refreshDriver = GetRefreshDriver(); 1.41 + if (refreshDriver) { 1.42 + mStartTime = refreshDriver->MostRecentRefresh(); 1.43 + } else { 1.44 + mStartTime = mozilla::TimeStamp::Now(); 1.45 + } 1.46 + mCurrentSampleTime = mStartTime; 1.47 + 1.48 + Begin(); 1.49 +} 1.50 + 1.51 +nsSMILAnimationController::~nsSMILAnimationController() 1.52 +{ 1.53 + NS_ASSERTION(mAnimationElementTable.Count() == 0, 1.54 + "Animation controller shouldn't be tracking any animation" 1.55 + " elements when it dies"); 1.56 + NS_ASSERTION(!mRegisteredWithRefreshDriver, 1.57 + "Leaving stale entry in refresh driver's observer list"); 1.58 +} 1.59 + 1.60 +void 1.61 +nsSMILAnimationController::Disconnect() 1.62 +{ 1.63 + NS_ABORT_IF_FALSE(mDocument, "disconnecting when we weren't connected...?"); 1.64 + NS_ABORT_IF_FALSE(mRefCnt.get() == 1, 1.65 + "Expecting to disconnect when doc is sole remaining owner"); 1.66 + NS_ASSERTION(mPauseState & nsSMILTimeContainer::PAUSE_PAGEHIDE, 1.67 + "Expecting to be paused for pagehide before disconnect"); 1.68 + 1.69 + StopSampling(GetRefreshDriver()); 1.70 + 1.71 + mDocument = nullptr; // (raw pointer) 1.72 +} 1.73 + 1.74 +//---------------------------------------------------------------------- 1.75 +// nsSMILTimeContainer methods: 1.76 + 1.77 +void 1.78 +nsSMILAnimationController::Pause(uint32_t aType) 1.79 +{ 1.80 + nsSMILTimeContainer::Pause(aType); 1.81 + 1.82 + if (mPauseState) { 1.83 + mDeferredStartSampling = false; 1.84 + StopSampling(GetRefreshDriver()); 1.85 + } 1.86 +} 1.87 + 1.88 +void 1.89 +nsSMILAnimationController::Resume(uint32_t aType) 1.90 +{ 1.91 + bool wasPaused = (mPauseState != 0); 1.92 + // Update mCurrentSampleTime so that calls to GetParentTime--used for 1.93 + // calculating parent offsets--are accurate 1.94 + mCurrentSampleTime = mozilla::TimeStamp::Now(); 1.95 + 1.96 + nsSMILTimeContainer::Resume(aType); 1.97 + 1.98 + if (wasPaused && !mPauseState && mChildContainerTable.Count()) { 1.99 + MaybeStartSampling(GetRefreshDriver()); 1.100 + Sample(); // Run the first sample manually 1.101 + } 1.102 +} 1.103 + 1.104 +nsSMILTime 1.105 +nsSMILAnimationController::GetParentTime() const 1.106 +{ 1.107 + return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds(); 1.108 +} 1.109 + 1.110 +//---------------------------------------------------------------------- 1.111 +// nsARefreshObserver methods: 1.112 +NS_IMPL_ADDREF(nsSMILAnimationController) 1.113 +NS_IMPL_RELEASE(nsSMILAnimationController) 1.114 + 1.115 +// nsRefreshDriver Callback function 1.116 +void 1.117 +nsSMILAnimationController::WillRefresh(mozilla::TimeStamp aTime) 1.118 +{ 1.119 + // Although we never expect aTime to go backwards, when we initialise the 1.120 + // animation controller, if we can't get hold of a refresh driver we 1.121 + // initialise mCurrentSampleTime to Now(). It may be possible that after 1.122 + // doing so we get sampled by a refresh driver whose most recent refresh time 1.123 + // predates when we were initialised, so to be safe we make sure to take the 1.124 + // most recent time here. 1.125 + aTime = std::max(mCurrentSampleTime, aTime); 1.126 + 1.127 + // Sleep detection: If the time between samples is a whole lot greater than we 1.128 + // were expecting then we assume the computer went to sleep or someone's 1.129 + // messing with the clock. In that case, fiddle our parent offset and use our 1.130 + // average time between samples to calculate the new sample time. This 1.131 + // prevents us from hanging while trying to catch up on all the missed time. 1.132 + 1.133 + // Smoothing of coefficient for the average function. 0.2 should let us track 1.134 + // the sample rate reasonably tightly without being overly affected by 1.135 + // occasional delays. 1.136 + static const double SAMPLE_DUR_WEIGHTING = 0.2; 1.137 + // If the elapsed time exceeds our expectation by this number of times we'll 1.138 + // initiate special behaviour to basically ignore the intervening time. 1.139 + static const double SAMPLE_DEV_THRESHOLD = 200.0; 1.140 + 1.141 + nsSMILTime elapsedTime = 1.142 + (nsSMILTime)(aTime - mCurrentSampleTime).ToMilliseconds(); 1.143 + if (mAvgTimeBetweenSamples == 0) { 1.144 + // First sample. 1.145 + mAvgTimeBetweenSamples = elapsedTime; 1.146 + } else { 1.147 + if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) { 1.148 + // Unexpectedly long delay between samples. 1.149 + NS_WARNING("Detected really long delay between samples, continuing from " 1.150 + "previous sample"); 1.151 + mParentOffset += elapsedTime - mAvgTimeBetweenSamples; 1.152 + } 1.153 + // Update the moving average. Due to truncation here the average will 1.154 + // normally be a little less than it should be but that's probably ok. 1.155 + mAvgTimeBetweenSamples = 1.156 + (nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING + 1.157 + mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING)); 1.158 + } 1.159 + mCurrentSampleTime = aTime; 1.160 + 1.161 + Sample(); 1.162 +} 1.163 + 1.164 +//---------------------------------------------------------------------- 1.165 +// Animation element registration methods: 1.166 + 1.167 +void 1.168 +nsSMILAnimationController::RegisterAnimationElement( 1.169 + SVGAnimationElement* aAnimationElement) 1.170 +{ 1.171 + mAnimationElementTable.PutEntry(aAnimationElement); 1.172 + if (mDeferredStartSampling) { 1.173 + mDeferredStartSampling = false; 1.174 + if (mChildContainerTable.Count()) { 1.175 + // mAnimationElementTable was empty, but now we've added its 1st element 1.176 + NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1, 1.177 + "we shouldn't have deferred sampling if we already had " 1.178 + "animations registered"); 1.179 + StartSampling(GetRefreshDriver()); 1.180 + Sample(); // Run the first sample manually 1.181 + } // else, don't sample until a time container is registered (via AddChild) 1.182 + } 1.183 +} 1.184 + 1.185 +void 1.186 +nsSMILAnimationController::UnregisterAnimationElement( 1.187 + SVGAnimationElement* aAnimationElement) 1.188 +{ 1.189 + mAnimationElementTable.RemoveEntry(aAnimationElement); 1.190 +} 1.191 + 1.192 +//---------------------------------------------------------------------- 1.193 +// Page show/hide 1.194 + 1.195 +void 1.196 +nsSMILAnimationController::OnPageShow() 1.197 +{ 1.198 + Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE); 1.199 +} 1.200 + 1.201 +void 1.202 +nsSMILAnimationController::OnPageHide() 1.203 +{ 1.204 + Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE); 1.205 +} 1.206 + 1.207 +//---------------------------------------------------------------------- 1.208 +// Cycle-collection support 1.209 + 1.210 +void 1.211 +nsSMILAnimationController::Traverse( 1.212 + nsCycleCollectionTraversalCallback* aCallback) 1.213 +{ 1.214 + // Traverse last compositor table 1.215 + if (mLastCompositorTable) { 1.216 + mLastCompositorTable->EnumerateEntries(CompositorTableEntryTraverse, 1.217 + aCallback); 1.218 + } 1.219 +} 1.220 + 1.221 +/*static*/ PLDHashOperator 1.222 +nsSMILAnimationController::CompositorTableEntryTraverse( 1.223 + nsSMILCompositor* aCompositor, 1.224 + void* aArg) 1.225 +{ 1.226 + nsCycleCollectionTraversalCallback* cb = 1.227 + static_cast<nsCycleCollectionTraversalCallback*>(aArg); 1.228 + aCompositor->Traverse(cb); 1.229 + return PL_DHASH_NEXT; 1.230 +} 1.231 + 1.232 +void 1.233 +nsSMILAnimationController::Unlink() 1.234 +{ 1.235 + mLastCompositorTable = nullptr; 1.236 +} 1.237 + 1.238 +//---------------------------------------------------------------------- 1.239 +// Refresh driver lifecycle related methods 1.240 + 1.241 +void 1.242 +nsSMILAnimationController::NotifyRefreshDriverCreated( 1.243 + nsRefreshDriver* aRefreshDriver) 1.244 +{ 1.245 + if (!mPauseState) { 1.246 + MaybeStartSampling(aRefreshDriver); 1.247 + } 1.248 +} 1.249 + 1.250 +void 1.251 +nsSMILAnimationController::NotifyRefreshDriverDestroying( 1.252 + nsRefreshDriver* aRefreshDriver) 1.253 +{ 1.254 + if (!mPauseState && !mDeferredStartSampling) { 1.255 + StopSampling(aRefreshDriver); 1.256 + } 1.257 +} 1.258 + 1.259 +//---------------------------------------------------------------------- 1.260 +// Timer-related implementation helpers 1.261 + 1.262 +void 1.263 +nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver) 1.264 +{ 1.265 + NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused"); 1.266 + NS_ASSERTION(!mDeferredStartSampling, 1.267 + "Started sampling but the deferred start flag is still set"); 1.268 + if (aRefreshDriver) { 1.269 + MOZ_ASSERT(!mRegisteredWithRefreshDriver, 1.270 + "Redundantly registering with refresh driver"); 1.271 + NS_ABORT_IF_FALSE(!GetRefreshDriver() || 1.272 + aRefreshDriver == GetRefreshDriver(), 1.273 + "Starting sampling with wrong refresh driver"); 1.274 + // We're effectively resuming from a pause so update our current sample time 1.275 + // or else it will confuse our "average time between samples" calculations. 1.276 + mCurrentSampleTime = mozilla::TimeStamp::Now(); 1.277 + aRefreshDriver->AddRefreshObserver(this, Flush_Style); 1.278 + mRegisteredWithRefreshDriver = true; 1.279 + } 1.280 +} 1.281 + 1.282 +void 1.283 +nsSMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver) 1.284 +{ 1.285 + if (aRefreshDriver && mRegisteredWithRefreshDriver) { 1.286 + // NOTE: The document might already have been detached from its PresContext 1.287 + // (and RefreshDriver), which would make GetRefreshDriver() return null. 1.288 + NS_ABORT_IF_FALSE(!GetRefreshDriver() || 1.289 + aRefreshDriver == GetRefreshDriver(), 1.290 + "Stopping sampling with wrong refresh driver"); 1.291 + aRefreshDriver->RemoveRefreshObserver(this, Flush_Style); 1.292 + mRegisteredWithRefreshDriver = false; 1.293 + } 1.294 +} 1.295 + 1.296 +void 1.297 +nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver) 1.298 +{ 1.299 + if (mDeferredStartSampling) { 1.300 + // We've received earlier 'MaybeStartSampling' calls, and we're 1.301 + // deferring until we get a registered animation. 1.302 + return; 1.303 + } 1.304 + 1.305 + if (mAnimationElementTable.Count()) { 1.306 + StartSampling(aRefreshDriver); 1.307 + } else { 1.308 + mDeferredStartSampling = true; 1.309 + } 1.310 +} 1.311 + 1.312 +//---------------------------------------------------------------------- 1.313 +// Sample-related methods and callbacks 1.314 + 1.315 +PLDHashOperator 1.316 +TransferCachedBaseValue(nsSMILCompositor* aCompositor, 1.317 + void* aData) 1.318 +{ 1.319 + nsSMILCompositorTable* lastCompositorTable = 1.320 + static_cast<nsSMILCompositorTable*>(aData); 1.321 + nsSMILCompositor* lastCompositor = 1.322 + lastCompositorTable->GetEntry(aCompositor->GetKey()); 1.323 + 1.324 + if (lastCompositor) { 1.325 + aCompositor->StealCachedBaseValue(lastCompositor); 1.326 + } 1.327 + 1.328 + return PL_DHASH_NEXT; 1.329 +} 1.330 + 1.331 +PLDHashOperator 1.332 +RemoveCompositorFromTable(nsSMILCompositor* aCompositor, 1.333 + void* aData) 1.334 +{ 1.335 + nsSMILCompositorTable* lastCompositorTable = 1.336 + static_cast<nsSMILCompositorTable*>(aData); 1.337 + lastCompositorTable->RemoveEntry(aCompositor->GetKey()); 1.338 + return PL_DHASH_NEXT; 1.339 +} 1.340 + 1.341 +PLDHashOperator 1.342 +DoClearAnimationEffects(nsSMILCompositor* aCompositor, 1.343 + void* /*aData*/) 1.344 +{ 1.345 + aCompositor->ClearAnimationEffects(); 1.346 + return PL_DHASH_NEXT; 1.347 +} 1.348 + 1.349 +PLDHashOperator 1.350 +DoComposeAttribute(nsSMILCompositor* aCompositor, 1.351 + void* /*aData*/) 1.352 +{ 1.353 + aCompositor->ComposeAttribute(); 1.354 + return PL_DHASH_NEXT; 1.355 +} 1.356 + 1.357 +void 1.358 +nsSMILAnimationController::DoSample() 1.359 +{ 1.360 + DoSample(true); // Skip unchanged time containers 1.361 +} 1.362 + 1.363 +void 1.364 +nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers) 1.365 +{ 1.366 + if (!mDocument) { 1.367 + NS_ERROR("Shouldn't be sampling after document has disconnected"); 1.368 + return; 1.369 + } 1.370 + if (mRunningSample) { 1.371 + NS_ERROR("Shouldn't be recursively sampling"); 1.372 + return; 1.373 + } 1.374 + 1.375 + mResampleNeeded = false; 1.376 + // Set running sample flag -- do this before flushing styles so that when we 1.377 + // flush styles we don't end up requesting extra samples 1.378 + AutoRestore<bool> autoRestoreRunningSample(mRunningSample); 1.379 + mRunningSample = true; 1.380 + 1.381 + // STEP 1: Bring model up to date 1.382 + // (i) Rewind elements where necessary 1.383 + // (ii) Run milestone samples 1.384 + RewindElements(); 1.385 + DoMilestoneSamples(); 1.386 + 1.387 + // STEP 2: Sample the child time containers 1.388 + // 1.389 + // When we sample the child time containers they will simply record the sample 1.390 + // time in document time. 1.391 + TimeContainerHashtable activeContainers(mChildContainerTable.Count()); 1.392 + SampleTimeContainerParams tcParams = { &activeContainers, 1.393 + aSkipUnchangedContainers }; 1.394 + mChildContainerTable.EnumerateEntries(SampleTimeContainer, &tcParams); 1.395 + 1.396 + // STEP 3: (i) Sample the timed elements AND 1.397 + // (ii) Create a table of compositors 1.398 + // 1.399 + // (i) Here we sample the timed elements (fetched from the 1.400 + // SVGAnimationElements) which determine from the active time if the 1.401 + // element is active and what its simple time etc. is. This information is 1.402 + // then passed to its time client (nsSMILAnimationFunction). 1.403 + // 1.404 + // (ii) During the same loop we also build up a table that contains one 1.405 + // compositor for each animated attribute and which maps animated elements to 1.406 + // the corresponding compositor for their target attribute. 1.407 + // 1.408 + // Note that this compositor table needs to be allocated on the heap so we can 1.409 + // store it until the next sample. This lets us find out which elements were 1.410 + // animated in sample 'n-1' but not in sample 'n' (and hence need to have 1.411 + // their animation effects removed in sample 'n'). 1.412 + // 1.413 + // Parts (i) and (ii) are not functionally related but we combine them here to 1.414 + // save iterating over the animation elements twice. 1.415 + 1.416 + // Create the compositor table 1.417 + nsAutoPtr<nsSMILCompositorTable> 1.418 + currentCompositorTable(new nsSMILCompositorTable(0)); 1.419 + 1.420 + SampleAnimationParams saParams = { &activeContainers, 1.421 + currentCompositorTable }; 1.422 + mAnimationElementTable.EnumerateEntries(SampleAnimation, 1.423 + &saParams); 1.424 + activeContainers.Clear(); 1.425 + 1.426 + // STEP 4: Compare previous sample's compositors against this sample's. 1.427 + // (Transfer cached base values across, & remove animation effects from 1.428 + // no-longer-animated targets.) 1.429 + if (mLastCompositorTable) { 1.430 + // * Transfer over cached base values, from last sample's compositors 1.431 + currentCompositorTable->EnumerateEntries(TransferCachedBaseValue, 1.432 + mLastCompositorTable); 1.433 + 1.434 + // * For each compositor in current sample's hash table, remove entry from 1.435 + // prev sample's hash table -- we don't need to clear animation 1.436 + // effects of those compositors, since they're still being animated. 1.437 + currentCompositorTable->EnumerateEntries(RemoveCompositorFromTable, 1.438 + mLastCompositorTable); 1.439 + 1.440 + // * For each entry that remains in prev sample's hash table (i.e. for 1.441 + // every target that's no longer animated), clear animation effects. 1.442 + mLastCompositorTable->EnumerateEntries(DoClearAnimationEffects, nullptr); 1.443 + } 1.444 + 1.445 + // return early if there are no active animations to avoid a style flush 1.446 + if (currentCompositorTable->Count() == 0) { 1.447 + mLastCompositorTable = nullptr; 1.448 + return; 1.449 + } 1.450 + 1.451 + nsCOMPtr<nsIDocument> kungFuDeathGrip(mDocument); // keeps 'this' alive too 1.452 + mDocument->FlushPendingNotifications(Flush_Style); 1.453 + 1.454 + // WARNING: 1.455 + // WARNING: the above flush may have destroyed the pres shell and/or 1.456 + // WARNING: frames and other layout related objects. 1.457 + // WARNING: 1.458 + 1.459 + // STEP 5: Compose currently-animated attributes. 1.460 + // XXXdholbert: This step traverses our animation targets in an effectively 1.461 + // random order. For animation from/to 'inherit' values to work correctly 1.462 + // when the inherited value is *also* being animated, we really should be 1.463 + // traversing our animated nodes in an ancestors-first order (bug 501183) 1.464 + currentCompositorTable->EnumerateEntries(DoComposeAttribute, nullptr); 1.465 + 1.466 + // Update last compositor table 1.467 + mLastCompositorTable = currentCompositorTable.forget(); 1.468 + 1.469 + NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!"); 1.470 +} 1.471 + 1.472 +void 1.473 +nsSMILAnimationController::RewindElements() 1.474 +{ 1.475 + bool rewindNeeded = false; 1.476 + mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded); 1.477 + if (!rewindNeeded) 1.478 + return; 1.479 + 1.480 + mAnimationElementTable.EnumerateEntries(RewindAnimation, nullptr); 1.481 + mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nullptr); 1.482 +} 1.483 + 1.484 +/*static*/ PLDHashOperator 1.485 +nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey, 1.486 + void* aData) 1.487 +{ 1.488 + NS_ABORT_IF_FALSE(aData, 1.489 + "Null data pointer during time container enumeration"); 1.490 + bool* rewindNeeded = static_cast<bool*>(aData); 1.491 + 1.492 + nsSMILTimeContainer* container = aKey->GetKey(); 1.493 + if (container->NeedsRewind()) { 1.494 + *rewindNeeded = true; 1.495 + return PL_DHASH_STOP; 1.496 + } 1.497 + 1.498 + return PL_DHASH_NEXT; 1.499 +} 1.500 + 1.501 +/*static*/ PLDHashOperator 1.502 +nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey, 1.503 + void* aData) 1.504 +{ 1.505 + SVGAnimationElement* animElem = aKey->GetKey(); 1.506 + nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer(); 1.507 + if (timeContainer && timeContainer->NeedsRewind()) { 1.508 + animElem->TimedElement().Rewind(); 1.509 + } 1.510 + 1.511 + return PL_DHASH_NEXT; 1.512 +} 1.513 + 1.514 +/*static*/ PLDHashOperator 1.515 +nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey, 1.516 + void* aData) 1.517 +{ 1.518 + aKey->GetKey()->ClearNeedsRewind(); 1.519 + return PL_DHASH_NEXT; 1.520 +} 1.521 + 1.522 +void 1.523 +nsSMILAnimationController::DoMilestoneSamples() 1.524 +{ 1.525 + // We need to sample the timing model but because SMIL operates independently 1.526 + // of the frame-rate, we can get one sample at t=0s and the next at t=10min. 1.527 + // 1.528 + // In between those two sample times a whole string of significant events 1.529 + // might be expected to take place: events firing, new interdependencies 1.530 + // between animations resolved and dissolved, etc. 1.531 + // 1.532 + // Furthermore, at any given time, we want to sample all the intervals that 1.533 + // end at that time BEFORE any that begin. This behaviour is implied by SMIL's 1.534 + // endpoint-exclusive timing model. 1.535 + // 1.536 + // So we have the animations (specifically the timed elements) register the 1.537 + // next significant moment (called a milestone) in their lifetime and then we 1.538 + // step through the model at each of these moments and sample those animations 1.539 + // registered for those times. This way events can fire in the correct order, 1.540 + // dependencies can be resolved etc. 1.541 + 1.542 + nsSMILTime sampleTime = INT64_MIN; 1.543 + 1.544 + while (true) { 1.545 + // We want to find any milestones AT OR BEFORE the current sample time so we 1.546 + // initialise the next milestone to the moment after (1ms after, to be 1.547 + // precise) the current sample time and see if there are any milestones 1.548 + // before that. Any other milestones will be dealt with in a subsequent 1.549 + // sample. 1.550 + nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true); 1.551 + mChildContainerTable.EnumerateEntries(GetNextMilestone, &nextMilestone); 1.552 + 1.553 + if (nextMilestone.mTime > GetCurrentTime()) { 1.554 + break; 1.555 + } 1.556 + 1.557 + GetMilestoneElementsParams params; 1.558 + params.mMilestone = nextMilestone; 1.559 + mChildContainerTable.EnumerateEntries(GetMilestoneElements, ¶ms); 1.560 + uint32_t length = params.mElements.Length(); 1.561 + 1.562 + // During the course of a sampling we don't want to actually go backwards. 1.563 + // Due to negative offsets, early ends and the like, a timed element might 1.564 + // register a milestone that is actually in the past. That's fine, but it's 1.565 + // still only going to get *sampled* with whatever time we're up to and no 1.566 + // earlier. 1.567 + // 1.568 + // Because we're only performing this clamping at the last moment, the 1.569 + // animations will still all get sampled in the correct order and 1.570 + // dependencies will be appropriately resolved. 1.571 + sampleTime = std::max(nextMilestone.mTime, sampleTime); 1.572 + 1.573 + for (uint32_t i = 0; i < length; ++i) { 1.574 + SVGAnimationElement* elem = params.mElements[i].get(); 1.575 + NS_ABORT_IF_FALSE(elem, "nullptr animation element in list"); 1.576 + nsSMILTimeContainer* container = elem->GetTimeContainer(); 1.577 + if (!container) 1.578 + // The container may be nullptr if the element has been detached from its 1.579 + // parent since registering a milestone. 1.580 + continue; 1.581 + 1.582 + nsSMILTimeValue containerTimeValue = 1.583 + container->ParentToContainerTime(sampleTime); 1.584 + if (!containerTimeValue.IsDefinite()) 1.585 + continue; 1.586 + 1.587 + // Clamp the converted container time to non-negative values. 1.588 + nsSMILTime containerTime = std::max<nsSMILTime>(0, containerTimeValue.GetMillis()); 1.589 + 1.590 + if (nextMilestone.mIsEnd) { 1.591 + elem->TimedElement().SampleEndAt(containerTime); 1.592 + } else { 1.593 + elem->TimedElement().SampleAt(containerTime); 1.594 + } 1.595 + } 1.596 + } 1.597 +} 1.598 + 1.599 +/*static*/ PLDHashOperator 1.600 +nsSMILAnimationController::GetNextMilestone(TimeContainerPtrKey* aKey, 1.601 + void* aData) 1.602 +{ 1.603 + NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table"); 1.604 + NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table"); 1.605 + NS_ABORT_IF_FALSE(aData, 1.606 + "Null data pointer during time container enumeration"); 1.607 + 1.608 + nsSMILMilestone* nextMilestone = static_cast<nsSMILMilestone*>(aData); 1.609 + 1.610 + nsSMILTimeContainer* container = aKey->GetKey(); 1.611 + if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) 1.612 + return PL_DHASH_NEXT; 1.613 + 1.614 + nsSMILMilestone thisMilestone; 1.615 + bool didGetMilestone = 1.616 + container->GetNextMilestoneInParentTime(thisMilestone); 1.617 + if (didGetMilestone && thisMilestone < *nextMilestone) { 1.618 + *nextMilestone = thisMilestone; 1.619 + } 1.620 + 1.621 + return PL_DHASH_NEXT; 1.622 +} 1.623 + 1.624 +/*static*/ PLDHashOperator 1.625 +nsSMILAnimationController::GetMilestoneElements(TimeContainerPtrKey* aKey, 1.626 + void* aData) 1.627 +{ 1.628 + NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table"); 1.629 + NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table"); 1.630 + NS_ABORT_IF_FALSE(aData, 1.631 + "Null data pointer during time container enumeration"); 1.632 + 1.633 + GetMilestoneElementsParams* params = 1.634 + static_cast<GetMilestoneElementsParams*>(aData); 1.635 + 1.636 + nsSMILTimeContainer* container = aKey->GetKey(); 1.637 + if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) 1.638 + return PL_DHASH_NEXT; 1.639 + 1.640 + container->PopMilestoneElementsAtMilestone(params->mMilestone, 1.641 + params->mElements); 1.642 + 1.643 + return PL_DHASH_NEXT; 1.644 +} 1.645 + 1.646 +/*static*/ PLDHashOperator 1.647 +nsSMILAnimationController::SampleTimeContainer(TimeContainerPtrKey* aKey, 1.648 + void* aData) 1.649 +{ 1.650 + NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT); 1.651 + NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT); 1.652 + NS_ENSURE_TRUE(aData, PL_DHASH_NEXT); 1.653 + 1.654 + SampleTimeContainerParams* params = 1.655 + static_cast<SampleTimeContainerParams*>(aData); 1.656 + 1.657 + nsSMILTimeContainer* container = aKey->GetKey(); 1.658 + if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) && 1.659 + (container->NeedsSample() || !params->mSkipUnchangedContainers)) { 1.660 + container->ClearMilestones(); 1.661 + container->Sample(); 1.662 + container->MarkSeekFinished(); 1.663 + params->mActiveContainers->PutEntry(container); 1.664 + } 1.665 + 1.666 + return PL_DHASH_NEXT; 1.667 +} 1.668 + 1.669 +/*static*/ PLDHashOperator 1.670 +nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey, 1.671 + void* aData) 1.672 +{ 1.673 + NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT); 1.674 + NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT); 1.675 + NS_ENSURE_TRUE(aData, PL_DHASH_NEXT); 1.676 + 1.677 + SVGAnimationElement* animElem = aKey->GetKey(); 1.678 + if (animElem->PassesConditionalProcessingTests()) { 1.679 + SampleAnimationParams* params = static_cast<SampleAnimationParams*>(aData); 1.680 + 1.681 + SampleTimedElement(animElem, params->mActiveContainers); 1.682 + AddAnimationToCompositorTable(animElem, params->mCompositorTable); 1.683 + } 1.684 + 1.685 + return PL_DHASH_NEXT; 1.686 +} 1.687 + 1.688 +/*static*/ void 1.689 +nsSMILAnimationController::SampleTimedElement( 1.690 + SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) 1.691 +{ 1.692 + nsSMILTimeContainer* timeContainer = aElement->GetTimeContainer(); 1.693 + if (!timeContainer) 1.694 + return; 1.695 + 1.696 + // We'd like to call timeContainer->NeedsSample() here and skip all timed 1.697 + // elements that belong to paused time containers that don't need a sample, 1.698 + // but that doesn't work because we've already called Sample() on all the time 1.699 + // containers so the paused ones don't need a sample any more and they'll 1.700 + // return false. 1.701 + // 1.702 + // Instead we build up a hashmap of active time containers during the previous 1.703 + // step (SampleTimeContainer) and then test here if the container for this 1.704 + // timed element is in the list. 1.705 + if (!aActiveContainers->GetEntry(timeContainer)) 1.706 + return; 1.707 + 1.708 + nsSMILTime containerTime = timeContainer->GetCurrentTime(); 1.709 + 1.710 + NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(), 1.711 + "Doing a regular sample but the time container is still seeking"); 1.712 + aElement->TimedElement().SampleAt(containerTime); 1.713 +} 1.714 + 1.715 +/*static*/ void 1.716 +nsSMILAnimationController::AddAnimationToCompositorTable( 1.717 + SVGAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable) 1.718 +{ 1.719 + // Add a compositor to the hash table if there's not already one there 1.720 + nsSMILTargetIdentifier key; 1.721 + if (!GetTargetIdentifierForAnimation(aElement, key)) 1.722 + // Something's wrong/missing about animation's target; skip this animation 1.723 + return; 1.724 + 1.725 + nsSMILAnimationFunction& func = aElement->AnimationFunction(); 1.726 + 1.727 + // Only add active animation functions. If there are no active animations 1.728 + // targeting an attribute, no compositor will be created and any previously 1.729 + // applied animations will be cleared. 1.730 + if (func.IsActiveOrFrozen()) { 1.731 + // Look up the compositor for our target, & add our animation function 1.732 + // to its list of animation functions. 1.733 + nsSMILCompositor* result = aCompositorTable->PutEntry(key); 1.734 + result->AddAnimationFunction(&func); 1.735 + 1.736 + } else if (func.HasChanged()) { 1.737 + // Look up the compositor for our target, and force it to skip the 1.738 + // "nothing's changed so don't bother compositing" optimization for this 1.739 + // sample. |func| is inactive, but it's probably *newly* inactive (since 1.740 + // it's got HasChanged() == true), so we need to make sure to recompose 1.741 + // its target. 1.742 + nsSMILCompositor* result = aCompositorTable->PutEntry(key); 1.743 + result->ToggleForceCompositing(); 1.744 + 1.745 + // We've now made sure that |func|'s inactivity will be reflected as of 1.746 + // this sample. We need to clear its HasChanged() flag so that it won't 1.747 + // trigger this same clause in future samples (until it changes again). 1.748 + func.ClearHasChanged(); 1.749 + } 1.750 +} 1.751 + 1.752 +static inline bool 1.753 +IsTransformAttribute(int32_t aNamespaceID, nsIAtom *aAttributeName) 1.754 +{ 1.755 + return aNamespaceID == kNameSpaceID_None && 1.756 + (aAttributeName == nsGkAtoms::transform || 1.757 + aAttributeName == nsGkAtoms::patternTransform || 1.758 + aAttributeName == nsGkAtoms::gradientTransform); 1.759 +} 1.760 + 1.761 +// Helper function that, given a SVGAnimationElement, looks up its target 1.762 +// element & target attribute and populates a nsSMILTargetIdentifier 1.763 +// for this target. 1.764 +/*static*/ bool 1.765 +nsSMILAnimationController::GetTargetIdentifierForAnimation( 1.766 + SVGAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult) 1.767 +{ 1.768 + // Look up target (animated) element 1.769 + Element* targetElem = aAnimElem->GetTargetElementContent(); 1.770 + if (!targetElem) 1.771 + // Animation has no target elem -- skip it. 1.772 + return false; 1.773 + 1.774 + // Look up target (animated) attribute 1.775 + // SMILANIM section 3.1, attributeName may 1.776 + // have an XMLNS prefix to indicate the XML namespace. 1.777 + nsCOMPtr<nsIAtom> attributeName; 1.778 + int32_t attributeNamespaceID; 1.779 + if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID, 1.780 + getter_AddRefs(attributeName))) 1.781 + // Animation has no target attr -- skip it. 1.782 + return false; 1.783 + 1.784 + // animateTransform can only animate transforms, conversely transforms 1.785 + // can only be animated by animateTransform 1.786 + if (IsTransformAttribute(attributeNamespaceID, attributeName) != 1.787 + (aAnimElem->Tag() == nsGkAtoms::animateTransform)) 1.788 + return false; 1.789 + 1.790 + // Look up target (animated) attribute-type 1.791 + nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType(); 1.792 + 1.793 + // Check if an 'auto' attributeType refers to a CSS property or XML attribute. 1.794 + // Note that SMIL requires we search for CSS properties first. So if they 1.795 + // overlap, 'auto' = 'CSS'. (SMILANIM 3.1) 1.796 + bool isCSS = false; 1.797 + if (attributeType == eSMILTargetAttrType_auto) { 1.798 + if (attributeNamespaceID == kNameSpaceID_None) { 1.799 + // width/height are special as they may be attributes or for 1.800 + // outer-<svg> elements, mapped into style. 1.801 + if (attributeName == nsGkAtoms::width || 1.802 + attributeName == nsGkAtoms::height) { 1.803 + isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG; 1.804 + } else { 1.805 + nsCSSProperty prop = 1.806 + nsCSSProps::LookupProperty(nsDependentAtomString(attributeName), 1.807 + nsCSSProps::eEnabledForAllContent); 1.808 + isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop); 1.809 + } 1.810 + } 1.811 + } else { 1.812 + isCSS = (attributeType == eSMILTargetAttrType_CSS); 1.813 + } 1.814 + 1.815 + // Construct the key 1.816 + aResult.mElement = targetElem; 1.817 + aResult.mAttributeName = attributeName; 1.818 + aResult.mAttributeNamespaceID = attributeNamespaceID; 1.819 + aResult.mIsCSS = isCSS; 1.820 + 1.821 + return true; 1.822 +} 1.823 + 1.824 +//---------------------------------------------------------------------- 1.825 +// Add/remove child time containers 1.826 + 1.827 +nsresult 1.828 +nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild) 1.829 +{ 1.830 + TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild); 1.831 + NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY); 1.832 + 1.833 + if (!mPauseState && mChildContainerTable.Count() == 1) { 1.834 + MaybeStartSampling(GetRefreshDriver()); 1.835 + Sample(); // Run the first sample manually 1.836 + } 1.837 + 1.838 + return NS_OK; 1.839 +} 1.840 + 1.841 +void 1.842 +nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild) 1.843 +{ 1.844 + mChildContainerTable.RemoveEntry(&aChild); 1.845 + 1.846 + if (!mPauseState && mChildContainerTable.Count() == 0) { 1.847 + StopSampling(GetRefreshDriver()); 1.848 + } 1.849 +} 1.850 + 1.851 +// Helper method 1.852 +nsRefreshDriver* 1.853 +nsSMILAnimationController::GetRefreshDriver() 1.854 +{ 1.855 + if (!mDocument) { 1.856 + NS_ERROR("Requesting refresh driver after document has disconnected!"); 1.857 + return nullptr; 1.858 + } 1.859 + 1.860 + nsIPresShell* shell = mDocument->GetShell(); 1.861 + if (!shell) { 1.862 + return nullptr; 1.863 + } 1.864 + 1.865 + nsPresContext* context = shell->GetPresContext(); 1.866 + return context ? context->RefreshDriver() : nullptr; 1.867 +} 1.868 + 1.869 +void 1.870 +nsSMILAnimationController::FlagDocumentNeedsFlush() 1.871 +{ 1.872 + mDocument->SetNeedStyleFlush(); 1.873 +}