Bug 1882465 - Update .hg-annotate-ignore-revs and .git-blame-ignore-revs to reflect...
[gecko.git] / dom / smil / SMILTimeContainer.cpp
blob321c27ed2c43f790a468a8afd3411577762a75e8
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"
13 #include <algorithm>
15 namespace mozilla {
17 SMILTimeContainer::SMILTimeContainer()
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) {
31 SMILTimeContainer::~SMILTimeContainer() {
32 if (mParent) {
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() {
56 Resume(PAUSE_BEGIN);
57 if (mPauseState) {
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.
68 UpdateCurrentTime();
71 void SMILTimeContainer::Pause(uint32_t aType) {
72 bool didStartPause = false;
74 if (!mPauseState && aType) {
75 mPauseStart = GetParentTime();
76 mNeedsPauseSample = true;
77 didStartPause = true;
80 mPauseState |= aType;
82 if (didStartPause) {
83 NotifyTimeChange();
87 void SMILTimeContainer::Resume(uint32_t aType) {
88 if (!mPauseState) return;
90 mPauseState &= ~aType;
92 if (!mPauseState) {
93 SMILTime extraOffset = GetParentTime() - mPauseStart;
94 mParentOffset += extraOffset;
95 NotifyTimeChange();
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;
107 return mCurrentTime;
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;
122 mIsSeeking = true;
124 if (IsPaused()) {
125 mNeedsPauseSample = true;
126 mPauseStart = parentTime;
129 if (aSeekTo < mCurrentTime) {
130 // Backwards seek
131 mNeedsRewind = true;
132 ClearMilestones();
135 // Force an update to the current time in case we get a call to GetCurrentTime
136 // before another call to Sample().
137 UpdateCurrentTime();
139 NotifyTimeChange();
142 SMILTime SMILTimeContainer::GetParentTime() const {
143 if (mParent) return mParent->GetCurrentTimeAsSMILTime();
145 return 0L;
148 void SMILTimeContainer::SyncPauseTime() {
149 if (IsPaused()) {
150 SMILTime parentTime = GetParentTime();
151 SMILTime extraOffset = parentTime - mPauseStart;
152 mParentOffset += extraOffset;
153 mPauseStart = parentTime;
157 void SMILTimeContainer::Sample() {
158 if (!NeedsSample()) return;
160 UpdateCurrentTime();
161 DoSample();
163 mNeedsPauseSample = false;
166 nsresult SMILTimeContainer::SetParent(SMILTimeContainer* aParent) {
167 if (mParent) {
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;
176 mPauseStart = 0L;
179 mParent = aParent;
181 nsresult rv = NS_OK;
182 if (mParent) {
183 rv = mParent->AddChild(*this);
186 return rv;
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);
216 return true;
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(),
227 aMilestone.mIsEnd);
229 MOZ_ASSERT(mMilestoneEntries.Top().mMilestone >= containerMilestone,
230 "Trying to pop off earliest times but we have earlier ones that "
231 "were overlooked");
233 MOZ_ASSERT(!mHoldingEntries);
235 bool gotOne = false;
236 while (!mMilestoneEntries.IsEmpty() &&
237 mMilestoneEntries.Top().mMilestone == containerMilestone) {
238 aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase);
239 gotOne = true;
242 return gotOne;
245 void SMILTimeContainer::Traverse(
246 nsCycleCollectionTraversalCallback* aCallback) {
247 #ifdef DEBUG
248 AutoRestore<bool> saveHolding(mHoldingEntries);
249 mHoldingEntries = true;
250 #endif
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()));
255 ++p;
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
278 // time.
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;
291 #ifdef DEBUG
292 AutoRestore<bool> saveHolding(mHoldingEntries);
293 mHoldingEntries = true;
294 #endif
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