1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "SMILAnimationController.h"
8
9 #include <algorithm>
10
11 #include "mozilla/AutoRestore.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/PresShellInlines.h"
14 #include "mozilla/RestyleManager.h"
15 #include "mozilla/SMILTimedElement.h"
16 #include "mozilla/dom/DocumentInlines.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/SVGAnimationElement.h"
19 #include "nsContentUtils.h"
20 #include "nsCSSProps.h"
21 #include "nsRefreshDriver.h"
22 #include "mozilla/dom/Document.h"
23 #include "SMILCompositor.h"
24 #include "SMILCSSProperty.h"
25
26 using namespace mozilla::dom;
27
28 namespace mozilla {
29
30 //----------------------------------------------------------------------
31 // SMILAnimationController implementation
32
33 //----------------------------------------------------------------------
34 // ctors, dtors, factory methods
35
SMILAnimationController(Document * aDoc)36 SMILAnimationController::SMILAnimationController(Document* aDoc)
37 : mAvgTimeBetweenSamples(0),
38 mResampleNeeded(false),
39 mDeferredStartSampling(false),
40 mRunningSample(false),
41 mRegisteredWithRefreshDriver(false),
42 mMightHavePendingStyleUpdates(false),
43 mDocument(aDoc) {
44 MOZ_ASSERT(aDoc, "need a non-null document");
45
46 nsRefreshDriver* refreshDriver = GetRefreshDriver();
47 if (refreshDriver) {
48 mStartTime = refreshDriver->MostRecentRefresh();
49 } else {
50 mStartTime = mozilla::TimeStamp::Now();
51 }
52 mCurrentSampleTime = mStartTime;
53
54 Begin();
55 }
56
~SMILAnimationController()57 SMILAnimationController::~SMILAnimationController() {
58 NS_ASSERTION(mAnimationElementTable.Count() == 0,
59 "Animation controller shouldn't be tracking any animation"
60 " elements when it dies");
61 NS_ASSERTION(!mRegisteredWithRefreshDriver,
62 "Leaving stale entry in refresh driver's observer list");
63 }
64
Disconnect()65 void SMILAnimationController::Disconnect() {
66 MOZ_ASSERT(mDocument, "disconnecting when we weren't connected...?");
67 MOZ_ASSERT(mRefCnt.get() == 1,
68 "Expecting to disconnect when doc is sole remaining owner");
69 NS_ASSERTION(mPauseState & SMILTimeContainer::PAUSE_PAGEHIDE,
70 "Expecting to be paused for pagehide before disconnect");
71
72 StopSampling(GetRefreshDriver());
73
74 mDocument = nullptr; // (raw pointer)
75 }
76
77 //----------------------------------------------------------------------
78 // SMILTimeContainer methods:
79
Pause(uint32_t aType)80 void SMILAnimationController::Pause(uint32_t aType) {
81 SMILTimeContainer::Pause(aType);
82
83 if (mPauseState) {
84 mDeferredStartSampling = false;
85 StopSampling(GetRefreshDriver());
86 }
87 }
88
Resume(uint32_t aType)89 void SMILAnimationController::Resume(uint32_t aType) {
90 bool wasPaused = (mPauseState != 0);
91 // Update mCurrentSampleTime so that calls to GetParentTime--used for
92 // calculating parent offsets--are accurate
93 mCurrentSampleTime = mozilla::TimeStamp::Now();
94
95 SMILTimeContainer::Resume(aType);
96
97 if (wasPaused && !mPauseState && !mChildContainerTable.IsEmpty()) {
98 MaybeStartSampling(GetRefreshDriver());
99 Sample(); // Run the first sample manually
100 }
101 }
102
GetParentTime() const103 SMILTime SMILAnimationController::GetParentTime() const {
104 return (SMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
105 }
106
107 //----------------------------------------------------------------------
108 // nsARefreshObserver methods:
109 NS_IMPL_ADDREF(SMILAnimationController)
NS_IMPL_RELEASE(SMILAnimationController)110 NS_IMPL_RELEASE(SMILAnimationController)
111
112 // nsRefreshDriver Callback function
113 void SMILAnimationController::WillRefresh(mozilla::TimeStamp aTime) {
114 // Although we never expect aTime to go backwards, when we initialise the
115 // animation controller, if we can't get hold of a refresh driver we
116 // initialise mCurrentSampleTime to Now(). It may be possible that after
117 // doing so we get sampled by a refresh driver whose most recent refresh time
118 // predates when we were initialised, so to be safe we make sure to take the
119 // most recent time here.
120 aTime = std::max(mCurrentSampleTime, aTime);
121
122 // Sleep detection: If the time between samples is a whole lot greater than we
123 // were expecting then we assume the computer went to sleep or someone's
124 // messing with the clock. In that case, fiddle our parent offset and use our
125 // average time between samples to calculate the new sample time. This
126 // prevents us from hanging while trying to catch up on all the missed time.
127
128 // Smoothing of coefficient for the average function. 0.2 should let us track
129 // the sample rate reasonably tightly without being overly affected by
130 // occasional delays.
131 static const double SAMPLE_DUR_WEIGHTING = 0.2;
132 // If the elapsed time exceeds our expectation by this number of times we'll
133 // initiate special behaviour to basically ignore the intervening time.
134 static const double SAMPLE_DEV_THRESHOLD = 200.0;
135
136 SMILTime elapsedTime =
137 (SMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
138 if (mAvgTimeBetweenSamples == 0) {
139 // First sample.
140 mAvgTimeBetweenSamples = elapsedTime;
141 } else {
142 if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
143 // Unexpectedly long delay between samples.
144 NS_WARNING(
145 "Detected really long delay between samples, continuing from "
146 "previous sample");
147 mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
148 }
149 // Update the moving average. Due to truncation here the average will
150 // normally be a little less than it should be but that's probably ok.
151 mAvgTimeBetweenSamples =
152 (SMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
153 mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
154 }
155 mCurrentSampleTime = aTime;
156
157 Sample();
158 }
159
160 //----------------------------------------------------------------------
161 // Animation element registration methods:
162
RegisterAnimationElement(SVGAnimationElement * aAnimationElement)163 void SMILAnimationController::RegisterAnimationElement(
164 SVGAnimationElement* aAnimationElement) {
165 mAnimationElementTable.PutEntry(aAnimationElement);
166 if (mDeferredStartSampling) {
167 mDeferredStartSampling = false;
168 if (!mChildContainerTable.IsEmpty()) {
169 // mAnimationElementTable was empty, but now we've added its 1st element
170 MOZ_ASSERT(mAnimationElementTable.Count() == 1,
171 "we shouldn't have deferred sampling if we already had "
172 "animations registered");
173 StartSampling(GetRefreshDriver());
174 Sample(); // Run the first sample manually
175 } // else, don't sample until a time container is registered (via AddChild)
176 }
177 }
178
UnregisterAnimationElement(SVGAnimationElement * aAnimationElement)179 void SMILAnimationController::UnregisterAnimationElement(
180 SVGAnimationElement* aAnimationElement) {
181 mAnimationElementTable.RemoveEntry(aAnimationElement);
182 }
183
184 //----------------------------------------------------------------------
185 // Page show/hide
186
OnPageShow()187 void SMILAnimationController::OnPageShow() {
188 Resume(SMILTimeContainer::PAUSE_PAGEHIDE);
189 }
190
OnPageHide()191 void SMILAnimationController::OnPageHide() {
192 Pause(SMILTimeContainer::PAUSE_PAGEHIDE);
193 }
194
195 //----------------------------------------------------------------------
196 // Cycle-collection support
197
Traverse(nsCycleCollectionTraversalCallback * aCallback)198 void SMILAnimationController::Traverse(
199 nsCycleCollectionTraversalCallback* aCallback) {
200 // Traverse last compositor table
201 if (mLastCompositorTable) {
202 for (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
203 SMILCompositor* compositor = iter.Get();
204 compositor->Traverse(aCallback);
205 }
206 }
207 }
208
Unlink()209 void SMILAnimationController::Unlink() { mLastCompositorTable = nullptr; }
210
211 //----------------------------------------------------------------------
212 // Refresh driver lifecycle related methods
213
NotifyRefreshDriverCreated(nsRefreshDriver * aRefreshDriver)214 void SMILAnimationController::NotifyRefreshDriverCreated(
215 nsRefreshDriver* aRefreshDriver) {
216 if (!mPauseState && !mChildContainerTable.IsEmpty()) {
217 MaybeStartSampling(aRefreshDriver);
218 }
219 }
220
NotifyRefreshDriverDestroying(nsRefreshDriver * aRefreshDriver)221 void SMILAnimationController::NotifyRefreshDriverDestroying(
222 nsRefreshDriver* aRefreshDriver) {
223 if (!mPauseState && !mDeferredStartSampling) {
224 StopSampling(aRefreshDriver);
225 }
226 }
227
228 //----------------------------------------------------------------------
229 // Timer-related implementation helpers
230
StartSampling(nsRefreshDriver * aRefreshDriver)231 void SMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver) {
232 NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
233 NS_ASSERTION(!mDeferredStartSampling,
234 "Started sampling but the deferred start flag is still set");
235 if (aRefreshDriver) {
236 MOZ_ASSERT(!mRegisteredWithRefreshDriver,
237 "Redundantly registering with refresh driver");
238 MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
239 "Starting sampling with wrong refresh driver");
240 // We're effectively resuming from a pause so update our current sample time
241 // or else it will confuse our "average time between samples" calculations.
242 mCurrentSampleTime = mozilla::TimeStamp::Now();
243 aRefreshDriver->AddRefreshObserver(this, FlushType::Style,
244 "SMIL animations");
245 mRegisteredWithRefreshDriver = true;
246 }
247 }
248
StopSampling(nsRefreshDriver * aRefreshDriver)249 void SMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver) {
250 if (aRefreshDriver && mRegisteredWithRefreshDriver) {
251 // NOTE: The document might already have been detached from its PresContext
252 // (and RefreshDriver), which would make GetRefreshDriver() return null.
253 MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
254 "Stopping sampling with wrong refresh driver");
255 aRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
256 mRegisteredWithRefreshDriver = false;
257 }
258 }
259
MaybeStartSampling(nsRefreshDriver * aRefreshDriver)260 void SMILAnimationController::MaybeStartSampling(
261 nsRefreshDriver* aRefreshDriver) {
262 if (mDeferredStartSampling) {
263 // We've received earlier 'MaybeStartSampling' calls, and we're
264 // deferring until we get a registered animation.
265 return;
266 }
267
268 if (mAnimationElementTable.Count()) {
269 StartSampling(aRefreshDriver);
270 } else {
271 mDeferredStartSampling = true;
272 }
273 }
274
275 //----------------------------------------------------------------------
276 // Sample-related methods and callbacks
277
DoSample()278 void SMILAnimationController::DoSample() {
279 DoSample(true); // Skip unchanged time containers
280 }
281
DoSample(bool aSkipUnchangedContainers)282 void SMILAnimationController::DoSample(bool aSkipUnchangedContainers) {
283 if (!mDocument) {
284 NS_ERROR("Shouldn't be sampling after document has disconnected");
285 return;
286 }
287 if (mRunningSample) {
288 NS_ERROR("Shouldn't be recursively sampling");
289 return;
290 }
291
292 bool isStyleFlushNeeded = mResampleNeeded;
293 mResampleNeeded = false;
294
295 nsCOMPtr<Document> document(mDocument); // keeps 'this' alive too
296
297 // Set running sample flag -- do this before flushing styles so that when we
298 // flush styles we don't end up requesting extra samples
299 AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
300 mRunningSample = true;
301
302 // STEP 1: Bring model up to date
303 // (i) Rewind elements where necessary
304 // (ii) Run milestone samples
305 RewindElements();
306 DoMilestoneSamples();
307
308 // STEP 2: Sample the child time containers
309 //
310 // When we sample the child time containers they will simply record the sample
311 // time in document time.
312 TimeContainerHashtable activeContainers(mChildContainerTable.Count());
313 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
314 if (!container) {
315 continue;
316 }
317
318 if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) &&
319 (container->NeedsSample() || !aSkipUnchangedContainers)) {
320 container->ClearMilestones();
321 container->Sample();
322 container->MarkSeekFinished();
323 activeContainers.PutEntry(container);
324 }
325 }
326
327 // STEP 3: (i) Sample the timed elements AND
328 // (ii) Create a table of compositors
329 //
330 // (i) Here we sample the timed elements (fetched from the
331 // SVGAnimationElements) which determine from the active time if the
332 // element is active and what its simple time etc. is. This information is
333 // then passed to its time client (SMILAnimationFunction).
334 //
335 // (ii) During the same loop we also build up a table that contains one
336 // compositor for each animated attribute and which maps animated elements to
337 // the corresponding compositor for their target attribute.
338 //
339 // Note that this compositor table needs to be allocated on the heap so we can
340 // store it until the next sample. This lets us find out which elements were
341 // animated in sample 'n-1' but not in sample 'n' (and hence need to have
342 // their animation effects removed in sample 'n').
343 //
344 // Parts (i) and (ii) are not functionally related but we combine them here to
345 // save iterating over the animation elements twice.
346
347 // Create the compositor table
348 UniquePtr<SMILCompositorTable> currentCompositorTable(
349 new SMILCompositorTable(0));
350 nsTArray<RefPtr<SVGAnimationElement>> animElems(
351 mAnimationElementTable.Count());
352
353 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
354 SampleTimedElement(animElem, &activeContainers);
355 AddAnimationToCompositorTable(animElem, currentCompositorTable.get(),
356 isStyleFlushNeeded);
357 animElems.AppendElement(animElem);
358 }
359 activeContainers.Clear();
360
361 // STEP 4: Compare previous sample's compositors against this sample's.
362 // (Transfer cached base values across, & remove animation effects from
363 // no-longer-animated targets.)
364 if (mLastCompositorTable) {
365 // * Transfer over cached base values, from last sample's compositors
366 for (auto iter = currentCompositorTable->Iter(); !iter.Done();
367 iter.Next()) {
368 SMILCompositor* compositor = iter.Get();
369 SMILCompositor* lastCompositor =
370 mLastCompositorTable->GetEntry(compositor->GetKey());
371
372 if (lastCompositor) {
373 compositor->StealCachedBaseValue(lastCompositor);
374 }
375 }
376
377 // * For each compositor in current sample's hash table, remove entry from
378 // prev sample's hash table -- we don't need to clear animation
379 // effects of those compositors, since they're still being animated.
380 for (const auto& key : currentCompositorTable->Keys()) {
381 mLastCompositorTable->RemoveEntry(key);
382 }
383
384 // * For each entry that remains in prev sample's hash table (i.e. for
385 // every target that's no longer animated), clear animation effects.
386 for (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
387 iter.Get()->ClearAnimationEffects();
388 }
389 }
390
391 // return early if there are no active animations to avoid a style flush
392 if (currentCompositorTable->Count() == 0) {
393 mLastCompositorTable = nullptr;
394 return;
395 }
396
397 if (isStyleFlushNeeded) {
398 document->FlushPendingNotifications(FlushType::Style);
399 }
400
401 // WARNING:
402 // WARNING: the above flush may have destroyed the pres shell and/or
403 // WARNING: frames and other layout related objects.
404 // WARNING:
405
406 // STEP 5: Compose currently-animated attributes.
407 // XXXdholbert: This step traverses our animation targets in an effectively
408 // random order. For animation from/to 'inherit' values to work correctly
409 // when the inherited value is *also* being animated, we really should be
410 // traversing our animated nodes in an ancestors-first order (bug 501183)
411 bool mightHavePendingStyleUpdates = false;
412 for (auto iter = currentCompositorTable->Iter(); !iter.Done(); iter.Next()) {
413 iter.Get()->ComposeAttribute(mightHavePendingStyleUpdates);
414 }
415
416 // Update last compositor table
417 mLastCompositorTable = std::move(currentCompositorTable);
418 mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates;
419
420 NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
421 }
422
RewindElements()423 void SMILAnimationController::RewindElements() {
424 const bool rewindNeeded = std::any_of(
425 mChildContainerTable.Keys().cbegin(), mChildContainerTable.Keys().cend(),
426 [](SMILTimeContainer* container) { return container->NeedsRewind(); });
427
428 if (!rewindNeeded) return;
429
430 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
431 SMILTimeContainer* timeContainer = animElem->GetTimeContainer();
432 if (timeContainer && timeContainer->NeedsRewind()) {
433 animElem->TimedElement().Rewind();
434 }
435 }
436
437 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
438 container->ClearNeedsRewind();
439 }
440 }
441
DoMilestoneSamples()442 void SMILAnimationController::DoMilestoneSamples() {
443 // We need to sample the timing model but because SMIL operates independently
444 // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
445 //
446 // In between those two sample times a whole string of significant events
447 // might be expected to take place: events firing, new interdependencies
448 // between animations resolved and dissolved, etc.
449 //
450 // Furthermore, at any given time, we want to sample all the intervals that
451 // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
452 // endpoint-exclusive timing model.
453 //
454 // So we have the animations (specifically the timed elements) register the
455 // next significant moment (called a milestone) in their lifetime and then we
456 // step through the model at each of these moments and sample those animations
457 // registered for those times. This way events can fire in the correct order,
458 // dependencies can be resolved etc.
459
460 SMILTime sampleTime = INT64_MIN;
461
462 while (true) {
463 // We want to find any milestones AT OR BEFORE the current sample time so we
464 // initialise the next milestone to the moment after (1ms after, to be
465 // precise) the current sample time and see if there are any milestones
466 // before that. Any other milestones will be dealt with in a subsequent
467 // sample.
468 SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
469 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
470 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
471 continue;
472 }
473 SMILMilestone thisMilestone;
474 bool didGetMilestone =
475 container->GetNextMilestoneInParentTime(thisMilestone);
476 if (didGetMilestone && thisMilestone < nextMilestone) {
477 nextMilestone = thisMilestone;
478 }
479 }
480
481 if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) {
482 break;
483 }
484
485 nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elements;
486 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
487 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
488 continue;
489 }
490 container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
491 }
492
493 uint32_t length = elements.Length();
494
495 // During the course of a sampling we don't want to actually go backwards.
496 // Due to negative offsets, early ends and the like, a timed element might
497 // register a milestone that is actually in the past. That's fine, but it's
498 // still only going to get *sampled* with whatever time we're up to and no
499 // earlier.
500 //
501 // Because we're only performing this clamping at the last moment, the
502 // animations will still all get sampled in the correct order and
503 // dependencies will be appropriately resolved.
504 sampleTime = std::max(nextMilestone.mTime, sampleTime);
505
506 for (uint32_t i = 0; i < length; ++i) {
507 SVGAnimationElement* elem = elements[i].get();
508 MOZ_ASSERT(elem, "nullptr animation element in list");
509 SMILTimeContainer* container = elem->GetTimeContainer();
510 if (!container)
511 // The container may be nullptr if the element has been detached from
512 // its parent since registering a milestone.
513 continue;
514
515 SMILTimeValue containerTimeValue =
516 container->ParentToContainerTime(sampleTime);
517 if (!containerTimeValue.IsDefinite()) continue;
518
519 // Clamp the converted container time to non-negative values.
520 SMILTime containerTime =
521 std::max<SMILTime>(0, containerTimeValue.GetMillis());
522
523 if (nextMilestone.mIsEnd) {
524 elem->TimedElement().SampleEndAt(containerTime);
525 } else {
526 elem->TimedElement().SampleAt(containerTime);
527 }
528 }
529 }
530 }
531
532 /*static*/
SampleTimedElement(SVGAnimationElement * aElement,TimeContainerHashtable * aActiveContainers)533 void SMILAnimationController::SampleTimedElement(
534 SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) {
535 SMILTimeContainer* timeContainer = aElement->GetTimeContainer();
536 if (!timeContainer) return;
537
538 // We'd like to call timeContainer->NeedsSample() here and skip all timed
539 // elements that belong to paused time containers that don't need a sample,
540 // but that doesn't work because we've already called Sample() on all the time
541 // containers so the paused ones don't need a sample any more and they'll
542 // return false.
543 //
544 // Instead we build up a hashmap of active time containers during the previous
545 // step (SampleTimeContainer) and then test here if the container for this
546 // timed element is in the list.
547 if (!aActiveContainers->GetEntry(timeContainer)) return;
548
549 SMILTime containerTime = timeContainer->GetCurrentTimeAsSMILTime();
550
551 MOZ_ASSERT(!timeContainer->IsSeeking(),
552 "Doing a regular sample but the time container is still seeking");
553 aElement->TimedElement().SampleAt(containerTime);
554 }
555
556 /*static*/
AddAnimationToCompositorTable(SVGAnimationElement * aElement,SMILCompositorTable * aCompositorTable,bool & aStyleFlushNeeded)557 void SMILAnimationController::AddAnimationToCompositorTable(
558 SVGAnimationElement* aElement, SMILCompositorTable* aCompositorTable,
559 bool& aStyleFlushNeeded) {
560 // Add a compositor to the hash table if there's not already one there
561 SMILTargetIdentifier key;
562 if (!GetTargetIdentifierForAnimation(aElement, key))
563 // Something's wrong/missing about animation's target; skip this animation
564 return;
565
566 SMILAnimationFunction& func = aElement->AnimationFunction();
567
568 // Only add active animation functions. If there are no active animations
569 // targeting an attribute, no compositor will be created and any previously
570 // applied animations will be cleared.
571 if (func.IsActiveOrFrozen()) {
572 // Look up the compositor for our target, & add our animation function
573 // to its list of animation functions.
574 SMILCompositor* result = aCompositorTable->PutEntry(key);
575 result->AddAnimationFunction(&func);
576
577 } else if (func.HasChanged()) {
578 // Look up the compositor for our target, and force it to skip the
579 // "nothing's changed so don't bother compositing" optimization for this
580 // sample. |func| is inactive, but it's probably *newly* inactive (since
581 // it's got HasChanged() == true), so we need to make sure to recompose
582 // its target.
583 SMILCompositor* result = aCompositorTable->PutEntry(key);
584 result->ToggleForceCompositing();
585
586 // We've now made sure that |func|'s inactivity will be reflected as of
587 // this sample. We need to clear its HasChanged() flag so that it won't
588 // trigger this same clause in future samples (until it changes again).
589 func.ClearHasChanged();
590 }
591 aStyleFlushNeeded |= func.ValueNeedsReparsingEverySample();
592 }
593
IsTransformAttribute(int32_t aNamespaceID,nsAtom * aAttributeName)594 static inline bool IsTransformAttribute(int32_t aNamespaceID,
595 nsAtom* aAttributeName) {
596 return aNamespaceID == kNameSpaceID_None &&
597 (aAttributeName == nsGkAtoms::transform ||
598 aAttributeName == nsGkAtoms::patternTransform ||
599 aAttributeName == nsGkAtoms::gradientTransform);
600 }
601
602 // Helper function that, given a SVGAnimationElement, looks up its target
603 // element & target attribute and populates a SMILTargetIdentifier
604 // for this target.
605 /*static*/
GetTargetIdentifierForAnimation(SVGAnimationElement * aAnimElem,SMILTargetIdentifier & aResult)606 bool SMILAnimationController::GetTargetIdentifierForAnimation(
607 SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) {
608 // Look up target (animated) element
609 Element* targetElem = aAnimElem->GetTargetElementContent();
610 if (!targetElem)
611 // Animation has no target elem -- skip it.
612 return false;
613
614 // Look up target (animated) attribute
615 // SMILANIM section 3.1, attributeName may
616 // have an XMLNS prefix to indicate the XML namespace.
617 RefPtr<nsAtom> attributeName;
618 int32_t attributeNamespaceID;
619 if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
620 getter_AddRefs(attributeName)))
621 // Animation has no target attr -- skip it.
622 return false;
623
624 // animateTransform can only animate transforms, conversely transforms
625 // can only be animated by animateTransform
626 if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
627 (aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)))
628 return false;
629
630 // Construct the key
631 aResult.mElement = targetElem;
632 aResult.mAttributeName = attributeName;
633 aResult.mAttributeNamespaceID = attributeNamespaceID;
634
635 return true;
636 }
637
PreTraverse()638 bool SMILAnimationController::PreTraverse() {
639 return PreTraverseInSubtree(nullptr);
640 }
641
PreTraverseInSubtree(Element * aRoot)642 bool SMILAnimationController::PreTraverseInSubtree(Element* aRoot) {
643 MOZ_ASSERT(NS_IsMainThread());
644
645 if (!mMightHavePendingStyleUpdates) {
646 return false;
647 }
648
649 nsPresContext* context = mDocument->GetPresContext();
650 if (!context) {
651 return false;
652 }
653
654 bool foundElementsNeedingRestyle = false;
655 for (SVGAnimationElement* animElement : mAnimationElementTable.Keys()) {
656 SMILTargetIdentifier key;
657 if (!GetTargetIdentifierForAnimation(animElement, key)) {
658 // Something's wrong/missing about animation's target; skip this animation
659 continue;
660 }
661
662 // Ignore restyles that aren't in the flattened tree subtree rooted at
663 // aRoot.
664 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
665 key.mElement, aRoot)) {
666 continue;
667 }
668
669 context->RestyleManager()->PostRestyleEventForAnimations(
670 key.mElement, PseudoStyleType::NotPseudo, RestyleHint::RESTYLE_SMIL);
671
672 foundElementsNeedingRestyle = true;
673 }
674
675 // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
676 // all restyles.
677 if (!aRoot) {
678 mMightHavePendingStyleUpdates = false;
679 }
680
681 return foundElementsNeedingRestyle;
682 }
683
684 //----------------------------------------------------------------------
685 // Add/remove child time containers
686
AddChild(SMILTimeContainer & aChild)687 nsresult SMILAnimationController::AddChild(SMILTimeContainer& aChild) {
688 TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
689 NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
690
691 if (!mPauseState && mChildContainerTable.Count() == 1) {
692 MaybeStartSampling(GetRefreshDriver());
693 Sample(); // Run the first sample manually
694 }
695
696 return NS_OK;
697 }
698
RemoveChild(SMILTimeContainer & aChild)699 void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) {
700 mChildContainerTable.RemoveEntry(&aChild);
701
702 if (!mPauseState && mChildContainerTable.IsEmpty()) {
703 StopSampling(GetRefreshDriver());
704 }
705 }
706
707 // Helper method
GetRefreshDriver()708 nsRefreshDriver* SMILAnimationController::GetRefreshDriver() {
709 if (!mDocument) {
710 NS_ERROR("Requesting refresh driver after document has disconnected!");
711 return nullptr;
712 }
713
714 nsPresContext* context = mDocument->GetPresContext();
715 return context ? context->RefreshDriver() : nullptr;
716 }
717
FlagDocumentNeedsFlush()718 void SMILAnimationController::FlagDocumentNeedsFlush() {
719 if (PresShell* presShell = mDocument->GetPresShell()) {
720 presShell->SetNeedStyleFlush();
721 }
722 }
723
724 } // namespace mozilla
725