1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/smil/nsSMILTimedElement.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2405 @@ 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 "mozilla/DebugOnly.h" 1.10 + 1.11 +#include "mozilla/BasicEvents.h" 1.12 +#include "mozilla/EventDispatcher.h" 1.13 +#include "mozilla/dom/SVGAnimationElement.h" 1.14 +#include "nsSMILTimedElement.h" 1.15 +#include "nsAttrValueInlines.h" 1.16 +#include "nsSMILAnimationFunction.h" 1.17 +#include "nsSMILTimeValue.h" 1.18 +#include "nsSMILTimeValueSpec.h" 1.19 +#include "nsSMILInstanceTime.h" 1.20 +#include "nsSMILParserUtils.h" 1.21 +#include "nsSMILTimeContainer.h" 1.22 +#include "nsGkAtoms.h" 1.23 +#include "nsReadableUtils.h" 1.24 +#include "nsMathUtils.h" 1.25 +#include "nsThreadUtils.h" 1.26 +#include "nsIPresShell.h" 1.27 +#include "prdtoa.h" 1.28 +#include "plstr.h" 1.29 +#include "prtime.h" 1.30 +#include "nsString.h" 1.31 +#include "mozilla/AutoRestore.h" 1.32 +#include "nsCharSeparatedTokenizer.h" 1.33 +#include <algorithm> 1.34 + 1.35 +using namespace mozilla; 1.36 +using namespace mozilla::dom; 1.37 + 1.38 +//---------------------------------------------------------------------- 1.39 +// Helper class: InstanceTimeComparator 1.40 + 1.41 +// Upon inserting an instance time into one of our instance time lists we assign 1.42 +// it a serial number. This allows us to sort the instance times in such a way 1.43 +// that where we have several equal instance times, the ones added later will 1.44 +// sort later. This means that when we call UpdateCurrentInterval during the 1.45 +// waiting state we won't unnecessarily change the begin instance. 1.46 +// 1.47 +// The serial number also means that every instance time has an unambiguous 1.48 +// position in the array so we can use RemoveElementSorted and the like. 1.49 +bool 1.50 +nsSMILTimedElement::InstanceTimeComparator::Equals( 1.51 + const nsSMILInstanceTime* aElem1, 1.52 + const nsSMILInstanceTime* aElem2) const 1.53 +{ 1.54 + NS_ABORT_IF_FALSE(aElem1 && aElem2, 1.55 + "Trying to compare null instance time pointers"); 1.56 + NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), 1.57 + "Instance times have not been assigned serial numbers"); 1.58 + NS_ABORT_IF_FALSE(aElem1 == aElem2 || aElem1->Serial() != aElem2->Serial(), 1.59 + "Serial numbers are not unique"); 1.60 + 1.61 + return aElem1->Serial() == aElem2->Serial(); 1.62 +} 1.63 + 1.64 +bool 1.65 +nsSMILTimedElement::InstanceTimeComparator::LessThan( 1.66 + const nsSMILInstanceTime* aElem1, 1.67 + const nsSMILInstanceTime* aElem2) const 1.68 +{ 1.69 + NS_ABORT_IF_FALSE(aElem1 && aElem2, 1.70 + "Trying to compare null instance time pointers"); 1.71 + NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), 1.72 + "Instance times have not been assigned serial numbers"); 1.73 + 1.74 + int8_t cmp = aElem1->Time().CompareTo(aElem2->Time()); 1.75 + return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0; 1.76 +} 1.77 + 1.78 +//---------------------------------------------------------------------- 1.79 +// Helper class: AsyncTimeEventRunner 1.80 + 1.81 +namespace 1.82 +{ 1.83 + class AsyncTimeEventRunner : public nsRunnable 1.84 + { 1.85 + protected: 1.86 + nsRefPtr<nsIContent> mTarget; 1.87 + uint32_t mMsg; 1.88 + int32_t mDetail; 1.89 + 1.90 + public: 1.91 + AsyncTimeEventRunner(nsIContent* aTarget, uint32_t aMsg, int32_t aDetail) 1.92 + : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail) 1.93 + { 1.94 + } 1.95 + 1.96 + NS_IMETHOD Run() 1.97 + { 1.98 + InternalUIEvent event(true, mMsg); 1.99 + event.eventStructType = NS_SMIL_TIME_EVENT; 1.100 + event.detail = mDetail; 1.101 + 1.102 + nsPresContext* context = nullptr; 1.103 + nsIDocument* doc = mTarget->GetCurrentDoc(); 1.104 + if (doc) { 1.105 + nsCOMPtr<nsIPresShell> shell = doc->GetShell(); 1.106 + if (shell) { 1.107 + context = shell->GetPresContext(); 1.108 + } 1.109 + } 1.110 + 1.111 + return EventDispatcher::Dispatch(mTarget, context, &event); 1.112 + } 1.113 + }; 1.114 +} 1.115 + 1.116 +//---------------------------------------------------------------------- 1.117 +// Helper class: AutoIntervalUpdateBatcher 1.118 + 1.119 +// Stack-based helper class to set the mDeferIntervalUpdates flag on an 1.120 +// nsSMILTimedElement and perform the UpdateCurrentInterval when the object is 1.121 +// destroyed. 1.122 +// 1.123 +// If several of these objects are allocated on the stack, the update will not 1.124 +// be performed until the last object for a given nsSMILTimedElement is 1.125 +// destroyed. 1.126 +class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdateBatcher 1.127 +{ 1.128 +public: 1.129 + AutoIntervalUpdateBatcher(nsSMILTimedElement& aTimedElement) 1.130 + : mTimedElement(aTimedElement), 1.131 + mDidSetFlag(!aTimedElement.mDeferIntervalUpdates) 1.132 + { 1.133 + mTimedElement.mDeferIntervalUpdates = true; 1.134 + } 1.135 + 1.136 + ~AutoIntervalUpdateBatcher() 1.137 + { 1.138 + if (!mDidSetFlag) 1.139 + return; 1.140 + 1.141 + mTimedElement.mDeferIntervalUpdates = false; 1.142 + 1.143 + if (mTimedElement.mDoDeferredUpdate) { 1.144 + mTimedElement.mDoDeferredUpdate = false; 1.145 + mTimedElement.UpdateCurrentInterval(); 1.146 + } 1.147 + } 1.148 + 1.149 +private: 1.150 + nsSMILTimedElement& mTimedElement; 1.151 + bool mDidSetFlag; 1.152 +}; 1.153 + 1.154 +//---------------------------------------------------------------------- 1.155 +// Helper class: AutoIntervalUpdater 1.156 + 1.157 +// Stack-based helper class to call UpdateCurrentInterval when it is destroyed 1.158 +// which helps avoid bugs where we forget to call UpdateCurrentInterval in the 1.159 +// case of early returns (e.g. due to parse errors). 1.160 +// 1.161 +// This can be safely used in conjunction with AutoIntervalUpdateBatcher; any 1.162 +// calls to UpdateCurrentInterval made by this class will simply be deferred if 1.163 +// there is an AutoIntervalUpdateBatcher on the stack. 1.164 +class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater 1.165 +{ 1.166 +public: 1.167 + AutoIntervalUpdater(nsSMILTimedElement& aTimedElement) 1.168 + : mTimedElement(aTimedElement) { } 1.169 + 1.170 + ~AutoIntervalUpdater() 1.171 + { 1.172 + mTimedElement.UpdateCurrentInterval(); 1.173 + } 1.174 + 1.175 +private: 1.176 + nsSMILTimedElement& mTimedElement; 1.177 +}; 1.178 + 1.179 +//---------------------------------------------------------------------- 1.180 +// Templated helper functions 1.181 + 1.182 +// Selectively remove elements from an array of type 1.183 +// nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance. 1.184 +template <class TestFunctor> 1.185 +void 1.186 +nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray, 1.187 + TestFunctor& aTest) 1.188 +{ 1.189 + InstanceTimeList newArray; 1.190 + for (uint32_t i = 0; i < aArray.Length(); ++i) { 1.191 + nsSMILInstanceTime* item = aArray[i].get(); 1.192 + if (aTest(item, i)) { 1.193 + // As per bugs 665334 and 669225 we should be careful not to remove the 1.194 + // instance time that corresponds to the previous interval's end time. 1.195 + // 1.196 + // Most functors supplied here fulfil this condition by checking if the 1.197 + // instance time is marked as "ShouldPreserve" and if so, not deleting it. 1.198 + // 1.199 + // However, when filtering instance times, we sometimes need to drop even 1.200 + // instance times marked as "ShouldPreserve". In that case we take special 1.201 + // care not to delete the end instance time of the previous interval. 1.202 + NS_ABORT_IF_FALSE(!GetPreviousInterval() || 1.203 + item != GetPreviousInterval()->End(), 1.204 + "Removing end instance time of previous interval"); 1.205 + item->Unlink(); 1.206 + } else { 1.207 + newArray.AppendElement(item); 1.208 + } 1.209 + } 1.210 + aArray.Clear(); 1.211 + aArray.SwapElements(newArray); 1.212 +} 1.213 + 1.214 +//---------------------------------------------------------------------- 1.215 +// Static members 1.216 + 1.217 +nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = { 1.218 + {"remove", FILL_REMOVE}, 1.219 + {"freeze", FILL_FREEZE}, 1.220 + {nullptr, 0} 1.221 +}; 1.222 + 1.223 +nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = { 1.224 + {"always", RESTART_ALWAYS}, 1.225 + {"whenNotActive", RESTART_WHENNOTACTIVE}, 1.226 + {"never", RESTART_NEVER}, 1.227 + {nullptr, 0} 1.228 +}; 1.229 + 1.230 +const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(INT64_MAX, false); 1.231 + 1.232 +// The thresholds at which point we start filtering intervals and instance times 1.233 +// indiscriminately. 1.234 +// See FilterIntervals and FilterInstanceTimes. 1.235 +const uint8_t nsSMILTimedElement::sMaxNumIntervals = 20; 1.236 +const uint8_t nsSMILTimedElement::sMaxNumInstanceTimes = 100; 1.237 + 1.238 +// Detect if we arrive in some sort of undetected recursive syncbase dependency 1.239 +// relationship 1.240 +const uint8_t nsSMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20; 1.241 + 1.242 +//---------------------------------------------------------------------- 1.243 +// Ctor, dtor 1.244 + 1.245 +nsSMILTimedElement::nsSMILTimedElement() 1.246 +: 1.247 + mAnimationElement(nullptr), 1.248 + mFillMode(FILL_REMOVE), 1.249 + mRestartMode(RESTART_ALWAYS), 1.250 + mInstanceSerialIndex(0), 1.251 + mClient(nullptr), 1.252 + mCurrentInterval(nullptr), 1.253 + mCurrentRepeatIteration(0), 1.254 + mPrevRegisteredMilestone(sMaxMilestone), 1.255 + mElementState(STATE_STARTUP), 1.256 + mSeekState(SEEK_NOT_SEEKING), 1.257 + mDeferIntervalUpdates(false), 1.258 + mDoDeferredUpdate(false), 1.259 + mDeleteCount(0), 1.260 + mUpdateIntervalRecursionDepth(0) 1.261 +{ 1.262 + mSimpleDur.SetIndefinite(); 1.263 + mMin.SetMillis(0L); 1.264 + mMax.SetIndefinite(); 1.265 +} 1.266 + 1.267 +nsSMILTimedElement::~nsSMILTimedElement() 1.268 +{ 1.269 + // Unlink all instance times from dependent intervals 1.270 + for (uint32_t i = 0; i < mBeginInstances.Length(); ++i) { 1.271 + mBeginInstances[i]->Unlink(); 1.272 + } 1.273 + mBeginInstances.Clear(); 1.274 + for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { 1.275 + mEndInstances[i]->Unlink(); 1.276 + } 1.277 + mEndInstances.Clear(); 1.278 + 1.279 + // Notify anyone listening to our intervals that they're gone 1.280 + // (We shouldn't get any callbacks from this because all our instance times 1.281 + // are now disassociated with any intervals) 1.282 + ClearIntervals(); 1.283 + 1.284 + // The following assertions are important in their own right (for checking 1.285 + // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers 1.286 + // to class so if they fail there's the possibility we might have dangling 1.287 + // pointers. 1.288 + NS_ABORT_IF_FALSE(!mDeferIntervalUpdates, 1.289 + "Interval updates should no longer be blocked when an nsSMILTimedElement " 1.290 + "disappears"); 1.291 + NS_ABORT_IF_FALSE(!mDoDeferredUpdate, 1.292 + "There should no longer be any pending updates when an " 1.293 + "nsSMILTimedElement disappears"); 1.294 +} 1.295 + 1.296 +void 1.297 +nsSMILTimedElement::SetAnimationElement(SVGAnimationElement* aElement) 1.298 +{ 1.299 + NS_ABORT_IF_FALSE(aElement, "NULL owner element"); 1.300 + NS_ABORT_IF_FALSE(!mAnimationElement, "Re-setting owner"); 1.301 + mAnimationElement = aElement; 1.302 +} 1.303 + 1.304 +nsSMILTimeContainer* 1.305 +nsSMILTimedElement::GetTimeContainer() 1.306 +{ 1.307 + return mAnimationElement ? mAnimationElement->GetTimeContainer() : nullptr; 1.308 +} 1.309 + 1.310 +dom::Element* 1.311 +nsSMILTimedElement::GetTargetElement() 1.312 +{ 1.313 + return mAnimationElement ? 1.314 + mAnimationElement->GetTargetElementContent() : 1.315 + nullptr; 1.316 +} 1.317 + 1.318 +//---------------------------------------------------------------------- 1.319 +// nsIDOMElementTimeControl methods 1.320 +// 1.321 +// The definition of the ElementTimeControl interface differs between SMIL 1.322 +// Animation and SVG 1.1. In SMIL Animation all methods have a void return 1.323 +// type and the new instance time is simply added to the list and restart 1.324 +// semantics are applied as with any other instance time. In the SVG definition 1.325 +// the methods return a bool depending on the restart mode. 1.326 +// 1.327 +// This inconsistency has now been addressed by an erratum in SVG 1.1: 1.328 +// 1.329 +// http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface 1.330 +// 1.331 +// which favours the definition in SMIL, i.e. instance times are just added 1.332 +// without first checking the restart mode. 1.333 + 1.334 +nsresult 1.335 +nsSMILTimedElement::BeginElementAt(double aOffsetSeconds) 1.336 +{ 1.337 + nsSMILTimeContainer* container = GetTimeContainer(); 1.338 + if (!container) 1.339 + return NS_ERROR_FAILURE; 1.340 + 1.341 + nsSMILTime currentTime = container->GetCurrentTime(); 1.342 + return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, true); 1.343 +} 1.344 + 1.345 +nsresult 1.346 +nsSMILTimedElement::EndElementAt(double aOffsetSeconds) 1.347 +{ 1.348 + nsSMILTimeContainer* container = GetTimeContainer(); 1.349 + if (!container) 1.350 + return NS_ERROR_FAILURE; 1.351 + 1.352 + nsSMILTime currentTime = container->GetCurrentTime(); 1.353 + return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, false); 1.354 +} 1.355 + 1.356 +//---------------------------------------------------------------------- 1.357 +// nsSVGAnimationElement methods 1.358 + 1.359 +nsSMILTimeValue 1.360 +nsSMILTimedElement::GetStartTime() const 1.361 +{ 1.362 + return mElementState == STATE_WAITING || mElementState == STATE_ACTIVE 1.363 + ? mCurrentInterval->Begin()->Time() 1.364 + : nsSMILTimeValue(); 1.365 +} 1.366 + 1.367 +//---------------------------------------------------------------------- 1.368 +// Hyperlinking support 1.369 + 1.370 +nsSMILTimeValue 1.371 +nsSMILTimedElement::GetHyperlinkTime() const 1.372 +{ 1.373 + nsSMILTimeValue hyperlinkTime; // Default ctor creates unresolved time 1.374 + 1.375 + if (mElementState == STATE_ACTIVE) { 1.376 + hyperlinkTime = mCurrentInterval->Begin()->Time(); 1.377 + } else if (!mBeginInstances.IsEmpty()) { 1.378 + hyperlinkTime = mBeginInstances[0]->Time(); 1.379 + } 1.380 + 1.381 + return hyperlinkTime; 1.382 +} 1.383 + 1.384 +//---------------------------------------------------------------------- 1.385 +// nsSMILTimedElement 1.386 + 1.387 +void 1.388 +nsSMILTimedElement::AddInstanceTime(nsSMILInstanceTime* aInstanceTime, 1.389 + bool aIsBegin) 1.390 +{ 1.391 + NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to add null instance time"); 1.392 + 1.393 + // Event-sensitivity: If an element is not active (but the parent time 1.394 + // container is), then events are only handled for begin specifications. 1.395 + if (mElementState != STATE_ACTIVE && !aIsBegin && 1.396 + aInstanceTime->IsDynamic()) 1.397 + { 1.398 + // No need to call Unlink here--dynamic instance times shouldn't be linked 1.399 + // to anything that's going to miss them 1.400 + NS_ABORT_IF_FALSE(!aInstanceTime->GetBaseInterval(), 1.401 + "Dynamic instance time has a base interval--we probably need to unlink" 1.402 + " it if we're not going to use it"); 1.403 + return; 1.404 + } 1.405 + 1.406 + aInstanceTime->SetSerial(++mInstanceSerialIndex); 1.407 + InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; 1.408 + nsRefPtr<nsSMILInstanceTime>* inserted = 1.409 + instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator()); 1.410 + if (!inserted) { 1.411 + NS_WARNING("Insufficient memory to insert instance time"); 1.412 + return; 1.413 + } 1.414 + 1.415 + UpdateCurrentInterval(); 1.416 +} 1.417 + 1.418 +void 1.419 +nsSMILTimedElement::UpdateInstanceTime(nsSMILInstanceTime* aInstanceTime, 1.420 + nsSMILTimeValue& aUpdatedTime, 1.421 + bool aIsBegin) 1.422 +{ 1.423 + NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to update null instance time"); 1.424 + 1.425 + // The reason we update the time here and not in the nsSMILTimeValueSpec is 1.426 + // that it means we *could* re-sort more efficiently by doing a sorted remove 1.427 + // and insert but currently this doesn't seem to be necessary given how 1.428 + // infrequently we get these change notices. 1.429 + aInstanceTime->DependentUpdate(aUpdatedTime); 1.430 + InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; 1.431 + instanceList.Sort(InstanceTimeComparator()); 1.432 + 1.433 + // Generally speaking, UpdateCurrentInterval makes changes to the current 1.434 + // interval and sends changes notices itself. However, in this case because 1.435 + // instance times are shared between the instance time list and the intervals 1.436 + // we are effectively changing the current interval outside 1.437 + // UpdateCurrentInterval so we need to explicitly signal that we've made 1.438 + // a change. 1.439 + // 1.440 + // This wouldn't be necessary if we cloned instance times on adding them to 1.441 + // the current interval but this introduces other complications (particularly 1.442 + // detecting which instance time is being used to define the begin of the 1.443 + // current interval when doing a Reset). 1.444 + bool changedCurrentInterval = mCurrentInterval && 1.445 + (mCurrentInterval->Begin() == aInstanceTime || 1.446 + mCurrentInterval->End() == aInstanceTime); 1.447 + 1.448 + UpdateCurrentInterval(changedCurrentInterval); 1.449 +} 1.450 + 1.451 +void 1.452 +nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime, 1.453 + bool aIsBegin) 1.454 +{ 1.455 + NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to remove null instance time"); 1.456 + 1.457 + // If the instance time should be kept (because it is or was the fixed end 1.458 + // point of an interval) then just disassociate it from the creator. 1.459 + if (aInstanceTime->ShouldPreserve()) { 1.460 + aInstanceTime->Unlink(); 1.461 + return; 1.462 + } 1.463 + 1.464 + InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; 1.465 + mozilla::DebugOnly<bool> found = 1.466 + instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator()); 1.467 + NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete"); 1.468 + 1.469 + UpdateCurrentInterval(); 1.470 +} 1.471 + 1.472 +namespace 1.473 +{ 1.474 + class MOZ_STACK_CLASS RemoveByCreator 1.475 + { 1.476 + public: 1.477 + RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator) 1.478 + { } 1.479 + 1.480 + bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) 1.481 + { 1.482 + if (aInstanceTime->GetCreator() != mCreator) 1.483 + return false; 1.484 + 1.485 + // If the instance time should be kept (because it is or was the fixed end 1.486 + // point of an interval) then just disassociate it from the creator. 1.487 + if (aInstanceTime->ShouldPreserve()) { 1.488 + aInstanceTime->Unlink(); 1.489 + return false; 1.490 + } 1.491 + 1.492 + return true; 1.493 + } 1.494 + 1.495 + private: 1.496 + const nsSMILTimeValueSpec* mCreator; 1.497 + }; 1.498 +} 1.499 + 1.500 +void 1.501 +nsSMILTimedElement::RemoveInstanceTimesForCreator( 1.502 + const nsSMILTimeValueSpec* aCreator, bool aIsBegin) 1.503 +{ 1.504 + NS_ABORT_IF_FALSE(aCreator, "Creator not set"); 1.505 + 1.506 + InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; 1.507 + RemoveByCreator removeByCreator(aCreator); 1.508 + RemoveInstanceTimes(instances, removeByCreator); 1.509 + 1.510 + UpdateCurrentInterval(); 1.511 +} 1.512 + 1.513 +void 1.514 +nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient) 1.515 +{ 1.516 + // 1.517 + // No need to check for nullptr. A nullptr parameter simply means to remove the 1.518 + // previous client which we do by setting to nullptr anyway. 1.519 + // 1.520 + 1.521 + mClient = aClient; 1.522 +} 1.523 + 1.524 +void 1.525 +nsSMILTimedElement::SampleAt(nsSMILTime aContainerTime) 1.526 +{ 1.527 + // Milestones are cleared before a sample 1.528 + mPrevRegisteredMilestone = sMaxMilestone; 1.529 + 1.530 + DoSampleAt(aContainerTime, false); 1.531 +} 1.532 + 1.533 +void 1.534 +nsSMILTimedElement::SampleEndAt(nsSMILTime aContainerTime) 1.535 +{ 1.536 + // Milestones are cleared before a sample 1.537 + mPrevRegisteredMilestone = sMaxMilestone; 1.538 + 1.539 + // If the current interval changes, we don't bother trying to remove any old 1.540 + // milestones we'd registered. So it's possible to get a call here to end an 1.541 + // interval at a time that no longer reflects the end of the current interval. 1.542 + // 1.543 + // For now we just check that we're actually in an interval but note that the 1.544 + // initial sample we use to initialise the model is an end sample. This is 1.545 + // because we want to resolve all the instance times before committing to an 1.546 + // initial interval. Therefore an end sample from the startup state is also 1.547 + // acceptable. 1.548 + if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) { 1.549 + DoSampleAt(aContainerTime, true); // End sample 1.550 + } else { 1.551 + // Even if this was an unnecessary milestone sample we want to be sure that 1.552 + // our next real milestone is registered. 1.553 + RegisterMilestone(); 1.554 + } 1.555 +} 1.556 + 1.557 +void 1.558 +nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly) 1.559 +{ 1.560 + NS_ABORT_IF_FALSE(mAnimationElement, 1.561 + "Got sample before being registered with an animation element"); 1.562 + NS_ABORT_IF_FALSE(GetTimeContainer(), 1.563 + "Got sample without being registered with a time container"); 1.564 + 1.565 + // This could probably happen if we later implement externalResourcesRequired 1.566 + // (bug 277955) and whilst waiting for those resources (and the animation to 1.567 + // start) we transfer a node from another document fragment that has already 1.568 + // started. In such a case we might receive milestone samples registered with 1.569 + // the already active container. 1.570 + if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) 1.571 + return; 1.572 + 1.573 + // We use an end-sample to start animation since an end-sample lets us 1.574 + // tentatively create an interval without committing to it (by transitioning 1.575 + // to the ACTIVE state) and this is necessary because we might have 1.576 + // dependencies on other animations that are yet to start. After these 1.577 + // other animations start, it may be necessary to revise our initial interval. 1.578 + // 1.579 + // However, sometimes instead of an end-sample we can get a regular sample 1.580 + // during STARTUP state. This can happen, for example, if we register 1.581 + // a milestone before time t=0 and are then re-bound to the tree (which sends 1.582 + // us back to the STARTUP state). In such a case we should just ignore the 1.583 + // sample and wait for our real initial sample which will be an end-sample. 1.584 + if (mElementState == STATE_STARTUP && !aEndOnly) 1.585 + return; 1.586 + 1.587 + bool finishedSeek = false; 1.588 + if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) { 1.589 + mSeekState = mElementState == STATE_ACTIVE ? 1.590 + SEEK_FORWARD_FROM_ACTIVE : 1.591 + SEEK_FORWARD_FROM_INACTIVE; 1.592 + } else if (mSeekState != SEEK_NOT_SEEKING && 1.593 + !GetTimeContainer()->IsSeeking()) { 1.594 + finishedSeek = true; 1.595 + } 1.596 + 1.597 + bool stateChanged; 1.598 + nsSMILTimeValue sampleTime(aContainerTime); 1.599 + 1.600 + do { 1.601 +#ifdef DEBUG 1.602 + // Check invariant 1.603 + if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) { 1.604 + NS_ABORT_IF_FALSE(!mCurrentInterval, 1.605 + "Shouldn't have current interval in startup or postactive states"); 1.606 + } else { 1.607 + NS_ABORT_IF_FALSE(mCurrentInterval, 1.608 + "Should have current interval in waiting and active states"); 1.609 + } 1.610 +#endif 1.611 + 1.612 + stateChanged = false; 1.613 + 1.614 + switch (mElementState) 1.615 + { 1.616 + case STATE_STARTUP: 1.617 + { 1.618 + nsSMILInterval firstInterval; 1.619 + mElementState = GetNextInterval(nullptr, nullptr, nullptr, firstInterval) 1.620 + ? STATE_WAITING 1.621 + : STATE_POSTACTIVE; 1.622 + stateChanged = true; 1.623 + if (mElementState == STATE_WAITING) { 1.624 + mCurrentInterval = new nsSMILInterval(firstInterval); 1.625 + NotifyNewInterval(); 1.626 + } 1.627 + } 1.628 + break; 1.629 + 1.630 + case STATE_WAITING: 1.631 + { 1.632 + if (mCurrentInterval->Begin()->Time() <= sampleTime) { 1.633 + mElementState = STATE_ACTIVE; 1.634 + mCurrentInterval->FixBegin(); 1.635 + if (mClient) { 1.636 + mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis()); 1.637 + } 1.638 + if (mSeekState == SEEK_NOT_SEEKING) { 1.639 + FireTimeEventAsync(NS_SMIL_BEGIN, 0); 1.640 + } 1.641 + if (HasPlayed()) { 1.642 + Reset(); // Apply restart behaviour 1.643 + // The call to Reset() may mean that the end point of our current 1.644 + // interval should be changed and so we should update the interval 1.645 + // now. However, calling UpdateCurrentInterval could result in the 1.646 + // interval getting deleted (perhaps through some web of syncbase 1.647 + // dependencies) therefore we make updating the interval the last 1.648 + // thing we do. There is no guarantee that mCurrentInterval is set 1.649 + // after this. 1.650 + UpdateCurrentInterval(); 1.651 + } 1.652 + stateChanged = true; 1.653 + } 1.654 + } 1.655 + break; 1.656 + 1.657 + case STATE_ACTIVE: 1.658 + { 1.659 + // Ending early will change the interval but we don't notify dependents 1.660 + // of the change until we have closed off the current interval (since we 1.661 + // don't want dependencies to un-end our early end). 1.662 + bool didApplyEarlyEnd = ApplyEarlyEnd(sampleTime); 1.663 + 1.664 + if (mCurrentInterval->End()->Time() <= sampleTime) { 1.665 + nsSMILInterval newInterval; 1.666 + mElementState = 1.667 + GetNextInterval(mCurrentInterval, nullptr, nullptr, newInterval) 1.668 + ? STATE_WAITING 1.669 + : STATE_POSTACTIVE; 1.670 + if (mClient) { 1.671 + mClient->Inactivate(mFillMode == FILL_FREEZE); 1.672 + } 1.673 + mCurrentInterval->FixEnd(); 1.674 + if (mSeekState == SEEK_NOT_SEEKING) { 1.675 + FireTimeEventAsync(NS_SMIL_END, 0); 1.676 + } 1.677 + mCurrentRepeatIteration = 0; 1.678 + mOldIntervals.AppendElement(mCurrentInterval.forget()); 1.679 + SampleFillValue(); 1.680 + if (mElementState == STATE_WAITING) { 1.681 + mCurrentInterval = new nsSMILInterval(newInterval); 1.682 + } 1.683 + // We are now in a consistent state to dispatch notifications 1.684 + if (didApplyEarlyEnd) { 1.685 + NotifyChangedInterval( 1.686 + mOldIntervals[mOldIntervals.Length() - 1], false, true); 1.687 + } 1.688 + if (mElementState == STATE_WAITING) { 1.689 + NotifyNewInterval(); 1.690 + } 1.691 + FilterHistory(); 1.692 + stateChanged = true; 1.693 + } else { 1.694 + NS_ABORT_IF_FALSE(!didApplyEarlyEnd, 1.695 + "We got an early end, but didn't end"); 1.696 + nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis(); 1.697 + NS_ASSERTION(aContainerTime >= beginTime, 1.698 + "Sample time should not precede current interval"); 1.699 + nsSMILTime activeTime = aContainerTime - beginTime; 1.700 + 1.701 + // The 'min' attribute can cause the active interval to be longer than 1.702 + // the 'repeating interval'. 1.703 + // In that extended period we apply the fill mode. 1.704 + if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) { 1.705 + if (mClient && mClient->IsActive()) { 1.706 + mClient->Inactivate(mFillMode == FILL_FREEZE); 1.707 + } 1.708 + SampleFillValue(); 1.709 + } else { 1.710 + SampleSimpleTime(activeTime); 1.711 + 1.712 + // We register our repeat times as milestones (except when we're 1.713 + // seeking) so we should get a sample at exactly the time we repeat. 1.714 + // (And even when we are seeking we want to update 1.715 + // mCurrentRepeatIteration so we do that first before testing the 1.716 + // seek state.) 1.717 + uint32_t prevRepeatIteration = mCurrentRepeatIteration; 1.718 + if ( 1.719 + ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 && 1.720 + mCurrentRepeatIteration != prevRepeatIteration && 1.721 + mCurrentRepeatIteration && 1.722 + mSeekState == SEEK_NOT_SEEKING) { 1.723 + FireTimeEventAsync(NS_SMIL_REPEAT, 1.724 + static_cast<int32_t>(mCurrentRepeatIteration)); 1.725 + } 1.726 + } 1.727 + } 1.728 + } 1.729 + break; 1.730 + 1.731 + case STATE_POSTACTIVE: 1.732 + break; 1.733 + } 1.734 + 1.735 + // Generally we continue driving the state machine so long as we have changed 1.736 + // state. However, for end samples we only drive the state machine as far as 1.737 + // the waiting or postactive state because we don't want to commit to any new 1.738 + // interval (by transitioning to the active state) until all the end samples 1.739 + // have finished and we then have complete information about the available 1.740 + // instance times upon which to base our next interval. 1.741 + } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING && 1.742 + mElementState != STATE_POSTACTIVE))); 1.743 + 1.744 + if (finishedSeek) { 1.745 + DoPostSeek(); 1.746 + } 1.747 + RegisterMilestone(); 1.748 +} 1.749 + 1.750 +void 1.751 +nsSMILTimedElement::HandleContainerTimeChange() 1.752 +{ 1.753 + // In future we could possibly introduce a separate change notice for time 1.754 + // container changes and only notify those dependents who live in other time 1.755 + // containers. For now we don't bother because when we re-resolve the time in 1.756 + // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we 1.757 + // won't go any further. 1.758 + if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) { 1.759 + NotifyChangedInterval(mCurrentInterval, false, false); 1.760 + } 1.761 +} 1.762 + 1.763 +namespace 1.764 +{ 1.765 + bool 1.766 + RemoveNonDynamic(nsSMILInstanceTime* aInstanceTime) 1.767 + { 1.768 + // Generally dynamically-generated instance times (DOM calls, event-based 1.769 + // times) are not associated with their creator nsSMILTimeValueSpec since 1.770 + // they may outlive them. 1.771 + NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() || 1.772 + !aInstanceTime->GetCreator(), 1.773 + "Dynamic instance time should be unlinked from its creator"); 1.774 + return !aInstanceTime->IsDynamic() && !aInstanceTime->ShouldPreserve(); 1.775 + } 1.776 +} 1.777 + 1.778 +void 1.779 +nsSMILTimedElement::Rewind() 1.780 +{ 1.781 + NS_ABORT_IF_FALSE(mAnimationElement, 1.782 + "Got rewind request before being attached to an animation element"); 1.783 + 1.784 + // It's possible to get a rewind request whilst we're already in the middle of 1.785 + // a backwards seek. This can happen when we're performing tree surgery and 1.786 + // seeking containers at the same time because we can end up requesting 1.787 + // a local rewind on an element after binding it to a new container and then 1.788 + // performing a rewind on that container as a whole without sampling in 1.789 + // between. 1.790 + // 1.791 + // However, it should currently be impossible to get a rewind in the middle of 1.792 + // a forwards seek since forwards seeks are detected and processed within the 1.793 + // same (re)sample. 1.794 + if (mSeekState == SEEK_NOT_SEEKING) { 1.795 + mSeekState = mElementState == STATE_ACTIVE ? 1.796 + SEEK_BACKWARD_FROM_ACTIVE : 1.797 + SEEK_BACKWARD_FROM_INACTIVE; 1.798 + } 1.799 + NS_ABORT_IF_FALSE(mSeekState == SEEK_BACKWARD_FROM_INACTIVE || 1.800 + mSeekState == SEEK_BACKWARD_FROM_ACTIVE, 1.801 + "Rewind in the middle of a forwards seek?"); 1.802 + 1.803 + // Putting us in the startup state will ensure we skip doing any interval 1.804 + // updates 1.805 + mElementState = STATE_STARTUP; 1.806 + ClearIntervals(); 1.807 + 1.808 + UnsetBeginSpec(RemoveNonDynamic); 1.809 + UnsetEndSpec(RemoveNonDynamic); 1.810 + 1.811 + if (mClient) { 1.812 + mClient->Inactivate(false); 1.813 + } 1.814 + 1.815 + if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) { 1.816 + nsAutoString attValue; 1.817 + mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue); 1.818 + SetBeginSpec(attValue, mAnimationElement, RemoveNonDynamic); 1.819 + } 1.820 + 1.821 + if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) { 1.822 + nsAutoString attValue; 1.823 + mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue); 1.824 + SetEndSpec(attValue, mAnimationElement, RemoveNonDynamic); 1.825 + } 1.826 + 1.827 + mPrevRegisteredMilestone = sMaxMilestone; 1.828 + RegisterMilestone(); 1.829 + NS_ABORT_IF_FALSE(!mCurrentInterval, 1.830 + "Current interval is set at end of rewind"); 1.831 +} 1.832 + 1.833 +namespace 1.834 +{ 1.835 + bool 1.836 + RemoveNonDOM(nsSMILInstanceTime* aInstanceTime) 1.837 + { 1.838 + return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve(); 1.839 + } 1.840 +} 1.841 + 1.842 +bool 1.843 +nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue, 1.844 + nsAttrValue& aResult, 1.845 + Element* aContextNode, 1.846 + nsresult* aParseResult) 1.847 +{ 1.848 + bool foundMatch = true; 1.849 + nsresult parseResult = NS_OK; 1.850 + 1.851 + if (aAttribute == nsGkAtoms::begin) { 1.852 + parseResult = SetBeginSpec(aValue, aContextNode, RemoveNonDOM); 1.853 + } else if (aAttribute == nsGkAtoms::dur) { 1.854 + parseResult = SetSimpleDuration(aValue); 1.855 + } else if (aAttribute == nsGkAtoms::end) { 1.856 + parseResult = SetEndSpec(aValue, aContextNode, RemoveNonDOM); 1.857 + } else if (aAttribute == nsGkAtoms::fill) { 1.858 + parseResult = SetFillMode(aValue); 1.859 + } else if (aAttribute == nsGkAtoms::max) { 1.860 + parseResult = SetMax(aValue); 1.861 + } else if (aAttribute == nsGkAtoms::min) { 1.862 + parseResult = SetMin(aValue); 1.863 + } else if (aAttribute == nsGkAtoms::repeatCount) { 1.864 + parseResult = SetRepeatCount(aValue); 1.865 + } else if (aAttribute == nsGkAtoms::repeatDur) { 1.866 + parseResult = SetRepeatDur(aValue); 1.867 + } else if (aAttribute == nsGkAtoms::restart) { 1.868 + parseResult = SetRestart(aValue); 1.869 + } else { 1.870 + foundMatch = false; 1.871 + } 1.872 + 1.873 + if (foundMatch) { 1.874 + aResult.SetTo(aValue); 1.875 + if (aParseResult) { 1.876 + *aParseResult = parseResult; 1.877 + } 1.878 + } 1.879 + 1.880 + return foundMatch; 1.881 +} 1.882 + 1.883 +bool 1.884 +nsSMILTimedElement::UnsetAttr(nsIAtom* aAttribute) 1.885 +{ 1.886 + bool foundMatch = true; 1.887 + 1.888 + if (aAttribute == nsGkAtoms::begin) { 1.889 + UnsetBeginSpec(RemoveNonDOM); 1.890 + } else if (aAttribute == nsGkAtoms::dur) { 1.891 + UnsetSimpleDuration(); 1.892 + } else if (aAttribute == nsGkAtoms::end) { 1.893 + UnsetEndSpec(RemoveNonDOM); 1.894 + } else if (aAttribute == nsGkAtoms::fill) { 1.895 + UnsetFillMode(); 1.896 + } else if (aAttribute == nsGkAtoms::max) { 1.897 + UnsetMax(); 1.898 + } else if (aAttribute == nsGkAtoms::min) { 1.899 + UnsetMin(); 1.900 + } else if (aAttribute == nsGkAtoms::repeatCount) { 1.901 + UnsetRepeatCount(); 1.902 + } else if (aAttribute == nsGkAtoms::repeatDur) { 1.903 + UnsetRepeatDur(); 1.904 + } else if (aAttribute == nsGkAtoms::restart) { 1.905 + UnsetRestart(); 1.906 + } else { 1.907 + foundMatch = false; 1.908 + } 1.909 + 1.910 + return foundMatch; 1.911 +} 1.912 + 1.913 +//---------------------------------------------------------------------- 1.914 +// Setters and unsetters 1.915 + 1.916 +nsresult 1.917 +nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec, 1.918 + Element* aContextNode, 1.919 + RemovalTestFunction aRemove) 1.920 +{ 1.921 + return SetBeginOrEndSpec(aBeginSpec, aContextNode, true /*isBegin*/, 1.922 + aRemove); 1.923 +} 1.924 + 1.925 +void 1.926 +nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove) 1.927 +{ 1.928 + ClearSpecs(mBeginSpecs, mBeginInstances, aRemove); 1.929 + UpdateCurrentInterval(); 1.930 +} 1.931 + 1.932 +nsresult 1.933 +nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec, 1.934 + Element* aContextNode, 1.935 + RemovalTestFunction aRemove) 1.936 +{ 1.937 + return SetBeginOrEndSpec(aEndSpec, aContextNode, false /*!isBegin*/, 1.938 + aRemove); 1.939 +} 1.940 + 1.941 +void 1.942 +nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove) 1.943 +{ 1.944 + ClearSpecs(mEndSpecs, mEndInstances, aRemove); 1.945 + UpdateCurrentInterval(); 1.946 +} 1.947 + 1.948 +nsresult 1.949 +nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec) 1.950 +{ 1.951 + // Update the current interval before returning 1.952 + AutoIntervalUpdater updater(*this); 1.953 + 1.954 + nsSMILTimeValue duration; 1.955 + const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec); 1.956 + 1.957 + // SVG-specific: "For SVG's animation elements, if "media" is specified, the 1.958 + // attribute will be ignored." (SVG 1.1, section 19.2.6) 1.959 + if (dur.EqualsLiteral("media") || dur.EqualsLiteral("indefinite")) { 1.960 + duration.SetIndefinite(); 1.961 + } else { 1.962 + if (!nsSMILParserUtils::ParseClockValue(dur, &duration) || 1.963 + duration.GetMillis() == 0L) { 1.964 + mSimpleDur.SetIndefinite(); 1.965 + return NS_ERROR_FAILURE; 1.966 + } 1.967 + } 1.968 + // mSimpleDur should never be unresolved. ParseClockValue will either set 1.969 + // duration to resolved or will return false. 1.970 + NS_ABORT_IF_FALSE(duration.IsResolved(), 1.971 + "Setting unresolved simple duration"); 1.972 + 1.973 + mSimpleDur = duration; 1.974 + 1.975 + return NS_OK; 1.976 +} 1.977 + 1.978 +void 1.979 +nsSMILTimedElement::UnsetSimpleDuration() 1.980 +{ 1.981 + mSimpleDur.SetIndefinite(); 1.982 + UpdateCurrentInterval(); 1.983 +} 1.984 + 1.985 +nsresult 1.986 +nsSMILTimedElement::SetMin(const nsAString& aMinSpec) 1.987 +{ 1.988 + // Update the current interval before returning 1.989 + AutoIntervalUpdater updater(*this); 1.990 + 1.991 + nsSMILTimeValue duration; 1.992 + const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec); 1.993 + 1.994 + if (min.EqualsLiteral("media")) { 1.995 + duration.SetMillis(0L); 1.996 + } else { 1.997 + if (!nsSMILParserUtils::ParseClockValue(min, &duration)) { 1.998 + mMin.SetMillis(0L); 1.999 + return NS_ERROR_FAILURE; 1.1000 + } 1.1001 + } 1.1002 + 1.1003 + NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration"); 1.1004 + 1.1005 + mMin = duration; 1.1006 + 1.1007 + return NS_OK; 1.1008 +} 1.1009 + 1.1010 +void 1.1011 +nsSMILTimedElement::UnsetMin() 1.1012 +{ 1.1013 + mMin.SetMillis(0L); 1.1014 + UpdateCurrentInterval(); 1.1015 +} 1.1016 + 1.1017 +nsresult 1.1018 +nsSMILTimedElement::SetMax(const nsAString& aMaxSpec) 1.1019 +{ 1.1020 + // Update the current interval before returning 1.1021 + AutoIntervalUpdater updater(*this); 1.1022 + 1.1023 + nsSMILTimeValue duration; 1.1024 + const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec); 1.1025 + 1.1026 + if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) { 1.1027 + duration.SetIndefinite(); 1.1028 + } else { 1.1029 + if (!nsSMILParserUtils::ParseClockValue(max, &duration) || 1.1030 + duration.GetMillis() == 0L) { 1.1031 + mMax.SetIndefinite(); 1.1032 + return NS_ERROR_FAILURE; 1.1033 + } 1.1034 + NS_ABORT_IF_FALSE(duration.GetMillis() > 0L, "Invalid duration"); 1.1035 + } 1.1036 + 1.1037 + mMax = duration; 1.1038 + 1.1039 + return NS_OK; 1.1040 +} 1.1041 + 1.1042 +void 1.1043 +nsSMILTimedElement::UnsetMax() 1.1044 +{ 1.1045 + mMax.SetIndefinite(); 1.1046 + UpdateCurrentInterval(); 1.1047 +} 1.1048 + 1.1049 +nsresult 1.1050 +nsSMILTimedElement::SetRestart(const nsAString& aRestartSpec) 1.1051 +{ 1.1052 + nsAttrValue temp; 1.1053 + bool parseResult 1.1054 + = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true); 1.1055 + mRestartMode = parseResult 1.1056 + ? nsSMILRestartMode(temp.GetEnumValue()) 1.1057 + : RESTART_ALWAYS; 1.1058 + UpdateCurrentInterval(); 1.1059 + return parseResult ? NS_OK : NS_ERROR_FAILURE; 1.1060 +} 1.1061 + 1.1062 +void 1.1063 +nsSMILTimedElement::UnsetRestart() 1.1064 +{ 1.1065 + mRestartMode = RESTART_ALWAYS; 1.1066 + UpdateCurrentInterval(); 1.1067 +} 1.1068 + 1.1069 +nsresult 1.1070 +nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec) 1.1071 +{ 1.1072 + // Update the current interval before returning 1.1073 + AutoIntervalUpdater updater(*this); 1.1074 + 1.1075 + nsSMILRepeatCount newRepeatCount; 1.1076 + 1.1077 + if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) { 1.1078 + mRepeatCount = newRepeatCount; 1.1079 + return NS_OK; 1.1080 + } 1.1081 + mRepeatCount.Unset(); 1.1082 + return NS_ERROR_FAILURE; 1.1083 +} 1.1084 + 1.1085 +void 1.1086 +nsSMILTimedElement::UnsetRepeatCount() 1.1087 +{ 1.1088 + mRepeatCount.Unset(); 1.1089 + UpdateCurrentInterval(); 1.1090 +} 1.1091 + 1.1092 +nsresult 1.1093 +nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec) 1.1094 +{ 1.1095 + // Update the current interval before returning 1.1096 + AutoIntervalUpdater updater(*this); 1.1097 + 1.1098 + nsSMILTimeValue duration; 1.1099 + 1.1100 + const nsAString& repeatDur = 1.1101 + nsSMILParserUtils::TrimWhitespace(aRepeatDurSpec); 1.1102 + 1.1103 + if (repeatDur.EqualsLiteral("indefinite")) { 1.1104 + duration.SetIndefinite(); 1.1105 + } else { 1.1106 + if (!nsSMILParserUtils::ParseClockValue(repeatDur, &duration)) { 1.1107 + mRepeatDur.SetUnresolved(); 1.1108 + return NS_ERROR_FAILURE; 1.1109 + } 1.1110 + } 1.1111 + 1.1112 + mRepeatDur = duration; 1.1113 + 1.1114 + return NS_OK; 1.1115 +} 1.1116 + 1.1117 +void 1.1118 +nsSMILTimedElement::UnsetRepeatDur() 1.1119 +{ 1.1120 + mRepeatDur.SetUnresolved(); 1.1121 + UpdateCurrentInterval(); 1.1122 +} 1.1123 + 1.1124 +nsresult 1.1125 +nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec) 1.1126 +{ 1.1127 + uint16_t previousFillMode = mFillMode; 1.1128 + 1.1129 + nsAttrValue temp; 1.1130 + bool parseResult = 1.1131 + temp.ParseEnumValue(aFillModeSpec, sFillModeTable, true); 1.1132 + mFillMode = parseResult 1.1133 + ? nsSMILFillMode(temp.GetEnumValue()) 1.1134 + : FILL_REMOVE; 1.1135 + 1.1136 + // Update fill mode of client 1.1137 + if (mFillMode != previousFillMode && HasClientInFillRange()) { 1.1138 + mClient->Inactivate(mFillMode == FILL_FREEZE); 1.1139 + SampleFillValue(); 1.1140 + } 1.1141 + 1.1142 + return parseResult ? NS_OK : NS_ERROR_FAILURE; 1.1143 +} 1.1144 + 1.1145 +void 1.1146 +nsSMILTimedElement::UnsetFillMode() 1.1147 +{ 1.1148 + uint16_t previousFillMode = mFillMode; 1.1149 + mFillMode = FILL_REMOVE; 1.1150 + if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) { 1.1151 + mClient->Inactivate(false); 1.1152 + } 1.1153 +} 1.1154 + 1.1155 +void 1.1156 +nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent) 1.1157 +{ 1.1158 + // There's probably no harm in attempting to register a dependent 1.1159 + // nsSMILTimeValueSpec twice, but we're not expecting it to happen. 1.1160 + NS_ABORT_IF_FALSE(!mTimeDependents.GetEntry(&aDependent), 1.1161 + "nsSMILTimeValueSpec is already registered as a dependency"); 1.1162 + mTimeDependents.PutEntry(&aDependent); 1.1163 + 1.1164 + // Add current interval. We could add historical intervals too but that would 1.1165 + // cause unpredictable results since some intervals may have been filtered. 1.1166 + // SMIL doesn't say what to do here so for simplicity and consistency we 1.1167 + // simply add the current interval if there is one. 1.1168 + // 1.1169 + // It's not necessary to call SyncPauseTime since we're dealing with 1.1170 + // historical instance times not newly added ones. 1.1171 + if (mCurrentInterval) { 1.1172 + aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer()); 1.1173 + } 1.1174 +} 1.1175 + 1.1176 +void 1.1177 +nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent) 1.1178 +{ 1.1179 + mTimeDependents.RemoveEntry(&aDependent); 1.1180 +} 1.1181 + 1.1182 +bool 1.1183 +nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const 1.1184 +{ 1.1185 + const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance(); 1.1186 + const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance(); 1.1187 + 1.1188 + if (!thisBegin || !otherBegin) 1.1189 + return false; 1.1190 + 1.1191 + return thisBegin->IsDependentOn(*otherBegin); 1.1192 +} 1.1193 + 1.1194 +void 1.1195 +nsSMILTimedElement::BindToTree(nsIContent* aContextNode) 1.1196 +{ 1.1197 + // Reset previously registered milestone since we may be registering with 1.1198 + // a different time container now. 1.1199 + mPrevRegisteredMilestone = sMaxMilestone; 1.1200 + 1.1201 + // If we were already active then clear all our timing information and start 1.1202 + // afresh 1.1203 + if (mElementState != STATE_STARTUP) { 1.1204 + mSeekState = SEEK_NOT_SEEKING; 1.1205 + Rewind(); 1.1206 + } 1.1207 + 1.1208 + // Scope updateBatcher to last only for the ResolveReferences calls: 1.1209 + { 1.1210 + AutoIntervalUpdateBatcher updateBatcher(*this); 1.1211 + 1.1212 + // Resolve references to other parts of the tree 1.1213 + uint32_t count = mBeginSpecs.Length(); 1.1214 + for (uint32_t i = 0; i < count; ++i) { 1.1215 + mBeginSpecs[i]->ResolveReferences(aContextNode); 1.1216 + } 1.1217 + 1.1218 + count = mEndSpecs.Length(); 1.1219 + for (uint32_t j = 0; j < count; ++j) { 1.1220 + mEndSpecs[j]->ResolveReferences(aContextNode); 1.1221 + } 1.1222 + } 1.1223 + 1.1224 + RegisterMilestone(); 1.1225 +} 1.1226 + 1.1227 +void 1.1228 +nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget) 1.1229 +{ 1.1230 + AutoIntervalUpdateBatcher updateBatcher(*this); 1.1231 + 1.1232 + uint32_t count = mBeginSpecs.Length(); 1.1233 + for (uint32_t i = 0; i < count; ++i) { 1.1234 + mBeginSpecs[i]->HandleTargetElementChange(aNewTarget); 1.1235 + } 1.1236 + 1.1237 + count = mEndSpecs.Length(); 1.1238 + for (uint32_t j = 0; j < count; ++j) { 1.1239 + mEndSpecs[j]->HandleTargetElementChange(aNewTarget); 1.1240 + } 1.1241 +} 1.1242 + 1.1243 +void 1.1244 +nsSMILTimedElement::Traverse(nsCycleCollectionTraversalCallback* aCallback) 1.1245 +{ 1.1246 + uint32_t count = mBeginSpecs.Length(); 1.1247 + for (uint32_t i = 0; i < count; ++i) { 1.1248 + nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; 1.1249 + NS_ABORT_IF_FALSE(beginSpec, 1.1250 + "null nsSMILTimeValueSpec in list of begin specs"); 1.1251 + beginSpec->Traverse(aCallback); 1.1252 + } 1.1253 + 1.1254 + count = mEndSpecs.Length(); 1.1255 + for (uint32_t j = 0; j < count; ++j) { 1.1256 + nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; 1.1257 + NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); 1.1258 + endSpec->Traverse(aCallback); 1.1259 + } 1.1260 +} 1.1261 + 1.1262 +void 1.1263 +nsSMILTimedElement::Unlink() 1.1264 +{ 1.1265 + AutoIntervalUpdateBatcher updateBatcher(*this); 1.1266 + 1.1267 + // Remove dependencies on other elements 1.1268 + uint32_t count = mBeginSpecs.Length(); 1.1269 + for (uint32_t i = 0; i < count; ++i) { 1.1270 + nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; 1.1271 + NS_ABORT_IF_FALSE(beginSpec, 1.1272 + "null nsSMILTimeValueSpec in list of begin specs"); 1.1273 + beginSpec->Unlink(); 1.1274 + } 1.1275 + 1.1276 + count = mEndSpecs.Length(); 1.1277 + for (uint32_t j = 0; j < count; ++j) { 1.1278 + nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; 1.1279 + NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); 1.1280 + endSpec->Unlink(); 1.1281 + } 1.1282 + 1.1283 + ClearIntervals(); 1.1284 + 1.1285 + // Make sure we don't notify other elements of new intervals 1.1286 + mTimeDependents.Clear(); 1.1287 +} 1.1288 + 1.1289 +//---------------------------------------------------------------------- 1.1290 +// Implementation helpers 1.1291 + 1.1292 +nsresult 1.1293 +nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec, 1.1294 + Element* aContextNode, 1.1295 + bool aIsBegin, 1.1296 + RemovalTestFunction aRemove) 1.1297 +{ 1.1298 + TimeValueSpecList& timeSpecsList = aIsBegin ? mBeginSpecs : mEndSpecs; 1.1299 + InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; 1.1300 + 1.1301 + ClearSpecs(timeSpecsList, instances, aRemove); 1.1302 + 1.1303 + AutoIntervalUpdateBatcher updateBatcher(*this); 1.1304 + 1.1305 + nsCharSeparatedTokenizer tokenizer(aSpec, ';'); 1.1306 + if (!tokenizer.hasMoreTokens()) { // Empty list 1.1307 + return NS_ERROR_FAILURE; 1.1308 + } 1.1309 + 1.1310 + nsresult rv = NS_OK; 1.1311 + while (tokenizer.hasMoreTokens() && NS_SUCCEEDED(rv)) { 1.1312 + nsAutoPtr<nsSMILTimeValueSpec> 1.1313 + spec(new nsSMILTimeValueSpec(*this, aIsBegin)); 1.1314 + rv = spec->SetSpec(tokenizer.nextToken(), aContextNode); 1.1315 + if (NS_SUCCEEDED(rv)) { 1.1316 + timeSpecsList.AppendElement(spec.forget()); 1.1317 + } 1.1318 + } 1.1319 + 1.1320 + if (NS_FAILED(rv)) { 1.1321 + ClearSpecs(timeSpecsList, instances, aRemove); 1.1322 + } 1.1323 + 1.1324 + return rv; 1.1325 +} 1.1326 + 1.1327 +namespace 1.1328 +{ 1.1329 + // Adaptor functor for RemoveInstanceTimes that allows us to use function 1.1330 + // pointers instead. 1.1331 + // Without this we'd have to either templatize ClearSpecs and all its callers 1.1332 + // or pass bool flags around to specify which removal function to use here. 1.1333 + class MOZ_STACK_CLASS RemoveByFunction 1.1334 + { 1.1335 + public: 1.1336 + RemoveByFunction(nsSMILTimedElement::RemovalTestFunction aFunction) 1.1337 + : mFunction(aFunction) { } 1.1338 + bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) 1.1339 + { 1.1340 + return mFunction(aInstanceTime); 1.1341 + } 1.1342 + 1.1343 + private: 1.1344 + nsSMILTimedElement::RemovalTestFunction mFunction; 1.1345 + }; 1.1346 +} 1.1347 + 1.1348 +void 1.1349 +nsSMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs, 1.1350 + InstanceTimeList& aInstances, 1.1351 + RemovalTestFunction aRemove) 1.1352 +{ 1.1353 + AutoIntervalUpdateBatcher updateBatcher(*this); 1.1354 + 1.1355 + for (uint32_t i = 0; i < aSpecs.Length(); ++i) { 1.1356 + aSpecs[i]->Unlink(); 1.1357 + } 1.1358 + aSpecs.Clear(); 1.1359 + 1.1360 + RemoveByFunction removeByFunction(aRemove); 1.1361 + RemoveInstanceTimes(aInstances, removeByFunction); 1.1362 +} 1.1363 + 1.1364 +void 1.1365 +nsSMILTimedElement::ClearIntervals() 1.1366 +{ 1.1367 + if (mElementState != STATE_STARTUP) { 1.1368 + mElementState = STATE_POSTACTIVE; 1.1369 + } 1.1370 + mCurrentRepeatIteration = 0; 1.1371 + ResetCurrentInterval(); 1.1372 + 1.1373 + // Remove old intervals 1.1374 + for (int32_t i = mOldIntervals.Length() - 1; i >= 0; --i) { 1.1375 + mOldIntervals[i]->Unlink(); 1.1376 + } 1.1377 + mOldIntervals.Clear(); 1.1378 +} 1.1379 + 1.1380 +bool 1.1381 +nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime) 1.1382 +{ 1.1383 + // This should only be called within DoSampleAt as a helper function 1.1384 + NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE, 1.1385 + "Unexpected state to try to apply an early end"); 1.1386 + 1.1387 + bool updated = false; 1.1388 + 1.1389 + // Only apply an early end if we're not already ending. 1.1390 + if (mCurrentInterval->End()->Time() > aSampleTime) { 1.1391 + nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime); 1.1392 + if (earlyEnd) { 1.1393 + if (earlyEnd->IsDependent()) { 1.1394 + // Generate a new instance time for the early end since the 1.1395 + // existing instance time is part of some dependency chain that we 1.1396 + // don't want to participate in. 1.1397 + nsRefPtr<nsSMILInstanceTime> newEarlyEnd = 1.1398 + new nsSMILInstanceTime(earlyEnd->Time()); 1.1399 + mCurrentInterval->SetEnd(*newEarlyEnd); 1.1400 + } else { 1.1401 + mCurrentInterval->SetEnd(*earlyEnd); 1.1402 + } 1.1403 + updated = true; 1.1404 + } 1.1405 + } 1.1406 + return updated; 1.1407 +} 1.1408 + 1.1409 +namespace 1.1410 +{ 1.1411 + class MOZ_STACK_CLASS RemoveReset 1.1412 + { 1.1413 + public: 1.1414 + RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin) 1.1415 + : mCurrentIntervalBegin(aCurrentIntervalBegin) { } 1.1416 + bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) 1.1417 + { 1.1418 + // SMIL 3.0 section 5.4.3, 'Resetting element state': 1.1419 + // Any instance times associated with past Event-values, Repeat-values, 1.1420 + // Accesskey-values or added via DOM method calls are removed from the 1.1421 + // dependent begin and end instance times lists. In effect, all events 1.1422 + // and DOM methods calls in the past are cleared. This does not apply to 1.1423 + // an instance time that defines the begin of the current interval. 1.1424 + return aInstanceTime->IsDynamic() && 1.1425 + !aInstanceTime->ShouldPreserve() && 1.1426 + (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin); 1.1427 + } 1.1428 + 1.1429 + private: 1.1430 + const nsSMILInstanceTime* mCurrentIntervalBegin; 1.1431 + }; 1.1432 +} 1.1433 + 1.1434 +void 1.1435 +nsSMILTimedElement::Reset() 1.1436 +{ 1.1437 + RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nullptr); 1.1438 + RemoveInstanceTimes(mBeginInstances, resetBegin); 1.1439 + 1.1440 + RemoveReset resetEnd(nullptr); 1.1441 + RemoveInstanceTimes(mEndInstances, resetEnd); 1.1442 +} 1.1443 + 1.1444 +void 1.1445 +nsSMILTimedElement::DoPostSeek() 1.1446 +{ 1.1447 + // Finish backwards seek 1.1448 + if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE || 1.1449 + mSeekState == SEEK_BACKWARD_FROM_ACTIVE) { 1.1450 + // Previously some dynamic instance times may have been marked to be 1.1451 + // preserved because they were endpoints of an historic interval (which may 1.1452 + // or may not have been filtered). Now that we've finished a seek we should 1.1453 + // clear that flag for those instance times whose intervals are no longer 1.1454 + // historic. 1.1455 + UnpreserveInstanceTimes(mBeginInstances); 1.1456 + UnpreserveInstanceTimes(mEndInstances); 1.1457 + 1.1458 + // Now that the times have been unmarked perform a reset. This might seem 1.1459 + // counter-intuitive when we're only doing a seek within an interval but 1.1460 + // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing': 1.1461 + // Resolved end times associated with events, Repeat-values, 1.1462 + // Accesskey-values or added via DOM method calls are cleared when seeking 1.1463 + // to time earlier than the resolved end time. 1.1464 + Reset(); 1.1465 + UpdateCurrentInterval(); 1.1466 + } 1.1467 + 1.1468 + switch (mSeekState) 1.1469 + { 1.1470 + case SEEK_FORWARD_FROM_ACTIVE: 1.1471 + case SEEK_BACKWARD_FROM_ACTIVE: 1.1472 + if (mElementState != STATE_ACTIVE) { 1.1473 + FireTimeEventAsync(NS_SMIL_END, 0); 1.1474 + } 1.1475 + break; 1.1476 + 1.1477 + case SEEK_FORWARD_FROM_INACTIVE: 1.1478 + case SEEK_BACKWARD_FROM_INACTIVE: 1.1479 + if (mElementState == STATE_ACTIVE) { 1.1480 + FireTimeEventAsync(NS_SMIL_BEGIN, 0); 1.1481 + } 1.1482 + break; 1.1483 + 1.1484 + case SEEK_NOT_SEEKING: 1.1485 + /* Do nothing */ 1.1486 + break; 1.1487 + } 1.1488 + 1.1489 + mSeekState = SEEK_NOT_SEEKING; 1.1490 +} 1.1491 + 1.1492 +void 1.1493 +nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList) 1.1494 +{ 1.1495 + const nsSMILInterval* prevInterval = GetPreviousInterval(); 1.1496 + const nsSMILInstanceTime* cutoff = mCurrentInterval ? 1.1497 + mCurrentInterval->Begin() : 1.1498 + prevInterval ? prevInterval->Begin() : nullptr; 1.1499 + uint32_t count = aList.Length(); 1.1500 + for (uint32_t i = 0; i < count; ++i) { 1.1501 + nsSMILInstanceTime* instance = aList[i].get(); 1.1502 + if (!cutoff || cutoff->Time().CompareTo(instance->Time()) < 0) { 1.1503 + instance->UnmarkShouldPreserve(); 1.1504 + } 1.1505 + } 1.1506 +} 1.1507 + 1.1508 +void 1.1509 +nsSMILTimedElement::FilterHistory() 1.1510 +{ 1.1511 + // We should filter the intervals first, since instance times still used in an 1.1512 + // interval won't be filtered. 1.1513 + FilterIntervals(); 1.1514 + FilterInstanceTimes(mBeginInstances); 1.1515 + FilterInstanceTimes(mEndInstances); 1.1516 +} 1.1517 + 1.1518 +void 1.1519 +nsSMILTimedElement::FilterIntervals() 1.1520 +{ 1.1521 + // We can filter old intervals that: 1.1522 + // 1.1523 + // a) are not the previous interval; AND 1.1524 + // b) are not in the middle of a dependency chain; AND 1.1525 + // c) are not the first interval 1.1526 + // 1.1527 + // Condition (a) is necessary since the previous interval is used for applying 1.1528 + // fill effects and updating the current interval. 1.1529 + // 1.1530 + // Condition (b) is necessary since even if this interval itself is not 1.1531 + // active, it may be part of a dependency chain that includes active 1.1532 + // intervals. Such chains are used to establish priorities within the 1.1533 + // animation sandwich. 1.1534 + // 1.1535 + // Condition (c) is necessary to support hyperlinks that target animations 1.1536 + // since in some cases the defined behavior is to seek the document back to 1.1537 + // the first resolved begin time. Presumably the intention here is not 1.1538 + // actually to use the first resolved begin time, the 1.1539 + // _the_first_resolved_begin_time_that_produced_an_interval. That is, 1.1540 + // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek 1.1541 + // to 1s. The spec doesn't say this but I'm pretty sure that is the intention. 1.1542 + // It seems negative times were simply not considered. 1.1543 + // 1.1544 + // Although the above conditions allow us to safely filter intervals for most 1.1545 + // scenarios they do not cover all cases and there will still be scenarios 1.1546 + // that generate intervals indefinitely. In such a case we simply set 1.1547 + // a maximum number of intervals and drop any intervals beyond that threshold. 1.1548 + 1.1549 + uint32_t threshold = mOldIntervals.Length() > sMaxNumIntervals ? 1.1550 + mOldIntervals.Length() - sMaxNumIntervals : 1.1551 + 0; 1.1552 + IntervalList filteredList; 1.1553 + for (uint32_t i = 0; i < mOldIntervals.Length(); ++i) 1.1554 + { 1.1555 + nsSMILInterval* interval = mOldIntervals[i].get(); 1.1556 + if (i != 0 && /*skip first interval*/ 1.1557 + i + 1 < mOldIntervals.Length() && /*skip previous interval*/ 1.1558 + (i < threshold || !interval->IsDependencyChainLink())) { 1.1559 + interval->Unlink(true /*filtered, not deleted*/); 1.1560 + } else { 1.1561 + filteredList.AppendElement(mOldIntervals[i].forget()); 1.1562 + } 1.1563 + } 1.1564 + mOldIntervals.Clear(); 1.1565 + mOldIntervals.SwapElements(filteredList); 1.1566 +} 1.1567 + 1.1568 +namespace 1.1569 +{ 1.1570 + class MOZ_STACK_CLASS RemoveFiltered 1.1571 + { 1.1572 + public: 1.1573 + RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { } 1.1574 + bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) 1.1575 + { 1.1576 + // We can filter instance times that: 1.1577 + // a) Precede the end point of the previous interval; AND 1.1578 + // b) Are NOT syncbase times that might be updated to a time after the end 1.1579 + // point of the previous interval; AND 1.1580 + // c) Are NOT fixed end points in any remaining interval. 1.1581 + return aInstanceTime->Time() < mCutoff && 1.1582 + aInstanceTime->IsFixedTime() && 1.1583 + !aInstanceTime->ShouldPreserve(); 1.1584 + } 1.1585 + 1.1586 + private: 1.1587 + nsSMILTimeValue mCutoff; 1.1588 + }; 1.1589 + 1.1590 + class MOZ_STACK_CLASS RemoveBelowThreshold 1.1591 + { 1.1592 + public: 1.1593 + RemoveBelowThreshold(uint32_t aThreshold, 1.1594 + nsTArray<const nsSMILInstanceTime *>& aTimesToKeep) 1.1595 + : mThreshold(aThreshold), 1.1596 + mTimesToKeep(aTimesToKeep) { } 1.1597 + bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t aIndex) 1.1598 + { 1.1599 + return aIndex < mThreshold && !mTimesToKeep.Contains(aInstanceTime); 1.1600 + } 1.1601 + 1.1602 + private: 1.1603 + uint32_t mThreshold; 1.1604 + nsTArray<const nsSMILInstanceTime *>& mTimesToKeep; 1.1605 + }; 1.1606 +} 1.1607 + 1.1608 +void 1.1609 +nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList) 1.1610 +{ 1.1611 + if (GetPreviousInterval()) { 1.1612 + RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time()); 1.1613 + RemoveInstanceTimes(aList, removeFiltered); 1.1614 + } 1.1615 + 1.1616 + // As with intervals it is possible to create a document that, even despite 1.1617 + // our most aggressive filtering, will generate instance times indefinitely 1.1618 + // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as 1.1619 + // they're unpredictable due to the possibility of seeking the document which 1.1620 + // may prevent some events from being generated). Therefore we introduce 1.1621 + // a hard cutoff at which point we just drop the oldest instance times. 1.1622 + if (aList.Length() > sMaxNumInstanceTimes) { 1.1623 + uint32_t threshold = aList.Length() - sMaxNumInstanceTimes; 1.1624 + // There are a few instance times we should keep though, notably: 1.1625 + // - the current interval begin time, 1.1626 + // - the previous interval end time (see note in RemoveInstanceTimes) 1.1627 + // - the first interval begin time (see note in FilterIntervals) 1.1628 + nsTArray<const nsSMILInstanceTime *> timesToKeep; 1.1629 + if (mCurrentInterval) { 1.1630 + timesToKeep.AppendElement(mCurrentInterval->Begin()); 1.1631 + } 1.1632 + const nsSMILInterval* prevInterval = GetPreviousInterval(); 1.1633 + if (prevInterval) { 1.1634 + timesToKeep.AppendElement(prevInterval->End()); 1.1635 + } 1.1636 + if (!mOldIntervals.IsEmpty()) { 1.1637 + timesToKeep.AppendElement(mOldIntervals[0]->Begin()); 1.1638 + } 1.1639 + RemoveBelowThreshold removeBelowThreshold(threshold, timesToKeep); 1.1640 + RemoveInstanceTimes(aList, removeBelowThreshold); 1.1641 + } 1.1642 +} 1.1643 + 1.1644 +// 1.1645 +// This method is based on the pseudocode given in the SMILANIM spec. 1.1646 +// 1.1647 +// See: 1.1648 +// http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start 1.1649 +// 1.1650 +bool 1.1651 +nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval, 1.1652 + const nsSMILInterval* aReplacedInterval, 1.1653 + const nsSMILInstanceTime* aFixedBeginTime, 1.1654 + nsSMILInterval& aResult) const 1.1655 +{ 1.1656 + NS_ABORT_IF_FALSE(!aFixedBeginTime || aFixedBeginTime->Time().IsDefinite(), 1.1657 + "Unresolved or indefinite begin time specified for interval start"); 1.1658 + static const nsSMILTimeValue zeroTime(0L); 1.1659 + 1.1660 + if (mRestartMode == RESTART_NEVER && aPrevInterval) 1.1661 + return false; 1.1662 + 1.1663 + // Calc starting point 1.1664 + nsSMILTimeValue beginAfter; 1.1665 + bool prevIntervalWasZeroDur = false; 1.1666 + if (aPrevInterval) { 1.1667 + beginAfter = aPrevInterval->End()->Time(); 1.1668 + prevIntervalWasZeroDur 1.1669 + = aPrevInterval->End()->Time() == aPrevInterval->Begin()->Time(); 1.1670 + } else { 1.1671 + beginAfter.SetMillis(INT64_MIN); 1.1672 + } 1.1673 + 1.1674 + nsRefPtr<nsSMILInstanceTime> tempBegin; 1.1675 + nsRefPtr<nsSMILInstanceTime> tempEnd; 1.1676 + 1.1677 + while (true) { 1.1678 + // Calculate begin time 1.1679 + if (aFixedBeginTime) { 1.1680 + if (aFixedBeginTime->Time() < beginAfter) { 1.1681 + return false; 1.1682 + } 1.1683 + // our ref-counting is not const-correct 1.1684 + tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime); 1.1685 + } else if ((!mAnimationElement || 1.1686 + !mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) && 1.1687 + beginAfter <= zeroTime) { 1.1688 + tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0)); 1.1689 + } else { 1.1690 + int32_t beginPos = 0; 1.1691 + do { 1.1692 + tempBegin = 1.1693 + GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos); 1.1694 + if (!tempBegin || !tempBegin->Time().IsDefinite()) { 1.1695 + return false; 1.1696 + } 1.1697 + // If we're updating the current interval then skip any begin time that is 1.1698 + // dependent on the current interval's begin time. e.g. 1.1699 + // <animate id="a" begin="b.begin; a.begin+2s"... 1.1700 + // If b's interval disappears whilst 'a' is in the waiting state the begin 1.1701 + // time at "a.begin+2s" should be skipped since 'a' never begun. 1.1702 + } while (aReplacedInterval && 1.1703 + tempBegin->GetBaseTime() == aReplacedInterval->Begin()); 1.1704 + } 1.1705 + NS_ABORT_IF_FALSE(tempBegin && tempBegin->Time().IsDefinite() && 1.1706 + tempBegin->Time() >= beginAfter, 1.1707 + "Got a bad begin time while fetching next interval"); 1.1708 + 1.1709 + // Calculate end time 1.1710 + { 1.1711 + int32_t endPos = 0; 1.1712 + do { 1.1713 + tempEnd = 1.1714 + GetNextGreaterOrEqual(mEndInstances, tempBegin->Time(), endPos); 1.1715 + 1.1716 + // SMIL doesn't allow for coincident zero-duration intervals, so if the 1.1717 + // previous interval was zero-duration, and tempEnd is going to give us 1.1718 + // another zero duration interval, then look for another end to use 1.1719 + // instead. 1.1720 + if (tempEnd && prevIntervalWasZeroDur && 1.1721 + tempEnd->Time() == beginAfter) { 1.1722 + tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos); 1.1723 + } 1.1724 + // As above with begin times, avoid creating self-referential loops 1.1725 + // between instance times by checking that the newly found end instance 1.1726 + // time is not already dependent on the end of the current interval. 1.1727 + } while (tempEnd && aReplacedInterval && 1.1728 + tempEnd->GetBaseTime() == aReplacedInterval->End()); 1.1729 + 1.1730 + if (!tempEnd) { 1.1731 + // If all the ends are before the beginning we have a bad interval 1.1732 + // UNLESS: 1.1733 + // a) We never had any end attribute to begin with (the SMIL pseudocode 1.1734 + // places this condition earlier in the flow but that fails to allow 1.1735 + // for DOM calls when no "indefinite" condition is given), OR 1.1736 + // b) We never had any end instance times to begin with, OR 1.1737 + // c) We have end events which leave the interval open-ended. 1.1738 + bool openEndedIntervalOk = mEndSpecs.IsEmpty() || 1.1739 + mEndInstances.IsEmpty() || 1.1740 + EndHasEventConditions(); 1.1741 + 1.1742 + // The above conditions correspond with the SMIL pseudocode but SMIL 1.1743 + // doesn't address self-dependent instance times which we choose to 1.1744 + // ignore. 1.1745 + // 1.1746 + // Therefore we add a qualification of (b) above that even if 1.1747 + // there are end instance times but they all depend on the end of the 1.1748 + // current interval we should act as if they didn't exist and allow the 1.1749 + // open-ended interval. 1.1750 + // 1.1751 + // In the following condition we don't use |= because it doesn't provide 1.1752 + // short-circuit behavior. 1.1753 + openEndedIntervalOk = openEndedIntervalOk || 1.1754 + (aReplacedInterval && 1.1755 + AreEndTimesDependentOn(aReplacedInterval->End())); 1.1756 + 1.1757 + if (!openEndedIntervalOk) { 1.1758 + return false; // Bad interval 1.1759 + } 1.1760 + } 1.1761 + 1.1762 + nsSMILTimeValue intervalEnd = tempEnd 1.1763 + ? tempEnd->Time() : nsSMILTimeValue(); 1.1764 + nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd); 1.1765 + 1.1766 + if (!tempEnd || intervalEnd != activeEnd) { 1.1767 + tempEnd = new nsSMILInstanceTime(activeEnd); 1.1768 + } 1.1769 + } 1.1770 + NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval"); 1.1771 + 1.1772 + // When we choose the interval endpoints, we don't allow coincident 1.1773 + // zero-duration intervals, so if we arrive here and we have a zero-duration 1.1774 + // interval starting at the same point as a previous zero-duration interval, 1.1775 + // then it must be because we've applied constraints to the active duration. 1.1776 + // In that case, we will potentially run into an infinite loop, so we break 1.1777 + // it by searching for the next interval that starts AFTER our current 1.1778 + // zero-duration interval. 1.1779 + if (prevIntervalWasZeroDur && tempEnd->Time() == beginAfter) { 1.1780 + if (prevIntervalWasZeroDur) { 1.1781 + beginAfter.SetMillis(tempBegin->Time().GetMillis() + 1); 1.1782 + prevIntervalWasZeroDur = false; 1.1783 + continue; 1.1784 + } 1.1785 + } 1.1786 + prevIntervalWasZeroDur = tempBegin->Time() == tempEnd->Time(); 1.1787 + 1.1788 + // Check for valid interval 1.1789 + if (tempEnd->Time() > zeroTime || 1.1790 + (tempBegin->Time() == zeroTime && tempEnd->Time() == zeroTime)) { 1.1791 + aResult.Set(*tempBegin, *tempEnd); 1.1792 + return true; 1.1793 + } 1.1794 + 1.1795 + if (mRestartMode == RESTART_NEVER) { 1.1796 + // tempEnd <= 0 so we're going to loop which effectively means restarting 1.1797 + return false; 1.1798 + } 1.1799 + 1.1800 + beginAfter = tempEnd->Time(); 1.1801 + } 1.1802 + NS_NOTREACHED("Hmm... we really shouldn't be here"); 1.1803 + 1.1804 + return false; 1.1805 +} 1.1806 + 1.1807 +nsSMILInstanceTime* 1.1808 +nsSMILTimedElement::GetNextGreater(const InstanceTimeList& aList, 1.1809 + const nsSMILTimeValue& aBase, 1.1810 + int32_t& aPosition) const 1.1811 +{ 1.1812 + nsSMILInstanceTime* result = nullptr; 1.1813 + while ((result = GetNextGreaterOrEqual(aList, aBase, aPosition)) && 1.1814 + result->Time() == aBase) { } 1.1815 + return result; 1.1816 +} 1.1817 + 1.1818 +nsSMILInstanceTime* 1.1819 +nsSMILTimedElement::GetNextGreaterOrEqual(const InstanceTimeList& aList, 1.1820 + const nsSMILTimeValue& aBase, 1.1821 + int32_t& aPosition) const 1.1822 +{ 1.1823 + nsSMILInstanceTime* result = nullptr; 1.1824 + int32_t count = aList.Length(); 1.1825 + 1.1826 + for (; aPosition < count && !result; ++aPosition) { 1.1827 + nsSMILInstanceTime* val = aList[aPosition].get(); 1.1828 + NS_ABORT_IF_FALSE(val, "NULL instance time in list"); 1.1829 + if (val->Time() >= aBase) { 1.1830 + result = val; 1.1831 + } 1.1832 + } 1.1833 + 1.1834 + return result; 1.1835 +} 1.1836 + 1.1837 +/** 1.1838 + * @see SMILANIM 3.3.4 1.1839 + */ 1.1840 +nsSMILTimeValue 1.1841 +nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin, 1.1842 + const nsSMILTimeValue& aEnd) const 1.1843 +{ 1.1844 + nsSMILTimeValue result; 1.1845 + 1.1846 + NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), 1.1847 + "Unresolved simple duration in CalcActiveEnd"); 1.1848 + NS_ABORT_IF_FALSE(aBegin.IsDefinite(), 1.1849 + "Indefinite or unresolved begin time in CalcActiveEnd"); 1.1850 + 1.1851 + result = GetRepeatDuration(); 1.1852 + 1.1853 + if (aEnd.IsDefinite()) { 1.1854 + nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis(); 1.1855 + 1.1856 + if (result.IsDefinite()) { 1.1857 + result.SetMillis(std::min(result.GetMillis(), activeDur)); 1.1858 + } else { 1.1859 + result.SetMillis(activeDur); 1.1860 + } 1.1861 + } 1.1862 + 1.1863 + result = ApplyMinAndMax(result); 1.1864 + 1.1865 + if (result.IsDefinite()) { 1.1866 + nsSMILTime activeEnd = result.GetMillis() + aBegin.GetMillis(); 1.1867 + result.SetMillis(activeEnd); 1.1868 + } 1.1869 + 1.1870 + return result; 1.1871 +} 1.1872 + 1.1873 +nsSMILTimeValue 1.1874 +nsSMILTimedElement::GetRepeatDuration() const 1.1875 +{ 1.1876 + nsSMILTimeValue multipliedDuration; 1.1877 + if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) { 1.1878 + multipliedDuration.SetMillis( 1.1879 + nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()))); 1.1880 + } else { 1.1881 + multipliedDuration.SetIndefinite(); 1.1882 + } 1.1883 + 1.1884 + nsSMILTimeValue repeatDuration; 1.1885 + 1.1886 + if (mRepeatDur.IsResolved()) { 1.1887 + repeatDuration = std::min(multipliedDuration, mRepeatDur); 1.1888 + } else if (mRepeatCount.IsSet()) { 1.1889 + repeatDuration = multipliedDuration; 1.1890 + } else { 1.1891 + repeatDuration = mSimpleDur; 1.1892 + } 1.1893 + 1.1894 + return repeatDuration; 1.1895 +} 1.1896 + 1.1897 +nsSMILTimeValue 1.1898 +nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const 1.1899 +{ 1.1900 + if (!aDuration.IsResolved()) { 1.1901 + return aDuration; 1.1902 + } 1.1903 + 1.1904 + if (mMax < mMin) { 1.1905 + return aDuration; 1.1906 + } 1.1907 + 1.1908 + nsSMILTimeValue result; 1.1909 + 1.1910 + if (aDuration > mMax) { 1.1911 + result = mMax; 1.1912 + } else if (aDuration < mMin) { 1.1913 + result = mMin; 1.1914 + } else { 1.1915 + result = aDuration; 1.1916 + } 1.1917 + 1.1918 + return result; 1.1919 +} 1.1920 + 1.1921 +nsSMILTime 1.1922 +nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime, 1.1923 + uint32_t& aRepeatIteration) 1.1924 +{ 1.1925 + nsSMILTime result; 1.1926 + 1.1927 + NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), 1.1928 + "Unresolved simple duration in ActiveTimeToSimpleTime"); 1.1929 + NS_ABORT_IF_FALSE(aActiveTime >= 0, "Expecting non-negative active time"); 1.1930 + // Note that a negative aActiveTime will give us a negative value for 1.1931 + // aRepeatIteration, which is bad because aRepeatIteration is unsigned 1.1932 + 1.1933 + if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) { 1.1934 + aRepeatIteration = 0; 1.1935 + result = aActiveTime; 1.1936 + } else { 1.1937 + result = aActiveTime % mSimpleDur.GetMillis(); 1.1938 + aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis()); 1.1939 + } 1.1940 + 1.1941 + return result; 1.1942 +} 1.1943 + 1.1944 +// 1.1945 +// Although in many cases it would be possible to check for an early end and 1.1946 +// adjust the current interval well in advance the SMIL Animation spec seems to 1.1947 +// indicate that we should only apply an early end at the latest possible 1.1948 +// moment. In particular, this paragraph from section 3.6.8: 1.1949 +// 1.1950 +// 'If restart is set to "always", then the current interval will end early if 1.1951 +// there is an instance time in the begin list that is before (i.e. earlier 1.1952 +// than) the defined end for the current interval. Ending in this manner will 1.1953 +// also send a changed time notice to all time dependents for the current 1.1954 +// interval end.' 1.1955 +// 1.1956 +nsSMILInstanceTime* 1.1957 +nsSMILTimedElement::CheckForEarlyEnd( 1.1958 + const nsSMILTimeValue& aContainerTime) const 1.1959 +{ 1.1960 + NS_ABORT_IF_FALSE(mCurrentInterval, 1.1961 + "Checking for an early end but the current interval is not set"); 1.1962 + if (mRestartMode != RESTART_ALWAYS) 1.1963 + return nullptr; 1.1964 + 1.1965 + int32_t position = 0; 1.1966 + nsSMILInstanceTime* nextBegin = 1.1967 + GetNextGreater(mBeginInstances, mCurrentInterval->Begin()->Time(), 1.1968 + position); 1.1969 + 1.1970 + if (nextBegin && 1.1971 + nextBegin->Time() > mCurrentInterval->Begin()->Time() && 1.1972 + nextBegin->Time() < mCurrentInterval->End()->Time() && 1.1973 + nextBegin->Time() <= aContainerTime) { 1.1974 + return nextBegin; 1.1975 + } 1.1976 + 1.1977 + return nullptr; 1.1978 +} 1.1979 + 1.1980 +void 1.1981 +nsSMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice) 1.1982 +{ 1.1983 + // Check if updates are currently blocked (batched) 1.1984 + if (mDeferIntervalUpdates) { 1.1985 + mDoDeferredUpdate = true; 1.1986 + return; 1.1987 + } 1.1988 + 1.1989 + // We adopt the convention of not resolving intervals until the first 1.1990 + // sample. Otherwise, every time each attribute is set we'll re-resolve the 1.1991 + // current interval and notify all our time dependents of the change. 1.1992 + // 1.1993 + // The disadvantage of deferring resolving the interval is that DOM calls to 1.1994 + // to getStartTime will throw an INVALID_STATE_ERR exception until the 1.1995 + // document timeline begins since the start time has not yet been resolved. 1.1996 + if (mElementState == STATE_STARTUP) 1.1997 + return; 1.1998 + 1.1999 + // Although SMIL gives rules for detecting cycles in change notifications, 1.2000 + // some configurations can lead to create-delete-create-delete-etc. cycles 1.2001 + // which SMIL does not consider. 1.2002 + // 1.2003 + // In order to provide consistent behavior in such cases, we detect two 1.2004 + // deletes in a row and then refuse to create any further intervals. That is, 1.2005 + // we say the configuration is invalid. 1.2006 + if (mDeleteCount > 1) { 1.2007 + // When we update the delete count we also set the state to post active, so 1.2008 + // if we're not post active here then something other than 1.2009 + // UpdateCurrentInterval has updated the element state in between and all 1.2010 + // bets are off. 1.2011 + NS_ABORT_IF_FALSE(mElementState == STATE_POSTACTIVE, 1.2012 + "Expected to be in post-active state after performing double delete"); 1.2013 + return; 1.2014 + } 1.2015 + 1.2016 + // Check that we aren't stuck in infinite recursion updating some syncbase 1.2017 + // dependencies. Generally such situations should be detected in advance and 1.2018 + // the chain broken in a sensible and predictable manner, so if we're hitting 1.2019 + // this assertion we need to work out how to detect the case that's causing 1.2020 + // it. In release builds, just bail out before we overflow the stack. 1.2021 + AutoRestore<uint8_t> depthRestorer(mUpdateIntervalRecursionDepth); 1.2022 + if (++mUpdateIntervalRecursionDepth > sMaxUpdateIntervalRecursionDepth) { 1.2023 + NS_ABORT_IF_FALSE(false, 1.2024 + "Update current interval recursion depth exceeded threshold"); 1.2025 + return; 1.2026 + } 1.2027 + 1.2028 + // If the interval is active the begin time is fixed. 1.2029 + const nsSMILInstanceTime* beginTime = mElementState == STATE_ACTIVE 1.2030 + ? mCurrentInterval->Begin() 1.2031 + : nullptr; 1.2032 + nsSMILInterval updatedInterval; 1.2033 + if (GetNextInterval(GetPreviousInterval(), mCurrentInterval, 1.2034 + beginTime, updatedInterval)) { 1.2035 + 1.2036 + if (mElementState == STATE_POSTACTIVE) { 1.2037 + 1.2038 + NS_ABORT_IF_FALSE(!mCurrentInterval, 1.2039 + "In postactive state but the interval has been set"); 1.2040 + mCurrentInterval = new nsSMILInterval(updatedInterval); 1.2041 + mElementState = STATE_WAITING; 1.2042 + NotifyNewInterval(); 1.2043 + 1.2044 + } else { 1.2045 + 1.2046 + bool beginChanged = false; 1.2047 + bool endChanged = false; 1.2048 + 1.2049 + if (mElementState != STATE_ACTIVE && 1.2050 + !updatedInterval.Begin()->SameTimeAndBase( 1.2051 + *mCurrentInterval->Begin())) { 1.2052 + mCurrentInterval->SetBegin(*updatedInterval.Begin()); 1.2053 + beginChanged = true; 1.2054 + } 1.2055 + 1.2056 + if (!updatedInterval.End()->SameTimeAndBase(*mCurrentInterval->End())) { 1.2057 + mCurrentInterval->SetEnd(*updatedInterval.End()); 1.2058 + endChanged = true; 1.2059 + } 1.2060 + 1.2061 + if (beginChanged || endChanged || aForceChangeNotice) { 1.2062 + NotifyChangedInterval(mCurrentInterval, beginChanged, endChanged); 1.2063 + } 1.2064 + } 1.2065 + 1.2066 + // There's a chance our next milestone has now changed, so update the time 1.2067 + // container 1.2068 + RegisterMilestone(); 1.2069 + } else { // GetNextInterval failed: Current interval is no longer valid 1.2070 + if (mElementState == STATE_ACTIVE) { 1.2071 + // The interval is active so we can't just delete it, instead trim it so 1.2072 + // that begin==end. 1.2073 + if (!mCurrentInterval->End()->SameTimeAndBase(*mCurrentInterval->Begin())) 1.2074 + { 1.2075 + mCurrentInterval->SetEnd(*mCurrentInterval->Begin()); 1.2076 + NotifyChangedInterval(mCurrentInterval, false, true); 1.2077 + } 1.2078 + // The transition to the postactive state will take place on the next 1.2079 + // sample (along with firing end events, clearing intervals etc.) 1.2080 + RegisterMilestone(); 1.2081 + } else if (mElementState == STATE_WAITING) { 1.2082 + AutoRestore<uint8_t> deleteCountRestorer(mDeleteCount); 1.2083 + ++mDeleteCount; 1.2084 + mElementState = STATE_POSTACTIVE; 1.2085 + ResetCurrentInterval(); 1.2086 + } 1.2087 + } 1.2088 +} 1.2089 + 1.2090 +void 1.2091 +nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime) 1.2092 +{ 1.2093 + if (mClient) { 1.2094 + uint32_t repeatIteration; 1.2095 + nsSMILTime simpleTime = 1.2096 + ActiveTimeToSimpleTime(aActiveTime, repeatIteration); 1.2097 + mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); 1.2098 + } 1.2099 +} 1.2100 + 1.2101 +void 1.2102 +nsSMILTimedElement::SampleFillValue() 1.2103 +{ 1.2104 + if (mFillMode != FILL_FREEZE || !mClient) 1.2105 + return; 1.2106 + 1.2107 + nsSMILTime activeTime; 1.2108 + 1.2109 + if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) { 1.2110 + const nsSMILInterval* prevInterval = GetPreviousInterval(); 1.2111 + NS_ABORT_IF_FALSE(prevInterval, 1.2112 + "Attempting to sample fill value but there is no previous interval"); 1.2113 + NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() && 1.2114 + prevInterval->End()->IsFixedTime(), 1.2115 + "Attempting to sample fill value but the endpoint of the previous " 1.2116 + "interval is not resolved and fixed"); 1.2117 + 1.2118 + activeTime = prevInterval->End()->Time().GetMillis() - 1.2119 + prevInterval->Begin()->Time().GetMillis(); 1.2120 + 1.2121 + // If the interval's repeat duration was shorter than its active duration, 1.2122 + // use the end of the repeat duration to determine the frozen animation's 1.2123 + // state. 1.2124 + nsSMILTimeValue repeatDuration = GetRepeatDuration(); 1.2125 + if (repeatDuration.IsDefinite()) { 1.2126 + activeTime = std::min(repeatDuration.GetMillis(), activeTime); 1.2127 + } 1.2128 + } else { 1.2129 + MOZ_ASSERT(mElementState == STATE_ACTIVE, 1.2130 + "Attempting to sample fill value when we're in an unexpected state " 1.2131 + "(probably STATE_STARTUP)"); 1.2132 + 1.2133 + // If we are being asked to sample the fill value while active we *must* 1.2134 + // have a repeat duration shorter than the active duration so use that. 1.2135 + MOZ_ASSERT(GetRepeatDuration().IsDefinite(), 1.2136 + "Attempting to sample fill value of an active animation with " 1.2137 + "an indefinite repeat duration"); 1.2138 + activeTime = GetRepeatDuration().GetMillis(); 1.2139 + } 1.2140 + 1.2141 + uint32_t repeatIteration; 1.2142 + nsSMILTime simpleTime = 1.2143 + ActiveTimeToSimpleTime(activeTime, repeatIteration); 1.2144 + 1.2145 + if (simpleTime == 0L && repeatIteration) { 1.2146 + mClient->SampleLastValue(--repeatIteration); 1.2147 + } else { 1.2148 + mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); 1.2149 + } 1.2150 +} 1.2151 + 1.2152 +nsresult 1.2153 +nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime, 1.2154 + double aOffsetSeconds, bool aIsBegin) 1.2155 +{ 1.2156 + double offset = aOffsetSeconds * PR_MSEC_PER_SEC; 1.2157 + 1.2158 + // Check we won't overflow the range of nsSMILTime 1.2159 + if (aCurrentTime + NS_round(offset) > INT64_MAX) 1.2160 + return NS_ERROR_ILLEGAL_VALUE; 1.2161 + 1.2162 + nsSMILTimeValue timeVal(aCurrentTime + int64_t(NS_round(offset))); 1.2163 + 1.2164 + nsRefPtr<nsSMILInstanceTime> instanceTime = 1.2165 + new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM); 1.2166 + 1.2167 + AddInstanceTime(instanceTime, aIsBegin); 1.2168 + 1.2169 + return NS_OK; 1.2170 +} 1.2171 + 1.2172 +void 1.2173 +nsSMILTimedElement::RegisterMilestone() 1.2174 +{ 1.2175 + nsSMILTimeContainer* container = GetTimeContainer(); 1.2176 + if (!container) 1.2177 + return; 1.2178 + NS_ABORT_IF_FALSE(mAnimationElement, 1.2179 + "Got a time container without an owning animation element"); 1.2180 + 1.2181 + nsSMILMilestone nextMilestone; 1.2182 + if (!GetNextMilestone(nextMilestone)) 1.2183 + return; 1.2184 + 1.2185 + // This method is called every time we might possibly have updated our 1.2186 + // current interval, but since nsSMILTimeContainer makes no attempt to filter 1.2187 + // out redundant milestones we do some rudimentary filtering here. It's not 1.2188 + // perfect, but unnecessary samples are fairly cheap. 1.2189 + if (nextMilestone >= mPrevRegisteredMilestone) 1.2190 + return; 1.2191 + 1.2192 + container->AddMilestone(nextMilestone, *mAnimationElement); 1.2193 + mPrevRegisteredMilestone = nextMilestone; 1.2194 +} 1.2195 + 1.2196 +bool 1.2197 +nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const 1.2198 +{ 1.2199 + // Return the next key moment in our lifetime. 1.2200 + // 1.2201 + // XXX It may be possible in future to optimise this so that we only register 1.2202 + // for milestones if: 1.2203 + // a) We have time dependents, or 1.2204 + // b) We are dependent on events or syncbase relationships, or 1.2205 + // c) There are registered listeners for our events 1.2206 + // 1.2207 + // Then for the simple case where everything uses offset values we could 1.2208 + // ignore milestones altogether. 1.2209 + // 1.2210 + // We'd need to be careful, however, that if one of those conditions became 1.2211 + // true in between samples that we registered our next milestone at that 1.2212 + // point. 1.2213 + 1.2214 + switch (mElementState) 1.2215 + { 1.2216 + case STATE_STARTUP: 1.2217 + // All elements register for an initial end sample at t=0 where we resolve 1.2218 + // our initial interval. 1.2219 + aNextMilestone.mIsEnd = true; // Initial sample should be an end sample 1.2220 + aNextMilestone.mTime = 0; 1.2221 + return true; 1.2222 + 1.2223 + case STATE_WAITING: 1.2224 + NS_ABORT_IF_FALSE(mCurrentInterval, 1.2225 + "In waiting state but the current interval has not been set"); 1.2226 + aNextMilestone.mIsEnd = false; 1.2227 + aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis(); 1.2228 + return true; 1.2229 + 1.2230 + case STATE_ACTIVE: 1.2231 + { 1.2232 + // Work out what comes next: the interval end or the next repeat iteration 1.2233 + nsSMILTimeValue nextRepeat; 1.2234 + if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) { 1.2235 + nsSMILTime nextRepeatActiveTime = 1.2236 + (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis(); 1.2237 + // Check that the repeat fits within the repeat duration 1.2238 + if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) { 1.2239 + nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() + 1.2240 + nextRepeatActiveTime); 1.2241 + } 1.2242 + } 1.2243 + nsSMILTimeValue nextMilestone = 1.2244 + std::min(mCurrentInterval->End()->Time(), nextRepeat); 1.2245 + 1.2246 + // Check for an early end before that time 1.2247 + nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone); 1.2248 + if (earlyEnd) { 1.2249 + aNextMilestone.mIsEnd = true; 1.2250 + aNextMilestone.mTime = earlyEnd->Time().GetMillis(); 1.2251 + return true; 1.2252 + } 1.2253 + 1.2254 + // Apply the previously calculated milestone 1.2255 + if (nextMilestone.IsDefinite()) { 1.2256 + aNextMilestone.mIsEnd = nextMilestone != nextRepeat; 1.2257 + aNextMilestone.mTime = nextMilestone.GetMillis(); 1.2258 + return true; 1.2259 + } 1.2260 + 1.2261 + return false; 1.2262 + } 1.2263 + 1.2264 + case STATE_POSTACTIVE: 1.2265 + return false; 1.2266 + } 1.2267 + MOZ_CRASH("Invalid element state"); 1.2268 +} 1.2269 + 1.2270 +void 1.2271 +nsSMILTimedElement::NotifyNewInterval() 1.2272 +{ 1.2273 + NS_ABORT_IF_FALSE(mCurrentInterval, 1.2274 + "Attempting to notify dependents of a new interval but the interval " 1.2275 + "is not set"); 1.2276 + 1.2277 + nsSMILTimeContainer* container = GetTimeContainer(); 1.2278 + if (container) { 1.2279 + container->SyncPauseTime(); 1.2280 + } 1.2281 + 1.2282 + NotifyTimeDependentsParams params = { this, container }; 1.2283 + mTimeDependents.EnumerateEntries(NotifyNewIntervalCallback, ¶ms); 1.2284 +} 1.2285 + 1.2286 +void 1.2287 +nsSMILTimedElement::NotifyChangedInterval(nsSMILInterval* aInterval, 1.2288 + bool aBeginObjectChanged, 1.2289 + bool aEndObjectChanged) 1.2290 +{ 1.2291 + NS_ABORT_IF_FALSE(aInterval, "Null interval for change notification"); 1.2292 + 1.2293 + nsSMILTimeContainer* container = GetTimeContainer(); 1.2294 + if (container) { 1.2295 + container->SyncPauseTime(); 1.2296 + } 1.2297 + 1.2298 + // Copy the instance times list since notifying the instance times can result 1.2299 + // in a chain reaction whereby our own interval gets deleted along with its 1.2300 + // instance times. 1.2301 + InstanceTimeList times; 1.2302 + aInterval->GetDependentTimes(times); 1.2303 + 1.2304 + for (uint32_t i = 0; i < times.Length(); ++i) { 1.2305 + times[i]->HandleChangedInterval(container, aBeginObjectChanged, 1.2306 + aEndObjectChanged); 1.2307 + } 1.2308 +} 1.2309 + 1.2310 +void 1.2311 +nsSMILTimedElement::FireTimeEventAsync(uint32_t aMsg, int32_t aDetail) 1.2312 +{ 1.2313 + if (!mAnimationElement) 1.2314 + return; 1.2315 + 1.2316 + nsCOMPtr<nsIRunnable> event = 1.2317 + new AsyncTimeEventRunner(mAnimationElement, aMsg, aDetail); 1.2318 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.2319 +} 1.2320 + 1.2321 +const nsSMILInstanceTime* 1.2322 +nsSMILTimedElement::GetEffectiveBeginInstance() const 1.2323 +{ 1.2324 + switch (mElementState) 1.2325 + { 1.2326 + case STATE_STARTUP: 1.2327 + return nullptr; 1.2328 + 1.2329 + case STATE_ACTIVE: 1.2330 + return mCurrentInterval->Begin(); 1.2331 + 1.2332 + case STATE_WAITING: 1.2333 + case STATE_POSTACTIVE: 1.2334 + { 1.2335 + const nsSMILInterval* prevInterval = GetPreviousInterval(); 1.2336 + return prevInterval ? prevInterval->Begin() : nullptr; 1.2337 + } 1.2338 + } 1.2339 + MOZ_CRASH("Invalid element state"); 1.2340 +} 1.2341 + 1.2342 +const nsSMILInterval* 1.2343 +nsSMILTimedElement::GetPreviousInterval() const 1.2344 +{ 1.2345 + return mOldIntervals.IsEmpty() 1.2346 + ? nullptr 1.2347 + : mOldIntervals[mOldIntervals.Length()-1].get(); 1.2348 +} 1.2349 + 1.2350 +bool 1.2351 +nsSMILTimedElement::HasClientInFillRange() const 1.2352 +{ 1.2353 + // Returns true if we have a client that is in the range where it will fill 1.2354 + return mClient && 1.2355 + ((mElementState != STATE_ACTIVE && HasPlayed()) || 1.2356 + (mElementState == STATE_ACTIVE && !mClient->IsActive())); 1.2357 +} 1.2358 + 1.2359 +bool 1.2360 +nsSMILTimedElement::EndHasEventConditions() const 1.2361 +{ 1.2362 + for (uint32_t i = 0; i < mEndSpecs.Length(); ++i) { 1.2363 + if (mEndSpecs[i]->IsEventBased()) 1.2364 + return true; 1.2365 + } 1.2366 + return false; 1.2367 +} 1.2368 + 1.2369 +bool 1.2370 +nsSMILTimedElement::AreEndTimesDependentOn( 1.2371 + const nsSMILInstanceTime* aBase) const 1.2372 +{ 1.2373 + if (mEndInstances.IsEmpty()) 1.2374 + return false; 1.2375 + 1.2376 + for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { 1.2377 + if (mEndInstances[i]->GetBaseTime() != aBase) { 1.2378 + return false; 1.2379 + } 1.2380 + } 1.2381 + return true; 1.2382 +} 1.2383 + 1.2384 +//---------------------------------------------------------------------- 1.2385 +// Hashtable callback functions 1.2386 + 1.2387 +/* static */ PLDHashOperator 1.2388 +nsSMILTimedElement::NotifyNewIntervalCallback(TimeValueSpecPtrKey* aKey, 1.2389 + void* aData) 1.2390 +{ 1.2391 + NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table"); 1.2392 + NS_ABORT_IF_FALSE(aKey->GetKey(), 1.2393 + "null nsSMILTimeValueSpec in set of time dependents"); 1.2394 + 1.2395 + NotifyTimeDependentsParams* params = 1.2396 + static_cast<NotifyTimeDependentsParams*>(aData); 1.2397 + NS_ABORT_IF_FALSE(params, "null data ptr while enumerating hashtable"); 1.2398 + nsSMILInterval* interval = params->mTimedElement->mCurrentInterval; 1.2399 + // It's possible that in notifying one new time dependent of a new interval 1.2400 + // that a chain reaction is triggered which results in the original interval 1.2401 + // disappearing. If that's the case we can skip sending further notifications. 1.2402 + if (!interval) 1.2403 + return PL_DHASH_STOP; 1.2404 + 1.2405 + nsSMILTimeValueSpec* spec = aKey->GetKey(); 1.2406 + spec->HandleNewInterval(*interval, params->mTimeContainer); 1.2407 + return PL_DHASH_NEXT; 1.2408 +}