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