tor-browser

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

commit f5edda047abe2757d89bb0b14651cb1a7de2c082
parent 3644366ec62cdcef51a632ab489531229dc8357f
Author: Diego Escalante <descalante@mozilla.com>
Date:   Wed, 19 Nov 2025 14:59:29 +0000

Bug 1986627 - Parse attr function as a substitution function when generalized attr is preffed on. r=layout-reviewers,firefox-style-system-reviewers,emilio

Differential Revision: https://phabricator.services.mozilla.com/D272492

Diffstat:
Mservo/components/style/custom_properties.rs | 128++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mservo/components/style/properties/mod.rs | 9++++++++-
2 files changed, 82 insertions(+), 55 deletions(-)

diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs @@ -458,6 +458,13 @@ pub enum DeferFontRelativeCustomPropertyResolution { No, } +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)] +enum SubstitutionFunctionKind { + Var, + Env, + Attr, +} + #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] struct VariableFallback { start: num::NonZeroUsize, @@ -466,24 +473,25 @@ struct VariableFallback { } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -struct VarOrEnvReference { +struct SubstitutionFunctionReference { name: Name, start: usize, end: usize, fallback: Option<VariableFallback>, prev_token_type: TokenSerializationType, next_token_type: TokenSerializationType, - is_var: bool, + substitution_kind: SubstitutionFunctionKind, } /// A struct holding information about the external references to that a custom /// property value may have. #[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)] struct References { - refs: Vec<VarOrEnvReference>, + refs: Vec<SubstitutionFunctionReference>, non_custom_references: NonCustomReferences, any_env: bool, any_var: bool, + any_attr: bool, } impl References { @@ -784,32 +792,33 @@ fn parse_declaration_value_block<'i, 't>( return Err(input.new_custom_error(e)); }, Token::Function(ref name) => { - let is_var = name.eq_ignore_ascii_case("var"); - if is_var || name.eq_ignore_ascii_case("env") { + let substitution_kind = SubstitutionFunctionKind::from_ident(name).ok(); + if let Some(substitution_kind) = substitution_kind { let our_ref_index = references.refs.len(); let fallback = input.parse_nested_block(|input| { // TODO(emilio): For env() this should be <custom-ident> per spec, but no other browser does // that, see https://github.com/w3c/csswg-drafts/issues/3262. let name = input.expect_ident()?; - let name = Atom::from(if is_var { - match parse_name(name.as_ref()) { - Ok(name) => name, - Err(()) => { - let name = name.clone(); - return Err(input.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(name), - )); - }, - } - } else { - name.as_ref() - }); + let name = + Atom::from(if substitution_kind == SubstitutionFunctionKind::Var { + match parse_name(name.as_ref()) { + Ok(name) => name, + Err(()) => { + let name = name.clone(); + return Err(input.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(name), + )); + }, + } + } else { + name.as_ref() + }); // We want the order of the references to match source order. So we need to reserve our slot // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since // if this fails we discard the whole result anyways. let start = token_start.byte_index() - input_start.byte_index(); - references.refs.push(VarOrEnvReference { + references.refs.push(SubstitutionFunctionReference { name, start, // To be fixed up after parsing fallback and auto-closing via our_ref_index. @@ -819,7 +828,7 @@ fn parse_declaration_value_block<'i, 't>( next_token_type: TokenSerializationType::Nothing, // To be fixed up after parsing fallback. fallback: None, - is_var, + substitution_kind: substitution_kind.clone(), }); let mut fallback = None; @@ -863,11 +872,11 @@ fn parse_declaration_value_block<'i, 't>( reference.end = input.position().byte_index() - input_start.byte_index() + missing_closing_characters.len(); reference.fallback = fallback; - if is_var { - references.any_var = true; - } else { - references.any_env = true; - } + match substitution_kind { + SubstitutionFunctionKind::Var => references.any_var = true, + SubstitutionFunctionKind::Env => references.any_env = true, + SubstitutionFunctionKind::Attr => references.any_attr = true, + }; } else { nested!(); check_closed!(")"); @@ -1038,6 +1047,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> { // Non-custom dependency is really relevant for registered custom properties // that require computed value of such dependencies. let has_dependency = unparsed_value.references.any_var + || unparsed_value.references.any_attr || find_non_custom_references( registration, unparsed_value, @@ -1122,7 +1132,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> { _ => return, }; - if !refs.any_var { + if !refs.any_var && !refs.any_attr { return; } @@ -1151,7 +1161,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> { .refs .iter() .filter_map(|reference| { - if !reference.is_var { + if reference.substitution_kind != SubstitutionFunctionKind::Var { return None; } let registration = self @@ -1507,7 +1517,9 @@ fn substitute_all( /* include_unregistered = */ true, ); context.non_custom_references |= non_custom_refs.unwrap_or_default(); - let has_dependency = value.references.any_var || non_custom_refs.is_some(); + let has_dependency = value.references.any_var + || value.references.any_attr + || non_custom_refs.is_some(); // Nothing to resolve. if !has_dependency { debug_assert!(!value.references.any_env, "Should've been handled earlier"); @@ -1605,7 +1617,7 @@ fn substitute_all( // Visit other custom properties... // FIXME: Maybe avoid visiting the same var twice if not needed? for next in &v.references.refs { - if !next.is_var { + if next.substitution_kind != SubstitutionFunctionKind::Var { continue; } visit_link( @@ -1731,7 +1743,9 @@ fn substitute_all( ) .is_some() || v.references.refs.iter().any(|reference| { - reference.is_var && deferred.get(&reference.name).is_some() + (reference.substitution_kind == SubstitutionFunctionKind::Var + && deferred.get(&reference.name).is_some()) + || reference.substitution_kind == SubstitutionFunctionKind::Attr }); if defer { @@ -1742,7 +1756,7 @@ fn substitute_all( } // If there are no var references we should already be computed and substituted by now. - if !defer && v.references.any_var { + if !defer && (v.references.any_var || v.references.any_attr) { substitute_references_if_needed_and_apply( &name, v, @@ -1993,7 +2007,7 @@ fn do_substitute_chunk<'a>( custom_properties: &'a ComputedCustomProperties, stylist: &Stylist, computed_context: &computed::Context, - references: &mut std::iter::Peekable<std::slice::Iter<VarOrEnvReference>>, + references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>, ) -> Result<Substitution<'a>, ()> { if start == end { // Empty string. Easy. @@ -2054,31 +2068,37 @@ fn substitute_one_reference<'a>( css: &'a str, url_data: &UrlExtraData, custom_properties: &'a ComputedCustomProperties, - reference: &VarOrEnvReference, + reference: &SubstitutionFunctionReference, stylist: &Stylist, computed_context: &computed::Context, - references: &mut std::iter::Peekable<std::slice::Iter<VarOrEnvReference>>, + references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>, ) -> Result<Substitution<'a>, ()> { - if reference.is_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())); - } - } else { - 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)); - } - } + 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())); + } + }, + 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. + }, + }; let Some(ref fallback) = reference.fallback else { return Err(()); diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs @@ -747,7 +747,14 @@ fn parse_non_custom_property_declaration_value_into<'i>( }; input.reset(&start); - input.look_for_arbitrary_substitution_functions(&["var", "env"]); + input.look_for_arbitrary_substitution_functions( + if static_prefs::pref!("layout.css.attr.enabled") { + &["var", "env", "attr"] + } else { + &["var", "env"] + }, + ); + let err = match parse_entirely_into(declarations, input) { Ok(()) => { input.seen_arbitrary_substitution_functions();