Bug 606160 - CSS updates for fx button after caption button padding changes. r=dao...
[mozilla-central.git] / content / smil / nsSMILTimeValueSpec.cpp
blob4614ab3407083b9a85651d1c8229a135ecc22c87
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
13 * License.
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.
21 * Contributor(s):
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"
51 #include "nsString.h"
53 using namespace mozilla::dom;
55 //----------------------------------------------------------------------
56 // Nested class: EventListener
58 NS_IMPL_ISUPPORTS1(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
60 NS_IMETHODIMP
61 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
63 if (mSpec) {
64 mSpec->HandleEvent(aEvent);
66 return NS_OK;
69 //----------------------------------------------------------------------
70 // Implementation
72 #ifdef _MSC_VER
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.
76 #pragma warning(push)
77 #pragma warning(disable:4355)
78 #endif
79 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
80 PRBool aIsBegin)
81 : mOwner(&aOwner),
82 mIsBegin(aIsBegin),
83 mReferencedElement(this)
84 #ifdef _MSC_VER
85 #pragma warning(pop)
86 #endif
90 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
92 UnregisterFromReferencedElement(mReferencedElement.get());
93 if (mEventListener) {
94 mEventListener->Disconnect();
95 mEventListener = nsnull;
99 nsresult
100 nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
101 Element* aContextNode)
103 nsSMILTimeValueSpecParams params;
104 nsresult rv =
105 nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params);
107 if (NS_FAILED(rv))
108 return rv;
110 mParams = 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);
130 return rv;
133 void
134 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
136 if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
137 return;
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
143 // when that happens
144 if (!aContextNode->IsInDoc())
145 return;
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
149 // element.
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());
162 } else {
163 NS_ABORT_IF_FALSE(PR_FALSE, "Syncbase or repeat spec without ID");
165 UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
168 PRBool
169 nsSMILTimeValueSpec::IsEventBased() const
171 return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
172 mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
173 mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
176 void
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);
185 // Apply offset
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,
193 &aInterval);
194 mOwner->AddInstanceTime(newInstance, mIsBegin);
197 void
198 nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
200 if (!IsEventBased() || mParams.mDependentElemID)
201 return;
203 mReferencedElement.ResetWithElement(aNewTarget);
206 void
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())
216 return;
218 nsSMILTimeValue updatedTime =
219 ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
221 // Apply offset
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);
234 void
235 nsSMILTimeValueSpec::HandleDeletedInstanceTime(
236 nsSMILInstanceTime &aInstanceTime)
238 mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
241 PRBool
242 nsSMILTimeValueSpec::DependsOnBegin() const
244 return mParams.mSyncBegin;
247 void
248 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
250 mReferencedElement.Traverse(aCallback);
253 void
254 nsSMILTimeValueSpec::Unlink()
256 UnregisterFromReferencedElement(mReferencedElement.get());
257 mReferencedElement.Unlink();
260 //----------------------------------------------------------------------
261 // Implementation helpers
263 void
264 nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
266 if (aFrom == aTo)
267 return;
269 UnregisterFromReferencedElement(aFrom);
271 switch (mParams.mType)
273 case nsSMILTimeValueSpecParams::SYNCBASE:
275 nsSMILTimedElement* to = GetTimedElement(aTo);
276 if (to) {
277 to->AddDependent(*this);
280 break;
282 case nsSMILTimeValueSpecParams::EVENT:
283 case nsSMILTimeValueSpecParams::REPEAT:
284 case nsSMILTimeValueSpecParams::ACCESSKEY:
285 RegisterEventListener(aTo);
286 break;
288 default:
289 // not a referencing-type
290 break;
294 void
295 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
297 if (!aElement)
298 return;
300 if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
301 nsSMILTimedElement* timedElement = GetTimedElement(aElement);
302 if (timedElement) {
303 timedElement->RemoveDependent(*this);
305 mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
306 } else if (IsEventBased()) {
307 UnregisterEventListener(aElement);
311 nsSMILTimedElement*
312 nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
314 if (!aElement)
315 return nsnull;
317 nsCOMPtr<nsISMILAnimationElement> animElement = do_QueryInterface(aElement);
318 if (!animElement)
319 return nsnull;
321 return &animElement->TimedElement();
324 void
325 nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
327 NS_ABORT_IF_FALSE(IsEventBased(),
328 "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
329 " type");
330 NS_ABORT_IF_FALSE(mParams.mEventSymbol,
331 "Attempting to register event-listener but there is no event name");
333 if (!aTarget)
334 return;
336 if (!mEventListener) {
337 mEventListener = new EventListener(this);
340 nsCOMPtr<nsIDOMEventGroup> sysGroup;
341 nsIEventListenerManager* elm =
342 GetEventListenerManager(aTarget, getter_AddRefs(sysGroup));
343 if (!elm)
344 return;
346 elm->AddEventListenerByType(mEventListener,
347 nsDependentAtomString(mParams.mEventSymbol),
348 NS_EVENT_FLAG_BUBBLE |
349 NS_PRIV_EVENT_UNTRUSTED_PERMITTED,
350 sysGroup);
353 void
354 nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
356 if (!aTarget || !mEventListener)
357 return;
359 nsCOMPtr<nsIDOMEventGroup> sysGroup;
360 nsIEventListenerManager* elm =
361 GetEventListenerManager(aTarget, getter_AddRefs(sysGroup));
362 if (!elm)
363 return;
365 elm->RemoveEventListenerByType(mEventListener,
366 nsDependentAtomString(mParams.mEventSymbol),
367 NS_EVENT_FLAG_BUBBLE |
368 NS_PRIV_EVENT_UNTRUSTED_PERMITTED,
369 sysGroup);
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();
384 if (!doc)
385 return nsnull;
386 nsPIDOMWindow* win = doc->GetWindow();
387 if (!win)
388 return nsnull;
389 piTarget = do_QueryInterface(win);
390 } else {
391 piTarget = aTarget;
393 if (!piTarget)
394 return nsnull;
396 nsIEventListenerManager* elm = piTarget->GetListenerManager(PR_TRUE);
397 if (!elm)
398 return nsnull;
400 aTarget->GetSystemEventGroup(aSystemGroup);
401 if (!*aSystemGroup)
402 return nsnull;
404 return elm;
407 void
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
417 // time container
418 nsSMILTimeContainer* container = mOwner->GetTimeContainer();
419 if (!container)
420 return;
422 if (!CheckEventDetail(aEvent))
423 return;
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);
433 PRBool
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);
444 default:
445 // nothing to check
446 return PR_TRUE;
450 PRBool
451 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent)
453 nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
454 if (!timeEvent) {
455 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
456 return PR_FALSE;
459 PRInt32 detail;
460 timeEvent->GetDetail(&detail);
461 return detail > 0 && (PRUint32)detail == mParams.mRepeatIterationOrAccessKey;
464 PRBool
465 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent)
467 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
468 if (!keyEvent) {
469 NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
470 return PR_FALSE;
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.
476 PRBool isCtrl;
477 PRBool isMeta;
478 keyEvent->GetCtrlKey(&isCtrl);
479 keyEvent->GetMetaKey(&isMeta);
480 if (isCtrl || isMeta)
481 return PR_FALSE;
483 PRUint32 code;
484 keyEvent->GetCharCode(&code);
485 if (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.
492 PRBool isAlt;
493 PRBool isShift;
494 keyEvent->GetAltKey(&isAlt);
495 keyEvent->GetShiftKey(&isShift);
496 if (isAlt || isShift)
497 return PR_FALSE;
499 keyEvent->GetKeyCode(&code);
500 switch (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;
516 default:
517 return PR_FALSE;
521 nsSMILTimeValue
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
527 // to be the same
528 if (!aSrcTime.IsResolved())
529 return aSrcTime;
531 // Convert from source time container to our parent time container
532 const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
533 if (dstContainer == aSrcContainer)
534 return aSrcTime;
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.
547 return docTime;
549 NS_ABORT_IF_FALSE(docTime.IsResolved(),
550 "ContainerToParentTime gave us an unresolved time");
552 return dstContainer->ParentToContainerTime(docTime.GetMillis());