tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mservo/components/style/custom_properties.rs | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mservo/components/style/properties/cascade.rs | 53++++++++++++++++++++++++++++++++++++++++++++---------
Mservo/components/style/properties/declaration_block.rs | 4++++
Mservo/components/style/properties/helpers/animated_properties.mako.rs | 5+++++
Mservo/components/style/properties/mod.rs | 3+++
Mservo/components/style/properties_and_values/value.rs | 8++++++--
Mservo/ports/geckolib/glue.rs | 13+++++++++----
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(),