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/. */
7 #include "SMILTimeContainer.h"
9 #include "mozilla/AutoRestore.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/SMILTimedElement.h"
12 #include "mozilla/SMILTimeValue.h"
17 SMILTimeContainer::SMILTimeContainer()
22 mNeedsPauseSample(false),
26 mHoldingEntries(false),
28 mPauseState(PAUSE_BEGIN
) {
31 SMILTimeContainer::~SMILTimeContainer() {
33 mParent
->RemoveChild(*this);
37 SMILTimeValue
SMILTimeContainer::ContainerToParentTime(
38 SMILTime aContainerTime
) const {
39 // If we're paused, then future times are indefinite
40 if (IsPaused() && aContainerTime
> mCurrentTime
)
41 return SMILTimeValue::Indefinite();
43 return SMILTimeValue(aContainerTime
+ mParentOffset
);
46 SMILTimeValue
SMILTimeContainer::ParentToContainerTime(
47 SMILTime aParentTime
) const {
48 // If we're paused, then any time after when we paused is indefinite
49 if (IsPaused() && aParentTime
> mPauseStart
)
50 return SMILTimeValue::Indefinite();
52 return SMILTimeValue(aParentTime
- mParentOffset
);
55 void SMILTimeContainer::Begin() {
58 mNeedsPauseSample
= true;
61 // This is a little bit complicated here. Ideally we'd just like to call
62 // Sample() and force an initial sample but this turns out to be a bad idea
63 // because this may mean that NeedsSample() no longer reports true and so when
64 // we come to the first real sample our parent will skip us over altogether.
65 // So we force the time to be updated and adopt the policy to never call
66 // Sample() ourselves but to always leave that to our parent or client.
71 void SMILTimeContainer::Pause(uint32_t aType
) {
72 bool didStartPause
= false;
74 if (!mPauseState
&& aType
) {
75 mPauseStart
= GetParentTime();
76 mNeedsPauseSample
= true;
87 void SMILTimeContainer::Resume(uint32_t aType
) {
88 if (!mPauseState
) return;
90 mPauseState
&= ~aType
;
93 SMILTime extraOffset
= GetParentTime() - mPauseStart
;
94 mParentOffset
+= extraOffset
;
99 SMILTime
SMILTimeContainer::GetCurrentTimeAsSMILTime() const {
100 // The following behaviour is consistent with:
101 // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
102 // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
103 // which says that if GetCurrentTime is called before the document timeline
104 // has begun we should just return 0.
105 if (IsPausedByType(PAUSE_BEGIN
)) return 0L;
110 void SMILTimeContainer::SetCurrentTime(SMILTime aSeekTo
) {
111 // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's
112 // behaviour of clamping negative times to 0.
113 aSeekTo
= std::max
<SMILTime
>(0, aSeekTo
);
115 // The following behaviour is consistent with:
116 // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
117 // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
118 // which says that if SetCurrentTime is called before the document timeline
119 // has begun we should still adjust the offset.
120 SMILTime parentTime
= GetParentTime();
121 mParentOffset
= parentTime
- aSeekTo
;
125 mNeedsPauseSample
= true;
126 mPauseStart
= parentTime
;
129 if (aSeekTo
< mCurrentTime
) {
135 // Force an update to the current time in case we get a call to GetCurrentTime
136 // before another call to Sample().
142 SMILTime
SMILTimeContainer::GetParentTime() const {
143 if (mParent
) return mParent
->GetCurrentTimeAsSMILTime();
148 void SMILTimeContainer::SyncPauseTime() {
150 SMILTime parentTime
= GetParentTime();
151 SMILTime extraOffset
= parentTime
- mPauseStart
;
152 mParentOffset
+= extraOffset
;
153 mPauseStart
= parentTime
;
157 void SMILTimeContainer::Sample() {
158 if (!NeedsSample()) return;
163 mNeedsPauseSample
= false;
166 nsresult
SMILTimeContainer::SetParent(SMILTimeContainer
* aParent
) {
168 mParent
->RemoveChild(*this);
169 // When we're not attached to a parent time container, GetParentTime() will
170 // return 0. We need to adjust our pause state information to be relative to
171 // this new time base.
172 // Note that since "current time = parent time - parent offset" setting the
173 // parent offset and pause start as follows preserves our current time even
174 // while parent time = 0.
175 mParentOffset
= -mCurrentTime
;
183 rv
= mParent
->AddChild(*this);
189 void SMILTimeContainer::AddMilestone(
190 const SMILMilestone
& aMilestone
,
191 mozilla::dom::SVGAnimationElement
& aElement
) {
192 // We record the milestone time and store it along with the element but this
193 // time may change (e.g. if attributes are changed on the timed element in
194 // between samples). If this happens, then we may do an unecessary sample
195 // but that's pretty cheap.
196 MOZ_ASSERT(!mHoldingEntries
);
197 mMilestoneEntries
.Push(MilestoneEntry(aMilestone
, aElement
));
200 void SMILTimeContainer::ClearMilestones() {
201 MOZ_ASSERT(!mHoldingEntries
);
202 mMilestoneEntries
.Clear();
205 bool SMILTimeContainer::GetNextMilestoneInParentTime(
206 SMILMilestone
& aNextMilestone
) const {
207 if (mMilestoneEntries
.IsEmpty()) return false;
209 SMILTimeValue parentTime
=
210 ContainerToParentTime(mMilestoneEntries
.Top().mMilestone
.mTime
);
211 if (!parentTime
.IsDefinite()) return false;
213 aNextMilestone
= SMILMilestone(parentTime
.GetMillis(),
214 mMilestoneEntries
.Top().mMilestone
.mIsEnd
);
219 bool SMILTimeContainer::PopMilestoneElementsAtMilestone(
220 const SMILMilestone
& aMilestone
, AnimElemArray
& aMatchedElements
) {
221 if (mMilestoneEntries
.IsEmpty()) return false;
223 SMILTimeValue containerTime
= ParentToContainerTime(aMilestone
.mTime
);
224 if (!containerTime
.IsDefinite()) return false;
226 SMILMilestone
containerMilestone(containerTime
.GetMillis(),
229 MOZ_ASSERT(mMilestoneEntries
.Top().mMilestone
>= containerMilestone
,
230 "Trying to pop off earliest times but we have earlier ones that "
233 MOZ_ASSERT(!mHoldingEntries
);
236 while (!mMilestoneEntries
.IsEmpty() &&
237 mMilestoneEntries
.Top().mMilestone
== containerMilestone
) {
238 aMatchedElements
.AppendElement(mMilestoneEntries
.Pop().mTimebase
);
245 void SMILTimeContainer::Traverse(
246 nsCycleCollectionTraversalCallback
* aCallback
) {
248 AutoRestore
<bool> saveHolding(mHoldingEntries
);
249 mHoldingEntries
= true;
251 const MilestoneEntry
* p
= mMilestoneEntries
.Elements();
252 while (p
< mMilestoneEntries
.Elements() + mMilestoneEntries
.Length()) {
253 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback
, "mTimebase");
254 aCallback
->NoteXPCOMChild(static_cast<nsIContent
*>(p
->mTimebase
.get()));
259 void SMILTimeContainer::Unlink() {
260 MOZ_ASSERT(!mHoldingEntries
);
261 mMilestoneEntries
.Clear();
264 void SMILTimeContainer::UpdateCurrentTime() {
265 SMILTime now
= IsPaused() ? mPauseStart
: GetParentTime();
266 MOZ_ASSERT(now
>= mParentOffset
,
267 "Container has negative time with respect to parent");
268 const auto updatedCurrentTime
= CheckedInt
<SMILTime
>(now
) - mParentOffset
;
269 mCurrentTime
= updatedCurrentTime
.isValid()
270 ? updatedCurrentTime
.value()
271 : std::numeric_limits
<SMILTime
>::max();
274 void SMILTimeContainer::NotifyTimeChange() {
275 // Called when the container time is changed with respect to the document
276 // time. When this happens time dependencies in other time containers need to
277 // re-resolve their times because begin and end times are stored in container
280 // To get the list of timed elements with dependencies we simply re-use the
281 // milestone elements. This is because any timed element with dependents and
282 // with significant transitions yet to fire should have their next milestone
283 // registered. Other timed elements don't matter.
285 // Copy the timed elements to a separate array before calling
286 // HandleContainerTimeChange on each of them in case doing so mutates
287 // mMilestoneEntries.
288 nsTArray
<RefPtr
<mozilla::dom::SVGAnimationElement
>> elems
;
292 AutoRestore
<bool> saveHolding(mHoldingEntries
);
293 mHoldingEntries
= true;
295 for (const MilestoneEntry
* p
= mMilestoneEntries
.Elements();
296 p
< mMilestoneEntries
.Elements() + mMilestoneEntries
.Length(); ++p
) {
297 elems
.AppendElement(p
->mTimebase
.get());
301 for (auto& elem
: elems
) {
302 elem
->TimedElement().HandleContainerTimeChange();
306 } // namespace mozilla