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/. */
7 #include "mozilla/EventListenerManager.h"
8 #include "mozilla/SMILInstanceTime.h"
9 #include "mozilla/SMILInterval.h"
10 #include "mozilla/SMILParserUtils.h"
11 #include "mozilla/SMILTimeContainer.h"
12 #include "mozilla/SMILTimedElement.h"
13 #include "mozilla/SMILTimeValueSpec.h"
14 #include "mozilla/SMILTimeValue.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/Event.h"
17 #include "mozilla/dom/SVGAnimationElement.h"
18 #include "mozilla/dom/TimeEvent.h"
22 using namespace mozilla::dom
;
26 //----------------------------------------------------------------------
27 // Nested class: EventListener
29 NS_IMPL_ISUPPORTS(SMILTimeValueSpec::EventListener
, nsIDOMEventListener
)
32 SMILTimeValueSpec::EventListener::HandleEvent(Event
* aEvent
) {
34 mSpec
->HandleEvent(aEvent
);
39 //----------------------------------------------------------------------
42 SMILTimeValueSpec::SMILTimeValueSpec(SMILTimedElement
& aOwner
, bool aIsBegin
)
43 : mOwner(&aOwner
), mIsBegin(aIsBegin
), mReferencedElement(this) {}
45 SMILTimeValueSpec::~SMILTimeValueSpec() {
46 UnregisterFromReferencedElement(mReferencedElement
.get());
48 mEventListener
->Disconnect();
49 mEventListener
= nullptr;
53 nsresult
SMILTimeValueSpec::SetSpec(const nsAString
& aStringSpec
,
54 Element
& aContextElement
) {
55 SMILTimeValueSpecParams params
;
57 if (!SMILParserUtils::ParseTimeValueSpecParams(aStringSpec
, params
))
58 return NS_ERROR_FAILURE
;
62 // According to SMIL 3.0:
63 // The special value "indefinite" does not yield an instance time in the
64 // begin list. It will, however yield a single instance with the value
65 // "indefinite" in an end list. This value is not removed by a reset.
66 if (mParams
.mType
== SMILTimeValueSpecParams::OFFSET
||
67 (!mIsBegin
&& mParams
.mType
== SMILTimeValueSpecParams::INDEFINITE
)) {
68 mOwner
->AddInstanceTime(new SMILInstanceTime(mParams
.mOffset
), mIsBegin
);
71 // Fill in the event symbol to simplify handling later
72 if (mParams
.mType
== SMILTimeValueSpecParams::REPEAT
) {
73 mParams
.mEventSymbol
= nsGkAtoms::repeatEvent
;
76 ResolveReferences(aContextElement
);
81 void SMILTimeValueSpec::ResolveReferences(Element
& aContextElement
) {
82 if (mParams
.mType
!= SMILTimeValueSpecParams::SYNCBASE
&& !IsEventBased()) {
86 // If we're not bound to the document yet, don't worry, we'll get called again
88 if (!aContextElement
.IsInComposedDoc()) return;
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
93 RefPtr
<Element
> oldReferencedElement
= mReferencedElement
.get();
95 if (mParams
.mDependentElemID
) {
96 mReferencedElement
.ResetWithID(aContextElement
, mParams
.mDependentElemID
);
97 } else if (mParams
.mType
== SMILTimeValueSpecParams::EVENT
) {
98 Element
* target
= mOwner
->GetTargetElement();
99 mReferencedElement
.ResetWithElement(target
);
101 MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
103 UpdateReferencedElement(oldReferencedElement
, mReferencedElement
.get());
106 bool SMILTimeValueSpec::IsEventBased() const {
107 return mParams
.mType
== SMILTimeValueSpecParams::EVENT
||
108 mParams
.mType
== SMILTimeValueSpecParams::REPEAT
;
111 void SMILTimeValueSpec::HandleNewInterval(
112 SMILInterval
& aInterval
, const SMILTimeContainer
* aSrcContainer
) {
113 const SMILInstanceTime
& baseInstance
=
114 mParams
.mSyncBegin
? *aInterval
.Begin() : *aInterval
.End();
115 SMILTimeValue newTime
=
116 ConvertBetweenTimeContainers(baseInstance
.Time(), aSrcContainer
);
119 if (!ApplyOffset(newTime
)) {
120 NS_WARNING("New time overflows SMILTime, ignoring");
124 // Create the instance time and register it with the interval
125 RefPtr
<SMILInstanceTime
> newInstance
= new SMILInstanceTime(
126 newTime
, SMILInstanceTime::SOURCE_SYNCBASE
, this, &aInterval
);
127 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
130 void SMILTimeValueSpec::HandleTargetElementChange(Element
* aNewTarget
) {
131 if (!IsEventBased() || mParams
.mDependentElemID
) return;
133 mReferencedElement
.ResetWithElement(aNewTarget
);
136 void SMILTimeValueSpec::HandleChangedInstanceTime(
137 const SMILInstanceTime
& aBaseTime
, const SMILTimeContainer
* aSrcContainer
,
138 SMILInstanceTime
& aInstanceTimeToUpdate
, bool aObjectChanged
) {
139 // If the instance time is fixed (e.g. because it's being used as the begin
140 // time of an active or postactive interval) we just ignore the change.
141 if (aInstanceTimeToUpdate
.IsFixedTime()) return;
143 SMILTimeValue updatedTime
=
144 ConvertBetweenTimeContainers(aBaseTime
.Time(), aSrcContainer
);
147 if (!ApplyOffset(updatedTime
)) {
148 NS_WARNING("Updated time overflows SMILTime, ignoring");
152 // The timed element that owns the instance time does the updating so it can
153 // re-sort its array of instance times more efficiently
154 if (aInstanceTimeToUpdate
.Time() != updatedTime
|| aObjectChanged
) {
155 mOwner
->UpdateInstanceTime(&aInstanceTimeToUpdate
, updatedTime
, mIsBegin
);
159 void SMILTimeValueSpec::HandleDeletedInstanceTime(
160 SMILInstanceTime
& aInstanceTime
) {
161 mOwner
->RemoveInstanceTime(&aInstanceTime
, mIsBegin
);
164 bool SMILTimeValueSpec::DependsOnBegin() const { return mParams
.mSyncBegin
; }
166 void SMILTimeValueSpec::Traverse(
167 nsCycleCollectionTraversalCallback
* aCallback
) {
168 mReferencedElement
.Traverse(aCallback
);
171 void SMILTimeValueSpec::Unlink() {
172 UnregisterFromReferencedElement(mReferencedElement
.get());
173 mReferencedElement
.Unlink();
176 //----------------------------------------------------------------------
177 // Implementation helpers
179 void SMILTimeValueSpec::UpdateReferencedElement(Element
* aFrom
, Element
* aTo
) {
180 if (aFrom
== aTo
) return;
182 UnregisterFromReferencedElement(aFrom
);
184 switch (mParams
.mType
) {
185 case SMILTimeValueSpecParams::SYNCBASE
: {
186 SMILTimedElement
* to
= GetTimedElement(aTo
);
188 to
->AddDependent(*this);
192 case SMILTimeValueSpecParams::EVENT
:
193 case SMILTimeValueSpecParams::REPEAT
:
194 RegisterEventListener(aTo
);
198 // not a referencing-type
203 void SMILTimeValueSpec::UnregisterFromReferencedElement(Element
* aElement
) {
204 if (!aElement
) return;
206 if (mParams
.mType
== SMILTimeValueSpecParams::SYNCBASE
) {
207 SMILTimedElement
* timedElement
= GetTimedElement(aElement
);
209 timedElement
->RemoveDependent(*this);
211 mOwner
->RemoveInstanceTimesForCreator(this, mIsBegin
);
212 } else if (IsEventBased()) {
213 UnregisterEventListener(aElement
);
217 SMILTimedElement
* SMILTimeValueSpec::GetTimedElement(Element
* aElement
) {
218 auto* animationElement
= SVGAnimationElement::FromNodeOrNull(aElement
);
219 return animationElement
? &animationElement
->TimedElement() : nullptr;
222 // Indicates whether we're allowed to register an event-listener
223 // when scripting is disabled.
224 bool SMILTimeValueSpec::IsEventAllowedWhenScriptingIsDisabled() {
225 // The category of (SMIL-specific) "repeat(n)" events are allowed.
226 if (mParams
.mType
== SMILTimeValueSpecParams::REPEAT
) {
230 // A specific list of other SMIL-related events are allowed, too.
231 if (mParams
.mType
== SMILTimeValueSpecParams::EVENT
&&
232 (mParams
.mEventSymbol
== nsGkAtoms::repeat
||
233 mParams
.mEventSymbol
== nsGkAtoms::repeatEvent
||
234 mParams
.mEventSymbol
== nsGkAtoms::beginEvent
||
235 mParams
.mEventSymbol
== nsGkAtoms::endEvent
)) {
242 void SMILTimeValueSpec::RegisterEventListener(Element
* aTarget
) {
243 MOZ_ASSERT(IsEventBased(),
244 "Attempting to register event-listener for unexpected "
245 "SMILTimeValueSpec type");
246 MOZ_ASSERT(mParams
.mEventSymbol
,
247 "Attempting to register event-listener but there is no event "
250 if (!aTarget
) return;
252 // When script is disabled, only allow registration for limited events.
253 if (!aTarget
->GetOwnerDocument()->IsScriptEnabled() &&
254 !IsEventAllowedWhenScriptingIsDisabled()) {
258 if (!mEventListener
) {
259 mEventListener
= new EventListener(this);
262 EventListenerManager
* elm
= aTarget
->GetOrCreateListenerManager();
267 elm
->AddEventListenerByType(mEventListener
,
268 nsDependentAtomString(mParams
.mEventSymbol
),
269 AllEventsAtSystemGroupBubble());
272 void SMILTimeValueSpec::UnregisterEventListener(Element
* aTarget
) {
273 if (!aTarget
|| !mEventListener
) {
277 EventListenerManager
* elm
= aTarget
->GetOrCreateListenerManager();
282 elm
->RemoveEventListenerByType(mEventListener
,
283 nsDependentAtomString(mParams
.mEventSymbol
),
284 AllEventsAtSystemGroupBubble());
287 void SMILTimeValueSpec::HandleEvent(Event
* aEvent
) {
288 MOZ_ASSERT(mEventListener
, "Got event without an event listener");
289 MOZ_ASSERT(IsEventBased(), "Got event for non-event SMILTimeValueSpec");
290 MOZ_ASSERT(aEvent
, "No event supplied");
292 // XXX In the long run we should get the time from the event itself which will
293 // store the time in global document time which we'll need to convert to our
295 SMILTimeContainer
* container
= mOwner
->GetTimeContainer();
296 if (!container
) return;
298 if (mParams
.mType
== SMILTimeValueSpecParams::REPEAT
&&
299 !CheckRepeatEventDetail(aEvent
)) {
303 SMILTime currentTime
= container
->GetCurrentTimeAsSMILTime();
304 SMILTimeValue
newTime(currentTime
);
305 if (!ApplyOffset(newTime
)) {
306 NS_WARNING("New time generated from event overflows SMILTime, ignoring");
310 RefPtr
<SMILInstanceTime
> newInstance
=
311 new SMILInstanceTime(newTime
, SMILInstanceTime::SOURCE_EVENT
);
312 mOwner
->AddInstanceTime(newInstance
, mIsBegin
);
315 bool SMILTimeValueSpec::CheckRepeatEventDetail(Event
* aEvent
) {
316 TimeEvent
* timeEvent
= aEvent
->AsTimeEvent();
318 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
322 int32_t detail
= timeEvent
->Detail();
323 return detail
> 0 && (uint32_t)detail
== mParams
.mRepeatIteration
;
326 SMILTimeValue
SMILTimeValueSpec::ConvertBetweenTimeContainers(
327 const SMILTimeValue
& aSrcTime
, const SMILTimeContainer
* aSrcContainer
) {
328 // If the source time is either indefinite or unresolved the result is going
330 if (!aSrcTime
.IsDefinite()) return aSrcTime
;
332 // Convert from source time container to our parent time container
333 const SMILTimeContainer
* dstContainer
= mOwner
->GetTimeContainer();
334 if (dstContainer
== aSrcContainer
) return aSrcTime
;
336 // If one of the elements is not attached to a time container then we can't do
337 // any meaningful conversion
338 if (!aSrcContainer
|| !dstContainer
) return SMILTimeValue(); // unresolved
340 SMILTimeValue docTime
=
341 aSrcContainer
->ContainerToParentTime(aSrcTime
.GetMillis());
343 if (docTime
.IsIndefinite())
344 // This will happen if the source container is paused and we have a future
345 // time. Just return the indefinite time.
348 MOZ_ASSERT(docTime
.IsDefinite(),
349 "ContainerToParentTime gave us an unresolved or indefinite time");
351 return dstContainer
->ParentToContainerTime(docTime
.GetMillis());
354 bool SMILTimeValueSpec::ApplyOffset(SMILTimeValue
& aTime
) const {
355 // indefinite + offset = indefinite. Likewise for unresolved times.
356 if (!aTime
.IsDefinite()) {
360 double resultAsDouble
=
361 (double)aTime
.GetMillis() + mParams
.mOffset
.GetMillis();
362 if (resultAsDouble
> double(std::numeric_limits
<SMILTime
>::max()) ||
363 resultAsDouble
< double(std::numeric_limits
<SMILTime
>::min())) {
366 aTime
.SetMillis(aTime
.GetMillis() + mParams
.mOffset
.GetMillis());
370 } // namespace mozilla