/src/mozilla-central/dom/smil/nsSMILTimeValueSpec.cpp
Line | Count | Source (jump to first uncovered line) |
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/Event.h" |
9 | | #include "mozilla/dom/SVGAnimationElement.h" |
10 | | #include "mozilla/dom/TimeEvent.h" |
11 | | #include "nsSMILTimeValueSpec.h" |
12 | | #include "nsSMILInterval.h" |
13 | | #include "nsSMILTimeContainer.h" |
14 | | #include "nsSMILTimeValue.h" |
15 | | #include "nsSMILTimedElement.h" |
16 | | #include "nsSMILInstanceTime.h" |
17 | | #include "nsSMILParserUtils.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 | | NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener) |
28 | | |
29 | | NS_IMETHODIMP |
30 | | nsSMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent) |
31 | 0 | { |
32 | 0 | if (mSpec) { |
33 | 0 | mSpec->HandleEvent(aEvent); |
34 | 0 | } |
35 | 0 | return NS_OK; |
36 | 0 | } |
37 | | |
38 | | //---------------------------------------------------------------------- |
39 | | // Implementation |
40 | | |
41 | | nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, |
42 | | bool aIsBegin) |
43 | | : mOwner(&aOwner), |
44 | | mIsBegin(aIsBegin), |
45 | | mReferencedElement(this) |
46 | 0 | { |
47 | 0 | } |
48 | | |
49 | | nsSMILTimeValueSpec::~nsSMILTimeValueSpec() |
50 | 0 | { |
51 | 0 | UnregisterFromReferencedElement(mReferencedElement.get()); |
52 | 0 | if (mEventListener) { |
53 | 0 | mEventListener->Disconnect(); |
54 | 0 | mEventListener = nullptr; |
55 | 0 | } |
56 | 0 | } |
57 | | |
58 | | nsresult |
59 | | nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec, |
60 | | Element& aContextElement) |
61 | 0 | { |
62 | 0 | nsSMILTimeValueSpecParams params; |
63 | 0 |
|
64 | 0 | if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params)) |
65 | 0 | return NS_ERROR_FAILURE; |
66 | 0 | |
67 | 0 | mParams = params; |
68 | 0 |
|
69 | 0 | // According to SMIL 3.0: |
70 | 0 | // The special value "indefinite" does not yield an instance time in the |
71 | 0 | // begin list. It will, however yield a single instance with the value |
72 | 0 | // "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 | 0 | } |
77 | 0 |
|
78 | 0 | // Fill in the event symbol to simplify handling later |
79 | 0 | if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { |
80 | 0 | mParams.mEventSymbol = nsGkAtoms::repeatEvent; |
81 | 0 | } |
82 | 0 |
|
83 | 0 | ResolveReferences(aContextElement); |
84 | 0 |
|
85 | 0 | return NS_OK; |
86 | 0 | } |
87 | | |
88 | | void |
89 | | nsSMILTimeValueSpec::ResolveReferences(Element& aContextElement) |
90 | 0 | { |
91 | 0 | if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) { |
92 | 0 | return; |
93 | 0 | } |
94 | 0 | |
95 | 0 | // If we're not bound to the document yet, don't worry, we'll get called again |
96 | 0 | // when that happens |
97 | 0 | if (!aContextElement.IsInComposedDoc()) |
98 | 0 | return; |
99 | 0 | |
100 | 0 | // Hold ref to the old element so that it isn't destroyed in between resetting |
101 | 0 | // the referenced element and using the pointer to update the referenced |
102 | 0 | // element. |
103 | 0 | RefPtr<Element> oldReferencedElement = mReferencedElement.get(); |
104 | 0 |
|
105 | 0 | if (mParams.mDependentElemID) { |
106 | 0 | mReferencedElement.ResetWithID(aContextElement, mParams.mDependentElemID); |
107 | 0 | } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) { |
108 | 0 | Element* target = mOwner->GetTargetElement(); |
109 | 0 | mReferencedElement.ResetWithElement(target); |
110 | 0 | } else { |
111 | 0 | MOZ_ASSERT(false, "Syncbase or repeat spec without ID"); |
112 | 0 | } |
113 | 0 | UpdateReferencedElement(oldReferencedElement, mReferencedElement.get()); |
114 | 0 | } |
115 | | |
116 | | bool |
117 | | nsSMILTimeValueSpec::IsEventBased() const |
118 | 0 | { |
119 | 0 | return mParams.mType == nsSMILTimeValueSpecParams::EVENT || |
120 | 0 | mParams.mType == nsSMILTimeValueSpecParams::REPEAT; |
121 | 0 | } |
122 | | |
123 | | void |
124 | | nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval, |
125 | | const nsSMILTimeContainer* aSrcContainer) |
126 | 0 | { |
127 | 0 | const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin |
128 | 0 | ? *aInterval.Begin() : *aInterval.End(); |
129 | 0 | nsSMILTimeValue newTime = |
130 | 0 | ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer); |
131 | 0 |
|
132 | 0 | // Apply offset |
133 | 0 | if (!ApplyOffset(newTime)) { |
134 | 0 | NS_WARNING("New time overflows nsSMILTime, ignoring"); |
135 | 0 | return; |
136 | 0 | } |
137 | 0 |
|
138 | 0 | // Create the instance time and register it with the interval |
139 | 0 | RefPtr<nsSMILInstanceTime> newInstance = |
140 | 0 | new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this, |
141 | 0 | &aInterval); |
142 | 0 | mOwner->AddInstanceTime(newInstance, mIsBegin); |
143 | 0 | } |
144 | | |
145 | | void |
146 | | nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) |
147 | 0 | { |
148 | 0 | if (!IsEventBased() || mParams.mDependentElemID) |
149 | 0 | return; |
150 | 0 | |
151 | 0 | mReferencedElement.ResetWithElement(aNewTarget); |
152 | 0 | } |
153 | | |
154 | | void |
155 | | nsSMILTimeValueSpec::HandleChangedInstanceTime( |
156 | | const nsSMILInstanceTime& aBaseTime, |
157 | | const nsSMILTimeContainer* aSrcContainer, |
158 | | nsSMILInstanceTime& aInstanceTimeToUpdate, |
159 | | bool aObjectChanged) |
160 | 0 | { |
161 | 0 | // If the instance time is fixed (e.g. because it's being used as the begin |
162 | 0 | // time of an active or postactive interval) we just ignore the change. |
163 | 0 | if (aInstanceTimeToUpdate.IsFixedTime()) |
164 | 0 | return; |
165 | 0 | |
166 | 0 | nsSMILTimeValue updatedTime = |
167 | 0 | ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer); |
168 | 0 |
|
169 | 0 | // Apply offset |
170 | 0 | if (!ApplyOffset(updatedTime)) { |
171 | 0 | NS_WARNING("Updated time overflows nsSMILTime, ignoring"); |
172 | 0 | return; |
173 | 0 | } |
174 | 0 |
|
175 | 0 | // The timed element that owns the instance time does the updating so it can |
176 | 0 | // re-sort its array of instance times more efficiently |
177 | 0 | if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) { |
178 | 0 | mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin); |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | | void |
183 | | nsSMILTimeValueSpec::HandleDeletedInstanceTime( |
184 | | nsSMILInstanceTime &aInstanceTime) |
185 | 0 | { |
186 | 0 | mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin); |
187 | 0 | } |
188 | | |
189 | | bool |
190 | | nsSMILTimeValueSpec::DependsOnBegin() const |
191 | 0 | { |
192 | 0 | return mParams.mSyncBegin; |
193 | 0 | } |
194 | | |
195 | | void |
196 | | nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback) |
197 | 0 | { |
198 | 0 | mReferencedElement.Traverse(aCallback); |
199 | 0 | } |
200 | | |
201 | | void |
202 | | nsSMILTimeValueSpec::Unlink() |
203 | 0 | { |
204 | 0 | UnregisterFromReferencedElement(mReferencedElement.get()); |
205 | 0 | mReferencedElement.Unlink(); |
206 | 0 | } |
207 | | |
208 | | //---------------------------------------------------------------------- |
209 | | // Implementation helpers |
210 | | |
211 | | void |
212 | | nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) |
213 | 0 | { |
214 | 0 | if (aFrom == aTo) |
215 | 0 | return; |
216 | 0 | |
217 | 0 | UnregisterFromReferencedElement(aFrom); |
218 | 0 |
|
219 | 0 | switch (mParams.mType) |
220 | 0 | { |
221 | 0 | case nsSMILTimeValueSpecParams::SYNCBASE: |
222 | 0 | { |
223 | 0 | nsSMILTimedElement* to = GetTimedElement(aTo); |
224 | 0 | if (to) { |
225 | 0 | to->AddDependent(*this); |
226 | 0 | } |
227 | 0 | } |
228 | 0 | break; |
229 | 0 |
|
230 | 0 | case nsSMILTimeValueSpecParams::EVENT: |
231 | 0 | case nsSMILTimeValueSpecParams::REPEAT: |
232 | 0 | RegisterEventListener(aTo); |
233 | 0 | break; |
234 | 0 |
|
235 | 0 | default: |
236 | 0 | // not a referencing-type |
237 | 0 | break; |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | | void |
242 | | nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) |
243 | 0 | { |
244 | 0 | if (!aElement) |
245 | 0 | return; |
246 | 0 | |
247 | 0 | if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) { |
248 | 0 | nsSMILTimedElement* timedElement = GetTimedElement(aElement); |
249 | 0 | if (timedElement) { |
250 | 0 | timedElement->RemoveDependent(*this); |
251 | 0 | } |
252 | 0 | mOwner->RemoveInstanceTimesForCreator(this, mIsBegin); |
253 | 0 | } else if (IsEventBased()) { |
254 | 0 | UnregisterEventListener(aElement); |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | | nsSMILTimedElement* |
259 | | nsSMILTimeValueSpec::GetTimedElement(Element* aElement) |
260 | 0 | { |
261 | 0 | return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ? |
262 | 0 | &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr; |
263 | 0 | } |
264 | | |
265 | | // Indicates whether we're allowed to register an event-listener |
266 | | // when scripting is disabled. |
267 | | bool |
268 | | nsSMILTimeValueSpec::IsWhitelistedEvent() |
269 | 0 | { |
270 | 0 | // The category of (SMIL-specific) "repeat(n)" events are allowed. |
271 | 0 | if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { |
272 | 0 | return true; |
273 | 0 | } |
274 | 0 | |
275 | 0 | // A specific list of other SMIL-related events are allowed, too. |
276 | 0 | if (mParams.mType == nsSMILTimeValueSpecParams::EVENT && |
277 | 0 | (mParams.mEventSymbol == nsGkAtoms::repeat || |
278 | 0 | mParams.mEventSymbol == nsGkAtoms::repeatEvent || |
279 | 0 | mParams.mEventSymbol == nsGkAtoms::beginEvent || |
280 | 0 | mParams.mEventSymbol == nsGkAtoms::endEvent)) { |
281 | 0 | return true; |
282 | 0 | } |
283 | 0 | |
284 | 0 | return false; |
285 | 0 | } |
286 | | |
287 | | void |
288 | | nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget) |
289 | 0 | { |
290 | 0 | MOZ_ASSERT(IsEventBased(), |
291 | 0 | "Attempting to register event-listener for unexpected " |
292 | 0 | "nsSMILTimeValueSpec type"); |
293 | 0 | MOZ_ASSERT(mParams.mEventSymbol, |
294 | 0 | "Attempting to register event-listener but there is no event " |
295 | 0 | "name"); |
296 | 0 |
|
297 | 0 | if (!aTarget) |
298 | 0 | return; |
299 | 0 | |
300 | 0 | // When script is disabled, only allow registration for whitelisted events. |
301 | 0 | if (!aTarget->GetOwnerDocument()->IsScriptEnabled() && |
302 | 0 | !IsWhitelistedEvent()) { |
303 | 0 | return; |
304 | 0 | } |
305 | 0 | |
306 | 0 | if (!mEventListener) { |
307 | 0 | mEventListener = new EventListener(this); |
308 | 0 | } |
309 | 0 |
|
310 | 0 | EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); |
311 | 0 | if (!elm) { |
312 | 0 | return; |
313 | 0 | } |
314 | 0 | |
315 | 0 | elm->AddEventListenerByType(mEventListener, |
316 | 0 | nsDependentAtomString(mParams.mEventSymbol), |
317 | 0 | AllEventsAtSystemGroupBubble()); |
318 | 0 | } |
319 | | |
320 | | void |
321 | | nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget) |
322 | 0 | { |
323 | 0 | if (!aTarget || !mEventListener) { |
324 | 0 | return; |
325 | 0 | } |
326 | 0 | |
327 | 0 | EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); |
328 | 0 | if (!elm) { |
329 | 0 | return; |
330 | 0 | } |
331 | 0 | |
332 | 0 | elm->RemoveEventListenerByType(mEventListener, |
333 | 0 | nsDependentAtomString(mParams.mEventSymbol), |
334 | 0 | AllEventsAtSystemGroupBubble()); |
335 | 0 | } |
336 | | |
337 | | void |
338 | | nsSMILTimeValueSpec::HandleEvent(Event* aEvent) |
339 | 0 | { |
340 | 0 | MOZ_ASSERT(mEventListener, "Got event without an event listener"); |
341 | 0 | MOZ_ASSERT(IsEventBased(), |
342 | 0 | "Got event for non-event nsSMILTimeValueSpec"); |
343 | 0 | MOZ_ASSERT(aEvent, "No event supplied"); |
344 | 0 |
|
345 | 0 | // XXX In the long run we should get the time from the event itself which will |
346 | 0 | // store the time in global document time which we'll need to convert to our |
347 | 0 | // time container |
348 | 0 | nsSMILTimeContainer* container = mOwner->GetTimeContainer(); |
349 | 0 | if (!container) |
350 | 0 | return; |
351 | 0 | |
352 | 0 | if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT && |
353 | 0 | !CheckRepeatEventDetail(aEvent)) { |
354 | 0 | return; |
355 | 0 | } |
356 | 0 | |
357 | 0 | nsSMILTime currentTime = container->GetCurrentTime(); |
358 | 0 | nsSMILTimeValue newTime(currentTime); |
359 | 0 | if (!ApplyOffset(newTime)) { |
360 | 0 | NS_WARNING("New time generated from event overflows nsSMILTime, ignoring"); |
361 | 0 | return; |
362 | 0 | } |
363 | 0 |
|
364 | 0 | RefPtr<nsSMILInstanceTime> newInstance = |
365 | 0 | new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT); |
366 | 0 | mOwner->AddInstanceTime(newInstance, mIsBegin); |
367 | 0 | } |
368 | | |
369 | | bool |
370 | | nsSMILTimeValueSpec::CheckRepeatEventDetail(Event *aEvent) |
371 | 0 | { |
372 | 0 | TimeEvent* timeEvent = aEvent->AsTimeEvent(); |
373 | 0 | if (!timeEvent) { |
374 | 0 | NS_WARNING("Received a repeat event that was not a DOMTimeEvent"); |
375 | 0 | return false; |
376 | 0 | } |
377 | 0 |
|
378 | 0 | int32_t detail = timeEvent->Detail(); |
379 | 0 | return detail > 0 && (uint32_t)detail == mParams.mRepeatIteration; |
380 | 0 | } |
381 | | |
382 | | nsSMILTimeValue |
383 | | nsSMILTimeValueSpec::ConvertBetweenTimeContainers( |
384 | | const nsSMILTimeValue& aSrcTime, |
385 | | const nsSMILTimeContainer* aSrcContainer) |
386 | 0 | { |
387 | 0 | // If the source time is either indefinite or unresolved the result is going |
388 | 0 | // to be the same |
389 | 0 | if (!aSrcTime.IsDefinite()) |
390 | 0 | return aSrcTime; |
391 | 0 | |
392 | 0 | // Convert from source time container to our parent time container |
393 | 0 | const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer(); |
394 | 0 | if (dstContainer == aSrcContainer) |
395 | 0 | return aSrcTime; |
396 | 0 | |
397 | 0 | // If one of the elements is not attached to a time container then we can't do |
398 | 0 | // any meaningful conversion |
399 | 0 | if (!aSrcContainer || !dstContainer) |
400 | 0 | return nsSMILTimeValue(); // unresolved |
401 | 0 | |
402 | 0 | nsSMILTimeValue docTime = |
403 | 0 | aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis()); |
404 | 0 |
|
405 | 0 | if (docTime.IsIndefinite()) |
406 | 0 | // This will happen if the source container is paused and we have a future |
407 | 0 | // time. Just return the indefinite time. |
408 | 0 | return docTime; |
409 | 0 | |
410 | 0 | MOZ_ASSERT(docTime.IsDefinite(), |
411 | 0 | "ContainerToParentTime gave us an unresolved or indefinite time"); |
412 | 0 |
|
413 | 0 | return dstContainer->ParentToContainerTime(docTime.GetMillis()); |
414 | 0 | } |
415 | | |
416 | | bool |
417 | | nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const |
418 | 0 | { |
419 | 0 | // indefinite + offset = indefinite. Likewise for unresolved times. |
420 | 0 | if (!aTime.IsDefinite()) { |
421 | 0 | return true; |
422 | 0 | } |
423 | 0 | |
424 | 0 | double resultAsDouble = |
425 | 0 | (double)aTime.GetMillis() + mParams.mOffset.GetMillis(); |
426 | 0 | if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() || |
427 | 0 | resultAsDouble < std::numeric_limits<nsSMILTime>::min()) { |
428 | 0 | return false; |
429 | 0 | } |
430 | 0 | aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis()); |
431 | 0 | return true; |
432 | 0 | } |