0e55b69ec8eae22cf04f27d84fd802902de4b4d9
[WebKit-https.git] / Source / WebCore / animation / KeyframeEffect.cpp
1 /*
2  * Copyright (C) 2017 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 "CSSPropertyAnimation.h"
31 #include "Element.h"
32 #include "RenderStyle.h"
33 #include "StyleProperties.h"
34 #include "StyleResolver.h"
35 #include "WillChangeData.h"
36 #include <wtf/UUID.h>
37
38 namespace WebCore {
39 using namespace JSC;
40
41 ExceptionOr<Ref<KeyframeEffect>> KeyframeEffect::create(ExecState& state, Element* target, Strong<JSObject>&& keyframes)
42 {
43     auto keyframeEffect = adoptRef(*new KeyframeEffect(target));
44
45     auto setKeyframesResult = keyframeEffect->setKeyframes(state, WTFMove(keyframes));
46     if (setKeyframesResult.hasException())
47         return setKeyframesResult.releaseException();
48
49     return WTFMove(keyframeEffect);
50 }
51
52 KeyframeEffect::KeyframeEffect(Element* target)
53     : AnimationEffect(KeyframeEffectClass)
54     , m_target(target)
55     , m_keyframes(emptyString())
56 {
57 }
58
59 ExceptionOr<void> KeyframeEffect::setKeyframes(ExecState& state, Strong<JSObject>&& keyframes)
60 {
61     auto processKeyframesResult = processKeyframes(state, WTFMove(keyframes));
62     if (processKeyframesResult.hasException())
63         return processKeyframesResult.releaseException();
64     return { };
65 }
66
67 ExceptionOr<void> KeyframeEffect::processKeyframes(ExecState& state, Strong<JSObject>&& keyframes)
68 {
69     // FIXME: We only have primitive to-from parsing, for full support see webkit.org/b/179708.
70     // Full specification is at https://w3c.github.io/web-animations/#processing-a-keyframes-argument.
71
72     if (!m_target || !keyframes)
73         return { };
74
75     if (!isJSArray(keyframes.get()))
76         return Exception { TypeError };
77
78     KeyframeList newKeyframes("keyframe-effect-" + createCanonicalUUIDString());
79
80     VM& vm = state.vm();
81     auto scope = DECLARE_THROW_SCOPE(vm);
82
83     StyleResolver& styleResolver = m_target->styleResolver();
84     auto parserContext = CSSParserContext(HTMLStandardMode);
85
86     auto* array = jsCast<const JSArray*>(keyframes.get());
87     auto length = array->length();
88     if (length != 2)
89         return Exception { TypeError };
90
91     for (unsigned i = 0; i < length; ++i) {
92         const JSValue value = array->getIndex(&state, i);
93         if (scope.exception() || !value || !value.isObject())
94             return Exception { TypeError };
95         JSObject* keyframe = value.toObject(&state);
96         PropertyNameArray ownPropertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
97         JSObject::getOwnPropertyNames(keyframe, &state, ownPropertyNames, EnumerationMode());
98         size_t numberOfProperties = ownPropertyNames.size();
99
100         StringBuilder cssText;
101         for (size_t j = 0; j < numberOfProperties; ++j) {
102             cssText.append(ownPropertyNames[j].string());
103             cssText.appendLiteral(": ");
104             cssText.append(keyframe->get(&state, ownPropertyNames[j]).toWTFString(&state));
105             cssText.appendLiteral("; ");
106         }
107
108         auto renderStyle = RenderStyle::createPtr();
109         auto styleProperties = MutableStyleProperties::create();
110         styleProperties->parseDeclaration(cssText.toString(), parserContext);
111         unsigned numberOfCSSProperties = styleProperties->propertyCount();
112
113         KeyframeValue keyframeValue(0, nullptr);
114         for (unsigned k = 0; k < numberOfCSSProperties; ++k) {
115             auto cssPropertyId = styleProperties->propertyAt(k).id();
116             keyframeValue.addProperty(cssPropertyId);
117             newKeyframes.addProperty(cssPropertyId);
118             styleResolver.applyPropertyToStyle(cssPropertyId, styleProperties->propertyAt(k).value(), WTFMove(renderStyle));
119             renderStyle = styleResolver.state().takeStyle();
120         }
121
122         keyframeValue.setKey(i);
123         keyframeValue.setStyle(RenderStyle::clonePtr(*renderStyle));
124         newKeyframes.insert(WTFMove(keyframeValue));
125     }
126
127     m_keyframes = WTFMove(newKeyframes);
128
129     computeStackingContextImpact();
130
131     return { };
132 }
133
134 void KeyframeEffect::computeStackingContextImpact()
135 {
136     m_triggersStackingContext = false;
137     for (auto cssPropertyId : m_keyframes.properties()) {
138         if (WillChangeData::propertyCreatesStackingContext(cssPropertyId)) {
139             m_triggersStackingContext = true;
140             break;
141         }
142     }
143 }
144
145 void KeyframeEffect::applyAtLocalTime(Seconds localTime, RenderStyle& targetStyle)
146 {
147     if (!m_target)
148         return;
149
150     if (m_startedAccelerated && localTime >= timing()->duration()) {
151         m_startedAccelerated = false;
152         animation()->acceleratedRunningStateDidChange();
153     }
154
155     // FIXME: Assume animations only apply in the range [0, duration[
156     // until we support fill modes, delays and iterations.
157     if (localTime < 0_s || localTime >= timing()->duration())
158         return;
159
160     if (!timing()->duration())
161         return;
162
163     bool needsToStartAccelerated = false;
164
165     if (!m_started && !m_startedAccelerated) {
166         needsToStartAccelerated = shouldRunAccelerated();
167         m_startedAccelerated = needsToStartAccelerated;
168         if (needsToStartAccelerated)
169             animation()->acceleratedRunningStateDidChange();
170     }
171     m_started = true;
172
173     if (!needsToStartAccelerated && !m_startedAccelerated) {
174         float progress = localTime / timing()->duration();
175         for (auto cssPropertyId : m_keyframes.properties())
176             CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, m_keyframes[0].style(), m_keyframes[1].style(), progress);
177     }
178
179     // https://w3c.github.io/web-animations/#side-effects-section
180     // For every property targeted by at least one animation effect that is current or in effect, the user agent
181     // must act as if the will-change property ([css-will-change-1]) on the target element includes the property.
182     if (m_triggersStackingContext && targetStyle.hasAutoZIndex())
183         targetStyle.setZIndex(0);
184 }
185
186 bool KeyframeEffect::shouldRunAccelerated()
187 {
188     for (auto cssPropertyId : m_keyframes.properties()) {
189         if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId))
190             return false;
191     }
192     return true;
193 }
194
195 void KeyframeEffect::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
196 {
197     if (!animation() || !timing()->duration())
198         return;
199
200     auto localTime = animation()->currentTime();
201
202     // FIXME: Assume animations only apply in the range [0, duration[
203     // until we support fill modes, delays and iterations.
204     if (!localTime || localTime < 0_s || localTime >= timing()->duration())
205         return;
206
207     if (!m_keyframes.size())
208         return;
209
210     if (!animatedStyle)
211         animatedStyle = RenderStyle::clonePtr(renderer()->style());
212
213     for (auto cssPropertyId : m_keyframes.properties()) {
214         float progress = localTime.value() / timing()->duration();
215         CSSPropertyAnimation::blendProperties(this, cssPropertyId, animatedStyle.get(), m_keyframes[0].style(), m_keyframes[1].style(), progress);
216     }
217 }
218
219 void KeyframeEffect::startOrStopAccelerated()
220 {
221     auto* renderer = this->renderer();
222     if (!renderer || !renderer->isComposited())
223         return;
224
225     auto* compositedRenderer = downcast<RenderBoxModelObject>(renderer);
226     if (m_startedAccelerated) {
227         auto animation = Animation::create();
228         animation->setDuration(timing()->duration().value());
229         compositedRenderer->startAnimation(0, animation.ptr(), m_keyframes);
230     } else {
231         compositedRenderer->animationFinished(m_keyframes.animationName());
232         if (!m_target->document().renderTreeBeingDestroyed())
233             m_target->invalidateStyleAndLayerComposition();
234     }
235 }
236
237 RenderElement* KeyframeEffect::renderer() const
238 {
239     return m_target ? m_target->renderer() : nullptr;
240 }
241
242 const RenderStyle& KeyframeEffect::currentStyle() const
243 {
244     if (auto* renderer = this->renderer())
245         return renderer->style();
246     return RenderStyle::defaultStyle();
247 }
248
249 } // namespace WebCore