1 /* 2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann (at) kde.org> 3 * Copyright (C) 2004, 2005, 2006 Rob Buis <buis (at) kde.org> 4 * Copyright (C) 2008 Apple Inc. All rights reserved. 5 * Copyright (C) Research In Motion Limited 2011. All rights reserved. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 */ 22 23 #include "config.h" 24 25 #include "core/svg/SVGAnimateElement.h" 26 27 #include "CSSPropertyNames.h" 28 #include "core/css/CSSParser.h" 29 #include "core/css/StylePropertySet.h" 30 #include "core/dom/QualifiedName.h" 31 #include "core/svg/SVGAnimatedType.h" 32 #include "core/svg/SVGAnimatedTypeAnimator.h" 33 #include "core/svg/SVGAnimatorFactory.h" 34 #include "core/svg/SVGDocumentExtensions.h" 35 36 namespace WebCore { 37 38 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document) 39 : SVGAnimationElement(tagName, document) 40 , m_animatedPropertyType(AnimatedString) 41 { 42 ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag) || hasTagName(SVGNames::animateTransformTag)); 43 ScriptWrappable::init(this); 44 } 45 46 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document) 47 { 48 return adoptRef(new SVGAnimateElement(SVGNames::animateTag, document)); 49 } 50 51 SVGAnimateElement::~SVGAnimateElement() 52 { 53 if (targetElement()) 54 clearAnimatedType(targetElement()); 55 } 56 57 bool SVGAnimateElement::hasValidAttributeType() 58 { 59 SVGElement* targetElement = this->targetElement(); 60 if (!targetElement) 61 return false; 62 63 return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType(); 64 } 65 66 AnimatedPropertyType SVGAnimateElement::determineAnimatedPropertyType(SVGElement* targetElement) const 67 { 68 ASSERT(targetElement); 69 70 Vector<AnimatedPropertyType> propertyTypes; 71 targetElement->animatedPropertyTypeForAttribute(attributeName(), propertyTypes); 72 if (propertyTypes.isEmpty()) 73 return AnimatedUnknown; 74 75 ASSERT(propertyTypes.size() <= 2); 76 AnimatedPropertyType type = propertyTypes[0]; 77 if (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor) 78 return AnimatedUnknown; 79 80 // Animations of transform lists are not allowed for <animate> or <set> 81 // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties 82 if (type == AnimatedTransformList && !hasTagName(SVGNames::animateTransformTag)) 83 return AnimatedUnknown; 84 85 // Fortunately there's just one special case needed here: SVGMarkerElements orientAttr, which 86 // corresponds to SVGAnimatedAngle orientAngle and SVGAnimatedEnumeration orientType. We have to 87 // figure out whose value to change here. 88 if (targetElement->hasTagName(SVGNames::markerTag) && type == AnimatedAngle) { 89 ASSERT(propertyTypes.size() == 2); 90 ASSERT(propertyTypes[0] == AnimatedAngle); 91 ASSERT(propertyTypes[1] == AnimatedEnumeration); 92 } else if (propertyTypes.size() == 2) 93 ASSERT(propertyTypes[0] == propertyTypes[1]); 94 95 return type; 96 } 97 98 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement) 99 { 100 ASSERT(resultElement); 101 SVGElement* targetElement = this->targetElement(); 102 if (!targetElement || !isSVGAnimateElement(*resultElement)) 103 return; 104 105 ASSERT(m_animatedPropertyType == determineAnimatedPropertyType(targetElement)); 106 107 ASSERT(percentage >= 0 && percentage <= 1); 108 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag)); 109 ASSERT(m_animatedPropertyType != AnimatedUnknown); 110 ASSERT(m_animator); 111 ASSERT(m_animator->type() == m_animatedPropertyType); 112 ASSERT(m_fromType); 113 ASSERT(m_fromType->type() == m_animatedPropertyType); 114 ASSERT(m_toType); 115 116 SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement); 117 ASSERT(resultAnimationElement->m_animatedType); 118 ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType); 119 120 if (hasTagName(SVGNames::setTag)) 121 percentage = 1; 122 123 if (calcMode() == CalcModeDiscrete) 124 percentage = percentage < 0.5 ? 0 : 1; 125 126 // Target element might have changed. 127 m_animator->setContextElement(targetElement); 128 129 // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do 130 // if after calculateAnimatedValue() ran the cached pointers in the list propery tear 131 // offs would point nowhere, and we couldn't create copies of those values anymore, 132 // while detaching. This is covered by assertions, moving this down would fire them. 133 if (!m_animatedProperties.isEmpty()) 134 m_animator->animValWillChange(m_animatedProperties); 135 136 // Values-animation accumulates using the last values entry corresponding to the end of duration time. 137 SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get(); 138 m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get()); 139 } 140 141 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) 142 { 143 if (toAtEndOfDurationString.isEmpty()) 144 return false; 145 m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString); 146 return true; 147 } 148 149 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString) 150 { 151 SVGElement* targetElement = this->targetElement(); 152 if (!targetElement) 153 return false; 154 155 determinePropertyValueTypes(fromString, toString); 156 ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString); 157 ASSERT(m_animatedPropertyType == m_animator->type()); 158 return true; 159 } 160 161 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString) 162 { 163 SVGElement* targetElement = this->targetElement(); 164 if (!targetElement) 165 return false; 166 167 if (animationMode() == ByAnimation && !isAdditive()) 168 return false; 169 170 // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes). 171 if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition()) 172 return false; 173 174 ASSERT(!hasTagName(SVGNames::setTag)); 175 176 determinePropertyValueTypes(fromString, byString); 177 ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString); 178 ASSERT(m_animatedPropertyType == m_animator->type()); 179 return true; 180 } 181 182 #ifndef NDEBUG 183 static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes) 184 { 185 SVGElementAnimatedPropertyList::const_iterator end = animatedTypes.end(); 186 for (SVGElementAnimatedPropertyList::const_iterator it = animatedTypes.begin(); it != end; ++it) { 187 for (size_t i = 0; i < it->properties.size(); ++i) { 188 if (expectedPropertyType != it->properties[i]->animatedPropertyType()) { 189 // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute. 190 if (expectedPropertyType == AnimatedAngle && it->properties[i]->animatedPropertyType() == AnimatedEnumeration) 191 return true; 192 return false; 193 } 194 } 195 } 196 197 return true; 198 } 199 #endif 200 201 void SVGAnimateElement::resetAnimatedType() 202 { 203 SVGAnimatedTypeAnimator* animator = ensureAnimator(); 204 ASSERT(m_animatedPropertyType == animator->type()); 205 206 SVGElement* targetElement = this->targetElement(); 207 const QualifiedName& attributeName = this->attributeName(); 208 ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName); 209 210 if (shouldApply == DontApplyAnimation) 211 return; 212 213 if (shouldApply == ApplyXMLAnimation) { 214 // SVG DOM animVal animation code-path. 215 m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName); 216 SVGElementAnimatedPropertyList::const_iterator end = m_animatedProperties.end(); 217 for (SVGElementAnimatedPropertyList::const_iterator it = m_animatedProperties.begin(); it != end; ++it) 218 document().accessSVGExtensions()->addElementReferencingTarget(this, it->element); 219 220 ASSERT(!m_animatedProperties.isEmpty()); 221 222 ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties)); 223 if (!m_animatedType) 224 m_animatedType = animator->startAnimValAnimation(m_animatedProperties); 225 else { 226 animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get()); 227 animator->animValDidChange(m_animatedProperties); 228 } 229 return; 230 } 231 232 // CSS properties animation code-path. 233 ASSERT(m_animatedProperties.isEmpty()); 234 String baseValue; 235 236 if (shouldApply == ApplyCSSAnimation) { 237 ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName)); 238 computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue); 239 } 240 241 if (!m_animatedType) 242 m_animatedType = animator->constructFromString(baseValue); 243 else 244 m_animatedType->setValueAsString(attributeName, baseValue); 245 } 246 247 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value) 248 { 249 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 250 251 MutableStylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties(); 252 if (!propertySet->setProperty(id, value, false, 0)) 253 return; 254 255 targetElement->setNeedsStyleRecalc(LocalStyleChange); 256 } 257 258 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id) 259 { 260 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 261 targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id); 262 targetElement->setNeedsStyleRecalc(LocalStyleChange); 263 } 264 265 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString) 266 { 267 ASSERT(targetElement); 268 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 269 return; 270 271 CSSPropertyID id = cssPropertyID(attributeName.localName()); 272 273 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement); 274 applyCSSPropertyToTarget(targetElement, id, valueAsString); 275 276 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 277 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); 278 const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); 279 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { 280 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement()) 281 applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString); 282 } 283 } 284 285 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName) 286 { 287 ASSERT(targetElement); 288 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 289 return; 290 291 CSSPropertyID id = cssPropertyID(attributeName.localName()); 292 293 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement); 294 removeCSSPropertyFromTarget(targetElement, id); 295 296 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 297 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); 298 const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); 299 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { 300 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement()) 301 removeCSSPropertyFromTarget(shadowTreeElement, id); 302 } 303 } 304 305 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) 306 { 307 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 308 targetElement->svgAttributeChanged(attributeName); 309 } 310 311 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) 312 { 313 ASSERT(targetElement); 314 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 315 return; 316 317 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement); 318 notifyTargetAboutAnimValChange(targetElement, attributeName); 319 320 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 321 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement(); 322 const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); 323 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { 324 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement()) 325 notifyTargetAboutAnimValChange(shadowTreeElement, attributeName); 326 } 327 } 328 329 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement) 330 { 331 if (!m_animatedType) 332 return; 333 334 if (!targetElement) { 335 m_animatedType.clear(); 336 return; 337 } 338 339 if (m_animatedProperties.isEmpty()) { 340 // CSS properties animation code-path. 341 removeCSSPropertyFromTargetAndInstances(targetElement, attributeName()); 342 m_animatedType.clear(); 343 return; 344 } 345 346 // SVG DOM animVal animation code-path. 347 if (m_animator) { 348 m_animator->stopAnimValAnimation(m_animatedProperties); 349 notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName()); 350 } 351 352 m_animatedProperties.clear(); 353 m_animatedType.clear(); 354 } 355 356 void SVGAnimateElement::applyResultsToTarget() 357 { 358 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag)); 359 ASSERT(m_animatedPropertyType != AnimatedUnknown); 360 ASSERT(m_animator); 361 362 // Early exit if our animated type got destructed by a previous endedActiveInterval(). 363 if (!m_animatedType) 364 return; 365 366 if (m_animatedProperties.isEmpty()) { 367 // CSS properties animation code-path. 368 // Convert the result of the animation to a String and apply it as CSS property on the target & all instances. 369 applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedType->valueAsString()); 370 return; 371 } 372 373 // SVG DOM animVal animation code-path. 374 // At this point the SVG DOM values are already changed, unlike for CSS. 375 // We only have to trigger update notifications here. 376 m_animator->animValDidChange(m_animatedProperties); 377 notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName()); 378 } 379 380 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() const 381 { 382 // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties. 383 switch (m_animatedPropertyType) { 384 case AnimatedBoolean: 385 case AnimatedEnumeration: 386 case AnimatedPreserveAspectRatio: 387 case AnimatedString: 388 case AnimatedUnknown: 389 return false; 390 default: 391 return true; 392 } 393 } 394 395 bool SVGAnimateElement::isAdditive() const 396 { 397 if (animationMode() == ByAnimation || animationMode() == FromByAnimation) 398 if (!animatedPropertyTypeSupportsAddition()) 399 return false; 400 401 return SVGAnimationElement::isAdditive(); 402 } 403 404 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString) 405 { 406 // FIXME: A return value of float is not enough to support paced animations on lists. 407 SVGElement* targetElement = this->targetElement(); 408 if (!targetElement) 409 return -1; 410 411 return ensureAnimator()->calculateDistance(fromString, toString); 412 } 413 414 void SVGAnimateElement::setTargetElement(SVGElement* target) 415 { 416 SVGAnimationElement::setTargetElement(target); 417 resetAnimatedPropertyType(); 418 } 419 420 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName) 421 { 422 SVGAnimationElement::setAttributeName(attributeName); 423 resetAnimatedPropertyType(); 424 } 425 426 void SVGAnimateElement::resetAnimatedPropertyType() 427 { 428 ASSERT(!m_animatedType); 429 m_fromType.clear(); 430 m_toType.clear(); 431 m_toAtEndOfDurationType.clear(); 432 m_animator.clear(); 433 m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(targetElement()) : AnimatedString; 434 } 435 436 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator() 437 { 438 if (!m_animator) 439 m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType); 440 ASSERT(m_animatedPropertyType == m_animator->type()); 441 return m_animator.get(); 442 } 443 444 } 445