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 "core/CSSPropertyNames.h" 28 #include "core/css/parser/BisonCSSParser.h" 29 #include "core/css/StylePropertySet.h" 30 #include "core/dom/Document.h" 31 #include "core/dom/QualifiedName.h" 32 #include "core/svg/SVGAnimatedTypeAnimator.h" 33 #include "core/svg/SVGDocumentExtensions.h" 34 35 namespace WebCore { 36 37 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document& document) 38 : SVGAnimationElement(tagName, document) 39 { 40 ASSERT(isSVGAnimateElement(*this)); 41 ScriptWrappable::init(this); 42 } 43 44 PassRefPtrWillBeRawPtr<SVGAnimateElement> SVGAnimateElement::create(Document& document) 45 { 46 return adoptRefWillBeNoop(new SVGAnimateElement(SVGNames::animateTag, document)); 47 } 48 49 SVGAnimateElement::~SVGAnimateElement() 50 { 51 } 52 53 AnimatedPropertyType SVGAnimateElement::animatedPropertyType() 54 { 55 return ensureAnimator()->type(); 56 } 57 58 bool SVGAnimateElement::hasValidAttributeType() 59 { 60 SVGElement* targetElement = this->targetElement(); 61 if (!targetElement) 62 return false; 63 64 return animatedPropertyType() != AnimatedUnknown && !hasInvalidCSSAttributeType(); 65 } 66 67 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement) 68 { 69 ASSERT(resultElement); 70 SVGElement* targetElement = this->targetElement(); 71 if (!targetElement || !isSVGAnimateElement(*resultElement)) 72 return; 73 74 ASSERT(percentage >= 0 && percentage <= 1); 75 ASSERT(m_animator); 76 ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this)); 77 ASSERT(animatedPropertyType() != AnimatedUnknown); 78 ASSERT(m_fromProperty); 79 ASSERT(m_fromProperty->type() == animatedPropertyType()); 80 ASSERT(m_toProperty); 81 82 SVGAnimateElement* resultAnimationElement = toSVGAnimateElement(resultElement); 83 ASSERT(resultAnimationElement->m_animatedProperty); 84 ASSERT(resultAnimationElement->animatedPropertyType() == animatedPropertyType()); 85 86 if (isSVGSetElement(*this)) 87 percentage = 1; 88 89 if (calcMode() == CalcModeDiscrete) 90 percentage = percentage < 0.5 ? 0 : 1; 91 92 // Target element might have changed. 93 m_animator->setContextElement(targetElement); 94 95 // Values-animation accumulates using the last values entry corresponding to the end of duration time. 96 SVGPropertyBase* toAtEndOfDurationProperty = m_toAtEndOfDurationProperty ? m_toAtEndOfDurationProperty.get() : m_toProperty.get(); 97 m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromProperty.get(), m_toProperty.get(), toAtEndOfDurationProperty, resultAnimationElement->m_animatedProperty.get()); 98 } 99 100 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) 101 { 102 if (toAtEndOfDurationString.isEmpty()) 103 return false; 104 m_toAtEndOfDurationProperty = ensureAnimator()->constructFromString(toAtEndOfDurationString); 105 return true; 106 } 107 108 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString) 109 { 110 SVGElement* targetElement = this->targetElement(); 111 if (!targetElement) 112 return false; 113 114 determinePropertyValueTypes(fromString, toString); 115 ensureAnimator()->calculateFromAndToValues(m_fromProperty, m_toProperty, fromString, toString); 116 return true; 117 } 118 119 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString) 120 { 121 SVGElement* targetElement = this->targetElement(); 122 if (!targetElement) 123 return false; 124 125 if (animationMode() == ByAnimation && !isAdditive()) 126 return false; 127 128 // from-by animation may only be used with attributes that support addition (e.g. most numeric attributes). 129 if (animationMode() == FromByAnimation && !animatedPropertyTypeSupportsAddition()) 130 return false; 131 132 ASSERT(!isSVGSetElement(*this)); 133 134 determinePropertyValueTypes(fromString, byString); 135 ensureAnimator()->calculateFromAndByValues(m_fromProperty, m_toProperty, fromString, byString); 136 return true; 137 } 138 139 namespace { 140 141 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > findElementInstances(SVGElement* targetElement) 142 { 143 ASSERT(targetElement); 144 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements; 145 146 animatedElements.append(targetElement); 147 148 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement(); 149 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end(); 150 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) { 151 if (SVGElement* shadowTreeElement = *it) 152 animatedElements.append(shadowTreeElement); 153 } 154 155 return animatedElements; 156 } 157 158 } 159 160 void SVGAnimateElement::resetAnimatedType() 161 { 162 SVGAnimatedTypeAnimator* animator = ensureAnimator(); 163 164 SVGElement* targetElement = this->targetElement(); 165 const QualifiedName& attributeName = this->attributeName(); 166 ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName); 167 168 if (shouldApply == DontApplyAnimation) 169 return; 170 171 if (shouldApply == ApplyXMLAnimation) { 172 // SVG DOM animVal animation code-path. 173 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements = findElementInstances(targetElement); 174 ASSERT(!animatedElements.isEmpty()); 175 176 WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator end = animatedElements.end(); 177 for (WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator it = animatedElements.begin(); it != end; ++it) 178 document().accessSVGExtensions().addElementReferencingTarget(this, *it); 179 180 if (!m_animatedProperty) 181 m_animatedProperty = animator->startAnimValAnimation(animatedElements); 182 else 183 m_animatedProperty = animator->resetAnimValToBaseVal(animatedElements); 184 185 return; 186 } 187 188 // CSS properties animation code-path. 189 String baseValue; 190 191 if (shouldApply == ApplyCSSAnimation) { 192 ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName)); 193 computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue); 194 } 195 196 m_animatedProperty = animator->constructFromString(baseValue); 197 } 198 199 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value) 200 { 201 #if !ENABLE(OILPAN) 202 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 203 #endif 204 205 MutableStylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties(); 206 if (!propertySet->setProperty(id, value, false, 0)) 207 return; 208 209 targetElement->setNeedsStyleRecalc(LocalStyleChange); 210 } 211 212 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id) 213 { 214 #if !ENABLE(OILPAN) 215 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 216 #endif 217 targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id); 218 targetElement->setNeedsStyleRecalc(LocalStyleChange); 219 } 220 221 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString) 222 { 223 ASSERT(targetElement); 224 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 225 return; 226 227 CSSPropertyID id = cssPropertyID(attributeName.localName()); 228 229 SVGElement::InstanceUpdateBlocker blocker(targetElement); 230 applyCSSPropertyToTarget(targetElement, id, valueAsString); 231 232 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 233 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement(); 234 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end(); 235 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) { 236 if (SVGElement* shadowTreeElement = *it) 237 applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString); 238 } 239 } 240 241 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName) 242 { 243 ASSERT(targetElement); 244 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 245 return; 246 247 CSSPropertyID id = cssPropertyID(attributeName.localName()); 248 249 SVGElement::InstanceUpdateBlocker blocker(targetElement); 250 removeCSSPropertyFromTarget(targetElement, id); 251 252 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 253 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement(); 254 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end(); 255 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) { 256 if (SVGElement* shadowTreeElement = *it) 257 removeCSSPropertyFromTarget(shadowTreeElement, id); 258 } 259 } 260 261 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) 262 { 263 #if !ENABLE(OILPAN) 264 ASSERT_WITH_SECURITY_IMPLICATION(!targetElement->m_deletionHasBegun); 265 #endif 266 targetElement->invalidateSVGAttributes(); 267 targetElement->svgAttributeChanged(attributeName); 268 } 269 270 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName) 271 { 272 ASSERT(targetElement); 273 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode()) 274 return; 275 276 SVGElement::InstanceUpdateBlocker blocker(targetElement); 277 notifyTargetAboutAnimValChange(targetElement, attributeName); 278 279 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt. 280 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement(); 281 const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end(); 282 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) { 283 notifyTargetAboutAnimValChange(*it, attributeName); 284 } 285 } 286 287 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement) 288 { 289 if (!m_animatedProperty) 290 return; 291 292 if (!targetElement) { 293 m_animatedProperty.clear(); 294 return; 295 } 296 297 if (ensureAnimator()->isAnimatingCSSProperty()) { 298 // CSS properties animation code-path. 299 removeCSSPropertyFromTargetAndInstances(targetElement, attributeName()); 300 m_animatedProperty.clear(); 301 return; 302 } 303 304 // SVG DOM animVal animation code-path. 305 if (m_animator) { 306 WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements = findElementInstances(targetElement); 307 m_animator->stopAnimValAnimation(animatedElements); 308 notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName()); 309 } 310 311 m_animatedProperty.clear(); 312 } 313 314 void SVGAnimateElement::applyResultsToTarget() 315 { 316 ASSERT(m_animator); 317 ASSERT(animatedPropertyType() != AnimatedTransformList || isSVGAnimateTransformElement(*this)); 318 ASSERT(animatedPropertyType() != AnimatedUnknown); 319 320 // Early exit if our animated type got destructed by a previous endedActiveInterval(). 321 if (!m_animatedProperty) 322 return; 323 324 if (m_animator->isAnimatingCSSProperty()) { 325 // CSS properties animation code-path. 326 // Convert the result of the animation to a String and apply it as CSS property on the target & all instances. 327 applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedProperty->valueAsString()); 328 return; 329 } 330 331 // SVG DOM animVal animation code-path. 332 // At this point the SVG DOM values are already changed, unlike for CSS. 333 // We only have to trigger update notifications here. 334 notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName()); 335 } 336 337 bool SVGAnimateElement::animatedPropertyTypeSupportsAddition() 338 { 339 // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties. 340 switch (animatedPropertyType()) { 341 case AnimatedBoolean: 342 case AnimatedEnumeration: 343 case AnimatedPreserveAspectRatio: 344 case AnimatedString: 345 case AnimatedUnknown: 346 return false; 347 default: 348 return true; 349 } 350 } 351 352 bool SVGAnimateElement::isAdditive() 353 { 354 if (animationMode() == ByAnimation || animationMode() == FromByAnimation) 355 if (!animatedPropertyTypeSupportsAddition()) 356 return false; 357 358 return SVGAnimationElement::isAdditive(); 359 } 360 361 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString) 362 { 363 // FIXME: A return value of float is not enough to support paced animations on lists. 364 SVGElement* targetElement = this->targetElement(); 365 if (!targetElement) 366 return -1; 367 368 return ensureAnimator()->calculateDistance(fromString, toString); 369 } 370 371 void SVGAnimateElement::setTargetElement(SVGElement* target) 372 { 373 SVGAnimationElement::setTargetElement(target); 374 resetAnimatedPropertyType(); 375 } 376 377 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName) 378 { 379 SVGAnimationElement::setAttributeName(attributeName); 380 resetAnimatedPropertyType(); 381 } 382 383 void SVGAnimateElement::resetAnimatedPropertyType() 384 { 385 ASSERT(!m_animatedProperty); 386 m_fromProperty.clear(); 387 m_toProperty.clear(); 388 m_toAtEndOfDurationProperty.clear(); 389 m_animator.clear(); 390 } 391 392 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator() 393 { 394 if (!m_animator) 395 m_animator = SVGAnimatedTypeAnimator::create(this, targetElement()); 396 return m_animator.get(); 397 } 398 399 void SVGAnimateElement::trace(Visitor* visitor) 400 { 401 visitor->trace(m_animator); 402 SVGAnimationElement::trace(visitor); 403 } 404 405 } 406