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