|
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/. */ |
|
5 |
|
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> |
|
19 |
|
20 using namespace mozilla; |
|
21 using namespace mozilla::dom; |
|
22 |
|
23 //---------------------------------------------------------------------- |
|
24 // Nested class: EventListener |
|
25 |
|
26 NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener) |
|
27 |
|
28 NS_IMETHODIMP |
|
29 nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent) |
|
30 { |
|
31 if (mSpec) { |
|
32 mSpec->HandleEvent(aEvent); |
|
33 } |
|
34 return NS_OK; |
|
35 } |
|
36 |
|
37 //---------------------------------------------------------------------- |
|
38 // Implementation |
|
39 |
|
40 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, |
|
41 bool aIsBegin) |
|
42 : mOwner(&aOwner), |
|
43 mIsBegin(aIsBegin), |
|
44 mReferencedElement(MOZ_THIS_IN_INITIALIZER_LIST()) |
|
45 { |
|
46 } |
|
47 |
|
48 nsSMILTimeValueSpec::~nsSMILTimeValueSpec() |
|
49 { |
|
50 UnregisterFromReferencedElement(mReferencedElement.get()); |
|
51 if (mEventListener) { |
|
52 mEventListener->Disconnect(); |
|
53 mEventListener = nullptr; |
|
54 } |
|
55 } |
|
56 |
|
57 nsresult |
|
58 nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec, |
|
59 Element* aContextNode) |
|
60 { |
|
61 nsSMILTimeValueSpecParams params; |
|
62 |
|
63 if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params)) |
|
64 return NS_ERROR_FAILURE; |
|
65 |
|
66 mParams = params; |
|
67 |
|
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); |
|
75 } |
|
76 |
|
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; |
|
82 } |
|
83 |
|
84 ResolveReferences(aContextNode); |
|
85 |
|
86 return NS_OK; |
|
87 } |
|
88 |
|
89 void |
|
90 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode) |
|
91 { |
|
92 if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) |
|
93 return; |
|
94 |
|
95 NS_ABORT_IF_FALSE(aContextNode, |
|
96 "null context node for resolving timing references against"); |
|
97 |
|
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; |
|
102 |
|
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(); |
|
107 |
|
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"); |
|
120 } |
|
121 UpdateReferencedElement(oldReferencedElement, mReferencedElement.get()); |
|
122 } |
|
123 |
|
124 bool |
|
125 nsSMILTimeValueSpec::IsEventBased() const |
|
126 { |
|
127 return mParams.mType == nsSMILTimeValueSpecParams::EVENT || |
|
128 mParams.mType == nsSMILTimeValueSpecParams::REPEAT || |
|
129 mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY; |
|
130 } |
|
131 |
|
132 void |
|
133 nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval, |
|
134 const nsSMILTimeContainer* aSrcContainer) |
|
135 { |
|
136 const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin |
|
137 ? *aInterval.Begin() : *aInterval.End(); |
|
138 nsSMILTimeValue newTime = |
|
139 ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer); |
|
140 |
|
141 // Apply offset |
|
142 if (!ApplyOffset(newTime)) { |
|
143 NS_WARNING("New time overflows nsSMILTime, ignoring"); |
|
144 return; |
|
145 } |
|
146 |
|
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); |
|
152 } |
|
153 |
|
154 void |
|
155 nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) |
|
156 { |
|
157 if (!IsEventBased() || mParams.mDependentElemID) |
|
158 return; |
|
159 |
|
160 mReferencedElement.ResetWithElement(aNewTarget); |
|
161 } |
|
162 |
|
163 void |
|
164 nsSMILTimeValueSpec::HandleChangedInstanceTime( |
|
165 const nsSMILInstanceTime& aBaseTime, |
|
166 const nsSMILTimeContainer* aSrcContainer, |
|
167 nsSMILInstanceTime& aInstanceTimeToUpdate, |
|
168 bool aObjectChanged) |
|
169 { |
|
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; |
|
174 |
|
175 nsSMILTimeValue updatedTime = |
|
176 ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer); |
|
177 |
|
178 // Apply offset |
|
179 if (!ApplyOffset(updatedTime)) { |
|
180 NS_WARNING("Updated time overflows nsSMILTime, ignoring"); |
|
181 return; |
|
182 } |
|
183 |
|
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); |
|
188 } |
|
189 } |
|
190 |
|
191 void |
|
192 nsSMILTimeValueSpec::HandleDeletedInstanceTime( |
|
193 nsSMILInstanceTime &aInstanceTime) |
|
194 { |
|
195 mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin); |
|
196 } |
|
197 |
|
198 bool |
|
199 nsSMILTimeValueSpec::DependsOnBegin() const |
|
200 { |
|
201 return mParams.mSyncBegin; |
|
202 } |
|
203 |
|
204 void |
|
205 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback) |
|
206 { |
|
207 mReferencedElement.Traverse(aCallback); |
|
208 } |
|
209 |
|
210 void |
|
211 nsSMILTimeValueSpec::Unlink() |
|
212 { |
|
213 UnregisterFromReferencedElement(mReferencedElement.get()); |
|
214 mReferencedElement.Unlink(); |
|
215 } |
|
216 |
|
217 //---------------------------------------------------------------------- |
|
218 // Implementation helpers |
|
219 |
|
220 void |
|
221 nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) |
|
222 { |
|
223 if (aFrom == aTo) |
|
224 return; |
|
225 |
|
226 UnregisterFromReferencedElement(aFrom); |
|
227 |
|
228 switch (mParams.mType) |
|
229 { |
|
230 case nsSMILTimeValueSpecParams::SYNCBASE: |
|
231 { |
|
232 nsSMILTimedElement* to = GetTimedElement(aTo); |
|
233 if (to) { |
|
234 to->AddDependent(*this); |
|
235 } |
|
236 } |
|
237 break; |
|
238 |
|
239 case nsSMILTimeValueSpecParams::EVENT: |
|
240 case nsSMILTimeValueSpecParams::REPEAT: |
|
241 case nsSMILTimeValueSpecParams::ACCESSKEY: |
|
242 RegisterEventListener(aTo); |
|
243 break; |
|
244 |
|
245 default: |
|
246 // not a referencing-type |
|
247 break; |
|
248 } |
|
249 } |
|
250 |
|
251 void |
|
252 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) |
|
253 { |
|
254 if (!aElement) |
|
255 return; |
|
256 |
|
257 if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) { |
|
258 nsSMILTimedElement* timedElement = GetTimedElement(aElement); |
|
259 if (timedElement) { |
|
260 timedElement->RemoveDependent(*this); |
|
261 } |
|
262 mOwner->RemoveInstanceTimesForCreator(this, mIsBegin); |
|
263 } else if (IsEventBased()) { |
|
264 UnregisterEventListener(aElement); |
|
265 } |
|
266 } |
|
267 |
|
268 nsSMILTimedElement* |
|
269 nsSMILTimeValueSpec::GetTimedElement(Element* aElement) |
|
270 { |
|
271 return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ? |
|
272 &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr; |
|
273 } |
|
274 |
|
275 // Indicates whether we're allowed to register an event-listener |
|
276 // when scripting is disabled. |
|
277 bool |
|
278 nsSMILTimeValueSpec::IsWhitelistedEvent() |
|
279 { |
|
280 // The category of (SMIL-specific) "repeat(n)" events are allowed. |
|
281 if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { |
|
282 return true; |
|
283 } |
|
284 |
|
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; |
|
292 } |
|
293 |
|
294 return false; |
|
295 } |
|
296 |
|
297 void |
|
298 nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget) |
|
299 { |
|
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"); |
|
305 |
|
306 if (!aTarget) |
|
307 return; |
|
308 |
|
309 // When script is disabled, only allow registration for whitelisted events. |
|
310 if (!aTarget->GetOwnerDocument()->IsScriptEnabled() && |
|
311 !IsWhitelistedEvent()) { |
|
312 return; |
|
313 } |
|
314 |
|
315 if (!mEventListener) { |
|
316 mEventListener = new EventListener(this); |
|
317 } |
|
318 |
|
319 EventListenerManager* elm = GetEventListenerManager(aTarget); |
|
320 if (!elm) |
|
321 return; |
|
322 |
|
323 elm->AddEventListenerByType(mEventListener, |
|
324 nsDependentAtomString(mParams.mEventSymbol), |
|
325 AllEventsAtSystemGroupBubble()); |
|
326 } |
|
327 |
|
328 void |
|
329 nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget) |
|
330 { |
|
331 if (!aTarget || !mEventListener) |
|
332 return; |
|
333 |
|
334 EventListenerManager* elm = GetEventListenerManager(aTarget); |
|
335 if (!elm) |
|
336 return; |
|
337 |
|
338 elm->RemoveEventListenerByType(mEventListener, |
|
339 nsDependentAtomString(mParams.mEventSymbol), |
|
340 AllEventsAtSystemGroupBubble()); |
|
341 } |
|
342 |
|
343 EventListenerManager* |
|
344 nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget) |
|
345 { |
|
346 NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager"); |
|
347 |
|
348 nsCOMPtr<EventTarget> target; |
|
349 |
|
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; |
|
360 } |
|
361 if (!target) |
|
362 return nullptr; |
|
363 |
|
364 return target->GetOrCreateListenerManager(); |
|
365 } |
|
366 |
|
367 void |
|
368 nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent) |
|
369 { |
|
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"); |
|
374 |
|
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; |
|
381 |
|
382 if (!CheckEventDetail(aEvent)) |
|
383 return; |
|
384 |
|
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; |
|
390 } |
|
391 |
|
392 nsRefPtr<nsSMILInstanceTime> newInstance = |
|
393 new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT); |
|
394 mOwner->AddInstanceTime(newInstance, mIsBegin); |
|
395 } |
|
396 |
|
397 bool |
|
398 nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent) |
|
399 { |
|
400 switch (mParams.mType) |
|
401 { |
|
402 case nsSMILTimeValueSpecParams::REPEAT: |
|
403 return CheckRepeatEventDetail(aEvent); |
|
404 |
|
405 case nsSMILTimeValueSpecParams::ACCESSKEY: |
|
406 return CheckAccessKeyEventDetail(aEvent); |
|
407 |
|
408 default: |
|
409 // nothing to check |
|
410 return true; |
|
411 } |
|
412 } |
|
413 |
|
414 bool |
|
415 nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent) |
|
416 { |
|
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; |
|
421 } |
|
422 |
|
423 int32_t detail; |
|
424 timeEvent->GetDetail(&detail); |
|
425 return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey; |
|
426 } |
|
427 |
|
428 bool |
|
429 nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent) |
|
430 { |
|
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; |
|
435 } |
|
436 |
|
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; |
|
446 |
|
447 uint32_t code; |
|
448 keyEvent->GetCharCode(&code); |
|
449 if (code) |
|
450 return code == mParams.mRepeatIterationOrAccessKey; |
|
451 |
|
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; |
|
462 |
|
463 keyEvent->GetKeyCode(&code); |
|
464 switch (code) |
|
465 { |
|
466 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: |
|
467 return mParams.mRepeatIterationOrAccessKey == 0x08; |
|
468 |
|
469 case nsIDOMKeyEvent::DOM_VK_RETURN: |
|
470 return mParams.mRepeatIterationOrAccessKey == 0x0A || |
|
471 mParams.mRepeatIterationOrAccessKey == 0x0D; |
|
472 |
|
473 case nsIDOMKeyEvent::DOM_VK_ESCAPE: |
|
474 return mParams.mRepeatIterationOrAccessKey == 0x1B; |
|
475 |
|
476 case nsIDOMKeyEvent::DOM_VK_DELETE: |
|
477 return mParams.mRepeatIterationOrAccessKey == 0x7F; |
|
478 |
|
479 default: |
|
480 return false; |
|
481 } |
|
482 } |
|
483 |
|
484 nsSMILTimeValue |
|
485 nsSMILTimeValueSpec::ConvertBetweenTimeContainers( |
|
486 const nsSMILTimeValue& aSrcTime, |
|
487 const nsSMILTimeContainer* aSrcContainer) |
|
488 { |
|
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; |
|
493 |
|
494 // Convert from source time container to our parent time container |
|
495 const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer(); |
|
496 if (dstContainer == aSrcContainer) |
|
497 return aSrcTime; |
|
498 |
|
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 |
|
503 |
|
504 nsSMILTimeValue docTime = |
|
505 aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis()); |
|
506 |
|
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; |
|
511 |
|
512 NS_ABORT_IF_FALSE(docTime.IsDefinite(), |
|
513 "ContainerToParentTime gave us an unresolved or indefinite time"); |
|
514 |
|
515 return dstContainer->ParentToContainerTime(docTime.GetMillis()); |
|
516 } |
|
517 |
|
518 bool |
|
519 nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const |
|
520 { |
|
521 // indefinite + offset = indefinite. Likewise for unresolved times. |
|
522 if (!aTime.IsDefinite()) { |
|
523 return true; |
|
524 } |
|
525 |
|
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; |
|
531 } |
|
532 aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis()); |
|
533 return true; |
|
534 } |