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