Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "mozilla/DebugOnly.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "mozilla/BasicEvents.h" |
michael@0 | 9 | #include "mozilla/EventDispatcher.h" |
michael@0 | 10 | #include "mozilla/dom/SVGAnimationElement.h" |
michael@0 | 11 | #include "nsSMILTimedElement.h" |
michael@0 | 12 | #include "nsAttrValueInlines.h" |
michael@0 | 13 | #include "nsSMILAnimationFunction.h" |
michael@0 | 14 | #include "nsSMILTimeValue.h" |
michael@0 | 15 | #include "nsSMILTimeValueSpec.h" |
michael@0 | 16 | #include "nsSMILInstanceTime.h" |
michael@0 | 17 | #include "nsSMILParserUtils.h" |
michael@0 | 18 | #include "nsSMILTimeContainer.h" |
michael@0 | 19 | #include "nsGkAtoms.h" |
michael@0 | 20 | #include "nsReadableUtils.h" |
michael@0 | 21 | #include "nsMathUtils.h" |
michael@0 | 22 | #include "nsThreadUtils.h" |
michael@0 | 23 | #include "nsIPresShell.h" |
michael@0 | 24 | #include "prdtoa.h" |
michael@0 | 25 | #include "plstr.h" |
michael@0 | 26 | #include "prtime.h" |
michael@0 | 27 | #include "nsString.h" |
michael@0 | 28 | #include "mozilla/AutoRestore.h" |
michael@0 | 29 | #include "nsCharSeparatedTokenizer.h" |
michael@0 | 30 | #include <algorithm> |
michael@0 | 31 | |
michael@0 | 32 | using namespace mozilla; |
michael@0 | 33 | using namespace mozilla::dom; |
michael@0 | 34 | |
michael@0 | 35 | //---------------------------------------------------------------------- |
michael@0 | 36 | // Helper class: InstanceTimeComparator |
michael@0 | 37 | |
michael@0 | 38 | // Upon inserting an instance time into one of our instance time lists we assign |
michael@0 | 39 | // it a serial number. This allows us to sort the instance times in such a way |
michael@0 | 40 | // that where we have several equal instance times, the ones added later will |
michael@0 | 41 | // sort later. This means that when we call UpdateCurrentInterval during the |
michael@0 | 42 | // waiting state we won't unnecessarily change the begin instance. |
michael@0 | 43 | // |
michael@0 | 44 | // The serial number also means that every instance time has an unambiguous |
michael@0 | 45 | // position in the array so we can use RemoveElementSorted and the like. |
michael@0 | 46 | bool |
michael@0 | 47 | nsSMILTimedElement::InstanceTimeComparator::Equals( |
michael@0 | 48 | const nsSMILInstanceTime* aElem1, |
michael@0 | 49 | const nsSMILInstanceTime* aElem2) const |
michael@0 | 50 | { |
michael@0 | 51 | NS_ABORT_IF_FALSE(aElem1 && aElem2, |
michael@0 | 52 | "Trying to compare null instance time pointers"); |
michael@0 | 53 | NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), |
michael@0 | 54 | "Instance times have not been assigned serial numbers"); |
michael@0 | 55 | NS_ABORT_IF_FALSE(aElem1 == aElem2 || aElem1->Serial() != aElem2->Serial(), |
michael@0 | 56 | "Serial numbers are not unique"); |
michael@0 | 57 | |
michael@0 | 58 | return aElem1->Serial() == aElem2->Serial(); |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | bool |
michael@0 | 62 | nsSMILTimedElement::InstanceTimeComparator::LessThan( |
michael@0 | 63 | const nsSMILInstanceTime* aElem1, |
michael@0 | 64 | const nsSMILInstanceTime* aElem2) const |
michael@0 | 65 | { |
michael@0 | 66 | NS_ABORT_IF_FALSE(aElem1 && aElem2, |
michael@0 | 67 | "Trying to compare null instance time pointers"); |
michael@0 | 68 | NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), |
michael@0 | 69 | "Instance times have not been assigned serial numbers"); |
michael@0 | 70 | |
michael@0 | 71 | int8_t cmp = aElem1->Time().CompareTo(aElem2->Time()); |
michael@0 | 72 | return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | //---------------------------------------------------------------------- |
michael@0 | 76 | // Helper class: AsyncTimeEventRunner |
michael@0 | 77 | |
michael@0 | 78 | namespace |
michael@0 | 79 | { |
michael@0 | 80 | class AsyncTimeEventRunner : public nsRunnable |
michael@0 | 81 | { |
michael@0 | 82 | protected: |
michael@0 | 83 | nsRefPtr<nsIContent> mTarget; |
michael@0 | 84 | uint32_t mMsg; |
michael@0 | 85 | int32_t mDetail; |
michael@0 | 86 | |
michael@0 | 87 | public: |
michael@0 | 88 | AsyncTimeEventRunner(nsIContent* aTarget, uint32_t aMsg, int32_t aDetail) |
michael@0 | 89 | : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail) |
michael@0 | 90 | { |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | NS_IMETHOD Run() |
michael@0 | 94 | { |
michael@0 | 95 | InternalUIEvent event(true, mMsg); |
michael@0 | 96 | event.eventStructType = NS_SMIL_TIME_EVENT; |
michael@0 | 97 | event.detail = mDetail; |
michael@0 | 98 | |
michael@0 | 99 | nsPresContext* context = nullptr; |
michael@0 | 100 | nsIDocument* doc = mTarget->GetCurrentDoc(); |
michael@0 | 101 | if (doc) { |
michael@0 | 102 | nsCOMPtr<nsIPresShell> shell = doc->GetShell(); |
michael@0 | 103 | if (shell) { |
michael@0 | 104 | context = shell->GetPresContext(); |
michael@0 | 105 | } |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | return EventDispatcher::Dispatch(mTarget, context, &event); |
michael@0 | 109 | } |
michael@0 | 110 | }; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | //---------------------------------------------------------------------- |
michael@0 | 114 | // Helper class: AutoIntervalUpdateBatcher |
michael@0 | 115 | |
michael@0 | 116 | // Stack-based helper class to set the mDeferIntervalUpdates flag on an |
michael@0 | 117 | // nsSMILTimedElement and perform the UpdateCurrentInterval when the object is |
michael@0 | 118 | // destroyed. |
michael@0 | 119 | // |
michael@0 | 120 | // If several of these objects are allocated on the stack, the update will not |
michael@0 | 121 | // be performed until the last object for a given nsSMILTimedElement is |
michael@0 | 122 | // destroyed. |
michael@0 | 123 | class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdateBatcher |
michael@0 | 124 | { |
michael@0 | 125 | public: |
michael@0 | 126 | AutoIntervalUpdateBatcher(nsSMILTimedElement& aTimedElement) |
michael@0 | 127 | : mTimedElement(aTimedElement), |
michael@0 | 128 | mDidSetFlag(!aTimedElement.mDeferIntervalUpdates) |
michael@0 | 129 | { |
michael@0 | 130 | mTimedElement.mDeferIntervalUpdates = true; |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | ~AutoIntervalUpdateBatcher() |
michael@0 | 134 | { |
michael@0 | 135 | if (!mDidSetFlag) |
michael@0 | 136 | return; |
michael@0 | 137 | |
michael@0 | 138 | mTimedElement.mDeferIntervalUpdates = false; |
michael@0 | 139 | |
michael@0 | 140 | if (mTimedElement.mDoDeferredUpdate) { |
michael@0 | 141 | mTimedElement.mDoDeferredUpdate = false; |
michael@0 | 142 | mTimedElement.UpdateCurrentInterval(); |
michael@0 | 143 | } |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | private: |
michael@0 | 147 | nsSMILTimedElement& mTimedElement; |
michael@0 | 148 | bool mDidSetFlag; |
michael@0 | 149 | }; |
michael@0 | 150 | |
michael@0 | 151 | //---------------------------------------------------------------------- |
michael@0 | 152 | // Helper class: AutoIntervalUpdater |
michael@0 | 153 | |
michael@0 | 154 | // Stack-based helper class to call UpdateCurrentInterval when it is destroyed |
michael@0 | 155 | // which helps avoid bugs where we forget to call UpdateCurrentInterval in the |
michael@0 | 156 | // case of early returns (e.g. due to parse errors). |
michael@0 | 157 | // |
michael@0 | 158 | // This can be safely used in conjunction with AutoIntervalUpdateBatcher; any |
michael@0 | 159 | // calls to UpdateCurrentInterval made by this class will simply be deferred if |
michael@0 | 160 | // there is an AutoIntervalUpdateBatcher on the stack. |
michael@0 | 161 | class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater |
michael@0 | 162 | { |
michael@0 | 163 | public: |
michael@0 | 164 | AutoIntervalUpdater(nsSMILTimedElement& aTimedElement) |
michael@0 | 165 | : mTimedElement(aTimedElement) { } |
michael@0 | 166 | |
michael@0 | 167 | ~AutoIntervalUpdater() |
michael@0 | 168 | { |
michael@0 | 169 | mTimedElement.UpdateCurrentInterval(); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | private: |
michael@0 | 173 | nsSMILTimedElement& mTimedElement; |
michael@0 | 174 | }; |
michael@0 | 175 | |
michael@0 | 176 | //---------------------------------------------------------------------- |
michael@0 | 177 | // Templated helper functions |
michael@0 | 178 | |
michael@0 | 179 | // Selectively remove elements from an array of type |
michael@0 | 180 | // nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance. |
michael@0 | 181 | template <class TestFunctor> |
michael@0 | 182 | void |
michael@0 | 183 | nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray, |
michael@0 | 184 | TestFunctor& aTest) |
michael@0 | 185 | { |
michael@0 | 186 | InstanceTimeList newArray; |
michael@0 | 187 | for (uint32_t i = 0; i < aArray.Length(); ++i) { |
michael@0 | 188 | nsSMILInstanceTime* item = aArray[i].get(); |
michael@0 | 189 | if (aTest(item, i)) { |
michael@0 | 190 | // As per bugs 665334 and 669225 we should be careful not to remove the |
michael@0 | 191 | // instance time that corresponds to the previous interval's end time. |
michael@0 | 192 | // |
michael@0 | 193 | // Most functors supplied here fulfil this condition by checking if the |
michael@0 | 194 | // instance time is marked as "ShouldPreserve" and if so, not deleting it. |
michael@0 | 195 | // |
michael@0 | 196 | // However, when filtering instance times, we sometimes need to drop even |
michael@0 | 197 | // instance times marked as "ShouldPreserve". In that case we take special |
michael@0 | 198 | // care not to delete the end instance time of the previous interval. |
michael@0 | 199 | NS_ABORT_IF_FALSE(!GetPreviousInterval() || |
michael@0 | 200 | item != GetPreviousInterval()->End(), |
michael@0 | 201 | "Removing end instance time of previous interval"); |
michael@0 | 202 | item->Unlink(); |
michael@0 | 203 | } else { |
michael@0 | 204 | newArray.AppendElement(item); |
michael@0 | 205 | } |
michael@0 | 206 | } |
michael@0 | 207 | aArray.Clear(); |
michael@0 | 208 | aArray.SwapElements(newArray); |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | //---------------------------------------------------------------------- |
michael@0 | 212 | // Static members |
michael@0 | 213 | |
michael@0 | 214 | nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = { |
michael@0 | 215 | {"remove", FILL_REMOVE}, |
michael@0 | 216 | {"freeze", FILL_FREEZE}, |
michael@0 | 217 | {nullptr, 0} |
michael@0 | 218 | }; |
michael@0 | 219 | |
michael@0 | 220 | nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = { |
michael@0 | 221 | {"always", RESTART_ALWAYS}, |
michael@0 | 222 | {"whenNotActive", RESTART_WHENNOTACTIVE}, |
michael@0 | 223 | {"never", RESTART_NEVER}, |
michael@0 | 224 | {nullptr, 0} |
michael@0 | 225 | }; |
michael@0 | 226 | |
michael@0 | 227 | const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(INT64_MAX, false); |
michael@0 | 228 | |
michael@0 | 229 | // The thresholds at which point we start filtering intervals and instance times |
michael@0 | 230 | // indiscriminately. |
michael@0 | 231 | // See FilterIntervals and FilterInstanceTimes. |
michael@0 | 232 | const uint8_t nsSMILTimedElement::sMaxNumIntervals = 20; |
michael@0 | 233 | const uint8_t nsSMILTimedElement::sMaxNumInstanceTimes = 100; |
michael@0 | 234 | |
michael@0 | 235 | // Detect if we arrive in some sort of undetected recursive syncbase dependency |
michael@0 | 236 | // relationship |
michael@0 | 237 | const uint8_t nsSMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20; |
michael@0 | 238 | |
michael@0 | 239 | //---------------------------------------------------------------------- |
michael@0 | 240 | // Ctor, dtor |
michael@0 | 241 | |
michael@0 | 242 | nsSMILTimedElement::nsSMILTimedElement() |
michael@0 | 243 | : |
michael@0 | 244 | mAnimationElement(nullptr), |
michael@0 | 245 | mFillMode(FILL_REMOVE), |
michael@0 | 246 | mRestartMode(RESTART_ALWAYS), |
michael@0 | 247 | mInstanceSerialIndex(0), |
michael@0 | 248 | mClient(nullptr), |
michael@0 | 249 | mCurrentInterval(nullptr), |
michael@0 | 250 | mCurrentRepeatIteration(0), |
michael@0 | 251 | mPrevRegisteredMilestone(sMaxMilestone), |
michael@0 | 252 | mElementState(STATE_STARTUP), |
michael@0 | 253 | mSeekState(SEEK_NOT_SEEKING), |
michael@0 | 254 | mDeferIntervalUpdates(false), |
michael@0 | 255 | mDoDeferredUpdate(false), |
michael@0 | 256 | mDeleteCount(0), |
michael@0 | 257 | mUpdateIntervalRecursionDepth(0) |
michael@0 | 258 | { |
michael@0 | 259 | mSimpleDur.SetIndefinite(); |
michael@0 | 260 | mMin.SetMillis(0L); |
michael@0 | 261 | mMax.SetIndefinite(); |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | nsSMILTimedElement::~nsSMILTimedElement() |
michael@0 | 265 | { |
michael@0 | 266 | // Unlink all instance times from dependent intervals |
michael@0 | 267 | for (uint32_t i = 0; i < mBeginInstances.Length(); ++i) { |
michael@0 | 268 | mBeginInstances[i]->Unlink(); |
michael@0 | 269 | } |
michael@0 | 270 | mBeginInstances.Clear(); |
michael@0 | 271 | for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { |
michael@0 | 272 | mEndInstances[i]->Unlink(); |
michael@0 | 273 | } |
michael@0 | 274 | mEndInstances.Clear(); |
michael@0 | 275 | |
michael@0 | 276 | // Notify anyone listening to our intervals that they're gone |
michael@0 | 277 | // (We shouldn't get any callbacks from this because all our instance times |
michael@0 | 278 | // are now disassociated with any intervals) |
michael@0 | 279 | ClearIntervals(); |
michael@0 | 280 | |
michael@0 | 281 | // The following assertions are important in their own right (for checking |
michael@0 | 282 | // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers |
michael@0 | 283 | // to class so if they fail there's the possibility we might have dangling |
michael@0 | 284 | // pointers. |
michael@0 | 285 | NS_ABORT_IF_FALSE(!mDeferIntervalUpdates, |
michael@0 | 286 | "Interval updates should no longer be blocked when an nsSMILTimedElement " |
michael@0 | 287 | "disappears"); |
michael@0 | 288 | NS_ABORT_IF_FALSE(!mDoDeferredUpdate, |
michael@0 | 289 | "There should no longer be any pending updates when an " |
michael@0 | 290 | "nsSMILTimedElement disappears"); |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | void |
michael@0 | 294 | nsSMILTimedElement::SetAnimationElement(SVGAnimationElement* aElement) |
michael@0 | 295 | { |
michael@0 | 296 | NS_ABORT_IF_FALSE(aElement, "NULL owner element"); |
michael@0 | 297 | NS_ABORT_IF_FALSE(!mAnimationElement, "Re-setting owner"); |
michael@0 | 298 | mAnimationElement = aElement; |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | nsSMILTimeContainer* |
michael@0 | 302 | nsSMILTimedElement::GetTimeContainer() |
michael@0 | 303 | { |
michael@0 | 304 | return mAnimationElement ? mAnimationElement->GetTimeContainer() : nullptr; |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | dom::Element* |
michael@0 | 308 | nsSMILTimedElement::GetTargetElement() |
michael@0 | 309 | { |
michael@0 | 310 | return mAnimationElement ? |
michael@0 | 311 | mAnimationElement->GetTargetElementContent() : |
michael@0 | 312 | nullptr; |
michael@0 | 313 | } |
michael@0 | 314 | |
michael@0 | 315 | //---------------------------------------------------------------------- |
michael@0 | 316 | // nsIDOMElementTimeControl methods |
michael@0 | 317 | // |
michael@0 | 318 | // The definition of the ElementTimeControl interface differs between SMIL |
michael@0 | 319 | // Animation and SVG 1.1. In SMIL Animation all methods have a void return |
michael@0 | 320 | // type and the new instance time is simply added to the list and restart |
michael@0 | 321 | // semantics are applied as with any other instance time. In the SVG definition |
michael@0 | 322 | // the methods return a bool depending on the restart mode. |
michael@0 | 323 | // |
michael@0 | 324 | // This inconsistency has now been addressed by an erratum in SVG 1.1: |
michael@0 | 325 | // |
michael@0 | 326 | // http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface |
michael@0 | 327 | // |
michael@0 | 328 | // which favours the definition in SMIL, i.e. instance times are just added |
michael@0 | 329 | // without first checking the restart mode. |
michael@0 | 330 | |
michael@0 | 331 | nsresult |
michael@0 | 332 | nsSMILTimedElement::BeginElementAt(double aOffsetSeconds) |
michael@0 | 333 | { |
michael@0 | 334 | nsSMILTimeContainer* container = GetTimeContainer(); |
michael@0 | 335 | if (!container) |
michael@0 | 336 | return NS_ERROR_FAILURE; |
michael@0 | 337 | |
michael@0 | 338 | nsSMILTime currentTime = container->GetCurrentTime(); |
michael@0 | 339 | return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, true); |
michael@0 | 340 | } |
michael@0 | 341 | |
michael@0 | 342 | nsresult |
michael@0 | 343 | nsSMILTimedElement::EndElementAt(double aOffsetSeconds) |
michael@0 | 344 | { |
michael@0 | 345 | nsSMILTimeContainer* container = GetTimeContainer(); |
michael@0 | 346 | if (!container) |
michael@0 | 347 | return NS_ERROR_FAILURE; |
michael@0 | 348 | |
michael@0 | 349 | nsSMILTime currentTime = container->GetCurrentTime(); |
michael@0 | 350 | return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, false); |
michael@0 | 351 | } |
michael@0 | 352 | |
michael@0 | 353 | //---------------------------------------------------------------------- |
michael@0 | 354 | // nsSVGAnimationElement methods |
michael@0 | 355 | |
michael@0 | 356 | nsSMILTimeValue |
michael@0 | 357 | nsSMILTimedElement::GetStartTime() const |
michael@0 | 358 | { |
michael@0 | 359 | return mElementState == STATE_WAITING || mElementState == STATE_ACTIVE |
michael@0 | 360 | ? mCurrentInterval->Begin()->Time() |
michael@0 | 361 | : nsSMILTimeValue(); |
michael@0 | 362 | } |
michael@0 | 363 | |
michael@0 | 364 | //---------------------------------------------------------------------- |
michael@0 | 365 | // Hyperlinking support |
michael@0 | 366 | |
michael@0 | 367 | nsSMILTimeValue |
michael@0 | 368 | nsSMILTimedElement::GetHyperlinkTime() const |
michael@0 | 369 | { |
michael@0 | 370 | nsSMILTimeValue hyperlinkTime; // Default ctor creates unresolved time |
michael@0 | 371 | |
michael@0 | 372 | if (mElementState == STATE_ACTIVE) { |
michael@0 | 373 | hyperlinkTime = mCurrentInterval->Begin()->Time(); |
michael@0 | 374 | } else if (!mBeginInstances.IsEmpty()) { |
michael@0 | 375 | hyperlinkTime = mBeginInstances[0]->Time(); |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | return hyperlinkTime; |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | //---------------------------------------------------------------------- |
michael@0 | 382 | // nsSMILTimedElement |
michael@0 | 383 | |
michael@0 | 384 | void |
michael@0 | 385 | nsSMILTimedElement::AddInstanceTime(nsSMILInstanceTime* aInstanceTime, |
michael@0 | 386 | bool aIsBegin) |
michael@0 | 387 | { |
michael@0 | 388 | NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to add null instance time"); |
michael@0 | 389 | |
michael@0 | 390 | // Event-sensitivity: If an element is not active (but the parent time |
michael@0 | 391 | // container is), then events are only handled for begin specifications. |
michael@0 | 392 | if (mElementState != STATE_ACTIVE && !aIsBegin && |
michael@0 | 393 | aInstanceTime->IsDynamic()) |
michael@0 | 394 | { |
michael@0 | 395 | // No need to call Unlink here--dynamic instance times shouldn't be linked |
michael@0 | 396 | // to anything that's going to miss them |
michael@0 | 397 | NS_ABORT_IF_FALSE(!aInstanceTime->GetBaseInterval(), |
michael@0 | 398 | "Dynamic instance time has a base interval--we probably need to unlink" |
michael@0 | 399 | " it if we're not going to use it"); |
michael@0 | 400 | return; |
michael@0 | 401 | } |
michael@0 | 402 | |
michael@0 | 403 | aInstanceTime->SetSerial(++mInstanceSerialIndex); |
michael@0 | 404 | InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; |
michael@0 | 405 | nsRefPtr<nsSMILInstanceTime>* inserted = |
michael@0 | 406 | instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator()); |
michael@0 | 407 | if (!inserted) { |
michael@0 | 408 | NS_WARNING("Insufficient memory to insert instance time"); |
michael@0 | 409 | return; |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | UpdateCurrentInterval(); |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | void |
michael@0 | 416 | nsSMILTimedElement::UpdateInstanceTime(nsSMILInstanceTime* aInstanceTime, |
michael@0 | 417 | nsSMILTimeValue& aUpdatedTime, |
michael@0 | 418 | bool aIsBegin) |
michael@0 | 419 | { |
michael@0 | 420 | NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to update null instance time"); |
michael@0 | 421 | |
michael@0 | 422 | // The reason we update the time here and not in the nsSMILTimeValueSpec is |
michael@0 | 423 | // that it means we *could* re-sort more efficiently by doing a sorted remove |
michael@0 | 424 | // and insert but currently this doesn't seem to be necessary given how |
michael@0 | 425 | // infrequently we get these change notices. |
michael@0 | 426 | aInstanceTime->DependentUpdate(aUpdatedTime); |
michael@0 | 427 | InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; |
michael@0 | 428 | instanceList.Sort(InstanceTimeComparator()); |
michael@0 | 429 | |
michael@0 | 430 | // Generally speaking, UpdateCurrentInterval makes changes to the current |
michael@0 | 431 | // interval and sends changes notices itself. However, in this case because |
michael@0 | 432 | // instance times are shared between the instance time list and the intervals |
michael@0 | 433 | // we are effectively changing the current interval outside |
michael@0 | 434 | // UpdateCurrentInterval so we need to explicitly signal that we've made |
michael@0 | 435 | // a change. |
michael@0 | 436 | // |
michael@0 | 437 | // This wouldn't be necessary if we cloned instance times on adding them to |
michael@0 | 438 | // the current interval but this introduces other complications (particularly |
michael@0 | 439 | // detecting which instance time is being used to define the begin of the |
michael@0 | 440 | // current interval when doing a Reset). |
michael@0 | 441 | bool changedCurrentInterval = mCurrentInterval && |
michael@0 | 442 | (mCurrentInterval->Begin() == aInstanceTime || |
michael@0 | 443 | mCurrentInterval->End() == aInstanceTime); |
michael@0 | 444 | |
michael@0 | 445 | UpdateCurrentInterval(changedCurrentInterval); |
michael@0 | 446 | } |
michael@0 | 447 | |
michael@0 | 448 | void |
michael@0 | 449 | nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime, |
michael@0 | 450 | bool aIsBegin) |
michael@0 | 451 | { |
michael@0 | 452 | NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to remove null instance time"); |
michael@0 | 453 | |
michael@0 | 454 | // If the instance time should be kept (because it is or was the fixed end |
michael@0 | 455 | // point of an interval) then just disassociate it from the creator. |
michael@0 | 456 | if (aInstanceTime->ShouldPreserve()) { |
michael@0 | 457 | aInstanceTime->Unlink(); |
michael@0 | 458 | return; |
michael@0 | 459 | } |
michael@0 | 460 | |
michael@0 | 461 | InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; |
michael@0 | 462 | mozilla::DebugOnly<bool> found = |
michael@0 | 463 | instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator()); |
michael@0 | 464 | NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete"); |
michael@0 | 465 | |
michael@0 | 466 | UpdateCurrentInterval(); |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | namespace |
michael@0 | 470 | { |
michael@0 | 471 | class MOZ_STACK_CLASS RemoveByCreator |
michael@0 | 472 | { |
michael@0 | 473 | public: |
michael@0 | 474 | RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator) |
michael@0 | 475 | { } |
michael@0 | 476 | |
michael@0 | 477 | bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) |
michael@0 | 478 | { |
michael@0 | 479 | if (aInstanceTime->GetCreator() != mCreator) |
michael@0 | 480 | return false; |
michael@0 | 481 | |
michael@0 | 482 | // If the instance time should be kept (because it is or was the fixed end |
michael@0 | 483 | // point of an interval) then just disassociate it from the creator. |
michael@0 | 484 | if (aInstanceTime->ShouldPreserve()) { |
michael@0 | 485 | aInstanceTime->Unlink(); |
michael@0 | 486 | return false; |
michael@0 | 487 | } |
michael@0 | 488 | |
michael@0 | 489 | return true; |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | private: |
michael@0 | 493 | const nsSMILTimeValueSpec* mCreator; |
michael@0 | 494 | }; |
michael@0 | 495 | } |
michael@0 | 496 | |
michael@0 | 497 | void |
michael@0 | 498 | nsSMILTimedElement::RemoveInstanceTimesForCreator( |
michael@0 | 499 | const nsSMILTimeValueSpec* aCreator, bool aIsBegin) |
michael@0 | 500 | { |
michael@0 | 501 | NS_ABORT_IF_FALSE(aCreator, "Creator not set"); |
michael@0 | 502 | |
michael@0 | 503 | InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; |
michael@0 | 504 | RemoveByCreator removeByCreator(aCreator); |
michael@0 | 505 | RemoveInstanceTimes(instances, removeByCreator); |
michael@0 | 506 | |
michael@0 | 507 | UpdateCurrentInterval(); |
michael@0 | 508 | } |
michael@0 | 509 | |
michael@0 | 510 | void |
michael@0 | 511 | nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient) |
michael@0 | 512 | { |
michael@0 | 513 | // |
michael@0 | 514 | // No need to check for nullptr. A nullptr parameter simply means to remove the |
michael@0 | 515 | // previous client which we do by setting to nullptr anyway. |
michael@0 | 516 | // |
michael@0 | 517 | |
michael@0 | 518 | mClient = aClient; |
michael@0 | 519 | } |
michael@0 | 520 | |
michael@0 | 521 | void |
michael@0 | 522 | nsSMILTimedElement::SampleAt(nsSMILTime aContainerTime) |
michael@0 | 523 | { |
michael@0 | 524 | // Milestones are cleared before a sample |
michael@0 | 525 | mPrevRegisteredMilestone = sMaxMilestone; |
michael@0 | 526 | |
michael@0 | 527 | DoSampleAt(aContainerTime, false); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | void |
michael@0 | 531 | nsSMILTimedElement::SampleEndAt(nsSMILTime aContainerTime) |
michael@0 | 532 | { |
michael@0 | 533 | // Milestones are cleared before a sample |
michael@0 | 534 | mPrevRegisteredMilestone = sMaxMilestone; |
michael@0 | 535 | |
michael@0 | 536 | // If the current interval changes, we don't bother trying to remove any old |
michael@0 | 537 | // milestones we'd registered. So it's possible to get a call here to end an |
michael@0 | 538 | // interval at a time that no longer reflects the end of the current interval. |
michael@0 | 539 | // |
michael@0 | 540 | // For now we just check that we're actually in an interval but note that the |
michael@0 | 541 | // initial sample we use to initialise the model is an end sample. This is |
michael@0 | 542 | // because we want to resolve all the instance times before committing to an |
michael@0 | 543 | // initial interval. Therefore an end sample from the startup state is also |
michael@0 | 544 | // acceptable. |
michael@0 | 545 | if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) { |
michael@0 | 546 | DoSampleAt(aContainerTime, true); // End sample |
michael@0 | 547 | } else { |
michael@0 | 548 | // Even if this was an unnecessary milestone sample we want to be sure that |
michael@0 | 549 | // our next real milestone is registered. |
michael@0 | 550 | RegisterMilestone(); |
michael@0 | 551 | } |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | void |
michael@0 | 555 | nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly) |
michael@0 | 556 | { |
michael@0 | 557 | NS_ABORT_IF_FALSE(mAnimationElement, |
michael@0 | 558 | "Got sample before being registered with an animation element"); |
michael@0 | 559 | NS_ABORT_IF_FALSE(GetTimeContainer(), |
michael@0 | 560 | "Got sample without being registered with a time container"); |
michael@0 | 561 | |
michael@0 | 562 | // This could probably happen if we later implement externalResourcesRequired |
michael@0 | 563 | // (bug 277955) and whilst waiting for those resources (and the animation to |
michael@0 | 564 | // start) we transfer a node from another document fragment that has already |
michael@0 | 565 | // started. In such a case we might receive milestone samples registered with |
michael@0 | 566 | // the already active container. |
michael@0 | 567 | if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) |
michael@0 | 568 | return; |
michael@0 | 569 | |
michael@0 | 570 | // We use an end-sample to start animation since an end-sample lets us |
michael@0 | 571 | // tentatively create an interval without committing to it (by transitioning |
michael@0 | 572 | // to the ACTIVE state) and this is necessary because we might have |
michael@0 | 573 | // dependencies on other animations that are yet to start. After these |
michael@0 | 574 | // other animations start, it may be necessary to revise our initial interval. |
michael@0 | 575 | // |
michael@0 | 576 | // However, sometimes instead of an end-sample we can get a regular sample |
michael@0 | 577 | // during STARTUP state. This can happen, for example, if we register |
michael@0 | 578 | // a milestone before time t=0 and are then re-bound to the tree (which sends |
michael@0 | 579 | // us back to the STARTUP state). In such a case we should just ignore the |
michael@0 | 580 | // sample and wait for our real initial sample which will be an end-sample. |
michael@0 | 581 | if (mElementState == STATE_STARTUP && !aEndOnly) |
michael@0 | 582 | return; |
michael@0 | 583 | |
michael@0 | 584 | bool finishedSeek = false; |
michael@0 | 585 | if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) { |
michael@0 | 586 | mSeekState = mElementState == STATE_ACTIVE ? |
michael@0 | 587 | SEEK_FORWARD_FROM_ACTIVE : |
michael@0 | 588 | SEEK_FORWARD_FROM_INACTIVE; |
michael@0 | 589 | } else if (mSeekState != SEEK_NOT_SEEKING && |
michael@0 | 590 | !GetTimeContainer()->IsSeeking()) { |
michael@0 | 591 | finishedSeek = true; |
michael@0 | 592 | } |
michael@0 | 593 | |
michael@0 | 594 | bool stateChanged; |
michael@0 | 595 | nsSMILTimeValue sampleTime(aContainerTime); |
michael@0 | 596 | |
michael@0 | 597 | do { |
michael@0 | 598 | #ifdef DEBUG |
michael@0 | 599 | // Check invariant |
michael@0 | 600 | if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) { |
michael@0 | 601 | NS_ABORT_IF_FALSE(!mCurrentInterval, |
michael@0 | 602 | "Shouldn't have current interval in startup or postactive states"); |
michael@0 | 603 | } else { |
michael@0 | 604 | NS_ABORT_IF_FALSE(mCurrentInterval, |
michael@0 | 605 | "Should have current interval in waiting and active states"); |
michael@0 | 606 | } |
michael@0 | 607 | #endif |
michael@0 | 608 | |
michael@0 | 609 | stateChanged = false; |
michael@0 | 610 | |
michael@0 | 611 | switch (mElementState) |
michael@0 | 612 | { |
michael@0 | 613 | case STATE_STARTUP: |
michael@0 | 614 | { |
michael@0 | 615 | nsSMILInterval firstInterval; |
michael@0 | 616 | mElementState = GetNextInterval(nullptr, nullptr, nullptr, firstInterval) |
michael@0 | 617 | ? STATE_WAITING |
michael@0 | 618 | : STATE_POSTACTIVE; |
michael@0 | 619 | stateChanged = true; |
michael@0 | 620 | if (mElementState == STATE_WAITING) { |
michael@0 | 621 | mCurrentInterval = new nsSMILInterval(firstInterval); |
michael@0 | 622 | NotifyNewInterval(); |
michael@0 | 623 | } |
michael@0 | 624 | } |
michael@0 | 625 | break; |
michael@0 | 626 | |
michael@0 | 627 | case STATE_WAITING: |
michael@0 | 628 | { |
michael@0 | 629 | if (mCurrentInterval->Begin()->Time() <= sampleTime) { |
michael@0 | 630 | mElementState = STATE_ACTIVE; |
michael@0 | 631 | mCurrentInterval->FixBegin(); |
michael@0 | 632 | if (mClient) { |
michael@0 | 633 | mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis()); |
michael@0 | 634 | } |
michael@0 | 635 | if (mSeekState == SEEK_NOT_SEEKING) { |
michael@0 | 636 | FireTimeEventAsync(NS_SMIL_BEGIN, 0); |
michael@0 | 637 | } |
michael@0 | 638 | if (HasPlayed()) { |
michael@0 | 639 | Reset(); // Apply restart behaviour |
michael@0 | 640 | // The call to Reset() may mean that the end point of our current |
michael@0 | 641 | // interval should be changed and so we should update the interval |
michael@0 | 642 | // now. However, calling UpdateCurrentInterval could result in the |
michael@0 | 643 | // interval getting deleted (perhaps through some web of syncbase |
michael@0 | 644 | // dependencies) therefore we make updating the interval the last |
michael@0 | 645 | // thing we do. There is no guarantee that mCurrentInterval is set |
michael@0 | 646 | // after this. |
michael@0 | 647 | UpdateCurrentInterval(); |
michael@0 | 648 | } |
michael@0 | 649 | stateChanged = true; |
michael@0 | 650 | } |
michael@0 | 651 | } |
michael@0 | 652 | break; |
michael@0 | 653 | |
michael@0 | 654 | case STATE_ACTIVE: |
michael@0 | 655 | { |
michael@0 | 656 | // Ending early will change the interval but we don't notify dependents |
michael@0 | 657 | // of the change until we have closed off the current interval (since we |
michael@0 | 658 | // don't want dependencies to un-end our early end). |
michael@0 | 659 | bool didApplyEarlyEnd = ApplyEarlyEnd(sampleTime); |
michael@0 | 660 | |
michael@0 | 661 | if (mCurrentInterval->End()->Time() <= sampleTime) { |
michael@0 | 662 | nsSMILInterval newInterval; |
michael@0 | 663 | mElementState = |
michael@0 | 664 | GetNextInterval(mCurrentInterval, nullptr, nullptr, newInterval) |
michael@0 | 665 | ? STATE_WAITING |
michael@0 | 666 | : STATE_POSTACTIVE; |
michael@0 | 667 | if (mClient) { |
michael@0 | 668 | mClient->Inactivate(mFillMode == FILL_FREEZE); |
michael@0 | 669 | } |
michael@0 | 670 | mCurrentInterval->FixEnd(); |
michael@0 | 671 | if (mSeekState == SEEK_NOT_SEEKING) { |
michael@0 | 672 | FireTimeEventAsync(NS_SMIL_END, 0); |
michael@0 | 673 | } |
michael@0 | 674 | mCurrentRepeatIteration = 0; |
michael@0 | 675 | mOldIntervals.AppendElement(mCurrentInterval.forget()); |
michael@0 | 676 | SampleFillValue(); |
michael@0 | 677 | if (mElementState == STATE_WAITING) { |
michael@0 | 678 | mCurrentInterval = new nsSMILInterval(newInterval); |
michael@0 | 679 | } |
michael@0 | 680 | // We are now in a consistent state to dispatch notifications |
michael@0 | 681 | if (didApplyEarlyEnd) { |
michael@0 | 682 | NotifyChangedInterval( |
michael@0 | 683 | mOldIntervals[mOldIntervals.Length() - 1], false, true); |
michael@0 | 684 | } |
michael@0 | 685 | if (mElementState == STATE_WAITING) { |
michael@0 | 686 | NotifyNewInterval(); |
michael@0 | 687 | } |
michael@0 | 688 | FilterHistory(); |
michael@0 | 689 | stateChanged = true; |
michael@0 | 690 | } else { |
michael@0 | 691 | NS_ABORT_IF_FALSE(!didApplyEarlyEnd, |
michael@0 | 692 | "We got an early end, but didn't end"); |
michael@0 | 693 | nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis(); |
michael@0 | 694 | NS_ASSERTION(aContainerTime >= beginTime, |
michael@0 | 695 | "Sample time should not precede current interval"); |
michael@0 | 696 | nsSMILTime activeTime = aContainerTime - beginTime; |
michael@0 | 697 | |
michael@0 | 698 | // The 'min' attribute can cause the active interval to be longer than |
michael@0 | 699 | // the 'repeating interval'. |
michael@0 | 700 | // In that extended period we apply the fill mode. |
michael@0 | 701 | if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) { |
michael@0 | 702 | if (mClient && mClient->IsActive()) { |
michael@0 | 703 | mClient->Inactivate(mFillMode == FILL_FREEZE); |
michael@0 | 704 | } |
michael@0 | 705 | SampleFillValue(); |
michael@0 | 706 | } else { |
michael@0 | 707 | SampleSimpleTime(activeTime); |
michael@0 | 708 | |
michael@0 | 709 | // We register our repeat times as milestones (except when we're |
michael@0 | 710 | // seeking) so we should get a sample at exactly the time we repeat. |
michael@0 | 711 | // (And even when we are seeking we want to update |
michael@0 | 712 | // mCurrentRepeatIteration so we do that first before testing the |
michael@0 | 713 | // seek state.) |
michael@0 | 714 | uint32_t prevRepeatIteration = mCurrentRepeatIteration; |
michael@0 | 715 | if ( |
michael@0 | 716 | ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 && |
michael@0 | 717 | mCurrentRepeatIteration != prevRepeatIteration && |
michael@0 | 718 | mCurrentRepeatIteration && |
michael@0 | 719 | mSeekState == SEEK_NOT_SEEKING) { |
michael@0 | 720 | FireTimeEventAsync(NS_SMIL_REPEAT, |
michael@0 | 721 | static_cast<int32_t>(mCurrentRepeatIteration)); |
michael@0 | 722 | } |
michael@0 | 723 | } |
michael@0 | 724 | } |
michael@0 | 725 | } |
michael@0 | 726 | break; |
michael@0 | 727 | |
michael@0 | 728 | case STATE_POSTACTIVE: |
michael@0 | 729 | break; |
michael@0 | 730 | } |
michael@0 | 731 | |
michael@0 | 732 | // Generally we continue driving the state machine so long as we have changed |
michael@0 | 733 | // state. However, for end samples we only drive the state machine as far as |
michael@0 | 734 | // the waiting or postactive state because we don't want to commit to any new |
michael@0 | 735 | // interval (by transitioning to the active state) until all the end samples |
michael@0 | 736 | // have finished and we then have complete information about the available |
michael@0 | 737 | // instance times upon which to base our next interval. |
michael@0 | 738 | } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING && |
michael@0 | 739 | mElementState != STATE_POSTACTIVE))); |
michael@0 | 740 | |
michael@0 | 741 | if (finishedSeek) { |
michael@0 | 742 | DoPostSeek(); |
michael@0 | 743 | } |
michael@0 | 744 | RegisterMilestone(); |
michael@0 | 745 | } |
michael@0 | 746 | |
michael@0 | 747 | void |
michael@0 | 748 | nsSMILTimedElement::HandleContainerTimeChange() |
michael@0 | 749 | { |
michael@0 | 750 | // In future we could possibly introduce a separate change notice for time |
michael@0 | 751 | // container changes and only notify those dependents who live in other time |
michael@0 | 752 | // containers. For now we don't bother because when we re-resolve the time in |
michael@0 | 753 | // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we |
michael@0 | 754 | // won't go any further. |
michael@0 | 755 | if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) { |
michael@0 | 756 | NotifyChangedInterval(mCurrentInterval, false, false); |
michael@0 | 757 | } |
michael@0 | 758 | } |
michael@0 | 759 | |
michael@0 | 760 | namespace |
michael@0 | 761 | { |
michael@0 | 762 | bool |
michael@0 | 763 | RemoveNonDynamic(nsSMILInstanceTime* aInstanceTime) |
michael@0 | 764 | { |
michael@0 | 765 | // Generally dynamically-generated instance times (DOM calls, event-based |
michael@0 | 766 | // times) are not associated with their creator nsSMILTimeValueSpec since |
michael@0 | 767 | // they may outlive them. |
michael@0 | 768 | NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() || |
michael@0 | 769 | !aInstanceTime->GetCreator(), |
michael@0 | 770 | "Dynamic instance time should be unlinked from its creator"); |
michael@0 | 771 | return !aInstanceTime->IsDynamic() && !aInstanceTime->ShouldPreserve(); |
michael@0 | 772 | } |
michael@0 | 773 | } |
michael@0 | 774 | |
michael@0 | 775 | void |
michael@0 | 776 | nsSMILTimedElement::Rewind() |
michael@0 | 777 | { |
michael@0 | 778 | NS_ABORT_IF_FALSE(mAnimationElement, |
michael@0 | 779 | "Got rewind request before being attached to an animation element"); |
michael@0 | 780 | |
michael@0 | 781 | // It's possible to get a rewind request whilst we're already in the middle of |
michael@0 | 782 | // a backwards seek. This can happen when we're performing tree surgery and |
michael@0 | 783 | // seeking containers at the same time because we can end up requesting |
michael@0 | 784 | // a local rewind on an element after binding it to a new container and then |
michael@0 | 785 | // performing a rewind on that container as a whole without sampling in |
michael@0 | 786 | // between. |
michael@0 | 787 | // |
michael@0 | 788 | // However, it should currently be impossible to get a rewind in the middle of |
michael@0 | 789 | // a forwards seek since forwards seeks are detected and processed within the |
michael@0 | 790 | // same (re)sample. |
michael@0 | 791 | if (mSeekState == SEEK_NOT_SEEKING) { |
michael@0 | 792 | mSeekState = mElementState == STATE_ACTIVE ? |
michael@0 | 793 | SEEK_BACKWARD_FROM_ACTIVE : |
michael@0 | 794 | SEEK_BACKWARD_FROM_INACTIVE; |
michael@0 | 795 | } |
michael@0 | 796 | NS_ABORT_IF_FALSE(mSeekState == SEEK_BACKWARD_FROM_INACTIVE || |
michael@0 | 797 | mSeekState == SEEK_BACKWARD_FROM_ACTIVE, |
michael@0 | 798 | "Rewind in the middle of a forwards seek?"); |
michael@0 | 799 | |
michael@0 | 800 | // Putting us in the startup state will ensure we skip doing any interval |
michael@0 | 801 | // updates |
michael@0 | 802 | mElementState = STATE_STARTUP; |
michael@0 | 803 | ClearIntervals(); |
michael@0 | 804 | |
michael@0 | 805 | UnsetBeginSpec(RemoveNonDynamic); |
michael@0 | 806 | UnsetEndSpec(RemoveNonDynamic); |
michael@0 | 807 | |
michael@0 | 808 | if (mClient) { |
michael@0 | 809 | mClient->Inactivate(false); |
michael@0 | 810 | } |
michael@0 | 811 | |
michael@0 | 812 | if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) { |
michael@0 | 813 | nsAutoString attValue; |
michael@0 | 814 | mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue); |
michael@0 | 815 | SetBeginSpec(attValue, mAnimationElement, RemoveNonDynamic); |
michael@0 | 816 | } |
michael@0 | 817 | |
michael@0 | 818 | if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) { |
michael@0 | 819 | nsAutoString attValue; |
michael@0 | 820 | mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue); |
michael@0 | 821 | SetEndSpec(attValue, mAnimationElement, RemoveNonDynamic); |
michael@0 | 822 | } |
michael@0 | 823 | |
michael@0 | 824 | mPrevRegisteredMilestone = sMaxMilestone; |
michael@0 | 825 | RegisterMilestone(); |
michael@0 | 826 | NS_ABORT_IF_FALSE(!mCurrentInterval, |
michael@0 | 827 | "Current interval is set at end of rewind"); |
michael@0 | 828 | } |
michael@0 | 829 | |
michael@0 | 830 | namespace |
michael@0 | 831 | { |
michael@0 | 832 | bool |
michael@0 | 833 | RemoveNonDOM(nsSMILInstanceTime* aInstanceTime) |
michael@0 | 834 | { |
michael@0 | 835 | return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve(); |
michael@0 | 836 | } |
michael@0 | 837 | } |
michael@0 | 838 | |
michael@0 | 839 | bool |
michael@0 | 840 | nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue, |
michael@0 | 841 | nsAttrValue& aResult, |
michael@0 | 842 | Element* aContextNode, |
michael@0 | 843 | nsresult* aParseResult) |
michael@0 | 844 | { |
michael@0 | 845 | bool foundMatch = true; |
michael@0 | 846 | nsresult parseResult = NS_OK; |
michael@0 | 847 | |
michael@0 | 848 | if (aAttribute == nsGkAtoms::begin) { |
michael@0 | 849 | parseResult = SetBeginSpec(aValue, aContextNode, RemoveNonDOM); |
michael@0 | 850 | } else if (aAttribute == nsGkAtoms::dur) { |
michael@0 | 851 | parseResult = SetSimpleDuration(aValue); |
michael@0 | 852 | } else if (aAttribute == nsGkAtoms::end) { |
michael@0 | 853 | parseResult = SetEndSpec(aValue, aContextNode, RemoveNonDOM); |
michael@0 | 854 | } else if (aAttribute == nsGkAtoms::fill) { |
michael@0 | 855 | parseResult = SetFillMode(aValue); |
michael@0 | 856 | } else if (aAttribute == nsGkAtoms::max) { |
michael@0 | 857 | parseResult = SetMax(aValue); |
michael@0 | 858 | } else if (aAttribute == nsGkAtoms::min) { |
michael@0 | 859 | parseResult = SetMin(aValue); |
michael@0 | 860 | } else if (aAttribute == nsGkAtoms::repeatCount) { |
michael@0 | 861 | parseResult = SetRepeatCount(aValue); |
michael@0 | 862 | } else if (aAttribute == nsGkAtoms::repeatDur) { |
michael@0 | 863 | parseResult = SetRepeatDur(aValue); |
michael@0 | 864 | } else if (aAttribute == nsGkAtoms::restart) { |
michael@0 | 865 | parseResult = SetRestart(aValue); |
michael@0 | 866 | } else { |
michael@0 | 867 | foundMatch = false; |
michael@0 | 868 | } |
michael@0 | 869 | |
michael@0 | 870 | if (foundMatch) { |
michael@0 | 871 | aResult.SetTo(aValue); |
michael@0 | 872 | if (aParseResult) { |
michael@0 | 873 | *aParseResult = parseResult; |
michael@0 | 874 | } |
michael@0 | 875 | } |
michael@0 | 876 | |
michael@0 | 877 | return foundMatch; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | bool |
michael@0 | 881 | nsSMILTimedElement::UnsetAttr(nsIAtom* aAttribute) |
michael@0 | 882 | { |
michael@0 | 883 | bool foundMatch = true; |
michael@0 | 884 | |
michael@0 | 885 | if (aAttribute == nsGkAtoms::begin) { |
michael@0 | 886 | UnsetBeginSpec(RemoveNonDOM); |
michael@0 | 887 | } else if (aAttribute == nsGkAtoms::dur) { |
michael@0 | 888 | UnsetSimpleDuration(); |
michael@0 | 889 | } else if (aAttribute == nsGkAtoms::end) { |
michael@0 | 890 | UnsetEndSpec(RemoveNonDOM); |
michael@0 | 891 | } else if (aAttribute == nsGkAtoms::fill) { |
michael@0 | 892 | UnsetFillMode(); |
michael@0 | 893 | } else if (aAttribute == nsGkAtoms::max) { |
michael@0 | 894 | UnsetMax(); |
michael@0 | 895 | } else if (aAttribute == nsGkAtoms::min) { |
michael@0 | 896 | UnsetMin(); |
michael@0 | 897 | } else if (aAttribute == nsGkAtoms::repeatCount) { |
michael@0 | 898 | UnsetRepeatCount(); |
michael@0 | 899 | } else if (aAttribute == nsGkAtoms::repeatDur) { |
michael@0 | 900 | UnsetRepeatDur(); |
michael@0 | 901 | } else if (aAttribute == nsGkAtoms::restart) { |
michael@0 | 902 | UnsetRestart(); |
michael@0 | 903 | } else { |
michael@0 | 904 | foundMatch = false; |
michael@0 | 905 | } |
michael@0 | 906 | |
michael@0 | 907 | return foundMatch; |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | //---------------------------------------------------------------------- |
michael@0 | 911 | // Setters and unsetters |
michael@0 | 912 | |
michael@0 | 913 | nsresult |
michael@0 | 914 | nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec, |
michael@0 | 915 | Element* aContextNode, |
michael@0 | 916 | RemovalTestFunction aRemove) |
michael@0 | 917 | { |
michael@0 | 918 | return SetBeginOrEndSpec(aBeginSpec, aContextNode, true /*isBegin*/, |
michael@0 | 919 | aRemove); |
michael@0 | 920 | } |
michael@0 | 921 | |
michael@0 | 922 | void |
michael@0 | 923 | nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove) |
michael@0 | 924 | { |
michael@0 | 925 | ClearSpecs(mBeginSpecs, mBeginInstances, aRemove); |
michael@0 | 926 | UpdateCurrentInterval(); |
michael@0 | 927 | } |
michael@0 | 928 | |
michael@0 | 929 | nsresult |
michael@0 | 930 | nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec, |
michael@0 | 931 | Element* aContextNode, |
michael@0 | 932 | RemovalTestFunction aRemove) |
michael@0 | 933 | { |
michael@0 | 934 | return SetBeginOrEndSpec(aEndSpec, aContextNode, false /*!isBegin*/, |
michael@0 | 935 | aRemove); |
michael@0 | 936 | } |
michael@0 | 937 | |
michael@0 | 938 | void |
michael@0 | 939 | nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove) |
michael@0 | 940 | { |
michael@0 | 941 | ClearSpecs(mEndSpecs, mEndInstances, aRemove); |
michael@0 | 942 | UpdateCurrentInterval(); |
michael@0 | 943 | } |
michael@0 | 944 | |
michael@0 | 945 | nsresult |
michael@0 | 946 | nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec) |
michael@0 | 947 | { |
michael@0 | 948 | // Update the current interval before returning |
michael@0 | 949 | AutoIntervalUpdater updater(*this); |
michael@0 | 950 | |
michael@0 | 951 | nsSMILTimeValue duration; |
michael@0 | 952 | const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec); |
michael@0 | 953 | |
michael@0 | 954 | // SVG-specific: "For SVG's animation elements, if "media" is specified, the |
michael@0 | 955 | // attribute will be ignored." (SVG 1.1, section 19.2.6) |
michael@0 | 956 | if (dur.EqualsLiteral("media") || dur.EqualsLiteral("indefinite")) { |
michael@0 | 957 | duration.SetIndefinite(); |
michael@0 | 958 | } else { |
michael@0 | 959 | if (!nsSMILParserUtils::ParseClockValue(dur, &duration) || |
michael@0 | 960 | duration.GetMillis() == 0L) { |
michael@0 | 961 | mSimpleDur.SetIndefinite(); |
michael@0 | 962 | return NS_ERROR_FAILURE; |
michael@0 | 963 | } |
michael@0 | 964 | } |
michael@0 | 965 | // mSimpleDur should never be unresolved. ParseClockValue will either set |
michael@0 | 966 | // duration to resolved or will return false. |
michael@0 | 967 | NS_ABORT_IF_FALSE(duration.IsResolved(), |
michael@0 | 968 | "Setting unresolved simple duration"); |
michael@0 | 969 | |
michael@0 | 970 | mSimpleDur = duration; |
michael@0 | 971 | |
michael@0 | 972 | return NS_OK; |
michael@0 | 973 | } |
michael@0 | 974 | |
michael@0 | 975 | void |
michael@0 | 976 | nsSMILTimedElement::UnsetSimpleDuration() |
michael@0 | 977 | { |
michael@0 | 978 | mSimpleDur.SetIndefinite(); |
michael@0 | 979 | UpdateCurrentInterval(); |
michael@0 | 980 | } |
michael@0 | 981 | |
michael@0 | 982 | nsresult |
michael@0 | 983 | nsSMILTimedElement::SetMin(const nsAString& aMinSpec) |
michael@0 | 984 | { |
michael@0 | 985 | // Update the current interval before returning |
michael@0 | 986 | AutoIntervalUpdater updater(*this); |
michael@0 | 987 | |
michael@0 | 988 | nsSMILTimeValue duration; |
michael@0 | 989 | const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec); |
michael@0 | 990 | |
michael@0 | 991 | if (min.EqualsLiteral("media")) { |
michael@0 | 992 | duration.SetMillis(0L); |
michael@0 | 993 | } else { |
michael@0 | 994 | if (!nsSMILParserUtils::ParseClockValue(min, &duration)) { |
michael@0 | 995 | mMin.SetMillis(0L); |
michael@0 | 996 | return NS_ERROR_FAILURE; |
michael@0 | 997 | } |
michael@0 | 998 | } |
michael@0 | 999 | |
michael@0 | 1000 | NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration"); |
michael@0 | 1001 | |
michael@0 | 1002 | mMin = duration; |
michael@0 | 1003 | |
michael@0 | 1004 | return NS_OK; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | void |
michael@0 | 1008 | nsSMILTimedElement::UnsetMin() |
michael@0 | 1009 | { |
michael@0 | 1010 | mMin.SetMillis(0L); |
michael@0 | 1011 | UpdateCurrentInterval(); |
michael@0 | 1012 | } |
michael@0 | 1013 | |
michael@0 | 1014 | nsresult |
michael@0 | 1015 | nsSMILTimedElement::SetMax(const nsAString& aMaxSpec) |
michael@0 | 1016 | { |
michael@0 | 1017 | // Update the current interval before returning |
michael@0 | 1018 | AutoIntervalUpdater updater(*this); |
michael@0 | 1019 | |
michael@0 | 1020 | nsSMILTimeValue duration; |
michael@0 | 1021 | const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec); |
michael@0 | 1022 | |
michael@0 | 1023 | if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) { |
michael@0 | 1024 | duration.SetIndefinite(); |
michael@0 | 1025 | } else { |
michael@0 | 1026 | if (!nsSMILParserUtils::ParseClockValue(max, &duration) || |
michael@0 | 1027 | duration.GetMillis() == 0L) { |
michael@0 | 1028 | mMax.SetIndefinite(); |
michael@0 | 1029 | return NS_ERROR_FAILURE; |
michael@0 | 1030 | } |
michael@0 | 1031 | NS_ABORT_IF_FALSE(duration.GetMillis() > 0L, "Invalid duration"); |
michael@0 | 1032 | } |
michael@0 | 1033 | |
michael@0 | 1034 | mMax = duration; |
michael@0 | 1035 | |
michael@0 | 1036 | return NS_OK; |
michael@0 | 1037 | } |
michael@0 | 1038 | |
michael@0 | 1039 | void |
michael@0 | 1040 | nsSMILTimedElement::UnsetMax() |
michael@0 | 1041 | { |
michael@0 | 1042 | mMax.SetIndefinite(); |
michael@0 | 1043 | UpdateCurrentInterval(); |
michael@0 | 1044 | } |
michael@0 | 1045 | |
michael@0 | 1046 | nsresult |
michael@0 | 1047 | nsSMILTimedElement::SetRestart(const nsAString& aRestartSpec) |
michael@0 | 1048 | { |
michael@0 | 1049 | nsAttrValue temp; |
michael@0 | 1050 | bool parseResult |
michael@0 | 1051 | = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true); |
michael@0 | 1052 | mRestartMode = parseResult |
michael@0 | 1053 | ? nsSMILRestartMode(temp.GetEnumValue()) |
michael@0 | 1054 | : RESTART_ALWAYS; |
michael@0 | 1055 | UpdateCurrentInterval(); |
michael@0 | 1056 | return parseResult ? NS_OK : NS_ERROR_FAILURE; |
michael@0 | 1057 | } |
michael@0 | 1058 | |
michael@0 | 1059 | void |
michael@0 | 1060 | nsSMILTimedElement::UnsetRestart() |
michael@0 | 1061 | { |
michael@0 | 1062 | mRestartMode = RESTART_ALWAYS; |
michael@0 | 1063 | UpdateCurrentInterval(); |
michael@0 | 1064 | } |
michael@0 | 1065 | |
michael@0 | 1066 | nsresult |
michael@0 | 1067 | nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec) |
michael@0 | 1068 | { |
michael@0 | 1069 | // Update the current interval before returning |
michael@0 | 1070 | AutoIntervalUpdater updater(*this); |
michael@0 | 1071 | |
michael@0 | 1072 | nsSMILRepeatCount newRepeatCount; |
michael@0 | 1073 | |
michael@0 | 1074 | if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) { |
michael@0 | 1075 | mRepeatCount = newRepeatCount; |
michael@0 | 1076 | return NS_OK; |
michael@0 | 1077 | } |
michael@0 | 1078 | mRepeatCount.Unset(); |
michael@0 | 1079 | return NS_ERROR_FAILURE; |
michael@0 | 1080 | } |
michael@0 | 1081 | |
michael@0 | 1082 | void |
michael@0 | 1083 | nsSMILTimedElement::UnsetRepeatCount() |
michael@0 | 1084 | { |
michael@0 | 1085 | mRepeatCount.Unset(); |
michael@0 | 1086 | UpdateCurrentInterval(); |
michael@0 | 1087 | } |
michael@0 | 1088 | |
michael@0 | 1089 | nsresult |
michael@0 | 1090 | nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec) |
michael@0 | 1091 | { |
michael@0 | 1092 | // Update the current interval before returning |
michael@0 | 1093 | AutoIntervalUpdater updater(*this); |
michael@0 | 1094 | |
michael@0 | 1095 | nsSMILTimeValue duration; |
michael@0 | 1096 | |
michael@0 | 1097 | const nsAString& repeatDur = |
michael@0 | 1098 | nsSMILParserUtils::TrimWhitespace(aRepeatDurSpec); |
michael@0 | 1099 | |
michael@0 | 1100 | if (repeatDur.EqualsLiteral("indefinite")) { |
michael@0 | 1101 | duration.SetIndefinite(); |
michael@0 | 1102 | } else { |
michael@0 | 1103 | if (!nsSMILParserUtils::ParseClockValue(repeatDur, &duration)) { |
michael@0 | 1104 | mRepeatDur.SetUnresolved(); |
michael@0 | 1105 | return NS_ERROR_FAILURE; |
michael@0 | 1106 | } |
michael@0 | 1107 | } |
michael@0 | 1108 | |
michael@0 | 1109 | mRepeatDur = duration; |
michael@0 | 1110 | |
michael@0 | 1111 | return NS_OK; |
michael@0 | 1112 | } |
michael@0 | 1113 | |
michael@0 | 1114 | void |
michael@0 | 1115 | nsSMILTimedElement::UnsetRepeatDur() |
michael@0 | 1116 | { |
michael@0 | 1117 | mRepeatDur.SetUnresolved(); |
michael@0 | 1118 | UpdateCurrentInterval(); |
michael@0 | 1119 | } |
michael@0 | 1120 | |
michael@0 | 1121 | nsresult |
michael@0 | 1122 | nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec) |
michael@0 | 1123 | { |
michael@0 | 1124 | uint16_t previousFillMode = mFillMode; |
michael@0 | 1125 | |
michael@0 | 1126 | nsAttrValue temp; |
michael@0 | 1127 | bool parseResult = |
michael@0 | 1128 | temp.ParseEnumValue(aFillModeSpec, sFillModeTable, true); |
michael@0 | 1129 | mFillMode = parseResult |
michael@0 | 1130 | ? nsSMILFillMode(temp.GetEnumValue()) |
michael@0 | 1131 | : FILL_REMOVE; |
michael@0 | 1132 | |
michael@0 | 1133 | // Update fill mode of client |
michael@0 | 1134 | if (mFillMode != previousFillMode && HasClientInFillRange()) { |
michael@0 | 1135 | mClient->Inactivate(mFillMode == FILL_FREEZE); |
michael@0 | 1136 | SampleFillValue(); |
michael@0 | 1137 | } |
michael@0 | 1138 | |
michael@0 | 1139 | return parseResult ? NS_OK : NS_ERROR_FAILURE; |
michael@0 | 1140 | } |
michael@0 | 1141 | |
michael@0 | 1142 | void |
michael@0 | 1143 | nsSMILTimedElement::UnsetFillMode() |
michael@0 | 1144 | { |
michael@0 | 1145 | uint16_t previousFillMode = mFillMode; |
michael@0 | 1146 | mFillMode = FILL_REMOVE; |
michael@0 | 1147 | if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) { |
michael@0 | 1148 | mClient->Inactivate(false); |
michael@0 | 1149 | } |
michael@0 | 1150 | } |
michael@0 | 1151 | |
michael@0 | 1152 | void |
michael@0 | 1153 | nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent) |
michael@0 | 1154 | { |
michael@0 | 1155 | // There's probably no harm in attempting to register a dependent |
michael@0 | 1156 | // nsSMILTimeValueSpec twice, but we're not expecting it to happen. |
michael@0 | 1157 | NS_ABORT_IF_FALSE(!mTimeDependents.GetEntry(&aDependent), |
michael@0 | 1158 | "nsSMILTimeValueSpec is already registered as a dependency"); |
michael@0 | 1159 | mTimeDependents.PutEntry(&aDependent); |
michael@0 | 1160 | |
michael@0 | 1161 | // Add current interval. We could add historical intervals too but that would |
michael@0 | 1162 | // cause unpredictable results since some intervals may have been filtered. |
michael@0 | 1163 | // SMIL doesn't say what to do here so for simplicity and consistency we |
michael@0 | 1164 | // simply add the current interval if there is one. |
michael@0 | 1165 | // |
michael@0 | 1166 | // It's not necessary to call SyncPauseTime since we're dealing with |
michael@0 | 1167 | // historical instance times not newly added ones. |
michael@0 | 1168 | if (mCurrentInterval) { |
michael@0 | 1169 | aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer()); |
michael@0 | 1170 | } |
michael@0 | 1171 | } |
michael@0 | 1172 | |
michael@0 | 1173 | void |
michael@0 | 1174 | nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent) |
michael@0 | 1175 | { |
michael@0 | 1176 | mTimeDependents.RemoveEntry(&aDependent); |
michael@0 | 1177 | } |
michael@0 | 1178 | |
michael@0 | 1179 | bool |
michael@0 | 1180 | nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const |
michael@0 | 1181 | { |
michael@0 | 1182 | const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance(); |
michael@0 | 1183 | const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance(); |
michael@0 | 1184 | |
michael@0 | 1185 | if (!thisBegin || !otherBegin) |
michael@0 | 1186 | return false; |
michael@0 | 1187 | |
michael@0 | 1188 | return thisBegin->IsDependentOn(*otherBegin); |
michael@0 | 1189 | } |
michael@0 | 1190 | |
michael@0 | 1191 | void |
michael@0 | 1192 | nsSMILTimedElement::BindToTree(nsIContent* aContextNode) |
michael@0 | 1193 | { |
michael@0 | 1194 | // Reset previously registered milestone since we may be registering with |
michael@0 | 1195 | // a different time container now. |
michael@0 | 1196 | mPrevRegisteredMilestone = sMaxMilestone; |
michael@0 | 1197 | |
michael@0 | 1198 | // If we were already active then clear all our timing information and start |
michael@0 | 1199 | // afresh |
michael@0 | 1200 | if (mElementState != STATE_STARTUP) { |
michael@0 | 1201 | mSeekState = SEEK_NOT_SEEKING; |
michael@0 | 1202 | Rewind(); |
michael@0 | 1203 | } |
michael@0 | 1204 | |
michael@0 | 1205 | // Scope updateBatcher to last only for the ResolveReferences calls: |
michael@0 | 1206 | { |
michael@0 | 1207 | AutoIntervalUpdateBatcher updateBatcher(*this); |
michael@0 | 1208 | |
michael@0 | 1209 | // Resolve references to other parts of the tree |
michael@0 | 1210 | uint32_t count = mBeginSpecs.Length(); |
michael@0 | 1211 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 1212 | mBeginSpecs[i]->ResolveReferences(aContextNode); |
michael@0 | 1213 | } |
michael@0 | 1214 | |
michael@0 | 1215 | count = mEndSpecs.Length(); |
michael@0 | 1216 | for (uint32_t j = 0; j < count; ++j) { |
michael@0 | 1217 | mEndSpecs[j]->ResolveReferences(aContextNode); |
michael@0 | 1218 | } |
michael@0 | 1219 | } |
michael@0 | 1220 | |
michael@0 | 1221 | RegisterMilestone(); |
michael@0 | 1222 | } |
michael@0 | 1223 | |
michael@0 | 1224 | void |
michael@0 | 1225 | nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget) |
michael@0 | 1226 | { |
michael@0 | 1227 | AutoIntervalUpdateBatcher updateBatcher(*this); |
michael@0 | 1228 | |
michael@0 | 1229 | uint32_t count = mBeginSpecs.Length(); |
michael@0 | 1230 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 1231 | mBeginSpecs[i]->HandleTargetElementChange(aNewTarget); |
michael@0 | 1232 | } |
michael@0 | 1233 | |
michael@0 | 1234 | count = mEndSpecs.Length(); |
michael@0 | 1235 | for (uint32_t j = 0; j < count; ++j) { |
michael@0 | 1236 | mEndSpecs[j]->HandleTargetElementChange(aNewTarget); |
michael@0 | 1237 | } |
michael@0 | 1238 | } |
michael@0 | 1239 | |
michael@0 | 1240 | void |
michael@0 | 1241 | nsSMILTimedElement::Traverse(nsCycleCollectionTraversalCallback* aCallback) |
michael@0 | 1242 | { |
michael@0 | 1243 | uint32_t count = mBeginSpecs.Length(); |
michael@0 | 1244 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 1245 | nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; |
michael@0 | 1246 | NS_ABORT_IF_FALSE(beginSpec, |
michael@0 | 1247 | "null nsSMILTimeValueSpec in list of begin specs"); |
michael@0 | 1248 | beginSpec->Traverse(aCallback); |
michael@0 | 1249 | } |
michael@0 | 1250 | |
michael@0 | 1251 | count = mEndSpecs.Length(); |
michael@0 | 1252 | for (uint32_t j = 0; j < count; ++j) { |
michael@0 | 1253 | nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; |
michael@0 | 1254 | NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); |
michael@0 | 1255 | endSpec->Traverse(aCallback); |
michael@0 | 1256 | } |
michael@0 | 1257 | } |
michael@0 | 1258 | |
michael@0 | 1259 | void |
michael@0 | 1260 | nsSMILTimedElement::Unlink() |
michael@0 | 1261 | { |
michael@0 | 1262 | AutoIntervalUpdateBatcher updateBatcher(*this); |
michael@0 | 1263 | |
michael@0 | 1264 | // Remove dependencies on other elements |
michael@0 | 1265 | uint32_t count = mBeginSpecs.Length(); |
michael@0 | 1266 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 1267 | nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; |
michael@0 | 1268 | NS_ABORT_IF_FALSE(beginSpec, |
michael@0 | 1269 | "null nsSMILTimeValueSpec in list of begin specs"); |
michael@0 | 1270 | beginSpec->Unlink(); |
michael@0 | 1271 | } |
michael@0 | 1272 | |
michael@0 | 1273 | count = mEndSpecs.Length(); |
michael@0 | 1274 | for (uint32_t j = 0; j < count; ++j) { |
michael@0 | 1275 | nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; |
michael@0 | 1276 | NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); |
michael@0 | 1277 | endSpec->Unlink(); |
michael@0 | 1278 | } |
michael@0 | 1279 | |
michael@0 | 1280 | ClearIntervals(); |
michael@0 | 1281 | |
michael@0 | 1282 | // Make sure we don't notify other elements of new intervals |
michael@0 | 1283 | mTimeDependents.Clear(); |
michael@0 | 1284 | } |
michael@0 | 1285 | |
michael@0 | 1286 | //---------------------------------------------------------------------- |
michael@0 | 1287 | // Implementation helpers |
michael@0 | 1288 | |
michael@0 | 1289 | nsresult |
michael@0 | 1290 | nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec, |
michael@0 | 1291 | Element* aContextNode, |
michael@0 | 1292 | bool aIsBegin, |
michael@0 | 1293 | RemovalTestFunction aRemove) |
michael@0 | 1294 | { |
michael@0 | 1295 | TimeValueSpecList& timeSpecsList = aIsBegin ? mBeginSpecs : mEndSpecs; |
michael@0 | 1296 | InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; |
michael@0 | 1297 | |
michael@0 | 1298 | ClearSpecs(timeSpecsList, instances, aRemove); |
michael@0 | 1299 | |
michael@0 | 1300 | AutoIntervalUpdateBatcher updateBatcher(*this); |
michael@0 | 1301 | |
michael@0 | 1302 | nsCharSeparatedTokenizer tokenizer(aSpec, ';'); |
michael@0 | 1303 | if (!tokenizer.hasMoreTokens()) { // Empty list |
michael@0 | 1304 | return NS_ERROR_FAILURE; |
michael@0 | 1305 | } |
michael@0 | 1306 | |
michael@0 | 1307 | nsresult rv = NS_OK; |
michael@0 | 1308 | while (tokenizer.hasMoreTokens() && NS_SUCCEEDED(rv)) { |
michael@0 | 1309 | nsAutoPtr<nsSMILTimeValueSpec> |
michael@0 | 1310 | spec(new nsSMILTimeValueSpec(*this, aIsBegin)); |
michael@0 | 1311 | rv = spec->SetSpec(tokenizer.nextToken(), aContextNode); |
michael@0 | 1312 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 1313 | timeSpecsList.AppendElement(spec.forget()); |
michael@0 | 1314 | } |
michael@0 | 1315 | } |
michael@0 | 1316 | |
michael@0 | 1317 | if (NS_FAILED(rv)) { |
michael@0 | 1318 | ClearSpecs(timeSpecsList, instances, aRemove); |
michael@0 | 1319 | } |
michael@0 | 1320 | |
michael@0 | 1321 | return rv; |
michael@0 | 1322 | } |
michael@0 | 1323 | |
michael@0 | 1324 | namespace |
michael@0 | 1325 | { |
michael@0 | 1326 | // Adaptor functor for RemoveInstanceTimes that allows us to use function |
michael@0 | 1327 | // pointers instead. |
michael@0 | 1328 | // Without this we'd have to either templatize ClearSpecs and all its callers |
michael@0 | 1329 | // or pass bool flags around to specify which removal function to use here. |
michael@0 | 1330 | class MOZ_STACK_CLASS RemoveByFunction |
michael@0 | 1331 | { |
michael@0 | 1332 | public: |
michael@0 | 1333 | RemoveByFunction(nsSMILTimedElement::RemovalTestFunction aFunction) |
michael@0 | 1334 | : mFunction(aFunction) { } |
michael@0 | 1335 | bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) |
michael@0 | 1336 | { |
michael@0 | 1337 | return mFunction(aInstanceTime); |
michael@0 | 1338 | } |
michael@0 | 1339 | |
michael@0 | 1340 | private: |
michael@0 | 1341 | nsSMILTimedElement::RemovalTestFunction mFunction; |
michael@0 | 1342 | }; |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | void |
michael@0 | 1346 | nsSMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs, |
michael@0 | 1347 | InstanceTimeList& aInstances, |
michael@0 | 1348 | RemovalTestFunction aRemove) |
michael@0 | 1349 | { |
michael@0 | 1350 | AutoIntervalUpdateBatcher updateBatcher(*this); |
michael@0 | 1351 | |
michael@0 | 1352 | for (uint32_t i = 0; i < aSpecs.Length(); ++i) { |
michael@0 | 1353 | aSpecs[i]->Unlink(); |
michael@0 | 1354 | } |
michael@0 | 1355 | aSpecs.Clear(); |
michael@0 | 1356 | |
michael@0 | 1357 | RemoveByFunction removeByFunction(aRemove); |
michael@0 | 1358 | RemoveInstanceTimes(aInstances, removeByFunction); |
michael@0 | 1359 | } |
michael@0 | 1360 | |
michael@0 | 1361 | void |
michael@0 | 1362 | nsSMILTimedElement::ClearIntervals() |
michael@0 | 1363 | { |
michael@0 | 1364 | if (mElementState != STATE_STARTUP) { |
michael@0 | 1365 | mElementState = STATE_POSTACTIVE; |
michael@0 | 1366 | } |
michael@0 | 1367 | mCurrentRepeatIteration = 0; |
michael@0 | 1368 | ResetCurrentInterval(); |
michael@0 | 1369 | |
michael@0 | 1370 | // Remove old intervals |
michael@0 | 1371 | for (int32_t i = mOldIntervals.Length() - 1; i >= 0; --i) { |
michael@0 | 1372 | mOldIntervals[i]->Unlink(); |
michael@0 | 1373 | } |
michael@0 | 1374 | mOldIntervals.Clear(); |
michael@0 | 1375 | } |
michael@0 | 1376 | |
michael@0 | 1377 | bool |
michael@0 | 1378 | nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime) |
michael@0 | 1379 | { |
michael@0 | 1380 | // This should only be called within DoSampleAt as a helper function |
michael@0 | 1381 | NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE, |
michael@0 | 1382 | "Unexpected state to try to apply an early end"); |
michael@0 | 1383 | |
michael@0 | 1384 | bool updated = false; |
michael@0 | 1385 | |
michael@0 | 1386 | // Only apply an early end if we're not already ending. |
michael@0 | 1387 | if (mCurrentInterval->End()->Time() > aSampleTime) { |
michael@0 | 1388 | nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime); |
michael@0 | 1389 | if (earlyEnd) { |
michael@0 | 1390 | if (earlyEnd->IsDependent()) { |
michael@0 | 1391 | // Generate a new instance time for the early end since the |
michael@0 | 1392 | // existing instance time is part of some dependency chain that we |
michael@0 | 1393 | // don't want to participate in. |
michael@0 | 1394 | nsRefPtr<nsSMILInstanceTime> newEarlyEnd = |
michael@0 | 1395 | new nsSMILInstanceTime(earlyEnd->Time()); |
michael@0 | 1396 | mCurrentInterval->SetEnd(*newEarlyEnd); |
michael@0 | 1397 | } else { |
michael@0 | 1398 | mCurrentInterval->SetEnd(*earlyEnd); |
michael@0 | 1399 | } |
michael@0 | 1400 | updated = true; |
michael@0 | 1401 | } |
michael@0 | 1402 | } |
michael@0 | 1403 | return updated; |
michael@0 | 1404 | } |
michael@0 | 1405 | |
michael@0 | 1406 | namespace |
michael@0 | 1407 | { |
michael@0 | 1408 | class MOZ_STACK_CLASS RemoveReset |
michael@0 | 1409 | { |
michael@0 | 1410 | public: |
michael@0 | 1411 | RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin) |
michael@0 | 1412 | : mCurrentIntervalBegin(aCurrentIntervalBegin) { } |
michael@0 | 1413 | bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) |
michael@0 | 1414 | { |
michael@0 | 1415 | // SMIL 3.0 section 5.4.3, 'Resetting element state': |
michael@0 | 1416 | // Any instance times associated with past Event-values, Repeat-values, |
michael@0 | 1417 | // Accesskey-values or added via DOM method calls are removed from the |
michael@0 | 1418 | // dependent begin and end instance times lists. In effect, all events |
michael@0 | 1419 | // and DOM methods calls in the past are cleared. This does not apply to |
michael@0 | 1420 | // an instance time that defines the begin of the current interval. |
michael@0 | 1421 | return aInstanceTime->IsDynamic() && |
michael@0 | 1422 | !aInstanceTime->ShouldPreserve() && |
michael@0 | 1423 | (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin); |
michael@0 | 1424 | } |
michael@0 | 1425 | |
michael@0 | 1426 | private: |
michael@0 | 1427 | const nsSMILInstanceTime* mCurrentIntervalBegin; |
michael@0 | 1428 | }; |
michael@0 | 1429 | } |
michael@0 | 1430 | |
michael@0 | 1431 | void |
michael@0 | 1432 | nsSMILTimedElement::Reset() |
michael@0 | 1433 | { |
michael@0 | 1434 | RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nullptr); |
michael@0 | 1435 | RemoveInstanceTimes(mBeginInstances, resetBegin); |
michael@0 | 1436 | |
michael@0 | 1437 | RemoveReset resetEnd(nullptr); |
michael@0 | 1438 | RemoveInstanceTimes(mEndInstances, resetEnd); |
michael@0 | 1439 | } |
michael@0 | 1440 | |
michael@0 | 1441 | void |
michael@0 | 1442 | nsSMILTimedElement::DoPostSeek() |
michael@0 | 1443 | { |
michael@0 | 1444 | // Finish backwards seek |
michael@0 | 1445 | if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE || |
michael@0 | 1446 | mSeekState == SEEK_BACKWARD_FROM_ACTIVE) { |
michael@0 | 1447 | // Previously some dynamic instance times may have been marked to be |
michael@0 | 1448 | // preserved because they were endpoints of an historic interval (which may |
michael@0 | 1449 | // or may not have been filtered). Now that we've finished a seek we should |
michael@0 | 1450 | // clear that flag for those instance times whose intervals are no longer |
michael@0 | 1451 | // historic. |
michael@0 | 1452 | UnpreserveInstanceTimes(mBeginInstances); |
michael@0 | 1453 | UnpreserveInstanceTimes(mEndInstances); |
michael@0 | 1454 | |
michael@0 | 1455 | // Now that the times have been unmarked perform a reset. This might seem |
michael@0 | 1456 | // counter-intuitive when we're only doing a seek within an interval but |
michael@0 | 1457 | // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing': |
michael@0 | 1458 | // Resolved end times associated with events, Repeat-values, |
michael@0 | 1459 | // Accesskey-values or added via DOM method calls are cleared when seeking |
michael@0 | 1460 | // to time earlier than the resolved end time. |
michael@0 | 1461 | Reset(); |
michael@0 | 1462 | UpdateCurrentInterval(); |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | switch (mSeekState) |
michael@0 | 1466 | { |
michael@0 | 1467 | case SEEK_FORWARD_FROM_ACTIVE: |
michael@0 | 1468 | case SEEK_BACKWARD_FROM_ACTIVE: |
michael@0 | 1469 | if (mElementState != STATE_ACTIVE) { |
michael@0 | 1470 | FireTimeEventAsync(NS_SMIL_END, 0); |
michael@0 | 1471 | } |
michael@0 | 1472 | break; |
michael@0 | 1473 | |
michael@0 | 1474 | case SEEK_FORWARD_FROM_INACTIVE: |
michael@0 | 1475 | case SEEK_BACKWARD_FROM_INACTIVE: |
michael@0 | 1476 | if (mElementState == STATE_ACTIVE) { |
michael@0 | 1477 | FireTimeEventAsync(NS_SMIL_BEGIN, 0); |
michael@0 | 1478 | } |
michael@0 | 1479 | break; |
michael@0 | 1480 | |
michael@0 | 1481 | case SEEK_NOT_SEEKING: |
michael@0 | 1482 | /* Do nothing */ |
michael@0 | 1483 | break; |
michael@0 | 1484 | } |
michael@0 | 1485 | |
michael@0 | 1486 | mSeekState = SEEK_NOT_SEEKING; |
michael@0 | 1487 | } |
michael@0 | 1488 | |
michael@0 | 1489 | void |
michael@0 | 1490 | nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList) |
michael@0 | 1491 | { |
michael@0 | 1492 | const nsSMILInterval* prevInterval = GetPreviousInterval(); |
michael@0 | 1493 | const nsSMILInstanceTime* cutoff = mCurrentInterval ? |
michael@0 | 1494 | mCurrentInterval->Begin() : |
michael@0 | 1495 | prevInterval ? prevInterval->Begin() : nullptr; |
michael@0 | 1496 | uint32_t count = aList.Length(); |
michael@0 | 1497 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 1498 | nsSMILInstanceTime* instance = aList[i].get(); |
michael@0 | 1499 | if (!cutoff || cutoff->Time().CompareTo(instance->Time()) < 0) { |
michael@0 | 1500 | instance->UnmarkShouldPreserve(); |
michael@0 | 1501 | } |
michael@0 | 1502 | } |
michael@0 | 1503 | } |
michael@0 | 1504 | |
michael@0 | 1505 | void |
michael@0 | 1506 | nsSMILTimedElement::FilterHistory() |
michael@0 | 1507 | { |
michael@0 | 1508 | // We should filter the intervals first, since instance times still used in an |
michael@0 | 1509 | // interval won't be filtered. |
michael@0 | 1510 | FilterIntervals(); |
michael@0 | 1511 | FilterInstanceTimes(mBeginInstances); |
michael@0 | 1512 | FilterInstanceTimes(mEndInstances); |
michael@0 | 1513 | } |
michael@0 | 1514 | |
michael@0 | 1515 | void |
michael@0 | 1516 | nsSMILTimedElement::FilterIntervals() |
michael@0 | 1517 | { |
michael@0 | 1518 | // We can filter old intervals that: |
michael@0 | 1519 | // |
michael@0 | 1520 | // a) are not the previous interval; AND |
michael@0 | 1521 | // b) are not in the middle of a dependency chain; AND |
michael@0 | 1522 | // c) are not the first interval |
michael@0 | 1523 | // |
michael@0 | 1524 | // Condition (a) is necessary since the previous interval is used for applying |
michael@0 | 1525 | // fill effects and updating the current interval. |
michael@0 | 1526 | // |
michael@0 | 1527 | // Condition (b) is necessary since even if this interval itself is not |
michael@0 | 1528 | // active, it may be part of a dependency chain that includes active |
michael@0 | 1529 | // intervals. Such chains are used to establish priorities within the |
michael@0 | 1530 | // animation sandwich. |
michael@0 | 1531 | // |
michael@0 | 1532 | // Condition (c) is necessary to support hyperlinks that target animations |
michael@0 | 1533 | // since in some cases the defined behavior is to seek the document back to |
michael@0 | 1534 | // the first resolved begin time. Presumably the intention here is not |
michael@0 | 1535 | // actually to use the first resolved begin time, the |
michael@0 | 1536 | // _the_first_resolved_begin_time_that_produced_an_interval. That is, |
michael@0 | 1537 | // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek |
michael@0 | 1538 | // to 1s. The spec doesn't say this but I'm pretty sure that is the intention. |
michael@0 | 1539 | // It seems negative times were simply not considered. |
michael@0 | 1540 | // |
michael@0 | 1541 | // Although the above conditions allow us to safely filter intervals for most |
michael@0 | 1542 | // scenarios they do not cover all cases and there will still be scenarios |
michael@0 | 1543 | // that generate intervals indefinitely. In such a case we simply set |
michael@0 | 1544 | // a maximum number of intervals and drop any intervals beyond that threshold. |
michael@0 | 1545 | |
michael@0 | 1546 | uint32_t threshold = mOldIntervals.Length() > sMaxNumIntervals ? |
michael@0 | 1547 | mOldIntervals.Length() - sMaxNumIntervals : |
michael@0 | 1548 | 0; |
michael@0 | 1549 | IntervalList filteredList; |
michael@0 | 1550 | for (uint32_t i = 0; i < mOldIntervals.Length(); ++i) |
michael@0 | 1551 | { |
michael@0 | 1552 | nsSMILInterval* interval = mOldIntervals[i].get(); |
michael@0 | 1553 | if (i != 0 && /*skip first interval*/ |
michael@0 | 1554 | i + 1 < mOldIntervals.Length() && /*skip previous interval*/ |
michael@0 | 1555 | (i < threshold || !interval->IsDependencyChainLink())) { |
michael@0 | 1556 | interval->Unlink(true /*filtered, not deleted*/); |
michael@0 | 1557 | } else { |
michael@0 | 1558 | filteredList.AppendElement(mOldIntervals[i].forget()); |
michael@0 | 1559 | } |
michael@0 | 1560 | } |
michael@0 | 1561 | mOldIntervals.Clear(); |
michael@0 | 1562 | mOldIntervals.SwapElements(filteredList); |
michael@0 | 1563 | } |
michael@0 | 1564 | |
michael@0 | 1565 | namespace |
michael@0 | 1566 | { |
michael@0 | 1567 | class MOZ_STACK_CLASS RemoveFiltered |
michael@0 | 1568 | { |
michael@0 | 1569 | public: |
michael@0 | 1570 | RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { } |
michael@0 | 1571 | bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) |
michael@0 | 1572 | { |
michael@0 | 1573 | // We can filter instance times that: |
michael@0 | 1574 | // a) Precede the end point of the previous interval; AND |
michael@0 | 1575 | // b) Are NOT syncbase times that might be updated to a time after the end |
michael@0 | 1576 | // point of the previous interval; AND |
michael@0 | 1577 | // c) Are NOT fixed end points in any remaining interval. |
michael@0 | 1578 | return aInstanceTime->Time() < mCutoff && |
michael@0 | 1579 | aInstanceTime->IsFixedTime() && |
michael@0 | 1580 | !aInstanceTime->ShouldPreserve(); |
michael@0 | 1581 | } |
michael@0 | 1582 | |
michael@0 | 1583 | private: |
michael@0 | 1584 | nsSMILTimeValue mCutoff; |
michael@0 | 1585 | }; |
michael@0 | 1586 | |
michael@0 | 1587 | class MOZ_STACK_CLASS RemoveBelowThreshold |
michael@0 | 1588 | { |
michael@0 | 1589 | public: |
michael@0 | 1590 | RemoveBelowThreshold(uint32_t aThreshold, |
michael@0 | 1591 | nsTArray<const nsSMILInstanceTime *>& aTimesToKeep) |
michael@0 | 1592 | : mThreshold(aThreshold), |
michael@0 | 1593 | mTimesToKeep(aTimesToKeep) { } |
michael@0 | 1594 | bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t aIndex) |
michael@0 | 1595 | { |
michael@0 | 1596 | return aIndex < mThreshold && !mTimesToKeep.Contains(aInstanceTime); |
michael@0 | 1597 | } |
michael@0 | 1598 | |
michael@0 | 1599 | private: |
michael@0 | 1600 | uint32_t mThreshold; |
michael@0 | 1601 | nsTArray<const nsSMILInstanceTime *>& mTimesToKeep; |
michael@0 | 1602 | }; |
michael@0 | 1603 | } |
michael@0 | 1604 | |
michael@0 | 1605 | void |
michael@0 | 1606 | nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList) |
michael@0 | 1607 | { |
michael@0 | 1608 | if (GetPreviousInterval()) { |
michael@0 | 1609 | RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time()); |
michael@0 | 1610 | RemoveInstanceTimes(aList, removeFiltered); |
michael@0 | 1611 | } |
michael@0 | 1612 | |
michael@0 | 1613 | // As with intervals it is possible to create a document that, even despite |
michael@0 | 1614 | // our most aggressive filtering, will generate instance times indefinitely |
michael@0 | 1615 | // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as |
michael@0 | 1616 | // they're unpredictable due to the possibility of seeking the document which |
michael@0 | 1617 | // may prevent some events from being generated). Therefore we introduce |
michael@0 | 1618 | // a hard cutoff at which point we just drop the oldest instance times. |
michael@0 | 1619 | if (aList.Length() > sMaxNumInstanceTimes) { |
michael@0 | 1620 | uint32_t threshold = aList.Length() - sMaxNumInstanceTimes; |
michael@0 | 1621 | // There are a few instance times we should keep though, notably: |
michael@0 | 1622 | // - the current interval begin time, |
michael@0 | 1623 | // - the previous interval end time (see note in RemoveInstanceTimes) |
michael@0 | 1624 | // - the first interval begin time (see note in FilterIntervals) |
michael@0 | 1625 | nsTArray<const nsSMILInstanceTime *> timesToKeep; |
michael@0 | 1626 | if (mCurrentInterval) { |
michael@0 | 1627 | timesToKeep.AppendElement(mCurrentInterval->Begin()); |
michael@0 | 1628 | } |
michael@0 | 1629 | const nsSMILInterval* prevInterval = GetPreviousInterval(); |
michael@0 | 1630 | if (prevInterval) { |
michael@0 | 1631 | timesToKeep.AppendElement(prevInterval->End()); |
michael@0 | 1632 | } |
michael@0 | 1633 | if (!mOldIntervals.IsEmpty()) { |
michael@0 | 1634 | timesToKeep.AppendElement(mOldIntervals[0]->Begin()); |
michael@0 | 1635 | } |
michael@0 | 1636 | RemoveBelowThreshold removeBelowThreshold(threshold, timesToKeep); |
michael@0 | 1637 | RemoveInstanceTimes(aList, removeBelowThreshold); |
michael@0 | 1638 | } |
michael@0 | 1639 | } |
michael@0 | 1640 | |
michael@0 | 1641 | // |
michael@0 | 1642 | // This method is based on the pseudocode given in the SMILANIM spec. |
michael@0 | 1643 | // |
michael@0 | 1644 | // See: |
michael@0 | 1645 | // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start |
michael@0 | 1646 | // |
michael@0 | 1647 | bool |
michael@0 | 1648 | nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval, |
michael@0 | 1649 | const nsSMILInterval* aReplacedInterval, |
michael@0 | 1650 | const nsSMILInstanceTime* aFixedBeginTime, |
michael@0 | 1651 | nsSMILInterval& aResult) const |
michael@0 | 1652 | { |
michael@0 | 1653 | NS_ABORT_IF_FALSE(!aFixedBeginTime || aFixedBeginTime->Time().IsDefinite(), |
michael@0 | 1654 | "Unresolved or indefinite begin time specified for interval start"); |
michael@0 | 1655 | static const nsSMILTimeValue zeroTime(0L); |
michael@0 | 1656 | |
michael@0 | 1657 | if (mRestartMode == RESTART_NEVER && aPrevInterval) |
michael@0 | 1658 | return false; |
michael@0 | 1659 | |
michael@0 | 1660 | // Calc starting point |
michael@0 | 1661 | nsSMILTimeValue beginAfter; |
michael@0 | 1662 | bool prevIntervalWasZeroDur = false; |
michael@0 | 1663 | if (aPrevInterval) { |
michael@0 | 1664 | beginAfter = aPrevInterval->End()->Time(); |
michael@0 | 1665 | prevIntervalWasZeroDur |
michael@0 | 1666 | = aPrevInterval->End()->Time() == aPrevInterval->Begin()->Time(); |
michael@0 | 1667 | } else { |
michael@0 | 1668 | beginAfter.SetMillis(INT64_MIN); |
michael@0 | 1669 | } |
michael@0 | 1670 | |
michael@0 | 1671 | nsRefPtr<nsSMILInstanceTime> tempBegin; |
michael@0 | 1672 | nsRefPtr<nsSMILInstanceTime> tempEnd; |
michael@0 | 1673 | |
michael@0 | 1674 | while (true) { |
michael@0 | 1675 | // Calculate begin time |
michael@0 | 1676 | if (aFixedBeginTime) { |
michael@0 | 1677 | if (aFixedBeginTime->Time() < beginAfter) { |
michael@0 | 1678 | return false; |
michael@0 | 1679 | } |
michael@0 | 1680 | // our ref-counting is not const-correct |
michael@0 | 1681 | tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime); |
michael@0 | 1682 | } else if ((!mAnimationElement || |
michael@0 | 1683 | !mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) && |
michael@0 | 1684 | beginAfter <= zeroTime) { |
michael@0 | 1685 | tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0)); |
michael@0 | 1686 | } else { |
michael@0 | 1687 | int32_t beginPos = 0; |
michael@0 | 1688 | do { |
michael@0 | 1689 | tempBegin = |
michael@0 | 1690 | GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos); |
michael@0 | 1691 | if (!tempBegin || !tempBegin->Time().IsDefinite()) { |
michael@0 | 1692 | return false; |
michael@0 | 1693 | } |
michael@0 | 1694 | // If we're updating the current interval then skip any begin time that is |
michael@0 | 1695 | // dependent on the current interval's begin time. e.g. |
michael@0 | 1696 | // <animate id="a" begin="b.begin; a.begin+2s"... |
michael@0 | 1697 | // If b's interval disappears whilst 'a' is in the waiting state the begin |
michael@0 | 1698 | // time at "a.begin+2s" should be skipped since 'a' never begun. |
michael@0 | 1699 | } while (aReplacedInterval && |
michael@0 | 1700 | tempBegin->GetBaseTime() == aReplacedInterval->Begin()); |
michael@0 | 1701 | } |
michael@0 | 1702 | NS_ABORT_IF_FALSE(tempBegin && tempBegin->Time().IsDefinite() && |
michael@0 | 1703 | tempBegin->Time() >= beginAfter, |
michael@0 | 1704 | "Got a bad begin time while fetching next interval"); |
michael@0 | 1705 | |
michael@0 | 1706 | // Calculate end time |
michael@0 | 1707 | { |
michael@0 | 1708 | int32_t endPos = 0; |
michael@0 | 1709 | do { |
michael@0 | 1710 | tempEnd = |
michael@0 | 1711 | GetNextGreaterOrEqual(mEndInstances, tempBegin->Time(), endPos); |
michael@0 | 1712 | |
michael@0 | 1713 | // SMIL doesn't allow for coincident zero-duration intervals, so if the |
michael@0 | 1714 | // previous interval was zero-duration, and tempEnd is going to give us |
michael@0 | 1715 | // another zero duration interval, then look for another end to use |
michael@0 | 1716 | // instead. |
michael@0 | 1717 | if (tempEnd && prevIntervalWasZeroDur && |
michael@0 | 1718 | tempEnd->Time() == beginAfter) { |
michael@0 | 1719 | tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos); |
michael@0 | 1720 | } |
michael@0 | 1721 | // As above with begin times, avoid creating self-referential loops |
michael@0 | 1722 | // between instance times by checking that the newly found end instance |
michael@0 | 1723 | // time is not already dependent on the end of the current interval. |
michael@0 | 1724 | } while (tempEnd && aReplacedInterval && |
michael@0 | 1725 | tempEnd->GetBaseTime() == aReplacedInterval->End()); |
michael@0 | 1726 | |
michael@0 | 1727 | if (!tempEnd) { |
michael@0 | 1728 | // If all the ends are before the beginning we have a bad interval |
michael@0 | 1729 | // UNLESS: |
michael@0 | 1730 | // a) We never had any end attribute to begin with (the SMIL pseudocode |
michael@0 | 1731 | // places this condition earlier in the flow but that fails to allow |
michael@0 | 1732 | // for DOM calls when no "indefinite" condition is given), OR |
michael@0 | 1733 | // b) We never had any end instance times to begin with, OR |
michael@0 | 1734 | // c) We have end events which leave the interval open-ended. |
michael@0 | 1735 | bool openEndedIntervalOk = mEndSpecs.IsEmpty() || |
michael@0 | 1736 | mEndInstances.IsEmpty() || |
michael@0 | 1737 | EndHasEventConditions(); |
michael@0 | 1738 | |
michael@0 | 1739 | // The above conditions correspond with the SMIL pseudocode but SMIL |
michael@0 | 1740 | // doesn't address self-dependent instance times which we choose to |
michael@0 | 1741 | // ignore. |
michael@0 | 1742 | // |
michael@0 | 1743 | // Therefore we add a qualification of (b) above that even if |
michael@0 | 1744 | // there are end instance times but they all depend on the end of the |
michael@0 | 1745 | // current interval we should act as if they didn't exist and allow the |
michael@0 | 1746 | // open-ended interval. |
michael@0 | 1747 | // |
michael@0 | 1748 | // In the following condition we don't use |= because it doesn't provide |
michael@0 | 1749 | // short-circuit behavior. |
michael@0 | 1750 | openEndedIntervalOk = openEndedIntervalOk || |
michael@0 | 1751 | (aReplacedInterval && |
michael@0 | 1752 | AreEndTimesDependentOn(aReplacedInterval->End())); |
michael@0 | 1753 | |
michael@0 | 1754 | if (!openEndedIntervalOk) { |
michael@0 | 1755 | return false; // Bad interval |
michael@0 | 1756 | } |
michael@0 | 1757 | } |
michael@0 | 1758 | |
michael@0 | 1759 | nsSMILTimeValue intervalEnd = tempEnd |
michael@0 | 1760 | ? tempEnd->Time() : nsSMILTimeValue(); |
michael@0 | 1761 | nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd); |
michael@0 | 1762 | |
michael@0 | 1763 | if (!tempEnd || intervalEnd != activeEnd) { |
michael@0 | 1764 | tempEnd = new nsSMILInstanceTime(activeEnd); |
michael@0 | 1765 | } |
michael@0 | 1766 | } |
michael@0 | 1767 | NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval"); |
michael@0 | 1768 | |
michael@0 | 1769 | // When we choose the interval endpoints, we don't allow coincident |
michael@0 | 1770 | // zero-duration intervals, so if we arrive here and we have a zero-duration |
michael@0 | 1771 | // interval starting at the same point as a previous zero-duration interval, |
michael@0 | 1772 | // then it must be because we've applied constraints to the active duration. |
michael@0 | 1773 | // In that case, we will potentially run into an infinite loop, so we break |
michael@0 | 1774 | // it by searching for the next interval that starts AFTER our current |
michael@0 | 1775 | // zero-duration interval. |
michael@0 | 1776 | if (prevIntervalWasZeroDur && tempEnd->Time() == beginAfter) { |
michael@0 | 1777 | if (prevIntervalWasZeroDur) { |
michael@0 | 1778 | beginAfter.SetMillis(tempBegin->Time().GetMillis() + 1); |
michael@0 | 1779 | prevIntervalWasZeroDur = false; |
michael@0 | 1780 | continue; |
michael@0 | 1781 | } |
michael@0 | 1782 | } |
michael@0 | 1783 | prevIntervalWasZeroDur = tempBegin->Time() == tempEnd->Time(); |
michael@0 | 1784 | |
michael@0 | 1785 | // Check for valid interval |
michael@0 | 1786 | if (tempEnd->Time() > zeroTime || |
michael@0 | 1787 | (tempBegin->Time() == zeroTime && tempEnd->Time() == zeroTime)) { |
michael@0 | 1788 | aResult.Set(*tempBegin, *tempEnd); |
michael@0 | 1789 | return true; |
michael@0 | 1790 | } |
michael@0 | 1791 | |
michael@0 | 1792 | if (mRestartMode == RESTART_NEVER) { |
michael@0 | 1793 | // tempEnd <= 0 so we're going to loop which effectively means restarting |
michael@0 | 1794 | return false; |
michael@0 | 1795 | } |
michael@0 | 1796 | |
michael@0 | 1797 | beginAfter = tempEnd->Time(); |
michael@0 | 1798 | } |
michael@0 | 1799 | NS_NOTREACHED("Hmm... we really shouldn't be here"); |
michael@0 | 1800 | |
michael@0 | 1801 | return false; |
michael@0 | 1802 | } |
michael@0 | 1803 | |
michael@0 | 1804 | nsSMILInstanceTime* |
michael@0 | 1805 | nsSMILTimedElement::GetNextGreater(const InstanceTimeList& aList, |
michael@0 | 1806 | const nsSMILTimeValue& aBase, |
michael@0 | 1807 | int32_t& aPosition) const |
michael@0 | 1808 | { |
michael@0 | 1809 | nsSMILInstanceTime* result = nullptr; |
michael@0 | 1810 | while ((result = GetNextGreaterOrEqual(aList, aBase, aPosition)) && |
michael@0 | 1811 | result->Time() == aBase) { } |
michael@0 | 1812 | return result; |
michael@0 | 1813 | } |
michael@0 | 1814 | |
michael@0 | 1815 | nsSMILInstanceTime* |
michael@0 | 1816 | nsSMILTimedElement::GetNextGreaterOrEqual(const InstanceTimeList& aList, |
michael@0 | 1817 | const nsSMILTimeValue& aBase, |
michael@0 | 1818 | int32_t& aPosition) const |
michael@0 | 1819 | { |
michael@0 | 1820 | nsSMILInstanceTime* result = nullptr; |
michael@0 | 1821 | int32_t count = aList.Length(); |
michael@0 | 1822 | |
michael@0 | 1823 | for (; aPosition < count && !result; ++aPosition) { |
michael@0 | 1824 | nsSMILInstanceTime* val = aList[aPosition].get(); |
michael@0 | 1825 | NS_ABORT_IF_FALSE(val, "NULL instance time in list"); |
michael@0 | 1826 | if (val->Time() >= aBase) { |
michael@0 | 1827 | result = val; |
michael@0 | 1828 | } |
michael@0 | 1829 | } |
michael@0 | 1830 | |
michael@0 | 1831 | return result; |
michael@0 | 1832 | } |
michael@0 | 1833 | |
michael@0 | 1834 | /** |
michael@0 | 1835 | * @see SMILANIM 3.3.4 |
michael@0 | 1836 | */ |
michael@0 | 1837 | nsSMILTimeValue |
michael@0 | 1838 | nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin, |
michael@0 | 1839 | const nsSMILTimeValue& aEnd) const |
michael@0 | 1840 | { |
michael@0 | 1841 | nsSMILTimeValue result; |
michael@0 | 1842 | |
michael@0 | 1843 | NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), |
michael@0 | 1844 | "Unresolved simple duration in CalcActiveEnd"); |
michael@0 | 1845 | NS_ABORT_IF_FALSE(aBegin.IsDefinite(), |
michael@0 | 1846 | "Indefinite or unresolved begin time in CalcActiveEnd"); |
michael@0 | 1847 | |
michael@0 | 1848 | result = GetRepeatDuration(); |
michael@0 | 1849 | |
michael@0 | 1850 | if (aEnd.IsDefinite()) { |
michael@0 | 1851 | nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis(); |
michael@0 | 1852 | |
michael@0 | 1853 | if (result.IsDefinite()) { |
michael@0 | 1854 | result.SetMillis(std::min(result.GetMillis(), activeDur)); |
michael@0 | 1855 | } else { |
michael@0 | 1856 | result.SetMillis(activeDur); |
michael@0 | 1857 | } |
michael@0 | 1858 | } |
michael@0 | 1859 | |
michael@0 | 1860 | result = ApplyMinAndMax(result); |
michael@0 | 1861 | |
michael@0 | 1862 | if (result.IsDefinite()) { |
michael@0 | 1863 | nsSMILTime activeEnd = result.GetMillis() + aBegin.GetMillis(); |
michael@0 | 1864 | result.SetMillis(activeEnd); |
michael@0 | 1865 | } |
michael@0 | 1866 | |
michael@0 | 1867 | return result; |
michael@0 | 1868 | } |
michael@0 | 1869 | |
michael@0 | 1870 | nsSMILTimeValue |
michael@0 | 1871 | nsSMILTimedElement::GetRepeatDuration() const |
michael@0 | 1872 | { |
michael@0 | 1873 | nsSMILTimeValue multipliedDuration; |
michael@0 | 1874 | if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) { |
michael@0 | 1875 | multipliedDuration.SetMillis( |
michael@0 | 1876 | nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()))); |
michael@0 | 1877 | } else { |
michael@0 | 1878 | multipliedDuration.SetIndefinite(); |
michael@0 | 1879 | } |
michael@0 | 1880 | |
michael@0 | 1881 | nsSMILTimeValue repeatDuration; |
michael@0 | 1882 | |
michael@0 | 1883 | if (mRepeatDur.IsResolved()) { |
michael@0 | 1884 | repeatDuration = std::min(multipliedDuration, mRepeatDur); |
michael@0 | 1885 | } else if (mRepeatCount.IsSet()) { |
michael@0 | 1886 | repeatDuration = multipliedDuration; |
michael@0 | 1887 | } else { |
michael@0 | 1888 | repeatDuration = mSimpleDur; |
michael@0 | 1889 | } |
michael@0 | 1890 | |
michael@0 | 1891 | return repeatDuration; |
michael@0 | 1892 | } |
michael@0 | 1893 | |
michael@0 | 1894 | nsSMILTimeValue |
michael@0 | 1895 | nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const |
michael@0 | 1896 | { |
michael@0 | 1897 | if (!aDuration.IsResolved()) { |
michael@0 | 1898 | return aDuration; |
michael@0 | 1899 | } |
michael@0 | 1900 | |
michael@0 | 1901 | if (mMax < mMin) { |
michael@0 | 1902 | return aDuration; |
michael@0 | 1903 | } |
michael@0 | 1904 | |
michael@0 | 1905 | nsSMILTimeValue result; |
michael@0 | 1906 | |
michael@0 | 1907 | if (aDuration > mMax) { |
michael@0 | 1908 | result = mMax; |
michael@0 | 1909 | } else if (aDuration < mMin) { |
michael@0 | 1910 | result = mMin; |
michael@0 | 1911 | } else { |
michael@0 | 1912 | result = aDuration; |
michael@0 | 1913 | } |
michael@0 | 1914 | |
michael@0 | 1915 | return result; |
michael@0 | 1916 | } |
michael@0 | 1917 | |
michael@0 | 1918 | nsSMILTime |
michael@0 | 1919 | nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime, |
michael@0 | 1920 | uint32_t& aRepeatIteration) |
michael@0 | 1921 | { |
michael@0 | 1922 | nsSMILTime result; |
michael@0 | 1923 | |
michael@0 | 1924 | NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), |
michael@0 | 1925 | "Unresolved simple duration in ActiveTimeToSimpleTime"); |
michael@0 | 1926 | NS_ABORT_IF_FALSE(aActiveTime >= 0, "Expecting non-negative active time"); |
michael@0 | 1927 | // Note that a negative aActiveTime will give us a negative value for |
michael@0 | 1928 | // aRepeatIteration, which is bad because aRepeatIteration is unsigned |
michael@0 | 1929 | |
michael@0 | 1930 | if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) { |
michael@0 | 1931 | aRepeatIteration = 0; |
michael@0 | 1932 | result = aActiveTime; |
michael@0 | 1933 | } else { |
michael@0 | 1934 | result = aActiveTime % mSimpleDur.GetMillis(); |
michael@0 | 1935 | aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis()); |
michael@0 | 1936 | } |
michael@0 | 1937 | |
michael@0 | 1938 | return result; |
michael@0 | 1939 | } |
michael@0 | 1940 | |
michael@0 | 1941 | // |
michael@0 | 1942 | // Although in many cases it would be possible to check for an early end and |
michael@0 | 1943 | // adjust the current interval well in advance the SMIL Animation spec seems to |
michael@0 | 1944 | // indicate that we should only apply an early end at the latest possible |
michael@0 | 1945 | // moment. In particular, this paragraph from section 3.6.8: |
michael@0 | 1946 | // |
michael@0 | 1947 | // 'If restart is set to "always", then the current interval will end early if |
michael@0 | 1948 | // there is an instance time in the begin list that is before (i.e. earlier |
michael@0 | 1949 | // than) the defined end for the current interval. Ending in this manner will |
michael@0 | 1950 | // also send a changed time notice to all time dependents for the current |
michael@0 | 1951 | // interval end.' |
michael@0 | 1952 | // |
michael@0 | 1953 | nsSMILInstanceTime* |
michael@0 | 1954 | nsSMILTimedElement::CheckForEarlyEnd( |
michael@0 | 1955 | const nsSMILTimeValue& aContainerTime) const |
michael@0 | 1956 | { |
michael@0 | 1957 | NS_ABORT_IF_FALSE(mCurrentInterval, |
michael@0 | 1958 | "Checking for an early end but the current interval is not set"); |
michael@0 | 1959 | if (mRestartMode != RESTART_ALWAYS) |
michael@0 | 1960 | return nullptr; |
michael@0 | 1961 | |
michael@0 | 1962 | int32_t position = 0; |
michael@0 | 1963 | nsSMILInstanceTime* nextBegin = |
michael@0 | 1964 | GetNextGreater(mBeginInstances, mCurrentInterval->Begin()->Time(), |
michael@0 | 1965 | position); |
michael@0 | 1966 | |
michael@0 | 1967 | if (nextBegin && |
michael@0 | 1968 | nextBegin->Time() > mCurrentInterval->Begin()->Time() && |
michael@0 | 1969 | nextBegin->Time() < mCurrentInterval->End()->Time() && |
michael@0 | 1970 | nextBegin->Time() <= aContainerTime) { |
michael@0 | 1971 | return nextBegin; |
michael@0 | 1972 | } |
michael@0 | 1973 | |
michael@0 | 1974 | return nullptr; |
michael@0 | 1975 | } |
michael@0 | 1976 | |
michael@0 | 1977 | void |
michael@0 | 1978 | nsSMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice) |
michael@0 | 1979 | { |
michael@0 | 1980 | // Check if updates are currently blocked (batched) |
michael@0 | 1981 | if (mDeferIntervalUpdates) { |
michael@0 | 1982 | mDoDeferredUpdate = true; |
michael@0 | 1983 | return; |
michael@0 | 1984 | } |
michael@0 | 1985 | |
michael@0 | 1986 | // We adopt the convention of not resolving intervals until the first |
michael@0 | 1987 | // sample. Otherwise, every time each attribute is set we'll re-resolve the |
michael@0 | 1988 | // current interval and notify all our time dependents of the change. |
michael@0 | 1989 | // |
michael@0 | 1990 | // The disadvantage of deferring resolving the interval is that DOM calls to |
michael@0 | 1991 | // to getStartTime will throw an INVALID_STATE_ERR exception until the |
michael@0 | 1992 | // document timeline begins since the start time has not yet been resolved. |
michael@0 | 1993 | if (mElementState == STATE_STARTUP) |
michael@0 | 1994 | return; |
michael@0 | 1995 | |
michael@0 | 1996 | // Although SMIL gives rules for detecting cycles in change notifications, |
michael@0 | 1997 | // some configurations can lead to create-delete-create-delete-etc. cycles |
michael@0 | 1998 | // which SMIL does not consider. |
michael@0 | 1999 | // |
michael@0 | 2000 | // In order to provide consistent behavior in such cases, we detect two |
michael@0 | 2001 | // deletes in a row and then refuse to create any further intervals. That is, |
michael@0 | 2002 | // we say the configuration is invalid. |
michael@0 | 2003 | if (mDeleteCount > 1) { |
michael@0 | 2004 | // When we update the delete count we also set the state to post active, so |
michael@0 | 2005 | // if we're not post active here then something other than |
michael@0 | 2006 | // UpdateCurrentInterval has updated the element state in between and all |
michael@0 | 2007 | // bets are off. |
michael@0 | 2008 | NS_ABORT_IF_FALSE(mElementState == STATE_POSTACTIVE, |
michael@0 | 2009 | "Expected to be in post-active state after performing double delete"); |
michael@0 | 2010 | return; |
michael@0 | 2011 | } |
michael@0 | 2012 | |
michael@0 | 2013 | // Check that we aren't stuck in infinite recursion updating some syncbase |
michael@0 | 2014 | // dependencies. Generally such situations should be detected in advance and |
michael@0 | 2015 | // the chain broken in a sensible and predictable manner, so if we're hitting |
michael@0 | 2016 | // this assertion we need to work out how to detect the case that's causing |
michael@0 | 2017 | // it. In release builds, just bail out before we overflow the stack. |
michael@0 | 2018 | AutoRestore<uint8_t> depthRestorer(mUpdateIntervalRecursionDepth); |
michael@0 | 2019 | if (++mUpdateIntervalRecursionDepth > sMaxUpdateIntervalRecursionDepth) { |
michael@0 | 2020 | NS_ABORT_IF_FALSE(false, |
michael@0 | 2021 | "Update current interval recursion depth exceeded threshold"); |
michael@0 | 2022 | return; |
michael@0 | 2023 | } |
michael@0 | 2024 | |
michael@0 | 2025 | // If the interval is active the begin time is fixed. |
michael@0 | 2026 | const nsSMILInstanceTime* beginTime = mElementState == STATE_ACTIVE |
michael@0 | 2027 | ? mCurrentInterval->Begin() |
michael@0 | 2028 | : nullptr; |
michael@0 | 2029 | nsSMILInterval updatedInterval; |
michael@0 | 2030 | if (GetNextInterval(GetPreviousInterval(), mCurrentInterval, |
michael@0 | 2031 | beginTime, updatedInterval)) { |
michael@0 | 2032 | |
michael@0 | 2033 | if (mElementState == STATE_POSTACTIVE) { |
michael@0 | 2034 | |
michael@0 | 2035 | NS_ABORT_IF_FALSE(!mCurrentInterval, |
michael@0 | 2036 | "In postactive state but the interval has been set"); |
michael@0 | 2037 | mCurrentInterval = new nsSMILInterval(updatedInterval); |
michael@0 | 2038 | mElementState = STATE_WAITING; |
michael@0 | 2039 | NotifyNewInterval(); |
michael@0 | 2040 | |
michael@0 | 2041 | } else { |
michael@0 | 2042 | |
michael@0 | 2043 | bool beginChanged = false; |
michael@0 | 2044 | bool endChanged = false; |
michael@0 | 2045 | |
michael@0 | 2046 | if (mElementState != STATE_ACTIVE && |
michael@0 | 2047 | !updatedInterval.Begin()->SameTimeAndBase( |
michael@0 | 2048 | *mCurrentInterval->Begin())) { |
michael@0 | 2049 | mCurrentInterval->SetBegin(*updatedInterval.Begin()); |
michael@0 | 2050 | beginChanged = true; |
michael@0 | 2051 | } |
michael@0 | 2052 | |
michael@0 | 2053 | if (!updatedInterval.End()->SameTimeAndBase(*mCurrentInterval->End())) { |
michael@0 | 2054 | mCurrentInterval->SetEnd(*updatedInterval.End()); |
michael@0 | 2055 | endChanged = true; |
michael@0 | 2056 | } |
michael@0 | 2057 | |
michael@0 | 2058 | if (beginChanged || endChanged || aForceChangeNotice) { |
michael@0 | 2059 | NotifyChangedInterval(mCurrentInterval, beginChanged, endChanged); |
michael@0 | 2060 | } |
michael@0 | 2061 | } |
michael@0 | 2062 | |
michael@0 | 2063 | // There's a chance our next milestone has now changed, so update the time |
michael@0 | 2064 | // container |
michael@0 | 2065 | RegisterMilestone(); |
michael@0 | 2066 | } else { // GetNextInterval failed: Current interval is no longer valid |
michael@0 | 2067 | if (mElementState == STATE_ACTIVE) { |
michael@0 | 2068 | // The interval is active so we can't just delete it, instead trim it so |
michael@0 | 2069 | // that begin==end. |
michael@0 | 2070 | if (!mCurrentInterval->End()->SameTimeAndBase(*mCurrentInterval->Begin())) |
michael@0 | 2071 | { |
michael@0 | 2072 | mCurrentInterval->SetEnd(*mCurrentInterval->Begin()); |
michael@0 | 2073 | NotifyChangedInterval(mCurrentInterval, false, true); |
michael@0 | 2074 | } |
michael@0 | 2075 | // The transition to the postactive state will take place on the next |
michael@0 | 2076 | // sample (along with firing end events, clearing intervals etc.) |
michael@0 | 2077 | RegisterMilestone(); |
michael@0 | 2078 | } else if (mElementState == STATE_WAITING) { |
michael@0 | 2079 | AutoRestore<uint8_t> deleteCountRestorer(mDeleteCount); |
michael@0 | 2080 | ++mDeleteCount; |
michael@0 | 2081 | mElementState = STATE_POSTACTIVE; |
michael@0 | 2082 | ResetCurrentInterval(); |
michael@0 | 2083 | } |
michael@0 | 2084 | } |
michael@0 | 2085 | } |
michael@0 | 2086 | |
michael@0 | 2087 | void |
michael@0 | 2088 | nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime) |
michael@0 | 2089 | { |
michael@0 | 2090 | if (mClient) { |
michael@0 | 2091 | uint32_t repeatIteration; |
michael@0 | 2092 | nsSMILTime simpleTime = |
michael@0 | 2093 | ActiveTimeToSimpleTime(aActiveTime, repeatIteration); |
michael@0 | 2094 | mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); |
michael@0 | 2095 | } |
michael@0 | 2096 | } |
michael@0 | 2097 | |
michael@0 | 2098 | void |
michael@0 | 2099 | nsSMILTimedElement::SampleFillValue() |
michael@0 | 2100 | { |
michael@0 | 2101 | if (mFillMode != FILL_FREEZE || !mClient) |
michael@0 | 2102 | return; |
michael@0 | 2103 | |
michael@0 | 2104 | nsSMILTime activeTime; |
michael@0 | 2105 | |
michael@0 | 2106 | if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) { |
michael@0 | 2107 | const nsSMILInterval* prevInterval = GetPreviousInterval(); |
michael@0 | 2108 | NS_ABORT_IF_FALSE(prevInterval, |
michael@0 | 2109 | "Attempting to sample fill value but there is no previous interval"); |
michael@0 | 2110 | NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() && |
michael@0 | 2111 | prevInterval->End()->IsFixedTime(), |
michael@0 | 2112 | "Attempting to sample fill value but the endpoint of the previous " |
michael@0 | 2113 | "interval is not resolved and fixed"); |
michael@0 | 2114 | |
michael@0 | 2115 | activeTime = prevInterval->End()->Time().GetMillis() - |
michael@0 | 2116 | prevInterval->Begin()->Time().GetMillis(); |
michael@0 | 2117 | |
michael@0 | 2118 | // If the interval's repeat duration was shorter than its active duration, |
michael@0 | 2119 | // use the end of the repeat duration to determine the frozen animation's |
michael@0 | 2120 | // state. |
michael@0 | 2121 | nsSMILTimeValue repeatDuration = GetRepeatDuration(); |
michael@0 | 2122 | if (repeatDuration.IsDefinite()) { |
michael@0 | 2123 | activeTime = std::min(repeatDuration.GetMillis(), activeTime); |
michael@0 | 2124 | } |
michael@0 | 2125 | } else { |
michael@0 | 2126 | MOZ_ASSERT(mElementState == STATE_ACTIVE, |
michael@0 | 2127 | "Attempting to sample fill value when we're in an unexpected state " |
michael@0 | 2128 | "(probably STATE_STARTUP)"); |
michael@0 | 2129 | |
michael@0 | 2130 | // If we are being asked to sample the fill value while active we *must* |
michael@0 | 2131 | // have a repeat duration shorter than the active duration so use that. |
michael@0 | 2132 | MOZ_ASSERT(GetRepeatDuration().IsDefinite(), |
michael@0 | 2133 | "Attempting to sample fill value of an active animation with " |
michael@0 | 2134 | "an indefinite repeat duration"); |
michael@0 | 2135 | activeTime = GetRepeatDuration().GetMillis(); |
michael@0 | 2136 | } |
michael@0 | 2137 | |
michael@0 | 2138 | uint32_t repeatIteration; |
michael@0 | 2139 | nsSMILTime simpleTime = |
michael@0 | 2140 | ActiveTimeToSimpleTime(activeTime, repeatIteration); |
michael@0 | 2141 | |
michael@0 | 2142 | if (simpleTime == 0L && repeatIteration) { |
michael@0 | 2143 | mClient->SampleLastValue(--repeatIteration); |
michael@0 | 2144 | } else { |
michael@0 | 2145 | mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); |
michael@0 | 2146 | } |
michael@0 | 2147 | } |
michael@0 | 2148 | |
michael@0 | 2149 | nsresult |
michael@0 | 2150 | nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime, |
michael@0 | 2151 | double aOffsetSeconds, bool aIsBegin) |
michael@0 | 2152 | { |
michael@0 | 2153 | double offset = aOffsetSeconds * PR_MSEC_PER_SEC; |
michael@0 | 2154 | |
michael@0 | 2155 | // Check we won't overflow the range of nsSMILTime |
michael@0 | 2156 | if (aCurrentTime + NS_round(offset) > INT64_MAX) |
michael@0 | 2157 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 2158 | |
michael@0 | 2159 | nsSMILTimeValue timeVal(aCurrentTime + int64_t(NS_round(offset))); |
michael@0 | 2160 | |
michael@0 | 2161 | nsRefPtr<nsSMILInstanceTime> instanceTime = |
michael@0 | 2162 | new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM); |
michael@0 | 2163 | |
michael@0 | 2164 | AddInstanceTime(instanceTime, aIsBegin); |
michael@0 | 2165 | |
michael@0 | 2166 | return NS_OK; |
michael@0 | 2167 | } |
michael@0 | 2168 | |
michael@0 | 2169 | void |
michael@0 | 2170 | nsSMILTimedElement::RegisterMilestone() |
michael@0 | 2171 | { |
michael@0 | 2172 | nsSMILTimeContainer* container = GetTimeContainer(); |
michael@0 | 2173 | if (!container) |
michael@0 | 2174 | return; |
michael@0 | 2175 | NS_ABORT_IF_FALSE(mAnimationElement, |
michael@0 | 2176 | "Got a time container without an owning animation element"); |
michael@0 | 2177 | |
michael@0 | 2178 | nsSMILMilestone nextMilestone; |
michael@0 | 2179 | if (!GetNextMilestone(nextMilestone)) |
michael@0 | 2180 | return; |
michael@0 | 2181 | |
michael@0 | 2182 | // This method is called every time we might possibly have updated our |
michael@0 | 2183 | // current interval, but since nsSMILTimeContainer makes no attempt to filter |
michael@0 | 2184 | // out redundant milestones we do some rudimentary filtering here. It's not |
michael@0 | 2185 | // perfect, but unnecessary samples are fairly cheap. |
michael@0 | 2186 | if (nextMilestone >= mPrevRegisteredMilestone) |
michael@0 | 2187 | return; |
michael@0 | 2188 | |
michael@0 | 2189 | container->AddMilestone(nextMilestone, *mAnimationElement); |
michael@0 | 2190 | mPrevRegisteredMilestone = nextMilestone; |
michael@0 | 2191 | } |
michael@0 | 2192 | |
michael@0 | 2193 | bool |
michael@0 | 2194 | nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const |
michael@0 | 2195 | { |
michael@0 | 2196 | // Return the next key moment in our lifetime. |
michael@0 | 2197 | // |
michael@0 | 2198 | // XXX It may be possible in future to optimise this so that we only register |
michael@0 | 2199 | // for milestones if: |
michael@0 | 2200 | // a) We have time dependents, or |
michael@0 | 2201 | // b) We are dependent on events or syncbase relationships, or |
michael@0 | 2202 | // c) There are registered listeners for our events |
michael@0 | 2203 | // |
michael@0 | 2204 | // Then for the simple case where everything uses offset values we could |
michael@0 | 2205 | // ignore milestones altogether. |
michael@0 | 2206 | // |
michael@0 | 2207 | // We'd need to be careful, however, that if one of those conditions became |
michael@0 | 2208 | // true in between samples that we registered our next milestone at that |
michael@0 | 2209 | // point. |
michael@0 | 2210 | |
michael@0 | 2211 | switch (mElementState) |
michael@0 | 2212 | { |
michael@0 | 2213 | case STATE_STARTUP: |
michael@0 | 2214 | // All elements register for an initial end sample at t=0 where we resolve |
michael@0 | 2215 | // our initial interval. |
michael@0 | 2216 | aNextMilestone.mIsEnd = true; // Initial sample should be an end sample |
michael@0 | 2217 | aNextMilestone.mTime = 0; |
michael@0 | 2218 | return true; |
michael@0 | 2219 | |
michael@0 | 2220 | case STATE_WAITING: |
michael@0 | 2221 | NS_ABORT_IF_FALSE(mCurrentInterval, |
michael@0 | 2222 | "In waiting state but the current interval has not been set"); |
michael@0 | 2223 | aNextMilestone.mIsEnd = false; |
michael@0 | 2224 | aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis(); |
michael@0 | 2225 | return true; |
michael@0 | 2226 | |
michael@0 | 2227 | case STATE_ACTIVE: |
michael@0 | 2228 | { |
michael@0 | 2229 | // Work out what comes next: the interval end or the next repeat iteration |
michael@0 | 2230 | nsSMILTimeValue nextRepeat; |
michael@0 | 2231 | if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) { |
michael@0 | 2232 | nsSMILTime nextRepeatActiveTime = |
michael@0 | 2233 | (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis(); |
michael@0 | 2234 | // Check that the repeat fits within the repeat duration |
michael@0 | 2235 | if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) { |
michael@0 | 2236 | nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() + |
michael@0 | 2237 | nextRepeatActiveTime); |
michael@0 | 2238 | } |
michael@0 | 2239 | } |
michael@0 | 2240 | nsSMILTimeValue nextMilestone = |
michael@0 | 2241 | std::min(mCurrentInterval->End()->Time(), nextRepeat); |
michael@0 | 2242 | |
michael@0 | 2243 | // Check for an early end before that time |
michael@0 | 2244 | nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone); |
michael@0 | 2245 | if (earlyEnd) { |
michael@0 | 2246 | aNextMilestone.mIsEnd = true; |
michael@0 | 2247 | aNextMilestone.mTime = earlyEnd->Time().GetMillis(); |
michael@0 | 2248 | return true; |
michael@0 | 2249 | } |
michael@0 | 2250 | |
michael@0 | 2251 | // Apply the previously calculated milestone |
michael@0 | 2252 | if (nextMilestone.IsDefinite()) { |
michael@0 | 2253 | aNextMilestone.mIsEnd = nextMilestone != nextRepeat; |
michael@0 | 2254 | aNextMilestone.mTime = nextMilestone.GetMillis(); |
michael@0 | 2255 | return true; |
michael@0 | 2256 | } |
michael@0 | 2257 | |
michael@0 | 2258 | return false; |
michael@0 | 2259 | } |
michael@0 | 2260 | |
michael@0 | 2261 | case STATE_POSTACTIVE: |
michael@0 | 2262 | return false; |
michael@0 | 2263 | } |
michael@0 | 2264 | MOZ_CRASH("Invalid element state"); |
michael@0 | 2265 | } |
michael@0 | 2266 | |
michael@0 | 2267 | void |
michael@0 | 2268 | nsSMILTimedElement::NotifyNewInterval() |
michael@0 | 2269 | { |
michael@0 | 2270 | NS_ABORT_IF_FALSE(mCurrentInterval, |
michael@0 | 2271 | "Attempting to notify dependents of a new interval but the interval " |
michael@0 | 2272 | "is not set"); |
michael@0 | 2273 | |
michael@0 | 2274 | nsSMILTimeContainer* container = GetTimeContainer(); |
michael@0 | 2275 | if (container) { |
michael@0 | 2276 | container->SyncPauseTime(); |
michael@0 | 2277 | } |
michael@0 | 2278 | |
michael@0 | 2279 | NotifyTimeDependentsParams params = { this, container }; |
michael@0 | 2280 | mTimeDependents.EnumerateEntries(NotifyNewIntervalCallback, ¶ms); |
michael@0 | 2281 | } |
michael@0 | 2282 | |
michael@0 | 2283 | void |
michael@0 | 2284 | nsSMILTimedElement::NotifyChangedInterval(nsSMILInterval* aInterval, |
michael@0 | 2285 | bool aBeginObjectChanged, |
michael@0 | 2286 | bool aEndObjectChanged) |
michael@0 | 2287 | { |
michael@0 | 2288 | NS_ABORT_IF_FALSE(aInterval, "Null interval for change notification"); |
michael@0 | 2289 | |
michael@0 | 2290 | nsSMILTimeContainer* container = GetTimeContainer(); |
michael@0 | 2291 | if (container) { |
michael@0 | 2292 | container->SyncPauseTime(); |
michael@0 | 2293 | } |
michael@0 | 2294 | |
michael@0 | 2295 | // Copy the instance times list since notifying the instance times can result |
michael@0 | 2296 | // in a chain reaction whereby our own interval gets deleted along with its |
michael@0 | 2297 | // instance times. |
michael@0 | 2298 | InstanceTimeList times; |
michael@0 | 2299 | aInterval->GetDependentTimes(times); |
michael@0 | 2300 | |
michael@0 | 2301 | for (uint32_t i = 0; i < times.Length(); ++i) { |
michael@0 | 2302 | times[i]->HandleChangedInterval(container, aBeginObjectChanged, |
michael@0 | 2303 | aEndObjectChanged); |
michael@0 | 2304 | } |
michael@0 | 2305 | } |
michael@0 | 2306 | |
michael@0 | 2307 | void |
michael@0 | 2308 | nsSMILTimedElement::FireTimeEventAsync(uint32_t aMsg, int32_t aDetail) |
michael@0 | 2309 | { |
michael@0 | 2310 | if (!mAnimationElement) |
michael@0 | 2311 | return; |
michael@0 | 2312 | |
michael@0 | 2313 | nsCOMPtr<nsIRunnable> event = |
michael@0 | 2314 | new AsyncTimeEventRunner(mAnimationElement, aMsg, aDetail); |
michael@0 | 2315 | NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); |
michael@0 | 2316 | } |
michael@0 | 2317 | |
michael@0 | 2318 | const nsSMILInstanceTime* |
michael@0 | 2319 | nsSMILTimedElement::GetEffectiveBeginInstance() const |
michael@0 | 2320 | { |
michael@0 | 2321 | switch (mElementState) |
michael@0 | 2322 | { |
michael@0 | 2323 | case STATE_STARTUP: |
michael@0 | 2324 | return nullptr; |
michael@0 | 2325 | |
michael@0 | 2326 | case STATE_ACTIVE: |
michael@0 | 2327 | return mCurrentInterval->Begin(); |
michael@0 | 2328 | |
michael@0 | 2329 | case STATE_WAITING: |
michael@0 | 2330 | case STATE_POSTACTIVE: |
michael@0 | 2331 | { |
michael@0 | 2332 | const nsSMILInterval* prevInterval = GetPreviousInterval(); |
michael@0 | 2333 | return prevInterval ? prevInterval->Begin() : nullptr; |
michael@0 | 2334 | } |
michael@0 | 2335 | } |
michael@0 | 2336 | MOZ_CRASH("Invalid element state"); |
michael@0 | 2337 | } |
michael@0 | 2338 | |
michael@0 | 2339 | const nsSMILInterval* |
michael@0 | 2340 | nsSMILTimedElement::GetPreviousInterval() const |
michael@0 | 2341 | { |
michael@0 | 2342 | return mOldIntervals.IsEmpty() |
michael@0 | 2343 | ? nullptr |
michael@0 | 2344 | : mOldIntervals[mOldIntervals.Length()-1].get(); |
michael@0 | 2345 | } |
michael@0 | 2346 | |
michael@0 | 2347 | bool |
michael@0 | 2348 | nsSMILTimedElement::HasClientInFillRange() const |
michael@0 | 2349 | { |
michael@0 | 2350 | // Returns true if we have a client that is in the range where it will fill |
michael@0 | 2351 | return mClient && |
michael@0 | 2352 | ((mElementState != STATE_ACTIVE && HasPlayed()) || |
michael@0 | 2353 | (mElementState == STATE_ACTIVE && !mClient->IsActive())); |
michael@0 | 2354 | } |
michael@0 | 2355 | |
michael@0 | 2356 | bool |
michael@0 | 2357 | nsSMILTimedElement::EndHasEventConditions() const |
michael@0 | 2358 | { |
michael@0 | 2359 | for (uint32_t i = 0; i < mEndSpecs.Length(); ++i) { |
michael@0 | 2360 | if (mEndSpecs[i]->IsEventBased()) |
michael@0 | 2361 | return true; |
michael@0 | 2362 | } |
michael@0 | 2363 | return false; |
michael@0 | 2364 | } |
michael@0 | 2365 | |
michael@0 | 2366 | bool |
michael@0 | 2367 | nsSMILTimedElement::AreEndTimesDependentOn( |
michael@0 | 2368 | const nsSMILInstanceTime* aBase) const |
michael@0 | 2369 | { |
michael@0 | 2370 | if (mEndInstances.IsEmpty()) |
michael@0 | 2371 | return false; |
michael@0 | 2372 | |
michael@0 | 2373 | for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { |
michael@0 | 2374 | if (mEndInstances[i]->GetBaseTime() != aBase) { |
michael@0 | 2375 | return false; |
michael@0 | 2376 | } |
michael@0 | 2377 | } |
michael@0 | 2378 | return true; |
michael@0 | 2379 | } |
michael@0 | 2380 | |
michael@0 | 2381 | //---------------------------------------------------------------------- |
michael@0 | 2382 | // Hashtable callback functions |
michael@0 | 2383 | |
michael@0 | 2384 | /* static */ PLDHashOperator |
michael@0 | 2385 | nsSMILTimedElement::NotifyNewIntervalCallback(TimeValueSpecPtrKey* aKey, |
michael@0 | 2386 | void* aData) |
michael@0 | 2387 | { |
michael@0 | 2388 | NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table"); |
michael@0 | 2389 | NS_ABORT_IF_FALSE(aKey->GetKey(), |
michael@0 | 2390 | "null nsSMILTimeValueSpec in set of time dependents"); |
michael@0 | 2391 | |
michael@0 | 2392 | NotifyTimeDependentsParams* params = |
michael@0 | 2393 | static_cast<NotifyTimeDependentsParams*>(aData); |
michael@0 | 2394 | NS_ABORT_IF_FALSE(params, "null data ptr while enumerating hashtable"); |
michael@0 | 2395 | nsSMILInterval* interval = params->mTimedElement->mCurrentInterval; |
michael@0 | 2396 | // It's possible that in notifying one new time dependent of a new interval |
michael@0 | 2397 | // that a chain reaction is triggered which results in the original interval |
michael@0 | 2398 | // disappearing. If that's the case we can skip sending further notifications. |
michael@0 | 2399 | if (!interval) |
michael@0 | 2400 | return PL_DHASH_STOP; |
michael@0 | 2401 | |
michael@0 | 2402 | nsSMILTimeValueSpec* spec = aKey->GetKey(); |
michael@0 | 2403 | spec->HandleNewInterval(*interval, params->mTimeContainer); |
michael@0 | 2404 | return PL_DHASH_NEXT; |
michael@0 | 2405 | } |