blob: c886de60e0fd9ac0b5fd5311c296dd622da15f3c [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/css_animations_adapter.h"
#include <memory>
#include <vector>
#include "cobalt/base/tokens.h"
#include "cobalt/cssom/css_declared_style_data.h"
#include "cobalt/cssom/css_keyframes_rule.h"
#include "cobalt/cssom/property_definitions.h"
#include "cobalt/cssom/timing_function_list_value.h"
#include "cobalt/dom/animation_event.h"
#include "cobalt/dom/dom_animatable.h"
#include "cobalt/web_animations/keyframe.h"
#include "cobalt/web_animations/keyframe_effect_read_only.h"
namespace cobalt {
namespace dom {
CSSAnimationsAdapter::CSSAnimationsAdapter(
const scoped_refptr<dom::DOMAnimatable>& target)
: animatable_(target) {}
CSSAnimationsAdapter::~CSSAnimationsAdapter() {
for (AnimationMap::iterator iter = animation_map_.begin();
iter != animation_map_.end(); ++iter) {
delete iter->second;
}
}
namespace {
// Convert a cssom::Animation::FillMode enum value to a
// web_animations::AnimationEffectTimingReadOnly::FillMode value.
web_animations::AnimationEffectTimingReadOnly::FillMode CSSToWebFillMode(
cssom::Animation::FillMode css_fill_mode) {
switch (css_fill_mode) {
case cssom::Animation::kNone:
return web_animations::AnimationEffectTimingReadOnly::kNone;
case cssom::Animation::kForwards:
return web_animations::AnimationEffectTimingReadOnly::kForwards;
case cssom::Animation::kBackwards:
return web_animations::AnimationEffectTimingReadOnly::kBackwards;
case cssom::Animation::kBoth:
return web_animations::AnimationEffectTimingReadOnly::kBoth;
}
NOTREACHED();
return web_animations::AnimationEffectTimingReadOnly::kNone;
}
// Convert a cssom::Animation::PlaybackDirection enum value to a
// web_animations::AnimationEffectTimingReadOnly::PlaybackDirection value.
web_animations::AnimationEffectTimingReadOnly::PlaybackDirection
CSSToWebDirection(cssom::Animation::PlaybackDirection css_direction) {
switch (css_direction) {
case cssom::Animation::kNormal:
return web_animations::AnimationEffectTimingReadOnly::kNormal;
case cssom::Animation::kReverse:
return web_animations::AnimationEffectTimingReadOnly::kReverse;
case cssom::Animation::kAlternate:
return web_animations::AnimationEffectTimingReadOnly::kAlternate;
case cssom::Animation::kAlternateReverse:
return web_animations::AnimationEffectTimingReadOnly::kAlternateReverse;
}
NOTREACHED();
return web_animations::AnimationEffectTimingReadOnly::kNormal;
}
scoped_refptr<cssom::TimingFunction> GetTimingFunctionFromKeyframePropertyValue(
const scoped_refptr<cssom::PropertyValue>& value) {
// An 'animation-timing-function' specified as a property value in a keyframe
// means that we should use the first value (since animation-timing-function
// is a list) as the keyframe's timing function.
return base::polymorphic_downcast<cssom::TimingFunctionListValue*>(
value.get())
->value()[0];
}
std::vector<web_animations::Keyframe::Data>
ConvertCSSKeyframesToWebAnimationsKeyframes(
const cssom::CSSKeyframesRule& keyframes,
const scoped_refptr<cssom::TimingFunction>& default_timing_function) {
// https://drafts.csswg.org/date/2015-03-02/web-animations-css-integration/#conversion-of-css-keyframes
const std::vector<cssom::CSSKeyframesRule::KeyframeInfo>&
sorted_css_keyframes = keyframes.sorted_keyframes();
// 1. Initialize an empty list keyframeList.
std::vector<web_animations::Keyframe::Data> keyframe_list;
if (sorted_css_keyframes.empty()) {
return keyframe_list;
}
keyframe_list.reserve(sorted_css_keyframes.size());
// 2. For each keyframe in keyframes:
for (size_t i = 0; i < sorted_css_keyframes.size(); ++i) {
const cssom::CSSKeyframesRule::KeyframeInfo& keyframe(
sorted_css_keyframes[i]);
// 2.1. Create a new dictionary newKeyframe.
// 2.2. Set newKeyframe's offset to the offset defined by keyframe.
web_animations::Keyframe::Data new_keyframe(keyframe.offset);
bool easing_was_set = false;
// 2.4. For each property defined in keyframe:
for (cssom::CSSDeclaredStyleData::PropertyValues::const_iterator iter =
keyframe.style->declared_property_values().begin();
iter != keyframe.style->declared_property_values().end(); ++iter) {
// 2.3. If keyframe defines an animation-timing-function, set
// newKeyframe's easing to the defined animation-timing-function.
if (iter->first == cssom::kAnimationTimingFunctionProperty) {
new_keyframe.set_easing(
GetTimingFunctionFromKeyframePropertyValue(iter->second));
easing_was_set = true;
} else {
// 2.4.1. Set newKeyframe[property] to the value associated with that
// property in keyframe
new_keyframe.AddPropertyValue(iter->first, iter->second);
}
}
if (!easing_was_set) {
// If no keyframe-specific easing was specified, use the animation's
// default easing value for this keyframe.
new_keyframe.set_easing(default_timing_function);
}
// 3. Add newKeyframe to keyframeList.
keyframe_list.push_back(new_keyframe);
}
return keyframe_list;
}
} // namespace
void CSSAnimationsAdapter::OnAnimationStarted(
const cssom::Animation& css_animation, cssom::AnimationSet* animation_set) {
// The process for constructing web animations from CSS animations is
// specified here:
// https://drafts.csswg.org/date/2015-03-02/web-animations-css-integration/#css-animations
// Transfer the CSS Animation timing information into a Web Animations
// AnimationEffectTimingReadOnly object.
// https://drafts.csswg.org/date/2015-03-02/web-animations-css-integration/#web-animations-instantiation
scoped_refptr<web_animations::AnimationEffectTimingReadOnly> timing_input(
new web_animations::AnimationEffectTimingReadOnly(
css_animation.delay(), base::TimeDelta(),
CSSToWebFillMode(css_animation.fill_mode()), 0.0,
css_animation.iteration_count(), css_animation.duration(),
CSSToWebDirection(css_animation.direction()),
cssom::TimingFunction::GetLinear()));
// Construct the web_animations keyframe data from the CSS Animations keyframe
// data.
scoped_refptr<web_animations::KeyframeEffectReadOnly> keyframe_effect(
new web_animations::KeyframeEffectReadOnly(
timing_input, animatable_,
ConvertCSSKeyframesToWebAnimationsKeyframes(
*css_animation.keyframes(), css_animation.timing_function())));
// Finally setup and play our animation.
scoped_refptr<web_animations::Animation> web_animation(
new web_animations::Animation(keyframe_effect,
animatable_->GetDefaultTimeline()));
// Setup an event handler on the animation so we can watch for when it enters
// the after phase, allowing us to then trigger the animation events.
std::unique_ptr<web_animations::Animation::EventHandler> event_handler =
web_animation->AttachEventHandler(
base::Bind(&CSSAnimationsAdapter::HandleAnimationEnterAfterPhase,
base::Unretained(this), animation_set));
// Track the animation in our map of all CSS Animations-created animations.
DCHECK(animation_map_.find(css_animation.name()) == animation_map_.end());
animation_map_.insert(std::make_pair(
css_animation.name(),
new AnimationWithEventHandler(web_animation, std::move(event_handler))));
web_animation->Play();
}
void CSSAnimationsAdapter::OnAnimationEnded(
const cssom::Animation& css_animation) {
// An animation has entered the "after phase", so we should correspondingly
// fire the animationend event.
// https://drafts.csswg.org/date/2015-03-02/web-animations-css-integration/#css-animations-events
// Cobalt assumes that the CSSOM does not change during computed style
// calculation. Post to dispatch the event asynchronously here to avoid
// calling event handlers during computed style calculation, which in turn
// may modify CSSOM and require restart of the computed style calculation.
animatable_->GetEventTarget()->PostToDispatchEvent(
FROM_HERE,
new AnimationEvent(
base::Tokens::animationend(), css_animation.name(),
static_cast<float>(css_animation.duration().InMillisecondsF() *
css_animation.iteration_count())));
}
void CSSAnimationsAdapter::OnAnimationRemoved(
const cssom::Animation& css_animation) {
// If a CSS Animation is removed from an element, its corresponding
// Web Animations animation should be stopped and removed also.
AnimationMap::iterator found = animation_map_.find(css_animation.name());
DCHECK(animation_map_.end() != found);
found->second->animation->Cancel();
delete found->second;
animation_map_.erase(found);
}
void CSSAnimationsAdapter::HandleAnimationEnterAfterPhase(
cssom::AnimationSet* animation_set) {
// The update processing handles signalling when an animation ends.
DCHECK(animatable_->GetDefaultTimeline()->current_time());
base::TimeDelta current_time = base::TimeDelta::FromMillisecondsD(
animatable_->GetDefaultTimeline()->current_time().value_or(0));
animation_set->Update(current_time, *animatable_->GetComputedStyle(),
animatable_->GetKeyframesMap());
}
} // namespace dom
} // namespace cobalt