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