[ Mac Debug ] (r251706) webanimations/empty-keyframes-crash.html is crashing
[WebKit-https.git] / Source / WebCore / animation / KeyframeEffect.cpp
1 /*
2  * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "KeyframeEffect.h"
28
29 #include "Animation.h"
30 #include "CSSAnimation.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSKeyframeRule.h"
33 #include "CSSPropertyAnimation.h"
34 #include "CSSPropertyNames.h"
35 #include "CSSStyleDeclaration.h"
36 #include "CSSTimingFunctionValue.h"
37 #include "CSSTransition.h"
38 #include "Element.h"
39 #include "FontCascade.h"
40 #include "FrameView.h"
41 #include "GeometryUtilities.h"
42 #include "JSCompositeOperation.h"
43 #include "JSCompositeOperationOrAuto.h"
44 #include "JSDOMConvert.h"
45 #include "JSKeyframeEffect.h"
46 #include "RenderBox.h"
47 #include "RenderBoxModelObject.h"
48 #include "RenderElement.h"
49 #include "RenderStyle.h"
50 #include "StylePendingResources.h"
51 #include "StyleResolver.h"
52 #include "TimingFunction.h"
53 #include "TranslateTransformOperation.h"
54 #include "WillChangeData.h"
55 #include <JavaScriptCore/Exception.h>
56 #include <wtf/UUID.h>
57
58 namespace WebCore {
59 using namespace JSC;
60
61 static inline void invalidateElement(Element* element)
62 {
63     if (element)
64         element->invalidateStyle();
65 }
66
67 static inline String CSSPropertyIDToIDLAttributeName(CSSPropertyID cssPropertyId)
68 {
69     // https://drafts.csswg.org/web-animations-1/#animation-property-name-to-idl-attribute-name
70     // 1. If property follows the <custom-property-name> production, return property.
71     // FIXME: We don't handle custom properties yet.
72
73     // 2. If property refers to the CSS float property, return the string "cssFloat".
74     if (cssPropertyId == CSSPropertyFloat)
75         return "cssFloat";
76
77     // 3. If property refers to the CSS offset property, return the string "cssOffset".
78     // FIXME: we don't support the CSS "offset" property
79
80     // 4. Otherwise, return the result of applying the CSS property to IDL attribute algorithm [CSSOM] to property.
81     return getJSPropertyName(cssPropertyId);
82 }
83
84 static inline CSSPropertyID IDLAttributeNameToAnimationPropertyName(const String& idlAttributeName)
85 {
86     // https://drafts.csswg.org/web-animations-1/#idl-attribute-name-to-animation-property-name
87     // 1. If attribute conforms to the <custom-property-name> production, return attribute.
88     // FIXME: We don't handle custom properties yet.
89
90     // 2. If attribute is the string "cssFloat", then return an animation property representing the CSS float property.
91     if (idlAttributeName == "cssFloat")
92         return CSSPropertyFloat;
93
94     // 3. If attribute is the string "cssOffset", then return an animation property representing the CSS offset property.
95     // FIXME: We don't support the CSS "offset" property.
96
97     // 4. Otherwise, return the result of applying the IDL attribute to CSS property algorithm [CSSOM] to attribute.
98     auto cssPropertyId = CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(idlAttributeName);
99
100     // We need to check that converting the property back to IDL form yields the same result such that a property passed
101     // in non-IDL form is rejected, for instance "font-size".
102     if (idlAttributeName != CSSPropertyIDToIDLAttributeName(cssPropertyId))
103         return CSSPropertyInvalid;
104
105     return cssPropertyId;
106 }
107
108 static inline void computeMissingKeyframeOffsets(Vector<KeyframeEffect::ParsedKeyframe>& keyframes)
109 {
110     // https://drafts.csswg.org/web-animations-1/#compute-missing-keyframe-offsets
111
112     if (keyframes.isEmpty())
113         return;
114
115     // 1. For each keyframe, in keyframes, let the computed keyframe offset of the keyframe be equal to its keyframe offset value.
116     // In our implementation, we only set non-null values to avoid making computedOffset Optional<double>. Instead, we'll know
117     // that a keyframe hasn't had a computed offset by checking if it has a null offset and a 0 computedOffset, since the first
118     // keyframe will already have a 0 computedOffset.
119     for (auto& keyframe : keyframes) {
120         auto computedOffset = keyframe.offset;
121         keyframe.computedOffset = computedOffset ? *computedOffset : 0;
122     }
123
124     // 2. If keyframes contains more than one keyframe and the computed keyframe offset of the first keyframe in keyframes is null,
125     //    set the computed keyframe offset of the first keyframe to 0.
126     if (keyframes.size() > 1 && !keyframes[0].offset)
127         keyframes[0].computedOffset = 0;
128
129     // 3. If the computed keyframe offset of the last keyframe in keyframes is null, set its computed keyframe offset to 1.
130     if (!keyframes.last().offset)
131         keyframes.last().computedOffset = 1;
132
133     // 4. For each pair of keyframes A and B where:
134     //    - A appears before B in keyframes, and
135     //    - A and B have a computed keyframe offset that is not null, and
136     //    - all keyframes between A and B have a null computed keyframe offset,
137     //    calculate the computed keyframe offset of each keyframe between A and B as follows:
138     //    1. Let offsetk be the computed keyframe offset of a keyframe k.
139     //    2. Let n be the number of keyframes between and including A and B minus 1.
140     //    3. Let index refer to the position of keyframe in the sequence of keyframes between A and B such that the first keyframe after A has an index of 1.
141     //    4. Set the computed keyframe offset of keyframe to offsetA + (offsetB − offsetA) × index / n.
142     size_t indexOfLastKeyframeWithNonNullOffset = 0;
143     for (size_t i = 1; i < keyframes.size(); ++i) {
144         auto& keyframe = keyframes[i];
145         // Keyframes with a null offset that don't yet have a non-zero computed offset are keyframes
146         // with an offset that needs to be computed.
147         if (!keyframe.offset && !keyframe.computedOffset)
148             continue;
149         if (indexOfLastKeyframeWithNonNullOffset != i - 1) {
150             double lastNonNullOffset = keyframes[indexOfLastKeyframeWithNonNullOffset].computedOffset;
151             double offsetDelta = keyframe.computedOffset - lastNonNullOffset;
152             double offsetIncrement = offsetDelta / (i - indexOfLastKeyframeWithNonNullOffset);
153             size_t indexOfFirstKeyframeWithNullOffset = indexOfLastKeyframeWithNonNullOffset + 1;
154             for (size_t j = indexOfFirstKeyframeWithNullOffset; j < i; ++j)
155                 keyframes[j].computedOffset = lastNonNullOffset + (j - indexOfLastKeyframeWithNonNullOffset) * offsetIncrement;
156         }
157         indexOfLastKeyframeWithNonNullOffset = i;
158     }
159 }
160
161 static inline ExceptionOr<KeyframeEffect::KeyframeLikeObject> processKeyframeLikeObject(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput, bool allowLists)
162 {
163     // https://drafts.csswg.org/web-animations-1/#process-a-keyframe-like-object
164
165     VM& vm = lexicalGlobalObject.vm();
166     auto scope = DECLARE_THROW_SCOPE(vm);
167
168     // 1. Run the procedure to convert an ECMAScript value to a dictionary type [WEBIDL] with keyframe input as the ECMAScript value as follows:
169     // 
170     //    If allow lists is true, use the following dictionary type:
171     //
172     //    dictionary BasePropertyIndexedKeyframe {
173     //        (double? or sequence<double?>)                                   offset = [];
174     //        (DOMString or sequence<DOMString>)                               easing = [];
175     //        (CompositeOperationOrAuto or sequence<CompositeOperationOrAuto>) composite = [];
176     //    };
177     //
178     //    Otherwise, use the following dictionary type:
179     //
180     //    dictionary BaseKeyframe {
181     //        double?                  offset = null;
182     //        DOMString                easing = "linear";
183     //        CompositeOperationOrAuto composite = "auto";
184     //    };
185     //
186     //    Store the result of this procedure as keyframe output.
187     KeyframeEffect::BasePropertyIndexedKeyframe baseProperties;
188     if (allowLists)
189         baseProperties = convert<IDLDictionary<KeyframeEffect::BasePropertyIndexedKeyframe>>(lexicalGlobalObject, keyframesInput.get());
190     else {
191         auto baseKeyframe = convert<IDLDictionary<KeyframeEffect::BaseKeyframe>>(lexicalGlobalObject, keyframesInput.get());
192         if (baseKeyframe.offset)
193             baseProperties.offset = baseKeyframe.offset.value();
194         else
195             baseProperties.offset = nullptr;
196         baseProperties.easing = baseKeyframe.easing;
197         baseProperties.composite = baseKeyframe.composite;
198     }
199     RETURN_IF_EXCEPTION(scope, Exception { TypeError });
200
201     KeyframeEffect::KeyframeLikeObject keyframeOuput;
202     keyframeOuput.baseProperties = baseProperties;
203
204     // 2. Build up a list of animatable properties as follows:
205     //
206     //    1. Let animatable properties be a list of property names (including shorthand properties that have longhand sub-properties
207     //       that are animatable) that can be animated by the implementation.
208     //    2. Convert each property name in animatable properties to the equivalent IDL attribute by applying the animation property
209     //       name to IDL attribute name algorithm.
210
211     // 3. Let input properties be the result of calling the EnumerableOwnNames operation with keyframe input as the object.
212     PropertyNameArray inputProperties(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
213     JSObject::getOwnPropertyNames(keyframesInput.get(), &lexicalGlobalObject, inputProperties, EnumerationMode());
214
215     // 4. Make up a new list animation properties that consists of all of the properties that are in both input properties and animatable
216     //    properties, or which are in input properties and conform to the <custom-property-name> production.
217     Vector<JSC::Identifier> animationProperties;
218     size_t numberOfProperties = inputProperties.size();
219     for (size_t i = 0; i < numberOfProperties; ++i) {
220         if (CSSPropertyAnimation::isPropertyAnimatable(IDLAttributeNameToAnimationPropertyName(inputProperties[i].string())))
221             animationProperties.append(inputProperties[i]);
222     }
223
224     // 5. Sort animation properties in ascending order by the Unicode codepoints that define each property name.
225     std::sort(animationProperties.begin(), animationProperties.end(), [](auto& lhs, auto& rhs) {
226         return lhs.string().utf8() < rhs.string().utf8();
227     });
228
229     // 6. For each property name in animation properties,
230     size_t numberOfAnimationProperties = animationProperties.size();
231     for (size_t i = 0; i < numberOfAnimationProperties; ++i) {
232         // 1. Let raw value be the result of calling the [[Get]] internal method on keyframe input, with property name as the property
233         //    key and keyframe input as the receiver.
234         auto rawValue = keyframesInput->get(&lexicalGlobalObject, animationProperties[i]);
235
236         // 2. Check the completion record of raw value.
237         RETURN_IF_EXCEPTION(scope, Exception { TypeError });
238
239         // 3. Convert raw value to a DOMString or sequence of DOMStrings property values as follows:
240         Vector<String> propertyValues;
241         if (allowLists) {
242             // If allow lists is true,
243             // Let property values be the result of converting raw value to IDL type (DOMString or sequence<DOMString>)
244             // using the procedures defined for converting an ECMAScript value to an IDL value [WEBIDL].
245             // If property values is a single DOMString, replace property values with a sequence of DOMStrings with the original value of property
246             // Values as the only element.
247             if (rawValue.isObject())
248                 propertyValues = convert<IDLSequence<IDLDOMString>>(lexicalGlobalObject, rawValue);
249             else
250                 propertyValues = { rawValue.toWTFString(&lexicalGlobalObject) };
251         } else {
252             // Otherwise,
253             // Let property values be the result of converting raw value to a DOMString using the procedure for converting an ECMAScript value to a DOMString.
254             propertyValues = { convert<IDLDOMString>(lexicalGlobalObject, rawValue) };
255         }
256         RETURN_IF_EXCEPTION(scope, Exception { TypeError });
257
258         // 4. Calculate the normalized property name as the result of applying the IDL attribute name to animation property name algorithm to property name.
259         auto cssPropertyID = IDLAttributeNameToAnimationPropertyName(animationProperties[i].string());
260
261         // 5. Add a property to to keyframe output with normalized property name as the property name, and property values as the property value.
262         keyframeOuput.propertiesAndValues.append({ cssPropertyID, propertyValues });
263     }
264
265     // 7. Return keyframe output.
266     return { WTFMove(keyframeOuput) };
267 }
268
269 static inline ExceptionOr<void> processIterableKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput, JSValue method, Vector<KeyframeEffect::ParsedKeyframe>& parsedKeyframes)
270 {
271     // 1. Let iter be GetIterator(object, method).
272     forEachInIterable(lexicalGlobalObject, keyframesInput.get(), method, [&parsedKeyframes](VM& vm, JSGlobalObject& lexicalGlobalObject, JSValue nextValue) -> ExceptionOr<void> {
273         // Steps 2 through 6 are already implemented by forEachInIterable().
274         auto scope = DECLARE_THROW_SCOPE(vm);
275         if (!nextValue || !nextValue.isObject()) {
276             throwException(&lexicalGlobalObject, scope, JSC::Exception::create(vm, createTypeError(&lexicalGlobalObject)));
277             return { };
278         }
279
280         // 7. Append to processed keyframes the result of running the procedure to process a keyframe-like object passing nextItem
281         // as the keyframe input and with the allow lists flag set to false.
282         auto processKeyframeLikeObjectResult = processKeyframeLikeObject(lexicalGlobalObject, Strong<JSObject>(vm, nextValue.toObject(&lexicalGlobalObject)), false);
283         if (processKeyframeLikeObjectResult.hasException())
284             return processKeyframeLikeObjectResult.releaseException();
285         auto keyframeLikeObject = processKeyframeLikeObjectResult.returnValue();
286
287         KeyframeEffect::ParsedKeyframe keyframeOutput;
288
289         // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only offset
290         // alternatives we should expect are double and nullptr.
291         if (WTF::holds_alternative<double>(keyframeLikeObject.baseProperties.offset))
292             keyframeOutput.offset = WTF::get<double>(keyframeLikeObject.baseProperties.offset);
293         else
294             ASSERT(WTF::holds_alternative<std::nullptr_t>(keyframeLikeObject.baseProperties.offset));
295
296         // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only easing
297         // alternative we should expect is String.
298         ASSERT(WTF::holds_alternative<String>(keyframeLikeObject.baseProperties.easing));
299         keyframeOutput.easing = WTF::get<String>(keyframeLikeObject.baseProperties.easing);
300
301         // When calling processKeyframeLikeObject() with the "allow lists" flag set to false, the only composite
302         // alternatives we should expect is CompositeOperationAuto.
303         ASSERT(WTF::holds_alternative<CompositeOperationOrAuto>(keyframeLikeObject.baseProperties.composite));
304         keyframeOutput.composite = WTF::get<CompositeOperationOrAuto>(keyframeLikeObject.baseProperties.composite);
305
306         for (auto& propertyAndValue : keyframeLikeObject.propertiesAndValues) {
307             auto cssPropertyId = propertyAndValue.property;
308             // When calling processKeyframeLikeObject() with the "allow lists" flag set to false,
309             // there should only ever be a single value for a given property.
310             ASSERT(propertyAndValue.values.size() == 1);
311             auto stringValue = propertyAndValue.values[0];
312             if (keyframeOutput.style->setProperty(cssPropertyId, stringValue))
313                 keyframeOutput.unparsedStyle.set(cssPropertyId, stringValue);
314         }
315
316         parsedKeyframes.append(WTFMove(keyframeOutput));
317
318         return { };
319     });
320
321     return { };
322 }
323
324 static inline ExceptionOr<void> processPropertyIndexedKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput, Vector<KeyframeEffect::ParsedKeyframe>& parsedKeyframes, Vector<String>& unusedEasings)
325 {
326     // 1. Let property-indexed keyframe be the result of running the procedure to process a keyframe-like object passing object as the keyframe input.
327     auto processKeyframeLikeObjectResult = processKeyframeLikeObject(lexicalGlobalObject, WTFMove(keyframesInput), true);
328     if (processKeyframeLikeObjectResult.hasException())
329         return processKeyframeLikeObjectResult.releaseException();
330     auto propertyIndexedKeyframe = processKeyframeLikeObjectResult.returnValue();
331
332     // 2. For each member, m, in property-indexed keyframe, perform the following steps:
333     for (auto& m : propertyIndexedKeyframe.propertiesAndValues) {
334         // 1. Let property name be the key for m.
335         auto propertyName = m.property;
336         // 2. If property name is “composite”, or “easing”, or “offset”, skip the remaining steps in this loop and continue from the next member in property-indexed
337         //    keyframe after m.
338         //    We skip this test since we split those properties and the actual CSS properties that we're currently iterating over.
339         // 3. Let property values be the value for m.
340         auto propertyValues = m.values;
341         // 4. Let property keyframes be an empty sequence of keyframes.
342         Vector<KeyframeEffect::ParsedKeyframe> propertyKeyframes;
343         // 5. For each value, v, in property values perform the following steps:
344         for (auto& v : propertyValues) {
345             // 1. Let k be a new keyframe with a null keyframe offset.
346             KeyframeEffect::ParsedKeyframe k;
347             // 2. Add the property-value pair, property name → v, to k.
348             if (k.style->setProperty(propertyName, v))
349                 k.unparsedStyle.set(propertyName, v);
350             // 3. Append k to property keyframes.
351             propertyKeyframes.append(WTFMove(k));
352         }
353         // 6. Apply the procedure to compute missing keyframe offsets to property keyframes.
354         computeMissingKeyframeOffsets(propertyKeyframes);
355
356         // 7. Add keyframes in property keyframes to processed keyframes.
357         for (auto& keyframe : propertyKeyframes)
358             parsedKeyframes.append(WTFMove(keyframe));
359     }
360
361     // 3. Sort processed keyframes by the computed keyframe offset of each keyframe in increasing order.
362     std::sort(parsedKeyframes.begin(), parsedKeyframes.end(), [](auto& lhs, auto& rhs) {
363         return lhs.computedOffset < rhs.computedOffset;
364     });
365
366     // 4. Merge adjacent keyframes in processed keyframes when they have equal computed keyframe offsets.
367     size_t i = 1;
368     while (i < parsedKeyframes.size()) {
369         auto& keyframe = parsedKeyframes[i];
370         auto& previousKeyframe = parsedKeyframes[i - 1];
371         // If the offsets of this keyframe and the previous keyframe are different,
372         // this means that the two keyframes should not be merged and we can move
373         // on to the next keyframe.
374         if (keyframe.computedOffset != previousKeyframe.computedOffset) {
375             i++;
376             continue;
377         }
378         // Otherwise, both this keyframe and the previous keyframe should be merged.
379         // Unprocessed keyframes in parsedKeyframes at this stage have at most a single
380         // property in cssPropertiesAndValues, so just set this on the previous keyframe.
381         // In case an invalid or null value was originally provided, then the property
382         // was not set and the property count is 0, in which case there is nothing to merge.
383         if (keyframe.style->propertyCount()) {
384             auto property = keyframe.style->propertyAt(0);
385             previousKeyframe.style->setProperty(property.id(), property.value());
386             previousKeyframe.unparsedStyle.set(property.id(), keyframe.unparsedStyle.get(property.id()));
387         }
388         // Since we've processed this keyframe, we can remove it and keep i the same
389         // so that we process the next keyframe in the next loop iteration.
390         parsedKeyframes.remove(i);
391     }
392
393     // 5. Let offsets be a sequence of nullable double values assigned based on the type of the “offset” member of the property-indexed keyframe as follows:
394     //    - sequence<double?>, the value of “offset” as-is.
395     //    - double?, a sequence of length one with the value of “offset” as its single item, i.e. « offset »,
396     Vector<Optional<double>> offsets;
397     if (WTF::holds_alternative<Vector<Optional<double>>>(propertyIndexedKeyframe.baseProperties.offset))
398         offsets = WTF::get<Vector<Optional<double>>>(propertyIndexedKeyframe.baseProperties.offset);
399     else if (WTF::holds_alternative<double>(propertyIndexedKeyframe.baseProperties.offset))
400         offsets.append(WTF::get<double>(propertyIndexedKeyframe.baseProperties.offset));
401     else if (WTF::holds_alternative<std::nullptr_t>(propertyIndexedKeyframe.baseProperties.offset))
402         offsets.append(WTF::nullopt);
403
404     // 6. Assign each value in offsets to the keyframe offset of the keyframe with corresponding position in property keyframes until the end of either sequence is reached.
405     for (size_t i = 0; i < offsets.size() && i < parsedKeyframes.size(); ++i)
406         parsedKeyframes[i].offset = offsets[i];
407
408     // 7. Let easings be a sequence of DOMString values assigned based on the type of the “easing” member of the property-indexed keyframe as follows:
409     //    - sequence<DOMString>, the value of “easing” as-is.
410     //    - DOMString, a sequence of length one with the value of “easing” as its single item, i.e. « easing »,
411     Vector<String> easings;
412     if (WTF::holds_alternative<Vector<String>>(propertyIndexedKeyframe.baseProperties.easing))
413         easings = WTF::get<Vector<String>>(propertyIndexedKeyframe.baseProperties.easing);
414     else if (WTF::holds_alternative<String>(propertyIndexedKeyframe.baseProperties.easing))
415         easings.append(WTF::get<String>(propertyIndexedKeyframe.baseProperties.easing));
416
417     // 8. If easings is an empty sequence, let it be a sequence of length one containing the single value “linear”, i.e. « "linear" ».
418     if (easings.isEmpty())
419         easings.append("linear");
420
421     // 9. If easings has fewer items than property keyframes, repeat the elements in easings successively starting from the beginning of the list until easings has as many
422     //    items as property keyframes.
423     if (easings.size() < parsedKeyframes.size()) {
424         size_t initialNumberOfEasings = easings.size();
425         for (i = initialNumberOfEasings; i < parsedKeyframes.size(); ++i)
426             easings.append(easings[i % initialNumberOfEasings]);
427     }
428
429     // 10. If easings has more items than property keyframes, store the excess items as unused easings.
430     while (easings.size() > parsedKeyframes.size())
431         unusedEasings.append(easings.takeLast());
432
433     // 11. Assign each value in easings to a property named “easing” on the keyframe with the corresponding position in property keyframes until the end of property keyframes
434     //     is reached.
435     for (size_t i = 0; i < parsedKeyframes.size(); ++i)
436         parsedKeyframes[i].easing = easings[i];
437
438     // 12. If the “composite” member of the property-indexed keyframe is not an empty sequence:
439     Vector<CompositeOperationOrAuto> compositeModes;
440     if (WTF::holds_alternative<Vector<CompositeOperationOrAuto>>(propertyIndexedKeyframe.baseProperties.composite))
441         compositeModes = WTF::get<Vector<CompositeOperationOrAuto>>(propertyIndexedKeyframe.baseProperties.composite);
442     else if (WTF::holds_alternative<CompositeOperationOrAuto>(propertyIndexedKeyframe.baseProperties.composite))
443         compositeModes.append(WTF::get<CompositeOperationOrAuto>(propertyIndexedKeyframe.baseProperties.composite));
444     if (!compositeModes.isEmpty()) {
445         // 1. Let composite modes be a sequence of CompositeOperationOrAuto values assigned from the “composite” member of property-indexed keyframe. If that member is a single
446         //    CompositeOperationOrAuto value operation, let composite modes be a sequence of length one, with the value of the “composite” as its single item.
447         // 2. As with easings, if composite modes has fewer items than processed keyframes, repeat the elements in composite modes successively starting from the beginning of
448         //    the list until composite modes has as many items as processed keyframes.
449         if (compositeModes.size() < parsedKeyframes.size()) {
450             size_t initialNumberOfCompositeModes = compositeModes.size();
451             for (i = initialNumberOfCompositeModes; i < parsedKeyframes.size(); ++i)
452                 compositeModes.append(compositeModes[i % initialNumberOfCompositeModes]);
453         }
454         // 3. Assign each value in composite modes that is not auto to the keyframe-specific composite operation on the keyframe with the corresponding position in processed
455         //    keyframes until the end of processed keyframes is reached.
456         for (size_t i = 0; i < compositeModes.size() && i < parsedKeyframes.size(); ++i) {
457             if (compositeModes[i] != CompositeOperationOrAuto::Auto)
458                 parsedKeyframes[i].composite = compositeModes[i];
459         }
460     }
461
462     return { };
463 }
464
465 ExceptionOr<Ref<KeyframeEffect>> KeyframeEffect::create(JSGlobalObject& lexicalGlobalObject, Element* target, Strong<JSObject>&& keyframes, Optional<Variant<double, KeyframeEffectOptions>>&& options)
466 {
467     auto keyframeEffect = adoptRef(*new KeyframeEffect(target));
468
469     if (options) {
470         OptionalEffectTiming timing;
471         auto optionsValue = options.value();
472         if (WTF::holds_alternative<double>(optionsValue)) {
473             Variant<double, String> duration = WTF::get<double>(optionsValue);
474             timing.duration = duration;
475         } else {
476             auto keyframeEffectOptions = WTF::get<KeyframeEffectOptions>(optionsValue);
477             timing = {
478                 keyframeEffectOptions.duration,
479                 keyframeEffectOptions.iterations,
480                 keyframeEffectOptions.delay,
481                 keyframeEffectOptions.endDelay,
482                 keyframeEffectOptions.iterationStart,
483                 keyframeEffectOptions.easing,
484                 keyframeEffectOptions.fill,
485                 keyframeEffectOptions.direction
486             };
487         }
488         auto updateTimingResult = keyframeEffect->updateTiming(timing);
489         if (updateTimingResult.hasException())
490             return updateTimingResult.releaseException();
491     }
492
493     auto processKeyframesResult = keyframeEffect->processKeyframes(lexicalGlobalObject, WTFMove(keyframes));
494     if (processKeyframesResult.hasException())
495         return processKeyframesResult.releaseException();
496
497     return keyframeEffect;
498 }
499
500 ExceptionOr<Ref<KeyframeEffect>> KeyframeEffect::create(JSC::JSGlobalObject&, Ref<KeyframeEffect>&& source)
501 {
502     auto keyframeEffect = adoptRef(*new KeyframeEffect(nullptr));
503     keyframeEffect->copyPropertiesFromSource(WTFMove(source));
504     return keyframeEffect;
505 }
506
507 Ref<KeyframeEffect> KeyframeEffect::create(const Element& target)
508 {
509     return adoptRef(*new KeyframeEffect(const_cast<Element*>(&target)));
510 }
511
512 KeyframeEffect::KeyframeEffect(Element* target)
513     : m_target(target)
514 {
515 }
516
517 void KeyframeEffect::copyPropertiesFromSource(Ref<KeyframeEffect>&& source)
518 {
519     m_target = source->m_target;
520     m_compositeOperation = source->m_compositeOperation;
521     m_iterationCompositeOperation = source->m_iterationCompositeOperation;
522
523     Vector<ParsedKeyframe> parsedKeyframes;
524     for (auto& sourceParsedKeyframe : source->m_parsedKeyframes) {
525         ParsedKeyframe parsedKeyframe;
526         parsedKeyframe.easing = sourceParsedKeyframe.easing;
527         parsedKeyframe.offset = sourceParsedKeyframe.offset;
528         parsedKeyframe.composite = sourceParsedKeyframe.composite;
529         parsedKeyframe.unparsedStyle = sourceParsedKeyframe.unparsedStyle;
530         parsedKeyframe.computedOffset = sourceParsedKeyframe.computedOffset;
531         parsedKeyframe.timingFunction = sourceParsedKeyframe.timingFunction;
532         parsedKeyframe.style = sourceParsedKeyframe.style->mutableCopy();
533         parsedKeyframes.append(WTFMove(parsedKeyframe));
534     }
535     m_parsedKeyframes = WTFMove(parsedKeyframes);
536
537     setFill(source->fill());
538     setDelay(source->delay());
539     setEndDelay(source->endDelay());
540     setDirection(source->direction());
541     setIterations(source->iterations());
542     setTimingFunction(source->timingFunction());
543     setIterationStart(source->iterationStart());
544     setIterationDuration(source->iterationDuration());
545     updateStaticTimingProperties();
546
547     KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
548     for (auto& keyframe : source->m_blendingKeyframes.keyframes()) {
549         KeyframeValue keyframeValue(keyframe.key(), RenderStyle::clonePtr(*keyframe.style()));
550         for (auto propertyId : keyframe.properties())
551             keyframeValue.addProperty(propertyId);
552         keyframeList.insert(WTFMove(keyframeValue));
553     }
554     setBlendingKeyframes(keyframeList);
555 }
556
557 Vector<Strong<JSObject>> KeyframeEffect::getKeyframes(JSGlobalObject& lexicalGlobalObject)
558 {
559     // https://drafts.csswg.org/web-animations-1/#dom-keyframeeffectreadonly-getkeyframes
560
561     auto lock = JSLockHolder { &lexicalGlobalObject };
562
563     // Since keyframes are represented by a partially open-ended dictionary type that is not currently able to be expressed with WebIDL,
564     // the procedure used to prepare the result of this method is defined in prose below:
565     //
566     // 1. Let result be an empty sequence of objects.
567     Vector<Strong<JSObject>> result;
568
569     // 2. Let keyframes be the result of applying the procedure to compute missing keyframe offsets to the keyframes for this keyframe effect.
570
571     // 3. For each keyframe in keyframes perform the following steps:
572     if (is<DeclarativeAnimation>(animation())) {
573         auto computedStyleExtractor = ComputedStyleExtractor(m_target.get());
574         for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
575             // 1. Initialize a dictionary object, output keyframe, using the following definition:
576             //
577             // dictionary BaseComputedKeyframe {
578             //      double?                  offset = null;
579             //      double                   computedOffset;
580             //      DOMString                easing = "linear";
581             //      CompositeOperationOrAuto composite = "auto";
582             // };
583
584             auto& keyframe = m_blendingKeyframes[i];
585
586             // 2. Set offset, computedOffset, easing members of output keyframe to the respective values keyframe offset, computed keyframe offset,
587             // and keyframe-specific timing function of keyframe.
588             BaseComputedKeyframe computedKeyframe;
589             computedKeyframe.offset = keyframe.key();
590             computedKeyframe.computedOffset = keyframe.key();
591             // For CSS transitions, all keyframes should return "linear" since the effect's global timing function applies.
592             computedKeyframe.easing = is<CSSTransition>(animation()) ? "linear" : timingFunctionForKeyframeAtIndex(i)->cssText();
593
594             auto outputKeyframe = convertDictionaryToJS(lexicalGlobalObject, *jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject), computedKeyframe);
595
596             // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
597             auto& style = *keyframe.style();
598             for (auto cssPropertyId : keyframe.properties()) {
599                 if (cssPropertyId == CSSPropertyCustom)
600                     continue;
601                 // 1. Let property name be the result of applying the animation property name to IDL attribute name algorithm to the property name of declaration.
602                 auto propertyName = CSSPropertyIDToIDLAttributeName(cssPropertyId);
603                 // 2. Let IDL value be the result of serializing the property value of declaration by passing declaration to the algorithm to serialize a CSS value.
604                 String idlValue = "";
605                 if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(style, cssPropertyId))
606                     idlValue = cssValue->cssText();
607                 // 3. Let value be the result of converting IDL value to an ECMAScript String value.
608                 auto value = toJS<IDLDOMString>(lexicalGlobalObject, idlValue);
609                 // 4. Call the [[DefineOwnProperty]] internal method on output keyframe with property name property name,
610                 //    Property Descriptor { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true, [[Value]]: value } and Boolean flag false.
611                 JSObject::defineOwnProperty(outputKeyframe, &lexicalGlobalObject, AtomString(propertyName).impl(), PropertyDescriptor(value, 0), false);
612             }
613
614             // 5. Append output keyframe to result.
615             result.append(JSC::Strong<JSC::JSObject> { lexicalGlobalObject.vm(), outputKeyframe });
616         }
617     } else {
618         for (size_t i = 0; i < m_parsedKeyframes.size(); ++i) {
619             // 1. Initialize a dictionary object, output keyframe, using the following definition:
620             //
621             // dictionary BaseComputedKeyframe {
622             //      double?                  offset = null;
623             //      double                   computedOffset;
624             //      DOMString                easing = "linear";
625             //      CompositeOperationOrAuto composite = "auto";
626             // };
627
628             auto& parsedKeyframe = m_parsedKeyframes[i];
629
630             // 2. Set offset, computedOffset, easing, composite members of output keyframe to the respective values keyframe offset, computed keyframe
631             // offset, keyframe-specific timing function and keyframe-specific composite operation of keyframe.
632             BaseComputedKeyframe computedKeyframe;
633             computedKeyframe.offset = parsedKeyframe.offset;
634             computedKeyframe.computedOffset = parsedKeyframe.computedOffset;
635             computedKeyframe.easing = timingFunctionForKeyframeAtIndex(i)->cssText();
636             computedKeyframe.composite = parsedKeyframe.composite;
637
638             auto outputKeyframe = convertDictionaryToJS(lexicalGlobalObject, *jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject), computedKeyframe);
639
640             // 3. For each animation property-value pair specified on keyframe, declaration, perform the following steps:
641             for (auto it = parsedKeyframe.unparsedStyle.begin(), end = parsedKeyframe.unparsedStyle.end(); it != end; ++it) {
642                 // 1. Let property name be the result of applying the animation property name to IDL attribute name algorithm to the property name of declaration.
643                 auto propertyName = CSSPropertyIDToIDLAttributeName(it->key);
644                 // 2. Let IDL value be the result of serializing the property value of declaration by passing declaration to the algorithm to serialize a CSS value.
645                 // 3. Let value be the result of converting IDL value to an ECMAScript String value.
646                 auto value = toJS<IDLDOMString>(lexicalGlobalObject, it->value);
647                 // 4. Call the [[DefineOwnProperty]] internal method on output keyframe with property name property name,
648                 //    Property Descriptor { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true, [[Value]]: value } and Boolean flag false.
649                 JSObject::defineOwnProperty(outputKeyframe, &lexicalGlobalObject, AtomString(propertyName).impl(), PropertyDescriptor(value, 0), false);
650             }
651
652             // 4. Append output keyframe to result.
653             result.append(JSC::Strong<JSC::JSObject> { lexicalGlobalObject.vm(), outputKeyframe });
654         }
655     }
656
657     // 4. Return result.
658     return result;
659 }
660
661 ExceptionOr<void> KeyframeEffect::setKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
662 {
663     return processKeyframes(lexicalGlobalObject, WTFMove(keyframesInput));
664 }
665
666 ExceptionOr<void> KeyframeEffect::processKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
667 {
668     // 1. If object is null, return an empty sequence of keyframes.
669     if (!keyframesInput.get())
670         return { };
671
672     VM& vm = lexicalGlobalObject.vm();
673     auto scope = DECLARE_THROW_SCOPE(vm);
674
675     // 2. Let processed keyframes be an empty sequence of keyframes.
676     Vector<ParsedKeyframe> parsedKeyframes;
677
678     // 3. Let method be the result of GetMethod(object, @@iterator).
679     auto method = keyframesInput.get()->get(&lexicalGlobalObject, vm.propertyNames->iteratorSymbol);
680
681     // 4. Check the completion record of method.
682     RETURN_IF_EXCEPTION(scope, Exception { TypeError });
683
684     // 5. Perform the steps corresponding to the first matching condition from below,
685     Vector<String> unusedEasings;
686     if (!method.isUndefined())
687         processIterableKeyframes(lexicalGlobalObject, WTFMove(keyframesInput), WTFMove(method), parsedKeyframes);
688     else
689         processPropertyIndexedKeyframes(lexicalGlobalObject, WTFMove(keyframesInput), parsedKeyframes, unusedEasings);
690
691     // 6. If processed keyframes is not loosely sorted by offset, throw a TypeError and abort these steps.
692     // 7. If there exist any keyframe in processed keyframes whose keyframe offset is non-null and less than
693     //    zero or greater than one, throw a TypeError and abort these steps.
694     double lastNonNullOffset = -1;
695     for (auto& keyframe : parsedKeyframes) {
696         if (!keyframe.offset)
697             continue;
698         auto offset = keyframe.offset.value();
699         if (offset < lastNonNullOffset || offset < 0 || offset > 1)
700             return Exception { TypeError };
701         lastNonNullOffset = offset;
702     }
703
704     // We take a slight detour from the spec text and compute the missing keyframe offsets right away
705     // since they can be computed up-front.
706     computeMissingKeyframeOffsets(parsedKeyframes);
707
708     // 8. For each frame in processed keyframes, perform the following steps:
709     for (auto& keyframe : parsedKeyframes) {
710         // Let the timing function of frame be the result of parsing the “easing” property on frame using the CSS syntax
711         // defined for the easing property of the AnimationEffectTiming interface.
712         // If parsing the “easing” property fails, throw a TypeError and abort this procedure.
713         auto timingFunctionResult = TimingFunction::createFromCSSText(keyframe.easing);
714         if (timingFunctionResult.hasException())
715             return timingFunctionResult.releaseException();
716         keyframe.timingFunction = timingFunctionResult.returnValue();
717     }
718
719     // 9. Parse each of the values in unused easings using the CSS syntax defined for easing property of the
720     //    AnimationEffectTiming interface, and if any of the values fail to parse, throw a TypeError
721     //    and abort this procedure.
722     for (auto& easing : unusedEasings) {
723         auto timingFunctionResult = TimingFunction::createFromCSSText(easing);
724         if (timingFunctionResult.hasException())
725             return timingFunctionResult.releaseException();
726     }
727
728     m_parsedKeyframes = WTFMove(parsedKeyframes);
729
730     clearBlendingKeyframes();
731
732     return { };
733 }
734
735 void KeyframeEffect::updateBlendingKeyframes(RenderStyle& elementStyle)
736 {
737     if (!m_blendingKeyframes.isEmpty() || !m_target)
738         return;
739
740     KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
741     StyleResolver& styleResolver = m_target->styleResolver();
742
743     for (auto& keyframe : m_parsedKeyframes) {
744         styleResolver.setNewStateWithElement(*m_target);
745         KeyframeValue keyframeValue(keyframe.computedOffset, nullptr);
746
747         auto styleProperties = keyframe.style->immutableCopyIfNeeded();
748         for (unsigned i = 0; i < styleProperties->propertyCount(); ++i)
749             keyframeList.addProperty(styleProperties->propertyAt(i).id());
750
751         auto keyframeRule = StyleRuleKeyframe::create(WTFMove(styleProperties));
752         keyframeValue.setStyle(styleResolver.styleForKeyframe(&elementStyle, keyframeRule.ptr(), keyframeValue));
753         keyframeList.insert(WTFMove(keyframeValue));
754     }
755
756     setBlendingKeyframes(keyframeList);
757 }
758
759 bool KeyframeEffect::forceLayoutIfNeeded()
760 {
761     if (!m_needsForcedLayout || !m_target)
762         return false;
763
764     auto* renderer = m_target->renderer();
765     if (!renderer || !renderer->parent())
766         return false;
767
768     auto* frameView = m_target->document().view();
769     if (!frameView)
770         return false;
771
772     frameView->forceLayout();
773     return true;
774 }
775
776
777 void KeyframeEffect::clearBlendingKeyframes()
778 {
779     m_blendingKeyframesSource = BlendingKeyframesSource::WebAnimation;
780     m_blendingKeyframes.clear();
781 }
782
783 void KeyframeEffect::setBlendingKeyframes(KeyframeList& blendingKeyframes)
784 {
785     m_blendingKeyframes = WTFMove(blendingKeyframes);
786
787     computedNeedsForcedLayout();
788     computeStackingContextImpact();
789     computeShouldRunAccelerated();
790
791     checkForMatchingTransformFunctionLists();
792     checkForMatchingFilterFunctionLists();
793 #if ENABLE(FILTERS_LEVEL_2)
794     checkForMatchingBackdropFilterFunctionLists();
795 #endif
796     checkForMatchingColorFilterFunctionLists();
797 }
798
799 void KeyframeEffect::checkForMatchingTransformFunctionLists()
800 {
801     m_transformFunctionListsMatch = false;
802
803     if (m_blendingKeyframes.size() < 2 || !m_blendingKeyframes.containsProperty(CSSPropertyTransform))
804         return;
805
806     // Empty transforms match anything, so find the first non-empty entry as the reference.
807     size_t numKeyframes = m_blendingKeyframes.size();
808     size_t firstNonEmptyTransformKeyframeIndex = numKeyframes;
809
810     for (size_t i = 0; i < numKeyframes; ++i) {
811         const KeyframeValue& currentKeyframe = m_blendingKeyframes[i];
812         if (currentKeyframe.style()->transform().operations().size()) {
813             firstNonEmptyTransformKeyframeIndex = i;
814             break;
815         }
816     }
817
818     if (firstNonEmptyTransformKeyframeIndex == numKeyframes)
819         return;
820
821     const TransformOperations* firstVal = &m_blendingKeyframes[firstNonEmptyTransformKeyframeIndex].style()->transform();
822     for (size_t i = firstNonEmptyTransformKeyframeIndex + 1; i < numKeyframes; ++i) {
823         const KeyframeValue& currentKeyframe = m_blendingKeyframes[i];
824         const TransformOperations* val = &currentKeyframe.style()->transform();
825
826         // An empty transform list matches anything.
827         if (val->operations().isEmpty())
828             continue;
829
830         if (!firstVal->operationsMatch(*val))
831             return;
832     }
833
834     m_transformFunctionListsMatch = true;
835 }
836
837 bool KeyframeEffect::checkForMatchingFilterFunctionLists(CSSPropertyID propertyID, const std::function<const FilterOperations& (const RenderStyle&)>& filtersGetter) const
838 {
839     if (m_blendingKeyframes.size() < 2 || !m_blendingKeyframes.containsProperty(propertyID))
840         return false;
841
842     // Empty filters match anything, so find the first non-empty entry as the reference.
843     size_t numKeyframes = m_blendingKeyframes.size();
844     size_t firstNonEmptyKeyframeIndex = numKeyframes;
845
846     for (size_t i = 0; i < numKeyframes; ++i) {
847         if (filtersGetter(*m_blendingKeyframes[i].style()).operations().size()) {
848             firstNonEmptyKeyframeIndex = i;
849             break;
850         }
851     }
852
853     if (firstNonEmptyKeyframeIndex == numKeyframes)
854         return false;
855
856     auto& firstVal = filtersGetter(*m_blendingKeyframes[firstNonEmptyKeyframeIndex].style());
857     for (size_t i = firstNonEmptyKeyframeIndex + 1; i < numKeyframes; ++i) {
858         auto& value = filtersGetter(*m_blendingKeyframes[i].style());
859
860         // An empty filter list matches anything.
861         if (value.operations().isEmpty())
862             continue;
863
864         if (!firstVal.operationsMatch(value))
865             return false;
866     }
867
868     return true;
869 }
870
871 void KeyframeEffect::checkForMatchingFilterFunctionLists()
872 {
873     m_filterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyFilter, [] (const RenderStyle& style) -> const FilterOperations& {
874         return style.filter();
875     });
876 }
877
878 #if ENABLE(FILTERS_LEVEL_2)
879 void KeyframeEffect::checkForMatchingBackdropFilterFunctionLists()
880 {
881     m_backdropFilterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyWebkitBackdropFilter, [] (const RenderStyle& style) -> const FilterOperations& {
882         return style.backdropFilter();
883     });
884 }
885 #endif
886
887 void KeyframeEffect::checkForMatchingColorFilterFunctionLists()
888 {
889     m_colorFilterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyAppleColorFilter, [] (const RenderStyle& style) -> const FilterOperations& {
890         return style.appleColorFilter();
891     });
892 }
893
894 void KeyframeEffect::computeDeclarativeAnimationBlendingKeyframes(const RenderStyle* oldStyle, const RenderStyle& newStyle)
895 {
896     ASSERT(is<DeclarativeAnimation>(animation()));
897     if (is<CSSAnimation>(animation()))
898         computeCSSAnimationBlendingKeyframes();
899     else if (is<CSSTransition>(animation()))
900         computeCSSTransitionBlendingKeyframes(oldStyle, newStyle);
901 }
902
903 void KeyframeEffect::computeCSSAnimationBlendingKeyframes()
904 {
905     ASSERT(is<CSSAnimation>(animation()));
906
907     auto cssAnimation = downcast<CSSAnimation>(animation());
908     auto& backingAnimation = cssAnimation->backingAnimation();
909
910     KeyframeList keyframeList(backingAnimation.name());
911     if (auto* styleScope = Style::Scope::forOrdinal(*m_target, backingAnimation.nameStyleScopeOrdinal()))
912         styleScope->resolver().keyframeStylesForAnimation(*m_target, &cssAnimation->unanimatedStyle(), keyframeList);
913
914     // Ensure resource loads for all the frames.
915     for (auto& keyframe : keyframeList.keyframes()) {
916         if (auto* style = const_cast<RenderStyle*>(keyframe.style()))
917             Style::loadPendingResources(*style, m_target->document(), m_target.get());
918     }
919
920     m_blendingKeyframesSource = BlendingKeyframesSource::CSSAnimation;
921     setBlendingKeyframes(keyframeList);
922 }
923
924 void KeyframeEffect::computeCSSTransitionBlendingKeyframes(const RenderStyle* oldStyle, const RenderStyle& newStyle)
925 {
926     ASSERT(is<CSSTransition>(animation()));
927
928     if (!oldStyle || m_blendingKeyframes.size())
929         return;
930
931     auto property = downcast<CSSTransition>(animation())->property();
932
933     auto toStyle = RenderStyle::clonePtr(newStyle);
934     if (m_target)
935         Style::loadPendingResources(*toStyle, m_target->document(), m_target.get());
936
937     KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
938     keyframeList.addProperty(property);
939
940     KeyframeValue fromKeyframeValue(0, RenderStyle::clonePtr(*oldStyle));
941     fromKeyframeValue.addProperty(property);
942     keyframeList.insert(WTFMove(fromKeyframeValue));
943
944     KeyframeValue toKeyframeValue(1, WTFMove(toStyle));
945     toKeyframeValue.addProperty(property);
946     keyframeList.insert(WTFMove(toKeyframeValue));
947
948     m_blendingKeyframesSource = BlendingKeyframesSource::CSSTransition;
949     setBlendingKeyframes(keyframeList);
950 }
951
952 void KeyframeEffect::computedNeedsForcedLayout()
953 {
954     m_needsForcedLayout = false;
955     if (is<CSSTransition>(animation()) || !m_blendingKeyframes.containsProperty(CSSPropertyTransform))
956         return;
957
958     size_t numberOfKeyframes = m_blendingKeyframes.size();
959     for (size_t i = 0; i < numberOfKeyframes; i++) {
960         auto* keyframeStyle = m_blendingKeyframes[i].style();
961         if (!keyframeStyle) {
962             ASSERT_NOT_REACHED();
963             continue;
964         }
965         if (keyframeStyle->hasTransform()) {
966             auto& transformOperations = keyframeStyle->transform();
967             for (const auto& operation : transformOperations.operations()) {
968                 if (operation->isTranslateTransformOperationType()) {
969                     auto translation = downcast<TranslateTransformOperation>(operation.get());
970                     if (translation->x().isPercent() || translation->y().isPercent()) {
971                         m_needsForcedLayout = true;
972                         return;
973                     }
974                 }
975             }
976         }
977     }
978 }
979
980 void KeyframeEffect::computeStackingContextImpact()
981 {
982     m_triggersStackingContext = false;
983     for (auto cssPropertyId : m_blendingKeyframes.properties()) {
984         if (WillChangeData::propertyCreatesStackingContext(cssPropertyId)) {
985             m_triggersStackingContext = true;
986             break;
987         }
988     }
989 }
990
991 void KeyframeEffect::setTarget(RefPtr<Element>&& newTarget)
992 {
993     if (m_target == newTarget)
994         return;
995
996     auto previousTarget = std::exchange(m_target, WTFMove(newTarget));
997
998     if (auto* effectAnimation = animation())
999         effectAnimation->effectTargetDidChange(previousTarget.get(), m_target.get());
1000
1001     clearBlendingKeyframes();
1002
1003     // We need to invalidate the effect now that the target has changed
1004     // to ensure the effect's styles are applied to the new target right away.
1005     invalidate();
1006
1007     // Likewise, we need to invalidate styles on the previous target so that
1008     // any animated styles are removed immediately.
1009     invalidateElement(previousTarget.get());
1010 }
1011
1012 void KeyframeEffect::apply(RenderStyle& targetStyle)
1013 {
1014     if (!m_target)
1015         return;
1016
1017     updateBlendingKeyframes(targetStyle);
1018
1019     updateAcceleratedAnimationState();
1020
1021     auto progress = getComputedTiming().progress;
1022     if (!progress)
1023         return;
1024
1025     setAnimatedPropertiesInStyle(targetStyle, progress.value());
1026
1027     // https://w3c.github.io/web-animations/#side-effects-section
1028     // For every property targeted by at least one animation effect that is current or in effect, the user agent
1029     // must act as if the will-change property ([css-will-change-1]) on the target element includes the property.
1030     if (m_triggersStackingContext && targetStyle.hasAutoZIndex())
1031         targetStyle.setZIndex(0);
1032 }
1033
1034 void KeyframeEffect::invalidate()
1035 {
1036     invalidateElement(m_target.get());
1037 }
1038
1039 void KeyframeEffect::computeShouldRunAccelerated()
1040 {
1041     m_shouldRunAccelerated = hasBlendingKeyframes();
1042     for (auto cssPropertyId : m_blendingKeyframes.properties()) {
1043         if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId)) {
1044             m_shouldRunAccelerated = false;
1045             return;
1046         }
1047     }
1048 }
1049
1050 void KeyframeEffect::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
1051 {
1052     if (!m_target || !animation())
1053         return;
1054
1055     auto progress = getComputedTiming().progress;
1056     if (!progress)
1057         return;
1058
1059     if (!animatedStyle)
1060         animatedStyle = RenderStyle::clonePtr(renderer()->style());
1061
1062     setAnimatedPropertiesInStyle(*animatedStyle.get(), progress.value());
1063 }
1064
1065 void KeyframeEffect::setAnimatedPropertiesInStyle(RenderStyle& targetStyle, double iterationProgress)
1066 {
1067     auto& properties = m_blendingKeyframes.properties();
1068
1069     // In the case of CSS Transitions we already know that there are only two keyframes, one where offset=0 and one where offset=1,
1070     // and only a single CSS property so we can simply blend based on the style available on those keyframes with the provided iteration
1071     // progress which already accounts for the transition's timing function.
1072     if (m_blendingKeyframesSource == BlendingKeyframesSource::CSSTransition) {
1073         ASSERT(properties.size() == 1);
1074         CSSPropertyAnimation::blendProperties(this, *properties.begin(), &targetStyle, m_blendingKeyframes[0].style(), m_blendingKeyframes[1].style(), iterationProgress);
1075         return;
1076     }
1077
1078     // 4.4.3. The effect value of a keyframe effect
1079     // https://drafts.csswg.org/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect
1080     //
1081     // The effect value of a single property referenced by a keyframe effect as one of its target properties,
1082     // for a given iteration progress, current iteration and underlying value is calculated as follows.
1083
1084     updateBlendingKeyframes(targetStyle);
1085     if (m_blendingKeyframes.isEmpty())
1086         return;
1087
1088     for (auto cssPropertyId : properties) {
1089         // 1. If iteration progress is unresolved abort this procedure.
1090         // 2. Let target property be the longhand property for which the effect value is to be calculated.
1091         // 3. If animation type of the target property is not animatable abort this procedure since the effect cannot be applied.
1092         // 4. Define the neutral value for composition as a value which, when combined with an underlying value using the add composite operation,
1093         //    produces the underlying value.
1094
1095         // 5. Let property-specific keyframes be the result of getting the set of computed keyframes for this keyframe effect.
1096         // 6. Remove any keyframes from property-specific keyframes that do not have a property value for target property.
1097         unsigned numberOfKeyframesWithZeroOffset = 0;
1098         unsigned numberOfKeyframesWithOneOffset = 0;
1099         Vector<Optional<size_t>> propertySpecificKeyframes;
1100         for (size_t i = 0; i < m_blendingKeyframes.size(); ++i) {
1101             auto& keyframe = m_blendingKeyframes[i];
1102             auto offset = keyframe.key();
1103             if (!keyframe.containsProperty(cssPropertyId)) {
1104                 // If we're dealing with a CSS animation, we consider the first and last keyframes to always have the property listed
1105                 // since the underlying style was provided and should be captured.
1106                 if (m_blendingKeyframesSource == BlendingKeyframesSource::WebAnimation || (offset && offset < 1))
1107                     continue;
1108             }
1109             if (!offset)
1110                 numberOfKeyframesWithZeroOffset++;
1111             if (offset == 1)
1112                 numberOfKeyframesWithOneOffset++;
1113             propertySpecificKeyframes.append(i);
1114         }
1115
1116         // 7. If property-specific keyframes is empty, return underlying value.
1117         if (propertySpecificKeyframes.isEmpty())
1118             continue;
1119
1120         // 8. If there is no keyframe in property-specific keyframes with a computed keyframe offset of 0, create a new keyframe with a computed keyframe
1121         //    offset of 0, a property value set to the neutral value for composition, and a composite operation of add, and prepend it to the beginning of
1122         //    property-specific keyframes.
1123         if (!numberOfKeyframesWithZeroOffset) {
1124             propertySpecificKeyframes.insert(0, WTF::nullopt);
1125             numberOfKeyframesWithZeroOffset = 1;
1126         }
1127
1128         // 9. Similarly, if there is no keyframe in property-specific keyframes with a computed keyframe offset of 1, create a new keyframe with a computed
1129         //    keyframe offset of 1, a property value set to the neutral value for composition, and a composite operation of add, and append it to the end of
1130         //    property-specific keyframes.
1131         if (!numberOfKeyframesWithOneOffset) {
1132             propertySpecificKeyframes.append(WTF::nullopt);
1133             numberOfKeyframesWithOneOffset = 1;
1134         }
1135
1136         // 10. Let interval endpoints be an empty sequence of keyframes.
1137         Vector<Optional<size_t>> intervalEndpoints;
1138
1139         // 11. Populate interval endpoints by following the steps from the first matching condition from below:
1140         if (iterationProgress < 0 && numberOfKeyframesWithZeroOffset > 1) {
1141             // If iteration progress < 0 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 0,
1142             // Add the first keyframe in property-specific keyframes to interval endpoints.
1143             intervalEndpoints.append(propertySpecificKeyframes.first());
1144         } else if (iterationProgress >= 1 && numberOfKeyframesWithOneOffset > 1) {
1145             // If iteration progress ≥ 1 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 1,
1146             // Add the last keyframe in property-specific keyframes to interval endpoints.
1147             intervalEndpoints.append(propertySpecificKeyframes.last());
1148         } else {
1149             // Otherwise,
1150             // 1. Append to interval endpoints the last keyframe in property-specific keyframes whose computed keyframe offset is less than or equal
1151             //    to iteration progress and less than 1. If there is no such keyframe (because, for example, the iteration progress is negative),
1152             //    add the last keyframe whose computed keyframe offset is 0.
1153             // 2. Append to interval endpoints the next keyframe in property-specific keyframes after the one added in the previous step.
1154             size_t indexOfLastKeyframeWithZeroOffset = 0;
1155             int indexOfFirstKeyframeToAddToIntervalEndpoints = -1;
1156             for (size_t i = 0; i < propertySpecificKeyframes.size(); ++i) {
1157                 auto keyframeIndex = propertySpecificKeyframes[i];
1158                 auto offset = [&] () -> double {
1159                     if (!keyframeIndex)
1160                         return i ? 1 : 0;
1161                     return m_blendingKeyframes[keyframeIndex.value()].key();
1162                 }();
1163                 if (!offset)
1164                     indexOfLastKeyframeWithZeroOffset = i;
1165                 if (offset <= iterationProgress && offset < 1)
1166                     indexOfFirstKeyframeToAddToIntervalEndpoints = i;
1167                 else
1168                     break;
1169             }
1170
1171             if (indexOfFirstKeyframeToAddToIntervalEndpoints >= 0) {
1172                 intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints]);
1173                 intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints + 1]);
1174             } else {
1175                 ASSERT(indexOfLastKeyframeWithZeroOffset < propertySpecificKeyframes.size() - 1);
1176                 intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset]);
1177                 intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset + 1]);
1178             }
1179         }
1180
1181         // 12. For each keyframe in interval endpoints…
1182         // FIXME: we don't support this step yet since we don't deal with any composite operation other than "replace".
1183
1184         // 13. If there is only one keyframe in interval endpoints return the property value of target property on that keyframe.
1185         if (intervalEndpoints.size() == 1) {
1186             auto keyframeIndex = intervalEndpoints[0];
1187             auto keyframeStyle = !keyframeIndex ? &targetStyle : m_blendingKeyframes[keyframeIndex.value()].style();
1188             CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, keyframeStyle, keyframeStyle, 0);
1189             continue;
1190         }
1191
1192         // 14. Let start offset be the computed keyframe offset of the first keyframe in interval endpoints.
1193         auto startKeyframeIndex = intervalEndpoints.first();
1194         auto startOffset = !startKeyframeIndex ? 0 : m_blendingKeyframes[startKeyframeIndex.value()].key();
1195
1196         // 15. Let end offset be the computed keyframe offset of last keyframe in interval endpoints.
1197         auto endKeyframeIndex = intervalEndpoints.last();
1198         auto endOffset = !endKeyframeIndex ? 1 : m_blendingKeyframes[endKeyframeIndex.value()].key();
1199
1200         // 16. Let interval distance be the result of evaluating (iteration progress - start offset) / (end offset - start offset).
1201         auto intervalDistance = (iterationProgress - startOffset) / (endOffset - startOffset);
1202
1203         // 17. Let transformed distance be the result of evaluating the timing function associated with the first keyframe in interval endpoints
1204         //     passing interval distance as the input progress.
1205         auto transformedDistance = intervalDistance;
1206         if (startKeyframeIndex) {
1207             if (auto duration = iterationDuration()) {
1208                 auto rangeDuration = (endOffset - startOffset) * duration.seconds();
1209                 if (auto* timingFunction = timingFunctionForKeyframeAtIndex(startKeyframeIndex.value()))
1210                     transformedDistance = timingFunction->transformTime(intervalDistance, rangeDuration);
1211             }
1212         }
1213
1214         // 18. Return the result of applying the interpolation procedure defined by the animation type of the target property, to the values of the target
1215         //     property specified on the two keyframes in interval endpoints taking the first such value as Vstart and the second as Vend and using transformed
1216         //     distance as the interpolation parameter p.
1217         auto startStyle = !startKeyframeIndex ? &targetStyle : m_blendingKeyframes[startKeyframeIndex.value()].style();
1218         auto endStyle = !endKeyframeIndex ? &targetStyle : m_blendingKeyframes[endKeyframeIndex.value()].style();
1219         CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, startStyle, endStyle, transformedDistance);
1220     }
1221 }
1222
1223 TimingFunction* KeyframeEffect::timingFunctionForKeyframeAtIndex(size_t index)
1224 {
1225     if (!m_parsedKeyframes.isEmpty())
1226         return m_parsedKeyframes[index].timingFunction.get();
1227
1228     auto effectAnimation = animation();
1229     if (is<DeclarativeAnimation>(effectAnimation)) {
1230         // If we're dealing with a CSS Animation, the timing function is specified either on the keyframe itself.
1231         if (is<CSSAnimation>(effectAnimation)) {
1232             if (auto* timingFunction = m_blendingKeyframes[index].timingFunction())
1233                 return timingFunction;
1234         }
1235
1236         // Failing that, or for a CSS Transition, the timing function is inherited from the backing Animation object.
1237         return downcast<DeclarativeAnimation>(effectAnimation)->backingAnimation().timingFunction();
1238     }
1239
1240     return nullptr;
1241 }
1242
1243 void KeyframeEffect::updateAcceleratedAnimationState()
1244 {
1245     if (!m_shouldRunAccelerated)
1246         return;
1247
1248     if (!renderer()) {
1249         if (isRunningAccelerated())
1250             addPendingAcceleratedAction(AcceleratedAction::Stop);
1251         return;
1252     }
1253
1254     auto localTime = animation()->currentTime();
1255
1256     // If we don't have a localTime or localTime < 0, we either don't have a start time or we're before the startTime
1257     // so we shouldn't be running.
1258     if (!localTime || localTime.value() < 0_s) {
1259         if (isRunningAccelerated())
1260             addPendingAcceleratedAction(AcceleratedAction::Stop);
1261         return;
1262     }
1263
1264     auto playState = animation()->playState();
1265     if (playState == WebAnimation::PlayState::Paused) {
1266         if (m_lastRecordedAcceleratedAction != AcceleratedAction::Pause) {
1267             if (m_lastRecordedAcceleratedAction == AcceleratedAction::Stop)
1268                 addPendingAcceleratedAction(AcceleratedAction::Play);
1269             addPendingAcceleratedAction(AcceleratedAction::Pause);
1270         }
1271         return;
1272     }
1273
1274     if (playState == WebAnimation::PlayState::Finished) {
1275         if (isRunningAccelerated())
1276             addPendingAcceleratedAction(AcceleratedAction::Stop);
1277         else {
1278             m_lastRecordedAcceleratedAction = AcceleratedAction::Stop;
1279             m_pendingAcceleratedActions.clear();
1280             animation()->acceleratedStateDidChange();
1281         }
1282         return;
1283     }
1284
1285     if (playState == WebAnimation::PlayState::Running && localTime >= 0_s) {
1286         if (m_lastRecordedAcceleratedAction != AcceleratedAction::Play)
1287             addPendingAcceleratedAction(AcceleratedAction::Play);
1288         return;
1289     }
1290 }
1291
1292 void KeyframeEffect::addPendingAcceleratedAction(AcceleratedAction action)
1293 {
1294     if (action == AcceleratedAction::Stop)
1295         m_pendingAcceleratedActions.clear();
1296     m_pendingAcceleratedActions.append(action);
1297     if (action != AcceleratedAction::Seek)
1298         m_lastRecordedAcceleratedAction = action;
1299     animation()->acceleratedStateDidChange();
1300 }
1301
1302 void KeyframeEffect::animationDidSeek()
1303 {
1304     // There is no need to seek if we're not playing an animation already. If seeking
1305     // means we're moving into an active lexicalGlobalObject, we'll pick this up in apply().
1306     if (m_shouldRunAccelerated && isRunningAccelerated())
1307         addPendingAcceleratedAction(AcceleratedAction::Seek);
1308 }
1309
1310 void KeyframeEffect::animationSuspensionStateDidChange(bool animationIsSuspended)
1311 {
1312     if (m_shouldRunAccelerated)
1313         addPendingAcceleratedAction(animationIsSuspended ? AcceleratedAction::Pause : AcceleratedAction::Play);
1314 }
1315
1316 void KeyframeEffect::applyPendingAcceleratedActions()
1317 {
1318     // Once an accelerated animation has been committed, we no longer want to force a layout.
1319     // This should have been performed by a call to forceLayoutIfNeeded() prior to applying
1320     // pending accelerated actions.
1321     m_needsForcedLayout = false;
1322
1323     if (m_pendingAcceleratedActions.isEmpty())
1324         return;
1325
1326     auto* renderer = this->renderer();
1327     if (!renderer || !renderer->isComposited())
1328         return;
1329
1330     auto pendingAcceleratedActions = m_pendingAcceleratedActions;
1331     m_pendingAcceleratedActions.clear();
1332
1333     auto* compositedRenderer = downcast<RenderBoxModelObject>(renderer);
1334
1335     // To simplify the code we use a default of 0s for an unresolved current time since for a Stop action that is acceptable.
1336     auto timeOffset = animation()->currentTime().valueOr(0_s).seconds() - delay().seconds();
1337
1338     for (const auto& action : pendingAcceleratedActions) {
1339         switch (action) {
1340         case AcceleratedAction::Play:
1341             if (!compositedRenderer->startAnimation(timeOffset, backingAnimationForCompositedRenderer(), m_blendingKeyframes)) {
1342                 m_shouldRunAccelerated = false;
1343                 m_lastRecordedAcceleratedAction = AcceleratedAction::Stop;
1344                 animation()->acceleratedStateDidChange();
1345                 return;
1346             }
1347             break;
1348         case AcceleratedAction::Pause:
1349             compositedRenderer->animationPaused(timeOffset, m_blendingKeyframes.animationName());
1350             break;
1351         case AcceleratedAction::Seek:
1352             compositedRenderer->animationSeeked(timeOffset, m_blendingKeyframes.animationName());
1353             break;
1354         case AcceleratedAction::Stop:
1355             compositedRenderer->animationFinished(m_blendingKeyframes.animationName());
1356             if (!m_target->document().renderTreeBeingDestroyed())
1357                 m_target->invalidateStyleAndLayerComposition();
1358             break;
1359         }
1360     }
1361 }
1362
1363 Ref<const Animation> KeyframeEffect::backingAnimationForCompositedRenderer() const
1364 {
1365     auto effectAnimation = animation();
1366     if (is<DeclarativeAnimation>(effectAnimation))
1367         return downcast<DeclarativeAnimation>(effectAnimation)->backingAnimation();
1368
1369     // FIXME: The iterationStart and endDelay AnimationEffectTiming properties do not have
1370     // corresponding Animation properties.
1371     auto animation = Animation::create();
1372     animation->setDuration(iterationDuration().seconds());
1373     animation->setDelay(delay().seconds());
1374     animation->setIterationCount(iterations());
1375     animation->setTimingFunction(timingFunction()->clone());
1376
1377     switch (fill()) {
1378     case FillMode::None:
1379     case FillMode::Auto:
1380         animation->setFillMode(AnimationFillMode::None);
1381         break;
1382     case FillMode::Backwards:
1383         animation->setFillMode(AnimationFillMode::Backwards);
1384         break;
1385     case FillMode::Forwards:
1386         animation->setFillMode(AnimationFillMode::Forwards);
1387         break;
1388     case FillMode::Both:
1389         animation->setFillMode(AnimationFillMode::Both);
1390         break;
1391     }
1392
1393     switch (direction()) {
1394     case PlaybackDirection::Normal:
1395         animation->setDirection(Animation::AnimationDirectionNormal);
1396         break;
1397     case PlaybackDirection::Alternate:
1398         animation->setDirection(Animation::AnimationDirectionAlternate);
1399         break;
1400     case PlaybackDirection::Reverse:
1401         animation->setDirection(Animation::AnimationDirectionReverse);
1402         break;
1403     case PlaybackDirection::AlternateReverse:
1404         animation->setDirection(Animation::AnimationDirectionAlternateReverse);
1405         break;
1406     }
1407
1408     return animation;
1409 }
1410
1411 RenderElement* KeyframeEffect::renderer() const
1412 {
1413     return m_target ? m_target->renderer() : nullptr;
1414 }
1415
1416 const RenderStyle& KeyframeEffect::currentStyle() const
1417 {
1418     if (auto* renderer = this->renderer())
1419         return renderer->style();
1420     return RenderStyle::defaultStyle();
1421 }
1422
1423 bool KeyframeEffect::computeExtentOfTransformAnimation(LayoutRect& bounds) const
1424 {
1425     ASSERT(m_blendingKeyframes.containsProperty(CSSPropertyTransform));
1426
1427     if (!is<RenderBox>(renderer()))
1428         return true; // Non-boxes don't get transformed;
1429
1430     auto& box = downcast<RenderBox>(*renderer());
1431     auto rendererBox = snapRectToDevicePixels(box.borderBoxRect(), box.document().deviceScaleFactor());
1432
1433     auto cumulativeBounds = bounds;
1434
1435     for (const auto& keyframe : m_blendingKeyframes.keyframes()) {
1436         const auto* keyframeStyle = keyframe.style();
1437
1438         // FIXME: maybe for declarative animations we always say it's true for the first and last keyframe.
1439         if (!keyframe.containsProperty(CSSPropertyTransform)) {
1440             // If the first keyframe is missing transform style, use the current style.
1441             if (!keyframe.key())
1442                 keyframeStyle = &box.style();
1443             else
1444                 continue;
1445         }
1446
1447         auto keyframeBounds = bounds;
1448
1449         bool canCompute;
1450         if (transformFunctionListsMatch())
1451             canCompute = computeTransformedExtentViaTransformList(rendererBox, *keyframeStyle, keyframeBounds);
1452         else
1453             canCompute = computeTransformedExtentViaMatrix(rendererBox, *keyframeStyle, keyframeBounds);
1454
1455         if (!canCompute)
1456             return false;
1457
1458         cumulativeBounds.unite(keyframeBounds);
1459     }
1460
1461     bounds = cumulativeBounds;
1462     return true;
1463 }
1464
1465 static bool containsRotation(const Vector<RefPtr<TransformOperation>>& operations)
1466 {
1467     for (const auto& operation : operations) {
1468         if (operation->type() == TransformOperation::ROTATE)
1469             return true;
1470     }
1471     return false;
1472 }
1473
1474 bool KeyframeEffect::computeTransformedExtentViaTransformList(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
1475 {
1476     FloatRect floatBounds = bounds;
1477     FloatPoint transformOrigin;
1478
1479     bool applyTransformOrigin = containsRotation(style.transform().operations()) || style.transform().affectedByTransformOrigin();
1480     if (applyTransformOrigin) {
1481         transformOrigin.setX(rendererBox.x() + floatValueForLength(style.transformOriginX(), rendererBox.width()));
1482         transformOrigin.setY(rendererBox.y() + floatValueForLength(style.transformOriginY(), rendererBox.height()));
1483         // Ignore transformOriginZ because we'll bail if we encounter any 3D transforms.
1484
1485         floatBounds.moveBy(-transformOrigin);
1486     }
1487
1488     for (const auto& operation : style.transform().operations()) {
1489         if (operation->type() == TransformOperation::ROTATE) {
1490             // For now, just treat this as a full rotation. This could take angle into account to reduce inflation.
1491             floatBounds = boundsOfRotatingRect(floatBounds);
1492         } else {
1493             TransformationMatrix transform;
1494             operation->apply(transform, rendererBox.size());
1495             if (!transform.isAffine())
1496                 return false;
1497
1498             if (operation->type() == TransformOperation::MATRIX || operation->type() == TransformOperation::MATRIX_3D) {
1499                 TransformationMatrix::Decomposed2Type toDecomp;
1500                 transform.decompose2(toDecomp);
1501                 // Any rotation prevents us from using a simple start/end rect union.
1502                 if (toDecomp.angle)
1503                     return false;
1504             }
1505
1506             floatBounds = transform.mapRect(floatBounds);
1507         }
1508     }
1509
1510     if (applyTransformOrigin)
1511         floatBounds.moveBy(transformOrigin);
1512
1513     bounds = LayoutRect(floatBounds);
1514     return true;
1515 }
1516
1517 bool KeyframeEffect::computeTransformedExtentViaMatrix(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
1518 {
1519     TransformationMatrix transform;
1520     style.applyTransform(transform, rendererBox, RenderStyle::IncludeTransformOrigin);
1521     if (!transform.isAffine())
1522         return false;
1523
1524     TransformationMatrix::Decomposed2Type fromDecomp;
1525     transform.decompose2(fromDecomp);
1526     // Any rotation prevents us from using a simple start/end rect union.
1527     if (fromDecomp.angle)
1528         return false;
1529
1530     bounds = LayoutRect(transform.mapRect(bounds));
1531     return true;
1532 }
1533
1534 } // namespace WebCore