tor-browser

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

SMILTimeValueSpec.cpp (12449B)


      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 "mozilla/SMILTimeValueSpec.h"
      8 
      9 #include <limits>
     10 
     11 #include "mozilla/EventListenerManager.h"
     12 #include "mozilla/SMILInstanceTime.h"
     13 #include "mozilla/SMILInterval.h"
     14 #include "mozilla/SMILParserUtils.h"
     15 #include "mozilla/SMILTimeContainer.h"
     16 #include "mozilla/SMILTimeValue.h"
     17 #include "mozilla/SMILTimedElement.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/Event.h"
     20 #include "mozilla/dom/SVGAnimationElement.h"
     21 #include "mozilla/dom/TimeEvent.h"
     22 #include "nsString.h"
     23 
     24 using namespace mozilla::dom;
     25 
     26 namespace mozilla {
     27 
     28 //----------------------------------------------------------------------
     29 // Nested class: EventListener
     30 
     31 NS_IMPL_ISUPPORTS(SMILTimeValueSpec::EventListener, nsIDOMEventListener)
     32 
     33 NS_IMETHODIMP
     34 SMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent) {
     35  if (mSpec) {
     36    mSpec->HandleEvent(aEvent);
     37  }
     38  return NS_OK;
     39 }
     40 
     41 //----------------------------------------------------------------------
     42 // Implementation
     43 
     44 SMILTimeValueSpec::SMILTimeValueSpec(SMILTimedElement& aOwner, bool aIsBegin)
     45    : mOwner(&aOwner), mIsBegin(aIsBegin), mReferencedElement(this) {}
     46 
     47 SMILTimeValueSpec::~SMILTimeValueSpec() {
     48  UnregisterFromReferencedElement(mReferencedElement.get());
     49  if (mEventListener) {
     50    mEventListener->Disconnect();
     51    mEventListener = nullptr;
     52  }
     53 }
     54 
     55 nsresult SMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
     56                                    Element& aContextElement) {
     57  SMILTimeValueSpecParams params;
     58 
     59  if (!SMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
     60    return NS_ERROR_FAILURE;
     61 
     62  mParams = params;
     63 
     64  // According to SMIL 3.0:
     65  //   The special value "indefinite" does not yield an instance time in the
     66  //   begin list. It will, however yield a single instance with the value
     67  //   "indefinite" in an end list. This value is not removed by a reset.
     68  if (mParams.mType == SMILTimeValueSpecParams::OFFSET ||
     69      (!mIsBegin && mParams.mType == SMILTimeValueSpecParams::INDEFINITE)) {
     70    mOwner->AddInstanceTime(new SMILInstanceTime(mParams.mOffset), mIsBegin);
     71  }
     72 
     73  // Fill in the event symbol to simplify handling later
     74  if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
     75    mParams.mEventSymbol = nsGkAtoms::repeatEvent;
     76  }
     77 
     78  ResolveReferences(aContextElement);
     79 
     80  return NS_OK;
     81 }
     82 
     83 void SMILTimeValueSpec::ResolveReferences(Element& aContextElement) {
     84  if (mParams.mType != SMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
     85    return;
     86  }
     87 
     88  // If we're not bound to the document yet, don't worry, we'll get called again
     89  // when that happens
     90  if (!aContextElement.IsInComposedDoc()) return;
     91 
     92  // Hold ref to the old element so that it isn't destroyed in between resetting
     93  // the referenced element and using the pointer to update the referenced
     94  // element.
     95  RefPtr<Element> oldReferencedElement = mReferencedElement.get();
     96 
     97  if (mParams.mDependentElemID) {
     98    mReferencedElement.ResetToID(aContextElement, mParams.mDependentElemID);
     99  } else if (mParams.mType == SMILTimeValueSpecParams::EVENT) {
    100    Element* target = mOwner->GetTargetElement();
    101    mReferencedElement.ResetWithElement(target);
    102  } else {
    103    MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
    104  }
    105  UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
    106 }
    107 
    108 bool SMILTimeValueSpec::IsEventBased() const {
    109  return mParams.mType == SMILTimeValueSpecParams::EVENT ||
    110         mParams.mType == SMILTimeValueSpecParams::REPEAT;
    111 }
    112 
    113 void SMILTimeValueSpec::HandleNewInterval(
    114    SMILInterval& aInterval, const SMILTimeContainer* aSrcContainer) {
    115  const SMILInstanceTime& baseInstance =
    116      mParams.mSyncBegin ? *aInterval.Begin() : *aInterval.End();
    117  SMILTimeValue newTime =
    118      ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
    119 
    120  // Apply offset
    121  if (!ApplyOffset(newTime)) {
    122    NS_WARNING("New time overflows SMILTime, ignoring");
    123    return;
    124  }
    125 
    126  // Create the instance time and register it with the interval
    127  RefPtr<SMILInstanceTime> newInstance = new SMILInstanceTime(
    128      newTime, SMILInstanceTime::SOURCE_SYNCBASE, this, &aInterval);
    129  mOwner->AddInstanceTime(newInstance, mIsBegin);
    130 }
    131 
    132 void SMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) {
    133  if (!IsEventBased() || mParams.mDependentElemID) return;
    134 
    135  mReferencedElement.ResetWithElement(aNewTarget);
    136 }
    137 
    138 void SMILTimeValueSpec::HandleChangedInstanceTime(
    139    const SMILInstanceTime& aBaseTime, const SMILTimeContainer* aSrcContainer,
    140    SMILInstanceTime& aInstanceTimeToUpdate, bool aObjectChanged) {
    141  // If the instance time is fixed (e.g. because it's being used as the begin
    142  // time of an active or postactive interval) we just ignore the change.
    143  if (aInstanceTimeToUpdate.IsFixedTime()) return;
    144 
    145  SMILTimeValue updatedTime =
    146      ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
    147 
    148  // Apply offset
    149  if (!ApplyOffset(updatedTime)) {
    150    NS_WARNING("Updated time overflows SMILTime, ignoring");
    151    return;
    152  }
    153 
    154  // The timed element that owns the instance time does the updating so it can
    155  // re-sort its array of instance times more efficiently
    156  if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
    157    mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
    158  }
    159 }
    160 
    161 void SMILTimeValueSpec::HandleDeletedInstanceTime(
    162    SMILInstanceTime& aInstanceTime) {
    163  mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
    164 }
    165 
    166 bool SMILTimeValueSpec::DependsOnBegin() const { return mParams.mSyncBegin; }
    167 
    168 void SMILTimeValueSpec::Traverse(
    169    nsCycleCollectionTraversalCallback* aCallback) {
    170  mReferencedElement.Traverse(aCallback);
    171 }
    172 
    173 void SMILTimeValueSpec::Unlink() {
    174  UnregisterFromReferencedElement(mReferencedElement.get());
    175  mReferencedElement.Unlink();
    176 }
    177 
    178 //----------------------------------------------------------------------
    179 // Implementation helpers
    180 
    181 void SMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) {
    182  if (aFrom == aTo) return;
    183 
    184  UnregisterFromReferencedElement(aFrom);
    185 
    186  switch (mParams.mType) {
    187    case SMILTimeValueSpecParams::SYNCBASE: {
    188      SMILTimedElement* to = GetTimedElement(aTo);
    189      if (to) {
    190        to->AddDependent(*this);
    191      }
    192    } break;
    193 
    194    case SMILTimeValueSpecParams::EVENT:
    195    case SMILTimeValueSpecParams::REPEAT:
    196      RegisterEventListener(aTo);
    197      break;
    198 
    199    default:
    200      // not a referencing-type
    201      break;
    202  }
    203 }
    204 
    205 void SMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) {
    206  if (!aElement) return;
    207 
    208  if (mParams.mType == SMILTimeValueSpecParams::SYNCBASE) {
    209    SMILTimedElement* timedElement = GetTimedElement(aElement);
    210    if (timedElement) {
    211      timedElement->RemoveDependent(*this);
    212    }
    213    mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
    214  } else if (IsEventBased()) {
    215    UnregisterEventListener(aElement);
    216  }
    217 }
    218 
    219 SMILTimedElement* SMILTimeValueSpec::GetTimedElement(Element* aElement) {
    220  auto* animationElement = SVGAnimationElement::FromNodeOrNull(aElement);
    221  return animationElement ? &animationElement->TimedElement() : nullptr;
    222 }
    223 
    224 // Indicates whether we're allowed to register an event-listener
    225 // when scripting is disabled.
    226 bool SMILTimeValueSpec::IsEventAllowedWhenScriptingIsDisabled() {
    227  // The category of (SMIL-specific) "repeat(n)" events are allowed.
    228  if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
    229    return true;
    230  }
    231 
    232  // A specific list of other SMIL-related events are allowed, too.
    233  if (mParams.mType == SMILTimeValueSpecParams::EVENT &&
    234      (mParams.mEventSymbol == nsGkAtoms::repeat ||
    235       mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
    236       mParams.mEventSymbol == nsGkAtoms::beginEvent ||
    237       mParams.mEventSymbol == nsGkAtoms::endEvent)) {
    238    return true;
    239  }
    240 
    241  return false;
    242 }
    243 
    244 void SMILTimeValueSpec::RegisterEventListener(Element* aTarget) {
    245  MOZ_ASSERT(IsEventBased(),
    246             "Attempting to register event-listener for unexpected "
    247             "SMILTimeValueSpec type");
    248  MOZ_ASSERT(mParams.mEventSymbol,
    249             "Attempting to register event-listener but there is no event "
    250             "name");
    251 
    252  if (!aTarget) return;
    253 
    254  // When script is disabled, only allow registration for limited events.
    255  if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
    256      !IsEventAllowedWhenScriptingIsDisabled()) {
    257    return;
    258  }
    259 
    260  if (!mEventListener) {
    261    mEventListener = new EventListener(this);
    262  }
    263 
    264  EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
    265  if (!elm) {
    266    return;
    267  }
    268 
    269  elm->AddEventListenerByType(mEventListener,
    270                              nsDependentAtomString(mParams.mEventSymbol),
    271                              AllEventsAtSystemGroupBubble());
    272 }
    273 
    274 void SMILTimeValueSpec::UnregisterEventListener(Element* aTarget) {
    275  if (!aTarget || !mEventListener) {
    276    return;
    277  }
    278 
    279  EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
    280  if (!elm) {
    281    return;
    282  }
    283 
    284  elm->RemoveEventListenerByType(mEventListener,
    285                                 nsDependentAtomString(mParams.mEventSymbol),
    286                                 AllEventsAtSystemGroupBubble());
    287 }
    288 
    289 void SMILTimeValueSpec::HandleEvent(Event* aEvent) {
    290  MOZ_ASSERT(mEventListener, "Got event without an event listener");
    291  MOZ_ASSERT(IsEventBased(), "Got event for non-event SMILTimeValueSpec");
    292  MOZ_ASSERT(aEvent, "No event supplied");
    293 
    294  // XXX In the long run we should get the time from the event itself which will
    295  // store the time in global document time which we'll need to convert to our
    296  // time container
    297  SMILTimeContainer* container = mOwner->GetTimeContainer();
    298  if (!container) return;
    299 
    300  if (mParams.mType == SMILTimeValueSpecParams::REPEAT &&
    301      !CheckRepeatEventDetail(aEvent)) {
    302    return;
    303  }
    304 
    305  SMILTime currentTime = container->GetCurrentTimeAsSMILTime();
    306  SMILTimeValue newTime(currentTime);
    307  if (!ApplyOffset(newTime)) {
    308    NS_WARNING("New time generated from event overflows SMILTime, ignoring");
    309    return;
    310  }
    311 
    312  RefPtr<SMILInstanceTime> newInstance =
    313      new SMILInstanceTime(newTime, SMILInstanceTime::SOURCE_EVENT);
    314  mOwner->AddInstanceTime(newInstance, mIsBegin);
    315 }
    316 
    317 bool SMILTimeValueSpec::CheckRepeatEventDetail(Event* aEvent) {
    318  TimeEvent* timeEvent = aEvent->AsTimeEvent();
    319  if (!timeEvent) {
    320    NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
    321    return false;
    322  }
    323 
    324  int32_t detail = timeEvent->Detail();
    325  return detail > 0 && (uint32_t)detail == mParams.mRepeatIteration;
    326 }
    327 
    328 SMILTimeValue SMILTimeValueSpec::ConvertBetweenTimeContainers(
    329    const SMILTimeValue& aSrcTime, const SMILTimeContainer* aSrcContainer) {
    330  // If the source time is either indefinite or unresolved the result is going
    331  // to be the same
    332  if (!aSrcTime.IsDefinite()) return aSrcTime;
    333 
    334  // Convert from source time container to our parent time container
    335  const SMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
    336  if (dstContainer == aSrcContainer) return aSrcTime;
    337 
    338  // If one of the elements is not attached to a time container then we can't do
    339  // any meaningful conversion
    340  if (!aSrcContainer || !dstContainer) return SMILTimeValue();  // unresolved
    341 
    342  SMILTimeValue docTime =
    343      aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
    344 
    345  if (docTime.IsIndefinite())
    346    // This will happen if the source container is paused and we have a future
    347    // time. Just return the indefinite time.
    348    return docTime;
    349 
    350  MOZ_ASSERT(docTime.IsDefinite(),
    351             "ContainerToParentTime gave us an unresolved or indefinite time");
    352 
    353  return dstContainer->ParentToContainerTime(docTime.GetMillis());
    354 }
    355 
    356 bool SMILTimeValueSpec::ApplyOffset(SMILTimeValue& aTime) const {
    357  // indefinite + offset = indefinite. Likewise for unresolved times.
    358  if (!aTime.IsDefinite()) {
    359    return true;
    360  }
    361 
    362  double resultAsDouble =
    363      (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
    364  if (resultAsDouble > double(std::numeric_limits<SMILTime>::max()) ||
    365      resultAsDouble < double(std::numeric_limits<SMILTime>::min())) {
    366    return false;
    367  }
    368  aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
    369  return true;
    370 }
    371 
    372 }  // namespace mozilla