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