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