1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/smil/nsSMILTimeValueSpec.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,534 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/EventListenerManager.h" 1.10 +#include "mozilla/dom/SVGAnimationElement.h" 1.11 +#include "nsSMILTimeValueSpec.h" 1.12 +#include "nsSMILInterval.h" 1.13 +#include "nsSMILTimeContainer.h" 1.14 +#include "nsSMILTimeValue.h" 1.15 +#include "nsSMILTimedElement.h" 1.16 +#include "nsSMILInstanceTime.h" 1.17 +#include "nsSMILParserUtils.h" 1.18 +#include "nsIDOMKeyEvent.h" 1.19 +#include "nsIDOMTimeEvent.h" 1.20 +#include "nsString.h" 1.21 +#include <limits> 1.22 + 1.23 +using namespace mozilla; 1.24 +using namespace mozilla::dom; 1.25 + 1.26 +//---------------------------------------------------------------------- 1.27 +// Nested class: EventListener 1.28 + 1.29 +NS_IMPL_ISUPPORTS(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener) 1.30 + 1.31 +NS_IMETHODIMP 1.32 +nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent) 1.33 +{ 1.34 + if (mSpec) { 1.35 + mSpec->HandleEvent(aEvent); 1.36 + } 1.37 + return NS_OK; 1.38 +} 1.39 + 1.40 +//---------------------------------------------------------------------- 1.41 +// Implementation 1.42 + 1.43 +nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, 1.44 + bool aIsBegin) 1.45 + : mOwner(&aOwner), 1.46 + mIsBegin(aIsBegin), 1.47 + mReferencedElement(MOZ_THIS_IN_INITIALIZER_LIST()) 1.48 +{ 1.49 +} 1.50 + 1.51 +nsSMILTimeValueSpec::~nsSMILTimeValueSpec() 1.52 +{ 1.53 + UnregisterFromReferencedElement(mReferencedElement.get()); 1.54 + if (mEventListener) { 1.55 + mEventListener->Disconnect(); 1.56 + mEventListener = nullptr; 1.57 + } 1.58 +} 1.59 + 1.60 +nsresult 1.61 +nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec, 1.62 + Element* aContextNode) 1.63 +{ 1.64 + nsSMILTimeValueSpecParams params; 1.65 + 1.66 + if (!nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params)) 1.67 + return NS_ERROR_FAILURE; 1.68 + 1.69 + mParams = params; 1.70 + 1.71 + // According to SMIL 3.0: 1.72 + // The special value "indefinite" does not yield an instance time in the 1.73 + // begin list. It will, however yield a single instance with the value 1.74 + // "indefinite" in an end list. This value is not removed by a reset. 1.75 + if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET || 1.76 + (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) { 1.77 + mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin); 1.78 + } 1.79 + 1.80 + // Fill in the event symbol to simplify handling later 1.81 + if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { 1.82 + mParams.mEventSymbol = nsGkAtoms::repeatEvent; 1.83 + } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { 1.84 + mParams.mEventSymbol = nsGkAtoms::keypress; 1.85 + } 1.86 + 1.87 + ResolveReferences(aContextNode); 1.88 + 1.89 + return NS_OK; 1.90 +} 1.91 + 1.92 +void 1.93 +nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode) 1.94 +{ 1.95 + if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) 1.96 + return; 1.97 + 1.98 + NS_ABORT_IF_FALSE(aContextNode, 1.99 + "null context node for resolving timing references against"); 1.100 + 1.101 + // If we're not bound to the document yet, don't worry, we'll get called again 1.102 + // when that happens 1.103 + if (!aContextNode->IsInDoc()) 1.104 + return; 1.105 + 1.106 + // Hold ref to the old element so that it isn't destroyed in between resetting 1.107 + // the referenced element and using the pointer to update the referenced 1.108 + // element. 1.109 + nsRefPtr<Element> oldReferencedElement = mReferencedElement.get(); 1.110 + 1.111 + if (mParams.mDependentElemID) { 1.112 + mReferencedElement.ResetWithID(aContextNode, 1.113 + nsDependentAtomString(mParams.mDependentElemID)); 1.114 + } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) { 1.115 + Element* target = mOwner->GetTargetElement(); 1.116 + mReferencedElement.ResetWithElement(target); 1.117 + } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { 1.118 + nsIDocument* doc = aContextNode->GetCurrentDoc(); 1.119 + NS_ABORT_IF_FALSE(doc, "We are in the document but current doc is null"); 1.120 + mReferencedElement.ResetWithElement(doc->GetRootElement()); 1.121 + } else { 1.122 + NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID"); 1.123 + } 1.124 + UpdateReferencedElement(oldReferencedElement, mReferencedElement.get()); 1.125 +} 1.126 + 1.127 +bool 1.128 +nsSMILTimeValueSpec::IsEventBased() const 1.129 +{ 1.130 + return mParams.mType == nsSMILTimeValueSpecParams::EVENT || 1.131 + mParams.mType == nsSMILTimeValueSpecParams::REPEAT || 1.132 + mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY; 1.133 +} 1.134 + 1.135 +void 1.136 +nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval, 1.137 + const nsSMILTimeContainer* aSrcContainer) 1.138 +{ 1.139 + const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin 1.140 + ? *aInterval.Begin() : *aInterval.End(); 1.141 + nsSMILTimeValue newTime = 1.142 + ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer); 1.143 + 1.144 + // Apply offset 1.145 + if (!ApplyOffset(newTime)) { 1.146 + NS_WARNING("New time overflows nsSMILTime, ignoring"); 1.147 + return; 1.148 + } 1.149 + 1.150 + // Create the instance time and register it with the interval 1.151 + nsRefPtr<nsSMILInstanceTime> newInstance = 1.152 + new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this, 1.153 + &aInterval); 1.154 + mOwner->AddInstanceTime(newInstance, mIsBegin); 1.155 +} 1.156 + 1.157 +void 1.158 +nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) 1.159 +{ 1.160 + if (!IsEventBased() || mParams.mDependentElemID) 1.161 + return; 1.162 + 1.163 + mReferencedElement.ResetWithElement(aNewTarget); 1.164 +} 1.165 + 1.166 +void 1.167 +nsSMILTimeValueSpec::HandleChangedInstanceTime( 1.168 + const nsSMILInstanceTime& aBaseTime, 1.169 + const nsSMILTimeContainer* aSrcContainer, 1.170 + nsSMILInstanceTime& aInstanceTimeToUpdate, 1.171 + bool aObjectChanged) 1.172 +{ 1.173 + // If the instance time is fixed (e.g. because it's being used as the begin 1.174 + // time of an active or postactive interval) we just ignore the change. 1.175 + if (aInstanceTimeToUpdate.IsFixedTime()) 1.176 + return; 1.177 + 1.178 + nsSMILTimeValue updatedTime = 1.179 + ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer); 1.180 + 1.181 + // Apply offset 1.182 + if (!ApplyOffset(updatedTime)) { 1.183 + NS_WARNING("Updated time overflows nsSMILTime, ignoring"); 1.184 + return; 1.185 + } 1.186 + 1.187 + // The timed element that owns the instance time does the updating so it can 1.188 + // re-sort its array of instance times more efficiently 1.189 + if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) { 1.190 + mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin); 1.191 + } 1.192 +} 1.193 + 1.194 +void 1.195 +nsSMILTimeValueSpec::HandleDeletedInstanceTime( 1.196 + nsSMILInstanceTime &aInstanceTime) 1.197 +{ 1.198 + mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin); 1.199 +} 1.200 + 1.201 +bool 1.202 +nsSMILTimeValueSpec::DependsOnBegin() const 1.203 +{ 1.204 + return mParams.mSyncBegin; 1.205 +} 1.206 + 1.207 +void 1.208 +nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback) 1.209 +{ 1.210 + mReferencedElement.Traverse(aCallback); 1.211 +} 1.212 + 1.213 +void 1.214 +nsSMILTimeValueSpec::Unlink() 1.215 +{ 1.216 + UnregisterFromReferencedElement(mReferencedElement.get()); 1.217 + mReferencedElement.Unlink(); 1.218 +} 1.219 + 1.220 +//---------------------------------------------------------------------- 1.221 +// Implementation helpers 1.222 + 1.223 +void 1.224 +nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) 1.225 +{ 1.226 + if (aFrom == aTo) 1.227 + return; 1.228 + 1.229 + UnregisterFromReferencedElement(aFrom); 1.230 + 1.231 + switch (mParams.mType) 1.232 + { 1.233 + case nsSMILTimeValueSpecParams::SYNCBASE: 1.234 + { 1.235 + nsSMILTimedElement* to = GetTimedElement(aTo); 1.236 + if (to) { 1.237 + to->AddDependent(*this); 1.238 + } 1.239 + } 1.240 + break; 1.241 + 1.242 + case nsSMILTimeValueSpecParams::EVENT: 1.243 + case nsSMILTimeValueSpecParams::REPEAT: 1.244 + case nsSMILTimeValueSpecParams::ACCESSKEY: 1.245 + RegisterEventListener(aTo); 1.246 + break; 1.247 + 1.248 + default: 1.249 + // not a referencing-type 1.250 + break; 1.251 + } 1.252 +} 1.253 + 1.254 +void 1.255 +nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) 1.256 +{ 1.257 + if (!aElement) 1.258 + return; 1.259 + 1.260 + if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) { 1.261 + nsSMILTimedElement* timedElement = GetTimedElement(aElement); 1.262 + if (timedElement) { 1.263 + timedElement->RemoveDependent(*this); 1.264 + } 1.265 + mOwner->RemoveInstanceTimesForCreator(this, mIsBegin); 1.266 + } else if (IsEventBased()) { 1.267 + UnregisterEventListener(aElement); 1.268 + } 1.269 +} 1.270 + 1.271 +nsSMILTimedElement* 1.272 +nsSMILTimeValueSpec::GetTimedElement(Element* aElement) 1.273 +{ 1.274 + return aElement && aElement->IsNodeOfType(nsINode::eANIMATION) ? 1.275 + &static_cast<SVGAnimationElement*>(aElement)->TimedElement() : nullptr; 1.276 +} 1.277 + 1.278 +// Indicates whether we're allowed to register an event-listener 1.279 +// when scripting is disabled. 1.280 +bool 1.281 +nsSMILTimeValueSpec::IsWhitelistedEvent() 1.282 +{ 1.283 + // The category of (SMIL-specific) "repeat(n)" events are allowed. 1.284 + if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) { 1.285 + return true; 1.286 + } 1.287 + 1.288 + // A specific list of other SMIL-related events are allowed, too. 1.289 + if (mParams.mType == nsSMILTimeValueSpecParams::EVENT && 1.290 + (mParams.mEventSymbol == nsGkAtoms::repeat || 1.291 + mParams.mEventSymbol == nsGkAtoms::repeatEvent || 1.292 + mParams.mEventSymbol == nsGkAtoms::beginEvent || 1.293 + mParams.mEventSymbol == nsGkAtoms::endEvent)) { 1.294 + return true; 1.295 + } 1.296 + 1.297 + return false; 1.298 +} 1.299 + 1.300 +void 1.301 +nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget) 1.302 +{ 1.303 + NS_ABORT_IF_FALSE(IsEventBased(), 1.304 + "Attempting to register event-listener for unexpected nsSMILTimeValueSpec" 1.305 + " type"); 1.306 + NS_ABORT_IF_FALSE(mParams.mEventSymbol, 1.307 + "Attempting to register event-listener but there is no event name"); 1.308 + 1.309 + if (!aTarget) 1.310 + return; 1.311 + 1.312 + // When script is disabled, only allow registration for whitelisted events. 1.313 + if (!aTarget->GetOwnerDocument()->IsScriptEnabled() && 1.314 + !IsWhitelistedEvent()) { 1.315 + return; 1.316 + } 1.317 + 1.318 + if (!mEventListener) { 1.319 + mEventListener = new EventListener(this); 1.320 + } 1.321 + 1.322 + EventListenerManager* elm = GetEventListenerManager(aTarget); 1.323 + if (!elm) 1.324 + return; 1.325 + 1.326 + elm->AddEventListenerByType(mEventListener, 1.327 + nsDependentAtomString(mParams.mEventSymbol), 1.328 + AllEventsAtSystemGroupBubble()); 1.329 +} 1.330 + 1.331 +void 1.332 +nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget) 1.333 +{ 1.334 + if (!aTarget || !mEventListener) 1.335 + return; 1.336 + 1.337 + EventListenerManager* elm = GetEventListenerManager(aTarget); 1.338 + if (!elm) 1.339 + return; 1.340 + 1.341 + elm->RemoveEventListenerByType(mEventListener, 1.342 + nsDependentAtomString(mParams.mEventSymbol), 1.343 + AllEventsAtSystemGroupBubble()); 1.344 +} 1.345 + 1.346 +EventListenerManager* 1.347 +nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget) 1.348 +{ 1.349 + NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager"); 1.350 + 1.351 + nsCOMPtr<EventTarget> target; 1.352 + 1.353 + if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) { 1.354 + nsIDocument* doc = aTarget->GetCurrentDoc(); 1.355 + if (!doc) 1.356 + return nullptr; 1.357 + nsPIDOMWindow* win = doc->GetWindow(); 1.358 + if (!win) 1.359 + return nullptr; 1.360 + target = do_QueryInterface(win); 1.361 + } else { 1.362 + target = aTarget; 1.363 + } 1.364 + if (!target) 1.365 + return nullptr; 1.366 + 1.367 + return target->GetOrCreateListenerManager(); 1.368 +} 1.369 + 1.370 +void 1.371 +nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent) 1.372 +{ 1.373 + NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener"); 1.374 + NS_ABORT_IF_FALSE(IsEventBased(), 1.375 + "Got event for non-event nsSMILTimeValueSpec"); 1.376 + NS_ABORT_IF_FALSE(aEvent, "No event supplied"); 1.377 + 1.378 + // XXX In the long run we should get the time from the event itself which will 1.379 + // store the time in global document time which we'll need to convert to our 1.380 + // time container 1.381 + nsSMILTimeContainer* container = mOwner->GetTimeContainer(); 1.382 + if (!container) 1.383 + return; 1.384 + 1.385 + if (!CheckEventDetail(aEvent)) 1.386 + return; 1.387 + 1.388 + nsSMILTime currentTime = container->GetCurrentTime(); 1.389 + nsSMILTimeValue newTime(currentTime); 1.390 + if (!ApplyOffset(newTime)) { 1.391 + NS_WARNING("New time generated from event overflows nsSMILTime, ignoring"); 1.392 + return; 1.393 + } 1.394 + 1.395 + nsRefPtr<nsSMILInstanceTime> newInstance = 1.396 + new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT); 1.397 + mOwner->AddInstanceTime(newInstance, mIsBegin); 1.398 +} 1.399 + 1.400 +bool 1.401 +nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent) 1.402 +{ 1.403 + switch (mParams.mType) 1.404 + { 1.405 + case nsSMILTimeValueSpecParams::REPEAT: 1.406 + return CheckRepeatEventDetail(aEvent); 1.407 + 1.408 + case nsSMILTimeValueSpecParams::ACCESSKEY: 1.409 + return CheckAccessKeyEventDetail(aEvent); 1.410 + 1.411 + default: 1.412 + // nothing to check 1.413 + return true; 1.414 + } 1.415 +} 1.416 + 1.417 +bool 1.418 +nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent) 1.419 +{ 1.420 + nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent); 1.421 + if (!timeEvent) { 1.422 + NS_WARNING("Received a repeat event that was not a DOMTimeEvent"); 1.423 + return false; 1.424 + } 1.425 + 1.426 + int32_t detail; 1.427 + timeEvent->GetDetail(&detail); 1.428 + return detail > 0 && (uint32_t)detail == mParams.mRepeatIterationOrAccessKey; 1.429 +} 1.430 + 1.431 +bool 1.432 +nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent) 1.433 +{ 1.434 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent); 1.435 + if (!keyEvent) { 1.436 + NS_WARNING("Received an accesskey event that was not a DOMKeyEvent"); 1.437 + return false; 1.438 + } 1.439 + 1.440 + // Ignore the key event if any modifier keys are pressed UNLESS we're matching 1.441 + // on the charCode in which case we ignore the state of the shift and alt keys 1.442 + // since they might be needed to generate the character in question. 1.443 + bool isCtrl; 1.444 + bool isMeta; 1.445 + keyEvent->GetCtrlKey(&isCtrl); 1.446 + keyEvent->GetMetaKey(&isMeta); 1.447 + if (isCtrl || isMeta) 1.448 + return false; 1.449 + 1.450 + uint32_t code; 1.451 + keyEvent->GetCharCode(&code); 1.452 + if (code) 1.453 + return code == mParams.mRepeatIterationOrAccessKey; 1.454 + 1.455 + // Only match on the keyCode if it corresponds to some ASCII character that 1.456 + // does not produce a charCode. 1.457 + // In this case we can safely bail out if either alt or shift is pressed since 1.458 + // they won't already be incorporated into the keyCode unlike the charCode. 1.459 + bool isAlt; 1.460 + bool isShift; 1.461 + keyEvent->GetAltKey(&isAlt); 1.462 + keyEvent->GetShiftKey(&isShift); 1.463 + if (isAlt || isShift) 1.464 + return false; 1.465 + 1.466 + keyEvent->GetKeyCode(&code); 1.467 + switch (code) 1.468 + { 1.469 + case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: 1.470 + return mParams.mRepeatIterationOrAccessKey == 0x08; 1.471 + 1.472 + case nsIDOMKeyEvent::DOM_VK_RETURN: 1.473 + return mParams.mRepeatIterationOrAccessKey == 0x0A || 1.474 + mParams.mRepeatIterationOrAccessKey == 0x0D; 1.475 + 1.476 + case nsIDOMKeyEvent::DOM_VK_ESCAPE: 1.477 + return mParams.mRepeatIterationOrAccessKey == 0x1B; 1.478 + 1.479 + case nsIDOMKeyEvent::DOM_VK_DELETE: 1.480 + return mParams.mRepeatIterationOrAccessKey == 0x7F; 1.481 + 1.482 + default: 1.483 + return false; 1.484 + } 1.485 +} 1.486 + 1.487 +nsSMILTimeValue 1.488 +nsSMILTimeValueSpec::ConvertBetweenTimeContainers( 1.489 + const nsSMILTimeValue& aSrcTime, 1.490 + const nsSMILTimeContainer* aSrcContainer) 1.491 +{ 1.492 + // If the source time is either indefinite or unresolved the result is going 1.493 + // to be the same 1.494 + if (!aSrcTime.IsDefinite()) 1.495 + return aSrcTime; 1.496 + 1.497 + // Convert from source time container to our parent time container 1.498 + const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer(); 1.499 + if (dstContainer == aSrcContainer) 1.500 + return aSrcTime; 1.501 + 1.502 + // If one of the elements is not attached to a time container then we can't do 1.503 + // any meaningful conversion 1.504 + if (!aSrcContainer || !dstContainer) 1.505 + return nsSMILTimeValue(); // unresolved 1.506 + 1.507 + nsSMILTimeValue docTime = 1.508 + aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis()); 1.509 + 1.510 + if (docTime.IsIndefinite()) 1.511 + // This will happen if the source container is paused and we have a future 1.512 + // time. Just return the indefinite time. 1.513 + return docTime; 1.514 + 1.515 + NS_ABORT_IF_FALSE(docTime.IsDefinite(), 1.516 + "ContainerToParentTime gave us an unresolved or indefinite time"); 1.517 + 1.518 + return dstContainer->ParentToContainerTime(docTime.GetMillis()); 1.519 +} 1.520 + 1.521 +bool 1.522 +nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const 1.523 +{ 1.524 + // indefinite + offset = indefinite. Likewise for unresolved times. 1.525 + if (!aTime.IsDefinite()) { 1.526 + return true; 1.527 + } 1.528 + 1.529 + double resultAsDouble = 1.530 + (double)aTime.GetMillis() + mParams.mOffset.GetMillis(); 1.531 + if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() || 1.532 + resultAsDouble < std::numeric_limits<nsSMILTime>::min()) { 1.533 + return false; 1.534 + } 1.535 + aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis()); 1.536 + return true; 1.537 +}