blob: 03f3893830b27631661f57df7f6db8e3b0ca1afc [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/animation/EffectInput.h"
#include "bindings/core/v8/Dictionary.h"
#include "bindings/core/v8/UnionTypesCore.h"
#include "core/HTMLNames.h"
#include "core/SVGNames.h"
#include "core/XLinkNames.h"
#include "core/animation/AnimationInputHelpers.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/StringKeyframe.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/svg/animation/SVGSMILElement.h"
#include "wtf/ASCIICType.h"
#include "wtf/HashSet.h"
#include "wtf/NonCopyingSort.h"
namespace blink {
namespace {
bool svgPrefixed(const String& property)
{
return property.length() >= 4 && property.startsWith("svg") && isASCIIUpper(property[3]);
}
QualifiedName svgAttributeName(String property)
{
// Replace 'svgTransform' with 'transform', etc.
ASSERT(svgPrefixed(property));
UChar first = toASCIILower(property[3]);
property.remove(0, 4);
property.insert(&first, 1, 0);
if (property == "href")
return XLinkNames::hrefAttr;
return QualifiedName(nullAtom, AtomicString(property), SVGNames::amplitudeAttr.namespaceURI());
}
const QualifiedName* supportedSVGAttribute(const String& property, SVGElement* svgElement)
{
typedef HashMap<QualifiedName, const QualifiedName*> AttributeNameMap;
DEFINE_STATIC_LOCAL(AttributeNameMap, supportedAttributes, ());
if (supportedAttributes.isEmpty()) {
// Fill the set for the first use.
// Animatable attributes from http://www.w3.org/TR/SVG/attindex.html
const QualifiedName* attributes[] = {
&HTMLNames::classAttr,
&SVGNames::amplitudeAttr,
&SVGNames::azimuthAttr,
&SVGNames::baseFrequencyAttr,
&SVGNames::biasAttr,
&SVGNames::clipPathUnitsAttr,
&SVGNames::cxAttr,
&SVGNames::cyAttr,
&SVGNames::dAttr,
&SVGNames::diffuseConstantAttr,
&SVGNames::divisorAttr,
&SVGNames::dxAttr,
&SVGNames::dyAttr,
&SVGNames::edgeModeAttr,
&SVGNames::elevationAttr,
&SVGNames::exponentAttr,
&SVGNames::filterUnitsAttr,
&SVGNames::fxAttr,
&SVGNames::fyAttr,
&SVGNames::gradientTransformAttr,
&SVGNames::gradientUnitsAttr,
&SVGNames::heightAttr,
&SVGNames::in2Attr,
&SVGNames::inAttr,
&SVGNames::interceptAttr,
&SVGNames::k1Attr,
&SVGNames::k2Attr,
&SVGNames::k3Attr,
&SVGNames::k4Attr,
&SVGNames::kernelMatrixAttr,
&SVGNames::kernelUnitLengthAttr,
&SVGNames::lengthAdjustAttr,
&SVGNames::limitingConeAngleAttr,
&SVGNames::markerHeightAttr,
&SVGNames::markerUnitsAttr,
&SVGNames::markerWidthAttr,
&SVGNames::maskContentUnitsAttr,
&SVGNames::maskUnitsAttr,
&SVGNames::methodAttr,
&SVGNames::modeAttr,
&SVGNames::numOctavesAttr,
&SVGNames::offsetAttr,
&SVGNames::operatorAttr,
&SVGNames::orderAttr,
&SVGNames::orientAttr,
&SVGNames::pathLengthAttr,
&SVGNames::patternContentUnitsAttr,
&SVGNames::patternTransformAttr,
&SVGNames::patternUnitsAttr,
&SVGNames::pointsAtXAttr,
&SVGNames::pointsAtYAttr,
&SVGNames::pointsAtZAttr,
&SVGNames::pointsAttr,
&SVGNames::preserveAlphaAttr,
&SVGNames::preserveAspectRatioAttr,
&SVGNames::primitiveUnitsAttr,
&SVGNames::rAttr,
&SVGNames::radiusAttr,
&SVGNames::refXAttr,
&SVGNames::refYAttr,
&SVGNames::resultAttr,
&SVGNames::rotateAttr,
&SVGNames::rxAttr,
&SVGNames::ryAttr,
&SVGNames::scaleAttr,
&SVGNames::seedAttr,
&SVGNames::slopeAttr,
&SVGNames::spacingAttr,
&SVGNames::specularConstantAttr,
&SVGNames::specularExponentAttr,
&SVGNames::spreadMethodAttr,
&SVGNames::startOffsetAttr,
&SVGNames::stdDeviationAttr,
&SVGNames::stitchTilesAttr,
&SVGNames::surfaceScaleAttr,
&SVGNames::tableValuesAttr,
&SVGNames::targetAttr,
&SVGNames::targetXAttr,
&SVGNames::targetYAttr,
&SVGNames::textLengthAttr,
&SVGNames::transformAttr,
&SVGNames::typeAttr,
&SVGNames::valuesAttr,
&SVGNames::viewBoxAttr,
&SVGNames::widthAttr,
&SVGNames::x1Attr,
&SVGNames::x2Attr,
&SVGNames::xAttr,
&SVGNames::xChannelSelectorAttr,
&SVGNames::y1Attr,
&SVGNames::y2Attr,
&SVGNames::yAttr,
&SVGNames::yChannelSelectorAttr,
&SVGNames::zAttr,
&XLinkNames::hrefAttr,
};
for (size_t i = 0; i < WTF_ARRAY_LENGTH(attributes); i++)
supportedAttributes.set(*attributes[i], attributes[i]);
}
if (isSVGSMILElement(*svgElement))
return nullptr;
QualifiedName attributeName = svgAttributeName(property);
auto iter = supportedAttributes.find(attributeName);
if (iter == supportedAttributes.end() || !svgElement->propertyFromAttribute(*iter->value))
return nullptr;
return iter->value;
}
} // namespace
PassRefPtrWillBeRawPtr<EffectModel> EffectInput::convert(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState& exceptionState)
{
if (!element)
return nullptr;
// TODO(alancutter): Remove this once composited animations no longer depend on AnimatableValues.
if (element->inActiveDocument())
element->document().updateLayoutTreeForNodeIfNeeded(element);
StyleSheetContents* styleSheetContents = element->document().elementSheet().contents();
StringKeyframeVector keyframes;
double lastOffset = 0;
for (const auto& keyframeDictionary : keyframeDictionaryVector) {
RefPtrWillBeRawPtr<StringKeyframe> keyframe = StringKeyframe::create();
ScriptValue scriptValue;
bool frameHasOffset = DictionaryHelper::get(keyframeDictionary, "offset", scriptValue) && !scriptValue.isNull();
if (frameHasOffset) {
double offset;
DictionaryHelper::get(keyframeDictionary, "offset", offset);
// Keyframes with offsets outside the range [0.0, 1.0] are an error.
if (std::isnan(offset)) {
exceptionState.throwDOMException(InvalidModificationError, "Non numeric offset provided");
}
if (offset < 0 || offset > 1) {
exceptionState.throwDOMException(InvalidModificationError, "Offsets provided outside the range [0, 1]");
return nullptr;
}
if (offset < lastOffset) {
exceptionState.throwDOMException(InvalidModificationError, "Keyframes with specified offsets are not sorted");
return nullptr;
}
lastOffset = offset;
keyframe->setOffset(offset);
}
keyframes.append(keyframe);
String compositeString;
DictionaryHelper::get(keyframeDictionary, "composite", compositeString);
if (compositeString == "add")
keyframe->setComposite(EffectModel::CompositeAdd);
String timingFunctionString;
if (DictionaryHelper::get(keyframeDictionary, "easing", timingFunctionString)) {
if (RefPtr<TimingFunction> timingFunction = AnimationInputHelpers::parseTimingFunction(timingFunctionString))
keyframe->setEasing(timingFunction);
}
Vector<String> keyframeProperties;
keyframeDictionary.getPropertyNames(keyframeProperties);
for (const auto& property : keyframeProperties) {
String value;
DictionaryHelper::get(keyframeDictionary, property, value);
CSSPropertyID id = AnimationInputHelpers::keyframeAttributeToCSSPropertyID(property);
if (id != CSSPropertyInvalid) {
keyframe->setPropertyValue(id, value, element, styleSheetContents);
continue;
}
if (property == "offset"
|| property == "composite"
|| property == "easing") {
continue;
}
if (!RuntimeEnabledFeatures::webAnimationsSVGEnabled() || !element->isSVGElement() || !svgPrefixed(property))
continue;
SVGElement* svgElement = toSVGElement(element);
const QualifiedName* qualifiedName = supportedSVGAttribute(property, svgElement);
if (qualifiedName)
keyframe->setPropertyValue(*qualifiedName, value, svgElement);
}
}
RefPtrWillBeRawPtr<StringKeyframeEffectModel> keyframeEffectModel = StringKeyframeEffectModel::create(keyframes);
if (keyframeEffectModel->hasSyntheticKeyframes()) {
exceptionState.throwDOMException(NotSupportedError, "Partial keyframes are not supported.");
return nullptr;
}
if (!keyframeEffectModel->isReplaceOnly()) {
exceptionState.throwDOMException(NotSupportedError, "Additive animations are not supported.");
return nullptr;
}
keyframeEffectModel->forceConversionsToAnimatableValues(*element, element->computedStyle());
return keyframeEffectModel;
}
PassRefPtrWillBeRawPtr<EffectModel> EffectInput::convert(Element* element, const EffectModelOrDictionarySequence& effectInput, ExceptionState& exceptionState)
{
if (effectInput.isEffectModel())
return effectInput.getAsEffectModel();
if (effectInput.isDictionarySequence())
return convert(element, effectInput.getAsDictionarySequence(), exceptionState);
return nullptr;
}
} // namespace blink