Bug 1756130 [wpt PR 32898] - [CSP] Enhance unsafe-eval test to check both realms...
[gecko.git] / servo / components / style / custom_properties.rs
blob5e5ec217b5cbc3ed935aa8047d4e0a22d665014b
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].
6 //!
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};
13 use crate::Atom;
14 use cssparser::{
15     CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
17 use indexmap::IndexMap;
18 use selectors::parser::SelectorParseErrorKind;
19 use servo_arc::Arc;
20 use smallvec::SmallVec;
21 use std::borrow::Cow;
22 use std::cmp;
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.
29 ///
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 {
38     name: Atom,
39     evaluator: EnvironmentEvaluator,
42 macro_rules! make_variable {
43     ($name:expr, $evaluator:expr) => {{
44         EnvironmentVariable {
45             name: $name,
46             evaluator: $evaluator,
47         }
48     }};
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 {
75     ($id:ident) => {
76         unsafe {
77             crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
78                 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
79             )
80         }
81     };
84 macro_rules! lnf_int_variable {
85     ($atom:expr, $id:ident, $ctor:ident) => {{
86         fn __eval(_: &Device) -> VariableValue {
87             VariableValue::$ctor(lnf_int!($id))
88         }
89         make_variable!($atom, __eval)
90     }};
93 static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
94     lnf_int_variable!(
95         atom!("-moz-gtk-csd-titlebar-radius"),
96         TitlebarRadius,
97         int_pixels
98     ),
99     lnf_int_variable!(atom!("-moz-gtk-csd-menu-radius"), GtkMenuRadius, int_pixels),
100     lnf_int_variable!(
101         atom!("-moz-gtk-csd-close-button-position"),
102         GTKCSDCloseButtonPosition,
103         integer
104     ),
105     lnf_int_variable!(
106         atom!("-moz-gtk-csd-minimize-button-position"),
107         GTKCSDMinimizeButtonPosition,
108         integer
109     ),
110     lnf_int_variable!(
111         atom!("-moz-gtk-csd-maximize-button-position"),
112         GTKCSDMaximizeButtonPosition,
113         integer
114     ),
117 impl CssEnvironment {
118     #[inline]
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));
122         }
123         if !device.is_chrome_document() {
124             return None;
125         }
126         let var = CHROME_ENVIRONMENT_VARIABLES
127             .iter()
128             .find(|var| var.name == *name)?;
129         Some((var.evaluator)(device))
130     }
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 {
143         Ok(&s[2..])
144     } else {
145         Err(())
146     }
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 {
155     css: String,
157     first_token_type: TokenSerializationType,
158     last_token_type: TokenSerializationType,
160     /// Whether a variable value has a reference to an environment variable.
161     ///
162     /// If this is the case, we need to perform variable substitution on the
163     /// value.
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
172     where
173         W: Write,
174     {
175         dest.write_str(&self.css)
176     }
179 /// A map from CSS variable names to CSS variable computed values, used for
180 /// resolving.
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
188 /// properties.
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.
201 #[derive(Default)]
202 struct VarOrEnvReferences {
203     custom_property_references: PrecomputedHashSet<Name>,
204     references_environment: bool,
207 impl VariableValue {
208     fn empty() -> Self {
209         Self {
210             css: String::new(),
211             last_token_type: TokenSerializationType::nothing(),
212             first_token_type: TokenSerializationType::nothing(),
213             references: Default::default(),
214             references_environment: false,
215         }
216     }
218     fn push<'i>(
219         &mut self,
220         input: &Parser<'i, '_>,
221         css: &str,
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.
227         ///
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));
236         }
238         // This happens e.g. between two subsequent var() functions:
239         // `var(--a)var(--b)`.
240         //
241         // In that case, css_*_token_type is nonsensical.
242         if css.is_empty() {
243             return Ok(());
244         }
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:
249         if self
250             .last_token_type
251             .needs_separator_when_before(css_first_token_type)
252         {
253             self.css.push_str("/**/")
254         }
255         self.css.push_str(css);
256         self.last_token_type = css_last_token_type;
257         Ok(())
258     }
260     fn push_from<'i>(
261         &mut self,
262         input: &Parser<'i, '_>,
263         position: (SourcePosition, TokenSerializationType),
264         last_token_type: TokenSerializationType,
265     ) -> Result<(), ParseError<'i>> {
266         self.push(
267             input,
268             input.slice_from(position.0),
269             position.1,
270             last_token_type,
271         )
272     }
274     fn push_variable<'i>(
275         &mut self,
276         input: &Parser<'i, '_>,
277         variable: &ComputedValue,
278     ) -> Result<(), ParseError<'i>> {
279         debug_assert!(variable.references.is_empty());
280         self.push(
281             input,
282             &variable.css,
283             variable.first_token_type,
284             variable.last_token_type,
285         )
286     }
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
297             .into_iter()
298             .collect::<Vec<_>>()
299             .into_boxed_slice();
301         let mut css = css.into_owned();
302         css.shrink_to_fit();
304         Ok(Arc::new(VariableValue {
305             css,
306             first_token_type,
307             last_token_type,
308             references: custom_property_references,
309             references_environment: references.references_environment,
310         }))
311     }
313     /// Create VariableValue from an int.
314     fn integer(number: i32) -> Self {
315         Self::from_token(Token::Number {
316             has_sign: false,
317             value: number as f32,
318             int_value: Some(number),
319         })
320     }
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
326         // Token object.
327         Self::from_token(Token::Dimension {
328             has_sign: false,
329             value: number,
330             int_value: None,
331             unit: CowRcStr::from("px"),
332         })
333     }
335     /// Create VariableValue from an integer amount of CSS pixels.
336     fn int_pixels(number: i32) -> Self {
337         Self::from_token(Token::Dimension {
338             has_sign: false,
339             value: number as f32,
340             int_value: Some(number),
341             unit: CowRcStr::from("px"),
342         })
343     }
345     fn from_token(token: Token) -> Self {
346         let token_type = token.serialization_type();
347         let mut css = token.to_css_string();
348         css.shrink_to_fit();
350         VariableValue {
351             css,
352             first_token_type: token_type,
353             last_token_type: token_type,
354             references: Default::default(),
355             references_environment: false,
356         }
357     }
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();
374     let (first, last) =
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'\'') {
380             css.to_mut().pop();
381         }
382         css.to_mut().push_str(&missing_closing_characters);
383     }
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)
395     })
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() {
408         Ok(token) => token,
409         Err(_) => {
410             return Ok((
411                 TokenSerializationType::nothing(),
412                 TokenSerializationType::nothing(),
413             ));
414         },
415     };
416     let first_token_type = token.serialization_type();
417     loop {
418         macro_rules! nested {
419             () => {
420                 input.parse_nested_block(|input| {
421                     parse_declaration_value_block(
422                         input,
423                         references.as_mut().map(|r| &mut **r),
424                         missing_closing_characters,
425                     )
426                 })?
427             };
428         }
429         macro_rules! check_closed {
430             ($closing:expr) => {
431                 if !input.slice_from(token_start).ends_with($closing) {
432                     missing_closing_characters.push_str($closing)
433                 }
434             };
435         }
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('*') {
442                         "/"
443                     } else {
444                         "*/"
445                     })
446                 }
447                 serialization_type
448             },
449             Token::BadUrl(ref u) => {
450                 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
451                 return Err(input.new_custom_error(e));
452             },
453             Token::BadString(ref s) => {
454                 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
455                 return Err(input.new_custom_error(e));
456             },
457             Token::CloseParenthesis => {
458                 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
459                 return Err(input.new_custom_error(e));
460             },
461             Token::CloseSquareBracket => {
462                 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
463                 return Err(input.new_custom_error(e));
464             },
465             Token::CloseCurlyBracket => {
466                 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
467                 return Err(input.new_custom_error(e));
468             },
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))
474                     })?;
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))
480                     })?;
481                     input.reset(&args_start);
482                 }
483                 nested!();
484                 check_closed!(")");
485                 Token::CloseParenthesis.serialization_type()
486             },
487             Token::ParenthesisBlock => {
488                 nested!();
489                 check_closed!(")");
490                 Token::CloseParenthesis.serialization_type()
491             },
492             Token::CurlyBracketBlock => {
493                 nested!();
494                 check_closed!("}");
495                 Token::CloseCurlyBracket.serialization_type()
496             },
497             Token::SquareBracketBlock => {
498                 nested!();
499                 check_closed!("]");
500                 Token::CloseSquareBracket.serialization_type()
501             },
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)
509                 }
510                 serialization_type
511             },
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) |
517             Token::Dimension {
518                 unit: ref value, ..
519             } => {
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("�")
528                 }
529                 if is_unquoted_url {
530                     check_closed!(")");
531                 }
532                 serialization_type
533             },
534             _ => token.serialization_type(),
535         };
537         token_start = input.position();
538         token = match input.next_including_whitespace_and_comments() {
539             Ok(token) => token,
540             Err(..) => return Ok((first_token_type, last_token_type)),
541         };
542     }
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() {}
551         Ok(())
552     })
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()))
563     })?;
564     if input.try_parse(|input| input.expect_comma()).is_ok() {
565         parse_fallback(input)?;
566     }
567     if let Some(refs) = references {
568         refs.custom_property_references.insert(Atom::from(name));
569     }
570     Ok(())
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)?;
582     }
583     if let Some(references) = references {
584         references.references_environment = true;
585     }
586     Ok(())
589 /// A struct that takes care of encapsulating the cascade process for custom
590 /// properties.
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)>,
597     device: &'a Device,
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 {
603         Self {
604             seen: PrecomputedHashSet::default(),
605             reverted: Default::default(),
606             may_have_cycles: false,
607             custom_properties: None,
608             inherited,
609             device,
610         }
611     }
613     /// Cascade a given custom property declaration.
614     pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) {
615         let CustomDeclaration {
616             ref name,
617             ref value,
618         } = *declaration;
620         if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
621             if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
622                 return;
623             }
624         }
626         let was_already_present = !self.seen.insert(name);
627         if was_already_present {
628             return;
629         }
631         if !self.value_may_affect_style(name, value) {
632             return;
633         }
635         if self.custom_properties.is_none() {
636             self.custom_properties = Some(match self.inherited {
637                 Some(inherited) => (**inherited).clone(),
638                 None => CustomPropertiesMap::default(),
639             });
640         }
642         let map = self.custom_properties.as_mut().unwrap();
643         match *value {
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);
653                     match result {
654                         Ok(new_value) => new_value,
655                         Err(..) => {
656                             map.remove(name);
657                             return;
658                         },
659                     }
660                 } else {
661                     (*unparsed_value).clone()
662                 };
663                 map.insert(name.clone(), value);
664             },
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));
670                 },
671                 CSSWideKeyword::Initial => {
672                     map.remove(name);
673                 },
674                 // handled in value_may_affect_style
675                 CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(),
676             },
677         }
678     }
680     fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
681         match *value {
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.
687                 return false;
688             },
689             _ => {},
690         }
692         let existing_value = self
693             .custom_properties
694             .as_ref()
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.
702                 return false;
703             },
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 {
708                     return false;
709                 }
710             },
711             _ => {},
712         }
714         true
715     }
717     fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool {
718         let inherited = match self.inherited {
719             Some(inherited) => inherited,
720             None => return false,
721         };
722         if inherited.len() != map.len() {
723             return false;
724         }
725         for name in self.seen.iter() {
726             if inherited.get(*name) != map.get(*name) {
727                 return false;
728             }
729         }
730         true
731     }
733     /// Returns the final map of applicable custom properties.
734     ///
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.
737     ///
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() {
741             Some(m) => m,
742             None => return self.inherited.cloned(),
743         };
745         if self.may_have_cycles {
746             substitute_all(&mut map, &self.seen, self.device);
747         }
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
752         // map in that case.
753         if self.inherited_properties_match(&map) {
754             return self.inherited.cloned();
755         }
757         map.shrink_to_fit();
758         Some(Arc::new(map))
759     }
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
769     // listed in
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.
774     #[derive(Debug)]
775     struct VarInfo {
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.
780         name: Option<Name>,
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.
785         lowlink: usize,
786     }
787     /// Context struct for traversing the variable graph, so that we can
788     /// avoid referencing all the fields multiple times.
789     #[derive(Debug)]
790     struct Context<'a> {
791         /// Number of variables visited. This is used as the order index
792         /// when we visit a new unresolved variable.
793         count: usize,
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.
803         device: &'a Device,
804     }
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.
810     ///
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() {
831                 debug_assert!(
832                     !value.references_environment,
833                     "Should've been handled earlier"
834                 );
835                 return None;
836             }
838             // Whether this variable has been visited in this traversal.
839             let key;
840             match context.index_map.entry(name.clone()) {
841                 Entry::Occupied(entry) => {
842                     return Some(*entry.get());
843                 },
844                 Entry::Vacant(entry) => {
845                     key = entry.key().clone();
846                     entry.insert(context.count);
847                 },
848             }
850             // Hold a strong reference to the value so that we don't
851             // need to keep reference to context.map.
852             (key, value.clone())
853         };
855         // Add new entry to the information table.
856         let index = context.count;
857         context.count += 1;
858         debug_assert_eq!(index, context.var_info.len());
859         context.var_info.push(VarInfo {
860             name: Some(name),
861             lowlink: index,
862         });
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.
872                 None => {
873                     continue;
874                 },
875             };
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 {
883                 self_ref = true;
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);
888             }
889         }
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.
896             //
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.
900             return Some(index);
901         }
903         // This is the root of a strong-connected component.
904         let mut in_loop = self_ref;
905         let name;
906         loop {
907             let var_index = context
908                 .stack
909                 .pop()
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
914             // reference count.
915             let var_name = var_info
916                 .name
917                 .take()
918                 .expect("Variable should not be poped from stack twice");
919             if var_index == index {
920                 name = var_name;
921                 break;
922             }
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);
927             in_loop = true;
928         }
929         if in_loop {
930             // This variable is in loop. Resolve to invalid.
931             context.map.remove(&name);
932             return None;
933         }
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
937         // now.
938         let result = substitute_references_in_value(&value, &context.map, &context.device);
939         match result {
940             Ok(computed_value) => {
941                 context.map.insert(name, computed_value);
942             },
943             Err(..) => {
944                 // This is invalid, reset it to the guaranteed-invalid value.
945                 context.map.remove(&name);
946             },
947         }
949         // All resolved, so return the signal value.
950         None
951     }
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.
956     for name in seen {
957         let mut context = Context {
958             count: 0,
959             index_map: PrecomputedHashMap::default(),
960             stack: SmallVec::new(),
961             var_info: SmallVec::new(),
962             map: custom_properties_map,
963             device,
964         };
965         traverse(name, &mut context);
966     }
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,
973     device: &Device,
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(
983         &mut input,
984         &mut position,
985         &mut computed_value,
986         custom_properties,
987         device,
988     )?;
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,
1010     device: &Device,
1011 ) -> Result<TokenSerializationType, ParseError<'i>> {
1012     let mut last_token_type = TokenSerializationType::nothing();
1013     let mut set_position_at_next_iteration = false;
1014     loop {
1015         let before_this_token = input.position();
1016         let next = input.next_including_whitespace_and_comments();
1017         if set_position_at_next_iteration {
1018             *position = (
1019                 before_this_token,
1020                 match next {
1021                     Ok(token) => token.serialization_type(),
1022                     Err(_) => TokenSerializationType::nothing(),
1023                 },
1024             );
1025             set_position_at_next_iteration = false;
1026         }
1027         let token = match next {
1028             Ok(token) => token,
1029             Err(..) => break,
1030         };
1031         match token {
1032             Token::Function(ref name)
1033                 if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") =>
1034             {
1035                 let is_env = name.eq_ignore_ascii_case("env");
1037                 partial_computed_value.push(
1038                     input,
1039                     input.slice(position.0..before_this_token),
1040                     position.1,
1041                     last_token_type,
1042                 )?;
1043                 input.parse_nested_block(|input| {
1044                     // parse_var_function() / parse_env_function() ensure neither .unwrap() will fail.
1045                     let name = {
1046                         let name = input.expect_ident().unwrap();
1047                         if is_env {
1048                             Atom::from(&**name)
1049                         } else {
1050                             Atom::from(parse_name(&name).unwrap())
1051                         }
1052                     };
1054                     let env_value;
1055                     let value = if is_env {
1056                         if let Some(v) = device.environment().get(&name, device) {
1057                             env_value = v;
1058                             Some(&env_value)
1059                         } else {
1060                             None
1061                         }
1062                     } else {
1063                         custom_properties.get(&name).map(|v| &**v)
1064                     };
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() {}
1073                     } else {
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()
1079                             .ok()
1080                             .map_or_else(TokenSerializationType::nothing, |t| {
1081                                 t.serialization_type()
1082                             });
1083                         input.reset(&after_comma);
1084                         let mut position = (after_comma.position(), first_token_type);
1085                         last_token_type = substitute_block(
1086                             input,
1087                             &mut position,
1088                             partial_computed_value,
1089                             custom_properties,
1090                             device,
1091                         )?;
1092                         partial_computed_value.push_from(input, position, last_token_type)?;
1093                     }
1094                     Ok(())
1095                 })?;
1096                 set_position_at_next_iteration = true
1097             },
1098             Token::Function(_) |
1099             Token::ParenthesisBlock |
1100             Token::CurlyBracketBlock |
1101             Token::SquareBracketBlock => {
1102                 input.parse_nested_block(|input| {
1103                     substitute_block(
1104                         input,
1105                         position,
1106                         partial_computed_value,
1107                         custom_properties,
1108                         device,
1109                     )
1110                 })?;
1111                 // It's the same type for CloseCurlyBracket and CloseSquareBracket.
1112                 last_token_type = Token::CloseParenthesis.serialization_type();
1113             },
1115             _ => last_token_type = token.serialization_type(),
1116         }
1117     }
1118     // FIXME: deal with things being implicitly closed at the end of the input. E.g.
1119     // ```html
1120     // <div style="--color: rgb(0,0,0">
1121     // <p style="background: var(--color) var(--image) top left; --image: url('a.png"></p>
1122     // </div>
1123     // ```
1124     Ok(last_token_type)
1127 /// Replace `var()` and `env()` functions for a non-custom property.
1129 /// Return `Err(())` for invalid at computed time.
1130 pub fn substitute<'i>(
1131     input: &'i str,
1132     first_token_type: TokenSerializationType,
1133     computed_values_map: Option<&Arc<CustomPropertiesMap>>,
1134     device: &Device,
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 {
1142         Some(m) => &**m,
1143         None => &empty_map,
1144     };
1145     let last_token_type = substitute_block(
1146         &mut input,
1147         &mut position,
1148         &mut substituted,
1149         &custom_properties,
1150         device,
1151     )?;
1152     substituted.push_from(&input, position, last_token_type)?;
1153     Ok(substituted.css)