Bug 1889091 - Part 6: Remove "scratch" register parameter from emitPushArguments...
[gecko.git] / dom / smil / SMILTimedElement.cpp
blob4be4a8840e637c9453e7e2719cabff57c0687baf
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"
26 #include "prdtoa.h"
27 #include "prtime.h"
28 #include "nsString.h"
29 #include "nsCharSeparatedTokenizer.h"
30 #include <algorithm>
32 using namespace mozilla::dom;
34 namespace mozilla {
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
71 namespace {
72 class AsyncTimeEventRunner : public Runnable {
73 protected:
74 const RefPtr<nsIContent> mTarget;
75 EventMessage mMsg;
76 int32_t mDetail;
78 public:
79 AsyncTimeEventRunner(nsIContent* aTarget, EventMessage aMsg, int32_t aDetail)
80 : mozilla::Runnable("AsyncTimeEventRunner"),
81 mTarget(aTarget),
82 mMsg(aMsg),
83 mDetail(aDetail) {}
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();
92 if (doc) {
93 context = doc->GetPresContext();
96 return EventDispatcher::Dispatch(mTarget, context, &event);
99 } // namespace
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
106 // destroyed.
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
110 // destroyed.
111 class MOZ_STACK_CLASS SMILTimedElement::AutoIntervalUpdateBatcher {
112 public:
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();
130 private:
131 SMILTimedElement& mTimedElement;
132 bool mDidSetFlag;
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 {
146 public:
147 explicit AutoIntervalUpdater(SMILTimedElement& aTimedElement)
148 : mTimedElement(aTimedElement) {}
150 ~AutoIntervalUpdater() { mTimedElement.UpdateCurrentInterval(); }
152 private:
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");
179 item->Unlink();
180 } else {
181 newArray.AppendElement(item);
184 aArray = std::move(newArray);
187 //----------------------------------------------------------------------
188 // Static members
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},
197 {nullptr, 0}};
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
203 // indiscriminately.
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
209 // relationship
210 const uint8_t SMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20;
212 //----------------------------------------------------------------------
213 // Ctor, dtor
215 SMILTimedElement::SMILTimedElement()
216 : mAnimationElement(nullptr),
217 mFillMode(FILL_REMOVE),
218 mRestartMode(RESTART_ALWAYS),
219 mInstanceSerialIndex(0),
220 mClient(nullptr),
221 mCurrentInterval(nullptr),
222 mCurrentRepeatIteration(0),
223 mPrevRegisteredMilestone(sMaxMilestone),
224 mElementState(STATE_STARTUP),
225 mSeekState(SEEK_NOT_SEEKING),
226 mDeferIntervalUpdates(false),
227 mDoDeferredUpdate(false),
228 mIsDisabled(false),
229 mDeleteCount(0),
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) {
239 instance->Unlink();
241 mBeginInstances.Clear();
242 for (RefPtr<SMILInstanceTime>& instance : mEndInstances) {
243 instance->Unlink();
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)
250 ClearIntervals();
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
255 // pointers.
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()
276 : nullptr;
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()
317 : SMILTimeValue();
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 //----------------------------------------------------------------------
336 // SMILTimedElement
338 void SMILTimedElement::AddInstanceTime(SMILInstanceTime* aInstanceTime,
339 bool aIsBegin) {
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");
351 return;
354 aInstanceTime->SetSerial(++mInstanceSerialIndex);
355 InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
356 RefPtr<SMILInstanceTime>* inserted =
357 instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator());
358 if (!inserted) {
359 NS_WARNING("Insufficient memory to insert instance time");
360 return;
363 UpdateCurrentInterval();
366 void SMILTimedElement::UpdateInstanceTime(SMILInstanceTime* aInstanceTime,
367 SMILTimeValue& aUpdatedTime,
368 bool aIsBegin) {
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
384 // a change.
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,
398 bool aIsBegin) {
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();
405 return;
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();
416 namespace {
417 class MOZ_STACK_CLASS RemoveByCreator {
418 public:
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();
429 return false;
432 return true;
435 private:
436 const SMILTimeValueSpec* mCreator;
438 } // namespace
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.
457 mClient = aClient;
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
483 // acceptable.
484 if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) {
485 DoSampleAt(aContainerTime, true); // End sample
486 } else {
487 // Even if this was an unnecessary milestone sample we want to be sure that
488 // our next real milestone is registered.
489 RegisterMilestone();
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))
505 return;
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()) {
526 finishedSeek = true;
529 bool stateChanged;
530 SMILTimeValue sampleTime(aContainerTime);
532 do {
533 #ifdef DEBUG
534 // Check invariant
535 if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) {
536 MOZ_ASSERT(!mCurrentInterval,
537 "Shouldn't have current interval in startup or postactive "
538 "states");
539 } else {
540 MOZ_ASSERT(mCurrentInterval,
541 "Should have current interval in waiting and active states");
543 #endif
545 stateChanged = false;
547 switch (mElementState) {
548 case STATE_STARTUP: {
549 SMILInterval firstInterval;
550 mElementState =
551 GetNextInterval(nullptr, nullptr, nullptr, firstInterval)
552 ? STATE_WAITING
553 : STATE_POSTACTIVE;
554 stateChanged = true;
555 if (mElementState == STATE_WAITING) {
556 mCurrentInterval = MakeUnique<SMILInterval>(firstInterval);
557 NotifyNewInterval();
559 } break;
561 case STATE_WAITING: {
562 if (mCurrentInterval->Begin()->Time() <= sampleTime) {
563 mElementState = STATE_ACTIVE;
564 mCurrentInterval->FixBegin();
565 if (mClient) {
566 mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis());
568 if (mSeekState == SEEK_NOT_SEEKING) {
569 FireTimeEventAsync(eSMILBeginEvent, 0);
571 if (HasPlayed()) {
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
579 // after this.
580 UpdateCurrentInterval();
582 stateChanged = true;
584 } break;
586 case STATE_ACTIVE: {
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)
596 ? STATE_WAITING
597 : STATE_POSTACTIVE;
598 if (mClient) {
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));
607 SampleFillValue();
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,
614 true);
616 if (mElementState == STATE_WAITING) {
617 NotifyNewInterval();
619 FilterHistory();
620 stateChanged = true;
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);
633 SampleFillValue();
634 } else {
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
641 // seek state.)
642 uint32_t prevRepeatIteration = mCurrentRepeatIteration;
643 if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration) ==
644 0 &&
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.
657 } break;
659 case STATE_POSTACTIVE:
660 break;
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)));
672 if (finishedSeek) {
673 DoPostSeek();
675 RegisterMilestone();
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);
689 namespace {
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();
698 } // namespace
700 void SMILTimedElement::Rewind() {
701 MOZ_ASSERT(mAnimationElement,
702 "Got rewind request before being attached to an animation "
703 "element");
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
710 // between.
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
714 // same (re)sample.
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");
729 namespace {
730 bool RemoveAll(SMILInstanceTime* aInstanceTime) { return true; }
731 } // namespace
733 bool SMILTimedElement::SetIsDisabled(bool aIsDisabled) {
734 if (mIsDisabled == aIsDisabled) return false;
736 if (aIsDisabled) {
737 mIsDisabled = true;
738 ClearTimingState(RemoveAll);
739 } else {
740 RebuildTimingState(RemoveAll);
741 mIsDisabled = false;
743 return true;
746 namespace {
747 bool RemoveNonDOM(SMILInstanceTime* aInstanceTime) {
748 return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve();
750 } // namespace
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);
776 } else {
777 foundMatch = false;
780 if (foundMatch) {
781 aResult.SetTo(aValue);
782 if (aParseResult) {
783 *aParseResult = parseResult;
787 return foundMatch;
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) {
800 UnsetFillMode();
801 } else if (aAttribute == nsGkAtoms::max) {
802 UnsetMax();
803 } else if (aAttribute == nsGkAtoms::min) {
804 UnsetMin();
805 } else if (aAttribute == nsGkAtoms::repeatCount) {
806 UnsetRepeatCount();
807 } else if (aAttribute == nsGkAtoms::repeatDur) {
808 UnsetRepeatDur();
809 } else if (aAttribute == nsGkAtoms::restart) {
810 UnsetRestart();
811 } else {
812 foundMatch = false;
815 return foundMatch;
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*/,
825 aRemove);
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*/,
837 aRemove);
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();
856 } else {
857 if (!SMILParserUtils::ParseClockValue(
858 dur, SMILTimeValue::Rounding::EnsureNonZero, &duration) ||
859 duration.IsZero()) {
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;
870 return NS_OK;
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();
887 } else {
888 if (!SMILParserUtils::ParseClockValue(min, SMILTimeValue::Rounding::Nearest,
889 &duration)) {
890 mMin = SMILTimeValue::Zero();
891 return NS_ERROR_FAILURE;
895 MOZ_ASSERT(duration.GetMillis() >= 0L, "Invalid duration");
897 mMin = duration;
899 return NS_OK;
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();
916 } else {
917 if (!SMILParserUtils::ParseClockValue(
918 max, SMILTimeValue::Rounding::EnsureNonZero, &duration) ||
919 duration.IsZero()) {
920 mMax.SetIndefinite();
921 return NS_ERROR_FAILURE;
923 MOZ_ASSERT(duration.GetMillis() > 0L, "Invalid duration");
926 mMax = duration;
928 return NS_OK;
931 void SMILTimedElement::UnsetMax() {
932 mMax.SetIndefinite();
933 UpdateCurrentInterval();
936 nsresult SMILTimedElement::SetRestart(const nsAString& aRestartSpec) {
937 nsAttrValue temp;
938 bool parseResult = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true);
939 mRestartMode =
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;
958 return NS_OK;
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();
979 } else {
980 if (!SMILParserUtils::ParseClockValue(
981 repeatDur, SMILTimeValue::Rounding::EnsureNonZero, &duration)) {
982 mRepeatDur.SetUnresolved();
983 return NS_ERROR_FAILURE;
987 mRepeatDur = duration;
989 return NS_OK;
992 void SMILTimedElement::UnsetRepeatDur() {
993 mRepeatDur.SetUnresolved();
994 UpdateCurrentInterval();
997 nsresult SMILTimedElement::SetFillMode(const nsAString& aFillModeSpec) {
998 uint16_t previousFillMode = mFillMode;
1000 nsAttrValue temp;
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);
1007 SampleFillValue();
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
1059 // afresh
1060 if (mElementState != STATE_STARTUP) {
1061 mSeekState = SEEK_NOT_SEEKING;
1062 Rewind();
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");
1117 endSpec->Unlink();
1120 ClearIntervals();
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,
1131 bool aIsBegin,
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));
1151 } else {
1152 hadFailure = true;
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;
1163 namespace {
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 {
1169 public:
1170 explicit RemoveByFunction(SMILTimedElement::RemovalTestFunction aFunction)
1171 : mFunction(aFunction) {}
1172 bool operator()(SMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) {
1173 return mFunction(aInstanceTime);
1176 private:
1177 SMILTimedElement::RemovalTestFunction mFunction;
1179 } // namespace
1181 void SMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs,
1182 InstanceTimeList& aInstances,
1183 RemovalTestFunction aRemove) {
1184 AutoIntervalUpdateBatcher updateBatcher(*this);
1186 for (UniquePtr<SMILTimeValueSpec>& spec : aSpecs) {
1187 spec->Unlink();
1189 aSpecs.Clear();
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);
1219 if (earlyEnd) {
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);
1227 } else {
1228 mCurrentInterval->SetEnd(*earlyEnd);
1230 updated = true;
1233 return updated;
1236 namespace {
1237 class MOZ_STACK_CLASS RemoveReset {
1238 public:
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);
1252 private:
1253 const SMILInstanceTime* mCurrentIntervalBegin;
1255 } // namespace
1257 void SMILTimedElement::Reset() {
1258 RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin()
1259 : nullptr);
1260 RemoveInstanceTimes(mBeginInstances, resetBegin);
1262 RemoveReset resetEnd(nullptr);
1263 RemoveInstanceTimes(mEndInstances, resetEnd);
1266 void SMILTimedElement::ClearTimingState(RemovalTestFunction aRemove) {
1267 mElementState = STATE_STARTUP;
1268 ClearIntervals();
1270 UnsetBeginSpec(aRemove);
1271 UnsetEndSpec(aRemove);
1273 if (mClient) {
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
1309 // historic.
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.
1319 Reset();
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);
1329 break;
1331 case SEEK_FORWARD_FROM_INACTIVE:
1332 case SEEK_BACKWARD_FROM_INACTIVE:
1333 if (mElementState == STATE_ACTIVE) {
1334 FireTimeEventAsync(eSMILBeginEvent, 0);
1336 break;
1338 case SEEK_NOT_SEEKING:
1339 /* Do nothing */
1340 break;
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()
1350 : nullptr;
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.
1361 FilterIntervals();
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
1397 : 0;
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*/);
1405 } else {
1406 filteredList.AppendElement(std::move(mOldIntervals[i]));
1409 mOldIntervals = std::move(filteredList);
1412 namespace {
1413 class MOZ_STACK_CLASS RemoveFiltered {
1414 public:
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();
1426 private:
1427 SMILTimeValue mCutoff;
1430 class MOZ_STACK_CLASS RemoveBelowThreshold {
1431 public:
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);
1439 private:
1440 uint32_t mThreshold;
1441 nsTArray<const SMILInstanceTime*>& mTimesToKeep;
1443 } // namespace
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();
1468 if (prevInterval) {
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.
1482 // See:
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();
1502 } else {
1503 beginAfter.SetMillis(INT64_MIN);
1506 RefPtr<SMILInstanceTime> tempBegin;
1507 RefPtr<SMILInstanceTime> tempEnd;
1509 while (true) {
1510 // Calculate begin time
1511 if (aFixedBeginTime) {
1512 if (aFixedBeginTime->Time() < beginAfter) {
1513 return false;
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));
1521 } else {
1522 int32_t beginPos = 0;
1523 do {
1524 tempBegin =
1525 GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos);
1526 if (!tempBegin || !tempBegin->Time().IsDefinite()) {
1527 return false;
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
1543 int32_t endPos = 0;
1544 do {
1545 tempEnd =
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
1551 // instead.
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());
1562 if (!tempEnd) {
1563 // If all the ends are before the beginning we have a bad interval
1564 // UNLESS:
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
1576 // ignore.
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;
1614 continue;
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);
1622 return true;
1625 if (mRestartMode == RESTART_NEVER) {
1626 // tempEnd <= 0 so we're going to loop which effectively means restarting
1627 return false;
1630 beginAfter = tempEnd->Time();
1632 MOZ_ASSERT_UNREACHABLE("Hmm... we really shouldn't be here");
1634 return false;
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) {
1644 return result;
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) {
1657 result = val;
1661 return result;
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));
1683 } else {
1684 result.SetMillis(activeDur);
1688 result = ApplyMinAndMax(result);
1690 if (result.IsDefinite()) {
1691 SMILTime activeEnd = result.GetMillis() + aBegin.GetMillis();
1692 result.SetMillis(activeEnd);
1695 return result;
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()));
1706 } else {
1707 multipliedDuration.SetIndefinite();
1710 SMILTimeValue repeatDuration;
1712 if (mRepeatDur.IsResolved()) {
1713 repeatDuration = std::min(multipliedDuration, mRepeatDur);
1714 } else if (mRepeatCount.IsSet()) {
1715 repeatDuration = multipliedDuration;
1716 } else {
1717 repeatDuration = mSimpleDur;
1720 return repeatDuration;
1723 SMILTimeValue SMILTimedElement::ApplyMinAndMax(
1724 const SMILTimeValue& aDuration) const {
1725 if (!aDuration.IsResolved()) {
1726 return aDuration;
1729 if (mMax < mMin) {
1730 return aDuration;
1733 SMILTimeValue result;
1735 if (aDuration > mMax) {
1736 result = mMax;
1737 } else if (aDuration < mMin) {
1738 result = mMin;
1739 } else {
1740 result = aDuration;
1743 return result;
1746 SMILTime SMILTimedElement::ActiveTimeToSimpleTime(SMILTime aActiveTime,
1747 uint32_t& aRepeatIteration) {
1748 SMILTime result;
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;
1759 } else {
1760 result = aActiveTime % mSimpleDur.GetMillis();
1761 aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis());
1764 return result;
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
1777 // interval end.'
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) {
1792 return nextBegin;
1795 return nullptr;
1798 void SMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice) {
1799 // Check if updates are currently blocked (batched)
1800 if (mDeferIntervalUpdates) {
1801 mDoDeferredUpdate = true;
1802 return;
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
1825 // bets are off.
1826 MOZ_ASSERT(mElementState == STATE_POSTACTIVE,
1827 "Expected to be in post-active state after performing double "
1828 "delete");
1829 return;
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) {
1839 MOZ_ASSERT(false,
1840 "Update current interval recursion depth exceeded threshold");
1841 return;
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,
1849 updatedInterval)) {
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();
1857 } else {
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());
1870 endChanged = true;
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
1879 // container
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
1884 // that begin==end.
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);
1895 ++mDeleteCount;
1896 mElementState = STATE_POSTACTIVE;
1897 ResetCurrentInterval();
1902 void SMILTimedElement::SampleSimpleTime(SMILTime aActiveTime) {
1903 if (mClient) {
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 "
1919 "interval");
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
1930 // state.
1931 SMILTimeValue repeatDuration = GetRepeatDuration();
1932 if (repeatDuration.IsDefinite()) {
1933 activeTime = std::min(repeatDuration.GetMillis(), activeTime);
1935 } else {
1936 MOZ_ASSERT(
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);
1954 } else {
1955 mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);
1959 nsresult SMILTimedElement::AddInstanceTimeFromCurrentTime(SMILTime aCurrentTime,
1960 double aOffsetSeconds,
1961 bool aIsBegin) {
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);
1975 return NS_OK;
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
2011 // point.
2013 switch (mElementState) {
2014 case STATE_STARTUP:
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;
2019 return true;
2021 case STATE_WAITING:
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();
2026 return true;
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);
2045 if (earlyEnd) {
2046 aNextMilestone.mIsEnd = true;
2047 aNextMilestone.mTime = earlyEnd->Time().GetMillis();
2048 return true;
2051 // Apply the previously calculated milestone
2052 if (nextMilestone.IsDefinite()) {
2053 aNextMilestone.mIsEnd = nextMilestone != nextRepeat;
2054 aNextMilestone.mTime = nextMilestone.GetMillis();
2055 return true;
2058 return false;
2061 case STATE_POSTACTIVE:
2062 return false;
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();
2073 if (container) {
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
2082 // notifications.
2083 if (!interval) {
2084 break;
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();
2096 if (container) {
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
2102 // instance times.
2103 InstanceTimeList times;
2104 aInterval->GetDependentTimes(times);
2106 for (RefPtr<SMILInstanceTime>& time : times) {
2107 time->HandleChangedInterval(container, aBeginObjectChanged,
2108 aEndObjectChanged);
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) {
2122 case STATE_STARTUP:
2123 return nullptr;
2125 case STATE_ACTIVE:
2126 return mCurrentInterval->Begin();
2128 case STATE_WAITING:
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;
2151 return false;
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) {
2160 return false;
2163 return true;
2166 } // namespace mozilla