1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is the Mozilla SMIL module.
17 * The Initial Developer of the Original Code is Brian Birtles.
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
22 * Brian Birtles <birtles@gmail.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #include "nsSMILTimeValueSpec.h"
39 #include "nsSMILInterval.h"
40 #include "nsSMILTimeContainer.h"
41 #include "nsSMILTimeValue.h"
42 #include "nsSMILTimedElement.h"
43 #include "nsSMILInstanceTime.h"
44 #include "nsSMILParserUtils.h"
45 #include "nsISMILAnimationElement.h"
46 #include "nsContentUtils.h"
47 #include "nsIEventListenerManager.h"
48 #include "nsIDOMEventGroup.h"
49 #include "nsGUIEvent.h"
50 #include "nsIDOMTimeEvent.h"
53 using namespace mozilla::dom
;
55 //----------------------------------------------------------------------
56 // Nested class: EventListener
58 NS_IMPL_ISUPPORTS1(nsSMILTimeValueSpec::EventListener
, nsIDOMEventListener
)
61 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent
* aEvent
)
64 mSpec
->HandleEvent(aEvent
);
69 //----------------------------------------------------------------------
73 // Disable "warning C4355: 'this' : used in base member initializer list".
74 // We can ignore that warning because we know that mReferencedElement's
75 // constructor doesn't dereference the pointer passed to it.
77 #pragma warning(disable:4355)
79 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement
& aOwner
,
83 mReferencedElement(this)
90 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
92 UnregisterFromReferencedElement(mReferencedElement
.get());
94 mEventListener
->Disconnect();
95 mEventListener
= nsnull
;
100 nsSMILTimeValueSpec::SetSpec(const nsAString
& aStringSpec
,
101 Element
* aContextNode
)
103 nsSMILTimeValueSpecParams params
;
105 nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec
, params
);
112 // According to SMIL 3.0:
113 // The special value "indefinite" does not yield an instance time in the
114 // begin list. It will, however yield a single instance with the value
115 // "indefinite" in an end list. This value is not removed by a reset.
116 if (mParams
.mType
== nsSMILTimeValueSpecParams::OFFSET
||
117 (!mIsBegin
&& mParams
.mType
== nsSMILTimeValueSpecParams::INDEFINITE
)) {
118 mOwner
->AddInstanceTime(new nsSMILInstanceTime(mParams
.mOffset
), mIsBegin
);
121 // Fill in the event symbol to simplify handling later
122 if (mParams
.mType
== nsSMILTimeValueSpecParams::REPEAT
) {
123 mParams
.mEventSymbol
= nsGkAtoms::repeatEvent
;
124 } else if (mParams
.mType
== nsSMILTimeValueSpecParams::ACCESSKEY
) {
125 mParams
.mEventSymbol
= nsGkAtoms::keypress
;
128 ResolveReferences(aContextNode
);
134 nsSMILTimeValueSpec::ResolveReferences(nsIContent
* aContextNode
)
136 if (mParams
.mType
!= nsSMILTimeValueSpecParams::SYNCBASE
&& !IsEventBased())
139 NS_ABORT_IF_FALSE(aContextNode
,
140 "null context node for resolving timing references against");
142 // If we're not bound to the document yet, don't worry, we'll get called again
144 if (!aContextNode
->IsInDoc())
147 // Hold ref to the old element so that it isn't destroyed in between resetting
148 // the referenced element and using the pointer to update the referenced
150 nsRefPtr
<Element
> oldReferencedElement
= mReferencedElement
.get();
152 if (mParams
.mDependentElemID
) {
153 mReferencedElement
.ResetWithID(aContextNode
,
154 nsDependentAtomString(mParams
.mDependentElemID
));
155 } else if (mParams
.mType
== nsSMILTimeValueSpecParams::EVENT
) {
156 Element
* target
= mOwner
->GetTargetElement();
157 mReferencedElement
.ResetWithElement(target
);
158 } else if (mParams
.mType
== nsSMILTimeValueSpecParams::ACCESSKEY
) {
159 nsIDocument
* doc
= aContextNode
->GetCurrentDoc();
160 NS_ABORT_IF_FALSE(doc
, "We are in the document but current doc is null");
161 mReferencedElement
.ResetWithElement(doc
->GetRootElement());
163 NS_ABORT_IF_FALSE(PR_FALSE
, "Syncbase or repeat spec without ID");
165 UpdateReferencedElement(oldReferencedElement
, mReferencedElement
.get());
169 nsSMILTimeValueSpec::IsEventBased() const
171 return mParams
.mType
== nsSMILTimeValueSpecParams::EVENT
||
172 mParams
.mType
== nsSMILTimeValueSpecParams::REPEAT
||
173 mParams
.mType
== nsSMILTimeValueSpecParams::ACCESSKEY
;
177 nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval
& aInterval
,
178 const nsSMILTimeContainer
* aSrcContainer
)
180 const nsSMILInstanceTime
& baseInstance
= mParams
.mSyncBegin
181 ? *aInterval
.Begin() : *aInterval
.End();
182 nsSMILTimeValue newTime
=
183 ConvertBetweenTimeContainers(baseInstance
.Time(), aSrcContainer
);
186 if (newTime
.IsResolved()) {
187 newTime
.SetMillis(newTime
.GetMillis() + mParams
.mOffset
.GetMillis());
190 // Create the instance time and register it with the interval
191 nsRefPtr
<nsSMILInstanceTime
> newInstance
=
192 new nsSMILInstanceTime(newTime
, nsSMILInstanceTime::SOURCE_SYNCBASE
, this,
194 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
198 nsSMILTimeValueSpec::HandleTargetElementChange(Element
* aNewTarget
)
200 if (!IsEventBased() || mParams
.mDependentElemID
)
203 mReferencedElement
.ResetWithElement(aNewTarget
);
207 nsSMILTimeValueSpec::HandleChangedInstanceTime(
208 const nsSMILInstanceTime
& aBaseTime
,
209 const nsSMILTimeContainer
* aSrcContainer
,
210 nsSMILInstanceTime
& aInstanceTimeToUpdate
,
211 PRBool aObjectChanged
)
213 // If the instance time is fixed (e.g. because it's being used as the begin
214 // time of an active or postactive interval) we just ignore the change.
215 if (aInstanceTimeToUpdate
.IsFixedTime())
218 nsSMILTimeValue updatedTime
=
219 ConvertBetweenTimeContainers(aBaseTime
.Time(), aSrcContainer
);
222 if (updatedTime
.IsResolved()) {
223 updatedTime
.SetMillis(updatedTime
.GetMillis() +
224 mParams
.mOffset
.GetMillis());
227 // The timed element that owns the instance time does the updating so it can
228 // re-sort its array of instance times more efficiently
229 if (aInstanceTimeToUpdate
.Time() != updatedTime
|| aObjectChanged
) {
230 mOwner
->UpdateInstanceTime(&aInstanceTimeToUpdate
, updatedTime
, mIsBegin
);
235 nsSMILTimeValueSpec::HandleDeletedInstanceTime(
236 nsSMILInstanceTime
&aInstanceTime
)
238 mOwner
->RemoveInstanceTime(&aInstanceTime
, mIsBegin
);
242 nsSMILTimeValueSpec::DependsOnBegin() const
244 return mParams
.mSyncBegin
;
248 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback
* aCallback
)
250 mReferencedElement
.Traverse(aCallback
);
254 nsSMILTimeValueSpec::Unlink()
256 UnregisterFromReferencedElement(mReferencedElement
.get());
257 mReferencedElement
.Unlink();
260 //----------------------------------------------------------------------
261 // Implementation helpers
264 nsSMILTimeValueSpec::UpdateReferencedElement(Element
* aFrom
, Element
* aTo
)
269 UnregisterFromReferencedElement(aFrom
);
271 switch (mParams
.mType
)
273 case nsSMILTimeValueSpecParams::SYNCBASE
:
275 nsSMILTimedElement
* to
= GetTimedElement(aTo
);
277 to
->AddDependent(*this);
282 case nsSMILTimeValueSpecParams::EVENT
:
283 case nsSMILTimeValueSpecParams::REPEAT
:
284 case nsSMILTimeValueSpecParams::ACCESSKEY
:
285 RegisterEventListener(aTo
);
289 // not a referencing-type
295 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element
* aElement
)
300 if (mParams
.mType
== nsSMILTimeValueSpecParams::SYNCBASE
) {
301 nsSMILTimedElement
* timedElement
= GetTimedElement(aElement
);
303 timedElement
->RemoveDependent(*this);
305 mOwner
->RemoveInstanceTimesForCreator(this, mIsBegin
);
306 } else if (IsEventBased()) {
307 UnregisterEventListener(aElement
);
312 nsSMILTimeValueSpec::GetTimedElement(Element
* aElement
)
317 nsCOMPtr
<nsISMILAnimationElement
> animElement
= do_QueryInterface(aElement
);
321 return &animElement
->TimedElement();
325 nsSMILTimeValueSpec::RegisterEventListener(Element
* aTarget
)
327 NS_ABORT_IF_FALSE(IsEventBased(),
328 "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
330 NS_ABORT_IF_FALSE(mParams
.mEventSymbol
,
331 "Attempting to register event-listener but there is no event name");
336 if (!mEventListener
) {
337 mEventListener
= new EventListener(this);
340 nsCOMPtr
<nsIDOMEventGroup
> sysGroup
;
341 nsIEventListenerManager
* elm
=
342 GetEventListenerManager(aTarget
, getter_AddRefs(sysGroup
));
346 elm
->AddEventListenerByType(mEventListener
,
347 nsDependentAtomString(mParams
.mEventSymbol
),
348 NS_EVENT_FLAG_BUBBLE
|
349 NS_PRIV_EVENT_UNTRUSTED_PERMITTED
,
354 nsSMILTimeValueSpec::UnregisterEventListener(Element
* aTarget
)
356 if (!aTarget
|| !mEventListener
)
359 nsCOMPtr
<nsIDOMEventGroup
> sysGroup
;
360 nsIEventListenerManager
* elm
=
361 GetEventListenerManager(aTarget
, getter_AddRefs(sysGroup
));
365 elm
->RemoveEventListenerByType(mEventListener
,
366 nsDependentAtomString(mParams
.mEventSymbol
),
367 NS_EVENT_FLAG_BUBBLE
|
368 NS_PRIV_EVENT_UNTRUSTED_PERMITTED
,
372 nsIEventListenerManager
*
373 nsSMILTimeValueSpec::GetEventListenerManager(Element
* aTarget
,
374 nsIDOMEventGroup
** aSystemGroup
)
376 NS_ABORT_IF_FALSE(aTarget
, "null target; can't get EventListenerManager");
377 NS_ABORT_IF_FALSE(aSystemGroup
&& !*aSystemGroup
,
378 "Bad out param for system group");
380 nsCOMPtr
<nsPIDOMEventTarget
> piTarget
;
382 if (mParams
.mType
== nsSMILTimeValueSpecParams::ACCESSKEY
) {
383 nsIDocument
* doc
= aTarget
->GetCurrentDoc();
386 nsPIDOMWindow
* win
= doc
->GetWindow();
389 piTarget
= do_QueryInterface(win
);
396 nsIEventListenerManager
* elm
= piTarget
->GetListenerManager(PR_TRUE
);
400 aTarget
->GetSystemEventGroup(aSystemGroup
);
408 nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent
* aEvent
)
410 NS_ABORT_IF_FALSE(mEventListener
, "Got event without an event listener");
411 NS_ABORT_IF_FALSE(IsEventBased(),
412 "Got event for non-event nsSMILTimeValueSpec");
413 NS_ABORT_IF_FALSE(aEvent
, "No event supplied");
415 // XXX In the long run we should get the time from the event itself which will
416 // store the time in global document time which we'll need to convert to our
418 nsSMILTimeContainer
* container
= mOwner
->GetTimeContainer();
422 if (!CheckEventDetail(aEvent
))
425 nsSMILTime currentTime
= container
->GetCurrentTime();
426 nsSMILTimeValue
newTime(currentTime
+ mParams
.mOffset
.GetMillis());
428 nsRefPtr
<nsSMILInstanceTime
> newInstance
=
429 new nsSMILInstanceTime(newTime
, nsSMILInstanceTime::SOURCE_EVENT
);
430 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
434 nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent
*aEvent
)
436 switch (mParams
.mType
)
438 case nsSMILTimeValueSpecParams::REPEAT
:
439 return CheckRepeatEventDetail(aEvent
);
441 case nsSMILTimeValueSpecParams::ACCESSKEY
:
442 return CheckAccessKeyEventDetail(aEvent
);
451 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent
*aEvent
)
453 nsCOMPtr
<nsIDOMTimeEvent
> timeEvent
= do_QueryInterface(aEvent
);
455 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
460 timeEvent
->GetDetail(&detail
);
461 return detail
> 0 && (PRUint32
)detail
== mParams
.mRepeatIterationOrAccessKey
;
465 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent
*aEvent
)
467 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
469 NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
473 // Ignore the key event if any modifier keys are pressed UNLESS we're matching
474 // on the charCode in which case we ignore the state of the shift and alt keys
475 // since they might be needed to generate the character in question.
478 keyEvent
->GetCtrlKey(&isCtrl
);
479 keyEvent
->GetMetaKey(&isMeta
);
480 if (isCtrl
|| isMeta
)
484 keyEvent
->GetCharCode(&code
);
486 return code
== mParams
.mRepeatIterationOrAccessKey
;
488 // Only match on the keyCode if it corresponds to some ASCII character that
489 // does not produce a charCode.
490 // In this case we can safely bail out if either alt or shift is pressed since
491 // they won't already be incorporated into the keyCode unlike the charCode.
494 keyEvent
->GetAltKey(&isAlt
);
495 keyEvent
->GetShiftKey(&isShift
);
496 if (isAlt
|| isShift
)
499 keyEvent
->GetKeyCode(&code
);
502 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE
:
503 return mParams
.mRepeatIterationOrAccessKey
== 0x08;
505 case nsIDOMKeyEvent::DOM_VK_RETURN
:
506 case nsIDOMKeyEvent::DOM_VK_ENTER
:
507 return mParams
.mRepeatIterationOrAccessKey
== 0x0A ||
508 mParams
.mRepeatIterationOrAccessKey
== 0x0D;
510 case nsIDOMKeyEvent::DOM_VK_ESCAPE
:
511 return mParams
.mRepeatIterationOrAccessKey
== 0x1B;
513 case nsIDOMKeyEvent::DOM_VK_DELETE
:
514 return mParams
.mRepeatIterationOrAccessKey
== 0x7F;
522 nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
523 const nsSMILTimeValue
& aSrcTime
,
524 const nsSMILTimeContainer
* aSrcContainer
)
526 // If the source time is either indefinite or unresolved the result is going
528 if (!aSrcTime
.IsResolved())
531 // Convert from source time container to our parent time container
532 const nsSMILTimeContainer
* dstContainer
= mOwner
->GetTimeContainer();
533 if (dstContainer
== aSrcContainer
)
536 // If one of the elements is not attached to a time container then we can't do
537 // any meaningful conversion
538 if (!aSrcContainer
|| !dstContainer
)
539 return nsSMILTimeValue(); // unresolved
541 nsSMILTimeValue docTime
=
542 aSrcContainer
->ContainerToParentTime(aSrcTime
.GetMillis());
544 if (docTime
.IsIndefinite())
545 // This will happen if the source container is paused and we have a future
546 // time. Just return the indefinite time.
549 NS_ABORT_IF_FALSE(docTime
.IsResolved(),
550 "ContainerToParentTime gave us an unresolved time");
552 return dstContainer
->ParentToContainerTime(docTime
.GetMillis());