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"
20 using namespace mozilla
;
21 using namespace mozilla::dom
;
23 //----------------------------------------------------------------------
24 // Nested class: EventListener
26 NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener
, nsIDOMEventListener
)
29 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent
* aEvent
)
32 mSpec
->HandleEvent(aEvent
);
37 //----------------------------------------------------------------------
40 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement
& aOwner
,
44 mReferencedElement(this)
48 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
50 UnregisterFromReferencedElement(mReferencedElement
.get());
52 mEventListener
->Disconnect();
53 mEventListener
= nullptr;
58 nsSMILTimeValueSpec::SetSpec(const nsAString
& aStringSpec
,
59 Element
* aContextNode
)
61 nsSMILTimeValueSpecParams params
;
63 if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec
, params
))
64 return NS_ERROR_FAILURE
;
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
);
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
;
84 ResolveReferences(aContextNode
);
90 nsSMILTimeValueSpec::ResolveReferences(nsIContent
* aContextNode
)
92 if (mParams
.mType
!= nsSMILTimeValueSpecParams::SYNCBASE
&& !IsEventBased())
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
100 if (!aContextNode
->IsInDoc())
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
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());
119 NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID");
121 UpdateReferencedElement(oldReferencedElement
, mReferencedElement
.get());
125 nsSMILTimeValueSpec::IsEventBased() const
127 return mParams
.mType
== nsSMILTimeValueSpecParams::EVENT
||
128 mParams
.mType
== nsSMILTimeValueSpecParams::REPEAT
||
129 mParams
.mType
== nsSMILTimeValueSpecParams::ACCESSKEY
;
133 nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval
& aInterval
,
134 const nsSMILTimeContainer
* aSrcContainer
)
136 const nsSMILInstanceTime
& baseInstance
= mParams
.mSyncBegin
137 ? *aInterval
.Begin() : *aInterval
.End();
138 nsSMILTimeValue newTime
=
139 ConvertBetweenTimeContainers(baseInstance
.Time(), aSrcContainer
);
142 if (!ApplyOffset(newTime
)) {
143 NS_WARNING("New time overflows nsSMILTime, ignoring");
147 // Create the instance time and register it with the interval
148 nsRefPtr
<nsSMILInstanceTime
> newInstance
=
149 new nsSMILInstanceTime(newTime
, nsSMILInstanceTime::SOURCE_SYNCBASE
, this,
151 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
155 nsSMILTimeValueSpec::HandleTargetElementChange(Element
* aNewTarget
)
157 if (!IsEventBased() || mParams
.mDependentElemID
)
160 mReferencedElement
.ResetWithElement(aNewTarget
);
164 nsSMILTimeValueSpec::HandleChangedInstanceTime(
165 const nsSMILInstanceTime
& aBaseTime
,
166 const nsSMILTimeContainer
* aSrcContainer
,
167 nsSMILInstanceTime
& aInstanceTimeToUpdate
,
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())
175 nsSMILTimeValue updatedTime
=
176 ConvertBetweenTimeContainers(aBaseTime
.Time(), aSrcContainer
);
179 if (!ApplyOffset(updatedTime
)) {
180 NS_WARNING("Updated time overflows nsSMILTime, ignoring");
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
);
192 nsSMILTimeValueSpec::HandleDeletedInstanceTime(
193 nsSMILInstanceTime
&aInstanceTime
)
195 mOwner
->RemoveInstanceTime(&aInstanceTime
, mIsBegin
);
199 nsSMILTimeValueSpec::DependsOnBegin() const
201 return mParams
.mSyncBegin
;
205 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback
* aCallback
)
207 mReferencedElement
.Traverse(aCallback
);
211 nsSMILTimeValueSpec::Unlink()
213 UnregisterFromReferencedElement(mReferencedElement
.get());
214 mReferencedElement
.Unlink();
217 //----------------------------------------------------------------------
218 // Implementation helpers
221 nsSMILTimeValueSpec::UpdateReferencedElement(Element
* aFrom
, Element
* aTo
)
226 UnregisterFromReferencedElement(aFrom
);
228 switch (mParams
.mType
)
230 case nsSMILTimeValueSpecParams::SYNCBASE
:
232 nsSMILTimedElement
* to
= GetTimedElement(aTo
);
234 to
->AddDependent(*this);
239 case nsSMILTimeValueSpecParams::EVENT
:
240 case nsSMILTimeValueSpecParams::REPEAT
:
241 case nsSMILTimeValueSpecParams::ACCESSKEY
:
242 RegisterEventListener(aTo
);
246 // not a referencing-type
252 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element
* aElement
)
257 if (mParams
.mType
== nsSMILTimeValueSpecParams::SYNCBASE
) {
258 nsSMILTimedElement
* timedElement
= GetTimedElement(aElement
);
260 timedElement
->RemoveDependent(*this);
262 mOwner
->RemoveInstanceTimesForCreator(this, mIsBegin
);
263 } else if (IsEventBased()) {
264 UnregisterEventListener(aElement
);
269 nsSMILTimeValueSpec::GetTimedElement(Element
* aElement
)
271 return aElement
&& aElement
->IsNodeOfType(nsINode::eANIMATION
) ?
272 &static_cast<SVGAnimationElement
*>(aElement
)->TimedElement() : nullptr;
275 // Indicates whether we're allowed to register an event-listener
276 // when scripting is disabled.
278 nsSMILTimeValueSpec::IsWhitelistedEvent()
280 // The category of (SMIL-specific) "repeat(n)" events are allowed.
281 if (mParams
.mType
== nsSMILTimeValueSpecParams::REPEAT
) {
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
)) {
298 nsSMILTimeValueSpec::RegisterEventListener(Element
* aTarget
)
300 NS_ABORT_IF_FALSE(IsEventBased(),
301 "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
303 NS_ABORT_IF_FALSE(mParams
.mEventSymbol
,
304 "Attempting to register event-listener but there is no event name");
309 // When script is disabled, only allow registration for whitelisted events.
310 if (!aTarget
->GetOwnerDocument()->IsScriptEnabled() &&
311 !IsWhitelistedEvent()) {
315 if (!mEventListener
) {
316 mEventListener
= new EventListener(this);
319 EventListenerManager
* elm
= GetEventListenerManager(aTarget
);
323 elm
->AddEventListenerByType(mEventListener
,
324 nsDependentAtomString(mParams
.mEventSymbol
),
325 AllEventsAtSystemGroupBubble());
329 nsSMILTimeValueSpec::UnregisterEventListener(Element
* aTarget
)
331 if (!aTarget
|| !mEventListener
)
334 EventListenerManager
* elm
= GetEventListenerManager(aTarget
);
338 elm
->RemoveEventListenerByType(mEventListener
,
339 nsDependentAtomString(mParams
.mEventSymbol
),
340 AllEventsAtSystemGroupBubble());
343 EventListenerManager
*
344 nsSMILTimeValueSpec::GetEventListenerManager(Element
* aTarget
)
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();
354 nsPIDOMWindow
* win
= doc
->GetWindow();
357 target
= do_QueryInterface(win
);
364 return target
->GetOrCreateListenerManager();
368 nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent
* aEvent
)
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
378 nsSMILTimeContainer
* container
= mOwner
->GetTimeContainer();
382 if (!CheckEventDetail(aEvent
))
385 nsSMILTime currentTime
= container
->GetCurrentTime();
386 nsSMILTimeValue
newTime(currentTime
);
387 if (!ApplyOffset(newTime
)) {
388 NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
392 nsRefPtr
<nsSMILInstanceTime
> newInstance
=
393 new nsSMILInstanceTime(newTime
, nsSMILInstanceTime::SOURCE_EVENT
);
394 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
398 nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent
*aEvent
)
400 switch (mParams
.mType
)
402 case nsSMILTimeValueSpecParams::REPEAT
:
403 return CheckRepeatEventDetail(aEvent
);
405 case nsSMILTimeValueSpecParams::ACCESSKEY
:
406 return CheckAccessKeyEventDetail(aEvent
);
415 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent
*aEvent
)
417 nsCOMPtr
<nsIDOMTimeEvent
> timeEvent
= do_QueryInterface(aEvent
);
419 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
424 timeEvent
->GetDetail(&detail
);
425 return detail
> 0 && (uint32_t)detail
== mParams
.mRepeatIterationOrAccessKey
;
429 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent
*aEvent
)
431 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
433 NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
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.
442 keyEvent
->GetCtrlKey(&isCtrl
);
443 keyEvent
->GetMetaKey(&isMeta
);
444 if (isCtrl
|| isMeta
)
448 keyEvent
->GetCharCode(&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.
458 keyEvent
->GetAltKey(&isAlt
);
459 keyEvent
->GetShiftKey(&isShift
);
460 if (isAlt
|| isShift
)
463 keyEvent
->GetKeyCode(&code
);
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;
485 nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
486 const nsSMILTimeValue
& aSrcTime
,
487 const nsSMILTimeContainer
* aSrcContainer
)
489 // If the source time is either indefinite or unresolved the result is going
491 if (!aSrcTime
.IsDefinite())
494 // Convert from source time container to our parent time container
495 const nsSMILTimeContainer
* dstContainer
= mOwner
->GetTimeContainer();
496 if (dstContainer
== aSrcContainer
)
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.
512 NS_ABORT_IF_FALSE(docTime
.IsDefinite(),
513 "ContainerToParentTime gave us an unresolved or indefinite time");
515 return dstContainer
->ParentToContainerTime(docTime
.GetMillis());
519 nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue
& aTime
) const
521 // indefinite + offset = indefinite. Likewise for unresolved times.
522 if (!aTime
.IsDefinite()) {
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()) {
532 aTime
.SetMillis(aTime
.GetMillis() + mParams
.mOffset
.GetMillis());