1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/smil/nsSMILTimeContainer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,321 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsSMILTimeContainer.h" 1.10 +#include "nsSMILTimeValue.h" 1.11 +#include "nsSMILTimedElement.h" 1.12 +#include <algorithm> 1.13 + 1.14 +nsSMILTimeContainer::nsSMILTimeContainer() 1.15 +: 1.16 + mParent(nullptr), 1.17 + mCurrentTime(0L), 1.18 + mParentOffset(0L), 1.19 + mPauseStart(0L), 1.20 + mNeedsPauseSample(false), 1.21 + mNeedsRewind(false), 1.22 + mIsSeeking(false), 1.23 + mPauseState(PAUSE_BEGIN) 1.24 +{ 1.25 +} 1.26 + 1.27 +nsSMILTimeContainer::~nsSMILTimeContainer() 1.28 +{ 1.29 + if (mParent) { 1.30 + mParent->RemoveChild(*this); 1.31 + } 1.32 +} 1.33 + 1.34 +nsSMILTimeValue 1.35 +nsSMILTimeContainer::ContainerToParentTime(nsSMILTime aContainerTime) const 1.36 +{ 1.37 + // If we're paused, then future times are indefinite 1.38 + if (IsPaused() && aContainerTime > mCurrentTime) 1.39 + return nsSMILTimeValue::Indefinite(); 1.40 + 1.41 + return nsSMILTimeValue(aContainerTime + mParentOffset); 1.42 +} 1.43 + 1.44 +nsSMILTimeValue 1.45 +nsSMILTimeContainer::ParentToContainerTime(nsSMILTime aParentTime) const 1.46 +{ 1.47 + // If we're paused, then any time after when we paused is indefinite 1.48 + if (IsPaused() && aParentTime > mPauseStart) 1.49 + return nsSMILTimeValue::Indefinite(); 1.50 + 1.51 + return nsSMILTimeValue(aParentTime - mParentOffset); 1.52 +} 1.53 + 1.54 +void 1.55 +nsSMILTimeContainer::Begin() 1.56 +{ 1.57 + Resume(PAUSE_BEGIN); 1.58 + if (mPauseState) { 1.59 + mNeedsPauseSample = true; 1.60 + } 1.61 + 1.62 + // This is a little bit complicated here. Ideally we'd just like to call 1.63 + // Sample() and force an initial sample but this turns out to be a bad idea 1.64 + // because this may mean that NeedsSample() no longer reports true and so when 1.65 + // we come to the first real sample our parent will skip us over altogether. 1.66 + // So we force the time to be updated and adopt the policy to never call 1.67 + // Sample() ourselves but to always leave that to our parent or client. 1.68 + 1.69 + UpdateCurrentTime(); 1.70 +} 1.71 + 1.72 +void 1.73 +nsSMILTimeContainer::Pause(uint32_t aType) 1.74 +{ 1.75 + bool didStartPause = false; 1.76 + 1.77 + if (!mPauseState && aType) { 1.78 + mPauseStart = GetParentTime(); 1.79 + mNeedsPauseSample = true; 1.80 + didStartPause = true; 1.81 + } 1.82 + 1.83 + mPauseState |= aType; 1.84 + 1.85 + if (didStartPause) { 1.86 + NotifyTimeChange(); 1.87 + } 1.88 +} 1.89 + 1.90 +void 1.91 +nsSMILTimeContainer::Resume(uint32_t aType) 1.92 +{ 1.93 + if (!mPauseState) 1.94 + return; 1.95 + 1.96 + mPauseState &= ~aType; 1.97 + 1.98 + if (!mPauseState) { 1.99 + nsSMILTime extraOffset = GetParentTime() - mPauseStart; 1.100 + mParentOffset += extraOffset; 1.101 + NotifyTimeChange(); 1.102 + } 1.103 +} 1.104 + 1.105 +nsSMILTime 1.106 +nsSMILTimeContainer::GetCurrentTime() const 1.107 +{ 1.108 + // The following behaviour is consistent with: 1.109 + // http://www.w3.org/2003/01/REC-SVG11-20030114-errata 1.110 + // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin 1.111 + // which says that if GetCurrentTime is called before the document timeline 1.112 + // has begun we should just return 0. 1.113 + if (IsPausedByType(PAUSE_BEGIN)) 1.114 + return 0L; 1.115 + 1.116 + return mCurrentTime; 1.117 +} 1.118 + 1.119 +void 1.120 +nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo) 1.121 +{ 1.122 + // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's 1.123 + // behaviour of clamping negative times to 0. 1.124 + aSeekTo = std::max<nsSMILTime>(0, aSeekTo); 1.125 + 1.126 + // The following behaviour is consistent with: 1.127 + // http://www.w3.org/2003/01/REC-SVG11-20030114-errata 1.128 + // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin 1.129 + // which says that if SetCurrentTime is called before the document timeline 1.130 + // has begun we should still adjust the offset. 1.131 + nsSMILTime parentTime = GetParentTime(); 1.132 + mParentOffset = parentTime - aSeekTo; 1.133 + mIsSeeking = true; 1.134 + 1.135 + if (IsPaused()) { 1.136 + mNeedsPauseSample = true; 1.137 + mPauseStart = parentTime; 1.138 + } 1.139 + 1.140 + if (aSeekTo < mCurrentTime) { 1.141 + // Backwards seek 1.142 + mNeedsRewind = true; 1.143 + ClearMilestones(); 1.144 + } 1.145 + 1.146 + // Force an update to the current time in case we get a call to GetCurrentTime 1.147 + // before another call to Sample(). 1.148 + UpdateCurrentTime(); 1.149 + 1.150 + NotifyTimeChange(); 1.151 +} 1.152 + 1.153 +nsSMILTime 1.154 +nsSMILTimeContainer::GetParentTime() const 1.155 +{ 1.156 + if (mParent) 1.157 + return mParent->GetCurrentTime(); 1.158 + 1.159 + return 0L; 1.160 +} 1.161 + 1.162 +void 1.163 +nsSMILTimeContainer::SyncPauseTime() 1.164 +{ 1.165 + if (IsPaused()) { 1.166 + nsSMILTime parentTime = GetParentTime(); 1.167 + nsSMILTime extraOffset = parentTime - mPauseStart; 1.168 + mParentOffset += extraOffset; 1.169 + mPauseStart = parentTime; 1.170 + } 1.171 +} 1.172 + 1.173 +void 1.174 +nsSMILTimeContainer::Sample() 1.175 +{ 1.176 + if (!NeedsSample()) 1.177 + return; 1.178 + 1.179 + UpdateCurrentTime(); 1.180 + DoSample(); 1.181 + 1.182 + mNeedsPauseSample = false; 1.183 +} 1.184 + 1.185 +nsresult 1.186 +nsSMILTimeContainer::SetParent(nsSMILTimeContainer* aParent) 1.187 +{ 1.188 + if (mParent) { 1.189 + mParent->RemoveChild(*this); 1.190 + // When we're not attached to a parent time container, GetParentTime() will 1.191 + // return 0. We need to adjust our pause state information to be relative to 1.192 + // this new time base. 1.193 + // Note that since "current time = parent time - parent offset" setting the 1.194 + // parent offset and pause start as follows preserves our current time even 1.195 + // while parent time = 0. 1.196 + mParentOffset = -mCurrentTime; 1.197 + mPauseStart = 0L; 1.198 + } 1.199 + 1.200 + mParent = aParent; 1.201 + 1.202 + nsresult rv = NS_OK; 1.203 + if (mParent) { 1.204 + rv = mParent->AddChild(*this); 1.205 + } 1.206 + 1.207 + return rv; 1.208 +} 1.209 + 1.210 +bool 1.211 +nsSMILTimeContainer::AddMilestone(const nsSMILMilestone& aMilestone, 1.212 + mozilla::dom::SVGAnimationElement& aElement) 1.213 +{ 1.214 + // We record the milestone time and store it along with the element but this 1.215 + // time may change (e.g. if attributes are changed on the timed element in 1.216 + // between samples). If this happens, then we may do an unecessary sample 1.217 + // but that's pretty cheap. 1.218 + return mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement)); 1.219 +} 1.220 + 1.221 +void 1.222 +nsSMILTimeContainer::ClearMilestones() 1.223 +{ 1.224 + mMilestoneEntries.Clear(); 1.225 +} 1.226 + 1.227 +bool 1.228 +nsSMILTimeContainer::GetNextMilestoneInParentTime( 1.229 + nsSMILMilestone& aNextMilestone) const 1.230 +{ 1.231 + if (mMilestoneEntries.IsEmpty()) 1.232 + return false; 1.233 + 1.234 + nsSMILTimeValue parentTime = 1.235 + ContainerToParentTime(mMilestoneEntries.Top().mMilestone.mTime); 1.236 + if (!parentTime.IsDefinite()) 1.237 + return false; 1.238 + 1.239 + aNextMilestone = nsSMILMilestone(parentTime.GetMillis(), 1.240 + mMilestoneEntries.Top().mMilestone.mIsEnd); 1.241 + 1.242 + return true; 1.243 +} 1.244 + 1.245 +bool 1.246 +nsSMILTimeContainer::PopMilestoneElementsAtMilestone( 1.247 + const nsSMILMilestone& aMilestone, 1.248 + AnimElemArray& aMatchedElements) 1.249 +{ 1.250 + if (mMilestoneEntries.IsEmpty()) 1.251 + return false; 1.252 + 1.253 + nsSMILTimeValue containerTime = ParentToContainerTime(aMilestone.mTime); 1.254 + if (!containerTime.IsDefinite()) 1.255 + return false; 1.256 + 1.257 + nsSMILMilestone containerMilestone(containerTime.GetMillis(), 1.258 + aMilestone.mIsEnd); 1.259 + 1.260 + NS_ABORT_IF_FALSE(mMilestoneEntries.Top().mMilestone >= containerMilestone, 1.261 + "Trying to pop off earliest times but we have earlier ones that were " 1.262 + "overlooked"); 1.263 + 1.264 + bool gotOne = false; 1.265 + while (!mMilestoneEntries.IsEmpty() && 1.266 + mMilestoneEntries.Top().mMilestone == containerMilestone) 1.267 + { 1.268 + aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase); 1.269 + gotOne = true; 1.270 + } 1.271 + 1.272 + return gotOne; 1.273 +} 1.274 + 1.275 +void 1.276 +nsSMILTimeContainer::Traverse(nsCycleCollectionTraversalCallback* aCallback) 1.277 +{ 1.278 + const MilestoneEntry* p = mMilestoneEntries.Elements(); 1.279 + while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { 1.280 + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase"); 1.281 + aCallback->NoteXPCOMChild(static_cast<nsIContent*>(p->mTimebase.get())); 1.282 + ++p; 1.283 + } 1.284 +} 1.285 + 1.286 +void 1.287 +nsSMILTimeContainer::Unlink() 1.288 +{ 1.289 + mMilestoneEntries.Clear(); 1.290 +} 1.291 + 1.292 +void 1.293 +nsSMILTimeContainer::UpdateCurrentTime() 1.294 +{ 1.295 + nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime(); 1.296 + mCurrentTime = now - mParentOffset; 1.297 + NS_ABORT_IF_FALSE(mCurrentTime >= 0, "Container has negative time"); 1.298 +} 1.299 + 1.300 +void 1.301 +nsSMILTimeContainer::NotifyTimeChange() 1.302 +{ 1.303 + // Called when the container time is changed with respect to the document 1.304 + // time. When this happens time dependencies in other time containers need to 1.305 + // re-resolve their times because begin and end times are stored in container 1.306 + // time. 1.307 + // 1.308 + // To get the list of timed elements with dependencies we simply re-use the 1.309 + // milestone elements. This is because any timed element with dependents and 1.310 + // with significant transitions yet to fire should have their next milestone 1.311 + // registered. Other timed elements don't matter. 1.312 + const MilestoneEntry* p = mMilestoneEntries.Elements(); 1.313 +#if DEBUG 1.314 + uint32_t queueLength = mMilestoneEntries.Length(); 1.315 +#endif 1.316 + while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { 1.317 + mozilla::dom::SVGAnimationElement* elem = p->mTimebase.get(); 1.318 + elem->TimedElement().HandleContainerTimeChange(); 1.319 + NS_ABORT_IF_FALSE(queueLength == mMilestoneEntries.Length(), 1.320 + "Call to HandleContainerTimeChange resulted in a change to the " 1.321 + "queue of milestones"); 1.322 + ++p; 1.323 + } 1.324 +}