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