Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 40 #include "core/dom/Document.h" | 40 #include "core/dom/Document.h" |
| 41 #include "core/dom/Element.h" | 41 #include "core/dom/Element.h" |
| 42 #include "core/dom/ExceptionCode.h" | 42 #include "core/dom/ExceptionCode.h" |
| 43 #include "core/dom/NodeComputedStyle.h" | 43 #include "core/dom/NodeComputedStyle.h" |
| 44 #include "wtf/ASCIICType.h" | 44 #include "wtf/ASCIICType.h" |
| 45 #include "wtf/HashSet.h" | 45 #include "wtf/HashSet.h" |
| 46 #include "wtf/NonCopyingSort.h" | 46 #include "wtf/NonCopyingSort.h" |
| 47 | 47 |
| 48 namespace blink { | 48 namespace blink { |
| 49 | 49 |
| 50 namespace { | |
| 51 | |
| 52 bool compareKeyframes(const RefPtr<StringKeyframe>& a, const RefPtr<StringKeyfra me>& b) | |
| 53 { | |
| 54 return a->offset() < b->offset(); | |
| 55 } | |
| 56 | |
| 57 // Gets offset value from keyframeDictionary and returns false if this value was invalid. | |
| 58 bool getAndCheckOffset(const Dictionary& keyframeDictionary, double& offset, dou ble lastOffset, ExceptionState& exceptionState) | |
| 59 { | |
| 60 DictionaryHelper::get(keyframeDictionary, "offset", offset); | |
| 61 | |
| 62 // Keyframes with offsets outside the range [0.0, 1.0] are an error. | |
| 63 if (std::isnan(offset)) { | |
| 64 exceptionState.throwTypeError("Non numeric offset provided"); | |
| 65 return false; | |
| 66 } | |
| 67 | |
| 68 if (offset < 0 || offset > 1) { | |
| 69 exceptionState.throwTypeError("Offsets provided outside the range [0, 1] "); | |
| 70 return false; | |
| 71 } | |
| 72 | |
| 73 if (offset < lastOffset) { | |
| 74 exceptionState.throwTypeError("Keyframes with specified offsets are not sorted"); | |
| 75 return false; | |
| 76 } | |
| 77 | |
| 78 return true; | |
| 79 } | |
| 80 | |
| 81 // Returns true if the property passed in is a compositable property. | |
| 82 bool setKeyframeValue(Element& element, StringKeyframe& keyframe, const String& property, const String& value) | |
| 83 { | |
| 84 StyleSheetContents* styleSheetContents = element.document().elementSheet().c ontents(); | |
| 85 CSSPropertyID cssProperty = AnimationInputHelpers::keyframeAttributeToCSSPro perty(property, element.document()); | |
| 86 if (cssProperty != CSSPropertyInvalid) { | |
| 87 keyframe.setCSSPropertyValue(cssProperty, value, &element, styleSheetCon tents); | |
| 88 return CompositorAnimations::isCompositableProperty(cssProperty); | |
| 89 } | |
| 90 cssProperty = AnimationInputHelpers::keyframeAttributeToPresentationAttribut e(property, element); | |
| 91 if (cssProperty != CSSPropertyInvalid) { | |
| 92 keyframe.setPresentationAttributeValue(cssProperty, value, &element, sty leSheetContents); | |
| 93 return false; | |
| 94 } | |
| 95 const QualifiedName* svgAttribute = AnimationInputHelpers::keyframeAttribute ToSVGAttribute(property, element); | |
| 96 if (svgAttribute) | |
| 97 keyframe.setSVGAttributeValue(*svgAttribute, value); | |
| 98 return false; | |
| 99 } | |
| 100 | |
| 101 EffectModel* createEffectModelFromKeyframes(Element& element, const StringKeyfra meVector& keyframes, bool encounteredCompositableProperty, ExceptionState& excep tionState) | |
| 102 { | |
| 103 // TODO(alancutter): Remove this once composited animations no longer depend on AnimatableValues. | |
| 104 if (encounteredCompositableProperty && element.inActiveDocument()) | |
| 105 element.document().updateLayoutTreeForNode(&element); | |
| 106 | |
| 107 StringKeyframeEffectModel* keyframeEffectModel = StringKeyframeEffectModel:: create(keyframes); | |
| 108 if (!RuntimeEnabledFeatures::cssAdditiveAnimationsEnabled()) { | |
| 109 for (const auto& keyframeGroup : keyframeEffectModel->getPropertySpecifi cKeyframeGroups()) { | |
| 110 PropertyHandle property = keyframeGroup.key; | |
| 111 if (!property.isCSSProperty()) | |
| 112 continue; | |
| 113 | |
| 114 for (const auto& keyframe : keyframeGroup.value->keyframes()) { | |
| 115 if (keyframe->isNeutral()) { | |
| 116 exceptionState.throwDOMException(NotSupportedError, "Partial keyframes are not supported."); | |
| 117 return nullptr; | |
| 118 } | |
| 119 if (keyframe->composite() != EffectModel::CompositeReplace) { | |
| 120 exceptionState.throwDOMException(NotSupportedError, "Additiv e animations are not supported."); | |
| 121 return nullptr; | |
| 122 } | |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 keyframeEffectModel->forceConversionsToAnimatableValues(element, element.com putedStyle()); | |
| 127 | |
| 128 return keyframeEffectModel; | |
| 129 } | |
| 130 | |
| 131 } // namespace | |
| 132 | |
| 133 EffectModel* EffectInput::convert(Element* element, const EffectModelOrDictionar ySequenceOrDictionary& effectInput, ExceptionState& exceptionState) | |
| 134 { | |
| 135 if (effectInput.isEffectModel()) | |
| 136 return effectInput.getAsEffectModel(); | |
| 137 if (effectInput.isDictionarySequence()) | |
| 138 return convert(element, effectInput.getAsDictionarySequence(), exception State); | |
| 139 if (effectInput.isDictionary()) { | |
| 140 return convert(element, effectInput.getAsDictionary(), exceptionState); | |
| 141 } | |
| 142 return nullptr; | |
| 143 } | |
| 144 | |
| 50 EffectModel* EffectInput::convert(Element* element, const Vector<Dictionary>& ke yframeDictionaryVector, ExceptionState& exceptionState) | 145 EffectModel* EffectInput::convert(Element* element, const Vector<Dictionary>& ke yframeDictionaryVector, ExceptionState& exceptionState) |
| 51 { | 146 { |
| 52 if (!element) | 147 if (!element) |
| 53 return nullptr; | 148 return nullptr; |
| 54 | 149 |
| 55 StyleSheetContents* styleSheetContents = element->document().elementSheet(). contents(); | |
| 56 StringKeyframeVector keyframes; | 150 StringKeyframeVector keyframes; |
| 57 double lastOffset = 0; | 151 double lastOffset = 0; |
| 58 bool encounteredCompositableProperty = false; | 152 bool encounteredCompositableProperty = false; |
| 59 | 153 |
| 60 for (const auto& keyframeDictionary : keyframeDictionaryVector) { | 154 for (const auto& keyframeDictionary : keyframeDictionaryVector) { |
| 61 RefPtr<StringKeyframe> keyframe = StringKeyframe::create(); | 155 RefPtr<StringKeyframe> keyframe = StringKeyframe::create(); |
| 62 | 156 |
| 63 ScriptValue scriptValue; | 157 ScriptValue scriptValue; |
| 64 bool frameHasOffset = DictionaryHelper::get(keyframeDictionary, "offset" , scriptValue) && !scriptValue.isNull(); | 158 bool frameHasOffset = DictionaryHelper::get(keyframeDictionary, "offset" , scriptValue) && !scriptValue.isNull(); |
| 65 | 159 |
| 160 double offset; | |
| 66 if (frameHasOffset) { | 161 if (frameHasOffset) { |
| 67 double offset; | 162 if (!getAndCheckOffset(keyframeDictionary, offset, lastOffset, excep tionState)) |
| 68 DictionaryHelper::get(keyframeDictionary, "offset", offset); | |
| 69 | |
| 70 // Keyframes with offsets outside the range [0.0, 1.0] are an error. | |
| 71 if (std::isnan(offset)) { | |
| 72 exceptionState.throwDOMException(InvalidModificationError, "Non numeric offset provided"); | |
| 73 } | |
| 74 | |
| 75 if (offset < 0 || offset > 1) { | |
| 76 exceptionState.throwDOMException(InvalidModificationError, "Offs ets provided outside the range [0, 1]"); | |
| 77 return nullptr; | 163 return nullptr; |
| 78 } | |
| 79 | |
| 80 if (offset < lastOffset) { | |
| 81 exceptionState.throwDOMException(InvalidModificationError, "Keyf rames with specified offsets are not sorted"); | |
| 82 return nullptr; | |
| 83 } | |
| 84 | |
| 85 lastOffset = offset; | 164 lastOffset = offset; |
| 86 | |
| 87 keyframe->setOffset(offset); | 165 keyframe->setOffset(offset); |
| 88 } | 166 } |
| 89 keyframes.append(keyframe); | |
| 90 | 167 |
| 91 String compositeString; | 168 String compositeString; |
| 92 DictionaryHelper::get(keyframeDictionary, "composite", compositeString); | 169 DictionaryHelper::get(keyframeDictionary, "composite", compositeString); |
| 93 if (compositeString == "add") | 170 if (compositeString == "add") |
| 94 keyframe->setComposite(EffectModel::CompositeAdd); | 171 keyframe->setComposite(EffectModel::CompositeAdd); |
| 95 // TODO(alancutter): Support "accumulate" keyframe composition. | 172 // TODO(alancutter): Support "accumulate" keyframe composition. |
| 96 | 173 |
| 97 String timingFunctionString; | 174 String timingFunctionString; |
| 98 if (DictionaryHelper::get(keyframeDictionary, "easing", timingFunctionSt ring)) { | 175 if (DictionaryHelper::get(keyframeDictionary, "easing", timingFunctionSt ring)) { |
| 99 if (RefPtr<TimingFunction> timingFunction = AnimationInputHelpers::p arseTimingFunction(timingFunctionString)) | 176 if (RefPtr<TimingFunction> timingFunction = AnimationInputHelpers::p arseTimingFunction(timingFunctionString)) |
| 100 keyframe->setEasing(timingFunction); | 177 keyframe->setEasing(timingFunction); |
| 101 } | 178 } |
| 102 | 179 |
| 103 Vector<String> keyframeProperties; | 180 Vector<String> keyframeProperties; |
| 104 keyframeDictionary.getPropertyNames(keyframeProperties); | 181 keyframeDictionary.getPropertyNames(keyframeProperties); |
| 105 for (const auto& property : keyframeProperties) { | 182 for (const auto& property : keyframeProperties) { |
| 106 String value; | |
| 107 DictionaryHelper::get(keyframeDictionary, property, value); | |
| 108 | |
| 109 CSSPropertyID cssProperty = AnimationInputHelpers::keyframeAttribute ToCSSProperty(property, element->document()); | |
| 110 if (cssProperty != CSSPropertyInvalid) { | |
| 111 if (!encounteredCompositableProperty && CompositorAnimations::is CompositableProperty(cssProperty)) | |
| 112 encounteredCompositableProperty = true; | |
| 113 | |
| 114 keyframe->setCSSPropertyValue(cssProperty, value, element, style SheetContents); | |
| 115 continue; | |
| 116 } | |
| 117 | |
| 118 if (property == "offset" | 183 if (property == "offset" |
| 119 || property == "composite" | 184 || property == "composite" |
| 120 || property == "easing") { | 185 || property == "easing") { |
| 121 continue; | 186 continue; |
| 122 } | 187 } |
| 123 | 188 |
| 124 cssProperty = AnimationInputHelpers::keyframeAttributeToPresentation Attribute(property, *element); | 189 Vector<String> values; |
| 125 if (cssProperty != CSSPropertyInvalid) { | 190 if (DictionaryHelper::get(keyframeDictionary, property, values)) { |
| 126 keyframe->setPresentationAttributeValue(cssProperty, value, elem ent, styleSheetContents); | 191 exceptionState.throwTypeError("Lists of values not permitted in array-form list of keyframes"); |
| 127 continue; | 192 return nullptr; |
| 128 } | 193 } |
| 129 | 194 |
| 130 const QualifiedName* svgAttribute = AnimationInputHelpers::keyframeA ttributeToSVGAttribute(property, *element); | 195 String value; |
| 131 if (svgAttribute) | 196 DictionaryHelper::get(keyframeDictionary, property, value); |
| 132 keyframe->setSVGAttributeValue(*svgAttribute, value); | 197 |
| 198 encounteredCompositableProperty |= setKeyframeValue(*element, *keyfr ame.get(), property, value); | |
| 199 } | |
| 200 keyframes.append(keyframe); | |
| 201 } | |
| 202 | |
| 203 return createEffectModelFromKeyframes(*element, keyframes, encounteredCompos itableProperty, exceptionState); | |
| 204 } | |
| 205 | |
| 206 EffectModel* EffectInput::convert(Element* element, const Dictionary& keyframeDi ctionary, ExceptionState& exceptionState) | |
| 207 { | |
| 208 if (!element) | |
| 209 return nullptr; | |
| 210 | |
| 211 StringKeyframeVector keyframes; | |
| 212 bool encounteredCompositableProperty = false; | |
| 213 | |
| 214 String timingFunctionString; | |
| 215 RefPtr<TimingFunction> timingFunction = nullptr; | |
| 216 if (DictionaryHelper::get(keyframeDictionary, "easing", timingFunctionString )) | |
| 217 timingFunction = AnimationInputHelpers::parseTimingFunction(timingFuncti onString); | |
| 218 | |
| 219 ScriptValue scriptValue; | |
| 220 bool frameHasOffset = DictionaryHelper::get(keyframeDictionary, "offset", sc riptValue) && !scriptValue.isNull(); | |
| 221 double offset; | |
| 222 if (frameHasOffset && !getAndCheckOffset(keyframeDictionary, offset, 0.0, ex ceptionState)) | |
| 223 return nullptr; | |
| 224 | |
| 225 String compositeString; | |
| 226 DictionaryHelper::get(keyframeDictionary, "composite", compositeString); | |
| 227 | |
| 228 Vector<String> keyframeProperties; | |
| 229 keyframeDictionary.getPropertyNames(keyframeProperties); | |
| 230 for (const auto& property : keyframeProperties) { | |
| 231 if (property == "offset" | |
| 232 || property == "composite" | |
| 233 || property == "easing") { | |
| 234 continue; | |
| 235 } | |
| 236 | |
| 237 Vector<String> values; | |
| 238 bool isList = DictionaryHelper::get(keyframeDictionary, property, values ); | |
| 239 if (!isList) { | |
| 240 String value; | |
| 241 DictionaryHelper::get(keyframeDictionary, property, value); | |
| 242 values.append(value); | |
| 243 } | |
| 244 | |
| 245 size_t numKeyframes = values.size(); | |
| 246 | |
| 247 Vector<double> offsets; | |
| 248 if (frameHasOffset) { | |
| 249 for (size_t i = 0; i < numKeyframes; ++i) { | |
| 250 offsets.append(offset); | |
| 251 } | |
| 252 } else if (numKeyframes == 1) { | |
| 253 offsets.append(1.0); | |
| 254 } else { | |
| 255 for (size_t i = 0; i < numKeyframes; ++i) { | |
| 256 offsets.append(i / (numKeyframes - 1.0)); | |
| 257 } | |
|
alancutter (OOO until 2018)
2016/02/28 22:47:35
I think we can avoid having the offsets vector and
suzyh_UTC10 (ex-contributor)
2016/02/29 00:59:46
Good point. Done.
| |
| 258 } | |
| 259 | |
| 260 for (size_t i = 0; i < numKeyframes; ++i) { | |
| 261 RefPtr<StringKeyframe> keyframe = StringKeyframe::create(); | |
| 262 keyframe->setOffset(offsets[i]); | |
| 263 | |
| 264 const String& value = values[i]; | |
|
alancutter (OOO until 2018)
2016/02/28 22:47:35
No need for this local variable.
suzyh_UTC10 (ex-contributor)
2016/02/29 00:59:46
Done.
| |
| 265 | |
| 266 if (timingFunction) | |
| 267 keyframe->setEasing(timingFunction); | |
| 268 | |
| 269 if (compositeString == "add") | |
| 270 keyframe->setComposite(EffectModel::CompositeAdd); | |
| 271 // TODO(alancutter): Support "accumulate" keyframe composition. | |
| 272 | |
| 273 encounteredCompositableProperty |= setKeyframeValue(*element, *keyfr ame.get(), property, value); | |
| 274 keyframes.append(keyframe); | |
| 133 } | 275 } |
| 134 } | 276 } |
| 135 | 277 |
| 136 // TODO(alancutter): Remove this once composited animations no longer depend on AnimatableValues. | 278 std::sort(keyframes.begin(), keyframes.end(), compareKeyframes); |
| 137 if (encounteredCompositableProperty && element->inActiveDocument()) | |
| 138 element->document().updateLayoutTreeForNode(element); | |
| 139 | 279 |
| 140 StringKeyframeEffectModel* keyframeEffectModel = StringKeyframeEffectModel:: create(keyframes); | 280 return createEffectModelFromKeyframes(*element, keyframes, encounteredCompos itableProperty, exceptionState); |
| 141 if (!RuntimeEnabledFeatures::cssAdditiveAnimationsEnabled()) { | |
| 142 for (const auto& keyframeGroup : keyframeEffectModel->getPropertySpecifi cKeyframeGroups()) { | |
| 143 PropertyHandle property = keyframeGroup.key; | |
| 144 if (!property.isCSSProperty()) | |
| 145 continue; | |
| 146 | |
| 147 for (const auto& keyframe : keyframeGroup.value->keyframes()) { | |
| 148 if (keyframe->isNeutral()) { | |
| 149 exceptionState.throwDOMException(NotSupportedError, "Partial keyframes are not supported."); | |
| 150 return nullptr; | |
| 151 } | |
| 152 if (keyframe->composite() != EffectModel::CompositeReplace) { | |
| 153 exceptionState.throwDOMException(NotSupportedError, "Additiv e animations are not supported."); | |
| 154 return nullptr; | |
| 155 } | |
| 156 } | |
| 157 } | |
| 158 } | |
| 159 keyframeEffectModel->forceConversionsToAnimatableValues(*element, element->c omputedStyle()); | |
| 160 | |
| 161 return keyframeEffectModel; | |
| 162 } | |
| 163 | |
| 164 EffectModel* EffectInput::convert(Element* element, const EffectModelOrDictionar ySequenceOrDictionary& effectInput, ExceptionState& exceptionState) | |
| 165 { | |
| 166 if (effectInput.isEffectModel()) | |
| 167 return effectInput.getAsEffectModel(); | |
| 168 if (effectInput.isDictionarySequence()) | |
| 169 return convert(element, effectInput.getAsDictionarySequence(), exception State); | |
| 170 if (effectInput.isDictionary()) { | |
| 171 Vector<Dictionary> keyframes; | |
| 172 keyframes.append(effectInput.getAsDictionary()); | |
| 173 return convert(element, keyframes, exceptionState); | |
| 174 } | |
| 175 return nullptr; | |
| 176 } | 281 } |
| 177 | 282 |
| 178 } // namespace blink | 283 } // namespace blink |
| OLD | NEW |