Bumping manifests a=b2g-bump
[gecko.git] / dom / smil / nsSMILTimeValueSpec.cpp
blob5b2f87e246560c0f7988c22321016ba11add5a3f
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"
17 #include "nsString.h"
18 #include <limits>
20 using namespace mozilla;
21 using namespace mozilla::dom;
23 //----------------------------------------------------------------------
24 // Nested class: EventListener
26 NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
28 NS_IMETHODIMP
29 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
31 if (mSpec) {
32 mSpec->HandleEvent(aEvent);
34 return NS_OK;
37 //----------------------------------------------------------------------
38 // Implementation
40 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
41 bool aIsBegin)
42 : mOwner(&aOwner),
43 mIsBegin(aIsBegin),
44 mReferencedElement(this)
48 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
50 UnregisterFromReferencedElement(mReferencedElement.get());
51 if (mEventListener) {
52 mEventListener->Disconnect();
53 mEventListener = nullptr;
57 nsresult
58 nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
59 Element* aContextNode)
61 nsSMILTimeValueSpecParams params;
63 if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
64 return NS_ERROR_FAILURE;
66 mParams = params;
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);
86 return NS_OK;
89 void
90 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
92 if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
93 return;
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
99 // when that happens
100 if (!aContextNode->IsInDoc())
101 return;
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
105 // element.
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());
118 } else {
119 NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID");
121 UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
124 bool
125 nsSMILTimeValueSpec::IsEventBased() const
127 return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
128 mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
129 mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
132 void
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);
141 // Apply offset
142 if (!ApplyOffset(newTime)) {
143 NS_WARNING("New time overflows nsSMILTime, ignoring");
144 return;
147 // Create the instance time and register it with the interval
148 nsRefPtr<nsSMILInstanceTime> newInstance =
149 new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
150 &aInterval);
151 mOwner->AddInstanceTime(newInstance, mIsBegin);
154 void
155 nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
157 if (!IsEventBased() || mParams.mDependentElemID)
158 return;
160 mReferencedElement.ResetWithElement(aNewTarget);
163 void
164 nsSMILTimeValueSpec::HandleChangedInstanceTime(
165 const nsSMILInstanceTime& aBaseTime,
166 const nsSMILTimeContainer* aSrcContainer,
167 nsSMILInstanceTime& aInstanceTimeToUpdate,
168 bool aObjectChanged)
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())
173 return;
175 nsSMILTimeValue updatedTime =
176 ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
178 // Apply offset
179 if (!ApplyOffset(updatedTime)) {
180 NS_WARNING("Updated time overflows nsSMILTime, ignoring");
181 return;
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);
191 void
192 nsSMILTimeValueSpec::HandleDeletedInstanceTime(
193 nsSMILInstanceTime &aInstanceTime)
195 mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
198 bool
199 nsSMILTimeValueSpec::DependsOnBegin() const
201 return mParams.mSyncBegin;
204 void
205 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
207 mReferencedElement.Traverse(aCallback);
210 void
211 nsSMILTimeValueSpec::Unlink()
213 UnregisterFromReferencedElement(mReferencedElement.get());
214 mReferencedElement.Unlink();
217 //----------------------------------------------------------------------
218 // Implementation helpers
220 void
221 nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
223 if (aFrom == aTo)
224 return;
226 UnregisterFromReferencedElement(aFrom);
228 switch (mParams.mType)
230 case nsSMILTimeValueSpecParams::SYNCBASE:
232 nsSMILTimedElement* to = GetTimedElement(aTo);
233 if (to) {
234 to->AddDependent(*this);
237 break;
239 case nsSMILTimeValueSpecParams::EVENT:
240 case nsSMILTimeValueSpecParams::REPEAT:
241 case nsSMILTimeValueSpecParams::ACCESSKEY:
242 RegisterEventListener(aTo);
243 break;
245 default:
246 // not a referencing-type
247 break;
251 void
252 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
254 if (!aElement)
255 return;
257 if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
258 nsSMILTimedElement* timedElement = GetTimedElement(aElement);
259 if (timedElement) {
260 timedElement->RemoveDependent(*this);
262 mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
263 } else if (IsEventBased()) {
264 UnregisterEventListener(aElement);
268 nsSMILTimedElement*
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.
277 bool
278 nsSMILTimeValueSpec::IsWhitelistedEvent()
280 // The category of (SMIL-specific) "repeat(n)" events are allowed.
281 if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
282 return true;
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)) {
291 return true;
294 return false;
297 void
298 nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
300 NS_ABORT_IF_FALSE(IsEventBased(),
301 "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
302 " type");
303 NS_ABORT_IF_FALSE(mParams.mEventSymbol,
304 "Attempting to register event-listener but there is no event name");
306 if (!aTarget)
307 return;
309 // When script is disabled, only allow registration for whitelisted events.
310 if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
311 !IsWhitelistedEvent()) {
312 return;
315 if (!mEventListener) {
316 mEventListener = new EventListener(this);
319 EventListenerManager* elm = GetEventListenerManager(aTarget);
320 if (!elm)
321 return;
323 elm->AddEventListenerByType(mEventListener,
324 nsDependentAtomString(mParams.mEventSymbol),
325 AllEventsAtSystemGroupBubble());
328 void
329 nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
331 if (!aTarget || !mEventListener)
332 return;
334 EventListenerManager* elm = GetEventListenerManager(aTarget);
335 if (!elm)
336 return;
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();
352 if (!doc)
353 return nullptr;
354 nsPIDOMWindow* win = doc->GetWindow();
355 if (!win)
356 return nullptr;
357 target = do_QueryInterface(win);
358 } else {
359 target = aTarget;
361 if (!target)
362 return nullptr;
364 return target->GetOrCreateListenerManager();
367 void
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
377 // time container
378 nsSMILTimeContainer* container = mOwner->GetTimeContainer();
379 if (!container)
380 return;
382 if (!CheckEventDetail(aEvent))
383 return;
385 nsSMILTime currentTime = container->GetCurrentTime();
386 nsSMILTimeValue newTime(currentTime);
387 if (!ApplyOffset(newTime)) {
388 NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
389 return;
392 nsRefPtr<nsSMILInstanceTime> newInstance =
393 new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
394 mOwner->AddInstanceTime(newInstance, mIsBegin);
397 bool
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);
408 default:
409 // nothing to check
410 return true;
414 bool
415 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent)
417 nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
418 if (!timeEvent) {
419 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
420 return false;
423 int32_t detail;
424 timeEvent->GetDetail(&detail);
425 return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey;
428 bool
429 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent)
431 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
432 if (!keyEvent) {
433 NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
434 return false;
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.
440 bool isCtrl;
441 bool isMeta;
442 keyEvent->GetCtrlKey(&isCtrl);
443 keyEvent->GetMetaKey(&isMeta);
444 if (isCtrl || isMeta)
445 return false;
447 uint32_t code;
448 keyEvent->GetCharCode(&code);
449 if (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.
456 bool isAlt;
457 bool isShift;
458 keyEvent->GetAltKey(&isAlt);
459 keyEvent->GetShiftKey(&isShift);
460 if (isAlt || isShift)
461 return false;
463 keyEvent->GetKeyCode(&code);
464 switch (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;
479 default:
480 return false;
484 nsSMILTimeValue
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
490 // to be the same
491 if (!aSrcTime.IsDefinite())
492 return aSrcTime;
494 // Convert from source time container to our parent time container
495 const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
496 if (dstContainer == aSrcContainer)
497 return aSrcTime;
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.
510 return docTime;
512 NS_ABORT_IF_FALSE(docTime.IsDefinite(),
513 "ContainerToParentTime gave us an unresolved or indefinite time");
515 return dstContainer->ParentToContainerTime(docTime.GetMillis());
518 bool
519 nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const
521 // indefinite + offset = indefinite. Likewise for unresolved times.
522 if (!aTime.IsDefinite()) {
523 return true;
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()) {
530 return false;
532 aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
533 return true;