Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/smil/nsSMILTimedElement.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 "mozilla/DebugOnly.h"
8
9
#include "mozilla/ContentEvents.h"
10
#include "mozilla/EventDispatcher.h"
11
#include "mozilla/dom/SVGAnimationElement.h"
12
#include "mozilla/TaskCategory.h"
13
#include "nsSMILTimedElement.h"
14
#include "nsAttrValueInlines.h"
15
#include "nsSMILAnimationFunction.h"
16
#include "nsSMILTimeValue.h"
17
#include "nsSMILTimeValueSpec.h"
18
#include "nsSMILInstanceTime.h"
19
#include "nsSMILParserUtils.h"
20
#include "nsSMILTimeContainer.h"
21
#include "nsGkAtoms.h"
22
#include "nsReadableUtils.h"
23
#include "nsMathUtils.h"
24
#include "nsThreadUtils.h"
25
#include "nsIPresShell.h"
26
#include "prdtoa.h"
27
#include "plstr.h"
28
#include "prtime.h"
29
#include "nsString.h"
30
#include "mozilla/AutoRestore.h"
31
#include "nsCharSeparatedTokenizer.h"
32
#include <algorithm>
33
34
using namespace mozilla;
35
using namespace mozilla::dom;
36
37
//----------------------------------------------------------------------
38
// Helper class: InstanceTimeComparator
39
40
// Upon inserting an instance time into one of our instance time lists we assign
41
// it a serial number. This allows us to sort the instance times in such a way
42
// that where we have several equal instance times, the ones added later will
43
// sort later. This means that when we call UpdateCurrentInterval during the
44
// waiting state we won't unnecessarily change the begin instance.
45
//
46
// The serial number also means that every instance time has an unambiguous
47
// position in the array so we can use RemoveElementSorted and the like.
48
bool
49
nsSMILTimedElement::InstanceTimeComparator::Equals(
50
    const nsSMILInstanceTime* aElem1,
51
    const nsSMILInstanceTime* aElem2) const
52
0
{
53
0
  MOZ_ASSERT(aElem1 && aElem2,
54
0
             "Trying to compare null instance time pointers");
55
0
  MOZ_ASSERT(aElem1->Serial() && aElem2->Serial(),
56
0
             "Instance times have not been assigned serial numbers");
57
0
  MOZ_ASSERT(aElem1 == aElem2 || aElem1->Serial() != aElem2->Serial(),
58
0
             "Serial numbers are not unique");
59
0
60
0
  return aElem1->Serial() == aElem2->Serial();
61
0
}
62
63
bool
64
nsSMILTimedElement::InstanceTimeComparator::LessThan(
65
    const nsSMILInstanceTime* aElem1,
66
    const nsSMILInstanceTime* aElem2) const
67
0
{
68
0
  MOZ_ASSERT(aElem1 && aElem2,
69
0
             "Trying to compare null instance time pointers");
70
0
  MOZ_ASSERT(aElem1->Serial() && aElem2->Serial(),
71
0
             "Instance times have not been assigned serial numbers");
72
0
73
0
  int8_t cmp = aElem1->Time().CompareTo(aElem2->Time());
74
0
  return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0;
75
0
}
76
77
//----------------------------------------------------------------------
78
// Helper class: AsyncTimeEventRunner
79
80
namespace
81
{
82
  class AsyncTimeEventRunner : public Runnable
83
  {
84
  protected:
85
    RefPtr<nsIContent> mTarget;
86
    EventMessage         mMsg;
87
    int32_t              mDetail;
88
89
  public:
90
    AsyncTimeEventRunner(nsIContent* aTarget,
91
                         EventMessage aMsg,
92
                         int32_t aDetail)
93
      : mozilla::Runnable("AsyncTimeEventRunner")
94
      , mTarget(aTarget)
95
      , mMsg(aMsg)
96
      , mDetail(aDetail)
97
0
    {
98
0
    }
99
100
    NS_IMETHOD Run() override
101
0
    {
102
0
      InternalSMILTimeEvent event(true, mMsg);
103
0
      event.mDetail = mDetail;
104
0
105
0
      nsPresContext* context = nullptr;
106
0
      nsIDocument* doc = mTarget->GetComposedDoc();
107
0
      if (doc) {
108
0
        context = doc->GetPresContext();
109
0
      }
110
0
111
0
      return EventDispatcher::Dispatch(mTarget, context, &event);
112
0
    }
113
  };
114
} // namespace
115
116
//----------------------------------------------------------------------
117
// Helper class: AutoIntervalUpdateBatcher
118
119
// Stack-based helper class to set the mDeferIntervalUpdates flag on an
120
// nsSMILTimedElement and perform the UpdateCurrentInterval when the object is
121
// destroyed.
122
//
123
// If several of these objects are allocated on the stack, the update will not
124
// be performed until the last object for a given nsSMILTimedElement is
125
// destroyed.
126
class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdateBatcher
127
{
128
public:
129
  explicit AutoIntervalUpdateBatcher(nsSMILTimedElement& aTimedElement)
130
    : mTimedElement(aTimedElement),
131
      mDidSetFlag(!aTimedElement.mDeferIntervalUpdates)
132
0
  {
133
0
    mTimedElement.mDeferIntervalUpdates = true;
134
0
  }
135
136
  ~AutoIntervalUpdateBatcher()
137
0
  {
138
0
    if (!mDidSetFlag)
139
0
      return;
140
0
141
0
    mTimedElement.mDeferIntervalUpdates = false;
142
0
143
0
    if (mTimedElement.mDoDeferredUpdate) {
144
0
      mTimedElement.mDoDeferredUpdate = false;
145
0
      mTimedElement.UpdateCurrentInterval();
146
0
    }
147
0
  }
148
149
private:
150
  nsSMILTimedElement& mTimedElement;
151
  bool mDidSetFlag;
152
};
153
154
//----------------------------------------------------------------------
155
// Helper class: AutoIntervalUpdater
156
157
// Stack-based helper class to call UpdateCurrentInterval when it is destroyed
158
// which helps avoid bugs where we forget to call UpdateCurrentInterval in the
159
// case of early returns (e.g. due to parse errors).
160
//
161
// This can be safely used in conjunction with AutoIntervalUpdateBatcher; any
162
// calls to UpdateCurrentInterval made by this class will simply be deferred if
163
// there is an AutoIntervalUpdateBatcher on the stack.
164
class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater
165
{
166
public:
167
  explicit AutoIntervalUpdater(nsSMILTimedElement& aTimedElement)
168
0
    : mTimedElement(aTimedElement) { }
169
170
  ~AutoIntervalUpdater()
171
0
  {
172
0
    mTimedElement.UpdateCurrentInterval();
173
0
  }
174
175
private:
176
  nsSMILTimedElement& mTimedElement;
177
};
178
179
//----------------------------------------------------------------------
180
// Templated helper functions
181
182
// Selectively remove elements from an array of type
183
// nsTArray<RefPtr<nsSMILInstanceTime> > with O(n) performance.
184
template <class TestFunctor>
185
void
186
nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray,
187
                                        TestFunctor& aTest)
188
0
{
189
0
  InstanceTimeList newArray;
190
0
  for (uint32_t i = 0; i < aArray.Length(); ++i) {
191
0
    nsSMILInstanceTime* item = aArray[i].get();
192
0
    if (aTest(item, i)) {
193
0
      // As per bugs 665334 and 669225 we should be careful not to remove the
194
0
      // instance time that corresponds to the previous interval's end time.
195
0
      //
196
0
      // Most functors supplied here fulfil this condition by checking if the
197
0
      // instance time is marked as "ShouldPreserve" and if so, not deleting it.
198
0
      //
199
0
      // However, when filtering instance times, we sometimes need to drop even
200
0
      // instance times marked as "ShouldPreserve". In that case we take special
201
0
      // care not to delete the end instance time of the previous interval.
202
0
      MOZ_ASSERT(!GetPreviousInterval() || item != GetPreviousInterval()->End(),
203
0
                 "Removing end instance time of previous interval");
204
0
      item->Unlink();
205
0
    } else {
206
0
      newArray.AppendElement(item);
207
0
    }
208
0
  }
209
0
  aArray.Clear();
210
0
  aArray.SwapElements(newArray);
211
0
}
Unexecuted instantiation: Unified_cpp_dom_smil1.cpp:void nsSMILTimedElement::RemoveInstanceTimes<(anonymous namespace)::RemoveByCreator>(nsTArray<RefPtr<nsSMILInstanceTime> >&, (anonymous namespace)::RemoveByCreator&)
Unexecuted instantiation: Unified_cpp_dom_smil1.cpp:void nsSMILTimedElement::RemoveInstanceTimes<(anonymous namespace)::RemoveByFunction>(nsTArray<RefPtr<nsSMILInstanceTime> >&, (anonymous namespace)::RemoveByFunction&)
Unexecuted instantiation: Unified_cpp_dom_smil1.cpp:void nsSMILTimedElement::RemoveInstanceTimes<(anonymous namespace)::RemoveReset>(nsTArray<RefPtr<nsSMILInstanceTime> >&, (anonymous namespace)::RemoveReset&)
Unexecuted instantiation: Unified_cpp_dom_smil1.cpp:void nsSMILTimedElement::RemoveInstanceTimes<(anonymous namespace)::RemoveFiltered>(nsTArray<RefPtr<nsSMILInstanceTime> >&, (anonymous namespace)::RemoveFiltered&)
Unexecuted instantiation: Unified_cpp_dom_smil1.cpp:void nsSMILTimedElement::RemoveInstanceTimes<(anonymous namespace)::RemoveBelowThreshold>(nsTArray<RefPtr<nsSMILInstanceTime> >&, (anonymous namespace)::RemoveBelowThreshold&)
212
213
//----------------------------------------------------------------------
214
// Static members
215
216
const nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = {
217
      {"remove", FILL_REMOVE},
218
      {"freeze", FILL_FREEZE},
219
      {nullptr, 0}
220
};
221
222
const nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = {
223
      {"always", RESTART_ALWAYS},
224
      {"whenNotActive", RESTART_WHENNOTACTIVE},
225
      {"never", RESTART_NEVER},
226
      {nullptr, 0}
227
};
228
229
const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(
230
  std::numeric_limits<nsSMILTime>::max(), false);
231
232
// The thresholds at which point we start filtering intervals and instance times
233
// indiscriminately.
234
// See FilterIntervals and FilterInstanceTimes.
235
const uint8_t nsSMILTimedElement::sMaxNumIntervals = 20;
236
const uint8_t nsSMILTimedElement::sMaxNumInstanceTimes = 100;
237
238
// Detect if we arrive in some sort of undetected recursive syncbase dependency
239
// relationship
240
const uint8_t nsSMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20;
241
242
//----------------------------------------------------------------------
243
// Ctor, dtor
244
245
nsSMILTimedElement::nsSMILTimedElement()
246
:
247
  mAnimationElement(nullptr),
248
  mFillMode(FILL_REMOVE),
249
  mRestartMode(RESTART_ALWAYS),
250
  mInstanceSerialIndex(0),
251
  mClient(nullptr),
252
  mCurrentInterval(nullptr),
253
  mCurrentRepeatIteration(0),
254
  mPrevRegisteredMilestone(sMaxMilestone),
255
  mElementState(STATE_STARTUP),
256
  mSeekState(SEEK_NOT_SEEKING),
257
  mDeferIntervalUpdates(false),
258
  mDoDeferredUpdate(false),
259
  mIsDisabled(false),
260
  mDeleteCount(0),
261
  mUpdateIntervalRecursionDepth(0)
262
0
{
263
0
  mSimpleDur.SetIndefinite();
264
0
  mMin.SetMillis(0L);
265
0
  mMax.SetIndefinite();
266
0
}
267
268
nsSMILTimedElement::~nsSMILTimedElement()
269
0
{
270
0
  // Unlink all instance times from dependent intervals
271
0
  for (uint32_t i = 0; i < mBeginInstances.Length(); ++i) {
272
0
    mBeginInstances[i]->Unlink();
273
0
  }
274
0
  mBeginInstances.Clear();
275
0
  for (uint32_t i = 0; i < mEndInstances.Length(); ++i) {
276
0
    mEndInstances[i]->Unlink();
277
0
  }
278
0
  mEndInstances.Clear();
279
0
280
0
  // Notify anyone listening to our intervals that they're gone
281
0
  // (We shouldn't get any callbacks from this because all our instance times
282
0
  // are now disassociated with any intervals)
283
0
  ClearIntervals();
284
0
285
0
  // The following assertions are important in their own right (for checking
286
0
  // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers
287
0
  // to class so if they fail there's the possibility we might have dangling
288
0
  // pointers.
289
0
  MOZ_ASSERT(!mDeferIntervalUpdates,
290
0
             "Interval updates should no longer be blocked when an "
291
0
             "nsSMILTimedElement disappears");
292
0
  MOZ_ASSERT(!mDoDeferredUpdate,
293
0
             "There should no longer be any pending updates when an "
294
0
             "nsSMILTimedElement disappears");
295
0
}
296
297
void
298
nsSMILTimedElement::SetAnimationElement(SVGAnimationElement* aElement)
299
0
{
300
0
  MOZ_ASSERT(aElement, "NULL owner element");
301
0
  MOZ_ASSERT(!mAnimationElement, "Re-setting owner");
302
0
  mAnimationElement = aElement;
303
0
}
304
305
nsSMILTimeContainer*
306
nsSMILTimedElement::GetTimeContainer()
307
0
{
308
0
  return mAnimationElement ? mAnimationElement->GetTimeContainer() : nullptr;
309
0
}
310
311
dom::Element*
312
nsSMILTimedElement::GetTargetElement()
313
0
{
314
0
  return mAnimationElement ?
315
0
      mAnimationElement->GetTargetElementContent() :
316
0
      nullptr;
317
0
}
318
319
//----------------------------------------------------------------------
320
// ElementTimeControl methods
321
//
322
// The definition of the ElementTimeControl interface differs between SMIL
323
// Animation and SVG 1.1. In SMIL Animation all methods have a void return
324
// type and the new instance time is simply added to the list and restart
325
// semantics are applied as with any other instance time. In the SVG definition
326
// the methods return a bool depending on the restart mode.
327
//
328
// This inconsistency has now been addressed by an erratum in SVG 1.1:
329
//
330
// http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface
331
//
332
// which favours the definition in SMIL, i.e. instance times are just added
333
// without first checking the restart mode.
334
335
nsresult
336
nsSMILTimedElement::BeginElementAt(double aOffsetSeconds)
337
0
{
338
0
  nsSMILTimeContainer* container = GetTimeContainer();
339
0
  if (!container)
340
0
    return NS_ERROR_FAILURE;
341
0
342
0
  nsSMILTime currentTime = container->GetCurrentTime();
343
0
  return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, true);
344
0
}
345
346
nsresult
347
nsSMILTimedElement::EndElementAt(double aOffsetSeconds)
348
0
{
349
0
  nsSMILTimeContainer* container = GetTimeContainer();
350
0
  if (!container)
351
0
    return NS_ERROR_FAILURE;
352
0
353
0
  nsSMILTime currentTime = container->GetCurrentTime();
354
0
  return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, false);
355
0
}
356
357
//----------------------------------------------------------------------
358
// nsSVGAnimationElement methods
359
360
nsSMILTimeValue
361
nsSMILTimedElement::GetStartTime() const
362
0
{
363
0
  return mElementState == STATE_WAITING || mElementState == STATE_ACTIVE
364
0
         ? mCurrentInterval->Begin()->Time()
365
0
         : nsSMILTimeValue();
366
0
}
367
368
//----------------------------------------------------------------------
369
// Hyperlinking support
370
371
nsSMILTimeValue
372
nsSMILTimedElement::GetHyperlinkTime() const
373
0
{
374
0
  nsSMILTimeValue hyperlinkTime; // Default ctor creates unresolved time
375
0
376
0
  if (mElementState == STATE_ACTIVE) {
377
0
    hyperlinkTime = mCurrentInterval->Begin()->Time();
378
0
  } else if (!mBeginInstances.IsEmpty()) {
379
0
    hyperlinkTime = mBeginInstances[0]->Time();
380
0
  }
381
0
382
0
  return hyperlinkTime;
383
0
}
384
385
//----------------------------------------------------------------------
386
// nsSMILTimedElement
387
388
void
389
nsSMILTimedElement::AddInstanceTime(nsSMILInstanceTime* aInstanceTime,
390
                                    bool aIsBegin)
391
0
{
392
0
  MOZ_ASSERT(aInstanceTime, "Attempting to add null instance time");
393
0
394
0
  // Event-sensitivity: If an element is not active (but the parent time
395
0
  // container is), then events are only handled for begin specifications.
396
0
  if (mElementState != STATE_ACTIVE && !aIsBegin &&
397
0
      aInstanceTime->IsDynamic())
398
0
  {
399
0
    // No need to call Unlink here--dynamic instance times shouldn't be linked
400
0
    // to anything that's going to miss them
401
0
    MOZ_ASSERT(!aInstanceTime->GetBaseInterval(),
402
0
               "Dynamic instance time has a base interval--we probably need "
403
0
               "to unlink it if we're not going to use it");
404
0
    return;
405
0
  }
406
0
407
0
  aInstanceTime->SetSerial(++mInstanceSerialIndex);
408
0
  InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
409
0
  RefPtr<nsSMILInstanceTime>* inserted =
410
0
    instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator());
411
0
  if (!inserted) {
412
0
    NS_WARNING("Insufficient memory to insert instance time");
413
0
    return;
414
0
  }
415
0
416
0
  UpdateCurrentInterval();
417
0
}
418
419
void
420
nsSMILTimedElement::UpdateInstanceTime(nsSMILInstanceTime* aInstanceTime,
421
                                       nsSMILTimeValue& aUpdatedTime,
422
                                       bool aIsBegin)
423
0
{
424
0
  MOZ_ASSERT(aInstanceTime, "Attempting to update null instance time");
425
0
426
0
  // The reason we update the time here and not in the nsSMILTimeValueSpec is
427
0
  // that it means we *could* re-sort more efficiently by doing a sorted remove
428
0
  // and insert but currently this doesn't seem to be necessary given how
429
0
  // infrequently we get these change notices.
430
0
  aInstanceTime->DependentUpdate(aUpdatedTime);
431
0
  InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
432
0
  instanceList.Sort(InstanceTimeComparator());
433
0
434
0
  // Generally speaking, UpdateCurrentInterval makes changes to the current
435
0
  // interval and sends changes notices itself. However, in this case because
436
0
  // instance times are shared between the instance time list and the intervals
437
0
  // we are effectively changing the current interval outside
438
0
  // UpdateCurrentInterval so we need to explicitly signal that we've made
439
0
  // a change.
440
0
  //
441
0
  // This wouldn't be necessary if we cloned instance times on adding them to
442
0
  // the current interval but this introduces other complications (particularly
443
0
  // detecting which instance time is being used to define the begin of the
444
0
  // current interval when doing a Reset).
445
0
  bool changedCurrentInterval = mCurrentInterval &&
446
0
    (mCurrentInterval->Begin() == aInstanceTime ||
447
0
     mCurrentInterval->End() == aInstanceTime);
448
0
449
0
  UpdateCurrentInterval(changedCurrentInterval);
450
0
}
451
452
void
453
nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime,
454
                                       bool aIsBegin)
455
0
{
456
0
  MOZ_ASSERT(aInstanceTime, "Attempting to remove null instance time");
457
0
458
0
  // If the instance time should be kept (because it is or was the fixed end
459
0
  // point of an interval) then just disassociate it from the creator.
460
0
  if (aInstanceTime->ShouldPreserve()) {
461
0
    aInstanceTime->Unlink();
462
0
    return;
463
0
  }
464
0
465
0
  InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
466
0
  mozilla::DebugOnly<bool> found =
467
0
    instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator());
468
0
  MOZ_ASSERT(found, "Couldn't find instance time to delete");
469
0
470
0
  UpdateCurrentInterval();
471
0
}
472
473
namespace
474
{
475
  class MOZ_STACK_CLASS RemoveByCreator
476
  {
477
  public:
478
    explicit RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator)
479
0
    { }
480
481
    bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
482
0
    {
483
0
      if (aInstanceTime->GetCreator() != mCreator)
484
0
        return false;
485
0
486
0
      // If the instance time should be kept (because it is or was the fixed end
487
0
      // point of an interval) then just disassociate it from the creator.
488
0
      if (aInstanceTime->ShouldPreserve()) {
489
0
        aInstanceTime->Unlink();
490
0
        return false;
491
0
      }
492
0
493
0
      return true;
494
0
    }
495
496
  private:
497
    const nsSMILTimeValueSpec* mCreator;
498
  };
499
} // namespace
500
501
void
502
nsSMILTimedElement::RemoveInstanceTimesForCreator(
503
    const nsSMILTimeValueSpec* aCreator, bool aIsBegin)
504
0
{
505
0
  MOZ_ASSERT(aCreator, "Creator not set");
506
0
507
0
  InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
508
0
  RemoveByCreator removeByCreator(aCreator);
509
0
  RemoveInstanceTimes(instances, removeByCreator);
510
0
511
0
  UpdateCurrentInterval();
512
0
}
513
514
void
515
nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient)
516
0
{
517
0
  //
518
0
  // No need to check for nullptr. A nullptr parameter simply means to remove the
519
0
  // previous client which we do by setting to nullptr anyway.
520
0
  //
521
0
522
0
  mClient = aClient;
523
0
}
524
525
void
526
nsSMILTimedElement::SampleAt(nsSMILTime aContainerTime)
527
0
{
528
0
  if (mIsDisabled)
529
0
    return;
530
0
531
0
  // Milestones are cleared before a sample
532
0
  mPrevRegisteredMilestone = sMaxMilestone;
533
0
534
0
  DoSampleAt(aContainerTime, false);
535
0
}
536
537
void
538
nsSMILTimedElement::SampleEndAt(nsSMILTime aContainerTime)
539
0
{
540
0
  if (mIsDisabled)
541
0
    return;
542
0
543
0
  // Milestones are cleared before a sample
544
0
  mPrevRegisteredMilestone = sMaxMilestone;
545
0
546
0
  // If the current interval changes, we don't bother trying to remove any old
547
0
  // milestones we'd registered. So it's possible to get a call here to end an
548
0
  // interval at a time that no longer reflects the end of the current interval.
549
0
  //
550
0
  // For now we just check that we're actually in an interval but note that the
551
0
  // initial sample we use to initialise the model is an end sample. This is
552
0
  // because we want to resolve all the instance times before committing to an
553
0
  // initial interval. Therefore an end sample from the startup state is also
554
0
  // acceptable.
555
0
  if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) {
556
0
    DoSampleAt(aContainerTime, true); // End sample
557
0
  } else {
558
0
    // Even if this was an unnecessary milestone sample we want to be sure that
559
0
    // our next real milestone is registered.
560
0
    RegisterMilestone();
561
0
  }
562
0
}
563
564
void
565
nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
566
0
{
567
0
  MOZ_ASSERT(mAnimationElement,
568
0
             "Got sample before being registered with an animation element");
569
0
  MOZ_ASSERT(GetTimeContainer(),
570
0
             "Got sample without being registered with a time container");
571
0
572
0
  // This could probably happen if we later implement externalResourcesRequired
573
0
  // (bug 277955) and whilst waiting for those resources (and the animation to
574
0
  // start) we transfer a node from another document fragment that has already
575
0
  // started. In such a case we might receive milestone samples registered with
576
0
  // the already active container.
577
0
  if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
578
0
    return;
579
0
580
0
  // We use an end-sample to start animation since an end-sample lets us
581
0
  // tentatively create an interval without committing to it (by transitioning
582
0
  // to the ACTIVE state) and this is necessary because we might have
583
0
  // dependencies on other animations that are yet to start. After these
584
0
  // other animations start, it may be necessary to revise our initial interval.
585
0
  //
586
0
  // However, sometimes instead of an end-sample we can get a regular sample
587
0
  // during STARTUP state. This can happen, for example, if we register
588
0
  // a milestone before time t=0 and are then re-bound to the tree (which sends
589
0
  // us back to the STARTUP state). In such a case we should just ignore the
590
0
  // sample and wait for our real initial sample which will be an end-sample.
591
0
  if (mElementState == STATE_STARTUP && !aEndOnly)
592
0
    return;
593
0
594
0
  bool finishedSeek = false;
595
0
  if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) {
596
0
    mSeekState = mElementState == STATE_ACTIVE ?
597
0
                 SEEK_FORWARD_FROM_ACTIVE :
598
0
                 SEEK_FORWARD_FROM_INACTIVE;
599
0
  } else if (mSeekState != SEEK_NOT_SEEKING &&
600
0
             !GetTimeContainer()->IsSeeking()) {
601
0
    finishedSeek = true;
602
0
  }
603
0
604
0
  bool            stateChanged;
605
0
  nsSMILTimeValue sampleTime(aContainerTime);
606
0
607
0
  do {
608
#ifdef DEBUG
609
    // Check invariant
610
    if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) {
611
      MOZ_ASSERT(!mCurrentInterval,
612
                 "Shouldn't have current interval in startup or postactive "
613
                 "states");
614
    } else {
615
      MOZ_ASSERT(mCurrentInterval,
616
                 "Should have current interval in waiting and active states");
617
    }
618
#endif
619
620
0
    stateChanged = false;
621
0
622
0
    switch (mElementState)
623
0
    {
624
0
    case STATE_STARTUP:
625
0
      {
626
0
        nsSMILInterval firstInterval;
627
0
        mElementState = GetNextInterval(nullptr, nullptr, nullptr, firstInterval)
628
0
         ? STATE_WAITING
629
0
         : STATE_POSTACTIVE;
630
0
        stateChanged = true;
631
0
        if (mElementState == STATE_WAITING) {
632
0
          mCurrentInterval = MakeUnique<nsSMILInterval>(firstInterval);
633
0
          NotifyNewInterval();
634
0
        }
635
0
      }
636
0
      break;
637
0
638
0
    case STATE_WAITING:
639
0
      {
640
0
        if (mCurrentInterval->Begin()->Time() <= sampleTime) {
641
0
          mElementState = STATE_ACTIVE;
642
0
          mCurrentInterval->FixBegin();
643
0
          if (mClient) {
644
0
            mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis());
645
0
          }
646
0
          if (mSeekState == SEEK_NOT_SEEKING) {
647
0
            FireTimeEventAsync(eSMILBeginEvent, 0);
648
0
          }
649
0
          if (HasPlayed()) {
650
0
            Reset(); // Apply restart behaviour
651
0
            // The call to Reset() may mean that the end point of our current
652
0
            // interval should be changed and so we should update the interval
653
0
            // now. However, calling UpdateCurrentInterval could result in the
654
0
            // interval getting deleted (perhaps through some web of syncbase
655
0
            // dependencies) therefore we make updating the interval the last
656
0
            // thing we do. There is no guarantee that mCurrentInterval is set
657
0
            // after this.
658
0
            UpdateCurrentInterval();
659
0
          }
660
0
          stateChanged = true;
661
0
        }
662
0
      }
663
0
      break;
664
0
665
0
    case STATE_ACTIVE:
666
0
      {
667
0
        // Ending early will change the interval but we don't notify dependents
668
0
        // of the change until we have closed off the current interval (since we
669
0
        // don't want dependencies to un-end our early end).
670
0
        bool didApplyEarlyEnd = ApplyEarlyEnd(sampleTime);
671
0
672
0
        if (mCurrentInterval->End()->Time() <= sampleTime) {
673
0
          nsSMILInterval newInterval;
674
0
          mElementState =
675
0
            GetNextInterval(mCurrentInterval.get(), nullptr, nullptr, newInterval)
676
0
            ? STATE_WAITING
677
0
            : STATE_POSTACTIVE;
678
0
          if (mClient) {
679
0
            mClient->Inactivate(mFillMode == FILL_FREEZE);
680
0
          }
681
0
          mCurrentInterval->FixEnd();
682
0
          if (mSeekState == SEEK_NOT_SEEKING) {
683
0
            FireTimeEventAsync(eSMILEndEvent, 0);
684
0
          }
685
0
          mCurrentRepeatIteration = 0;
686
0
          mOldIntervals.AppendElement(std::move(mCurrentInterval));
687
0
          SampleFillValue();
688
0
          if (mElementState == STATE_WAITING) {
689
0
            mCurrentInterval = MakeUnique<nsSMILInterval>(newInterval);
690
0
          }
691
0
          // We are now in a consistent state to dispatch notifications
692
0
          if (didApplyEarlyEnd) {
693
0
            NotifyChangedInterval(
694
0
                mOldIntervals[mOldIntervals.Length() - 1].get(), false, true);
695
0
          }
696
0
          if (mElementState == STATE_WAITING) {
697
0
            NotifyNewInterval();
698
0
          }
699
0
          FilterHistory();
700
0
          stateChanged = true;
701
0
        } else if (mCurrentInterval->Begin()->Time() <= sampleTime) {
702
0
          MOZ_ASSERT(!didApplyEarlyEnd,
703
0
                     "We got an early end, but didn't end");
704
0
          nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis();
705
0
          nsSMILTime activeTime = aContainerTime - beginTime;
706
0
707
0
          // The 'min' attribute can cause the active interval to be longer than
708
0
          // the 'repeating interval'.
709
0
          // In that extended period we apply the fill mode.
710
0
          if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) {
711
0
            if (mClient && mClient->IsActive()) {
712
0
              mClient->Inactivate(mFillMode == FILL_FREEZE);
713
0
            }
714
0
            SampleFillValue();
715
0
          } else {
716
0
            SampleSimpleTime(activeTime);
717
0
718
0
            // We register our repeat times as milestones (except when we're
719
0
            // seeking) so we should get a sample at exactly the time we repeat.
720
0
            // (And even when we are seeking we want to update
721
0
            // mCurrentRepeatIteration so we do that first before testing the
722
0
            // seek state.)
723
0
            uint32_t prevRepeatIteration = mCurrentRepeatIteration;
724
0
            if (
725
0
              ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
726
0
              mCurrentRepeatIteration != prevRepeatIteration &&
727
0
              mCurrentRepeatIteration &&
728
0
              mSeekState == SEEK_NOT_SEEKING) {
729
0
              FireTimeEventAsync(eSMILRepeatEvent,
730
0
                            static_cast<int32_t>(mCurrentRepeatIteration));
731
0
            }
732
0
          }
733
0
        }
734
0
        // Otherwise |sampleTime| is *before* the current interval. That
735
0
        // normally doesn't happen but can happen if we get a stray milestone
736
0
        // sample (e.g. if we registered a milestone with a time container that
737
0
        // later got re-attached as a child of a more advanced time container).
738
0
        // In that case we should just ignore the sample.
739
0
      }
740
0
      break;
741
0
742
0
    case STATE_POSTACTIVE:
743
0
      break;
744
0
    }
745
0
746
0
  // Generally we continue driving the state machine so long as we have changed
747
0
  // state. However, for end samples we only drive the state machine as far as
748
0
  // the waiting or postactive state because we don't want to commit to any new
749
0
  // interval (by transitioning to the active state) until all the end samples
750
0
  // have finished and we then have complete information about the available
751
0
  // instance times upon which to base our next interval.
752
0
  } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING &&
753
0
                                          mElementState != STATE_POSTACTIVE)));
754
0
755
0
  if (finishedSeek) {
756
0
    DoPostSeek();
757
0
  }
758
0
  RegisterMilestone();
759
0
}
760
761
void
762
nsSMILTimedElement::HandleContainerTimeChange()
763
0
{
764
0
  // In future we could possibly introduce a separate change notice for time
765
0
  // container changes and only notify those dependents who live in other time
766
0
  // containers. For now we don't bother because when we re-resolve the time in
767
0
  // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we
768
0
  // won't go any further.
769
0
  if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) {
770
0
    NotifyChangedInterval(mCurrentInterval.get(), false, false);
771
0
  }
772
0
}
773
774
namespace
775
{
776
  bool
777
  RemoveNonDynamic(nsSMILInstanceTime* aInstanceTime)
778
0
  {
779
0
    // Generally dynamically-generated instance times (DOM calls, event-based
780
0
    // times) are not associated with their creator nsSMILTimeValueSpec since
781
0
    // they may outlive them.
782
0
    MOZ_ASSERT(!aInstanceTime->IsDynamic() || !aInstanceTime->GetCreator(),
783
0
               "Dynamic instance time should be unlinked from its creator");
784
0
    return !aInstanceTime->IsDynamic() && !aInstanceTime->ShouldPreserve();
785
0
  }
786
} // namespace
787
788
void
789
nsSMILTimedElement::Rewind()
790
0
{
791
0
  MOZ_ASSERT(mAnimationElement,
792
0
             "Got rewind request before being attached to an animation "
793
0
             "element");
794
0
795
0
  // It's possible to get a rewind request whilst we're already in the middle of
796
0
  // a backwards seek. This can happen when we're performing tree surgery and
797
0
  // seeking containers at the same time because we can end up requesting
798
0
  // a local rewind on an element after binding it to a new container and then
799
0
  // performing a rewind on that container as a whole without sampling in
800
0
  // between.
801
0
  //
802
0
  // However, it should currently be impossible to get a rewind in the middle of
803
0
  // a forwards seek since forwards seeks are detected and processed within the
804
0
  // same (re)sample.
805
0
  if (mSeekState == SEEK_NOT_SEEKING) {
806
0
    mSeekState = mElementState == STATE_ACTIVE ?
807
0
                 SEEK_BACKWARD_FROM_ACTIVE :
808
0
                 SEEK_BACKWARD_FROM_INACTIVE;
809
0
  }
810
0
  MOZ_ASSERT(mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
811
0
             mSeekState == SEEK_BACKWARD_FROM_ACTIVE,
812
0
             "Rewind in the middle of a forwards seek?");
813
0
814
0
  ClearTimingState(RemoveNonDynamic);
815
0
  RebuildTimingState(RemoveNonDynamic);
816
0
817
0
  MOZ_ASSERT(!mCurrentInterval,
818
0
             "Current interval is set at end of rewind");
819
0
}
820
821
namespace
822
{
823
  bool
824
  RemoveAll(nsSMILInstanceTime* aInstanceTime)
825
0
  {
826
0
    return true;
827
0
  }
828
} // namespace
829
830
bool
831
nsSMILTimedElement::SetIsDisabled(bool aIsDisabled)
832
0
{
833
0
  if (mIsDisabled == aIsDisabled)
834
0
    return false;
835
0
836
0
  if (aIsDisabled) {
837
0
    mIsDisabled = true;
838
0
    ClearTimingState(RemoveAll);
839
0
  } else {
840
0
    RebuildTimingState(RemoveAll);
841
0
    mIsDisabled = false;
842
0
  }
843
0
  return true;
844
0
}
845
846
namespace
847
{
848
  bool
849
  RemoveNonDOM(nsSMILInstanceTime* aInstanceTime)
850
0
  {
851
0
    return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve();
852
0
  }
853
} // namespace
854
855
bool
856
nsSMILTimedElement::SetAttr(nsAtom* aAttribute,
857
                            const nsAString& aValue,
858
                            nsAttrValue& aResult,
859
                            Element& aContextElement,
860
                            nsresult* aParseResult)
861
0
{
862
0
  bool foundMatch = true;
863
0
  nsresult parseResult = NS_OK;
864
0
865
0
  if (aAttribute == nsGkAtoms::begin) {
866
0
    parseResult = SetBeginSpec(aValue, aContextElement, RemoveNonDOM);
867
0
  } else if (aAttribute == nsGkAtoms::dur) {
868
0
    parseResult = SetSimpleDuration(aValue);
869
0
  } else if (aAttribute == nsGkAtoms::end) {
870
0
    parseResult = SetEndSpec(aValue, aContextElement, RemoveNonDOM);
871
0
  } else if (aAttribute == nsGkAtoms::fill) {
872
0
    parseResult = SetFillMode(aValue);
873
0
  } else if (aAttribute == nsGkAtoms::max) {
874
0
    parseResult = SetMax(aValue);
875
0
  } else if (aAttribute == nsGkAtoms::min) {
876
0
    parseResult = SetMin(aValue);
877
0
  } else if (aAttribute == nsGkAtoms::repeatCount) {
878
0
    parseResult = SetRepeatCount(aValue);
879
0
  } else if (aAttribute == nsGkAtoms::repeatDur) {
880
0
    parseResult = SetRepeatDur(aValue);
881
0
  } else if (aAttribute == nsGkAtoms::restart) {
882
0
    parseResult = SetRestart(aValue);
883
0
  } else {
884
0
    foundMatch = false;
885
0
  }
886
0
887
0
  if (foundMatch) {
888
0
    aResult.SetTo(aValue);
889
0
    if (aParseResult) {
890
0
      *aParseResult = parseResult;
891
0
    }
892
0
  }
893
0
894
0
  return foundMatch;
895
0
}
896
897
bool
898
nsSMILTimedElement::UnsetAttr(nsAtom* aAttribute)
899
0
{
900
0
  bool foundMatch = true;
901
0
902
0
  if (aAttribute == nsGkAtoms::begin) {
903
0
    UnsetBeginSpec(RemoveNonDOM);
904
0
  } else if (aAttribute == nsGkAtoms::dur) {
905
0
    UnsetSimpleDuration();
906
0
  } else if (aAttribute == nsGkAtoms::end) {
907
0
    UnsetEndSpec(RemoveNonDOM);
908
0
  } else if (aAttribute == nsGkAtoms::fill) {
909
0
    UnsetFillMode();
910
0
  } else if (aAttribute == nsGkAtoms::max) {
911
0
    UnsetMax();
912
0
  } else if (aAttribute == nsGkAtoms::min) {
913
0
    UnsetMin();
914
0
  } else if (aAttribute == nsGkAtoms::repeatCount) {
915
0
    UnsetRepeatCount();
916
0
  } else if (aAttribute == nsGkAtoms::repeatDur) {
917
0
    UnsetRepeatDur();
918
0
  } else if (aAttribute == nsGkAtoms::restart) {
919
0
    UnsetRestart();
920
0
  } else {
921
0
    foundMatch = false;
922
0
  }
923
0
924
0
  return foundMatch;
925
0
}
926
927
//----------------------------------------------------------------------
928
// Setters and unsetters
929
930
nsresult
931
nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec,
932
                                 Element& aContextElement,
933
                                 RemovalTestFunction aRemove)
934
0
{
935
0
  return SetBeginOrEndSpec(aBeginSpec, aContextElement, true /*isBegin*/,
936
0
                           aRemove);
937
0
}
938
939
void
940
nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove)
941
0
{
942
0
  ClearSpecs(mBeginSpecs, mBeginInstances, aRemove);
943
0
  UpdateCurrentInterval();
944
0
}
945
946
nsresult
947
nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec,
948
                               Element& aContextElement,
949
                               RemovalTestFunction aRemove)
950
0
{
951
0
  return SetBeginOrEndSpec(aEndSpec, aContextElement, false /*!isBegin*/,
952
0
                           aRemove);
953
0
}
954
955
void
956
nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove)
957
0
{
958
0
  ClearSpecs(mEndSpecs, mEndInstances, aRemove);
959
0
  UpdateCurrentInterval();
960
0
}
961
962
nsresult
963
nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
964
0
{
965
0
  // Update the current interval before returning
966
0
  AutoIntervalUpdater updater(*this);
967
0
968
0
  nsSMILTimeValue duration;
969
0
  const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec);
970
0
971
0
  // SVG-specific: "For SVG's animation elements, if "media" is specified, the
972
0
  // attribute will be ignored." (SVG 1.1, section 19.2.6)
973
0
  if (dur.EqualsLiteral("media") || dur.EqualsLiteral("indefinite")) {
974
0
    duration.SetIndefinite();
975
0
  } else {
976
0
    if (!nsSMILParserUtils::ParseClockValue(dur, &duration) ||
977
0
        duration.GetMillis() == 0L) {
978
0
      mSimpleDur.SetIndefinite();
979
0
      return NS_ERROR_FAILURE;
980
0
    }
981
0
  }
982
0
  // mSimpleDur should never be unresolved. ParseClockValue will either set
983
0
  // duration to resolved or will return false.
984
0
  MOZ_ASSERT(duration.IsResolved(),
985
0
             "Setting unresolved simple duration");
986
0
987
0
  mSimpleDur = duration;
988
0
989
0
  return NS_OK;
990
0
}
991
992
void
993
nsSMILTimedElement::UnsetSimpleDuration()
994
0
{
995
0
  mSimpleDur.SetIndefinite();
996
0
  UpdateCurrentInterval();
997
0
}
998
999
nsresult
1000
nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
1001
0
{
1002
0
  // Update the current interval before returning
1003
0
  AutoIntervalUpdater updater(*this);
1004
0
1005
0
  nsSMILTimeValue duration;
1006
0
  const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec);
1007
0
1008
0
  if (min.EqualsLiteral("media")) {
1009
0
    duration.SetMillis(0L);
1010
0
  } else {
1011
0
    if (!nsSMILParserUtils::ParseClockValue(min, &duration)) {
1012
0
      mMin.SetMillis(0L);
1013
0
      return NS_ERROR_FAILURE;
1014
0
    }
1015
0
  }
1016
0
1017
0
  MOZ_ASSERT(duration.GetMillis() >= 0L, "Invalid duration");
1018
0
1019
0
  mMin = duration;
1020
0
1021
0
  return NS_OK;
1022
0
}
1023
1024
void
1025
nsSMILTimedElement::UnsetMin()
1026
0
{
1027
0
  mMin.SetMillis(0L);
1028
0
  UpdateCurrentInterval();
1029
0
}
1030
1031
nsresult
1032
nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
1033
0
{
1034
0
  // Update the current interval before returning
1035
0
  AutoIntervalUpdater updater(*this);
1036
0
1037
0
  nsSMILTimeValue duration;
1038
0
  const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec);
1039
0
1040
0
  if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) {
1041
0
    duration.SetIndefinite();
1042
0
  } else {
1043
0
    if (!nsSMILParserUtils::ParseClockValue(max, &duration) ||
1044
0
        duration.GetMillis() == 0L) {
1045
0
      mMax.SetIndefinite();
1046
0
      return NS_ERROR_FAILURE;
1047
0
    }
1048
0
    MOZ_ASSERT(duration.GetMillis() > 0L, "Invalid duration");
1049
0
  }
1050
0
1051
0
  mMax = duration;
1052
0
1053
0
  return NS_OK;
1054
0
}
1055
1056
void
1057
nsSMILTimedElement::UnsetMax()
1058
0
{
1059
0
  mMax.SetIndefinite();
1060
0
  UpdateCurrentInterval();
1061
0
}
1062
1063
nsresult
1064
nsSMILTimedElement::SetRestart(const nsAString& aRestartSpec)
1065
0
{
1066
0
  nsAttrValue temp;
1067
0
  bool parseResult
1068
0
    = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true);
1069
0
  mRestartMode = parseResult
1070
0
               ? nsSMILRestartMode(temp.GetEnumValue())
1071
0
               : RESTART_ALWAYS;
1072
0
  UpdateCurrentInterval();
1073
0
  return parseResult ? NS_OK : NS_ERROR_FAILURE;
1074
0
}
1075
1076
void
1077
nsSMILTimedElement::UnsetRestart()
1078
0
{
1079
0
  mRestartMode = RESTART_ALWAYS;
1080
0
  UpdateCurrentInterval();
1081
0
}
1082
1083
nsresult
1084
nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec)
1085
0
{
1086
0
  // Update the current interval before returning
1087
0
  AutoIntervalUpdater updater(*this);
1088
0
1089
0
  nsSMILRepeatCount newRepeatCount;
1090
0
1091
0
  if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) {
1092
0
    mRepeatCount = newRepeatCount;
1093
0
    return NS_OK;
1094
0
  }
1095
0
  mRepeatCount.Unset();
1096
0
  return NS_ERROR_FAILURE;
1097
0
}
1098
1099
void
1100
nsSMILTimedElement::UnsetRepeatCount()
1101
0
{
1102
0
  mRepeatCount.Unset();
1103
0
  UpdateCurrentInterval();
1104
0
}
1105
1106
nsresult
1107
nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
1108
0
{
1109
0
  // Update the current interval before returning
1110
0
  AutoIntervalUpdater updater(*this);
1111
0
1112
0
  nsSMILTimeValue duration;
1113
0
1114
0
  const nsAString& repeatDur =
1115
0
    nsSMILParserUtils::TrimWhitespace(aRepeatDurSpec);
1116
0
1117
0
  if (repeatDur.EqualsLiteral("indefinite")) {
1118
0
    duration.SetIndefinite();
1119
0
  } else {
1120
0
    if (!nsSMILParserUtils::ParseClockValue(repeatDur, &duration)) {
1121
0
      mRepeatDur.SetUnresolved();
1122
0
      return NS_ERROR_FAILURE;
1123
0
    }
1124
0
  }
1125
0
1126
0
  mRepeatDur = duration;
1127
0
1128
0
  return NS_OK;
1129
0
}
1130
1131
void
1132
nsSMILTimedElement::UnsetRepeatDur()
1133
0
{
1134
0
  mRepeatDur.SetUnresolved();
1135
0
  UpdateCurrentInterval();
1136
0
}
1137
1138
nsresult
1139
nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec)
1140
0
{
1141
0
  uint16_t previousFillMode = mFillMode;
1142
0
1143
0
  nsAttrValue temp;
1144
0
  bool parseResult =
1145
0
    temp.ParseEnumValue(aFillModeSpec, sFillModeTable, true);
1146
0
  mFillMode = parseResult
1147
0
            ? nsSMILFillMode(temp.GetEnumValue())
1148
0
            : FILL_REMOVE;
1149
0
1150
0
  // Update fill mode of client
1151
0
  if (mFillMode != previousFillMode && HasClientInFillRange()) {
1152
0
    mClient->Inactivate(mFillMode == FILL_FREEZE);
1153
0
    SampleFillValue();
1154
0
  }
1155
0
1156
0
  return parseResult ? NS_OK : NS_ERROR_FAILURE;
1157
0
}
1158
1159
void
1160
nsSMILTimedElement::UnsetFillMode()
1161
0
{
1162
0
  uint16_t previousFillMode = mFillMode;
1163
0
  mFillMode = FILL_REMOVE;
1164
0
  if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) {
1165
0
    mClient->Inactivate(false);
1166
0
  }
1167
0
}
1168
1169
void
1170
nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent)
1171
0
{
1172
0
  // There's probably no harm in attempting to register a dependent
1173
0
  // nsSMILTimeValueSpec twice, but we're not expecting it to happen.
1174
0
  MOZ_ASSERT(!mTimeDependents.GetEntry(&aDependent),
1175
0
             "nsSMILTimeValueSpec is already registered as a dependency");
1176
0
  mTimeDependents.PutEntry(&aDependent);
1177
0
1178
0
  // Add current interval. We could add historical intervals too but that would
1179
0
  // cause unpredictable results since some intervals may have been filtered.
1180
0
  // SMIL doesn't say what to do here so for simplicity and consistency we
1181
0
  // simply add the current interval if there is one.
1182
0
  //
1183
0
  // It's not necessary to call SyncPauseTime since we're dealing with
1184
0
  // historical instance times not newly added ones.
1185
0
  if (mCurrentInterval) {
1186
0
    aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer());
1187
0
  }
1188
0
}
1189
1190
void
1191
nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent)
1192
0
{
1193
0
  mTimeDependents.RemoveEntry(&aDependent);
1194
0
}
1195
1196
bool
1197
nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const
1198
0
{
1199
0
  const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance();
1200
0
  const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance();
1201
0
1202
0
  if (!thisBegin || !otherBegin)
1203
0
    return false;
1204
0
1205
0
  return thisBegin->IsDependentOn(*otherBegin);
1206
0
}
1207
1208
void
1209
nsSMILTimedElement::BindToTree(Element& aContextElement)
1210
0
{
1211
0
  // Reset previously registered milestone since we may be registering with
1212
0
  // a different time container now.
1213
0
  mPrevRegisteredMilestone = sMaxMilestone;
1214
0
1215
0
  // If we were already active then clear all our timing information and start
1216
0
  // afresh
1217
0
  if (mElementState != STATE_STARTUP) {
1218
0
    mSeekState = SEEK_NOT_SEEKING;
1219
0
    Rewind();
1220
0
  }
1221
0
1222
0
  // Scope updateBatcher to last only for the ResolveReferences calls:
1223
0
  {
1224
0
    AutoIntervalUpdateBatcher updateBatcher(*this);
1225
0
1226
0
    // Resolve references to other parts of the tree
1227
0
    uint32_t count = mBeginSpecs.Length();
1228
0
    for (uint32_t i = 0; i < count; ++i) {
1229
0
      mBeginSpecs[i]->ResolveReferences(aContextElement);
1230
0
    }
1231
0
1232
0
    count = mEndSpecs.Length();
1233
0
    for (uint32_t j = 0; j < count; ++j) {
1234
0
      mEndSpecs[j]->ResolveReferences(aContextElement);
1235
0
    }
1236
0
  }
1237
0
1238
0
  RegisterMilestone();
1239
0
}
1240
1241
void
1242
nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget)
1243
0
{
1244
0
  AutoIntervalUpdateBatcher updateBatcher(*this);
1245
0
1246
0
  uint32_t count = mBeginSpecs.Length();
1247
0
  for (uint32_t i = 0; i < count; ++i) {
1248
0
    mBeginSpecs[i]->HandleTargetElementChange(aNewTarget);
1249
0
  }
1250
0
1251
0
  count = mEndSpecs.Length();
1252
0
  for (uint32_t j = 0; j < count; ++j) {
1253
0
    mEndSpecs[j]->HandleTargetElementChange(aNewTarget);
1254
0
  }
1255
0
}
1256
1257
void
1258
nsSMILTimedElement::Traverse(nsCycleCollectionTraversalCallback* aCallback)
1259
0
{
1260
0
  uint32_t count = mBeginSpecs.Length();
1261
0
  for (uint32_t i = 0; i < count; ++i) {
1262
0
    nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i].get();
1263
0
    MOZ_ASSERT(beginSpec,
1264
0
               "null nsSMILTimeValueSpec in list of begin specs");
1265
0
    beginSpec->Traverse(aCallback);
1266
0
  }
1267
0
1268
0
  count = mEndSpecs.Length();
1269
0
  for (uint32_t j = 0; j < count; ++j) {
1270
0
    nsSMILTimeValueSpec* endSpec = mEndSpecs[j].get();
1271
0
    MOZ_ASSERT(endSpec, "null nsSMILTimeValueSpec in list of end specs");
1272
0
    endSpec->Traverse(aCallback);
1273
0
  }
1274
0
}
1275
1276
void
1277
nsSMILTimedElement::Unlink()
1278
0
{
1279
0
  AutoIntervalUpdateBatcher updateBatcher(*this);
1280
0
1281
0
  // Remove dependencies on other elements
1282
0
  uint32_t count = mBeginSpecs.Length();
1283
0
  for (uint32_t i = 0; i < count; ++i) {
1284
0
    nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i].get();
1285
0
    MOZ_ASSERT(beginSpec,
1286
0
               "null nsSMILTimeValueSpec in list of begin specs");
1287
0
    beginSpec->Unlink();
1288
0
  }
1289
0
1290
0
  count = mEndSpecs.Length();
1291
0
  for (uint32_t j = 0; j < count; ++j) {
1292
0
    nsSMILTimeValueSpec* endSpec = mEndSpecs[j].get();
1293
0
    MOZ_ASSERT(endSpec, "null nsSMILTimeValueSpec in list of end specs");
1294
0
    endSpec->Unlink();
1295
0
  }
1296
0
1297
0
  ClearIntervals();
1298
0
1299
0
  // Make sure we don't notify other elements of new intervals
1300
0
  mTimeDependents.Clear();
1301
0
}
1302
1303
//----------------------------------------------------------------------
1304
// Implementation helpers
1305
1306
nsresult
1307
nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
1308
                                      Element& aContextElement,
1309
                                      bool aIsBegin,
1310
                                      RemovalTestFunction aRemove)
1311
0
{
1312
0
  TimeValueSpecList& timeSpecsList = aIsBegin ? mBeginSpecs : mEndSpecs;
1313
0
  InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
1314
0
1315
0
  ClearSpecs(timeSpecsList, instances, aRemove);
1316
0
1317
0
  AutoIntervalUpdateBatcher updateBatcher(*this);
1318
0
1319
0
  nsCharSeparatedTokenizer tokenizer(aSpec, ';');
1320
0
  if (!tokenizer.hasMoreTokens()) { // Empty list
1321
0
    return NS_ERROR_FAILURE;
1322
0
  }
1323
0
1324
0
  bool hadFailure = false;
1325
0
  while (tokenizer.hasMoreTokens()) {
1326
0
    auto spec = MakeUnique<nsSMILTimeValueSpec>(*this, aIsBegin);
1327
0
    nsresult rv = spec->SetSpec(tokenizer.nextToken(), aContextElement);
1328
0
    if (NS_SUCCEEDED(rv)) {
1329
0
      timeSpecsList.AppendElement(std::move(spec));
1330
0
    } else {
1331
0
      hadFailure = true;
1332
0
    }
1333
0
  }
1334
0
1335
0
  // The return value from this function is only used to determine if we should
1336
0
  // print a console message or not, so we return failure if we had one or more
1337
0
  // failures but we don't need to differentiate between different types of
1338
0
  // failures or the number of failures.
1339
0
  return hadFailure ? NS_ERROR_FAILURE : NS_OK;
1340
0
}
1341
1342
namespace
1343
{
1344
  // Adaptor functor for RemoveInstanceTimes that allows us to use function
1345
  // pointers instead.
1346
  // Without this we'd have to either templatize ClearSpecs and all its callers
1347
  // or pass bool flags around to specify which removal function to use here.
1348
  class MOZ_STACK_CLASS RemoveByFunction
1349
  {
1350
  public:
1351
    explicit RemoveByFunction(nsSMILTimedElement::RemovalTestFunction aFunction)
1352
0
      : mFunction(aFunction) { }
1353
    bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
1354
0
    {
1355
0
      return mFunction(aInstanceTime);
1356
0
    }
1357
1358
  private:
1359
    nsSMILTimedElement::RemovalTestFunction mFunction;
1360
  };
1361
} // namespace
1362
1363
void
1364
nsSMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs,
1365
                               InstanceTimeList& aInstances,
1366
                               RemovalTestFunction aRemove)
1367
0
{
1368
0
  AutoIntervalUpdateBatcher updateBatcher(*this);
1369
0
1370
0
  for (uint32_t i = 0; i < aSpecs.Length(); ++i) {
1371
0
    aSpecs[i]->Unlink();
1372
0
  }
1373
0
  aSpecs.Clear();
1374
0
1375
0
  RemoveByFunction removeByFunction(aRemove);
1376
0
  RemoveInstanceTimes(aInstances, removeByFunction);
1377
0
}
1378
1379
void
1380
nsSMILTimedElement::ClearIntervals()
1381
0
{
1382
0
  if (mElementState != STATE_STARTUP) {
1383
0
    mElementState = STATE_POSTACTIVE;
1384
0
  }
1385
0
  mCurrentRepeatIteration = 0;
1386
0
  ResetCurrentInterval();
1387
0
1388
0
  // Remove old intervals
1389
0
  for (int32_t i = mOldIntervals.Length() - 1; i >= 0; --i) {
1390
0
    mOldIntervals[i]->Unlink();
1391
0
  }
1392
0
  mOldIntervals.Clear();
1393
0
}
1394
1395
bool
1396
nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime)
1397
0
{
1398
0
  // This should only be called within DoSampleAt as a helper function
1399
0
  MOZ_ASSERT(mElementState == STATE_ACTIVE,
1400
0
             "Unexpected state to try to apply an early end");
1401
0
1402
0
  bool updated = false;
1403
0
1404
0
  // Only apply an early end if we're not already ending.
1405
0
  if (mCurrentInterval->End()->Time() > aSampleTime) {
1406
0
    nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime);
1407
0
    if (earlyEnd) {
1408
0
      if (earlyEnd->IsDependent()) {
1409
0
        // Generate a new instance time for the early end since the
1410
0
        // existing instance time is part of some dependency chain that we
1411
0
        // don't want to participate in.
1412
0
        RefPtr<nsSMILInstanceTime> newEarlyEnd =
1413
0
          new nsSMILInstanceTime(earlyEnd->Time());
1414
0
        mCurrentInterval->SetEnd(*newEarlyEnd);
1415
0
      } else {
1416
0
        mCurrentInterval->SetEnd(*earlyEnd);
1417
0
      }
1418
0
      updated = true;
1419
0
    }
1420
0
  }
1421
0
  return updated;
1422
0
}
1423
1424
namespace
1425
{
1426
  class MOZ_STACK_CLASS RemoveReset
1427
  {
1428
  public:
1429
    explicit RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin)
1430
0
      : mCurrentIntervalBegin(aCurrentIntervalBegin) { }
1431
    bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
1432
0
    {
1433
0
      // SMIL 3.0 section 5.4.3, 'Resetting element state':
1434
0
      //   Any instance times associated with past Event-values, Repeat-values,
1435
0
      //   Accesskey-values or added via DOM method calls are removed from the
1436
0
      //   dependent begin and end instance times lists. In effect, all events
1437
0
      //   and DOM methods calls in the past are cleared. This does not apply to
1438
0
      //   an instance time that defines the begin of the current interval.
1439
0
      return aInstanceTime->IsDynamic() &&
1440
0
             !aInstanceTime->ShouldPreserve() &&
1441
0
             (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin);
1442
0
    }
1443
1444
  private:
1445
    const nsSMILInstanceTime* mCurrentIntervalBegin;
1446
  };
1447
} // namespace
1448
1449
void
1450
nsSMILTimedElement::Reset()
1451
0
{
1452
0
  RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nullptr);
1453
0
  RemoveInstanceTimes(mBeginInstances, resetBegin);
1454
0
1455
0
  RemoveReset resetEnd(nullptr);
1456
0
  RemoveInstanceTimes(mEndInstances, resetEnd);
1457
0
}
1458
1459
void
1460
nsSMILTimedElement::ClearTimingState(RemovalTestFunction aRemove)
1461
0
{
1462
0
  mElementState = STATE_STARTUP;
1463
0
  ClearIntervals();
1464
0
1465
0
  UnsetBeginSpec(aRemove);
1466
0
  UnsetEndSpec(aRemove);
1467
0
1468
0
  if (mClient) {
1469
0
    mClient->Inactivate(false);
1470
0
  }
1471
0
}
1472
1473
void
1474
nsSMILTimedElement::RebuildTimingState(RemovalTestFunction aRemove)
1475
0
{
1476
0
  MOZ_ASSERT(mAnimationElement,
1477
0
             "Attempting to enable a timed element not attached to an "
1478
0
             "animation element");
1479
0
  MOZ_ASSERT(mElementState == STATE_STARTUP,
1480
0
             "Rebuilding timing state from non-startup state");
1481
0
1482
0
  if (mAnimationElement->HasAttr(nsGkAtoms::begin)) {
1483
0
    nsAutoString attValue;
1484
0
    mAnimationElement->GetAttr(nsGkAtoms::begin, attValue);
1485
0
    SetBeginSpec(attValue, *mAnimationElement, aRemove);
1486
0
  }
1487
0
1488
0
  if (mAnimationElement->HasAttr(nsGkAtoms::end)) {
1489
0
    nsAutoString attValue;
1490
0
    mAnimationElement->GetAttr(nsGkAtoms::end, attValue);
1491
0
    SetEndSpec(attValue, *mAnimationElement, aRemove);
1492
0
  }
1493
0
1494
0
  mPrevRegisteredMilestone = sMaxMilestone;
1495
0
  RegisterMilestone();
1496
0
}
1497
1498
void
1499
nsSMILTimedElement::DoPostSeek()
1500
0
{
1501
0
  // Finish backwards seek
1502
0
  if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
1503
0
      mSeekState == SEEK_BACKWARD_FROM_ACTIVE) {
1504
0
    // Previously some dynamic instance times may have been marked to be
1505
0
    // preserved because they were endpoints of an historic interval (which may
1506
0
    // or may not have been filtered). Now that we've finished a seek we should
1507
0
    // clear that flag for those instance times whose intervals are no longer
1508
0
    // historic.
1509
0
    UnpreserveInstanceTimes(mBeginInstances);
1510
0
    UnpreserveInstanceTimes(mEndInstances);
1511
0
1512
0
    // Now that the times have been unmarked perform a reset. This might seem
1513
0
    // counter-intuitive when we're only doing a seek within an interval but
1514
0
    // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing':
1515
0
    //   Resolved end times associated with events, Repeat-values,
1516
0
    //   Accesskey-values or added via DOM method calls are cleared when seeking
1517
0
    //   to time earlier than the resolved end time.
1518
0
    Reset();
1519
0
    UpdateCurrentInterval();
1520
0
  }
1521
0
1522
0
  switch (mSeekState)
1523
0
  {
1524
0
  case SEEK_FORWARD_FROM_ACTIVE:
1525
0
  case SEEK_BACKWARD_FROM_ACTIVE:
1526
0
    if (mElementState != STATE_ACTIVE) {
1527
0
      FireTimeEventAsync(eSMILEndEvent, 0);
1528
0
    }
1529
0
    break;
1530
0
1531
0
  case SEEK_FORWARD_FROM_INACTIVE:
1532
0
  case SEEK_BACKWARD_FROM_INACTIVE:
1533
0
    if (mElementState == STATE_ACTIVE) {
1534
0
      FireTimeEventAsync(eSMILBeginEvent, 0);
1535
0
    }
1536
0
    break;
1537
0
1538
0
  case SEEK_NOT_SEEKING:
1539
0
    /* Do nothing */
1540
0
    break;
1541
0
  }
1542
0
1543
0
  mSeekState = SEEK_NOT_SEEKING;
1544
0
}
1545
1546
void
1547
nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList)
1548
0
{
1549
0
  const nsSMILInterval* prevInterval = GetPreviousInterval();
1550
0
  const nsSMILInstanceTime* cutoff = mCurrentInterval ?
1551
0
      mCurrentInterval->Begin() :
1552
0
      prevInterval ? prevInterval->Begin() : nullptr;
1553
0
  uint32_t count = aList.Length();
1554
0
  for (uint32_t i = 0; i < count; ++i) {
1555
0
    nsSMILInstanceTime* instance = aList[i].get();
1556
0
    if (!cutoff || cutoff->Time().CompareTo(instance->Time()) < 0) {
1557
0
      instance->UnmarkShouldPreserve();
1558
0
    }
1559
0
  }
1560
0
}
1561
1562
void
1563
nsSMILTimedElement::FilterHistory()
1564
0
{
1565
0
  // We should filter the intervals first, since instance times still used in an
1566
0
  // interval won't be filtered.
1567
0
  FilterIntervals();
1568
0
  FilterInstanceTimes(mBeginInstances);
1569
0
  FilterInstanceTimes(mEndInstances);
1570
0
}
1571
1572
void
1573
nsSMILTimedElement::FilterIntervals()
1574
0
{
1575
0
  // We can filter old intervals that:
1576
0
  //
1577
0
  // a) are not the previous interval; AND
1578
0
  // b) are not in the middle of a dependency chain; AND
1579
0
  // c) are not the first interval
1580
0
  //
1581
0
  // Condition (a) is necessary since the previous interval is used for applying
1582
0
  // fill effects and updating the current interval.
1583
0
  //
1584
0
  // Condition (b) is necessary since even if this interval itself is not
1585
0
  // active, it may be part of a dependency chain that includes active
1586
0
  // intervals. Such chains are used to establish priorities within the
1587
0
  // animation sandwich.
1588
0
  //
1589
0
  // Condition (c) is necessary to support hyperlinks that target animations
1590
0
  // since in some cases the defined behavior is to seek the document back to
1591
0
  // the first resolved begin time. Presumably the intention here is not
1592
0
  // actually to use the first resolved begin time, the
1593
0
  // _the_first_resolved_begin_time_that_produced_an_interval. That is,
1594
0
  // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek
1595
0
  // to 1s. The spec doesn't say this but I'm pretty sure that is the intention.
1596
0
  // It seems negative times were simply not considered.
1597
0
  //
1598
0
  // Although the above conditions allow us to safely filter intervals for most
1599
0
  // scenarios they do not cover all cases and there will still be scenarios
1600
0
  // that generate intervals indefinitely. In such a case we simply set
1601
0
  // a maximum number of intervals and drop any intervals beyond that threshold.
1602
0
1603
0
  uint32_t threshold = mOldIntervals.Length() > sMaxNumIntervals ?
1604
0
                       mOldIntervals.Length() - sMaxNumIntervals :
1605
0
                       0;
1606
0
  IntervalList filteredList;
1607
0
  for (uint32_t i = 0; i < mOldIntervals.Length(); ++i)
1608
0
  {
1609
0
    nsSMILInterval* interval = mOldIntervals[i].get();
1610
0
    if (i != 0 && /*skip first interval*/
1611
0
        i + 1 < mOldIntervals.Length() && /*skip previous interval*/
1612
0
        (i < threshold || !interval->IsDependencyChainLink())) {
1613
0
      interval->Unlink(true /*filtered, not deleted*/);
1614
0
    } else {
1615
0
      filteredList.AppendElement(std::move(mOldIntervals[i]));
1616
0
    }
1617
0
  }
1618
0
  mOldIntervals.Clear();
1619
0
  mOldIntervals.SwapElements(filteredList);
1620
0
}
1621
1622
namespace
1623
{
1624
  class MOZ_STACK_CLASS RemoveFiltered
1625
  {
1626
  public:
1627
0
    explicit RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { }
1628
    bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
1629
0
    {
1630
0
      // We can filter instance times that:
1631
0
      // a) Precede the end point of the previous interval; AND
1632
0
      // b) Are NOT syncbase times that might be updated to a time after the end
1633
0
      //    point of the previous interval; AND
1634
0
      // c) Are NOT fixed end points in any remaining interval.
1635
0
      return aInstanceTime->Time() < mCutoff &&
1636
0
             aInstanceTime->IsFixedTime() &&
1637
0
             !aInstanceTime->ShouldPreserve();
1638
0
    }
1639
1640
  private:
1641
    nsSMILTimeValue mCutoff;
1642
  };
1643
1644
  class MOZ_STACK_CLASS RemoveBelowThreshold
1645
  {
1646
  public:
1647
    RemoveBelowThreshold(uint32_t aThreshold,
1648
                         nsTArray<const nsSMILInstanceTime *>& aTimesToKeep)
1649
      : mThreshold(aThreshold),
1650
0
        mTimesToKeep(aTimesToKeep) { }
1651
    bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t aIndex)
1652
0
    {
1653
0
      return aIndex < mThreshold && !mTimesToKeep.Contains(aInstanceTime);
1654
0
    }
1655
1656
  private:
1657
    uint32_t mThreshold;
1658
    nsTArray<const nsSMILInstanceTime *>& mTimesToKeep;
1659
  };
1660
} // namespace
1661
1662
void
1663
nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList)
1664
0
{
1665
0
  if (GetPreviousInterval()) {
1666
0
    RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time());
1667
0
    RemoveInstanceTimes(aList, removeFiltered);
1668
0
  }
1669
0
1670
0
  // As with intervals it is possible to create a document that, even despite
1671
0
  // our most aggressive filtering, will generate instance times indefinitely
1672
0
  // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as
1673
0
  // they're unpredictable due to the possibility of seeking the document which
1674
0
  // may prevent some events from being generated). Therefore we introduce
1675
0
  // a hard cutoff at which point we just drop the oldest instance times.
1676
0
  if (aList.Length() > sMaxNumInstanceTimes) {
1677
0
    uint32_t threshold = aList.Length() - sMaxNumInstanceTimes;
1678
0
    // There are a few instance times we should keep though, notably:
1679
0
    // - the current interval begin time,
1680
0
    // - the previous interval end time (see note in RemoveInstanceTimes)
1681
0
    // - the first interval begin time (see note in FilterIntervals)
1682
0
    nsTArray<const nsSMILInstanceTime *> timesToKeep;
1683
0
    if (mCurrentInterval) {
1684
0
      timesToKeep.AppendElement(mCurrentInterval->Begin());
1685
0
    }
1686
0
    const nsSMILInterval* prevInterval = GetPreviousInterval();
1687
0
    if (prevInterval) {
1688
0
      timesToKeep.AppendElement(prevInterval->End());
1689
0
    }
1690
0
    if (!mOldIntervals.IsEmpty()) {
1691
0
      timesToKeep.AppendElement(mOldIntervals[0]->Begin());
1692
0
    }
1693
0
    RemoveBelowThreshold removeBelowThreshold(threshold, timesToKeep);
1694
0
    RemoveInstanceTimes(aList, removeBelowThreshold);
1695
0
  }
1696
0
}
1697
1698
//
1699
// This method is based on the pseudocode given in the SMILANIM spec.
1700
//
1701
// See:
1702
// http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start
1703
//
1704
bool
1705
nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval,
1706
                                    const nsSMILInterval* aReplacedInterval,
1707
                                    const nsSMILInstanceTime* aFixedBeginTime,
1708
                                    nsSMILInterval& aResult) const
1709
0
{
1710
0
  MOZ_ASSERT(!aFixedBeginTime || aFixedBeginTime->Time().IsDefinite(),
1711
0
             "Unresolved or indefinite begin time given for interval start");
1712
0
  static const nsSMILTimeValue zeroTime(0L);
1713
0
1714
0
  if (mRestartMode == RESTART_NEVER && aPrevInterval)
1715
0
    return false;
1716
0
1717
0
  // Calc starting point
1718
0
  nsSMILTimeValue beginAfter;
1719
0
  bool prevIntervalWasZeroDur = false;
1720
0
  if (aPrevInterval) {
1721
0
    beginAfter = aPrevInterval->End()->Time();
1722
0
    prevIntervalWasZeroDur
1723
0
      = aPrevInterval->End()->Time() == aPrevInterval->Begin()->Time();
1724
0
  } else {
1725
0
    beginAfter.SetMillis(INT64_MIN);
1726
0
  }
1727
0
1728
0
  RefPtr<nsSMILInstanceTime> tempBegin;
1729
0
  RefPtr<nsSMILInstanceTime> tempEnd;
1730
0
1731
0
  while (true) {
1732
0
    // Calculate begin time
1733
0
    if (aFixedBeginTime) {
1734
0
      if (aFixedBeginTime->Time() < beginAfter) {
1735
0
        return false;
1736
0
      }
1737
0
      // our ref-counting is not const-correct
1738
0
      tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime);
1739
0
    } else if ((!mAnimationElement ||
1740
0
                !mAnimationElement->HasAttr(nsGkAtoms::begin)) &&
1741
0
               beginAfter <= zeroTime) {
1742
0
      tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0));
1743
0
    } else {
1744
0
      int32_t beginPos = 0;
1745
0
      do {
1746
0
        tempBegin =
1747
0
          GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos);
1748
0
        if (!tempBegin || !tempBegin->Time().IsDefinite()) {
1749
0
          return false;
1750
0
        }
1751
0
      // If we're updating the current interval then skip any begin time that is
1752
0
      // dependent on the current interval's begin time. e.g.
1753
0
      //   <animate id="a" begin="b.begin; a.begin+2s"...
1754
0
      // If b's interval disappears whilst 'a' is in the waiting state the begin
1755
0
      // time at "a.begin+2s" should be skipped since 'a' never begun.
1756
0
      } while (aReplacedInterval &&
1757
0
               tempBegin->GetBaseTime() == aReplacedInterval->Begin());
1758
0
    }
1759
0
    MOZ_ASSERT(tempBegin && tempBegin->Time().IsDefinite() &&
1760
0
               tempBegin->Time() >= beginAfter,
1761
0
               "Got a bad begin time while fetching next interval");
1762
0
1763
0
    // Calculate end time
1764
0
    {
1765
0
      int32_t endPos = 0;
1766
0
      do {
1767
0
        tempEnd =
1768
0
          GetNextGreaterOrEqual(mEndInstances, tempBegin->Time(), endPos);
1769
0
1770
0
        // SMIL doesn't allow for coincident zero-duration intervals, so if the
1771
0
        // previous interval was zero-duration, and tempEnd is going to give us
1772
0
        // another zero duration interval, then look for another end to use
1773
0
        // instead.
1774
0
        if (tempEnd && prevIntervalWasZeroDur &&
1775
0
            tempEnd->Time() == beginAfter) {
1776
0
          tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos);
1777
0
        }
1778
0
      // As above with begin times, avoid creating self-referential loops
1779
0
      // between instance times by checking that the newly found end instance
1780
0
      // time is not already dependent on the end of the current interval.
1781
0
      } while (tempEnd && aReplacedInterval &&
1782
0
               tempEnd->GetBaseTime() == aReplacedInterval->End());
1783
0
1784
0
      if (!tempEnd) {
1785
0
        // If all the ends are before the beginning we have a bad interval
1786
0
        // UNLESS:
1787
0
        // a) We never had any end attribute to begin with (the SMIL pseudocode
1788
0
        //    places this condition earlier in the flow but that fails to allow
1789
0
        //    for DOM calls when no "indefinite" condition is given), OR
1790
0
        // b) We never had any end instance times to begin with, OR
1791
0
        // c) We have end events which leave the interval open-ended.
1792
0
        bool openEndedIntervalOk = mEndSpecs.IsEmpty() ||
1793
0
                                   mEndInstances.IsEmpty() ||
1794
0
                                   EndHasEventConditions();
1795
0
1796
0
        // The above conditions correspond with the SMIL pseudocode but SMIL
1797
0
        // doesn't address self-dependent instance times which we choose to
1798
0
        // ignore.
1799
0
        //
1800
0
        // Therefore we add a qualification of (b) above that even if
1801
0
        // there are end instance times but they all depend on the end of the
1802
0
        // current interval we should act as if they didn't exist and allow the
1803
0
        // open-ended interval.
1804
0
        //
1805
0
        // In the following condition we don't use |= because it doesn't provide
1806
0
        // short-circuit behavior.
1807
0
        openEndedIntervalOk = openEndedIntervalOk ||
1808
0
                             (aReplacedInterval &&
1809
0
                              AreEndTimesDependentOn(aReplacedInterval->End()));
1810
0
1811
0
        if (!openEndedIntervalOk) {
1812
0
          return false; // Bad interval
1813
0
        }
1814
0
      }
1815
0
1816
0
      nsSMILTimeValue intervalEnd = tempEnd
1817
0
                                  ? tempEnd->Time() : nsSMILTimeValue();
1818
0
      nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd);
1819
0
1820
0
      if (!tempEnd || intervalEnd != activeEnd) {
1821
0
        tempEnd = new nsSMILInstanceTime(activeEnd);
1822
0
      }
1823
0
    }
1824
0
    MOZ_ASSERT(tempEnd, "Failed to get end point for next interval");
1825
0
1826
0
    // When we choose the interval endpoints, we don't allow coincident
1827
0
    // zero-duration intervals, so if we arrive here and we have a zero-duration
1828
0
    // interval starting at the same point as a previous zero-duration interval,
1829
0
    // then it must be because we've applied constraints to the active duration.
1830
0
    // In that case, we will potentially run into an infinite loop, so we break
1831
0
    // it by searching for the next interval that starts AFTER our current
1832
0
    // zero-duration interval.
1833
0
    if (prevIntervalWasZeroDur && tempEnd->Time() == beginAfter) {
1834
0
      if (prevIntervalWasZeroDur) {
1835
0
        beginAfter.SetMillis(tempBegin->Time().GetMillis() + 1);
1836
0
        prevIntervalWasZeroDur = false;
1837
0
        continue;
1838
0
      }
1839
0
    }
1840
0
    prevIntervalWasZeroDur = tempBegin->Time() == tempEnd->Time();
1841
0
1842
0
    // Check for valid interval
1843
0
    if (tempEnd->Time() > zeroTime ||
1844
0
       (tempBegin->Time() == zeroTime && tempEnd->Time() == zeroTime)) {
1845
0
      aResult.Set(*tempBegin, *tempEnd);
1846
0
      return true;
1847
0
    }
1848
0
1849
0
    if (mRestartMode == RESTART_NEVER) {
1850
0
      // tempEnd <= 0 so we're going to loop which effectively means restarting
1851
0
      return false;
1852
0
    }
1853
0
1854
0
    beginAfter = tempEnd->Time();
1855
0
  }
1856
0
  MOZ_ASSERT_UNREACHABLE("Hmm... we really shouldn't be here");
1857
0
1858
0
  return false;
1859
0
}
1860
1861
nsSMILInstanceTime*
1862
nsSMILTimedElement::GetNextGreater(const InstanceTimeList& aList,
1863
                                   const nsSMILTimeValue& aBase,
1864
                                   int32_t& aPosition) const
1865
0
{
1866
0
  nsSMILInstanceTime* result = nullptr;
1867
0
  while ((result = GetNextGreaterOrEqual(aList, aBase, aPosition)) &&
1868
0
         result->Time() == aBase) { }
1869
0
  return result;
1870
0
}
1871
1872
nsSMILInstanceTime*
1873
nsSMILTimedElement::GetNextGreaterOrEqual(const InstanceTimeList& aList,
1874
                                          const nsSMILTimeValue& aBase,
1875
                                          int32_t& aPosition) const
1876
0
{
1877
0
  nsSMILInstanceTime* result = nullptr;
1878
0
  int32_t count = aList.Length();
1879
0
1880
0
  for (; aPosition < count && !result; ++aPosition) {
1881
0
    nsSMILInstanceTime* val = aList[aPosition].get();
1882
0
    MOZ_ASSERT(val, "NULL instance time in list");
1883
0
    if (val->Time() >= aBase) {
1884
0
      result = val;
1885
0
    }
1886
0
  }
1887
0
1888
0
  return result;
1889
0
}
1890
1891
/**
1892
 * @see SMILANIM 3.3.4
1893
 */
1894
nsSMILTimeValue
1895
nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
1896
                                  const nsSMILTimeValue& aEnd) const
1897
0
{
1898
0
  nsSMILTimeValue result;
1899
0
1900
0
  MOZ_ASSERT(mSimpleDur.IsResolved(),
1901
0
             "Unresolved simple duration in CalcActiveEnd");
1902
0
  MOZ_ASSERT(aBegin.IsDefinite(),
1903
0
             "Indefinite or unresolved begin time in CalcActiveEnd");
1904
0
1905
0
  result = GetRepeatDuration();
1906
0
1907
0
  if (aEnd.IsDefinite()) {
1908
0
    nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis();
1909
0
1910
0
    if (result.IsDefinite()) {
1911
0
      result.SetMillis(std::min(result.GetMillis(), activeDur));
1912
0
    } else {
1913
0
      result.SetMillis(activeDur);
1914
0
    }
1915
0
  }
1916
0
1917
0
  result = ApplyMinAndMax(result);
1918
0
1919
0
  if (result.IsDefinite()) {
1920
0
    nsSMILTime activeEnd = result.GetMillis() + aBegin.GetMillis();
1921
0
    result.SetMillis(activeEnd);
1922
0
  }
1923
0
1924
0
  return result;
1925
0
}
1926
1927
nsSMILTimeValue
1928
nsSMILTimedElement::GetRepeatDuration() const
1929
0
{
1930
0
  nsSMILTimeValue multipliedDuration;
1931
0
  if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
1932
0
    if (mRepeatCount * double(mSimpleDur.GetMillis()) <=
1933
0
        std::numeric_limits<nsSMILTime>::max()) {
1934
0
      multipliedDuration.SetMillis(
1935
0
        nsSMILTime(mRepeatCount * mSimpleDur.GetMillis()));
1936
0
    }
1937
0
  } else {
1938
0
    multipliedDuration.SetIndefinite();
1939
0
  }
1940
0
1941
0
  nsSMILTimeValue repeatDuration;
1942
0
1943
0
  if (mRepeatDur.IsResolved()) {
1944
0
    repeatDuration = std::min(multipliedDuration, mRepeatDur);
1945
0
  } else if (mRepeatCount.IsSet()) {
1946
0
    repeatDuration = multipliedDuration;
1947
0
  } else {
1948
0
    repeatDuration = mSimpleDur;
1949
0
  }
1950
0
1951
0
  return repeatDuration;
1952
0
}
1953
1954
nsSMILTimeValue
1955
nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const
1956
0
{
1957
0
  if (!aDuration.IsResolved()) {
1958
0
    return aDuration;
1959
0
  }
1960
0
1961
0
  if (mMax < mMin) {
1962
0
    return aDuration;
1963
0
  }
1964
0
1965
0
  nsSMILTimeValue result;
1966
0
1967
0
  if (aDuration > mMax) {
1968
0
    result = mMax;
1969
0
  } else if (aDuration < mMin) {
1970
0
    result = mMin;
1971
0
  } else {
1972
0
    result = aDuration;
1973
0
  }
1974
0
1975
0
  return result;
1976
0
}
1977
1978
nsSMILTime
1979
nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime,
1980
                                           uint32_t& aRepeatIteration)
1981
0
{
1982
0
  nsSMILTime result;
1983
0
1984
0
  MOZ_ASSERT(mSimpleDur.IsResolved(),
1985
0
             "Unresolved simple duration in ActiveTimeToSimpleTime");
1986
0
  MOZ_ASSERT(aActiveTime >= 0, "Expecting non-negative active time");
1987
0
  // Note that a negative aActiveTime will give us a negative value for
1988
0
  // aRepeatIteration, which is bad because aRepeatIteration is unsigned
1989
0
1990
0
  if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) {
1991
0
    aRepeatIteration = 0;
1992
0
    result = aActiveTime;
1993
0
  } else {
1994
0
    result = aActiveTime % mSimpleDur.GetMillis();
1995
0
    aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis());
1996
0
  }
1997
0
1998
0
  return result;
1999
0
}
2000
2001
//
2002
// Although in many cases it would be possible to check for an early end and
2003
// adjust the current interval well in advance the SMIL Animation spec seems to
2004
// indicate that we should only apply an early end at the latest possible
2005
// moment. In particular, this paragraph from section 3.6.8:
2006
//
2007
// 'If restart  is set to "always", then the current interval will end early if
2008
// there is an instance time in the begin list that is before (i.e. earlier
2009
// than) the defined end for the current interval. Ending in this manner will
2010
// also send a changed time notice to all time dependents for the current
2011
// interval end.'
2012
//
2013
nsSMILInstanceTime*
2014
nsSMILTimedElement::CheckForEarlyEnd(
2015
    const nsSMILTimeValue& aContainerTime) const
2016
0
{
2017
0
  MOZ_ASSERT(mCurrentInterval,
2018
0
             "Checking for an early end but the current interval is not set");
2019
0
  if (mRestartMode != RESTART_ALWAYS)
2020
0
    return nullptr;
2021
0
2022
0
  int32_t position = 0;
2023
0
  nsSMILInstanceTime* nextBegin =
2024
0
    GetNextGreater(mBeginInstances, mCurrentInterval->Begin()->Time(),
2025
0
                   position);
2026
0
2027
0
  if (nextBegin &&
2028
0
      nextBegin->Time() > mCurrentInterval->Begin()->Time() &&
2029
0
      nextBegin->Time() < mCurrentInterval->End()->Time() &&
2030
0
      nextBegin->Time() <= aContainerTime) {
2031
0
    return nextBegin;
2032
0
  }
2033
0
2034
0
  return nullptr;
2035
0
}
2036
2037
void
2038
nsSMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice)
2039
0
{
2040
0
  // Check if updates are currently blocked (batched)
2041
0
  if (mDeferIntervalUpdates) {
2042
0
    mDoDeferredUpdate = true;
2043
0
    return;
2044
0
  }
2045
0
2046
0
  // We adopt the convention of not resolving intervals until the first
2047
0
  // sample. Otherwise, every time each attribute is set we'll re-resolve the
2048
0
  // current interval and notify all our time dependents of the change.
2049
0
  //
2050
0
  // The disadvantage of deferring resolving the interval is that DOM calls to
2051
0
  // to getStartTime will throw an INVALID_STATE_ERR exception until the
2052
0
  // document timeline begins since the start time has not yet been resolved.
2053
0
  if (mElementState == STATE_STARTUP)
2054
0
    return;
2055
0
2056
0
  // Although SMIL gives rules for detecting cycles in change notifications,
2057
0
  // some configurations can lead to create-delete-create-delete-etc. cycles
2058
0
  // which SMIL does not consider.
2059
0
  //
2060
0
  // In order to provide consistent behavior in such cases, we detect two
2061
0
  // deletes in a row and then refuse to create any further intervals. That is,
2062
0
  // we say the configuration is invalid.
2063
0
  if (mDeleteCount > 1) {
2064
0
    // When we update the delete count we also set the state to post active, so
2065
0
    // if we're not post active here then something other than
2066
0
    // UpdateCurrentInterval has updated the element state in between and all
2067
0
    // bets are off.
2068
0
    MOZ_ASSERT(mElementState == STATE_POSTACTIVE,
2069
0
               "Expected to be in post-active state after performing double "
2070
0
               "delete");
2071
0
    return;
2072
0
  }
2073
0
2074
0
  // Check that we aren't stuck in infinite recursion updating some syncbase
2075
0
  // dependencies. Generally such situations should be detected in advance and
2076
0
  // the chain broken in a sensible and predictable manner, so if we're hitting
2077
0
  // this assertion we need to work out how to detect the case that's causing
2078
0
  // it. In release builds, just bail out before we overflow the stack.
2079
0
  AutoRestore<uint8_t> depthRestorer(mUpdateIntervalRecursionDepth);
2080
0
  if (++mUpdateIntervalRecursionDepth > sMaxUpdateIntervalRecursionDepth) {
2081
0
    MOZ_ASSERT(false,
2082
0
               "Update current interval recursion depth exceeded threshold");
2083
0
    return;
2084
0
  }
2085
0
2086
0
  // If the interval is active the begin time is fixed.
2087
0
  const nsSMILInstanceTime* beginTime = mElementState == STATE_ACTIVE
2088
0
                                      ? mCurrentInterval->Begin()
2089
0
                                      : nullptr;
2090
0
  nsSMILInterval updatedInterval;
2091
0
  if (GetNextInterval(GetPreviousInterval(), mCurrentInterval.get(),
2092
0
                      beginTime, updatedInterval)) {
2093
0
2094
0
    if (mElementState == STATE_POSTACTIVE) {
2095
0
2096
0
      MOZ_ASSERT(!mCurrentInterval,
2097
0
                 "In postactive state but the interval has been set");
2098
0
      mCurrentInterval = MakeUnique<nsSMILInterval>(updatedInterval);
2099
0
      mElementState = STATE_WAITING;
2100
0
      NotifyNewInterval();
2101
0
2102
0
    } else {
2103
0
2104
0
      bool beginChanged = false;
2105
0
      bool endChanged   = false;
2106
0
2107
0
      if (mElementState != STATE_ACTIVE &&
2108
0
          !updatedInterval.Begin()->SameTimeAndBase(
2109
0
            *mCurrentInterval->Begin())) {
2110
0
        mCurrentInterval->SetBegin(*updatedInterval.Begin());
2111
0
        beginChanged = true;
2112
0
      }
2113
0
2114
0
      if (!updatedInterval.End()->SameTimeAndBase(*mCurrentInterval->End())) {
2115
0
        mCurrentInterval->SetEnd(*updatedInterval.End());
2116
0
        endChanged = true;
2117
0
      }
2118
0
2119
0
      if (beginChanged || endChanged || aForceChangeNotice) {
2120
0
        NotifyChangedInterval(mCurrentInterval.get(), beginChanged, endChanged);
2121
0
      }
2122
0
    }
2123
0
2124
0
    // There's a chance our next milestone has now changed, so update the time
2125
0
    // container
2126
0
    RegisterMilestone();
2127
0
  } else { // GetNextInterval failed: Current interval is no longer valid
2128
0
    if (mElementState == STATE_ACTIVE) {
2129
0
      // The interval is active so we can't just delete it, instead trim it so
2130
0
      // that begin==end.
2131
0
      if (!mCurrentInterval->End()->SameTimeAndBase(*mCurrentInterval->Begin()))
2132
0
      {
2133
0
        mCurrentInterval->SetEnd(*mCurrentInterval->Begin());
2134
0
        NotifyChangedInterval(mCurrentInterval.get(), false, true);
2135
0
      }
2136
0
      // The transition to the postactive state will take place on the next
2137
0
      // sample (along with firing end events, clearing intervals etc.)
2138
0
      RegisterMilestone();
2139
0
    } else if (mElementState == STATE_WAITING) {
2140
0
      AutoRestore<uint8_t> deleteCountRestorer(mDeleteCount);
2141
0
      ++mDeleteCount;
2142
0
      mElementState = STATE_POSTACTIVE;
2143
0
      ResetCurrentInterval();
2144
0
    }
2145
0
  }
2146
0
}
2147
2148
void
2149
nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime)
2150
0
{
2151
0
  if (mClient) {
2152
0
    uint32_t repeatIteration;
2153
0
    nsSMILTime simpleTime =
2154
0
      ActiveTimeToSimpleTime(aActiveTime, repeatIteration);
2155
0
    mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);
2156
0
  }
2157
0
}
2158
2159
void
2160
nsSMILTimedElement::SampleFillValue()
2161
0
{
2162
0
  if (mFillMode != FILL_FREEZE || !mClient)
2163
0
    return;
2164
0
2165
0
  nsSMILTime activeTime;
2166
0
2167
0
  if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) {
2168
0
    const nsSMILInterval* prevInterval = GetPreviousInterval();
2169
0
    MOZ_ASSERT(prevInterval,
2170
0
               "Attempting to sample fill value but there is no previous "
2171
0
               "interval");
2172
0
    MOZ_ASSERT(prevInterval->End()->Time().IsDefinite() &&
2173
0
               prevInterval->End()->IsFixedTime(),
2174
0
               "Attempting to sample fill value but the endpoint of the "
2175
0
               "previous interval is not resolved and fixed");
2176
0
2177
0
    activeTime = prevInterval->End()->Time().GetMillis() -
2178
0
                 prevInterval->Begin()->Time().GetMillis();
2179
0
2180
0
    // If the interval's repeat duration was shorter than its active duration,
2181
0
    // use the end of the repeat duration to determine the frozen animation's
2182
0
    // state.
2183
0
    nsSMILTimeValue repeatDuration = GetRepeatDuration();
2184
0
    if (repeatDuration.IsDefinite()) {
2185
0
      activeTime = std::min(repeatDuration.GetMillis(), activeTime);
2186
0
    }
2187
0
  } else {
2188
0
    MOZ_ASSERT(mElementState == STATE_ACTIVE,
2189
0
        "Attempting to sample fill value when we're in an unexpected state "
2190
0
        "(probably STATE_STARTUP)");
2191
0
2192
0
    // If we are being asked to sample the fill value while active we *must*
2193
0
    // have a repeat duration shorter than the active duration so use that.
2194
0
    MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
2195
0
        "Attempting to sample fill value of an active animation with "
2196
0
        "an indefinite repeat duration");
2197
0
    activeTime = GetRepeatDuration().GetMillis();
2198
0
  }
2199
0
2200
0
  uint32_t repeatIteration;
2201
0
  nsSMILTime simpleTime =
2202
0
    ActiveTimeToSimpleTime(activeTime, repeatIteration);
2203
0
2204
0
  if (simpleTime == 0L && repeatIteration) {
2205
0
    mClient->SampleLastValue(--repeatIteration);
2206
0
  } else {
2207
0
    mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);
2208
0
  }
2209
0
}
2210
2211
nsresult
2212
nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
2213
    double aOffsetSeconds, bool aIsBegin)
2214
0
{
2215
0
  double offset = NS_round(aOffsetSeconds * PR_MSEC_PER_SEC);
2216
0
2217
0
  // Check we won't overflow the range of nsSMILTime
2218
0
  if (aCurrentTime + offset > std::numeric_limits<nsSMILTime>::max())
2219
0
    return NS_ERROR_ILLEGAL_VALUE;
2220
0
2221
0
  nsSMILTimeValue timeVal(aCurrentTime + int64_t(offset));
2222
0
2223
0
  RefPtr<nsSMILInstanceTime> instanceTime =
2224
0
    new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM);
2225
0
2226
0
  AddInstanceTime(instanceTime, aIsBegin);
2227
0
2228
0
  return NS_OK;
2229
0
}
2230
2231
void
2232
nsSMILTimedElement::RegisterMilestone()
2233
0
{
2234
0
  nsSMILTimeContainer* container = GetTimeContainer();
2235
0
  if (!container)
2236
0
    return;
2237
0
  MOZ_ASSERT(mAnimationElement,
2238
0
             "Got a time container without an owning animation element");
2239
0
2240
0
  nsSMILMilestone nextMilestone;
2241
0
  if (!GetNextMilestone(nextMilestone))
2242
0
    return;
2243
0
2244
0
  // This method is called every time we might possibly have updated our
2245
0
  // current interval, but since nsSMILTimeContainer makes no attempt to filter
2246
0
  // out redundant milestones we do some rudimentary filtering here. It's not
2247
0
  // perfect, but unnecessary samples are fairly cheap.
2248
0
  if (nextMilestone >= mPrevRegisteredMilestone)
2249
0
    return;
2250
0
2251
0
  container->AddMilestone(nextMilestone, *mAnimationElement);
2252
0
  mPrevRegisteredMilestone = nextMilestone;
2253
0
}
2254
2255
bool
2256
nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
2257
0
{
2258
0
  // Return the next key moment in our lifetime.
2259
0
  //
2260
0
  // XXX It may be possible in future to optimise this so that we only register
2261
0
  // for milestones if:
2262
0
  // a) We have time dependents, or
2263
0
  // b) We are dependent on events or syncbase relationships, or
2264
0
  // c) There are registered listeners for our events
2265
0
  //
2266
0
  // Then for the simple case where everything uses offset values we could
2267
0
  // ignore milestones altogether.
2268
0
  //
2269
0
  // We'd need to be careful, however, that if one of those conditions became
2270
0
  // true in between samples that we registered our next milestone at that
2271
0
  // point.
2272
0
2273
0
  switch (mElementState)
2274
0
  {
2275
0
  case STATE_STARTUP:
2276
0
    // All elements register for an initial end sample at t=0 where we resolve
2277
0
    // our initial interval.
2278
0
    aNextMilestone.mIsEnd = true; // Initial sample should be an end sample
2279
0
    aNextMilestone.mTime = 0;
2280
0
    return true;
2281
0
2282
0
  case STATE_WAITING:
2283
0
    MOZ_ASSERT(mCurrentInterval,
2284
0
               "In waiting state but the current interval has not been set");
2285
0
    aNextMilestone.mIsEnd = false;
2286
0
    aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis();
2287
0
    return true;
2288
0
2289
0
  case STATE_ACTIVE:
2290
0
    {
2291
0
      // Work out what comes next: the interval end or the next repeat iteration
2292
0
      nsSMILTimeValue nextRepeat;
2293
0
      if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
2294
0
        nsSMILTime nextRepeatActiveTime =
2295
0
          (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis();
2296
0
        // Check that the repeat fits within the repeat duration
2297
0
        if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) {
2298
0
          nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
2299
0
                               nextRepeatActiveTime);
2300
0
        }
2301
0
      }
2302
0
      nsSMILTimeValue nextMilestone =
2303
0
        std::min(mCurrentInterval->End()->Time(), nextRepeat);
2304
0
2305
0
      // Check for an early end before that time
2306
0
      nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone);
2307
0
      if (earlyEnd) {
2308
0
        aNextMilestone.mIsEnd = true;
2309
0
        aNextMilestone.mTime = earlyEnd->Time().GetMillis();
2310
0
        return true;
2311
0
      }
2312
0
2313
0
      // Apply the previously calculated milestone
2314
0
      if (nextMilestone.IsDefinite()) {
2315
0
        aNextMilestone.mIsEnd = nextMilestone != nextRepeat;
2316
0
        aNextMilestone.mTime = nextMilestone.GetMillis();
2317
0
        return true;
2318
0
      }
2319
0
2320
0
      return false;
2321
0
    }
2322
0
2323
0
  case STATE_POSTACTIVE:
2324
0
    return false;
2325
0
  }
2326
0
  MOZ_CRASH("Invalid element state");
2327
0
}
2328
2329
void
2330
nsSMILTimedElement::NotifyNewInterval()
2331
0
{
2332
0
  MOZ_ASSERT(mCurrentInterval,
2333
0
             "Attempting to notify dependents of a new interval but the "
2334
0
             "interval is not set");
2335
0
2336
0
  nsSMILTimeContainer* container = GetTimeContainer();
2337
0
  if (container) {
2338
0
    container->SyncPauseTime();
2339
0
  }
2340
0
2341
0
  for (auto iter = mTimeDependents.Iter(); !iter.Done(); iter.Next()) {
2342
0
    nsSMILInterval* interval = mCurrentInterval.get();
2343
0
    // It's possible that in notifying one new time dependent of a new interval
2344
0
    // that a chain reaction is triggered which results in the original
2345
0
    // interval disappearing. If that's the case we can skip sending further
2346
0
    // notifications.
2347
0
    if (!interval) {
2348
0
      break;
2349
0
    }
2350
0
    nsSMILTimeValueSpec* spec = iter.Get()->GetKey();
2351
0
    spec->HandleNewInterval(*interval, container);
2352
0
  }
2353
0
}
2354
2355
void
2356
nsSMILTimedElement::NotifyChangedInterval(nsSMILInterval* aInterval,
2357
                                          bool aBeginObjectChanged,
2358
                                          bool aEndObjectChanged)
2359
0
{
2360
0
  MOZ_ASSERT(aInterval, "Null interval for change notification");
2361
0
2362
0
  nsSMILTimeContainer* container = GetTimeContainer();
2363
0
  if (container) {
2364
0
    container->SyncPauseTime();
2365
0
  }
2366
0
2367
0
  // Copy the instance times list since notifying the instance times can result
2368
0
  // in a chain reaction whereby our own interval gets deleted along with its
2369
0
  // instance times.
2370
0
  InstanceTimeList times;
2371
0
  aInterval->GetDependentTimes(times);
2372
0
2373
0
  for (uint32_t i = 0; i < times.Length(); ++i) {
2374
0
    times[i]->HandleChangedInterval(container, aBeginObjectChanged,
2375
0
                                    aEndObjectChanged);
2376
0
  }
2377
0
}
2378
2379
void
2380
nsSMILTimedElement::FireTimeEventAsync(EventMessage aMsg, int32_t aDetail)
2381
0
{
2382
0
  if (!mAnimationElement)
2383
0
    return;
2384
0
2385
0
  nsCOMPtr<nsIRunnable> event =
2386
0
    new AsyncTimeEventRunner(mAnimationElement, aMsg, aDetail);
2387
0
  mAnimationElement->OwnerDoc()->Dispatch(TaskCategory::Other,
2388
0
                                          event.forget());
2389
0
}
2390
2391
const nsSMILInstanceTime*
2392
nsSMILTimedElement::GetEffectiveBeginInstance() const
2393
0
{
2394
0
  switch (mElementState)
2395
0
  {
2396
0
  case STATE_STARTUP:
2397
0
    return nullptr;
2398
0
2399
0
  case STATE_ACTIVE:
2400
0
    return mCurrentInterval->Begin();
2401
0
2402
0
  case STATE_WAITING:
2403
0
  case STATE_POSTACTIVE:
2404
0
    {
2405
0
      const nsSMILInterval* prevInterval = GetPreviousInterval();
2406
0
      return prevInterval ? prevInterval->Begin() : nullptr;
2407
0
    }
2408
0
  }
2409
0
  MOZ_CRASH("Invalid element state");
2410
0
}
2411
2412
const nsSMILInterval*
2413
nsSMILTimedElement::GetPreviousInterval() const
2414
0
{
2415
0
  return mOldIntervals.IsEmpty()
2416
0
    ? nullptr
2417
0
    : mOldIntervals[mOldIntervals.Length()-1].get();
2418
0
}
2419
2420
bool
2421
nsSMILTimedElement::HasClientInFillRange() const
2422
0
{
2423
0
  // Returns true if we have a client that is in the range where it will fill
2424
0
  return mClient &&
2425
0
         ((mElementState != STATE_ACTIVE && HasPlayed()) ||
2426
0
          (mElementState == STATE_ACTIVE && !mClient->IsActive()));
2427
0
}
2428
2429
bool
2430
nsSMILTimedElement::EndHasEventConditions() const
2431
0
{
2432
0
  for (uint32_t i = 0; i < mEndSpecs.Length(); ++i) {
2433
0
    if (mEndSpecs[i]->IsEventBased())
2434
0
      return true;
2435
0
  }
2436
0
  return false;
2437
0
}
2438
2439
bool
2440
nsSMILTimedElement::AreEndTimesDependentOn(
2441
  const nsSMILInstanceTime* aBase) const
2442
0
{
2443
0
  if (mEndInstances.IsEmpty())
2444
0
    return false;
2445
0
2446
0
  for (uint32_t i = 0; i < mEndInstances.Length(); ++i) {
2447
0
    if (mEndInstances[i]->GetBaseTime() != aBase) {
2448
0
      return false;
2449
0
    }
2450
0
  }
2451
0
  return true;
2452
0
}
2453