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