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