Backed out 2 changesets (bug 1539720) for causing caret related failures. CLOSED...
[gecko.git] / dom / smil / SMILTimeValueSpec.cpp
blob177c813bbcb2b3c89ac6de2acf22bf6fe4f42027
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"
19 #include "nsString.h"
20 #include <limits>
22 using namespace mozilla::dom;
24 namespace mozilla {
26 //----------------------------------------------------------------------
27 // Nested class: EventListener
29 NS_IMPL_ISUPPORTS(SMILTimeValueSpec::EventListener, nsIDOMEventListener)
31 NS_IMETHODIMP
32 SMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent) {
33 if (mSpec) {
34 mSpec->HandleEvent(aEvent);
36 return NS_OK;
39 //----------------------------------------------------------------------
40 // Implementation
42 SMILTimeValueSpec::SMILTimeValueSpec(SMILTimedElement& aOwner, bool aIsBegin)
43 : mOwner(&aOwner), mIsBegin(aIsBegin), mReferencedElement(this) {}
45 SMILTimeValueSpec::~SMILTimeValueSpec() {
46 UnregisterFromReferencedElement(mReferencedElement.get());
47 if (mEventListener) {
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;
60 mParams = params;
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);
78 return NS_OK;
81 void SMILTimeValueSpec::ResolveReferences(Element& aContextElement) {
82 if (mParams.mType != SMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
83 return;
86 // If we're not bound to the document yet, don't worry, we'll get called again
87 // when that happens
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
92 // element.
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);
100 } else {
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);
118 // Apply offset
119 if (!ApplyOffset(newTime)) {
120 NS_WARNING("New time overflows SMILTime, ignoring");
121 return;
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);
146 // Apply offset
147 if (!ApplyOffset(updatedTime)) {
148 NS_WARNING("Updated time overflows SMILTime, ignoring");
149 return;
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);
187 if (to) {
188 to->AddDependent(*this);
190 } break;
192 case SMILTimeValueSpecParams::EVENT:
193 case SMILTimeValueSpecParams::REPEAT:
194 RegisterEventListener(aTo);
195 break;
197 default:
198 // not a referencing-type
199 break;
203 void SMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) {
204 if (!aElement) return;
206 if (mParams.mType == SMILTimeValueSpecParams::SYNCBASE) {
207 SMILTimedElement* timedElement = GetTimedElement(aElement);
208 if (timedElement) {
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) {
227 return true;
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)) {
236 return true;
239 return false;
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 "
248 "name");
250 if (!aTarget) return;
252 // When script is disabled, only allow registration for limited events.
253 if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
254 !IsEventAllowedWhenScriptingIsDisabled()) {
255 return;
258 if (!mEventListener) {
259 mEventListener = new EventListener(this);
262 EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
263 if (!elm) {
264 return;
267 elm->AddEventListenerByType(mEventListener,
268 nsDependentAtomString(mParams.mEventSymbol),
269 AllEventsAtSystemGroupBubble());
272 void SMILTimeValueSpec::UnregisterEventListener(Element* aTarget) {
273 if (!aTarget || !mEventListener) {
274 return;
277 EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
278 if (!elm) {
279 return;
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
294 // time container
295 SMILTimeContainer* container = mOwner->GetTimeContainer();
296 if (!container) return;
298 if (mParams.mType == SMILTimeValueSpecParams::REPEAT &&
299 !CheckRepeatEventDetail(aEvent)) {
300 return;
303 SMILTime currentTime = container->GetCurrentTimeAsSMILTime();
304 SMILTimeValue newTime(currentTime);
305 if (!ApplyOffset(newTime)) {
306 NS_WARNING("New time generated from event overflows SMILTime, ignoring");
307 return;
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();
317 if (!timeEvent) {
318 NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
319 return false;
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
329 // to be the same
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.
346 return docTime;
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()) {
357 return true;
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())) {
364 return false;
366 aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
367 return true;
370 } // namespace mozilla