michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/dom/SVGAnimationElement.h" michael@0: #include "nsSMILTimeValueSpec.h" michael@0: #include "nsSMILInterval.h" michael@0: #include "nsSMILTimeContainer.h" michael@0: #include "nsSMILTimeValue.h" michael@0: #include "nsSMILTimedElement.h" michael@0: #include "nsSMILInstanceTime.h" michael@0: #include "nsSMILParserUtils.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMTimeEvent.h" michael@0: #include "nsString.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Nested class: EventListener michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener) michael@0: michael@0: NS_IMETHODIMP michael@0: nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: if (mSpec) { michael@0: mSpec->HandleEvent(aEvent); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, michael@0: bool aIsBegin) michael@0: : mOwner(&aOwner), michael@0: mIsBegin(aIsBegin), michael@0: mReferencedElement(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: { michael@0: } michael@0: michael@0: nsSMILTimeValueSpec::~nsSMILTimeValueSpec() michael@0: { michael@0: UnregisterFromReferencedElement(mReferencedElement.get()); michael@0: if (mEventListener) { michael@0: mEventListener->Disconnect(); michael@0: mEventListener = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec, michael@0: Element* aContextNode) michael@0: { michael@0: nsSMILTimeValueSpecParams params; michael@0: michael@0: if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mParams = params; michael@0: michael@0: // According to SMIL 3.0: michael@0: // The special value "indefinite" does not yield an instance time in the michael@0: // begin list. It will, however yield a single instance with the value michael@0: // "indefinite" in an end list. This value is not removed by a reset. michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET || michael@0: (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) { michael@0: mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin); michael@0: } michael@0: michael@0: // Fill in the event symbol to simplify handling later michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { michael@0: mParams.mEventSymbol = nsGkAtoms::repeatEvent; michael@0: } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { michael@0: mParams.mEventSymbol = nsGkAtoms::keypress; michael@0: } michael@0: michael@0: ResolveReferences(aContextNode); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode) michael@0: { michael@0: if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) michael@0: return; michael@0: michael@0: NS_ABORT_IF_FALSE(aContextNode, michael@0: "null context node for resolving timing references against"); michael@0: michael@0: // If we're not bound to the document yet, don't worry, we'll get called again michael@0: // when that happens michael@0: if (!aContextNode->IsInDoc()) michael@0: return; michael@0: michael@0: // Hold ref to the old element so that it isn't destroyed in between resetting michael@0: // the referenced element and using the pointer to update the referenced michael@0: // element. michael@0: nsRefPtr oldReferencedElement = mReferencedElement.get(); michael@0: michael@0: if (mParams.mDependentElemID) { michael@0: mReferencedElement.ResetWithID(aContextNode, michael@0: nsDependentAtomString(mParams.mDependentElemID)); michael@0: } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) { michael@0: Element* target = mOwner->GetTargetElement(); michael@0: mReferencedElement.ResetWithElement(target); michael@0: } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { michael@0: nsIDocument* doc = aContextNode->GetCurrentDoc(); michael@0: NS_ABORT_IF_FALSE(doc, "We are in the document but current doc is null"); michael@0: mReferencedElement.ResetWithElement(doc->GetRootElement()); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID"); michael@0: } michael@0: UpdateReferencedElement(oldReferencedElement, mReferencedElement.get()); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::IsEventBased() const michael@0: { michael@0: return mParams.mType == nsSMILTimeValueSpecParams::EVENT || michael@0: mParams.mType == nsSMILTimeValueSpecParams::REPEAT || michael@0: mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval, michael@0: const nsSMILTimeContainer* aSrcContainer) michael@0: { michael@0: const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin michael@0: ? *aInterval.Begin() : *aInterval.End(); michael@0: nsSMILTimeValue newTime = michael@0: ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer); michael@0: michael@0: // Apply offset michael@0: if (!ApplyOffset(newTime)) { michael@0: NS_WARNING("New time overflows nsSMILTime, ignoring"); michael@0: return; michael@0: } michael@0: michael@0: // Create the instance time and register it with the interval michael@0: nsRefPtr newInstance = michael@0: new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this, michael@0: &aInterval); michael@0: mOwner->AddInstanceTime(newInstance, mIsBegin); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) michael@0: { michael@0: if (!IsEventBased() || mParams.mDependentElemID) michael@0: return; michael@0: michael@0: mReferencedElement.ResetWithElement(aNewTarget); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::HandleChangedInstanceTime( michael@0: const nsSMILInstanceTime& aBaseTime, michael@0: const nsSMILTimeContainer* aSrcContainer, michael@0: nsSMILInstanceTime& aInstanceTimeToUpdate, michael@0: bool aObjectChanged) michael@0: { michael@0: // If the instance time is fixed (e.g. because it's being used as the begin michael@0: // time of an active or postactive interval) we just ignore the change. michael@0: if (aInstanceTimeToUpdate.IsFixedTime()) michael@0: return; michael@0: michael@0: nsSMILTimeValue updatedTime = michael@0: ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer); michael@0: michael@0: // Apply offset michael@0: if (!ApplyOffset(updatedTime)) { michael@0: NS_WARNING("Updated time overflows nsSMILTime, ignoring"); michael@0: return; michael@0: } michael@0: michael@0: // The timed element that owns the instance time does the updating so it can michael@0: // re-sort its array of instance times more efficiently michael@0: if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) { michael@0: mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::HandleDeletedInstanceTime( michael@0: nsSMILInstanceTime &aInstanceTime) michael@0: { michael@0: mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::DependsOnBegin() const michael@0: { michael@0: return mParams.mSyncBegin; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback) michael@0: { michael@0: mReferencedElement.Traverse(aCallback); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::Unlink() michael@0: { michael@0: UnregisterFromReferencedElement(mReferencedElement.get()); michael@0: mReferencedElement.Unlink(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation helpers michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) michael@0: { michael@0: if (aFrom == aTo) michael@0: return; michael@0: michael@0: UnregisterFromReferencedElement(aFrom); michael@0: michael@0: switch (mParams.mType) michael@0: { michael@0: case nsSMILTimeValueSpecParams::SYNCBASE: michael@0: { michael@0: nsSMILTimedElement* to = GetTimedElement(aTo); michael@0: if (to) { michael@0: to->AddDependent(*this); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case nsSMILTimeValueSpecParams::EVENT: michael@0: case nsSMILTimeValueSpecParams::REPEAT: michael@0: case nsSMILTimeValueSpecParams::ACCESSKEY: michael@0: RegisterEventListener(aTo); michael@0: break; michael@0: michael@0: default: michael@0: // not a referencing-type michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) michael@0: { michael@0: if (!aElement) michael@0: return; michael@0: michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) { michael@0: nsSMILTimedElement* timedElement = GetTimedElement(aElement); michael@0: if (timedElement) { michael@0: timedElement->RemoveDependent(*this); michael@0: } michael@0: mOwner->RemoveInstanceTimesForCreator(this, mIsBegin); michael@0: } else if (IsEventBased()) { michael@0: UnregisterEventListener(aElement); michael@0: } michael@0: } michael@0: michael@0: nsSMILTimedElement* michael@0: nsSMILTimeValueSpec::GetTimedElement(Element* aElement) michael@0: { michael@0: return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ? michael@0: &static_cast(aElement)->TimedElement() : nullptr; michael@0: } michael@0: michael@0: // Indicates whether we're allowed to register an event-listener michael@0: // when scripting is disabled. michael@0: bool michael@0: nsSMILTimeValueSpec::IsWhitelistedEvent() michael@0: { michael@0: // The category of (SMIL-specific) "repeat(n)" events are allowed. michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { michael@0: return true; michael@0: } michael@0: michael@0: // A specific list of other SMIL-related events are allowed, too. michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::EVENT && michael@0: (mParams.mEventSymbol == nsGkAtoms::repeat || michael@0: mParams.mEventSymbol == nsGkAtoms::repeatEvent || michael@0: mParams.mEventSymbol == nsGkAtoms::beginEvent || michael@0: mParams.mEventSymbol == nsGkAtoms::endEvent)) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget) michael@0: { michael@0: NS_ABORT_IF_FALSE(IsEventBased(), michael@0: "Attempting to register event-listener for unexpected nsSMILTimeValueSpec" michael@0: " type"); michael@0: NS_ABORT_IF_FALSE(mParams.mEventSymbol, michael@0: "Attempting to register event-listener but there is no event name"); michael@0: michael@0: if (!aTarget) michael@0: return; michael@0: michael@0: // When script is disabled, only allow registration for whitelisted events. michael@0: if (!aTarget->GetOwnerDocument()->IsScriptEnabled() && michael@0: !IsWhitelistedEvent()) { michael@0: return; michael@0: } michael@0: michael@0: if (!mEventListener) { michael@0: mEventListener = new EventListener(this); michael@0: } michael@0: michael@0: EventListenerManager* elm = GetEventListenerManager(aTarget); michael@0: if (!elm) michael@0: return; michael@0: michael@0: elm->AddEventListenerByType(mEventListener, michael@0: nsDependentAtomString(mParams.mEventSymbol), michael@0: AllEventsAtSystemGroupBubble()); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget) michael@0: { michael@0: if (!aTarget || !mEventListener) michael@0: return; michael@0: michael@0: EventListenerManager* elm = GetEventListenerManager(aTarget); michael@0: if (!elm) michael@0: return; michael@0: michael@0: elm->RemoveEventListenerByType(mEventListener, michael@0: nsDependentAtomString(mParams.mEventSymbol), michael@0: AllEventsAtSystemGroupBubble()); michael@0: } michael@0: michael@0: EventListenerManager* michael@0: nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget) michael@0: { michael@0: NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager"); michael@0: michael@0: nsCOMPtr target; michael@0: michael@0: if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { michael@0: nsIDocument* doc = aTarget->GetCurrentDoc(); michael@0: if (!doc) michael@0: return nullptr; michael@0: nsPIDOMWindow* win = doc->GetWindow(); michael@0: if (!win) michael@0: return nullptr; michael@0: target = do_QueryInterface(win); michael@0: } else { michael@0: target = aTarget; michael@0: } michael@0: if (!target) michael@0: return nullptr; michael@0: michael@0: return target->GetOrCreateListenerManager(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener"); michael@0: NS_ABORT_IF_FALSE(IsEventBased(), michael@0: "Got event for non-event nsSMILTimeValueSpec"); michael@0: NS_ABORT_IF_FALSE(aEvent, "No event supplied"); michael@0: michael@0: // XXX In the long run we should get the time from the event itself which will michael@0: // store the time in global document time which we'll need to convert to our michael@0: // time container michael@0: nsSMILTimeContainer* container = mOwner->GetTimeContainer(); michael@0: if (!container) michael@0: return; michael@0: michael@0: if (!CheckEventDetail(aEvent)) michael@0: return; michael@0: michael@0: nsSMILTime currentTime = container->GetCurrentTime(); michael@0: nsSMILTimeValue newTime(currentTime); michael@0: if (!ApplyOffset(newTime)) { michael@0: NS_WARNING("New time generated from event overflows nsSMILTime, ignoring"); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr newInstance = michael@0: new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT); michael@0: mOwner->AddInstanceTime(newInstance, mIsBegin); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent) michael@0: { michael@0: switch (mParams.mType) michael@0: { michael@0: case nsSMILTimeValueSpecParams::REPEAT: michael@0: return CheckRepeatEventDetail(aEvent); michael@0: michael@0: case nsSMILTimeValueSpecParams::ACCESSKEY: michael@0: return CheckAccessKeyEventDetail(aEvent); michael@0: michael@0: default: michael@0: // nothing to check michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent) michael@0: { michael@0: nsCOMPtr timeEvent = do_QueryInterface(aEvent); michael@0: if (!timeEvent) { michael@0: NS_WARNING("Received a repeat event that was not a DOMTimeEvent"); michael@0: return false; michael@0: } michael@0: michael@0: int32_t detail; michael@0: timeEvent->GetDetail(&detail); michael@0: return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent) michael@0: { michael@0: nsCOMPtr keyEvent = do_QueryInterface(aEvent); michael@0: if (!keyEvent) { michael@0: NS_WARNING("Received an accesskey event that was not a DOMKeyEvent"); michael@0: return false; michael@0: } michael@0: michael@0: // Ignore the key event if any modifier keys are pressed UNLESS we're matching michael@0: // on the charCode in which case we ignore the state of the shift and alt keys michael@0: // since they might be needed to generate the character in question. michael@0: bool isCtrl; michael@0: bool isMeta; michael@0: keyEvent->GetCtrlKey(&isCtrl); michael@0: keyEvent->GetMetaKey(&isMeta); michael@0: if (isCtrl || isMeta) michael@0: return false; michael@0: michael@0: uint32_t code; michael@0: keyEvent->GetCharCode(&code); michael@0: if (code) michael@0: return code == mParams.mRepeatIterationOrAccessKey; michael@0: michael@0: // Only match on the keyCode if it corresponds to some ASCII character that michael@0: // does not produce a charCode. michael@0: // In this case we can safely bail out if either alt or shift is pressed since michael@0: // they won't already be incorporated into the keyCode unlike the charCode. michael@0: bool isAlt; michael@0: bool isShift; michael@0: keyEvent->GetAltKey(&isAlt); michael@0: keyEvent->GetShiftKey(&isShift); michael@0: if (isAlt || isShift) michael@0: return false; michael@0: michael@0: keyEvent->GetKeyCode(&code); michael@0: switch (code) michael@0: { michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: return mParams.mRepeatIterationOrAccessKey == 0x08; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_RETURN: michael@0: return mParams.mRepeatIterationOrAccessKey == 0x0A || michael@0: mParams.mRepeatIterationOrAccessKey == 0x0D; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_ESCAPE: michael@0: return mParams.mRepeatIterationOrAccessKey == 0x1B; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE: michael@0: return mParams.mRepeatIterationOrAccessKey == 0x7F; michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimeValueSpec::ConvertBetweenTimeContainers( michael@0: const nsSMILTimeValue& aSrcTime, michael@0: const nsSMILTimeContainer* aSrcContainer) michael@0: { michael@0: // If the source time is either indefinite or unresolved the result is going michael@0: // to be the same michael@0: if (!aSrcTime.IsDefinite()) michael@0: return aSrcTime; michael@0: michael@0: // Convert from source time container to our parent time container michael@0: const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer(); michael@0: if (dstContainer == aSrcContainer) michael@0: return aSrcTime; michael@0: michael@0: // If one of the elements is not attached to a time container then we can't do michael@0: // any meaningful conversion michael@0: if (!aSrcContainer || !dstContainer) michael@0: return nsSMILTimeValue(); // unresolved michael@0: michael@0: nsSMILTimeValue docTime = michael@0: aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis()); michael@0: michael@0: if (docTime.IsIndefinite()) michael@0: // This will happen if the source container is paused and we have a future michael@0: // time. Just return the indefinite time. michael@0: return docTime; michael@0: michael@0: NS_ABORT_IF_FALSE(docTime.IsDefinite(), michael@0: "ContainerToParentTime gave us an unresolved or indefinite time"); michael@0: michael@0: return dstContainer->ParentToContainerTime(docTime.GetMillis()); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const michael@0: { michael@0: // indefinite + offset = indefinite. Likewise for unresolved times. michael@0: if (!aTime.IsDefinite()) { michael@0: return true; michael@0: } michael@0: michael@0: double resultAsDouble = michael@0: (double)aTime.GetMillis() + mParams.mOffset.GetMillis(); michael@0: if (resultAsDouble > std::numeric_limits::max() || michael@0: resultAsDouble < std::numeric_limits::min()) { michael@0: return false; michael@0: } michael@0: aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis()); michael@0: return true; michael@0: }