tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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