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