Coverage Report

Created: 2018-09-25 14:53

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