1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5 //! Support for [custom properties for cascading variables][custom].
7 //! [custom]: https://drafts.csswg.org/css-variables/
9 use crate::applicable_declarations::CascadePriority;
10 use crate::media_queries::Device;
11 use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue};
12 use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher};
15 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
17 use indexmap::IndexMap;
18 use selectors::parser::SelectorParseErrorKind;
20 use smallvec::SmallVec;
23 use std::collections::hash_map::Entry;
24 use std::fmt::{self, Write};
25 use std::hash::BuildHasherDefault;
26 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
28 /// The environment from which to get `env` function values.
30 /// TODO(emilio): If this becomes a bit more complex we should probably move it
31 /// to the `media_queries` module, or something.
32 #[derive(Debug, MallocSizeOf)]
33 pub struct CssEnvironment;
35 type EnvironmentEvaluator = fn(device: &Device) -> VariableValue;
37 struct EnvironmentVariable {
39 evaluator: EnvironmentEvaluator,
42 macro_rules! make_variable {
43 ($name:expr, $evaluator:expr) => {{
46 evaluator: $evaluator,
51 fn get_safearea_inset_top(device: &Device) -> VariableValue {
52 VariableValue::pixels(device.safe_area_insets().top)
55 fn get_safearea_inset_bottom(device: &Device) -> VariableValue {
56 VariableValue::pixels(device.safe_area_insets().bottom)
59 fn get_safearea_inset_left(device: &Device) -> VariableValue {
60 VariableValue::pixels(device.safe_area_insets().left)
63 fn get_safearea_inset_right(device: &Device) -> VariableValue {
64 VariableValue::pixels(device.safe_area_insets().right)
67 static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
68 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
69 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
70 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
71 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
74 macro_rules! lnf_int {
77 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
78 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
84 macro_rules! lnf_int_variable {
85 ($atom:expr, $id:ident, $ctor:ident) => {{
86 fn __eval(_: &Device) -> VariableValue {
87 VariableValue::$ctor(lnf_int!($id))
89 make_variable!($atom, __eval)
93 static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
95 atom!("-moz-gtk-csd-titlebar-radius"),
99 lnf_int_variable!(atom!("-moz-gtk-csd-menu-radius"), GtkMenuRadius, int_pixels),
101 atom!("-moz-gtk-csd-close-button-position"),
102 GTKCSDCloseButtonPosition,
106 atom!("-moz-gtk-csd-minimize-button-position"),
107 GTKCSDMinimizeButtonPosition,
111 atom!("-moz-gtk-csd-maximize-button-position"),
112 GTKCSDMaximizeButtonPosition,
117 impl CssEnvironment {
119 fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> {
120 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
121 return Some((var.evaluator)(device));
123 if !device.is_chrome_document() {
126 let var = CHROME_ENVIRONMENT_VARIABLES
128 .find(|var| var.name == *name)?;
129 Some((var.evaluator)(device))
133 /// A custom property name is just an `Atom`.
135 /// Note that this does not include the `--` prefix
136 pub type Name = Atom;
138 /// Parse a custom property name.
140 /// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name>
141 pub fn parse_name(s: &str) -> Result<&str, ()> {
142 if s.starts_with("--") && s.len() > 2 {
149 /// A value for a custom property is just a set of tokens.
151 /// We preserve the original CSS for serialization, and also the variable
152 /// references to other custom property names.
153 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
154 pub struct VariableValue {
157 first_token_type: TokenSerializationType,
158 last_token_type: TokenSerializationType,
160 /// Whether a variable value has a reference to an environment variable.
162 /// If this is the case, we need to perform variable substitution on the
164 references_environment: bool,
166 /// Custom property names in var() functions.
167 references: Box<[Name]>,
170 impl ToCss for SpecifiedValue {
171 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
175 dest.write_str(&self.css)
179 /// A map from CSS variable names to CSS variable computed values, used for
182 /// A consistent ordering is required for CSSDeclaration objects in the
183 /// DOM. CSSDeclarations expose property names as indexed properties, which
184 /// need to be stable. So we keep an array of property names which order is
185 /// determined on the order that they are added to the name-value map.
187 /// The variable values are guaranteed to not have references to other
189 pub type CustomPropertiesMap =
190 IndexMap<Name, Arc<VariableValue>, BuildHasherDefault<PrecomputedHasher>>;
192 /// Both specified and computed values are VariableValues, the difference is
193 /// whether var() functions are expanded.
194 pub type SpecifiedValue = VariableValue;
195 /// Both specified and computed values are VariableValues, the difference is
196 /// whether var() functions are expanded.
197 pub type ComputedValue = VariableValue;
199 /// A struct holding information about the external references to that a custom
200 /// property value may have.
202 struct VarOrEnvReferences {
203 custom_property_references: PrecomputedHashSet<Name>,
204 references_environment: bool,
211 last_token_type: TokenSerializationType::nothing(),
212 first_token_type: TokenSerializationType::nothing(),
213 references: Default::default(),
214 references_environment: false,
220 input: &Parser<'i, '_>,
222 css_first_token_type: TokenSerializationType,
223 css_last_token_type: TokenSerializationType,
224 ) -> Result<(), ParseError<'i>> {
225 /// Prevent values from getting terribly big since you can use custom
226 /// properties exponentially.
228 /// This number (2MB) is somewhat arbitrary, but silly enough that no
229 /// reasonable page should hit it. We could limit by number of total
230 /// substitutions, but that was very easy to work around in practice
231 /// (just choose a larger initial value and boom).
232 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
234 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
235 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
238 // This happens e.g. between two subsequent var() functions:
239 // `var(--a)var(--b)`.
241 // In that case, css_*_token_type is nonsensical.
246 self.first_token_type.set_if_nothing(css_first_token_type);
247 // If self.first_token_type was nothing,
248 // self.last_token_type is also nothing and this will be false:
251 .needs_separator_when_before(css_first_token_type)
253 self.css.push_str("/**/")
255 self.css.push_str(css);
256 self.last_token_type = css_last_token_type;
262 input: &Parser<'i, '_>,
263 position: (SourcePosition, TokenSerializationType),
264 last_token_type: TokenSerializationType,
265 ) -> Result<(), ParseError<'i>> {
268 input.slice_from(position.0),
274 fn push_variable<'i>(
276 input: &Parser<'i, '_>,
277 variable: &ComputedValue,
278 ) -> Result<(), ParseError<'i>> {
279 debug_assert!(variable.references.is_empty());
283 variable.first_token_type,
284 variable.last_token_type,
288 /// Parse a custom property value.
289 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Arc<Self>, ParseError<'i>> {
290 let mut references = VarOrEnvReferences::default();
292 let (first_token_type, css, last_token_type) =
293 parse_self_contained_declaration_value(input, Some(&mut references))?;
295 let custom_property_references = references
296 .custom_property_references
301 let mut css = css.into_owned();
304 Ok(Arc::new(VariableValue {
308 references: custom_property_references,
309 references_environment: references.references_environment,
313 /// Create VariableValue from an int.
314 fn integer(number: i32) -> Self {
315 Self::from_token(Token::Number {
317 value: number as f32,
318 int_value: Some(number),
322 /// Create VariableValue from a float amount of CSS pixels.
323 fn pixels(number: f32) -> Self {
324 // FIXME (https://github.com/servo/rust-cssparser/issues/266):
325 // No way to get TokenSerializationType::Dimension without creating
327 Self::from_token(Token::Dimension {
331 unit: CowRcStr::from("px"),
335 /// Create VariableValue from an integer amount of CSS pixels.
336 fn int_pixels(number: i32) -> Self {
337 Self::from_token(Token::Dimension {
339 value: number as f32,
340 int_value: Some(number),
341 unit: CowRcStr::from("px"),
345 fn from_token(token: Token) -> Self {
346 let token_type = token.serialization_type();
347 let mut css = token.to_css_string();
352 first_token_type: token_type,
353 last_token_type: token_type,
354 references: Default::default(),
355 references_environment: false,
360 /// Parse the value of a non-custom property that contains `var()` references.
361 pub fn parse_non_custom_with_var<'i, 't>(
362 input: &mut Parser<'i, 't>,
363 ) -> Result<(TokenSerializationType, Cow<'i, str>), ParseError<'i>> {
364 let (first_token_type, css, _) = parse_self_contained_declaration_value(input, None)?;
365 Ok((first_token_type, css))
368 fn parse_self_contained_declaration_value<'i, 't>(
369 input: &mut Parser<'i, 't>,
370 references: Option<&mut VarOrEnvReferences>,
371 ) -> Result<(TokenSerializationType, Cow<'i, str>, TokenSerializationType), ParseError<'i>> {
372 let start_position = input.position();
373 let mut missing_closing_characters = String::new();
375 parse_declaration_value(input, references, &mut missing_closing_characters)?;
376 let mut css: Cow<str> = input.slice_from(start_position).into();
377 if !missing_closing_characters.is_empty() {
378 // Unescaped backslash at EOF in a quoted string is ignored.
379 if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') {
382 css.to_mut().push_str(&missing_closing_characters);
384 Ok((first, css, last))
387 /// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value>
388 fn parse_declaration_value<'i, 't>(
389 input: &mut Parser<'i, 't>,
390 references: Option<&mut VarOrEnvReferences>,
391 missing_closing_characters: &mut String,
392 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
393 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
394 parse_declaration_value_block(input, references, missing_closing_characters)
398 /// Like parse_declaration_value, but accept `!` and `;` since they are only
399 /// invalid at the top level
400 fn parse_declaration_value_block<'i, 't>(
401 input: &mut Parser<'i, 't>,
402 mut references: Option<&mut VarOrEnvReferences>,
403 missing_closing_characters: &mut String,
404 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
405 input.skip_whitespace();
406 let mut token_start = input.position();
407 let mut token = match input.next_including_whitespace_and_comments() {
411 TokenSerializationType::nothing(),
412 TokenSerializationType::nothing(),
416 let first_token_type = token.serialization_type();
418 macro_rules! nested {
420 input.parse_nested_block(|input| {
421 parse_declaration_value_block(
423 references.as_mut().map(|r| &mut **r),
424 missing_closing_characters,
429 macro_rules! check_closed {
431 if !input.slice_from(token_start).ends_with($closing) {
432 missing_closing_characters.push_str($closing)
436 let last_token_type = match *token {
437 Token::Comment(_) => {
438 let serialization_type = token.serialization_type();
439 let token_slice = input.slice_from(token_start);
440 if !token_slice.ends_with("*/") {
441 missing_closing_characters.push_str(if token_slice.ends_with('*') {
449 Token::BadUrl(ref u) => {
450 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
451 return Err(input.new_custom_error(e));
453 Token::BadString(ref s) => {
454 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
455 return Err(input.new_custom_error(e));
457 Token::CloseParenthesis => {
458 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
459 return Err(input.new_custom_error(e));
461 Token::CloseSquareBracket => {
462 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
463 return Err(input.new_custom_error(e));
465 Token::CloseCurlyBracket => {
466 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
467 return Err(input.new_custom_error(e));
469 Token::Function(ref name) => {
470 if name.eq_ignore_ascii_case("var") {
471 let args_start = input.state();
472 input.parse_nested_block(|input| {
473 parse_var_function(input, references.as_mut().map(|r| &mut **r))
475 input.reset(&args_start);
476 } else if name.eq_ignore_ascii_case("env") {
477 let args_start = input.state();
478 input.parse_nested_block(|input| {
479 parse_env_function(input, references.as_mut().map(|r| &mut **r))
481 input.reset(&args_start);
485 Token::CloseParenthesis.serialization_type()
487 Token::ParenthesisBlock => {
490 Token::CloseParenthesis.serialization_type()
492 Token::CurlyBracketBlock => {
495 Token::CloseCurlyBracket.serialization_type()
497 Token::SquareBracketBlock => {
500 Token::CloseSquareBracket.serialization_type()
502 Token::QuotedString(_) => {
503 let serialization_type = token.serialization_type();
504 let token_slice = input.slice_from(token_start);
505 let quote = &token_slice[..1];
506 debug_assert!(matches!(quote, "\"" | "'"));
507 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
508 missing_closing_characters.push_str(quote)
512 Token::Ident(ref value) |
513 Token::AtKeyword(ref value) |
514 Token::Hash(ref value) |
515 Token::IDHash(ref value) |
516 Token::UnquotedUrl(ref value) |
520 let serialization_type = token.serialization_type();
521 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
522 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
523 // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
524 // Check the value in case the final backslash was itself escaped.
525 // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
526 // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
527 missing_closing_characters.push_str("�")
534 _ => token.serialization_type(),
537 token_start = input.position();
538 token = match input.next_including_whitespace_and_comments() {
540 Err(..) => return Ok((first_token_type, last_token_type)),
545 fn parse_fallback<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
546 // Exclude `!` and `;` at the top level
547 // https://drafts.csswg.org/css-syntax/#typedef-declaration-value
548 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
549 // Skip until the end.
550 while input.next_including_whitespace_and_comments().is_ok() {}
555 // If the var function is valid, return Ok((custom_property_name, fallback))
556 fn parse_var_function<'i, 't>(
557 input: &mut Parser<'i, 't>,
558 references: Option<&mut VarOrEnvReferences>,
559 ) -> Result<(), ParseError<'i>> {
560 let name = input.expect_ident_cloned()?;
561 let name = parse_name(&name).map_err(|()| {
562 input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))
564 if input.try_parse(|input| input.expect_comma()).is_ok() {
565 parse_fallback(input)?;
567 if let Some(refs) = references {
568 refs.custom_property_references.insert(Atom::from(name));
573 fn parse_env_function<'i, 't>(
574 input: &mut Parser<'i, 't>,
575 references: Option<&mut VarOrEnvReferences>,
576 ) -> Result<(), ParseError<'i>> {
577 // TODO(emilio): This should be <custom-ident> per spec, but no other
578 // browser does that, see https://github.com/w3c/csswg-drafts/issues/3262.
579 input.expect_ident()?;
580 if input.try_parse(|input| input.expect_comma()).is_ok() {
581 parse_fallback(input)?;
583 if let Some(references) = references {
584 references.references_environment = true;
589 /// A struct that takes care of encapsulating the cascade process for custom
591 pub struct CustomPropertiesBuilder<'a> {
592 seen: PrecomputedHashSet<&'a Name>,
593 may_have_cycles: bool,
594 custom_properties: Option<CustomPropertiesMap>,
595 inherited: Option<&'a Arc<CustomPropertiesMap>>,
596 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
600 impl<'a> CustomPropertiesBuilder<'a> {
601 /// Create a new builder, inheriting from a given custom properties map.
602 pub fn new(inherited: Option<&'a Arc<CustomPropertiesMap>>, device: &'a Device) -> Self {
604 seen: PrecomputedHashSet::default(),
605 reverted: Default::default(),
606 may_have_cycles: false,
607 custom_properties: None,
613 /// Cascade a given custom property declaration.
614 pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) {
615 let CustomDeclaration {
620 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
621 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
626 let was_already_present = !self.seen.insert(name);
627 if was_already_present {
631 if !self.value_may_affect_style(name, value) {
635 if self.custom_properties.is_none() {
636 self.custom_properties = Some(match self.inherited {
637 Some(inherited) => (**inherited).clone(),
638 None => CustomPropertiesMap::default(),
642 let map = self.custom_properties.as_mut().unwrap();
644 CustomDeclarationValue::Value(ref unparsed_value) => {
645 let has_references = !unparsed_value.references.is_empty();
646 self.may_have_cycles |= has_references;
648 // If the variable value has no references and it has an
649 // environment variable here, perform substitution here instead
650 // of forcing a full traversal in `substitute_all` afterwards.
651 let value = if !has_references && unparsed_value.references_environment {
652 let result = substitute_references_in_value(unparsed_value, &map, &self.device);
654 Ok(new_value) => new_value,
661 (*unparsed_value).clone()
663 map.insert(name.clone(), value);
665 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
666 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
667 let origin_revert = keyword == CSSWideKeyword::Revert;
668 self.seen.remove(name);
669 self.reverted.insert(name, (priority, origin_revert));
671 CSSWideKeyword::Initial => {
674 // handled in value_may_affect_style
675 CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(),
680 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
682 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) |
683 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
684 // Custom properties are inherited by default. So
685 // explicit 'inherit' or 'unset' means we can just use
686 // any existing value in the inherited CustomPropertiesMap.
692 let existing_value = self
695 .and_then(|m| m.get(name))
696 .or_else(|| self.inherited.and_then(|m| m.get(name)));
698 match (existing_value, value) {
699 (None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => {
700 // The initial value of a custom property is the same as it
701 // not existing in the map.
704 (Some(existing_value), &CustomDeclarationValue::Value(ref value)) => {
705 // Don't bother overwriting an existing inherited value with
706 // the same specified value.
707 if existing_value == value {
717 fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool {
718 let inherited = match self.inherited {
719 Some(inherited) => inherited,
720 None => return false,
722 if inherited.len() != map.len() {
725 for name in self.seen.iter() {
726 if inherited.get(*name) != map.get(*name) {
733 /// Returns the final map of applicable custom properties.
735 /// If there was any specified property, we've created a new map and now we
736 /// need to remove any potential cycles, and wrap it in an arc.
738 /// Otherwise, just use the inherited custom properties map.
739 pub fn build(mut self) -> Option<Arc<CustomPropertiesMap>> {
740 let mut map = match self.custom_properties.take() {
742 None => return self.inherited.cloned(),
745 if self.may_have_cycles {
746 substitute_all(&mut map, &self.seen, self.device);
749 // Some pages apply a lot of redundant custom properties, see e.g.
750 // bug 1758974 comment 5. Try to detect the case where the values
751 // haven't really changed, and save some memory by reusing the inherited
753 if self.inherited_properties_match(&map) {
754 return self.inherited.cloned();
762 /// Resolve all custom properties to either substituted, invalid, or unset
763 /// (meaning we should use the inherited value).
765 /// It does cycle dependencies removal at the same time as substitution.
766 fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, seen: &PrecomputedHashSet<&Name>, device: &Device) {
767 // The cycle dependencies removal in this function is a variant
768 // of Tarjan's algorithm. It is mostly based on the pseudo-code
770 // https://en.wikipedia.org/w/index.php?
771 // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495
773 /// Struct recording necessary information for each variable.
776 /// The name of the variable. It will be taken to save addref
777 /// when the corresponding variable is popped from the stack.
778 /// This also serves as a mark for whether the variable is
779 /// currently in the stack below.
781 /// If the variable is in a dependency cycle, lowlink represents
782 /// a smaller index which corresponds to a variable in the same
783 /// strong connected component, which is known to be accessible
784 /// from this variable. It is not necessarily the root, though.
787 /// Context struct for traversing the variable graph, so that we can
788 /// avoid referencing all the fields multiple times.
791 /// Number of variables visited. This is used as the order index
792 /// when we visit a new unresolved variable.
794 /// The map from custom property name to its order index.
795 index_map: PrecomputedHashMap<Name, usize>,
796 /// Information of each variable indexed by the order index.
797 var_info: SmallVec<[VarInfo; 5]>,
798 /// The stack of order index of visited variables. It contains
799 /// all unfinished strong connected components.
800 stack: SmallVec<[usize; 5]>,
801 map: &'a mut CustomPropertiesMap,
802 /// To resolve the environment to substitute `env()` variables.
806 /// This function combines the traversal for cycle removal and value
807 /// substitution. It returns either a signal None if this variable
808 /// has been fully resolved (to either having no reference or being
809 /// marked invalid), or the order index for the given name.
811 /// When it returns, the variable corresponds to the name would be
812 /// in one of the following states:
813 /// * It is still in context.stack, which means it is part of an
814 /// potentially incomplete dependency circle.
815 /// * It has been removed from the map. It can be either that the
816 /// substitution failed, or it is inside a dependency circle.
817 /// When this function removes a variable from the map because
818 /// of dependency circle, it would put all variables in the same
819 /// strong connected component to the set together.
820 /// * It doesn't have any reference, because either this variable
821 /// doesn't have reference at all in specified value, or it has
822 /// been completely resolved.
823 /// * There is no such variable at all.
824 fn traverse<'a>(name: &Name, context: &mut Context<'a>) -> Option<usize> {
825 // Some shortcut checks.
826 let (name, value) = {
827 let value = context.map.get(name)?;
829 // Nothing to resolve.
830 if value.references.is_empty() {
832 !value.references_environment,
833 "Should've been handled earlier"
838 // Whether this variable has been visited in this traversal.
840 match context.index_map.entry(name.clone()) {
841 Entry::Occupied(entry) => {
842 return Some(*entry.get());
844 Entry::Vacant(entry) => {
845 key = entry.key().clone();
846 entry.insert(context.count);
850 // Hold a strong reference to the value so that we don't
851 // need to keep reference to context.map.
855 // Add new entry to the information table.
856 let index = context.count;
858 debug_assert_eq!(index, context.var_info.len());
859 context.var_info.push(VarInfo {
863 context.stack.push(index);
865 let mut self_ref = false;
866 let mut lowlink = index;
867 for next in value.references.iter() {
868 let next_index = match traverse(next, context) {
869 Some(index) => index,
870 // There is nothing to do if the next variable has been
871 // fully resolved at this point.
876 let next_info = &context.var_info[next_index];
877 if next_index > index {
878 // The next variable has a larger index than us, so it
879 // must be inserted in the recursive call above. We want
880 // to get its lowlink.
881 lowlink = cmp::min(lowlink, next_info.lowlink);
882 } else if next_index == index {
884 } else if next_info.name.is_some() {
885 // The next variable has a smaller order index and it is
886 // in the stack, so we are at the same component.
887 lowlink = cmp::min(lowlink, next_index);
891 context.var_info[index].lowlink = lowlink;
892 if lowlink != index {
893 // This variable is in a loop, but it is not the root of
894 // this strong connected component. We simply return for
895 // now, and the root would remove it from the map.
897 // This cannot be removed from the map here, because
898 // otherwise the shortcut check at the beginning of this
899 // function would return the wrong value.
903 // This is the root of a strong-connected component.
904 let mut in_loop = self_ref;
907 let var_index = context
910 .expect("The current variable should still be in stack");
911 let var_info = &mut context.var_info[var_index];
912 // We should never visit the variable again, so it's safe
913 // to take the name away, so that we don't do additional
915 let var_name = var_info
918 .expect("Variable should not be poped from stack twice");
919 if var_index == index {
923 // Anything here is in a loop which can traverse to the
924 // variable we are handling, so remove it from the map, it's invalid
925 // at computed-value time.
926 context.map.remove(&var_name);
930 // This variable is in loop. Resolve to invalid.
931 context.map.remove(&name);
935 // Now we have shown that this variable is not in a loop, and all of its
936 // dependencies should have been resolved. We can start substitution
938 let result = substitute_references_in_value(&value, &context.map, &context.device);
940 Ok(computed_value) => {
941 context.map.insert(name, computed_value);
944 // This is invalid, reset it to the guaranteed-invalid value.
945 context.map.remove(&name);
949 // All resolved, so return the signal value.
953 // Note that `seen` doesn't contain names inherited from our parent, but
954 // those can't have variable references (since we inherit the computed
955 // variables) so we don't want to spend cycles traversing them anyway.
957 let mut context = Context {
959 index_map: PrecomputedHashMap::default(),
960 stack: SmallVec::new(),
961 var_info: SmallVec::new(),
962 map: custom_properties_map,
965 traverse(name, &mut context);
969 /// Replace `var()` and `env()` functions in a pre-existing variable value.
970 fn substitute_references_in_value<'i>(
971 value: &'i VariableValue,
972 custom_properties: &CustomPropertiesMap,
974 ) -> Result<Arc<ComputedValue>, ParseError<'i>> {
975 debug_assert!(!value.references.is_empty() || value.references_environment);
977 let mut input = ParserInput::new(&value.css);
978 let mut input = Parser::new(&mut input);
979 let mut position = (input.position(), value.first_token_type);
980 let mut computed_value = ComputedValue::empty();
982 let last_token_type = substitute_block(
990 computed_value.push_from(&input, position, last_token_type)?;
991 computed_value.css.shrink_to_fit();
992 Ok(Arc::new(computed_value))
995 /// Replace `var()` functions in an arbitrary bit of input.
997 /// If the variable has its initial value, the callback should return `Err(())`
998 /// and leave `partial_computed_value` unchanged.
1000 /// Otherwise, it should push the value of the variable (with its own `var()` functions replaced)
1001 /// to `partial_computed_value` and return `Ok(last_token_type of what was pushed)`
1003 /// Return `Err(())` if `input` is invalid at computed-value time.
1004 /// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise.
1005 fn substitute_block<'i>(
1006 input: &mut Parser<'i, '_>,
1007 position: &mut (SourcePosition, TokenSerializationType),
1008 partial_computed_value: &mut ComputedValue,
1009 custom_properties: &CustomPropertiesMap,
1011 ) -> Result<TokenSerializationType, ParseError<'i>> {
1012 let mut last_token_type = TokenSerializationType::nothing();
1013 let mut set_position_at_next_iteration = false;
1015 let before_this_token = input.position();
1016 let next = input.next_including_whitespace_and_comments();
1017 if set_position_at_next_iteration {
1021 Ok(token) => token.serialization_type(),
1022 Err(_) => TokenSerializationType::nothing(),
1025 set_position_at_next_iteration = false;
1027 let token = match next {
1032 Token::Function(ref name)
1033 if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") =>
1035 let is_env = name.eq_ignore_ascii_case("env");
1037 partial_computed_value.push(
1039 input.slice(position.0..before_this_token),
1043 input.parse_nested_block(|input| {
1044 // parse_var_function() / parse_env_function() ensure neither .unwrap() will fail.
1046 let name = input.expect_ident().unwrap();
1050 Atom::from(parse_name(&name).unwrap())
1055 let value = if is_env {
1056 if let Some(v) = device.environment().get(&name, device) {
1063 custom_properties.get(&name).map(|v| &**v)
1066 if let Some(v) = value {
1067 last_token_type = v.last_token_type;
1068 partial_computed_value.push_variable(input, v)?;
1069 // Skip over the fallback, as `parse_nested_block` would return `Err`
1070 // if we don't consume all of `input`.
1071 // FIXME: Add a specialized method to cssparser to do this with less work.
1072 while input.next().is_ok() {}
1074 input.expect_comma()?;
1075 input.skip_whitespace();
1076 let after_comma = input.state();
1077 let first_token_type = input
1078 .next_including_whitespace_and_comments()
1080 .map_or_else(TokenSerializationType::nothing, |t| {
1081 t.serialization_type()
1083 input.reset(&after_comma);
1084 let mut position = (after_comma.position(), first_token_type);
1085 last_token_type = substitute_block(
1088 partial_computed_value,
1092 partial_computed_value.push_from(input, position, last_token_type)?;
1096 set_position_at_next_iteration = true
1098 Token::Function(_) |
1099 Token::ParenthesisBlock |
1100 Token::CurlyBracketBlock |
1101 Token::SquareBracketBlock => {
1102 input.parse_nested_block(|input| {
1106 partial_computed_value,
1111 // It's the same type for CloseCurlyBracket and CloseSquareBracket.
1112 last_token_type = Token::CloseParenthesis.serialization_type();
1115 _ => last_token_type = token.serialization_type(),
1118 // FIXME: deal with things being implicitly closed at the end of the input. E.g.
1120 // <div style="--color: rgb(0,0,0">
1121 // <p style="background: var(--color) var(--image) top left; --image: url('a.png"></p>
1127 /// Replace `var()` and `env()` functions for a non-custom property.
1129 /// Return `Err(())` for invalid at computed time.
1130 pub fn substitute<'i>(
1132 first_token_type: TokenSerializationType,
1133 computed_values_map: Option<&Arc<CustomPropertiesMap>>,
1135 ) -> Result<String, ParseError<'i>> {
1136 let mut substituted = ComputedValue::empty();
1137 let mut input = ParserInput::new(input);
1138 let mut input = Parser::new(&mut input);
1139 let mut position = (input.position(), first_token_type);
1140 let empty_map = CustomPropertiesMap::default();
1141 let custom_properties = match computed_values_map {
1145 let last_token_type = substitute_block(
1152 substituted.push_from(&input, position, last_token_type)?;