/src/mozilla-central/dom/smil/nsSMILTimeContainer.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 "nsSMILTimeContainer.h" |
8 | | #include "nsSMILTimeValue.h" |
9 | | #include "nsSMILTimedElement.h" |
10 | | #include <algorithm> |
11 | | |
12 | | #include "mozilla/AutoRestore.h" |
13 | | |
14 | | using namespace mozilla; |
15 | | |
16 | | nsSMILTimeContainer::nsSMILTimeContainer() |
17 | | : |
18 | | mParent(nullptr), |
19 | | mCurrentTime(0L), |
20 | | mParentOffset(0L), |
21 | | mPauseStart(0L), |
22 | | mNeedsPauseSample(false), |
23 | | mNeedsRewind(false), |
24 | | mIsSeeking(false), |
25 | | #ifdef DEBUG |
26 | | mHoldingEntries(false), |
27 | | #endif |
28 | | mPauseState(PAUSE_BEGIN) |
29 | 0 | { |
30 | 0 | } |
31 | | |
32 | | nsSMILTimeContainer::~nsSMILTimeContainer() |
33 | 0 | { |
34 | 0 | if (mParent) { |
35 | 0 | mParent->RemoveChild(*this); |
36 | 0 | } |
37 | 0 | } |
38 | | |
39 | | nsSMILTimeValue |
40 | | nsSMILTimeContainer::ContainerToParentTime(nsSMILTime aContainerTime) const |
41 | 0 | { |
42 | 0 | // If we're paused, then future times are indefinite |
43 | 0 | if (IsPaused() && aContainerTime > mCurrentTime) |
44 | 0 | return nsSMILTimeValue::Indefinite(); |
45 | 0 | |
46 | 0 | return nsSMILTimeValue(aContainerTime + mParentOffset); |
47 | 0 | } |
48 | | |
49 | | nsSMILTimeValue |
50 | | nsSMILTimeContainer::ParentToContainerTime(nsSMILTime aParentTime) const |
51 | 0 | { |
52 | 0 | // If we're paused, then any time after when we paused is indefinite |
53 | 0 | if (IsPaused() && aParentTime > mPauseStart) |
54 | 0 | return nsSMILTimeValue::Indefinite(); |
55 | 0 | |
56 | 0 | return nsSMILTimeValue(aParentTime - mParentOffset); |
57 | 0 | } |
58 | | |
59 | | void |
60 | | nsSMILTimeContainer::Begin() |
61 | 0 | { |
62 | 0 | Resume(PAUSE_BEGIN); |
63 | 0 | if (mPauseState) { |
64 | 0 | mNeedsPauseSample = true; |
65 | 0 | } |
66 | 0 |
|
67 | 0 | // This is a little bit complicated here. Ideally we'd just like to call |
68 | 0 | // Sample() and force an initial sample but this turns out to be a bad idea |
69 | 0 | // because this may mean that NeedsSample() no longer reports true and so when |
70 | 0 | // we come to the first real sample our parent will skip us over altogether. |
71 | 0 | // So we force the time to be updated and adopt the policy to never call |
72 | 0 | // Sample() ourselves but to always leave that to our parent or client. |
73 | 0 |
|
74 | 0 | UpdateCurrentTime(); |
75 | 0 | } |
76 | | |
77 | | void |
78 | | nsSMILTimeContainer::Pause(uint32_t aType) |
79 | 0 | { |
80 | 0 | bool didStartPause = false; |
81 | 0 |
|
82 | 0 | if (!mPauseState && aType) { |
83 | 0 | mPauseStart = GetParentTime(); |
84 | 0 | mNeedsPauseSample = true; |
85 | 0 | didStartPause = true; |
86 | 0 | } |
87 | 0 |
|
88 | 0 | mPauseState |= aType; |
89 | 0 |
|
90 | 0 | if (didStartPause) { |
91 | 0 | NotifyTimeChange(); |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | void |
96 | | nsSMILTimeContainer::Resume(uint32_t aType) |
97 | 0 | { |
98 | 0 | if (!mPauseState) |
99 | 0 | return; |
100 | 0 | |
101 | 0 | mPauseState &= ~aType; |
102 | 0 |
|
103 | 0 | if (!mPauseState) { |
104 | 0 | nsSMILTime extraOffset = GetParentTime() - mPauseStart; |
105 | 0 | mParentOffset += extraOffset; |
106 | 0 | NotifyTimeChange(); |
107 | 0 | } |
108 | 0 | } |
109 | | |
110 | | nsSMILTime |
111 | | nsSMILTimeContainer::GetCurrentTime() const |
112 | 0 | { |
113 | 0 | // The following behaviour is consistent with: |
114 | 0 | // http://www.w3.org/2003/01/REC-SVG11-20030114-errata |
115 | 0 | // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin |
116 | 0 | // which says that if GetCurrentTime is called before the document timeline |
117 | 0 | // has begun we should just return 0. |
118 | 0 | if (IsPausedByType(PAUSE_BEGIN)) |
119 | 0 | return 0L; |
120 | 0 | |
121 | 0 | return mCurrentTime; |
122 | 0 | } |
123 | | |
124 | | void |
125 | | nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo) |
126 | 0 | { |
127 | 0 | // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's |
128 | 0 | // behaviour of clamping negative times to 0. |
129 | 0 | aSeekTo = std::max<nsSMILTime>(0, aSeekTo); |
130 | 0 |
|
131 | 0 | // The following behaviour is consistent with: |
132 | 0 | // http://www.w3.org/2003/01/REC-SVG11-20030114-errata |
133 | 0 | // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin |
134 | 0 | // which says that if SetCurrentTime is called before the document timeline |
135 | 0 | // has begun we should still adjust the offset. |
136 | 0 | nsSMILTime parentTime = GetParentTime(); |
137 | 0 | mParentOffset = parentTime - aSeekTo; |
138 | 0 | mIsSeeking = true; |
139 | 0 |
|
140 | 0 | if (IsPaused()) { |
141 | 0 | mNeedsPauseSample = true; |
142 | 0 | mPauseStart = parentTime; |
143 | 0 | } |
144 | 0 |
|
145 | 0 | if (aSeekTo < mCurrentTime) { |
146 | 0 | // Backwards seek |
147 | 0 | mNeedsRewind = true; |
148 | 0 | ClearMilestones(); |
149 | 0 | } |
150 | 0 |
|
151 | 0 | // Force an update to the current time in case we get a call to GetCurrentTime |
152 | 0 | // before another call to Sample(). |
153 | 0 | UpdateCurrentTime(); |
154 | 0 |
|
155 | 0 | NotifyTimeChange(); |
156 | 0 | } |
157 | | |
158 | | nsSMILTime |
159 | | nsSMILTimeContainer::GetParentTime() const |
160 | 0 | { |
161 | 0 | if (mParent) |
162 | 0 | return mParent->GetCurrentTime(); |
163 | 0 | |
164 | 0 | return 0L; |
165 | 0 | } |
166 | | |
167 | | void |
168 | | nsSMILTimeContainer::SyncPauseTime() |
169 | 0 | { |
170 | 0 | if (IsPaused()) { |
171 | 0 | nsSMILTime parentTime = GetParentTime(); |
172 | 0 | nsSMILTime extraOffset = parentTime - mPauseStart; |
173 | 0 | mParentOffset += extraOffset; |
174 | 0 | mPauseStart = parentTime; |
175 | 0 | } |
176 | 0 | } |
177 | | |
178 | | void |
179 | | nsSMILTimeContainer::Sample() |
180 | 0 | { |
181 | 0 | if (!NeedsSample()) |
182 | 0 | return; |
183 | 0 | |
184 | 0 | UpdateCurrentTime(); |
185 | 0 | DoSample(); |
186 | 0 |
|
187 | 0 | mNeedsPauseSample = false; |
188 | 0 | } |
189 | | |
190 | | nsresult |
191 | | nsSMILTimeContainer::SetParent(nsSMILTimeContainer* aParent) |
192 | 0 | { |
193 | 0 | if (mParent) { |
194 | 0 | mParent->RemoveChild(*this); |
195 | 0 | // When we're not attached to a parent time container, GetParentTime() will |
196 | 0 | // return 0. We need to adjust our pause state information to be relative to |
197 | 0 | // this new time base. |
198 | 0 | // Note that since "current time = parent time - parent offset" setting the |
199 | 0 | // parent offset and pause start as follows preserves our current time even |
200 | 0 | // while parent time = 0. |
201 | 0 | mParentOffset = -mCurrentTime; |
202 | 0 | mPauseStart = 0L; |
203 | 0 | } |
204 | 0 |
|
205 | 0 | mParent = aParent; |
206 | 0 |
|
207 | 0 | nsresult rv = NS_OK; |
208 | 0 | if (mParent) { |
209 | 0 | rv = mParent->AddChild(*this); |
210 | 0 | } |
211 | 0 |
|
212 | 0 | return rv; |
213 | 0 | } |
214 | | |
215 | | bool |
216 | | nsSMILTimeContainer::AddMilestone(const nsSMILMilestone& aMilestone, |
217 | | mozilla::dom::SVGAnimationElement& aElement) |
218 | 0 | { |
219 | 0 | // We record the milestone time and store it along with the element but this |
220 | 0 | // time may change (e.g. if attributes are changed on the timed element in |
221 | 0 | // between samples). If this happens, then we may do an unecessary sample |
222 | 0 | // but that's pretty cheap. |
223 | 0 | MOZ_ASSERT(!mHoldingEntries); |
224 | 0 | return mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement)); |
225 | 0 | } |
226 | | |
227 | | void |
228 | | nsSMILTimeContainer::ClearMilestones() |
229 | 0 | { |
230 | 0 | MOZ_ASSERT(!mHoldingEntries); |
231 | 0 | mMilestoneEntries.Clear(); |
232 | 0 | } |
233 | | |
234 | | bool |
235 | | nsSMILTimeContainer::GetNextMilestoneInParentTime( |
236 | | nsSMILMilestone& aNextMilestone) const |
237 | 0 | { |
238 | 0 | if (mMilestoneEntries.IsEmpty()) |
239 | 0 | return false; |
240 | 0 | |
241 | 0 | nsSMILTimeValue parentTime = |
242 | 0 | ContainerToParentTime(mMilestoneEntries.Top().mMilestone.mTime); |
243 | 0 | if (!parentTime.IsDefinite()) |
244 | 0 | return false; |
245 | 0 | |
246 | 0 | aNextMilestone = nsSMILMilestone(parentTime.GetMillis(), |
247 | 0 | mMilestoneEntries.Top().mMilestone.mIsEnd); |
248 | 0 |
|
249 | 0 | return true; |
250 | 0 | } |
251 | | |
252 | | bool |
253 | | nsSMILTimeContainer::PopMilestoneElementsAtMilestone( |
254 | | const nsSMILMilestone& aMilestone, |
255 | | AnimElemArray& aMatchedElements) |
256 | 0 | { |
257 | 0 | if (mMilestoneEntries.IsEmpty()) |
258 | 0 | return false; |
259 | 0 | |
260 | 0 | nsSMILTimeValue containerTime = ParentToContainerTime(aMilestone.mTime); |
261 | 0 | if (!containerTime.IsDefinite()) |
262 | 0 | return false; |
263 | 0 | |
264 | 0 | nsSMILMilestone containerMilestone(containerTime.GetMillis(), |
265 | 0 | aMilestone.mIsEnd); |
266 | 0 |
|
267 | 0 | MOZ_ASSERT(mMilestoneEntries.Top().mMilestone >= containerMilestone, |
268 | 0 | "Trying to pop off earliest times but we have earlier ones that " |
269 | 0 | "were overlooked"); |
270 | 0 |
|
271 | 0 | MOZ_ASSERT(!mHoldingEntries); |
272 | 0 |
|
273 | 0 | bool gotOne = false; |
274 | 0 | while (!mMilestoneEntries.IsEmpty() && |
275 | 0 | mMilestoneEntries.Top().mMilestone == containerMilestone) |
276 | 0 | { |
277 | 0 | aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase); |
278 | 0 | gotOne = true; |
279 | 0 | } |
280 | 0 |
|
281 | 0 | return gotOne; |
282 | 0 | } |
283 | | |
284 | | void |
285 | | nsSMILTimeContainer::Traverse(nsCycleCollectionTraversalCallback* aCallback) |
286 | 0 | { |
287 | | #ifdef DEBUG |
288 | | AutoRestore<bool> saveHolding(mHoldingEntries); |
289 | | mHoldingEntries = true; |
290 | | #endif |
291 | | const MilestoneEntry* p = mMilestoneEntries.Elements(); |
292 | 0 | while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { |
293 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase"); |
294 | 0 | aCallback->NoteXPCOMChild(static_cast<nsIContent*>(p->mTimebase.get())); |
295 | 0 | ++p; |
296 | 0 | } |
297 | 0 | } |
298 | | |
299 | | void |
300 | | nsSMILTimeContainer::Unlink() |
301 | 0 | { |
302 | 0 | MOZ_ASSERT(!mHoldingEntries); |
303 | 0 | mMilestoneEntries.Clear(); |
304 | 0 | } |
305 | | |
306 | | void |
307 | | nsSMILTimeContainer::UpdateCurrentTime() |
308 | 0 | { |
309 | 0 | nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime(); |
310 | 0 | mCurrentTime = now - mParentOffset; |
311 | 0 | MOZ_ASSERT(mCurrentTime >= 0, "Container has negative time"); |
312 | 0 | } |
313 | | |
314 | | void |
315 | | nsSMILTimeContainer::NotifyTimeChange() |
316 | 0 | { |
317 | 0 | // Called when the container time is changed with respect to the document |
318 | 0 | // time. When this happens time dependencies in other time containers need to |
319 | 0 | // re-resolve their times because begin and end times are stored in container |
320 | 0 | // time. |
321 | 0 | // |
322 | 0 | // To get the list of timed elements with dependencies we simply re-use the |
323 | 0 | // milestone elements. This is because any timed element with dependents and |
324 | 0 | // with significant transitions yet to fire should have their next milestone |
325 | 0 | // registered. Other timed elements don't matter. |
326 | 0 |
|
327 | 0 | // Copy the timed elements to a separate array before calling |
328 | 0 | // HandleContainerTimeChange on each of them in case doing so mutates |
329 | 0 | // mMilestoneEntries. |
330 | 0 | nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elems; |
331 | 0 |
|
332 | 0 | { |
333 | | #ifdef DEBUG |
334 | | AutoRestore<bool> saveHolding(mHoldingEntries); |
335 | | mHoldingEntries = true; |
336 | | #endif |
337 | | for (const MilestoneEntry* p = mMilestoneEntries.Elements(); |
338 | 0 | p < mMilestoneEntries.Elements() + mMilestoneEntries.Length(); |
339 | 0 | ++p) { |
340 | 0 | elems.AppendElement(p->mTimebase.get()); |
341 | 0 | } |
342 | 0 | } |
343 | 0 |
|
344 | 0 | for (auto& elem : elems) { |
345 | 0 | elem->TimedElement().HandleContainerTimeChange(); |
346 | 0 | } |
347 | 0 | } |