1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.h"
6 
7 #include "third_party/blink/renderer/core/animation/animation_input_helpers.h"
8 #include "third_party/blink/renderer/core/animation/animation_utils.h"
9 #include "third_party/blink/renderer/core/animation/property_handle.h"
10 #include "third_party/blink/renderer/core/animation/string_keyframe.h"
11 #include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
12 #include "third_party/blink/renderer/core/css/properties/css_property.h"
13 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
14 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
15 
16 namespace blink {
17 
18 namespace {
19 
20 using MissingPropertyValueMap = HashMap<String, String>;
21 
ResolveUnderlyingPropertyValues(Element & element,const PropertyHandleSet & properties,MissingPropertyValueMap & map)22 void ResolveUnderlyingPropertyValues(Element& element,
23                                      const PropertyHandleSet& properties,
24                                      MissingPropertyValueMap& map) {
25   // TODO(crbug.com/1069235): Should sample the underlying animation.
26   ActiveInterpolationsMap empty_interpolations_map;
27   AnimationUtils::ForEachInterpolatedPropertyValue(
28       &element, properties, empty_interpolations_map,
29       WTF::BindRepeating(
30           [](MissingPropertyValueMap* map, PropertyHandle property,
31              const CSSValue* value) {
32             if (property.IsCSSProperty()) {
33               String property_name =
34                   AnimationInputHelpers::PropertyHandleToKeyframeAttribute(
35                       property);
36               map->Set(property_name, value->CssText());
37             }
38           },
39           WTF::Unretained(&map)));
40 }
41 
AddMissingProperties(const MissingPropertyValueMap & property_map,const PropertyHandleSet & all_properties,const PropertyHandleSet & keyframe_properties,StringKeyframe * keyframe)42 void AddMissingProperties(const MissingPropertyValueMap& property_map,
43                           const PropertyHandleSet& all_properties,
44                           const PropertyHandleSet& keyframe_properties,
45                           StringKeyframe* keyframe) {
46   for (const auto& property : all_properties) {
47     // At present, custom properties are to be excluded from the keyframes.
48     // https://github.com/w3c/csswg-drafts/issues/5126.
49     if (property.IsCSSCustomProperty())
50       continue;
51 
52     if (keyframe_properties.Contains(property))
53       continue;
54 
55     String property_name =
56         AnimationInputHelpers::PropertyHandleToKeyframeAttribute(property);
57     if (property_map.Contains(property_name)) {
58       const String& value = property_map.at(property_name);
59       keyframe->SetCSSPropertyValue(property.GetCSSProperty().PropertyID(),
60                                     value, SecureContextMode::kInsecureContext,
61                                     nullptr);
62     }
63   }
64 }
65 
ResolveComputedValues(Element * element,StringKeyframe * keyframe)66 void ResolveComputedValues(Element* element, StringKeyframe* keyframe) {
67   DCHECK(element);
68   // Styles are flushed when getKeyframes is called on a CSS animation.
69   DCHECK(element->GetComputedStyle());
70   for (const auto& property : keyframe->Properties()) {
71     if (property.IsCSSCustomProperty()) {
72       // At present, custom properties are to be excluded from the keyframes.
73       // https://github.com/w3c/csswg-drafts/issues/5126.
74       // TODO(csswg/issues/5126): Revisit once issue regarding inclusion of
75       // custom properties is resolved. Perhaps registered should likely be
76       // included since they can be animated in Blink. Pruning unregistered
77       // variables seems justifiable.
78       keyframe->RemoveCustomCSSProperty(property);
79     } else if (property.IsCSSProperty()) {
80       const CSSValue& value = keyframe->CssPropertyValue(property);
81       const CSSPropertyName property_name =
82           property.IsCSSCustomProperty()
83               ? CSSPropertyName(property.CustomPropertyName())
84               : CSSPropertyName(property.GetCSSProperty().PropertyID());
85       const CSSValue* computed_value =
86           StyleResolver::ComputeValue(element, property_name, value);
87       if (computed_value) {
88         keyframe->SetCSSPropertyValue(property.GetCSSProperty(),
89                                       *computed_value);
90       }
91     }
92   }
93 }
94 
95 }  // namespace
96 
97 KeyframeEffectModelBase::KeyframeVector
GetComputedKeyframes(Element * element)98 CssKeyframeEffectModel::GetComputedKeyframes(Element* element) {
99   const KeyframeEffectModelBase::KeyframeVector& keyframes = GetFrames();
100   if (!element)
101     return keyframes;
102 
103   KeyframeEffectModelBase::KeyframeVector computed_keyframes;
104 
105   // Lazy resolution of values for missing properties.
106   PropertyHandleSet all_properties = Properties();
107   PropertyHandleSet from_properties;
108   PropertyHandleSet to_properties;
109 
110   Vector<double> computed_offsets =
111       KeyframeEffectModelBase::GetComputedOffsets(keyframes);
112   computed_keyframes.ReserveInitialCapacity(keyframes.size());
113   for (wtf_size_t i = 0; i < keyframes.size(); i++) {
114     Keyframe* keyframe = keyframes[i];
115     // TODO(crbug.com/1070627): Use computed values, prune variable references,
116     // and convert logical properties to physical properties.
117     StringKeyframe* computed_keyframe = To<StringKeyframe>(keyframe->Clone());
118     ResolveComputedValues(element, computed_keyframe);
119     computed_keyframes.push_back(computed_keyframe);
120     double offset = computed_offsets[i];
121     if (offset == 0) {
122       for (const auto& property : computed_keyframe->Properties()) {
123         from_properties.insert(property);
124       }
125     } else if (offset == 1) {
126       for (const auto& property : computed_keyframe->Properties()) {
127         to_properties.insert(property);
128       }
129     }
130   }
131 
132   // Add missing properties from the bounding keyframes.
133   MissingPropertyValueMap missing_property_value_map;
134   if (from_properties.size() < all_properties.size() ||
135       to_properties.size() < all_properties.size()) {
136     ResolveUnderlyingPropertyValues(*element, all_properties,
137                                     missing_property_value_map);
138   }
139   if (from_properties.size() < all_properties.size() &&
140       !computed_keyframes.IsEmpty()) {
141     AddMissingProperties(
142         missing_property_value_map, all_properties, from_properties,
143         DynamicTo<StringKeyframe>(computed_keyframes[0].Get()));
144   }
145   if (to_properties.size() < all_properties.size() &&
146       !computed_keyframes.IsEmpty()) {
147     wtf_size_t index = keyframes.size() - 1;
148     AddMissingProperties(
149         missing_property_value_map, all_properties, to_properties,
150         DynamicTo<StringKeyframe>(computed_keyframes[index].Get()));
151   }
152   return computed_keyframes;
153 }
154 
155 }  // namespace blink
156