tor-browser

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

SMILTimeContainer.cpp (9675B)


      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 "SMILTimeContainer.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "mozilla/AutoRestore.h"
     12 #include "mozilla/CheckedInt.h"
     13 #include "mozilla/SMILTimeValue.h"
     14 #include "mozilla/SMILTimedElement.h"
     15 
     16 namespace mozilla {
     17 
     18 SMILTimeContainer::SMILTimeContainer()
     19    : mParent(nullptr),
     20      mCurrentTime(0L),
     21      mParentOffset(0L),
     22      mPauseStart(0L),
     23      mNeedsPauseSample(false),
     24      mNeedsRewind(false),
     25      mIsSeeking(false),
     26 #ifdef DEBUG
     27      mHoldingEntries(false),
     28 #endif
     29      mPauseState(PAUSE_BEGIN) {
     30 }
     31 
     32 SMILTimeContainer::~SMILTimeContainer() {
     33  if (mParent) {
     34    mParent->RemoveChild(*this);
     35  }
     36 }
     37 
     38 SMILTimeValue SMILTimeContainer::ContainerToParentTime(
     39    SMILTime aContainerTime) const {
     40  // If we're paused, then future times are indefinite
     41  if (IsPaused() && aContainerTime > mCurrentTime)
     42    return SMILTimeValue::Indefinite();
     43 
     44  return SMILTimeValue(aContainerTime + mParentOffset);
     45 }
     46 
     47 SMILTimeValue SMILTimeContainer::ParentToContainerTime(
     48    SMILTime aParentTime) const {
     49  // If we're paused, then any time after when we paused is indefinite
     50  if (IsPaused() && aParentTime > mPauseStart)
     51    return SMILTimeValue::Indefinite();
     52 
     53  return SMILTimeValue(aParentTime - mParentOffset);
     54 }
     55 
     56 void SMILTimeContainer::Begin() {
     57  Resume(PAUSE_BEGIN);
     58  if (mPauseState) {
     59    mNeedsPauseSample = true;
     60  }
     61 
     62  // This is a little bit complicated here. Ideally we'd just like to call
     63  // Sample() and force an initial sample but this turns out to be a bad idea
     64  // because this may mean that NeedsSample() no longer reports true and so when
     65  // we come to the first real sample our parent will skip us over altogether.
     66  // So we force the time to be updated and adopt the policy to never call
     67  // Sample() ourselves but to always leave that to our parent or client.
     68 
     69  UpdateCurrentTime();
     70 }
     71 
     72 void SMILTimeContainer::Pause(uint32_t aType) {
     73  bool didStartPause = false;
     74 
     75  if (!mPauseState && aType) {
     76    mPauseStart = GetParentTime();
     77    mNeedsPauseSample = true;
     78    didStartPause = true;
     79  }
     80 
     81  mPauseState |= aType;
     82 
     83  if (didStartPause) {
     84    NotifyTimeChange();
     85  }
     86 }
     87 
     88 void SMILTimeContainer::PauseAt(SMILTime aTime) {
     89  mPauseTime = Some(std::max<SMILTime>(0, aTime));
     90 }
     91 
     92 void SMILTimeContainer::Resume(uint32_t aType) {
     93  if (!mPauseState) return;
     94 
     95  mPauseState &= ~aType;
     96 
     97  if (!mPauseState) {
     98    SMILTime extraOffset = GetParentTime() - mPauseStart;
     99    mParentOffset += extraOffset;
    100    NotifyTimeChange();
    101  }
    102 }
    103 
    104 SMILTime SMILTimeContainer::GetCurrentTimeAsSMILTime() const {
    105  // The following behaviour is consistent with:
    106  // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
    107  //  #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
    108  // which says that if GetCurrentTime is called before the document timeline
    109  // has begun we should just return 0.
    110  if (IsPausedByType(PAUSE_BEGIN)) return 0L;
    111 
    112  return mCurrentTime;
    113 }
    114 
    115 void SMILTimeContainer::SetCurrentTime(SMILTime aSeekTo) {
    116  // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's
    117  // behaviour of clamping negative times to 0.
    118  aSeekTo = std::max<SMILTime>(0, aSeekTo);
    119 
    120  // The following behaviour is consistent with:
    121  // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
    122  //  #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
    123  // which says that if SetCurrentTime is called before the document timeline
    124  // has begun we should still adjust the offset.
    125  SMILTime parentTime = GetParentTime();
    126  mParentOffset = parentTime - aSeekTo;
    127  mIsSeeking = true;
    128 
    129  if (IsPaused()) {
    130    mNeedsPauseSample = true;
    131    mPauseStart = parentTime;
    132  }
    133 
    134  if (aSeekTo < mCurrentTime) {
    135    // Backwards seek
    136    mNeedsRewind = true;
    137    ClearMilestones();
    138  }
    139 
    140  // Force an update to the current time in case we get a call to GetCurrentTime
    141  // before another call to Sample().
    142  UpdateCurrentTime();
    143 
    144  NotifyTimeChange();
    145 }
    146 
    147 SMILTime SMILTimeContainer::GetParentTime() const {
    148  if (mParent) return mParent->GetCurrentTimeAsSMILTime();
    149 
    150  return 0L;
    151 }
    152 
    153 void SMILTimeContainer::SyncPauseTime() {
    154  if (IsPaused()) {
    155    SMILTime parentTime = GetParentTime();
    156    SMILTime extraOffset = parentTime - mPauseStart;
    157    mParentOffset += extraOffset;
    158    mPauseStart = parentTime;
    159  }
    160 }
    161 
    162 void SMILTimeContainer::Sample() {
    163  if (!NeedsSample()) {
    164    return;
    165  }
    166 
    167  UpdateCurrentTime();
    168  DoSample();
    169  mNeedsPauseSample = false;
    170 
    171  if (mPauseTime && mCurrentTime >= mPauseTime.value()) {
    172    Pause(PAUSE_SCRIPT);
    173  }
    174 }
    175 
    176 nsresult SMILTimeContainer::SetParent(SMILTimeContainer* aParent) {
    177  if (mParent) {
    178    mParent->RemoveChild(*this);
    179    // When we're not attached to a parent time container, GetParentTime() will
    180    // return 0. We need to adjust our pause state information to be relative to
    181    // this new time base.
    182    // Note that since "current time = parent time - parent offset" setting the
    183    // parent offset and pause start as follows preserves our current time even
    184    // while parent time = 0.
    185    mParentOffset = -mCurrentTime;
    186    mPauseStart = 0L;
    187  }
    188 
    189  mParent = aParent;
    190 
    191  nsresult rv = NS_OK;
    192  if (mParent) {
    193    rv = mParent->AddChild(*this);
    194  }
    195 
    196  return rv;
    197 }
    198 
    199 void SMILTimeContainer::AddMilestone(
    200    const SMILMilestone& aMilestone,
    201    mozilla::dom::SVGAnimationElement& aElement) {
    202  // We record the milestone time and store it along with the element but this
    203  // time may change (e.g. if attributes are changed on the timed element in
    204  // between samples). If this happens, then we may do an unecessary sample
    205  // but that's pretty cheap.
    206  MOZ_ASSERT(!mHoldingEntries);
    207  mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement));
    208 }
    209 
    210 void SMILTimeContainer::ClearMilestones() {
    211  MOZ_ASSERT(!mHoldingEntries);
    212  mMilestoneEntries.Clear();
    213 }
    214 
    215 bool SMILTimeContainer::GetNextMilestoneInParentTime(
    216    SMILMilestone& aNextMilestone) const {
    217  if (mMilestoneEntries.IsEmpty()) return false;
    218 
    219  SMILTimeValue parentTime =
    220      ContainerToParentTime(mMilestoneEntries.Top().mMilestone.mTime);
    221  if (!parentTime.IsDefinite()) return false;
    222 
    223  aNextMilestone = SMILMilestone(parentTime.GetMillis(),
    224                                 mMilestoneEntries.Top().mMilestone.mIsEnd);
    225 
    226  return true;
    227 }
    228 
    229 bool SMILTimeContainer::PopMilestoneElementsAtMilestone(
    230    const SMILMilestone& aMilestone, AnimElemArray& aMatchedElements) {
    231  if (mMilestoneEntries.IsEmpty()) return false;
    232 
    233  SMILTimeValue containerTime = ParentToContainerTime(aMilestone.mTime);
    234  if (!containerTime.IsDefinite()) return false;
    235 
    236  SMILMilestone containerMilestone(containerTime.GetMillis(),
    237                                   aMilestone.mIsEnd);
    238 
    239  MOZ_ASSERT(mMilestoneEntries.Top().mMilestone >= containerMilestone,
    240             "Trying to pop off earliest times but we have earlier ones that "
    241             "were overlooked");
    242 
    243  MOZ_ASSERT(!mHoldingEntries);
    244 
    245  bool gotOne = false;
    246  while (!mMilestoneEntries.IsEmpty() &&
    247         mMilestoneEntries.Top().mMilestone == containerMilestone) {
    248    aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase);
    249    gotOne = true;
    250  }
    251 
    252  return gotOne;
    253 }
    254 
    255 void SMILTimeContainer::Traverse(
    256    nsCycleCollectionTraversalCallback* aCallback) {
    257 #ifdef DEBUG
    258  AutoRestore<bool> saveHolding(mHoldingEntries);
    259  mHoldingEntries = true;
    260 #endif
    261  const MilestoneEntry* p = mMilestoneEntries.Elements();
    262  while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) {
    263    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase");
    264    aCallback->NoteXPCOMChild(static_cast<nsIContent*>(p->mTimebase.get()));
    265    ++p;
    266  }
    267 }
    268 
    269 void SMILTimeContainer::Unlink() {
    270  MOZ_ASSERT(!mHoldingEntries);
    271  mMilestoneEntries.Clear();
    272 }
    273 
    274 void SMILTimeContainer::UpdateCurrentTime() {
    275  SMILTime now = IsPaused() ? mPauseStart : GetParentTime();
    276  MOZ_ASSERT(now >= mParentOffset,
    277             "Container has negative time with respect to parent");
    278  const auto updatedCurrentTime = CheckedInt<SMILTime>(now) - mParentOffset;
    279  mCurrentTime = updatedCurrentTime.isValid()
    280                     ? updatedCurrentTime.value()
    281                     : std::numeric_limits<SMILTime>::max();
    282 }
    283 
    284 void SMILTimeContainer::NotifyTimeChange() {
    285  // Called when the container time is changed with respect to the document
    286  // time. When this happens time dependencies in other time containers need to
    287  // re-resolve their times because begin and end times are stored in container
    288  // time.
    289  //
    290  // To get the list of timed elements with dependencies we simply re-use the
    291  // milestone elements. This is because any timed element with dependents and
    292  // with significant transitions yet to fire should have their next milestone
    293  // registered. Other timed elements don't matter.
    294 
    295  // Copy the timed elements to a separate array before calling
    296  // HandleContainerTimeChange on each of them in case doing so mutates
    297  // mMilestoneEntries.
    298  nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elems;
    299 
    300  {
    301 #ifdef DEBUG
    302    AutoRestore<bool> saveHolding(mHoldingEntries);
    303    mHoldingEntries = true;
    304 #endif
    305    for (const MilestoneEntry* p = mMilestoneEntries.Elements();
    306         p < mMilestoneEntries.Elements() + mMilestoneEntries.Length(); ++p) {
    307      elems.AppendElement(p->mTimebase.get());
    308    }
    309  }
    310 
    311  for (auto& elem : elems) {
    312    elem->TimedElement().HandleContainerTimeChange();
    313  }
    314 }
    315 
    316 }  // namespace mozilla