dom/smil/nsSMILTimeValueSpec.cpp

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

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

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

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/EventListenerManager.h"
     7 #include "mozilla/dom/SVGAnimationElement.h"
     8 #include "nsSMILTimeValueSpec.h"
     9 #include "nsSMILInterval.h"
    10 #include "nsSMILTimeContainer.h"
    11 #include "nsSMILTimeValue.h"
    12 #include "nsSMILTimedElement.h"
    13 #include "nsSMILInstanceTime.h"
    14 #include "nsSMILParserUtils.h"
    15 #include "nsIDOMKeyEvent.h"
    16 #include "nsIDOMTimeEvent.h"
    17 #include "nsString.h"
    18 #include <limits>
    20 using namespace mozilla;
    21 using namespace mozilla::dom;
    23 //----------------------------------------------------------------------
    24 // Nested class: EventListener
    26 NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
    28 NS_IMETHODIMP
    29 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
    30 {
    31   if (mSpec) {
    32     mSpec->HandleEvent(aEvent);
    33   }
    34   return NS_OK;
    35 }
    37 //----------------------------------------------------------------------
    38 // Implementation
    40 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
    41                                          bool aIsBegin)
    42   : mOwner(&aOwner),
    43     mIsBegin(aIsBegin),
    44     mReferencedElement(MOZ_THIS_IN_INITIALIZER_LIST())
    45 {
    46 }
    48 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
    49 {
    50   UnregisterFromReferencedElement(mReferencedElement.get());
    51   if (mEventListener) {
    52     mEventListener->Disconnect();
    53     mEventListener = nullptr;
    54   }
    55 }
    57 nsresult
    58 nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
    59                              Element* aContextNode)
    60 {
    61   nsSMILTimeValueSpecParams params;
    63   if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
    64     return NS_ERROR_FAILURE;
    66   mParams = params;
    68   // According to SMIL 3.0:
    69   //   The special value "indefinite" does not yield an instance time in the
    70   //   begin list. It will, however yield a single instance with the value
    71   //   "indefinite" in an end list. This value is not removed by a reset.
    72   if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET ||
    73       (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
    74     mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
    75   }
    77   // Fill in the event symbol to simplify handling later
    78   if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
    79     mParams.mEventSymbol = nsGkAtoms::repeatEvent;
    80   } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
    81     mParams.mEventSymbol = nsGkAtoms::keypress;
    82   }
    84   ResolveReferences(aContextNode);
    86   return NS_OK;
    87 }
    89 void
    90 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
    91 {
    92   if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
    93     return;
    95   NS_ABORT_IF_FALSE(aContextNode,
    96       "null context node for resolving timing references against");
    98   // If we're not bound to the document yet, don't worry, we'll get called again
    99   // when that happens
   100   if (!aContextNode->IsInDoc())
   101     return;
   103   // Hold ref to the old element so that it isn't destroyed in between resetting
   104   // the referenced element and using the pointer to update the referenced
   105   // element.
   106   nsRefPtr<Element> oldReferencedElement = mReferencedElement.get();
   108   if (mParams.mDependentElemID) {
   109     mReferencedElement.ResetWithID(aContextNode,
   110         nsDependentAtomString(mParams.mDependentElemID));
   111   } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
   112     Element* target = mOwner->GetTargetElement();
   113     mReferencedElement.ResetWithElement(target);
   114   } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
   115     nsIDocument* doc = aContextNode->GetCurrentDoc();
   116     NS_ABORT_IF_FALSE(doc, "We are in the document but current doc is null");
   117     mReferencedElement.ResetWithElement(doc->GetRootElement());
   118   } else {
   119     NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID");
   120   }
   121   UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
   122 }
   124 bool
   125 nsSMILTimeValueSpec::IsEventBased() const
   126 {
   127   return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
   128          mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
   129          mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
   130 }
   132 void
   133 nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval,
   134                                        const nsSMILTimeContainer* aSrcContainer)
   135 {
   136   const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin
   137     ? *aInterval.Begin() : *aInterval.End();
   138   nsSMILTimeValue newTime =
   139     ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
   141   // Apply offset
   142   if (!ApplyOffset(newTime)) {
   143     NS_WARNING("New time overflows nsSMILTime, ignoring");
   144     return;
   145   }
   147   // Create the instance time and register it with the interval
   148   nsRefPtr<nsSMILInstanceTime> newInstance =
   149     new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
   150                            &aInterval);
   151   mOwner->AddInstanceTime(newInstance, mIsBegin);
   152 }
   154 void
   155 nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
   156 {
   157   if (!IsEventBased() || mParams.mDependentElemID)
   158     return;
   160   mReferencedElement.ResetWithElement(aNewTarget);
   161 }
   163 void
   164 nsSMILTimeValueSpec::HandleChangedInstanceTime(
   165     const nsSMILInstanceTime& aBaseTime,
   166     const nsSMILTimeContainer* aSrcContainer,
   167     nsSMILInstanceTime& aInstanceTimeToUpdate,
   168     bool aObjectChanged)
   169 {
   170   // If the instance time is fixed (e.g. because it's being used as the begin
   171   // time of an active or postactive interval) we just ignore the change.
   172   if (aInstanceTimeToUpdate.IsFixedTime())
   173     return;
   175   nsSMILTimeValue updatedTime =
   176     ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
   178   // Apply offset
   179   if (!ApplyOffset(updatedTime)) {
   180     NS_WARNING("Updated time overflows nsSMILTime, ignoring");
   181     return;
   182   }
   184   // The timed element that owns the instance time does the updating so it can
   185   // re-sort its array of instance times more efficiently
   186   if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
   187     mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
   188   }
   189 }
   191 void
   192 nsSMILTimeValueSpec::HandleDeletedInstanceTime(
   193     nsSMILInstanceTime &aInstanceTime)
   194 {
   195   mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
   196 }
   198 bool
   199 nsSMILTimeValueSpec::DependsOnBegin() const
   200 {
   201   return mParams.mSyncBegin;
   202 }
   204 void
   205 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
   206 {
   207   mReferencedElement.Traverse(aCallback);
   208 }
   210 void
   211 nsSMILTimeValueSpec::Unlink()
   212 {
   213   UnregisterFromReferencedElement(mReferencedElement.get());
   214   mReferencedElement.Unlink();
   215 }
   217 //----------------------------------------------------------------------
   218 // Implementation helpers
   220 void
   221 nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
   222 {
   223   if (aFrom == aTo)
   224     return;
   226   UnregisterFromReferencedElement(aFrom);
   228   switch (mParams.mType)
   229   {
   230   case nsSMILTimeValueSpecParams::SYNCBASE:
   231     {
   232       nsSMILTimedElement* to = GetTimedElement(aTo);
   233       if (to) {
   234         to->AddDependent(*this);
   235       }
   236     }
   237     break;
   239   case nsSMILTimeValueSpecParams::EVENT:
   240   case nsSMILTimeValueSpecParams::REPEAT:
   241   case nsSMILTimeValueSpecParams::ACCESSKEY:
   242     RegisterEventListener(aTo);
   243     break;
   245   default:
   246     // not a referencing-type
   247     break;
   248   }
   249 }
   251 void
   252 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
   253 {
   254   if (!aElement)
   255     return;
   257   if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
   258     nsSMILTimedElement* timedElement = GetTimedElement(aElement);
   259     if (timedElement) {
   260       timedElement->RemoveDependent(*this);
   261     }
   262     mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
   263   } else if (IsEventBased()) {
   264     UnregisterEventListener(aElement);
   265   }
   266 }
   268 nsSMILTimedElement*
   269 nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
   270 {
   271   return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ?
   272     &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr;
   273 }
   275 // Indicates whether we're allowed to register an event-listener
   276 // when scripting is disabled.
   277 bool
   278 nsSMILTimeValueSpec::IsWhitelistedEvent()
   279 {
   280   // The category of (SMIL-specific) "repeat(n)" events are allowed.
   281   if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
   282     return true;
   283   }
   285   // A specific list of other SMIL-related events are allowed, too.
   286   if (mParams.mType == nsSMILTimeValueSpecParams::EVENT &&
   287       (mParams.mEventSymbol == nsGkAtoms::repeat ||
   288        mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
   289        mParams.mEventSymbol == nsGkAtoms::beginEvent ||
   290        mParams.mEventSymbol == nsGkAtoms::endEvent)) {
   291     return true;
   292   }
   294   return false;
   295 }
   297 void
   298 nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
   299 {
   300   NS_ABORT_IF_FALSE(IsEventBased(),
   301     "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
   302     " type");
   303   NS_ABORT_IF_FALSE(mParams.mEventSymbol,
   304     "Attempting to register event-listener but there is no event name");
   306   if (!aTarget)
   307     return;
   309   // When script is disabled, only allow registration for whitelisted events.
   310   if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
   311       !IsWhitelistedEvent()) {
   312     return;
   313   }
   315   if (!mEventListener) {
   316     mEventListener = new EventListener(this);
   317   }
   319   EventListenerManager* elm = GetEventListenerManager(aTarget);
   320   if (!elm)
   321     return;
   323   elm->AddEventListenerByType(mEventListener,
   324                               nsDependentAtomString(mParams.mEventSymbol),
   325                               AllEventsAtSystemGroupBubble());
   326 }
   328 void
   329 nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
   330 {
   331   if (!aTarget || !mEventListener)
   332     return;
   334   EventListenerManager* elm = GetEventListenerManager(aTarget);
   335   if (!elm)
   336     return;
   338   elm->RemoveEventListenerByType(mEventListener,
   339                                  nsDependentAtomString(mParams.mEventSymbol),
   340                                  AllEventsAtSystemGroupBubble());
   341 }
   343 EventListenerManager*
   344 nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget)
   345 {
   346   NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager");
   348   nsCOMPtr<EventTarget> target;
   350   if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
   351     nsIDocument* doc = aTarget->GetCurrentDoc();
   352     if (!doc)
   353       return nullptr;
   354     nsPIDOMWindow* win = doc->GetWindow();
   355     if (!win)
   356       return nullptr;
   357     target = do_QueryInterface(win);
   358   } else {
   359     target = aTarget;
   360   }
   361   if (!target)
   362     return nullptr;
   364   return target->GetOrCreateListenerManager();
   365 }
   367 void
   368 nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent)
   369 {
   370   NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener");
   371   NS_ABORT_IF_FALSE(IsEventBased(),
   372                     "Got event for non-event nsSMILTimeValueSpec");
   373   NS_ABORT_IF_FALSE(aEvent, "No event supplied");
   375   // XXX In the long run we should get the time from the event itself which will
   376   // store the time in global document time which we'll need to convert to our
   377   // time container
   378   nsSMILTimeContainer* container = mOwner->GetTimeContainer();
   379   if (!container)
   380     return;
   382   if (!CheckEventDetail(aEvent))
   383     return;
   385   nsSMILTime currentTime = container->GetCurrentTime();
   386   nsSMILTimeValue newTime(currentTime);
   387   if (!ApplyOffset(newTime)) {
   388     NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
   389     return;
   390   }
   392   nsRefPtr<nsSMILInstanceTime> newInstance =
   393     new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
   394   mOwner->AddInstanceTime(newInstance, mIsBegin);
   395 }
   397 bool
   398 nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent)
   399 {
   400   switch (mParams.mType)
   401   {
   402   case nsSMILTimeValueSpecParams::REPEAT:
   403     return CheckRepeatEventDetail(aEvent);
   405   case nsSMILTimeValueSpecParams::ACCESSKEY:
   406     return CheckAccessKeyEventDetail(aEvent);
   408   default:
   409     // nothing to check
   410     return true;
   411   }
   412 }
   414 bool
   415 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent)
   416 {
   417   nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
   418   if (!timeEvent) {
   419     NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
   420     return false;
   421   }
   423   int32_t detail;
   424   timeEvent->GetDetail(&detail);
   425   return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey;
   426 }
   428 bool
   429 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent)
   430 {
   431   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
   432   if (!keyEvent) {
   433     NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
   434     return false;
   435   }
   437   // Ignore the key event if any modifier keys are pressed UNLESS we're matching
   438   // on the charCode in which case we ignore the state of the shift and alt keys
   439   // since they might be needed to generate the character in question.
   440   bool isCtrl;
   441   bool isMeta;
   442   keyEvent->GetCtrlKey(&isCtrl);
   443   keyEvent->GetMetaKey(&isMeta);
   444   if (isCtrl || isMeta)
   445     return false;
   447   uint32_t code;
   448   keyEvent->GetCharCode(&code);
   449   if (code)
   450     return code == mParams.mRepeatIterationOrAccessKey;
   452   // Only match on the keyCode if it corresponds to some ASCII character that
   453   // does not produce a charCode.
   454   // In this case we can safely bail out if either alt or shift is pressed since
   455   // they won't already be incorporated into the keyCode unlike the charCode.
   456   bool isAlt;
   457   bool isShift;
   458   keyEvent->GetAltKey(&isAlt);
   459   keyEvent->GetShiftKey(&isShift);
   460   if (isAlt || isShift)
   461     return false;
   463   keyEvent->GetKeyCode(&code);
   464   switch (code)
   465   {
   466   case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
   467     return mParams.mRepeatIterationOrAccessKey == 0x08;
   469   case nsIDOMKeyEvent::DOM_VK_RETURN:
   470     return mParams.mRepeatIterationOrAccessKey == 0x0A ||
   471            mParams.mRepeatIterationOrAccessKey == 0x0D;
   473   case nsIDOMKeyEvent::DOM_VK_ESCAPE:
   474     return mParams.mRepeatIterationOrAccessKey == 0x1B;
   476   case nsIDOMKeyEvent::DOM_VK_DELETE:
   477     return mParams.mRepeatIterationOrAccessKey == 0x7F;
   479   default:
   480     return false;
   481   }
   482 }
   484 nsSMILTimeValue
   485 nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
   486     const nsSMILTimeValue& aSrcTime,
   487     const nsSMILTimeContainer* aSrcContainer)
   488 {
   489   // If the source time is either indefinite or unresolved the result is going
   490   // to be the same
   491   if (!aSrcTime.IsDefinite())
   492     return aSrcTime;
   494   // Convert from source time container to our parent time container
   495   const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
   496   if (dstContainer == aSrcContainer)
   497     return aSrcTime;
   499   // If one of the elements is not attached to a time container then we can't do
   500   // any meaningful conversion
   501   if (!aSrcContainer || !dstContainer)
   502     return nsSMILTimeValue(); // unresolved
   504   nsSMILTimeValue docTime =
   505     aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
   507   if (docTime.IsIndefinite())
   508     // This will happen if the source container is paused and we have a future
   509     // time. Just return the indefinite time.
   510     return docTime;
   512   NS_ABORT_IF_FALSE(docTime.IsDefinite(),
   513     "ContainerToParentTime gave us an unresolved or indefinite time");
   515   return dstContainer->ParentToContainerTime(docTime.GetMillis());
   516 }
   518 bool
   519 nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const
   520 {
   521   // indefinite + offset = indefinite. Likewise for unresolved times.
   522   if (!aTime.IsDefinite()) {
   523     return true;
   524   }
   526   double resultAsDouble =
   527     (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
   528   if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() ||
   529       resultAsDouble < std::numeric_limits<nsSMILTime>::min()) {
   530     return false;
   531   }
   532   aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
   533   return true;
   534 }

mercurial