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