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