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