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