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