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/. */
7 #include "SMILTimedElement.h"
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/dom/DocumentInlines.h"
20 #include "mozilla/dom/SVGAnimationElement.h"
21 #include "nsAttrValueInlines.h"
22 #include "nsGkAtoms.h"
23 #include "nsReadableUtils.h"
24 #include "nsMathUtils.h"
25 #include "nsThreadUtils.h"
29 #include "nsCharSeparatedTokenizer.h"
32 using namespace mozilla::dom
;
36 //----------------------------------------------------------------------
37 // Helper class: InstanceTimeComparator
39 // Upon inserting an instance time into one of our instance time lists we assign
40 // it a serial number. This allows us to sort the instance times in such a way
41 // that where we have several equal instance times, the ones added later will
42 // sort later. This means that when we call UpdateCurrentInterval during the
43 // waiting state we won't unnecessarily change the begin instance.
45 // The serial number also means that every instance time has an unambiguous
46 // position in the array so we can use RemoveElementSorted and the like.
47 bool SMILTimedElement::InstanceTimeComparator::Equals(
48 const SMILInstanceTime
* aElem1
, const SMILInstanceTime
* aElem2
) const {
49 MOZ_ASSERT(aElem1
&& aElem2
, "Trying to compare null instance time pointers");
50 MOZ_ASSERT(aElem1
->Serial() && aElem2
->Serial(),
51 "Instance times have not been assigned serial numbers");
52 MOZ_ASSERT(aElem1
== aElem2
|| aElem1
->Serial() != aElem2
->Serial(),
53 "Serial numbers are not unique");
55 return aElem1
->Serial() == aElem2
->Serial();
58 bool SMILTimedElement::InstanceTimeComparator::LessThan(
59 const SMILInstanceTime
* aElem1
, const SMILInstanceTime
* aElem2
) const {
60 MOZ_ASSERT(aElem1
&& aElem2
, "Trying to compare null instance time pointers");
61 MOZ_ASSERT(aElem1
->Serial() && aElem2
->Serial(),
62 "Instance times have not been assigned serial numbers");
64 int8_t cmp
= aElem1
->Time().CompareTo(aElem2
->Time());
65 return cmp
== 0 ? aElem1
->Serial() < aElem2
->Serial() : cmp
< 0;
68 //----------------------------------------------------------------------
69 // Helper class: AsyncTimeEventRunner
72 class AsyncTimeEventRunner
: public Runnable
{
74 const RefPtr
<nsIContent
> mTarget
;
79 AsyncTimeEventRunner(nsIContent
* aTarget
, EventMessage aMsg
, int32_t aDetail
)
80 : mozilla::Runnable("AsyncTimeEventRunner"),
85 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
86 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
87 InternalSMILTimeEvent
event(true, mMsg
);
88 event
.mDetail
= mDetail
;
90 RefPtr
<nsPresContext
> context
= nullptr;
91 Document
* doc
= mTarget
->GetComposedDoc();
93 context
= doc
->GetPresContext();
96 return EventDispatcher::Dispatch(mTarget
, context
, &event
);
101 //----------------------------------------------------------------------
102 // Helper class: AutoIntervalUpdateBatcher
104 // Stack-based helper class to set the mDeferIntervalUpdates flag on an
105 // SMILTimedElement and perform the UpdateCurrentInterval when the object is
108 // If several of these objects are allocated on the stack, the update will not
109 // be performed until the last object for a given SMILTimedElement is
111 class MOZ_STACK_CLASS
SMILTimedElement::AutoIntervalUpdateBatcher
{
113 explicit AutoIntervalUpdateBatcher(SMILTimedElement
& aTimedElement
)
114 : mTimedElement(aTimedElement
),
115 mDidSetFlag(!aTimedElement
.mDeferIntervalUpdates
) {
116 mTimedElement
.mDeferIntervalUpdates
= true;
119 ~AutoIntervalUpdateBatcher() {
120 if (!mDidSetFlag
) return;
122 mTimedElement
.mDeferIntervalUpdates
= false;
124 if (mTimedElement
.mDoDeferredUpdate
) {
125 mTimedElement
.mDoDeferredUpdate
= false;
126 mTimedElement
.UpdateCurrentInterval();
131 SMILTimedElement
& mTimedElement
;
135 //----------------------------------------------------------------------
136 // Helper class: AutoIntervalUpdater
138 // Stack-based helper class to call UpdateCurrentInterval when it is destroyed
139 // which helps avoid bugs where we forget to call UpdateCurrentInterval in the
140 // case of early returns (e.g. due to parse errors).
142 // This can be safely used in conjunction with AutoIntervalUpdateBatcher; any
143 // calls to UpdateCurrentInterval made by this class will simply be deferred if
144 // there is an AutoIntervalUpdateBatcher on the stack.
145 class MOZ_STACK_CLASS
SMILTimedElement::AutoIntervalUpdater
{
147 explicit AutoIntervalUpdater(SMILTimedElement
& aTimedElement
)
148 : mTimedElement(aTimedElement
) {}
150 ~AutoIntervalUpdater() { mTimedElement
.UpdateCurrentInterval(); }
153 SMILTimedElement
& mTimedElement
;
156 //----------------------------------------------------------------------
157 // Templated helper functions
159 // Selectively remove elements from an array of type
160 // nsTArray<RefPtr<SMILInstanceTime> > with O(n) performance.
161 template <class TestFunctor
>
162 void SMILTimedElement::RemoveInstanceTimes(InstanceTimeList
& aArray
,
163 TestFunctor
& aTest
) {
164 InstanceTimeList newArray
;
165 for (uint32_t i
= 0; i
< aArray
.Length(); ++i
) {
166 SMILInstanceTime
* item
= aArray
[i
].get();
167 if (aTest(item
, i
)) {
168 // As per bugs 665334 and 669225 we should be careful not to remove the
169 // instance time that corresponds to the previous interval's end time.
171 // Most functors supplied here fulfil this condition by checking if the
172 // instance time is marked as "ShouldPreserve" and if so, not deleting it.
174 // However, when filtering instance times, we sometimes need to drop even
175 // instance times marked as "ShouldPreserve". In that case we take special
176 // care not to delete the end instance time of the previous interval.
177 MOZ_ASSERT(!GetPreviousInterval() || item
!= GetPreviousInterval()->End(),
178 "Removing end instance time of previous interval");
181 newArray
.AppendElement(item
);
184 aArray
= std::move(newArray
);
187 //----------------------------------------------------------------------
190 const nsAttrValue::EnumTable
SMILTimedElement::sFillModeTable
[] = {
191 {"remove", FILL_REMOVE
}, {"freeze", FILL_FREEZE
}, {nullptr, 0}};
193 const nsAttrValue::EnumTable
SMILTimedElement::sRestartModeTable
[] = {
194 {"always", RESTART_ALWAYS
},
195 {"whenNotActive", RESTART_WHENNOTACTIVE
},
196 {"never", RESTART_NEVER
},
199 const SMILMilestone
SMILTimedElement::sMaxMilestone(
200 std::numeric_limits
<SMILTime
>::max(), false);
202 // The thresholds at which point we start filtering intervals and instance times
204 // See FilterIntervals and FilterInstanceTimes.
205 const uint8_t SMILTimedElement::sMaxNumIntervals
= 20;
206 const uint8_t SMILTimedElement::sMaxNumInstanceTimes
= 100;
208 // Detect if we arrive in some sort of undetected recursive syncbase dependency
210 const uint8_t SMILTimedElement::sMaxUpdateIntervalRecursionDepth
= 20;
212 //----------------------------------------------------------------------
215 SMILTimedElement::SMILTimedElement()
216 : mAnimationElement(nullptr),
217 mFillMode(FILL_REMOVE
),
218 mRestartMode(RESTART_ALWAYS
),
219 mInstanceSerialIndex(0),
221 mCurrentInterval(nullptr),
222 mCurrentRepeatIteration(0),
223 mPrevRegisteredMilestone(sMaxMilestone
),
224 mElementState(STATE_STARTUP
),
225 mSeekState(SEEK_NOT_SEEKING
),
226 mDeferIntervalUpdates(false),
227 mDoDeferredUpdate(false),
230 mUpdateIntervalRecursionDepth(0) {
231 mSimpleDur
.SetIndefinite();
232 mMin
= SMILTimeValue::Zero();
233 mMax
.SetIndefinite();
236 SMILTimedElement::~SMILTimedElement() {
237 // Unlink all instance times from dependent intervals
238 for (RefPtr
<SMILInstanceTime
>& instance
: mBeginInstances
) {
241 mBeginInstances
.Clear();
242 for (RefPtr
<SMILInstanceTime
>& instance
: mEndInstances
) {
245 mEndInstances
.Clear();
247 // Notify anyone listening to our intervals that they're gone
248 // (We shouldn't get any callbacks from this because all our instance times
249 // are now disassociated with any intervals)
252 // The following assertions are important in their own right (for checking
253 // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers
254 // to class so if they fail there's the possibility we might have dangling
256 MOZ_ASSERT(!mDeferIntervalUpdates
,
257 "Interval updates should no longer be blocked when an "
258 "SMILTimedElement disappears");
259 MOZ_ASSERT(!mDoDeferredUpdate
,
260 "There should no longer be any pending updates when an "
261 "SMILTimedElement disappears");
264 void SMILTimedElement::SetAnimationElement(SVGAnimationElement
* aElement
) {
265 MOZ_ASSERT(aElement
, "NULL owner element");
266 MOZ_ASSERT(!mAnimationElement
, "Re-setting owner");
267 mAnimationElement
= aElement
;
270 SMILTimeContainer
* SMILTimedElement::GetTimeContainer() {
271 return mAnimationElement
? mAnimationElement
->GetTimeContainer() : nullptr;
274 dom::Element
* SMILTimedElement::GetTargetElement() {
275 return mAnimationElement
? mAnimationElement
->GetTargetElementContent()
279 //----------------------------------------------------------------------
280 // ElementTimeControl methods
282 // The definition of the ElementTimeControl interface differs between SMIL
283 // Animation and SVG 1.1. In SMIL Animation all methods have a void return
284 // type and the new instance time is simply added to the list and restart
285 // semantics are applied as with any other instance time. In the SVG definition
286 // the methods return a bool depending on the restart mode.
288 // This inconsistency has now been addressed by an erratum in SVG 1.1:
290 // http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface
292 // which favours the definition in SMIL, i.e. instance times are just added
293 // without first checking the restart mode.
295 nsresult
SMILTimedElement::BeginElementAt(double aOffsetSeconds
) {
296 SMILTimeContainer
* container
= GetTimeContainer();
297 if (!container
) return NS_ERROR_FAILURE
;
299 SMILTime currentTime
= container
->GetCurrentTimeAsSMILTime();
300 return AddInstanceTimeFromCurrentTime(currentTime
, aOffsetSeconds
, true);
303 nsresult
SMILTimedElement::EndElementAt(double aOffsetSeconds
) {
304 SMILTimeContainer
* container
= GetTimeContainer();
305 if (!container
) return NS_ERROR_FAILURE
;
307 SMILTime currentTime
= container
->GetCurrentTimeAsSMILTime();
308 return AddInstanceTimeFromCurrentTime(currentTime
, aOffsetSeconds
, false);
311 //----------------------------------------------------------------------
312 // SVGAnimationElement methods
314 SMILTimeValue
SMILTimedElement::GetStartTime() const {
315 return mElementState
== STATE_WAITING
|| mElementState
== STATE_ACTIVE
316 ? mCurrentInterval
->Begin()->Time()
320 //----------------------------------------------------------------------
321 // Hyperlinking support
323 SMILTimeValue
SMILTimedElement::GetHyperlinkTime() const {
324 SMILTimeValue hyperlinkTime
; // Default ctor creates unresolved time
326 if (mElementState
== STATE_ACTIVE
) {
327 hyperlinkTime
= mCurrentInterval
->Begin()->Time();
328 } else if (!mBeginInstances
.IsEmpty()) {
329 hyperlinkTime
= mBeginInstances
[0]->Time();
332 return hyperlinkTime
;
335 //----------------------------------------------------------------------
338 void SMILTimedElement::AddInstanceTime(SMILInstanceTime
* aInstanceTime
,
340 MOZ_ASSERT(aInstanceTime
, "Attempting to add null instance time");
342 // Event-sensitivity: If an element is not active (but the parent time
343 // container is), then events are only handled for begin specifications.
344 if (mElementState
!= STATE_ACTIVE
&& !aIsBegin
&&
345 aInstanceTime
->IsDynamic()) {
346 // No need to call Unlink here--dynamic instance times shouldn't be linked
347 // to anything that's going to miss them
348 MOZ_ASSERT(!aInstanceTime
->GetBaseInterval(),
349 "Dynamic instance time has a base interval--we probably need "
350 "to unlink it if we're not going to use it");
354 aInstanceTime
->SetSerial(++mInstanceSerialIndex
);
355 InstanceTimeList
& instanceList
= aIsBegin
? mBeginInstances
: mEndInstances
;
356 RefPtr
<SMILInstanceTime
>* inserted
=
357 instanceList
.InsertElementSorted(aInstanceTime
, InstanceTimeComparator());
359 NS_WARNING("Insufficient memory to insert instance time");
363 UpdateCurrentInterval();
366 void SMILTimedElement::UpdateInstanceTime(SMILInstanceTime
* aInstanceTime
,
367 SMILTimeValue
& aUpdatedTime
,
369 MOZ_ASSERT(aInstanceTime
, "Attempting to update null instance time");
371 // The reason we update the time here and not in the SMILTimeValueSpec is
372 // that it means we *could* re-sort more efficiently by doing a sorted remove
373 // and insert but currently this doesn't seem to be necessary given how
374 // infrequently we get these change notices.
375 aInstanceTime
->DependentUpdate(aUpdatedTime
);
376 InstanceTimeList
& instanceList
= aIsBegin
? mBeginInstances
: mEndInstances
;
377 instanceList
.Sort(InstanceTimeComparator());
379 // Generally speaking, UpdateCurrentInterval makes changes to the current
380 // interval and sends changes notices itself. However, in this case because
381 // instance times are shared between the instance time list and the intervals
382 // we are effectively changing the current interval outside
383 // UpdateCurrentInterval so we need to explicitly signal that we've made
386 // This wouldn't be necessary if we cloned instance times on adding them to
387 // the current interval but this introduces other complications (particularly
388 // detecting which instance time is being used to define the begin of the
389 // current interval when doing a Reset).
390 bool changedCurrentInterval
=
391 mCurrentInterval
&& (mCurrentInterval
->Begin() == aInstanceTime
||
392 mCurrentInterval
->End() == aInstanceTime
);
394 UpdateCurrentInterval(changedCurrentInterval
);
397 void SMILTimedElement::RemoveInstanceTime(SMILInstanceTime
* aInstanceTime
,
399 MOZ_ASSERT(aInstanceTime
, "Attempting to remove null instance time");
401 // If the instance time should be kept (because it is or was the fixed end
402 // point of an interval) then just disassociate it from the creator.
403 if (aInstanceTime
->ShouldPreserve()) {
404 aInstanceTime
->Unlink();
408 InstanceTimeList
& instanceList
= aIsBegin
? mBeginInstances
: mEndInstances
;
409 mozilla::DebugOnly
<bool> found
=
410 instanceList
.RemoveElementSorted(aInstanceTime
, InstanceTimeComparator());
411 MOZ_ASSERT(found
, "Couldn't find instance time to delete");
413 UpdateCurrentInterval();
417 class MOZ_STACK_CLASS RemoveByCreator
{
419 explicit RemoveByCreator(const SMILTimeValueSpec
* aCreator
)
420 : mCreator(aCreator
) {}
422 bool operator()(SMILInstanceTime
* aInstanceTime
, uint32_t /*aIndex*/) {
423 if (aInstanceTime
->GetCreator() != mCreator
) return false;
425 // If the instance time should be kept (because it is or was the fixed end
426 // point of an interval) then just disassociate it from the creator.
427 if (aInstanceTime
->ShouldPreserve()) {
428 aInstanceTime
->Unlink();
436 const SMILTimeValueSpec
* mCreator
;
440 void SMILTimedElement::RemoveInstanceTimesForCreator(
441 const SMILTimeValueSpec
* aCreator
, bool aIsBegin
) {
442 MOZ_ASSERT(aCreator
, "Creator not set");
444 InstanceTimeList
& instances
= aIsBegin
? mBeginInstances
: mEndInstances
;
445 RemoveByCreator
removeByCreator(aCreator
);
446 RemoveInstanceTimes(instances
, removeByCreator
);
448 UpdateCurrentInterval();
451 void SMILTimedElement::SetTimeClient(SMILAnimationFunction
* aClient
) {
453 // No need to check for nullptr. A nullptr parameter simply means to remove
454 // the previous client which we do by setting to nullptr anyway.
460 void SMILTimedElement::SampleAt(SMILTime aContainerTime
) {
461 if (mIsDisabled
) return;
463 // Milestones are cleared before a sample
464 mPrevRegisteredMilestone
= sMaxMilestone
;
466 DoSampleAt(aContainerTime
, false);
469 void SMILTimedElement::SampleEndAt(SMILTime aContainerTime
) {
470 if (mIsDisabled
) return;
472 // Milestones are cleared before a sample
473 mPrevRegisteredMilestone
= sMaxMilestone
;
475 // If the current interval changes, we don't bother trying to remove any old
476 // milestones we'd registered. So it's possible to get a call here to end an
477 // interval at a time that no longer reflects the end of the current interval.
479 // For now we just check that we're actually in an interval but note that the
480 // initial sample we use to initialise the model is an end sample. This is
481 // because we want to resolve all the instance times before committing to an
482 // initial interval. Therefore an end sample from the startup state is also
484 if (mElementState
== STATE_ACTIVE
|| mElementState
== STATE_STARTUP
) {
485 DoSampleAt(aContainerTime
, true); // End sample
487 // Even if this was an unnecessary milestone sample we want to be sure that
488 // our next real milestone is registered.
493 void SMILTimedElement::DoSampleAt(SMILTime aContainerTime
, bool aEndOnly
) {
494 MOZ_ASSERT(mAnimationElement
,
495 "Got sample before being registered with an animation element");
496 MOZ_ASSERT(GetTimeContainer(),
497 "Got sample without being registered with a time container");
499 // This could probably happen if we later implement externalResourcesRequired
500 // (bug 277955) and whilst waiting for those resources (and the animation to
501 // start) we transfer a node from another document fragment that has already
502 // started. In such a case we might receive milestone samples registered with
503 // the already active container.
504 if (GetTimeContainer()->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN
))
507 // We use an end-sample to start animation since an end-sample lets us
508 // tentatively create an interval without committing to it (by transitioning
509 // to the ACTIVE state) and this is necessary because we might have
510 // dependencies on other animations that are yet to start. After these
511 // other animations start, it may be necessary to revise our initial interval.
513 // However, sometimes instead of an end-sample we can get a regular sample
514 // during STARTUP state. This can happen, for example, if we register
515 // a milestone before time t=0 and are then re-bound to the tree (which sends
516 // us back to the STARTUP state). In such a case we should just ignore the
517 // sample and wait for our real initial sample which will be an end-sample.
518 if (mElementState
== STATE_STARTUP
&& !aEndOnly
) return;
520 bool finishedSeek
= false;
521 if (GetTimeContainer()->IsSeeking() && mSeekState
== SEEK_NOT_SEEKING
) {
522 mSeekState
= mElementState
== STATE_ACTIVE
? SEEK_FORWARD_FROM_ACTIVE
523 : SEEK_FORWARD_FROM_INACTIVE
;
524 } else if (mSeekState
!= SEEK_NOT_SEEKING
&&
525 !GetTimeContainer()->IsSeeking()) {
530 SMILTimeValue
sampleTime(aContainerTime
);
535 if (mElementState
== STATE_STARTUP
|| mElementState
== STATE_POSTACTIVE
) {
536 MOZ_ASSERT(!mCurrentInterval
,
537 "Shouldn't have current interval in startup or postactive "
540 MOZ_ASSERT(mCurrentInterval
,
541 "Should have current interval in waiting and active states");
545 stateChanged
= false;
547 switch (mElementState
) {
548 case STATE_STARTUP
: {
549 SMILInterval firstInterval
;
551 GetNextInterval(nullptr, nullptr, nullptr, firstInterval
)
555 if (mElementState
== STATE_WAITING
) {
556 mCurrentInterval
= MakeUnique
<SMILInterval
>(firstInterval
);
561 case STATE_WAITING
: {
562 if (mCurrentInterval
->Begin()->Time() <= sampleTime
) {
563 mElementState
= STATE_ACTIVE
;
564 mCurrentInterval
->FixBegin();
566 mClient
->Activate(mCurrentInterval
->Begin()->Time().GetMillis());
568 if (mSeekState
== SEEK_NOT_SEEKING
) {
569 FireTimeEventAsync(eSMILBeginEvent
, 0);
572 Reset(); // Apply restart behaviour
573 // The call to Reset() may mean that the end point of our current
574 // interval should be changed and so we should update the interval
575 // now. However, calling UpdateCurrentInterval could result in the
576 // interval getting deleted (perhaps through some web of syncbase
577 // dependencies) therefore we make updating the interval the last
578 // thing we do. There is no guarantee that mCurrentInterval is set
580 UpdateCurrentInterval();
587 // Ending early will change the interval but we don't notify dependents
588 // of the change until we have closed off the current interval (since we
589 // don't want dependencies to un-end our early end).
590 bool didApplyEarlyEnd
= ApplyEarlyEnd(sampleTime
);
592 if (mCurrentInterval
->End()->Time() <= sampleTime
) {
593 SMILInterval newInterval
;
594 mElementState
= GetNextInterval(mCurrentInterval
.get(), nullptr,
595 nullptr, newInterval
)
599 mClient
->Inactivate(mFillMode
== FILL_FREEZE
);
601 mCurrentInterval
->FixEnd();
602 if (mSeekState
== SEEK_NOT_SEEKING
) {
603 FireTimeEventAsync(eSMILEndEvent
, 0);
605 mCurrentRepeatIteration
= 0;
606 mOldIntervals
.AppendElement(std::move(mCurrentInterval
));
608 if (mElementState
== STATE_WAITING
) {
609 mCurrentInterval
= MakeUnique
<SMILInterval
>(newInterval
);
611 // We are now in a consistent state to dispatch notifications
612 if (didApplyEarlyEnd
) {
613 NotifyChangedInterval(mOldIntervals
.LastElement().get(), false,
616 if (mElementState
== STATE_WAITING
) {
621 } else if (mCurrentInterval
->Begin()->Time() <= sampleTime
) {
622 MOZ_ASSERT(!didApplyEarlyEnd
, "We got an early end, but didn't end");
623 SMILTime beginTime
= mCurrentInterval
->Begin()->Time().GetMillis();
624 SMILTime activeTime
= aContainerTime
- beginTime
;
626 // The 'min' attribute can cause the active interval to be longer than
627 // the 'repeating interval'.
628 // In that extended period we apply the fill mode.
629 if (GetRepeatDuration() <= SMILTimeValue(activeTime
)) {
630 if (mClient
&& mClient
->IsActive()) {
631 mClient
->Inactivate(mFillMode
== FILL_FREEZE
);
635 SampleSimpleTime(activeTime
);
637 // We register our repeat times as milestones (except when we're
638 // seeking) so we should get a sample at exactly the time we repeat.
639 // (And even when we are seeking we want to update
640 // mCurrentRepeatIteration so we do that first before testing the
642 uint32_t prevRepeatIteration
= mCurrentRepeatIteration
;
643 if (ActiveTimeToSimpleTime(activeTime
, mCurrentRepeatIteration
) ==
645 mCurrentRepeatIteration
!= prevRepeatIteration
&&
646 mCurrentRepeatIteration
&& mSeekState
== SEEK_NOT_SEEKING
) {
647 FireTimeEventAsync(eSMILRepeatEvent
,
648 static_cast<int32_t>(mCurrentRepeatIteration
));
652 // Otherwise |sampleTime| is *before* the current interval. That
653 // normally doesn't happen but can happen if we get a stray milestone
654 // sample (e.g. if we registered a milestone with a time container that
655 // later got re-attached as a child of a more advanced time container).
656 // In that case we should just ignore the sample.
659 case STATE_POSTACTIVE
:
663 // Generally we continue driving the state machine so long as we have
664 // changed state. However, for end samples we only drive the state machine
665 // as far as the waiting or postactive state because we don't want to commit
666 // to any new interval (by transitioning to the active state) until all the
667 // end samples have finished and we then have complete information about the
668 // available instance times upon which to base our next interval.
669 } while (stateChanged
&& (!aEndOnly
|| (mElementState
!= STATE_WAITING
&&
670 mElementState
!= STATE_POSTACTIVE
)));
678 void SMILTimedElement::HandleContainerTimeChange() {
679 // In future we could possibly introduce a separate change notice for time
680 // container changes and only notify those dependents who live in other time
681 // containers. For now we don't bother because when we re-resolve the time in
682 // the SMILTimeValueSpec we'll check if anything has changed and if not, we
683 // won't go any further.
684 if (mElementState
== STATE_WAITING
|| mElementState
== STATE_ACTIVE
) {
685 NotifyChangedInterval(mCurrentInterval
.get(), false, false);
690 bool RemoveNonDynamic(SMILInstanceTime
* aInstanceTime
) {
691 // Generally dynamically-generated instance times (DOM calls, event-based
692 // times) are not associated with their creator SMILTimeValueSpec since
693 // they may outlive them.
694 MOZ_ASSERT(!aInstanceTime
->IsDynamic() || !aInstanceTime
->GetCreator(),
695 "Dynamic instance time should be unlinked from its creator");
696 return !aInstanceTime
->IsDynamic() && !aInstanceTime
->ShouldPreserve();
700 void SMILTimedElement::Rewind() {
701 MOZ_ASSERT(mAnimationElement
,
702 "Got rewind request before being attached to an animation "
705 // It's possible to get a rewind request whilst we're already in the middle of
706 // a backwards seek. This can happen when we're performing tree surgery and
707 // seeking containers at the same time because we can end up requesting
708 // a local rewind on an element after binding it to a new container and then
709 // performing a rewind on that container as a whole without sampling in
712 // However, it should currently be impossible to get a rewind in the middle of
713 // a forwards seek since forwards seeks are detected and processed within the
715 if (mSeekState
== SEEK_NOT_SEEKING
) {
716 mSeekState
= mElementState
== STATE_ACTIVE
? SEEK_BACKWARD_FROM_ACTIVE
717 : SEEK_BACKWARD_FROM_INACTIVE
;
719 MOZ_ASSERT(mSeekState
== SEEK_BACKWARD_FROM_INACTIVE
||
720 mSeekState
== SEEK_BACKWARD_FROM_ACTIVE
,
721 "Rewind in the middle of a forwards seek?");
723 ClearTimingState(RemoveNonDynamic
);
724 RebuildTimingState(RemoveNonDynamic
);
726 MOZ_ASSERT(!mCurrentInterval
, "Current interval is set at end of rewind");
730 bool RemoveAll(SMILInstanceTime
* aInstanceTime
) { return true; }
733 bool SMILTimedElement::SetIsDisabled(bool aIsDisabled
) {
734 if (mIsDisabled
== aIsDisabled
) return false;
738 ClearTimingState(RemoveAll
);
740 RebuildTimingState(RemoveAll
);
747 bool RemoveNonDOM(SMILInstanceTime
* aInstanceTime
) {
748 return !aInstanceTime
->FromDOM() && !aInstanceTime
->ShouldPreserve();
752 bool SMILTimedElement::SetAttr(nsAtom
* aAttribute
, const nsAString
& aValue
,
753 nsAttrValue
& aResult
, Element
& aContextElement
,
754 nsresult
* aParseResult
) {
755 bool foundMatch
= true;
756 nsresult parseResult
= NS_OK
;
758 if (aAttribute
== nsGkAtoms::begin
) {
759 parseResult
= SetBeginSpec(aValue
, aContextElement
, RemoveNonDOM
);
760 } else if (aAttribute
== nsGkAtoms::dur
) {
761 parseResult
= SetSimpleDuration(aValue
);
762 } else if (aAttribute
== nsGkAtoms::end
) {
763 parseResult
= SetEndSpec(aValue
, aContextElement
, RemoveNonDOM
);
764 } else if (aAttribute
== nsGkAtoms::fill
) {
765 parseResult
= SetFillMode(aValue
);
766 } else if (aAttribute
== nsGkAtoms::max
) {
767 parseResult
= SetMax(aValue
);
768 } else if (aAttribute
== nsGkAtoms::min
) {
769 parseResult
= SetMin(aValue
);
770 } else if (aAttribute
== nsGkAtoms::repeatCount
) {
771 parseResult
= SetRepeatCount(aValue
);
772 } else if (aAttribute
== nsGkAtoms::repeatDur
) {
773 parseResult
= SetRepeatDur(aValue
);
774 } else if (aAttribute
== nsGkAtoms::restart
) {
775 parseResult
= SetRestart(aValue
);
781 aResult
.SetTo(aValue
);
783 *aParseResult
= parseResult
;
790 bool SMILTimedElement::UnsetAttr(nsAtom
* aAttribute
) {
791 bool foundMatch
= true;
793 if (aAttribute
== nsGkAtoms::begin
) {
794 UnsetBeginSpec(RemoveNonDOM
);
795 } else if (aAttribute
== nsGkAtoms::dur
) {
796 UnsetSimpleDuration();
797 } else if (aAttribute
== nsGkAtoms::end
) {
798 UnsetEndSpec(RemoveNonDOM
);
799 } else if (aAttribute
== nsGkAtoms::fill
) {
801 } else if (aAttribute
== nsGkAtoms::max
) {
803 } else if (aAttribute
== nsGkAtoms::min
) {
805 } else if (aAttribute
== nsGkAtoms::repeatCount
) {
807 } else if (aAttribute
== nsGkAtoms::repeatDur
) {
809 } else if (aAttribute
== nsGkAtoms::restart
) {
818 //----------------------------------------------------------------------
819 // Setters and unsetters
821 nsresult
SMILTimedElement::SetBeginSpec(const nsAString
& aBeginSpec
,
822 Element
& aContextElement
,
823 RemovalTestFunction aRemove
) {
824 return SetBeginOrEndSpec(aBeginSpec
, aContextElement
, true /*isBegin*/,
828 void SMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove
) {
829 ClearSpecs(mBeginSpecs
, mBeginInstances
, aRemove
);
830 UpdateCurrentInterval();
833 nsresult
SMILTimedElement::SetEndSpec(const nsAString
& aEndSpec
,
834 Element
& aContextElement
,
835 RemovalTestFunction aRemove
) {
836 return SetBeginOrEndSpec(aEndSpec
, aContextElement
, false /*!isBegin*/,
840 void SMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove
) {
841 ClearSpecs(mEndSpecs
, mEndInstances
, aRemove
);
842 UpdateCurrentInterval();
845 nsresult
SMILTimedElement::SetSimpleDuration(const nsAString
& aDurSpec
) {
846 // Update the current interval before returning
847 AutoIntervalUpdater
updater(*this);
849 SMILTimeValue duration
;
850 const nsAString
& dur
= SMILParserUtils::TrimWhitespace(aDurSpec
);
852 // SVG-specific: "For SVG's animation elements, if "media" is specified, the
853 // attribute will be ignored." (SVG 1.1, section 19.2.6)
854 if (dur
.EqualsLiteral("media") || dur
.EqualsLiteral("indefinite")) {
855 duration
.SetIndefinite();
857 if (!SMILParserUtils::ParseClockValue(
858 dur
, SMILTimeValue::Rounding::EnsureNonZero
, &duration
) ||
860 mSimpleDur
.SetIndefinite();
861 return NS_ERROR_FAILURE
;
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");
868 mSimpleDur
= duration
;
873 void SMILTimedElement::UnsetSimpleDuration() {
874 mSimpleDur
.SetIndefinite();
875 UpdateCurrentInterval();
878 nsresult
SMILTimedElement::SetMin(const nsAString
& aMinSpec
) {
879 // Update the current interval before returning
880 AutoIntervalUpdater
updater(*this);
882 SMILTimeValue duration
;
883 const nsAString
& min
= SMILParserUtils::TrimWhitespace(aMinSpec
);
885 if (min
.EqualsLiteral("media")) {
886 duration
= SMILTimeValue::Zero();
888 if (!SMILParserUtils::ParseClockValue(min
, SMILTimeValue::Rounding::Nearest
,
890 mMin
= SMILTimeValue::Zero();
891 return NS_ERROR_FAILURE
;
895 MOZ_ASSERT(duration
.GetMillis() >= 0L, "Invalid duration");
902 void SMILTimedElement::UnsetMin() {
903 mMin
= SMILTimeValue::Zero();
904 UpdateCurrentInterval();
907 nsresult
SMILTimedElement::SetMax(const nsAString
& aMaxSpec
) {
908 // Update the current interval before returning
909 AutoIntervalUpdater
updater(*this);
911 SMILTimeValue duration
;
912 const nsAString
& max
= SMILParserUtils::TrimWhitespace(aMaxSpec
);
914 if (max
.EqualsLiteral("media") || max
.EqualsLiteral("indefinite")) {
915 duration
.SetIndefinite();
917 if (!SMILParserUtils::ParseClockValue(
918 max
, SMILTimeValue::Rounding::EnsureNonZero
, &duration
) ||
920 mMax
.SetIndefinite();
921 return NS_ERROR_FAILURE
;
923 MOZ_ASSERT(duration
.GetMillis() > 0L, "Invalid duration");
931 void SMILTimedElement::UnsetMax() {
932 mMax
.SetIndefinite();
933 UpdateCurrentInterval();
936 nsresult
SMILTimedElement::SetRestart(const nsAString
& aRestartSpec
) {
938 bool parseResult
= temp
.ParseEnumValue(aRestartSpec
, sRestartModeTable
, true);
940 parseResult
? SMILRestartMode(temp
.GetEnumValue()) : RESTART_ALWAYS
;
941 UpdateCurrentInterval();
942 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
945 void SMILTimedElement::UnsetRestart() {
946 mRestartMode
= RESTART_ALWAYS
;
947 UpdateCurrentInterval();
950 nsresult
SMILTimedElement::SetRepeatCount(const nsAString
& aRepeatCountSpec
) {
951 // Update the current interval before returning
952 AutoIntervalUpdater
updater(*this);
954 SMILRepeatCount newRepeatCount
;
956 if (SMILParserUtils::ParseRepeatCount(aRepeatCountSpec
, newRepeatCount
)) {
957 mRepeatCount
= newRepeatCount
;
960 mRepeatCount
.Unset();
961 return NS_ERROR_FAILURE
;
964 void SMILTimedElement::UnsetRepeatCount() {
965 mRepeatCount
.Unset();
966 UpdateCurrentInterval();
969 nsresult
SMILTimedElement::SetRepeatDur(const nsAString
& aRepeatDurSpec
) {
970 // Update the current interval before returning
971 AutoIntervalUpdater
updater(*this);
973 SMILTimeValue duration
;
975 const nsAString
& repeatDur
= SMILParserUtils::TrimWhitespace(aRepeatDurSpec
);
977 if (repeatDur
.EqualsLiteral("indefinite")) {
978 duration
.SetIndefinite();
980 if (!SMILParserUtils::ParseClockValue(
981 repeatDur
, SMILTimeValue::Rounding::EnsureNonZero
, &duration
)) {
982 mRepeatDur
.SetUnresolved();
983 return NS_ERROR_FAILURE
;
987 mRepeatDur
= duration
;
992 void SMILTimedElement::UnsetRepeatDur() {
993 mRepeatDur
.SetUnresolved();
994 UpdateCurrentInterval();
997 nsresult
SMILTimedElement::SetFillMode(const nsAString
& aFillModeSpec
) {
998 uint16_t previousFillMode
= mFillMode
;
1001 bool parseResult
= temp
.ParseEnumValue(aFillModeSpec
, sFillModeTable
, true);
1002 mFillMode
= parseResult
? SMILFillMode(temp
.GetEnumValue()) : FILL_REMOVE
;
1004 // Update fill mode of client
1005 if (mFillMode
!= previousFillMode
&& HasClientInFillRange()) {
1006 mClient
->Inactivate(mFillMode
== FILL_FREEZE
);
1010 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
1013 void SMILTimedElement::UnsetFillMode() {
1014 uint16_t previousFillMode
= mFillMode
;
1015 mFillMode
= FILL_REMOVE
;
1016 if (previousFillMode
== FILL_FREEZE
&& HasClientInFillRange()) {
1017 mClient
->Inactivate(false);
1021 void SMILTimedElement::AddDependent(SMILTimeValueSpec
& aDependent
) {
1022 // There's probably no harm in attempting to register a dependent
1023 // SMILTimeValueSpec twice, but we're not expecting it to happen.
1024 MOZ_ASSERT(!mTimeDependents
.GetEntry(&aDependent
),
1025 "SMILTimeValueSpec is already registered as a dependency");
1026 mTimeDependents
.PutEntry(&aDependent
);
1028 // Add current interval. We could add historical intervals too but that would
1029 // cause unpredictable results since some intervals may have been filtered.
1030 // SMIL doesn't say what to do here so for simplicity and consistency we
1031 // simply add the current interval if there is one.
1033 // It's not necessary to call SyncPauseTime since we're dealing with
1034 // historical instance times not newly added ones.
1035 if (mCurrentInterval
) {
1036 aDependent
.HandleNewInterval(*mCurrentInterval
, GetTimeContainer());
1040 void SMILTimedElement::RemoveDependent(SMILTimeValueSpec
& aDependent
) {
1041 mTimeDependents
.RemoveEntry(&aDependent
);
1044 bool SMILTimedElement::IsTimeDependent(const SMILTimedElement
& aOther
) const {
1045 const SMILInstanceTime
* thisBegin
= GetEffectiveBeginInstance();
1046 const SMILInstanceTime
* otherBegin
= aOther
.GetEffectiveBeginInstance();
1048 if (!thisBegin
|| !otherBegin
) return false;
1050 return thisBegin
->IsDependentOn(*otherBegin
);
1053 void SMILTimedElement::BindToTree(Element
& aContextElement
) {
1054 // Reset previously registered milestone since we may be registering with
1055 // a different time container now.
1056 mPrevRegisteredMilestone
= sMaxMilestone
;
1058 // If we were already active then clear all our timing information and start
1060 if (mElementState
!= STATE_STARTUP
) {
1061 mSeekState
= SEEK_NOT_SEEKING
;
1065 // Scope updateBatcher to last only for the ResolveReferences calls:
1067 AutoIntervalUpdateBatcher
updateBatcher(*this);
1069 // Resolve references to other parts of the tree
1070 for (UniquePtr
<SMILTimeValueSpec
>& beginSpec
: mBeginSpecs
) {
1071 beginSpec
->ResolveReferences(aContextElement
);
1074 for (UniquePtr
<SMILTimeValueSpec
>& endSpec
: mEndSpecs
) {
1075 endSpec
->ResolveReferences(aContextElement
);
1079 RegisterMilestone();
1082 void SMILTimedElement::HandleTargetElementChange(Element
* aNewTarget
) {
1083 AutoIntervalUpdateBatcher
updateBatcher(*this);
1085 for (UniquePtr
<SMILTimeValueSpec
>& beginSpec
: mBeginSpecs
) {
1086 beginSpec
->HandleTargetElementChange(aNewTarget
);
1089 for (UniquePtr
<SMILTimeValueSpec
>& endSpec
: mEndSpecs
) {
1090 endSpec
->HandleTargetElementChange(aNewTarget
);
1094 void SMILTimedElement::Traverse(nsCycleCollectionTraversalCallback
* aCallback
) {
1095 for (UniquePtr
<SMILTimeValueSpec
>& beginSpec
: mBeginSpecs
) {
1096 MOZ_ASSERT(beginSpec
, "null SMILTimeValueSpec in list of begin specs");
1097 beginSpec
->Traverse(aCallback
);
1100 for (UniquePtr
<SMILTimeValueSpec
>& endSpec
: mEndSpecs
) {
1101 MOZ_ASSERT(endSpec
, "null SMILTimeValueSpec in list of end specs");
1102 endSpec
->Traverse(aCallback
);
1106 void SMILTimedElement::Unlink() {
1107 AutoIntervalUpdateBatcher
updateBatcher(*this);
1109 // Remove dependencies on other elements
1110 for (UniquePtr
<SMILTimeValueSpec
>& beginSpec
: mBeginSpecs
) {
1111 MOZ_ASSERT(beginSpec
, "null SMILTimeValueSpec in list of begin specs");
1112 beginSpec
->Unlink();
1115 for (UniquePtr
<SMILTimeValueSpec
>& endSpec
: mEndSpecs
) {
1116 MOZ_ASSERT(endSpec
, "null SMILTimeValueSpec in list of end specs");
1122 // Make sure we don't notify other elements of new intervals
1123 mTimeDependents
.Clear();
1126 //----------------------------------------------------------------------
1127 // Implementation helpers
1129 nsresult
SMILTimedElement::SetBeginOrEndSpec(const nsAString
& aSpec
,
1130 Element
& aContextElement
,
1132 RemovalTestFunction aRemove
) {
1133 TimeValueSpecList
& timeSpecsList
= aIsBegin
? mBeginSpecs
: mEndSpecs
;
1134 InstanceTimeList
& instances
= aIsBegin
? mBeginInstances
: mEndInstances
;
1136 ClearSpecs(timeSpecsList
, instances
, aRemove
);
1138 AutoIntervalUpdateBatcher
updateBatcher(*this);
1140 nsCharSeparatedTokenizer
tokenizer(aSpec
, ';');
1141 if (!tokenizer
.hasMoreTokens()) { // Empty list
1142 return NS_ERROR_FAILURE
;
1145 bool hadFailure
= false;
1146 while (tokenizer
.hasMoreTokens()) {
1147 auto spec
= MakeUnique
<SMILTimeValueSpec
>(*this, aIsBegin
);
1148 nsresult rv
= spec
->SetSpec(tokenizer
.nextToken(), aContextElement
);
1149 if (NS_SUCCEEDED(rv
)) {
1150 timeSpecsList
.AppendElement(std::move(spec
));
1156 // The return value from this function is only used to determine if we should
1157 // print a console message or not, so we return failure if we had one or more
1158 // failures but we don't need to differentiate between different types of
1159 // failures or the number of failures.
1160 return hadFailure
? NS_ERROR_FAILURE
: NS_OK
;
1164 // Adaptor functor for RemoveInstanceTimes that allows us to use function
1165 // pointers instead.
1166 // Without this we'd have to either templatize ClearSpecs and all its callers
1167 // or pass bool flags around to specify which removal function to use here.
1168 class MOZ_STACK_CLASS RemoveByFunction
{
1170 explicit RemoveByFunction(SMILTimedElement::RemovalTestFunction aFunction
)
1171 : mFunction(aFunction
) {}
1172 bool operator()(SMILInstanceTime
* aInstanceTime
, uint32_t /*aIndex*/) {
1173 return mFunction(aInstanceTime
);
1177 SMILTimedElement::RemovalTestFunction mFunction
;
1181 void SMILTimedElement::ClearSpecs(TimeValueSpecList
& aSpecs
,
1182 InstanceTimeList
& aInstances
,
1183 RemovalTestFunction aRemove
) {
1184 AutoIntervalUpdateBatcher
updateBatcher(*this);
1186 for (UniquePtr
<SMILTimeValueSpec
>& spec
: aSpecs
) {
1191 RemoveByFunction
removeByFunction(aRemove
);
1192 RemoveInstanceTimes(aInstances
, removeByFunction
);
1195 void SMILTimedElement::ClearIntervals() {
1196 if (mElementState
!= STATE_STARTUP
) {
1197 mElementState
= STATE_POSTACTIVE
;
1199 mCurrentRepeatIteration
= 0;
1200 ResetCurrentInterval();
1202 // Remove old intervals
1203 for (int32_t i
= mOldIntervals
.Length() - 1; i
>= 0; --i
) {
1204 mOldIntervals
[i
]->Unlink();
1206 mOldIntervals
.Clear();
1209 bool SMILTimedElement::ApplyEarlyEnd(const SMILTimeValue
& aSampleTime
) {
1210 // This should only be called within DoSampleAt as a helper function
1211 MOZ_ASSERT(mElementState
== STATE_ACTIVE
,
1212 "Unexpected state to try to apply an early end");
1214 bool updated
= false;
1216 // Only apply an early end if we're not already ending.
1217 if (mCurrentInterval
->End()->Time() > aSampleTime
) {
1218 SMILInstanceTime
* earlyEnd
= CheckForEarlyEnd(aSampleTime
);
1220 if (earlyEnd
->IsDependent()) {
1221 // Generate a new instance time for the early end since the
1222 // existing instance time is part of some dependency chain that we
1223 // don't want to participate in.
1224 RefPtr
<SMILInstanceTime
> newEarlyEnd
=
1225 new SMILInstanceTime(earlyEnd
->Time());
1226 mCurrentInterval
->SetEnd(*newEarlyEnd
);
1228 mCurrentInterval
->SetEnd(*earlyEnd
);
1237 class MOZ_STACK_CLASS RemoveReset
{
1239 explicit RemoveReset(const SMILInstanceTime
* aCurrentIntervalBegin
)
1240 : mCurrentIntervalBegin(aCurrentIntervalBegin
) {}
1241 bool operator()(SMILInstanceTime
* aInstanceTime
, uint32_t /*aIndex*/) {
1242 // SMIL 3.0 section 5.4.3, 'Resetting element state':
1243 // Any instance times associated with past Event-values, Repeat-values,
1244 // Accesskey-values or added via DOM method calls are removed from the
1245 // dependent begin and end instance times lists. In effect, all events
1246 // and DOM methods calls in the past are cleared. This does not apply to
1247 // an instance time that defines the begin of the current interval.
1248 return aInstanceTime
->IsDynamic() && !aInstanceTime
->ShouldPreserve() &&
1249 (!mCurrentIntervalBegin
|| aInstanceTime
!= mCurrentIntervalBegin
);
1253 const SMILInstanceTime
* mCurrentIntervalBegin
;
1257 void SMILTimedElement::Reset() {
1258 RemoveReset
resetBegin(mCurrentInterval
? mCurrentInterval
->Begin()
1260 RemoveInstanceTimes(mBeginInstances
, resetBegin
);
1262 RemoveReset
resetEnd(nullptr);
1263 RemoveInstanceTimes(mEndInstances
, resetEnd
);
1266 void SMILTimedElement::ClearTimingState(RemovalTestFunction aRemove
) {
1267 mElementState
= STATE_STARTUP
;
1270 UnsetBeginSpec(aRemove
);
1271 UnsetEndSpec(aRemove
);
1274 mClient
->Inactivate(false);
1278 void SMILTimedElement::RebuildTimingState(RemovalTestFunction aRemove
) {
1279 MOZ_ASSERT(mAnimationElement
,
1280 "Attempting to enable a timed element not attached to an "
1281 "animation element");
1282 MOZ_ASSERT(mElementState
== STATE_STARTUP
,
1283 "Rebuilding timing state from non-startup state");
1285 if (mAnimationElement
->HasAttr(nsGkAtoms::begin
)) {
1286 nsAutoString attValue
;
1287 mAnimationElement
->GetAttr(nsGkAtoms::begin
, attValue
);
1288 SetBeginSpec(attValue
, *mAnimationElement
, aRemove
);
1291 if (mAnimationElement
->HasAttr(nsGkAtoms::end
)) {
1292 nsAutoString attValue
;
1293 mAnimationElement
->GetAttr(nsGkAtoms::end
, attValue
);
1294 SetEndSpec(attValue
, *mAnimationElement
, aRemove
);
1297 mPrevRegisteredMilestone
= sMaxMilestone
;
1298 RegisterMilestone();
1301 void SMILTimedElement::DoPostSeek() {
1302 // Finish backwards seek
1303 if (mSeekState
== SEEK_BACKWARD_FROM_INACTIVE
||
1304 mSeekState
== SEEK_BACKWARD_FROM_ACTIVE
) {
1305 // Previously some dynamic instance times may have been marked to be
1306 // preserved because they were endpoints of an historic interval (which may
1307 // or may not have been filtered). Now that we've finished a seek we should
1308 // clear that flag for those instance times whose intervals are no longer
1310 UnpreserveInstanceTimes(mBeginInstances
);
1311 UnpreserveInstanceTimes(mEndInstances
);
1313 // Now that the times have been unmarked perform a reset. This might seem
1314 // counter-intuitive when we're only doing a seek within an interval but
1315 // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing':
1316 // Resolved end times associated with events, Repeat-values,
1317 // Accesskey-values or added via DOM method calls are cleared when seeking
1318 // to time earlier than the resolved end time.
1320 UpdateCurrentInterval();
1323 switch (mSeekState
) {
1324 case SEEK_FORWARD_FROM_ACTIVE
:
1325 case SEEK_BACKWARD_FROM_ACTIVE
:
1326 if (mElementState
!= STATE_ACTIVE
) {
1327 FireTimeEventAsync(eSMILEndEvent
, 0);
1331 case SEEK_FORWARD_FROM_INACTIVE
:
1332 case SEEK_BACKWARD_FROM_INACTIVE
:
1333 if (mElementState
== STATE_ACTIVE
) {
1334 FireTimeEventAsync(eSMILBeginEvent
, 0);
1338 case SEEK_NOT_SEEKING
:
1343 mSeekState
= SEEK_NOT_SEEKING
;
1346 void SMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList
& aList
) {
1347 const SMILInterval
* prevInterval
= GetPreviousInterval();
1348 const SMILInstanceTime
* cutoff
= mCurrentInterval
? mCurrentInterval
->Begin()
1349 : prevInterval
? prevInterval
->Begin()
1351 for (RefPtr
<SMILInstanceTime
>& instance
: aList
) {
1352 if (!cutoff
|| cutoff
->Time().CompareTo(instance
->Time()) < 0) {
1353 instance
->UnmarkShouldPreserve();
1358 void SMILTimedElement::FilterHistory() {
1359 // We should filter the intervals first, since instance times still used in an
1360 // interval won't be filtered.
1362 FilterInstanceTimes(mBeginInstances
);
1363 FilterInstanceTimes(mEndInstances
);
1366 void SMILTimedElement::FilterIntervals() {
1367 // We can filter old intervals that:
1369 // a) are not the previous interval; AND
1370 // b) are not in the middle of a dependency chain; AND
1371 // c) are not the first interval
1373 // Condition (a) is necessary since the previous interval is used for applying
1374 // fill effects and updating the current interval.
1376 // Condition (b) is necessary since even if this interval itself is not
1377 // active, it may be part of a dependency chain that includes active
1378 // intervals. Such chains are used to establish priorities within the
1379 // animation sandwich.
1381 // Condition (c) is necessary to support hyperlinks that target animations
1382 // since in some cases the defined behavior is to seek the document back to
1383 // the first resolved begin time. Presumably the intention here is not
1384 // actually to use the first resolved begin time, the
1385 // _the_first_resolved_begin_time_that_produced_an_interval. That is,
1386 // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek
1387 // to 1s. The spec doesn't say this but I'm pretty sure that is the intention.
1388 // It seems negative times were simply not considered.
1390 // Although the above conditions allow us to safely filter intervals for most
1391 // scenarios they do not cover all cases and there will still be scenarios
1392 // that generate intervals indefinitely. In such a case we simply set
1393 // a maximum number of intervals and drop any intervals beyond that threshold.
1395 uint32_t threshold
= mOldIntervals
.Length() > sMaxNumIntervals
1396 ? mOldIntervals
.Length() - sMaxNumIntervals
1398 IntervalList filteredList
;
1399 for (uint32_t i
= 0; i
< mOldIntervals
.Length(); ++i
) {
1400 SMILInterval
* interval
= mOldIntervals
[i
].get();
1401 if (i
!= 0 && /*skip first interval*/
1402 i
+ 1 < mOldIntervals
.Length() && /*skip previous interval*/
1403 (i
< threshold
|| !interval
->IsDependencyChainLink())) {
1404 interval
->Unlink(true /*filtered, not deleted*/);
1406 filteredList
.AppendElement(std::move(mOldIntervals
[i
]));
1409 mOldIntervals
= std::move(filteredList
);
1413 class MOZ_STACK_CLASS RemoveFiltered
{
1415 explicit RemoveFiltered(SMILTimeValue aCutoff
) : mCutoff(aCutoff
) {}
1416 bool operator()(SMILInstanceTime
* aInstanceTime
, uint32_t /*aIndex*/) {
1417 // We can filter instance times that:
1418 // a) Precede the end point of the previous interval; AND
1419 // b) Are NOT syncbase times that might be updated to a time after the end
1420 // point of the previous interval; AND
1421 // c) Are NOT fixed end points in any remaining interval.
1422 return aInstanceTime
->Time() < mCutoff
&& aInstanceTime
->IsFixedTime() &&
1423 !aInstanceTime
->ShouldPreserve();
1427 SMILTimeValue mCutoff
;
1430 class MOZ_STACK_CLASS RemoveBelowThreshold
{
1432 RemoveBelowThreshold(uint32_t aThreshold
,
1433 nsTArray
<const SMILInstanceTime
*>& aTimesToKeep
)
1434 : mThreshold(aThreshold
), mTimesToKeep(aTimesToKeep
) {}
1435 bool operator()(SMILInstanceTime
* aInstanceTime
, uint32_t aIndex
) {
1436 return aIndex
< mThreshold
&& !mTimesToKeep
.Contains(aInstanceTime
);
1440 uint32_t mThreshold
;
1441 nsTArray
<const SMILInstanceTime
*>& mTimesToKeep
;
1445 void SMILTimedElement::FilterInstanceTimes(InstanceTimeList
& aList
) {
1446 if (GetPreviousInterval()) {
1447 RemoveFiltered
removeFiltered(GetPreviousInterval()->End()->Time());
1448 RemoveInstanceTimes(aList
, removeFiltered
);
1451 // As with intervals it is possible to create a document that, even despite
1452 // our most aggressive filtering, will generate instance times indefinitely
1453 // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as
1454 // they're unpredictable due to the possibility of seeking the document which
1455 // may prevent some events from being generated). Therefore we introduce
1456 // a hard cutoff at which point we just drop the oldest instance times.
1457 if (aList
.Length() > sMaxNumInstanceTimes
) {
1458 uint32_t threshold
= aList
.Length() - sMaxNumInstanceTimes
;
1459 // There are a few instance times we should keep though, notably:
1460 // - the current interval begin time,
1461 // - the previous interval end time (see note in RemoveInstanceTimes)
1462 // - the first interval begin time (see note in FilterIntervals)
1463 nsTArray
<const SMILInstanceTime
*> timesToKeep
;
1464 if (mCurrentInterval
) {
1465 timesToKeep
.AppendElement(mCurrentInterval
->Begin());
1467 const SMILInterval
* prevInterval
= GetPreviousInterval();
1469 timesToKeep
.AppendElement(prevInterval
->End());
1471 if (!mOldIntervals
.IsEmpty()) {
1472 timesToKeep
.AppendElement(mOldIntervals
[0]->Begin());
1474 RemoveBelowThreshold
removeBelowThreshold(threshold
, timesToKeep
);
1475 RemoveInstanceTimes(aList
, removeBelowThreshold
);
1480 // This method is based on the pseudocode given in the SMILANIM spec.
1483 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start
1485 bool SMILTimedElement::GetNextInterval(const SMILInterval
* aPrevInterval
,
1486 const SMILInterval
* aReplacedInterval
,
1487 const SMILInstanceTime
* aFixedBeginTime
,
1488 SMILInterval
& aResult
) const {
1489 MOZ_ASSERT(!aFixedBeginTime
|| aFixedBeginTime
->Time().IsDefinite(),
1490 "Unresolved or indefinite begin time given for interval start");
1491 static const SMILTimeValue
zeroTime(0L);
1493 if (mRestartMode
== RESTART_NEVER
&& aPrevInterval
) return false;
1495 // Calc starting point
1496 SMILTimeValue beginAfter
;
1497 bool prevIntervalWasZeroDur
= false;
1498 if (aPrevInterval
) {
1499 beginAfter
= aPrevInterval
->End()->Time();
1500 prevIntervalWasZeroDur
=
1501 aPrevInterval
->End()->Time() == aPrevInterval
->Begin()->Time();
1503 beginAfter
.SetMillis(INT64_MIN
);
1506 RefPtr
<SMILInstanceTime
> tempBegin
;
1507 RefPtr
<SMILInstanceTime
> tempEnd
;
1510 // Calculate begin time
1511 if (aFixedBeginTime
) {
1512 if (aFixedBeginTime
->Time() < beginAfter
) {
1515 // our ref-counting is not const-correct
1516 tempBegin
= const_cast<SMILInstanceTime
*>(aFixedBeginTime
);
1517 } else if ((!mAnimationElement
||
1518 !mAnimationElement
->HasAttr(nsGkAtoms::begin
)) &&
1519 beginAfter
<= zeroTime
) {
1520 tempBegin
= new SMILInstanceTime(SMILTimeValue(0));
1522 int32_t beginPos
= 0;
1525 GetNextGreaterOrEqual(mBeginInstances
, beginAfter
, beginPos
);
1526 if (!tempBegin
|| !tempBegin
->Time().IsDefinite()) {
1529 // If we're updating the current interval then skip any begin time that
1530 // is dependent on the current interval's begin time. e.g.
1531 // <animate id="a" begin="b.begin; a.begin+2s"...
1532 // If b's interval disappears whilst 'a' is in the waiting state the
1533 // begin time at "a.begin+2s" should be skipped since 'a' never begun.
1534 } while (aReplacedInterval
&&
1535 tempBegin
->GetBaseTime() == aReplacedInterval
->Begin());
1537 MOZ_ASSERT(tempBegin
&& tempBegin
->Time().IsDefinite() &&
1538 tempBegin
->Time() >= beginAfter
,
1539 "Got a bad begin time while fetching next interval");
1541 // Calculate end time
1546 GetNextGreaterOrEqual(mEndInstances
, tempBegin
->Time(), endPos
);
1548 // SMIL doesn't allow for coincident zero-duration intervals, so if the
1549 // previous interval was zero-duration, and tempEnd is going to give us
1550 // another zero duration interval, then look for another end to use
1552 if (tempEnd
&& prevIntervalWasZeroDur
&&
1553 tempEnd
->Time() == beginAfter
) {
1554 tempEnd
= GetNextGreater(mEndInstances
, tempBegin
->Time(), endPos
);
1556 // As above with begin times, avoid creating self-referential loops
1557 // between instance times by checking that the newly found end instance
1558 // time is not already dependent on the end of the current interval.
1559 } while (tempEnd
&& aReplacedInterval
&&
1560 tempEnd
->GetBaseTime() == aReplacedInterval
->End());
1563 // If all the ends are before the beginning we have a bad interval
1565 // a) We never had any end attribute to begin with (the SMIL pseudocode
1566 // places this condition earlier in the flow but that fails to allow
1567 // for DOM calls when no "indefinite" condition is given), OR
1568 // b) We never had any end instance times to begin with, OR
1569 // c) We have end events which leave the interval open-ended.
1570 bool openEndedIntervalOk
= mEndSpecs
.IsEmpty() ||
1571 mEndInstances
.IsEmpty() ||
1572 EndHasEventConditions();
1574 // The above conditions correspond with the SMIL pseudocode but SMIL
1575 // doesn't address self-dependent instance times which we choose to
1578 // Therefore we add a qualification of (b) above that even if
1579 // there are end instance times but they all depend on the end of the
1580 // current interval we should act as if they didn't exist and allow the
1581 // open-ended interval.
1583 // In the following condition we don't use |= because it doesn't provide
1584 // short-circuit behavior.
1585 openEndedIntervalOk
=
1586 openEndedIntervalOk
||
1587 (aReplacedInterval
&&
1588 AreEndTimesDependentOn(aReplacedInterval
->End()));
1590 if (!openEndedIntervalOk
) {
1591 return false; // Bad interval
1595 SMILTimeValue intervalEnd
= tempEnd
? tempEnd
->Time() : SMILTimeValue();
1596 SMILTimeValue activeEnd
= CalcActiveEnd(tempBegin
->Time(), intervalEnd
);
1598 if (!tempEnd
|| intervalEnd
!= activeEnd
) {
1599 tempEnd
= new SMILInstanceTime(activeEnd
);
1602 MOZ_ASSERT(tempEnd
, "Failed to get end point for next interval");
1604 // When we choose the interval endpoints, we don't allow coincident
1605 // zero-duration intervals, so if we arrive here and we have a zero-duration
1606 // interval starting at the same point as a previous zero-duration interval,
1607 // then it must be because we've applied constraints to the active duration.
1608 // In that case, we will potentially run into an infinite loop, so we break
1609 // it by searching for the next interval that starts AFTER our current
1610 // zero-duration interval.
1611 if (prevIntervalWasZeroDur
&& tempEnd
->Time() == beginAfter
) {
1612 beginAfter
.SetMillis(tempBegin
->Time().GetMillis() + 1);
1613 prevIntervalWasZeroDur
= false;
1616 prevIntervalWasZeroDur
= tempBegin
->Time() == tempEnd
->Time();
1618 // Check for valid interval
1619 if (tempEnd
->Time() > zeroTime
||
1620 (tempBegin
->Time() == zeroTime
&& tempEnd
->Time() == zeroTime
)) {
1621 aResult
.Set(*tempBegin
, *tempEnd
);
1625 if (mRestartMode
== RESTART_NEVER
) {
1626 // tempEnd <= 0 so we're going to loop which effectively means restarting
1630 beginAfter
= tempEnd
->Time();
1632 MOZ_ASSERT_UNREACHABLE("Hmm... we really shouldn't be here");
1637 SMILInstanceTime
* SMILTimedElement::GetNextGreater(
1638 const InstanceTimeList
& aList
, const SMILTimeValue
& aBase
,
1639 int32_t& aPosition
) const {
1640 SMILInstanceTime
* result
= nullptr;
1641 while ((result
= GetNextGreaterOrEqual(aList
, aBase
, aPosition
)) &&
1642 result
->Time() == aBase
) {
1647 SMILInstanceTime
* SMILTimedElement::GetNextGreaterOrEqual(
1648 const InstanceTimeList
& aList
, const SMILTimeValue
& aBase
,
1649 int32_t& aPosition
) const {
1650 SMILInstanceTime
* result
= nullptr;
1651 int32_t count
= aList
.Length();
1653 for (; aPosition
< count
&& !result
; ++aPosition
) {
1654 SMILInstanceTime
* val
= aList
[aPosition
].get();
1655 MOZ_ASSERT(val
, "NULL instance time in list");
1656 if (val
->Time() >= aBase
) {
1665 * @see SMILANIM 3.3.4
1667 SMILTimeValue
SMILTimedElement::CalcActiveEnd(const SMILTimeValue
& aBegin
,
1668 const SMILTimeValue
& aEnd
) const {
1669 SMILTimeValue result
;
1671 MOZ_ASSERT(mSimpleDur
.IsResolved(),
1672 "Unresolved simple duration in CalcActiveEnd");
1673 MOZ_ASSERT(aBegin
.IsDefinite(),
1674 "Indefinite or unresolved begin time in CalcActiveEnd");
1676 result
= GetRepeatDuration();
1678 if (aEnd
.IsDefinite()) {
1679 SMILTime activeDur
= aEnd
.GetMillis() - aBegin
.GetMillis();
1681 if (result
.IsDefinite()) {
1682 result
.SetMillis(std::min(result
.GetMillis(), activeDur
));
1684 result
.SetMillis(activeDur
);
1688 result
= ApplyMinAndMax(result
);
1690 if (result
.IsDefinite()) {
1691 SMILTime activeEnd
= result
.GetMillis() + aBegin
.GetMillis();
1692 result
.SetMillis(activeEnd
);
1698 SMILTimeValue
SMILTimedElement::GetRepeatDuration() const {
1699 SMILTimeValue multipliedDuration
;
1700 if (mRepeatCount
.IsDefinite() && mSimpleDur
.IsDefinite()) {
1701 if (mRepeatCount
* double(mSimpleDur
.GetMillis()) <
1702 double(std::numeric_limits
<SMILTime
>::max())) {
1703 multipliedDuration
.SetMillis(
1704 SMILTime(mRepeatCount
* mSimpleDur
.GetMillis()));
1707 multipliedDuration
.SetIndefinite();
1710 SMILTimeValue repeatDuration
;
1712 if (mRepeatDur
.IsResolved()) {
1713 repeatDuration
= std::min(multipliedDuration
, mRepeatDur
);
1714 } else if (mRepeatCount
.IsSet()) {
1715 repeatDuration
= multipliedDuration
;
1717 repeatDuration
= mSimpleDur
;
1720 return repeatDuration
;
1723 SMILTimeValue
SMILTimedElement::ApplyMinAndMax(
1724 const SMILTimeValue
& aDuration
) const {
1725 if (!aDuration
.IsResolved()) {
1733 SMILTimeValue result
;
1735 if (aDuration
> mMax
) {
1737 } else if (aDuration
< mMin
) {
1746 SMILTime
SMILTimedElement::ActiveTimeToSimpleTime(SMILTime aActiveTime
,
1747 uint32_t& aRepeatIteration
) {
1750 MOZ_ASSERT(mSimpleDur
.IsResolved(),
1751 "Unresolved simple duration in ActiveTimeToSimpleTime");
1752 MOZ_ASSERT(aActiveTime
>= 0, "Expecting non-negative active time");
1753 // Note that a negative aActiveTime will give us a negative value for
1754 // aRepeatIteration, which is bad because aRepeatIteration is unsigned
1756 if (mSimpleDur
.IsIndefinite() || mSimpleDur
.IsZero()) {
1757 aRepeatIteration
= 0;
1758 result
= aActiveTime
;
1760 result
= aActiveTime
% mSimpleDur
.GetMillis();
1761 aRepeatIteration
= (uint32_t)(aActiveTime
/ mSimpleDur
.GetMillis());
1768 // Although in many cases it would be possible to check for an early end and
1769 // adjust the current interval well in advance the SMIL Animation spec seems to
1770 // indicate that we should only apply an early end at the latest possible
1771 // moment. In particular, this paragraph from section 3.6.8:
1773 // 'If restart is set to "always", then the current interval will end early if
1774 // there is an instance time in the begin list that is before (i.e. earlier
1775 // than) the defined end for the current interval. Ending in this manner will
1776 // also send a changed time notice to all time dependents for the current
1779 SMILInstanceTime
* SMILTimedElement::CheckForEarlyEnd(
1780 const SMILTimeValue
& aContainerTime
) const {
1781 MOZ_ASSERT(mCurrentInterval
,
1782 "Checking for an early end but the current interval is not set");
1783 if (mRestartMode
!= RESTART_ALWAYS
) return nullptr;
1785 int32_t position
= 0;
1786 SMILInstanceTime
* nextBegin
= GetNextGreater(
1787 mBeginInstances
, mCurrentInterval
->Begin()->Time(), position
);
1789 if (nextBegin
&& nextBegin
->Time() > mCurrentInterval
->Begin()->Time() &&
1790 nextBegin
->Time() < mCurrentInterval
->End()->Time() &&
1791 nextBegin
->Time() <= aContainerTime
) {
1798 void SMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice
) {
1799 // Check if updates are currently blocked (batched)
1800 if (mDeferIntervalUpdates
) {
1801 mDoDeferredUpdate
= true;
1805 // We adopt the convention of not resolving intervals until the first
1806 // sample. Otherwise, every time each attribute is set we'll re-resolve the
1807 // current interval and notify all our time dependents of the change.
1809 // The disadvantage of deferring resolving the interval is that DOM calls to
1810 // to getStartTime will throw an INVALID_STATE_ERR exception until the
1811 // document timeline begins since the start time has not yet been resolved.
1812 if (mElementState
== STATE_STARTUP
) return;
1814 // Although SMIL gives rules for detecting cycles in change notifications,
1815 // some configurations can lead to create-delete-create-delete-etc. cycles
1816 // which SMIL does not consider.
1818 // In order to provide consistent behavior in such cases, we detect two
1819 // deletes in a row and then refuse to create any further intervals. That is,
1820 // we say the configuration is invalid.
1821 if (mDeleteCount
> 1) {
1822 // When we update the delete count we also set the state to post active, so
1823 // if we're not post active here then something other than
1824 // UpdateCurrentInterval has updated the element state in between and all
1826 MOZ_ASSERT(mElementState
== STATE_POSTACTIVE
,
1827 "Expected to be in post-active state after performing double "
1832 // Check that we aren't stuck in infinite recursion updating some syncbase
1833 // dependencies. Generally such situations should be detected in advance and
1834 // the chain broken in a sensible and predictable manner, so if we're hitting
1835 // this assertion we need to work out how to detect the case that's causing
1836 // it. In release builds, just bail out before we overflow the stack.
1837 AutoRestore
<uint8_t> depthRestorer(mUpdateIntervalRecursionDepth
);
1838 if (++mUpdateIntervalRecursionDepth
> sMaxUpdateIntervalRecursionDepth
) {
1840 "Update current interval recursion depth exceeded threshold");
1844 // If the interval is active the begin time is fixed.
1845 const SMILInstanceTime
* beginTime
=
1846 mElementState
== STATE_ACTIVE
? mCurrentInterval
->Begin() : nullptr;
1847 SMILInterval updatedInterval
;
1848 if (GetNextInterval(GetPreviousInterval(), mCurrentInterval
.get(), beginTime
,
1850 if (mElementState
== STATE_POSTACTIVE
) {
1851 MOZ_ASSERT(!mCurrentInterval
,
1852 "In postactive state but the interval has been set");
1853 mCurrentInterval
= MakeUnique
<SMILInterval
>(updatedInterval
);
1854 mElementState
= STATE_WAITING
;
1855 NotifyNewInterval();
1858 bool beginChanged
= false;
1859 bool endChanged
= false;
1861 if (mElementState
!= STATE_ACTIVE
&&
1862 !updatedInterval
.Begin()->SameTimeAndBase(
1863 *mCurrentInterval
->Begin())) {
1864 mCurrentInterval
->SetBegin(*updatedInterval
.Begin());
1865 beginChanged
= true;
1868 if (!updatedInterval
.End()->SameTimeAndBase(*mCurrentInterval
->End())) {
1869 mCurrentInterval
->SetEnd(*updatedInterval
.End());
1873 if (beginChanged
|| endChanged
|| aForceChangeNotice
) {
1874 NotifyChangedInterval(mCurrentInterval
.get(), beginChanged
, endChanged
);
1878 // There's a chance our next milestone has now changed, so update the time
1880 RegisterMilestone();
1881 } else { // GetNextInterval failed: Current interval is no longer valid
1882 if (mElementState
== STATE_ACTIVE
) {
1883 // The interval is active so we can't just delete it, instead trim it so
1885 if (!mCurrentInterval
->End()->SameTimeAndBase(
1886 *mCurrentInterval
->Begin())) {
1887 mCurrentInterval
->SetEnd(*mCurrentInterval
->Begin());
1888 NotifyChangedInterval(mCurrentInterval
.get(), false, true);
1890 // The transition to the postactive state will take place on the next
1891 // sample (along with firing end events, clearing intervals etc.)
1892 RegisterMilestone();
1893 } else if (mElementState
== STATE_WAITING
) {
1894 AutoRestore
<uint8_t> deleteCountRestorer(mDeleteCount
);
1896 mElementState
= STATE_POSTACTIVE
;
1897 ResetCurrentInterval();
1902 void SMILTimedElement::SampleSimpleTime(SMILTime aActiveTime
) {
1904 uint32_t repeatIteration
;
1905 SMILTime simpleTime
= ActiveTimeToSimpleTime(aActiveTime
, repeatIteration
);
1906 mClient
->SampleAt(simpleTime
, mSimpleDur
, repeatIteration
);
1910 void SMILTimedElement::SampleFillValue() {
1911 if (mFillMode
!= FILL_FREEZE
|| !mClient
) return;
1913 SMILTime activeTime
;
1915 if (mElementState
== STATE_WAITING
|| mElementState
== STATE_POSTACTIVE
) {
1916 const SMILInterval
* prevInterval
= GetPreviousInterval();
1917 MOZ_ASSERT(prevInterval
,
1918 "Attempting to sample fill value but there is no previous "
1920 MOZ_ASSERT(prevInterval
->End()->Time().IsDefinite() &&
1921 prevInterval
->End()->IsFixedTime(),
1922 "Attempting to sample fill value but the endpoint of the "
1923 "previous interval is not resolved and fixed");
1925 activeTime
= prevInterval
->End()->Time().GetMillis() -
1926 prevInterval
->Begin()->Time().GetMillis();
1928 // If the interval's repeat duration was shorter than its active duration,
1929 // use the end of the repeat duration to determine the frozen animation's
1931 SMILTimeValue repeatDuration
= GetRepeatDuration();
1932 if (repeatDuration
.IsDefinite()) {
1933 activeTime
= std::min(repeatDuration
.GetMillis(), activeTime
);
1937 mElementState
== STATE_ACTIVE
,
1938 "Attempting to sample fill value when we're in an unexpected state "
1939 "(probably STATE_STARTUP)");
1941 // If we are being asked to sample the fill value while active we *must*
1942 // have a repeat duration shorter than the active duration so use that.
1943 MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
1944 "Attempting to sample fill value of an active animation with "
1945 "an indefinite repeat duration");
1946 activeTime
= GetRepeatDuration().GetMillis();
1949 uint32_t repeatIteration
;
1950 SMILTime simpleTime
= ActiveTimeToSimpleTime(activeTime
, repeatIteration
);
1952 if (simpleTime
== 0L && repeatIteration
) {
1953 mClient
->SampleLastValue(--repeatIteration
);
1955 mClient
->SampleAt(simpleTime
, mSimpleDur
, repeatIteration
);
1959 nsresult
SMILTimedElement::AddInstanceTimeFromCurrentTime(SMILTime aCurrentTime
,
1960 double aOffsetSeconds
,
1962 double offset
= NS_round(aOffsetSeconds
* PR_MSEC_PER_SEC
);
1964 // Check we won't overflow the range of SMILTime
1965 if (aCurrentTime
+ offset
> double(std::numeric_limits
<SMILTime
>::max()))
1966 return NS_ERROR_ILLEGAL_VALUE
;
1968 SMILTimeValue
timeVal(aCurrentTime
+ int64_t(offset
));
1970 RefPtr
<SMILInstanceTime
> instanceTime
=
1971 new SMILInstanceTime(timeVal
, SMILInstanceTime::SOURCE_DOM
);
1973 AddInstanceTime(instanceTime
, aIsBegin
);
1978 void SMILTimedElement::RegisterMilestone() {
1979 SMILTimeContainer
* container
= GetTimeContainer();
1980 if (!container
) return;
1981 MOZ_ASSERT(mAnimationElement
,
1982 "Got a time container without an owning animation element");
1984 SMILMilestone nextMilestone
;
1985 if (!GetNextMilestone(nextMilestone
)) return;
1987 // This method is called every time we might possibly have updated our
1988 // current interval, but since SMILTimeContainer makes no attempt to filter
1989 // out redundant milestones we do some rudimentary filtering here. It's not
1990 // perfect, but unnecessary samples are fairly cheap.
1991 if (nextMilestone
>= mPrevRegisteredMilestone
) return;
1993 container
->AddMilestone(nextMilestone
, *mAnimationElement
);
1994 mPrevRegisteredMilestone
= nextMilestone
;
1997 bool SMILTimedElement::GetNextMilestone(SMILMilestone
& aNextMilestone
) const {
1998 // Return the next key moment in our lifetime.
2000 // XXX It may be possible in future to optimise this so that we only register
2001 // for milestones if:
2002 // a) We have time dependents, or
2003 // b) We are dependent on events or syncbase relationships, or
2004 // c) There are registered listeners for our events
2006 // Then for the simple case where everything uses offset values we could
2007 // ignore milestones altogether.
2009 // We'd need to be careful, however, that if one of those conditions became
2010 // true in between samples that we registered our next milestone at that
2013 switch (mElementState
) {
2015 // All elements register for an initial end sample at t=0 where we resolve
2016 // our initial interval.
2017 aNextMilestone
.mIsEnd
= true; // Initial sample should be an end sample
2018 aNextMilestone
.mTime
= 0;
2022 MOZ_ASSERT(mCurrentInterval
,
2023 "In waiting state but the current interval has not been set");
2024 aNextMilestone
.mIsEnd
= false;
2025 aNextMilestone
.mTime
= mCurrentInterval
->Begin()->Time().GetMillis();
2028 case STATE_ACTIVE
: {
2029 // Work out what comes next: the interval end or the next repeat iteration
2030 SMILTimeValue nextRepeat
;
2031 if (mSeekState
== SEEK_NOT_SEEKING
&& mSimpleDur
.IsDefinite()) {
2032 SMILTime nextRepeatActiveTime
=
2033 (mCurrentRepeatIteration
+ 1) * mSimpleDur
.GetMillis();
2034 // Check that the repeat fits within the repeat duration
2035 if (SMILTimeValue(nextRepeatActiveTime
) < GetRepeatDuration()) {
2036 nextRepeat
.SetMillis(mCurrentInterval
->Begin()->Time().GetMillis() +
2037 nextRepeatActiveTime
);
2040 SMILTimeValue nextMilestone
=
2041 std::min(mCurrentInterval
->End()->Time(), nextRepeat
);
2043 // Check for an early end before that time
2044 SMILInstanceTime
* earlyEnd
= CheckForEarlyEnd(nextMilestone
);
2046 aNextMilestone
.mIsEnd
= true;
2047 aNextMilestone
.mTime
= earlyEnd
->Time().GetMillis();
2051 // Apply the previously calculated milestone
2052 if (nextMilestone
.IsDefinite()) {
2053 aNextMilestone
.mIsEnd
= nextMilestone
!= nextRepeat
;
2054 aNextMilestone
.mTime
= nextMilestone
.GetMillis();
2061 case STATE_POSTACTIVE
:
2064 MOZ_CRASH("Invalid element state");
2067 void SMILTimedElement::NotifyNewInterval() {
2068 MOZ_ASSERT(mCurrentInterval
,
2069 "Attempting to notify dependents of a new interval but the "
2070 "interval is not set");
2072 SMILTimeContainer
* container
= GetTimeContainer();
2074 container
->SyncPauseTime();
2077 for (SMILTimeValueSpec
* spec
: mTimeDependents
.Keys()) {
2078 SMILInterval
* interval
= mCurrentInterval
.get();
2079 // It's possible that in notifying one new time dependent of a new interval
2080 // that a chain reaction is triggered which results in the original
2081 // interval disappearing. If that's the case we can skip sending further
2086 spec
->HandleNewInterval(*interval
, container
);
2090 void SMILTimedElement::NotifyChangedInterval(SMILInterval
* aInterval
,
2091 bool aBeginObjectChanged
,
2092 bool aEndObjectChanged
) {
2093 MOZ_ASSERT(aInterval
, "Null interval for change notification");
2095 SMILTimeContainer
* container
= GetTimeContainer();
2097 container
->SyncPauseTime();
2100 // Copy the instance times list since notifying the instance times can result
2101 // in a chain reaction whereby our own interval gets deleted along with its
2103 InstanceTimeList times
;
2104 aInterval
->GetDependentTimes(times
);
2106 for (RefPtr
<SMILInstanceTime
>& time
: times
) {
2107 time
->HandleChangedInterval(container
, aBeginObjectChanged
,
2112 void SMILTimedElement::FireTimeEventAsync(EventMessage aMsg
, int32_t aDetail
) {
2113 if (!mAnimationElement
) return;
2115 nsCOMPtr
<nsIRunnable
> event
=
2116 new AsyncTimeEventRunner(mAnimationElement
, aMsg
, aDetail
);
2117 mAnimationElement
->OwnerDoc()->Dispatch(event
.forget());
2120 const SMILInstanceTime
* SMILTimedElement::GetEffectiveBeginInstance() const {
2121 switch (mElementState
) {
2126 return mCurrentInterval
->Begin();
2129 case STATE_POSTACTIVE
: {
2130 const SMILInterval
* prevInterval
= GetPreviousInterval();
2131 return prevInterval
? prevInterval
->Begin() : nullptr;
2134 MOZ_CRASH("Invalid element state");
2137 const SMILInterval
* SMILTimedElement::GetPreviousInterval() const {
2138 return mOldIntervals
.IsEmpty() ? nullptr : mOldIntervals
.LastElement().get();
2141 bool SMILTimedElement::HasClientInFillRange() const {
2142 // Returns true if we have a client that is in the range where it will fill
2143 return mClient
&& ((mElementState
!= STATE_ACTIVE
&& HasPlayed()) ||
2144 (mElementState
== STATE_ACTIVE
&& !mClient
->IsActive()));
2147 bool SMILTimedElement::EndHasEventConditions() const {
2148 for (const UniquePtr
<SMILTimeValueSpec
>& endSpec
: mEndSpecs
) {
2149 if (endSpec
->IsEventBased()) return true;
2154 bool SMILTimedElement::AreEndTimesDependentOn(
2155 const SMILInstanceTime
* aBase
) const {
2156 if (mEndInstances
.IsEmpty()) return false;
2158 for (const RefPtr
<SMILInstanceTime
>& endInstance
: mEndInstances
) {
2159 if (endInstance
->GetBaseTime() != aBase
) {
2166 } // namespace mozilla