Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/EventListenerManager.h"
8 : #include "mozilla/dom/SVGAnimationElement.h"
9 : #include "nsSMILTimeValueSpec.h"
10 : #include "nsSMILInterval.h"
11 : #include "nsSMILTimeContainer.h"
12 : #include "nsSMILTimeValue.h"
13 : #include "nsSMILTimedElement.h"
14 : #include "nsSMILInstanceTime.h"
15 : #include "nsSMILParserUtils.h"
16 : #include "nsIDOMKeyEvent.h"
17 : #include "nsIDOMTimeEvent.h"
18 : #include "nsString.h"
19 : #include <limits>
20 :
21 : using namespace mozilla;
22 : using namespace mozilla::dom;
23 :
24 : //----------------------------------------------------------------------
25 : // Nested class: EventListener
26 :
27 0 : NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
28 :
29 : NS_IMETHODIMP
30 0 : nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
31 : {
32 0 : if (mSpec) {
33 0 : mSpec->HandleEvent(aEvent);
34 : }
35 0 : return NS_OK;
36 : }
37 :
38 : //----------------------------------------------------------------------
39 : // Implementation
40 :
41 0 : nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
42 0 : bool aIsBegin)
43 : : mOwner(&aOwner),
44 : mIsBegin(aIsBegin),
45 0 : mReferencedElement(this)
46 : {
47 0 : }
48 :
49 0 : nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
50 : {
51 0 : UnregisterFromReferencedElement(mReferencedElement.get());
52 0 : if (mEventListener) {
53 0 : mEventListener->Disconnect();
54 0 : mEventListener = nullptr;
55 : }
56 0 : }
57 :
58 : nsresult
59 0 : nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
60 : Element* aContextNode)
61 : {
62 0 : nsSMILTimeValueSpecParams params;
63 :
64 0 : if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
65 0 : return NS_ERROR_FAILURE;
66 :
67 0 : mParams = params;
68 :
69 : // According to SMIL 3.0:
70 : // The special value "indefinite" does not yield an instance time in the
71 : // begin list. It will, however yield a single instance with the value
72 : // "indefinite" in an end list. This value is not removed by a reset.
73 0 : if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET ||
74 0 : (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
75 0 : mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
76 : }
77 :
78 : // Fill in the event symbol to simplify handling later
79 0 : if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
80 0 : mParams.mEventSymbol = nsGkAtoms::repeatEvent;
81 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
82 0 : mParams.mEventSymbol = nsGkAtoms::keypress;
83 : }
84 :
85 0 : ResolveReferences(aContextNode);
86 :
87 0 : return NS_OK;
88 : }
89 :
90 : void
91 0 : nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
92 : {
93 0 : if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
94 0 : return;
95 :
96 0 : MOZ_ASSERT(aContextNode,
97 : "null context node for resolving timing references against");
98 :
99 : // If we're not bound to the document yet, don't worry, we'll get called again
100 : // when that happens
101 0 : if (!aContextNode->IsInUncomposedDoc())
102 0 : return;
103 :
104 : // Hold ref to the old element so that it isn't destroyed in between resetting
105 : // the referenced element and using the pointer to update the referenced
106 : // element.
107 0 : RefPtr<Element> oldReferencedElement = mReferencedElement.get();
108 :
109 0 : if (mParams.mDependentElemID) {
110 0 : mReferencedElement.ResetWithID(aContextNode,
111 0 : nsDependentAtomString(mParams.mDependentElemID));
112 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
113 0 : Element* target = mOwner->GetTargetElement();
114 0 : mReferencedElement.ResetWithElement(target);
115 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
116 0 : nsIDocument* doc = aContextNode->GetUncomposedDoc();
117 0 : MOZ_ASSERT(doc, "We are in the document but current doc is null");
118 0 : mReferencedElement.ResetWithElement(doc->GetRootElement());
119 : } else {
120 0 : MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
121 : }
122 0 : UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
123 : }
124 :
125 : bool
126 0 : nsSMILTimeValueSpec::IsEventBased() const
127 : {
128 0 : return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
129 0 : mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
130 0 : mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
131 : }
132 :
133 : void
134 0 : nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval,
135 : const nsSMILTimeContainer* aSrcContainer)
136 : {
137 0 : const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin
138 0 : ? *aInterval.Begin() : *aInterval.End();
139 : nsSMILTimeValue newTime =
140 0 : ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
141 :
142 : // Apply offset
143 0 : if (!ApplyOffset(newTime)) {
144 0 : NS_WARNING("New time overflows nsSMILTime, ignoring");
145 0 : return;
146 : }
147 :
148 : // Create the instance time and register it with the interval
149 : RefPtr<nsSMILInstanceTime> newInstance =
150 : new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
151 0 : &aInterval);
152 0 : mOwner->AddInstanceTime(newInstance, mIsBegin);
153 : }
154 :
155 : void
156 0 : nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
157 : {
158 0 : if (!IsEventBased() || mParams.mDependentElemID)
159 0 : return;
160 :
161 0 : mReferencedElement.ResetWithElement(aNewTarget);
162 : }
163 :
164 : void
165 0 : nsSMILTimeValueSpec::HandleChangedInstanceTime(
166 : const nsSMILInstanceTime& aBaseTime,
167 : const nsSMILTimeContainer* aSrcContainer,
168 : nsSMILInstanceTime& aInstanceTimeToUpdate,
169 : bool aObjectChanged)
170 : {
171 : // If the instance time is fixed (e.g. because it's being used as the begin
172 : // time of an active or postactive interval) we just ignore the change.
173 0 : if (aInstanceTimeToUpdate.IsFixedTime())
174 0 : return;
175 :
176 : nsSMILTimeValue updatedTime =
177 0 : ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
178 :
179 : // Apply offset
180 0 : if (!ApplyOffset(updatedTime)) {
181 0 : NS_WARNING("Updated time overflows nsSMILTime, ignoring");
182 0 : return;
183 : }
184 :
185 : // The timed element that owns the instance time does the updating so it can
186 : // re-sort its array of instance times more efficiently
187 0 : if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
188 0 : mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
189 : }
190 : }
191 :
192 : void
193 0 : nsSMILTimeValueSpec::HandleDeletedInstanceTime(
194 : nsSMILInstanceTime &aInstanceTime)
195 : {
196 0 : mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
197 0 : }
198 :
199 : bool
200 0 : nsSMILTimeValueSpec::DependsOnBegin() const
201 : {
202 0 : return mParams.mSyncBegin;
203 : }
204 :
205 : void
206 0 : nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
207 : {
208 0 : mReferencedElement.Traverse(aCallback);
209 0 : }
210 :
211 : void
212 0 : nsSMILTimeValueSpec::Unlink()
213 : {
214 0 : UnregisterFromReferencedElement(mReferencedElement.get());
215 0 : mReferencedElement.Unlink();
216 0 : }
217 :
218 : //----------------------------------------------------------------------
219 : // Implementation helpers
220 :
221 : void
222 0 : nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
223 : {
224 0 : if (aFrom == aTo)
225 0 : return;
226 :
227 0 : UnregisterFromReferencedElement(aFrom);
228 :
229 0 : switch (mParams.mType)
230 : {
231 : case nsSMILTimeValueSpecParams::SYNCBASE:
232 : {
233 0 : nsSMILTimedElement* to = GetTimedElement(aTo);
234 0 : if (to) {
235 0 : to->AddDependent(*this);
236 : }
237 : }
238 0 : break;
239 :
240 : case nsSMILTimeValueSpecParams::EVENT:
241 : case nsSMILTimeValueSpecParams::REPEAT:
242 : case nsSMILTimeValueSpecParams::ACCESSKEY:
243 0 : RegisterEventListener(aTo);
244 0 : break;
245 :
246 : default:
247 : // not a referencing-type
248 0 : break;
249 : }
250 : }
251 :
252 : void
253 0 : nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
254 : {
255 0 : if (!aElement)
256 0 : return;
257 :
258 0 : if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
259 0 : nsSMILTimedElement* timedElement = GetTimedElement(aElement);
260 0 : if (timedElement) {
261 0 : timedElement->RemoveDependent(*this);
262 : }
263 0 : mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
264 0 : } else if (IsEventBased()) {
265 0 : UnregisterEventListener(aElement);
266 : }
267 : }
268 :
269 : nsSMILTimedElement*
270 0 : nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
271 : {
272 0 : return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ?
273 0 : &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr;
274 : }
275 :
276 : // Indicates whether we're allowed to register an event-listener
277 : // when scripting is disabled.
278 : bool
279 0 : nsSMILTimeValueSpec::IsWhitelistedEvent()
280 : {
281 : // The category of (SMIL-specific) "repeat(n)" events are allowed.
282 0 : if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
283 0 : return true;
284 : }
285 :
286 : // A specific list of other SMIL-related events are allowed, too.
287 0 : if (mParams.mType == nsSMILTimeValueSpecParams::EVENT &&
288 0 : (mParams.mEventSymbol == nsGkAtoms::repeat ||
289 0 : mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
290 0 : mParams.mEventSymbol == nsGkAtoms::beginEvent ||
291 0 : mParams.mEventSymbol == nsGkAtoms::endEvent)) {
292 0 : return true;
293 : }
294 :
295 0 : return false;
296 : }
297 :
298 : void
299 0 : nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
300 : {
301 0 : MOZ_ASSERT(IsEventBased(),
302 : "Attempting to register event-listener for unexpected "
303 : "nsSMILTimeValueSpec type");
304 0 : MOZ_ASSERT(mParams.mEventSymbol,
305 : "Attempting to register event-listener but there is no event "
306 : "name");
307 :
308 0 : if (!aTarget)
309 0 : return;
310 :
311 : // When script is disabled, only allow registration for whitelisted events.
312 0 : if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
313 0 : !IsWhitelistedEvent()) {
314 0 : return;
315 : }
316 :
317 0 : if (!mEventListener) {
318 0 : mEventListener = new EventListener(this);
319 : }
320 :
321 0 : EventListenerManager* elm = GetEventListenerManager(aTarget);
322 0 : if (!elm)
323 0 : return;
324 :
325 : elm->AddEventListenerByType(mEventListener,
326 0 : nsDependentAtomString(mParams.mEventSymbol),
327 0 : AllEventsAtSystemGroupBubble());
328 : }
329 :
330 : void
331 0 : nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
332 : {
333 0 : if (!aTarget || !mEventListener)
334 0 : return;
335 :
336 0 : EventListenerManager* elm = GetEventListenerManager(aTarget);
337 0 : if (!elm)
338 0 : return;
339 :
340 : elm->RemoveEventListenerByType(mEventListener,
341 0 : nsDependentAtomString(mParams.mEventSymbol),
342 0 : AllEventsAtSystemGroupBubble());
343 : }
344 :
345 : EventListenerManager*
346 0 : nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget)
347 : {
348 0 : MOZ_ASSERT(aTarget, "null target; can't get EventListenerManager");
349 :
350 0 : nsCOMPtr<EventTarget> target;
351 :
352 0 : if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
353 0 : nsIDocument* doc = aTarget->GetUncomposedDoc();
354 0 : if (!doc)
355 0 : return nullptr;
356 0 : nsPIDOMWindowOuter* win = doc->GetWindow();
357 0 : if (!win)
358 0 : return nullptr;
359 0 : target = do_QueryInterface(win);
360 : } else {
361 0 : target = aTarget;
362 : }
363 0 : if (!target)
364 0 : return nullptr;
365 :
366 0 : return target->GetOrCreateListenerManager();
367 : }
368 :
369 : void
370 0 : nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent)
371 : {
372 0 : MOZ_ASSERT(mEventListener, "Got event without an event listener");
373 0 : MOZ_ASSERT(IsEventBased(),
374 : "Got event for non-event nsSMILTimeValueSpec");
375 0 : MOZ_ASSERT(aEvent, "No event supplied");
376 :
377 : // XXX In the long run we should get the time from the event itself which will
378 : // store the time in global document time which we'll need to convert to our
379 : // time container
380 0 : nsSMILTimeContainer* container = mOwner->GetTimeContainer();
381 0 : if (!container)
382 0 : return;
383 :
384 0 : if (!CheckEventDetail(aEvent))
385 0 : return;
386 :
387 0 : nsSMILTime currentTime = container->GetCurrentTime();
388 0 : nsSMILTimeValue newTime(currentTime);
389 0 : if (!ApplyOffset(newTime)) {
390 0 : NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
391 0 : return;
392 : }
393 :
394 : RefPtr<nsSMILInstanceTime> newInstance =
395 0 : new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
396 0 : mOwner->AddInstanceTime(newInstance, mIsBegin);
397 : }
398 :
399 : bool
400 0 : nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent)
401 : {
402 0 : switch (mParams.mType)
403 : {
404 : case nsSMILTimeValueSpecParams::REPEAT:
405 0 : return CheckRepeatEventDetail(aEvent);
406 :
407 : case nsSMILTimeValueSpecParams::ACCESSKEY:
408 0 : return CheckAccessKeyEventDetail(aEvent);
409 :
410 : default:
411 : // nothing to check
412 0 : return true;
413 : }
414 : }
415 :
416 : bool
417 0 : nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent)
418 : {
419 0 : nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
420 0 : if (!timeEvent) {
421 0 : NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
422 0 : return false;
423 : }
424 :
425 : int32_t detail;
426 0 : timeEvent->GetDetail(&detail);
427 0 : return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey;
428 : }
429 :
430 : bool
431 0 : nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent)
432 : {
433 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
434 0 : if (!keyEvent) {
435 0 : NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
436 0 : return false;
437 : }
438 :
439 : // Ignore the key event if any modifier keys are pressed UNLESS we're matching
440 : // on the charCode in which case we ignore the state of the shift and alt keys
441 : // since they might be needed to generate the character in question.
442 : bool isCtrl;
443 : bool isMeta;
444 0 : keyEvent->GetCtrlKey(&isCtrl);
445 0 : keyEvent->GetMetaKey(&isMeta);
446 0 : if (isCtrl || isMeta)
447 0 : return false;
448 :
449 : uint32_t code;
450 0 : keyEvent->GetCharCode(&code);
451 0 : if (code)
452 0 : return code == mParams.mRepeatIterationOrAccessKey;
453 :
454 : // Only match on the keyCode if it corresponds to some ASCII character that
455 : // does not produce a charCode.
456 : // In this case we can safely bail out if either alt or shift is pressed since
457 : // they won't already be incorporated into the keyCode unlike the charCode.
458 : bool isAlt;
459 : bool isShift;
460 0 : keyEvent->GetAltKey(&isAlt);
461 0 : keyEvent->GetShiftKey(&isShift);
462 0 : if (isAlt || isShift)
463 0 : return false;
464 :
465 0 : keyEvent->GetKeyCode(&code);
466 0 : switch (code)
467 : {
468 : case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
469 0 : return mParams.mRepeatIterationOrAccessKey == 0x08;
470 :
471 : case nsIDOMKeyEvent::DOM_VK_RETURN:
472 0 : return mParams.mRepeatIterationOrAccessKey == 0x0A ||
473 0 : mParams.mRepeatIterationOrAccessKey == 0x0D;
474 :
475 : case nsIDOMKeyEvent::DOM_VK_ESCAPE:
476 0 : return mParams.mRepeatIterationOrAccessKey == 0x1B;
477 :
478 : case nsIDOMKeyEvent::DOM_VK_DELETE:
479 0 : return mParams.mRepeatIterationOrAccessKey == 0x7F;
480 :
481 : default:
482 0 : return false;
483 : }
484 : }
485 :
486 : nsSMILTimeValue
487 0 : nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
488 : const nsSMILTimeValue& aSrcTime,
489 : const nsSMILTimeContainer* aSrcContainer)
490 : {
491 : // If the source time is either indefinite or unresolved the result is going
492 : // to be the same
493 0 : if (!aSrcTime.IsDefinite())
494 0 : return aSrcTime;
495 :
496 : // Convert from source time container to our parent time container
497 0 : const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
498 0 : if (dstContainer == aSrcContainer)
499 0 : return aSrcTime;
500 :
501 : // If one of the elements is not attached to a time container then we can't do
502 : // any meaningful conversion
503 0 : if (!aSrcContainer || !dstContainer)
504 0 : return nsSMILTimeValue(); // unresolved
505 :
506 : nsSMILTimeValue docTime =
507 0 : aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
508 :
509 0 : if (docTime.IsIndefinite())
510 : // This will happen if the source container is paused and we have a future
511 : // time. Just return the indefinite time.
512 0 : return docTime;
513 :
514 0 : MOZ_ASSERT(docTime.IsDefinite(),
515 : "ContainerToParentTime gave us an unresolved or indefinite time");
516 :
517 0 : return dstContainer->ParentToContainerTime(docTime.GetMillis());
518 : }
519 :
520 : bool
521 0 : nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const
522 : {
523 : // indefinite + offset = indefinite. Likewise for unresolved times.
524 0 : if (!aTime.IsDefinite()) {
525 0 : return true;
526 : }
527 :
528 : double resultAsDouble =
529 0 : (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
530 0 : if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() ||
531 0 : resultAsDouble < std::numeric_limits<nsSMILTime>::min()) {
532 0 : return false;
533 : }
534 0 : aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
535 0 : return true;
536 : }
|