commit 23e0d2dcbb7d33e8edb241f0ff47625ec975da73
parent 1fae47ef94c8c474d3837bb0a120e0230dd904f8
Author: Diego Escalante <descalante@mozilla.com>
Date: Tue, 2 Dec 2025 20:31:22 +0000
Bug 1986629 - Lookup element attribute custom properties for css attr(). r=emilio,firefox-style-system-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D274612
Diffstat:
7 files changed, 155 insertions(+), 52 deletions(-)
diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs
@@ -8,6 +8,7 @@
use crate::applicable_declarations::CascadePriority;
use crate::custom_properties_map::CustomPropertiesMap;
+use crate::dom::AttributeProvider;
use crate::media_queries::Device;
use crate::properties::{
CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
@@ -26,6 +27,7 @@ use crate::stylesheets::UrlExtraData;
use crate::stylist::Stylist;
use crate::values::computed::{self, ToComputedValue};
use crate::values::specified::FontRelativeLength;
+use crate::values::AtomIdent;
use crate::Atom;
use cssparser::{
CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
@@ -241,7 +243,7 @@ pub struct VariableValue {
first_token_type: TokenSerializationType,
last_token_type: TokenSerializationType,
- /// var(), env(), or non-custom property (e.g. through `em`) references.
+ /// var(), env(), attr() or non-custom property (e.g. through `em`) references.
references: References,
}
@@ -1023,7 +1025,12 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
}
/// Cascade a given custom property declaration.
- pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) {
+ pub fn cascade(
+ &mut self,
+ declaration: &'a CustomDeclaration,
+ priority: CascadePriority,
+ attr_provider: &dyn AttributeProvider,
+ ) {
let CustomDeclaration {
ref name,
ref value,
@@ -1075,6 +1082,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
map,
self.stylist,
self.computed_context,
+ attr_provider,
);
}
self.may_have_cycles = true;
@@ -1328,6 +1336,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
pub fn build(
mut self,
defer: DeferFontRelativeCustomPropertyResolution,
+ attr_provider: &dyn AttributeProvider,
) -> Option<CustomPropertiesMap> {
let mut deferred_custom_properties = None;
if self.may_have_cycles {
@@ -1344,6 +1353,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
&self.references_from_non_custom_properties,
self.stylist,
self.computed_context,
+ attr_provider,
);
self.computed_context.builder.invalid_non_custom_properties =
invalid_non_custom_properties;
@@ -1386,6 +1396,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
deferred: CustomPropertiesMap,
stylist: &Stylist,
computed_context: &mut computed::Context,
+ attr_provider: &dyn AttributeProvider,
) {
if deferred.is_empty() {
return;
@@ -1404,6 +1415,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
&mut custom_properties,
stylist,
computed_context,
+ attr_provider,
);
}
computed_context.builder.custom_properties = custom_properties;
@@ -1423,6 +1435,7 @@ fn substitute_all(
references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
stylist: &Stylist,
computed_context: &computed::Context,
+ attr_provider: &dyn AttributeProvider,
) {
// The cycle dependencies removal in this function is a variant
// of Tarjan's algorithm. It is mostly based on the pseudo-code
@@ -1509,6 +1522,7 @@ fn substitute_all(
var: VarType,
non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
context: &mut Context<'a, 'b>,
+ attr_provider: &dyn AttributeProvider,
) -> Option<usize> {
// Some shortcut checks.
let value = match var {
@@ -1551,6 +1565,7 @@ fn substitute_all(
&mut context.map,
context.stylist,
context.computed_context,
+ attr_provider,
);
}
return None;
@@ -1595,7 +1610,8 @@ fn substitute_all(
let mut lowlink = index;
let visit_link =
|var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| {
- let next_index = match traverse(var, non_custom_references, context) {
+ let next_index = match traverse(var, non_custom_references, context, attr_provider)
+ {
Some(index) => index,
// There is nothing to do if the next variable has been
// fully resolved at this point.
@@ -1772,6 +1788,7 @@ fn substitute_all(
&mut context.map,
context.stylist,
context.computed_context,
+ attr_provider,
);
}
}
@@ -1804,6 +1821,7 @@ fn substitute_all(
VarType::Custom((*name).clone()),
references_from_non_custom_properties,
&mut context,
+ attr_provider,
);
}
}
@@ -1840,13 +1858,14 @@ fn handle_invalid_at_computed_value_time(
custom_properties.remove(registration, name);
}
-/// Replace `var()` and `env()` functions in a pre-existing variable value.
+/// Replace `var()`, `env()`, and `attr()` functions in a pre-existing variable value.
fn substitute_references_if_needed_and_apply(
name: &Name,
value: &Arc<VariableValue>,
custom_properties: &mut ComputedCustomProperties,
stylist: &Stylist,
computed_context: &computed::Context,
+ attr_provider: &dyn AttributeProvider,
) {
let registration = stylist.get_custom_property_registration(&name);
if !value.has_references() && registration.syntax.is_universal() {
@@ -1858,14 +1877,19 @@ fn substitute_references_if_needed_and_apply(
let inherited = computed_context.inherited_custom_properties();
let url_data = &value.url_data;
- let substitution =
- match substitute_internal(value, custom_properties, stylist, computed_context) {
- Ok(v) => v,
- Err(..) => {
- handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
- return;
- },
- };
+ let substitution = match substitute_internal(
+ value,
+ custom_properties,
+ stylist,
+ computed_context,
+ attr_provider,
+ ) {
+ Ok(v) => v,
+ Err(..) => {
+ handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
+ return;
+ },
+ };
// If variable fallback results in a wide keyword, deal with it now.
{
@@ -1961,12 +1985,12 @@ impl<'a> Substitution<'a> {
}
fn new(
- css: &'a str,
+ css: Cow<'a, str>,
first_token_type: TokenSerializationType,
last_token_type: TokenSerializationType,
) -> Self {
Self {
- css: Cow::Borrowed(css),
+ css,
first_token_type,
last_token_type,
}
@@ -2017,6 +2041,7 @@ fn do_substitute_chunk<'a>(
stylist: &Stylist,
computed_context: &computed::Context,
references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
+ attr_provider: &dyn AttributeProvider,
) -> Result<Substitution<'a>, ()> {
if start == end {
// Empty string. Easy.
@@ -2028,7 +2053,11 @@ fn do_substitute_chunk<'a>(
.map_or(true, |reference| reference.end > end)
{
let result = &css[start..end];
- return Ok(Substitution::new(result, first_token_type, last_token_type));
+ return Ok(Substitution::new(
+ Cow::Borrowed(result),
+ first_token_type,
+ last_token_type,
+ ));
}
let mut substituted = ComputedValue::empty(url_data);
@@ -2051,6 +2080,7 @@ fn do_substitute_chunk<'a>(
stylist,
computed_context,
references,
+ attr_provider,
)?;
// Optimize the property: var(--...) case to avoid allocating at all.
@@ -2081,34 +2111,41 @@ fn substitute_one_reference<'a>(
stylist: &Stylist,
computed_context: &computed::Context,
references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
+ attr_provider: &dyn AttributeProvider,
) -> Result<Substitution<'a>, ()> {
- match reference.substitution_kind {
+ let substitution: Option<_> = match reference.substitution_kind {
SubstitutionFunctionKind::Var => {
let registration = stylist.get_custom_property_registration(&reference.name);
- if let Some(v) = custom_properties.get(registration, &reference.name) {
- // Skip references that are inside the outer variable (in fallback for example).
- while references
- .next_if(|next_ref| next_ref.end <= reference.end)
- .is_some()
- {}
- return Ok(Substitution::from_value(v.to_variable_value()));
- }
+ custom_properties
+ .get(registration, &reference.name)
+ .map(|v| Substitution::from_value(v.to_variable_value()))
},
SubstitutionFunctionKind::Env => {
let device = stylist.device();
- if let Some(v) = device.environment().get(&reference.name, device, url_data) {
- while references
- .next_if(|next_ref| next_ref.end <= reference.end)
- .is_some()
- {}
- return Ok(Substitution::from_value(v));
- }
- },
- SubstitutionFunctionKind::Attr => {
- // TODO(descalante): implement attribute lookup.
+ device
+ .environment()
+ .get(&reference.name, device, url_data)
+ .map(Substitution::from_value)
},
+ SubstitutionFunctionKind::Attr => attr_provider
+ .get_attr(AtomIdent::cast(&reference.name))
+ .map(|attr| {
+ Substitution::new(
+ Cow::Owned(attr),
+ TokenSerializationType::Nothing,
+ TokenSerializationType::Nothing,
+ )
+ }),
};
+ if let Some(s) = substitution {
+ while references
+ .next_if(|next_ref| next_ref.end <= reference.end)
+ .is_some()
+ {}
+ return Ok(s);
+ }
+
let Some(ref fallback) = reference.fallback else {
return Err(());
};
@@ -2124,15 +2161,17 @@ fn substitute_one_reference<'a>(
stylist,
computed_context,
references,
+ attr_provider,
)
}
-/// Replace `var()` and `env()` functions. Return `Err(..)` for invalid at computed time.
+/// Replace `var()`, `env()`, and `attr()` functions. Return `Err(..)` for invalid at computed time.
fn substitute_internal<'a>(
variable_value: &'a VariableValue,
custom_properties: &'a ComputedCustomProperties,
stylist: &Stylist,
computed_context: &computed::Context,
+ attr_provider: &dyn AttributeProvider,
) -> Result<Substitution<'a>, ()> {
let mut refs = variable_value.references.refs.iter().peekable();
do_substitute_chunk(
@@ -2146,17 +2185,25 @@ fn substitute_internal<'a>(
stylist,
computed_context,
&mut refs,
+ attr_provider,
)
}
-/// Replace var() and env() functions, returning the resulting CSS string.
+/// Replace var(), env(), and attr() functions, returning the resulting CSS string.
pub fn substitute<'a>(
variable_value: &'a VariableValue,
custom_properties: &'a ComputedCustomProperties,
stylist: &Stylist,
computed_context: &computed::Context,
+ attr_provider: &dyn AttributeProvider,
) -> Result<Cow<'a, str>, ()> {
debug_assert!(variable_value.has_references());
- let v = substitute_internal(variable_value, custom_properties, stylist, computed_context)?;
+ let v = substitute_internal(
+ variable_value,
+ custom_properties,
+ stylist,
+ computed_context,
+ attr_provider,
+ )?;
Ok(v.css)
}
diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs
@@ -10,7 +10,7 @@ use crate::computed_value_flags::ComputedValueFlags;
use crate::custom_properties::{
CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution,
};
-use crate::dom::TElement;
+use crate::dom::{AttributeProvider, DummyAttributeProvider, TElement};
#[cfg(feature = "gecko")]
use crate::font_metrics::FontMetricsOrientation;
use crate::logical_geometry::WritingMode;
@@ -236,11 +236,12 @@ fn iter_declarations<'builder, 'decls: 'builder>(
iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
declarations: &mut Declarations<'decls>,
mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>,
+ attr_provider: &dyn AttributeProvider,
) {
for (declaration, priority) in iter {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
if let Some(ref mut builder) = custom_builder {
- builder.cascade(declaration, priority);
+ builder.cascade(declaration, priority, attr_provider);
}
} else {
let id = declaration.id().as_longhand().unwrap();
@@ -308,6 +309,10 @@ where
let mut cascade = Cascade::new(first_line_reparenting, try_tactic, ignore_colors);
let mut declarations = Default::default();
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
+ let attr_provider: &dyn AttributeProvider = match element {
+ Some(ref attr_provider) => attr_provider,
+ None => &DummyAttributeProvider {},
+ };
let properties_to_apply = match cascade_mode {
CascadeMode::Visited { unvisited_context } => {
context.builder.custom_properties = unvisited_context.builder.custom_properties.clone();
@@ -320,28 +325,41 @@ where
// TODO(bug 1859385): If we match the same rules when visited and unvisited, we could
// try to avoid gathering the declarations. That'd be:
// unvisited_context.builder.rules.as_ref() == Some(rules)
- iter_declarations(iter, &mut declarations, None);
+ iter_declarations(iter, &mut declarations, None, attr_provider);
LonghandIdSet::visited_dependent()
},
CascadeMode::Unvisited { visited_rules } => {
let deferred_custom_properties = {
let mut builder = CustomPropertiesBuilder::new(stylist, &mut context);
- iter_declarations(iter, &mut declarations, Some(&mut builder));
+ iter_declarations(iter, &mut declarations, Some(&mut builder), attr_provider);
// Detect cycles, remove properties participating in them, and resolve properties, except:
// * Registered custom properties that depend on font-relative properties (Resolved)
// when prioritary properties are resolved), and
// * Any property that, in turn, depend on properties like above.
- builder.build(DeferFontRelativeCustomPropertyResolution::Yes)
+ builder.build(
+ DeferFontRelativeCustomPropertyResolution::Yes,
+ attr_provider,
+ )
};
// Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom
// properties.
- cascade.apply_prioritary_properties(&mut context, &declarations, &mut shorthand_cache);
+ cascade.apply_prioritary_properties(
+ &mut context,
+ &declarations,
+ &mut shorthand_cache,
+ attr_provider,
+ );
// Resolve the deferred custom properties.
if let Some(deferred) = deferred_custom_properties {
- CustomPropertiesBuilder::build_deferred(deferred, stylist, &mut context);
+ CustomPropertiesBuilder::build_deferred(
+ deferred,
+ stylist,
+ &mut context,
+ attr_provider,
+ );
}
if let Some(visited_rules) = visited_rules {
@@ -374,6 +392,7 @@ where
&declarations.longhand_declarations,
&mut shorthand_cache,
&properties_to_apply,
+ attr_provider,
);
cascade.finished_applying_properties(&mut context.builder);
@@ -667,6 +686,7 @@ impl<'b> Cascade<'b> {
context: &mut computed::Context,
shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
declaration: &'decl PropertyDeclaration,
+ attr_provider: &dyn AttributeProvider,
) -> Cow<'decl, PropertyDeclaration>
where
'cache: 'decl,
@@ -709,6 +729,7 @@ impl<'b> Cascade<'b> {
context.builder.stylist.unwrap(),
context,
shorthand_cache,
+ attr_provider,
)
}
@@ -718,6 +739,7 @@ impl<'b> Cascade<'b> {
decls: &Declarations,
cache: &mut ShorthandsWithPropertyReferencesCache,
id: PrioritaryPropertyId,
+ attr_provider: &dyn AttributeProvider,
) -> bool {
let mut index = decls.prioritary_positions[id as usize].most_important;
if index == DeclarationIndex::MAX {
@@ -731,7 +753,14 @@ impl<'b> Cascade<'b> {
);
loop {
let decl = decls.longhand_declarations[index as usize];
- self.apply_one_longhand(context, longhand_id, decl.decl, decl.priority, cache);
+ self.apply_one_longhand(
+ context,
+ longhand_id,
+ decl.decl,
+ decl.priority,
+ cache,
+ attr_provider,
+ );
if self.seen.contains(longhand_id) {
return true; // Common case, we're done.
}
@@ -758,6 +787,7 @@ impl<'b> Cascade<'b> {
context: &mut computed::Context,
decls: &Declarations,
cache: &mut ShorthandsWithPropertyReferencesCache,
+ attr_provider: &dyn AttributeProvider,
) {
// Keeps apply_one_prioritary_property calls readable, considering the repititious
// arguments.
@@ -768,6 +798,7 @@ impl<'b> Cascade<'b> {
decls,
cache,
PrioritaryPropertyId::$prop,
+ attr_provider,
)
};
}
@@ -858,6 +889,7 @@ impl<'b> Cascade<'b> {
longhand_declarations: &[Declaration],
shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
properties_to_apply: &LonghandIdSet,
+ attr_provider: &dyn AttributeProvider,
) {
debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties()));
debug_assert!(self.declarations_to_apply_unless_overridden.is_empty());
@@ -882,6 +914,7 @@ impl<'b> Cascade<'b> {
declaration.decl,
declaration.priority,
shorthand_cache,
+ attr_provider,
);
}
if !self.declarations_to_apply_unless_overridden.is_empty() {
@@ -923,6 +956,7 @@ impl<'b> Cascade<'b> {
declaration: &PropertyDeclaration,
priority: CascadePriority,
cache: &mut ShorthandsWithPropertyReferencesCache,
+ attr_provider: &dyn AttributeProvider,
) {
debug_assert!(!longhand_id.is_logical());
let origin = priority.cascade_level().origin();
@@ -938,7 +972,8 @@ impl<'b> Cascade<'b> {
}
}
- let mut declaration = self.substitute_variables_if_needed(context, cache, declaration);
+ let mut declaration =
+ self.substitute_variables_if_needed(context, cache, declaration, attr_provider);
// When document colors are disabled, do special handling of
// properties that are marked as ignored in that mode.
diff --git a/servo/components/style/properties/declaration_block.rs b/servo/components/style/properties/declaration_block.rs
@@ -14,6 +14,7 @@ use super::{
};
use crate::context::QuirksMode;
use crate::custom_properties;
+use crate::dom::DummyAttributeProvider;
use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
use crate::parser::ParserContext;
use crate::properties::{
@@ -360,6 +361,8 @@ impl<'a, 'cx, 'cx_a: 'cx> Iterator for AnimationValueIterator<'a, 'cx, 'cx_a> {
&mut self.context,
self.style,
self.default_values,
+ // TODO (descalante): should be able to get an attr from an animated element
+ &DummyAttributeProvider {},
);
if let Some(anim) = animation {
@@ -1011,6 +1014,7 @@ impl PropertyDeclarationBlock {
stylist,
&context,
&mut Default::default(),
+ &DummyAttributeProvider {},
)
.to_css(dest),
(ref d, _) => d.to_css(dest),
diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -26,6 +26,7 @@ use std::mem;
use rustc_hash::FxHashMap;
use super::ComputedValues;
use crate::properties::OwnedPropertyDeclarationId;
+use crate::dom::AttributeProvider;
use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
use crate::values::animated::effects::AnimatedFilter;
#[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
@@ -253,6 +254,7 @@ impl AnimationValue {
context: &mut Context,
style: &ComputedValues,
initial: &ComputedValues,
+ attr_provider: &dyn AttributeProvider,
) -> Option<Self> {
use super::PropertyDeclarationVariantRepr;
@@ -375,6 +377,7 @@ impl AnimationValue {
context.builder.stylist.unwrap(),
context,
&mut cache,
+ attr_provider,
)
};
return AnimationValue::from_declaration(
@@ -382,6 +385,7 @@ impl AnimationValue {
context,
style,
initial,
+ attr_provider,
)
},
PropertyDeclaration::Custom(ref declaration) => {
@@ -389,6 +393,7 @@ impl AnimationValue {
declaration,
context,
initial,
+ attr_provider
)?)
},
_ => return None // non animatable properties will get included because of shorthands. ignore.
diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs
@@ -20,6 +20,7 @@ pub mod generated {
}
use crate::custom_properties::{self, ComputedCustomProperties};
+use crate::dom::AttributeProvider;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::{CSSPropertyId, NonCustomCSSPropertyId, RefPtr};
use crate::logical_geometry::WritingMode;
@@ -1430,6 +1431,7 @@ impl UnparsedValue {
stylist: &Stylist,
computed_context: &computed::Context,
shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
+ attr_provider: &dyn AttributeProvider,
) -> Cow<'cache, PropertyDeclaration> {
let invalid_at_computed_value_time = || {
let keyword = if longhand_id.inherited() {
@@ -1464,6 +1466,7 @@ impl UnparsedValue {
custom_properties,
stylist,
computed_context,
+ attr_provider,
) {
Ok(css) => css,
Err(..) => return invalid_at_computed_value_time(),
diff --git a/servo/components/style/properties_and_values/value.rs b/servo/components/style/properties_and_values/value.rs
@@ -12,8 +12,6 @@ use super::{
data_type::DataType, Component as SyntaxComponent, ComponentName, Descriptor, Multiplier,
},
};
-use crate::custom_properties::ComputedValue as ComputedPropertyValue;
-use crate::parser::{Parse, ParserContext};
use crate::properties;
use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
use crate::values::{
@@ -21,6 +19,11 @@ use crate::values::{
computed::{self, ToComputedValue},
specified, CustomIdent,
};
+use crate::custom_properties::ComputedValue as ComputedPropertyValue;
+use crate::{
+ dom::AttributeProvider,
+ parser::{Parse, ParserContext},
+};
use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser as CSSParser, TokenSerializationType};
use selectors::matching::QuirksMode;
use servo_arc::Arc;
@@ -614,6 +617,7 @@ impl CustomAnimatedValue {
declaration: &properties::CustomDeclaration,
context: &mut computed::Context,
_initial: &properties::ComputedValues,
+ _attr_provider: &dyn AttributeProvider,
) -> Option<Self> {
let computed_value = match declaration.value {
properties::CustomDeclarationValue::Unparsed(ref value) => {
diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs
@@ -7080,12 +7080,12 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
let guard = declarations.read_with(&guard);
for decl in guard.normal_declaration_iter() {
if let PropertyDeclaration::Custom(ref declaration) = *decl {
- builder.cascade(declaration, priority);
+ builder.cascade(declaration, priority, &element);
}
}
}
iter.reset();
- let _deferred = builder.build(DeferFontRelativeCustomPropertyResolution::No);
+ let _deferred = builder.build(DeferFontRelativeCustomPropertyResolution::No, &element);
debug_assert!(
_deferred.is_none(),
"Custom property processing deferred despite specifying otherwise?"
@@ -7234,8 +7234,13 @@ pub extern "C" fn Servo_AnimationValue_Compute(
.next()
{
Some((decl, imp)) if imp == Importance::Normal => {
- let animation =
- AnimationValue::from_declaration(decl, &mut context, style, default_values);
+ let animation = AnimationValue::from_declaration(
+ decl,
+ &mut context,
+ style,
+ default_values,
+ &element,
+ );
animation.map_or(Strong::null(), |value| Arc::new(value).into())
},
_ => Strong::null(),