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