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