dom/smil/nsSMILTimedElement.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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, &params);
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 }

mercurial