forked from mirrors/gecko-dev
		
	 5a7e9e7eb6
			
		
	
	
		5a7e9e7eb6
		
	
	
	
	
		
			
			Depends on D43778 Differential Revision: https://phabricator.services.mozilla.com/D43779 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			370 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/EventListenerManager.h"
 | |
| #include "mozilla/SMILInstanceTime.h"
 | |
| #include "mozilla/SMILInterval.h"
 | |
| #include "mozilla/SMILParserUtils.h"
 | |
| #include "mozilla/SMILTimeContainer.h"
 | |
| #include "mozilla/SMILTimedElement.h"
 | |
| #include "mozilla/SMILTimeValueSpec.h"
 | |
| #include "mozilla/SMILTimeValue.h"
 | |
| #include "mozilla/dom/Event.h"
 | |
| #include "mozilla/dom/SVGAnimationElement.h"
 | |
| #include "mozilla/dom/TimeEvent.h"
 | |
| #include "nsString.h"
 | |
| #include <limits>
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| // Nested class: EventListener
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SMILTimeValueSpec::EventListener, nsIDOMEventListener)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| SMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent) {
 | |
|   if (mSpec) {
 | |
|     mSpec->HandleEvent(aEvent);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| // Implementation
 | |
| 
 | |
| SMILTimeValueSpec::SMILTimeValueSpec(SMILTimedElement& aOwner, bool aIsBegin)
 | |
|     : mOwner(&aOwner), mIsBegin(aIsBegin), mReferencedElement(this) {}
 | |
| 
 | |
| SMILTimeValueSpec::~SMILTimeValueSpec() {
 | |
|   UnregisterFromReferencedElement(mReferencedElement.get());
 | |
|   if (mEventListener) {
 | |
|     mEventListener->Disconnect();
 | |
|     mEventListener = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult SMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
 | |
|                                     Element& aContextElement) {
 | |
|   SMILTimeValueSpecParams params;
 | |
| 
 | |
|   if (!SMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
 | |
|     return NS_ERROR_FAILURE;
 | |
| 
 | |
|   mParams = params;
 | |
| 
 | |
|   // According to SMIL 3.0:
 | |
|   //   The special value "indefinite" does not yield an instance time in the
 | |
|   //   begin list. It will, however yield a single instance with the value
 | |
|   //   "indefinite" in an end list. This value is not removed by a reset.
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::OFFSET ||
 | |
|       (!mIsBegin && mParams.mType == SMILTimeValueSpecParams::INDEFINITE)) {
 | |
|     mOwner->AddInstanceTime(new SMILInstanceTime(mParams.mOffset), mIsBegin);
 | |
|   }
 | |
| 
 | |
|   // Fill in the event symbol to simplify handling later
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
 | |
|     mParams.mEventSymbol = nsGkAtoms::repeatEvent;
 | |
|   }
 | |
| 
 | |
|   ResolveReferences(aContextElement);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::ResolveReferences(Element& aContextElement) {
 | |
|   if (mParams.mType != SMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we're not bound to the document yet, don't worry, we'll get called again
 | |
|   // when that happens
 | |
|   if (!aContextElement.IsInComposedDoc()) return;
 | |
| 
 | |
|   // Hold ref to the old element so that it isn't destroyed in between resetting
 | |
|   // the referenced element and using the pointer to update the referenced
 | |
|   // element.
 | |
|   RefPtr<Element> oldReferencedElement = mReferencedElement.get();
 | |
| 
 | |
|   if (mParams.mDependentElemID) {
 | |
|     mReferencedElement.ResetWithID(aContextElement, mParams.mDependentElemID);
 | |
|   } else if (mParams.mType == SMILTimeValueSpecParams::EVENT) {
 | |
|     Element* target = mOwner->GetTargetElement();
 | |
|     mReferencedElement.ResetWithElement(target);
 | |
|   } else {
 | |
|     MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
 | |
|   }
 | |
|   UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
 | |
| }
 | |
| 
 | |
| bool SMILTimeValueSpec::IsEventBased() const {
 | |
|   return mParams.mType == SMILTimeValueSpecParams::EVENT ||
 | |
|          mParams.mType == SMILTimeValueSpecParams::REPEAT;
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::HandleNewInterval(
 | |
|     SMILInterval& aInterval, const SMILTimeContainer* aSrcContainer) {
 | |
|   const SMILInstanceTime& baseInstance =
 | |
|       mParams.mSyncBegin ? *aInterval.Begin() : *aInterval.End();
 | |
|   SMILTimeValue newTime =
 | |
|       ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
 | |
| 
 | |
|   // Apply offset
 | |
|   if (!ApplyOffset(newTime)) {
 | |
|     NS_WARNING("New time overflows SMILTime, ignoring");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Create the instance time and register it with the interval
 | |
|   RefPtr<SMILInstanceTime> newInstance = new SMILInstanceTime(
 | |
|       newTime, SMILInstanceTime::SOURCE_SYNCBASE, this, &aInterval);
 | |
|   mOwner->AddInstanceTime(newInstance, mIsBegin);
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) {
 | |
|   if (!IsEventBased() || mParams.mDependentElemID) return;
 | |
| 
 | |
|   mReferencedElement.ResetWithElement(aNewTarget);
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::HandleChangedInstanceTime(
 | |
|     const SMILInstanceTime& aBaseTime, const SMILTimeContainer* aSrcContainer,
 | |
|     SMILInstanceTime& aInstanceTimeToUpdate, bool aObjectChanged) {
 | |
|   // If the instance time is fixed (e.g. because it's being used as the begin
 | |
|   // time of an active or postactive interval) we just ignore the change.
 | |
|   if (aInstanceTimeToUpdate.IsFixedTime()) return;
 | |
| 
 | |
|   SMILTimeValue updatedTime =
 | |
|       ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
 | |
| 
 | |
|   // Apply offset
 | |
|   if (!ApplyOffset(updatedTime)) {
 | |
|     NS_WARNING("Updated time overflows SMILTime, ignoring");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // The timed element that owns the instance time does the updating so it can
 | |
|   // re-sort its array of instance times more efficiently
 | |
|   if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
 | |
|     mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::HandleDeletedInstanceTime(
 | |
|     SMILInstanceTime& aInstanceTime) {
 | |
|   mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
 | |
| }
 | |
| 
 | |
| bool SMILTimeValueSpec::DependsOnBegin() const { return mParams.mSyncBegin; }
 | |
| 
 | |
| void SMILTimeValueSpec::Traverse(
 | |
|     nsCycleCollectionTraversalCallback* aCallback) {
 | |
|   mReferencedElement.Traverse(aCallback);
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::Unlink() {
 | |
|   UnregisterFromReferencedElement(mReferencedElement.get());
 | |
|   mReferencedElement.Unlink();
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| // Implementation helpers
 | |
| 
 | |
| void SMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) {
 | |
|   if (aFrom == aTo) return;
 | |
| 
 | |
|   UnregisterFromReferencedElement(aFrom);
 | |
| 
 | |
|   switch (mParams.mType) {
 | |
|     case SMILTimeValueSpecParams::SYNCBASE: {
 | |
|       SMILTimedElement* to = GetTimedElement(aTo);
 | |
|       if (to) {
 | |
|         to->AddDependent(*this);
 | |
|       }
 | |
|     } break;
 | |
| 
 | |
|     case SMILTimeValueSpecParams::EVENT:
 | |
|     case SMILTimeValueSpecParams::REPEAT:
 | |
|       RegisterEventListener(aTo);
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       // not a referencing-type
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) {
 | |
|   if (!aElement) return;
 | |
| 
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::SYNCBASE) {
 | |
|     SMILTimedElement* timedElement = GetTimedElement(aElement);
 | |
|     if (timedElement) {
 | |
|       timedElement->RemoveDependent(*this);
 | |
|     }
 | |
|     mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
 | |
|   } else if (IsEventBased()) {
 | |
|     UnregisterEventListener(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| SMILTimedElement* SMILTimeValueSpec::GetTimedElement(Element* aElement) {
 | |
|   return aElement && aElement->IsNodeOfType(nsINode::eANIMATION)
 | |
|              ? &static_cast<SVGAnimationElement*>(aElement)->TimedElement()
 | |
|              : nullptr;
 | |
| }
 | |
| 
 | |
| // Indicates whether we're allowed to register an event-listener
 | |
| // when scripting is disabled.
 | |
| bool SMILTimeValueSpec::IsWhitelistedEvent() {
 | |
|   // The category of (SMIL-specific) "repeat(n)" events are allowed.
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // A specific list of other SMIL-related events are allowed, too.
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::EVENT &&
 | |
|       (mParams.mEventSymbol == nsGkAtoms::repeat ||
 | |
|        mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
 | |
|        mParams.mEventSymbol == nsGkAtoms::beginEvent ||
 | |
|        mParams.mEventSymbol == nsGkAtoms::endEvent)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::RegisterEventListener(Element* aTarget) {
 | |
|   MOZ_ASSERT(IsEventBased(),
 | |
|              "Attempting to register event-listener for unexpected "
 | |
|              "SMILTimeValueSpec type");
 | |
|   MOZ_ASSERT(mParams.mEventSymbol,
 | |
|              "Attempting to register event-listener but there is no event "
 | |
|              "name");
 | |
| 
 | |
|   if (!aTarget) return;
 | |
| 
 | |
|   // When script is disabled, only allow registration for whitelisted events.
 | |
|   if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
 | |
|       !IsWhitelistedEvent()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mEventListener) {
 | |
|     mEventListener = new EventListener(this);
 | |
|   }
 | |
| 
 | |
|   EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
 | |
|   if (!elm) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   elm->AddEventListenerByType(mEventListener,
 | |
|                               nsDependentAtomString(mParams.mEventSymbol),
 | |
|                               AllEventsAtSystemGroupBubble());
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::UnregisterEventListener(Element* aTarget) {
 | |
|   if (!aTarget || !mEventListener) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
 | |
|   if (!elm) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   elm->RemoveEventListenerByType(mEventListener,
 | |
|                                  nsDependentAtomString(mParams.mEventSymbol),
 | |
|                                  AllEventsAtSystemGroupBubble());
 | |
| }
 | |
| 
 | |
| void SMILTimeValueSpec::HandleEvent(Event* aEvent) {
 | |
|   MOZ_ASSERT(mEventListener, "Got event without an event listener");
 | |
|   MOZ_ASSERT(IsEventBased(), "Got event for non-event SMILTimeValueSpec");
 | |
|   MOZ_ASSERT(aEvent, "No event supplied");
 | |
| 
 | |
|   // XXX In the long run we should get the time from the event itself which will
 | |
|   // store the time in global document time which we'll need to convert to our
 | |
|   // time container
 | |
|   SMILTimeContainer* container = mOwner->GetTimeContainer();
 | |
|   if (!container) return;
 | |
| 
 | |
|   if (mParams.mType == SMILTimeValueSpecParams::REPEAT &&
 | |
|       !CheckRepeatEventDetail(aEvent)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SMILTime currentTime = container->GetCurrentTimeAsSMILTime();
 | |
|   SMILTimeValue newTime(currentTime);
 | |
|   if (!ApplyOffset(newTime)) {
 | |
|     NS_WARNING("New time generated from event overflows SMILTime, ignoring");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<SMILInstanceTime> newInstance =
 | |
|       new SMILInstanceTime(newTime, SMILInstanceTime::SOURCE_EVENT);
 | |
|   mOwner->AddInstanceTime(newInstance, mIsBegin);
 | |
| }
 | |
| 
 | |
| bool SMILTimeValueSpec::CheckRepeatEventDetail(Event* aEvent) {
 | |
|   TimeEvent* timeEvent = aEvent->AsTimeEvent();
 | |
|   if (!timeEvent) {
 | |
|     NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   int32_t detail = timeEvent->Detail();
 | |
|   return detail > 0 && (uint32_t)detail == mParams.mRepeatIteration;
 | |
| }
 | |
| 
 | |
| SMILTimeValue SMILTimeValueSpec::ConvertBetweenTimeContainers(
 | |
|     const SMILTimeValue& aSrcTime, const SMILTimeContainer* aSrcContainer) {
 | |
|   // If the source time is either indefinite or unresolved the result is going
 | |
|   // to be the same
 | |
|   if (!aSrcTime.IsDefinite()) return aSrcTime;
 | |
| 
 | |
|   // Convert from source time container to our parent time container
 | |
|   const SMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
 | |
|   if (dstContainer == aSrcContainer) return aSrcTime;
 | |
| 
 | |
|   // If one of the elements is not attached to a time container then we can't do
 | |
|   // any meaningful conversion
 | |
|   if (!aSrcContainer || !dstContainer) return SMILTimeValue();  // unresolved
 | |
| 
 | |
|   SMILTimeValue docTime =
 | |
|       aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
 | |
| 
 | |
|   if (docTime.IsIndefinite())
 | |
|     // This will happen if the source container is paused and we have a future
 | |
|     // time. Just return the indefinite time.
 | |
|     return docTime;
 | |
| 
 | |
|   MOZ_ASSERT(docTime.IsDefinite(),
 | |
|              "ContainerToParentTime gave us an unresolved or indefinite time");
 | |
| 
 | |
|   return dstContainer->ParentToContainerTime(docTime.GetMillis());
 | |
| }
 | |
| 
 | |
| bool SMILTimeValueSpec::ApplyOffset(SMILTimeValue& aTime) const {
 | |
|   // indefinite + offset = indefinite. Likewise for unresolved times.
 | |
|   if (!aTime.IsDefinite()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   double resultAsDouble =
 | |
|       (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
 | |
|   if (resultAsDouble > double(std::numeric_limits<SMILTime>::max()) ||
 | |
|       resultAsDouble < double(std::numeric_limits<SMILTime>::min())) {
 | |
|     return false;
 | |
|   }
 | |
|   aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |