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