1use crate::applicable_declarations::CascadePriority;
10use crate::custom_properties_map::CustomPropertiesMap;
11use crate::dom::AttributeTracker;
12use crate::media_queries::Device;
13use crate::properties::{
14 CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
15 PropertyDeclaration,
16};
17use crate::properties_and_values::{
18 registry::PropertyRegistrationData,
19 syntax::{data_type::DependentDataTypes, Descriptor},
20 value::{
21 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
22 SpecifiedValue as SpecifiedRegisteredValue,
23 },
24};
25use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
26use crate::stylesheets::UrlExtraData;
27use crate::stylist::Stylist;
28use crate::values::computed::{self, ToComputedValue};
29use crate::values::generics::calc::SortKey as AttrUnit;
30use crate::values::specified::FontRelativeLength;
31use crate::values::specified::ParsedNamespace;
32use crate::{derives::*, Namespace, Prefix};
33use crate::{Atom, LocalName};
34use cssparser::{
35 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
36};
37use rustc_hash::FxHashMap;
38use selectors::parser::SelectorParseErrorKind;
39use servo_arc::Arc;
40use smallvec::SmallVec;
41use std::borrow::Cow;
42use std::collections::hash_map::Entry;
43use std::fmt::{self, Write};
44use std::ops::{Index, IndexMut};
45use std::{cmp, num};
46use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
47
48#[derive(Debug, MallocSizeOf)]
53pub struct CssEnvironment;
54
55type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
56
57struct EnvironmentVariable {
58 name: Atom,
59 evaluator: EnvironmentEvaluator,
60}
61
62macro_rules! make_variable {
63 ($name:expr, $evaluator:expr) => {{
64 EnvironmentVariable {
65 name: $name,
66 evaluator: $evaluator,
67 }
68 }};
69}
70
71fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
72 VariableValue::pixels(device.safe_area_insets().top, url_data)
73}
74
75fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
76 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
77}
78
79fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
80 VariableValue::pixels(device.safe_area_insets().left, url_data)
81}
82
83fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
84 VariableValue::pixels(device.safe_area_insets().right, url_data)
85}
86
87#[cfg(feature = "gecko")]
88fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
89 use crate::queries::values::PrefersColorScheme;
90 let prefers_color_scheme = unsafe {
91 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
92 device.document(),
93 true,
94 )
95 };
96 VariableValue::ident(
97 match prefers_color_scheme {
98 PrefersColorScheme::Light => "light",
99 PrefersColorScheme::Dark => "dark",
100 },
101 url_data,
102 )
103}
104
105#[cfg(feature = "servo")]
106fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
107 VariableValue::ident("light", url_data)
109}
110
111fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
112 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
113}
114
115fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
116 VariableValue::pixels(
117 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
118 url_data,
119 )
120}
121
122static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
123 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
124 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
125 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
126 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
127];
128
129#[cfg(feature = "gecko")]
130macro_rules! lnf_int {
131 ($id:ident) => {
132 unsafe {
133 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
134 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
135 )
136 }
137 };
138}
139
140#[cfg(feature = "servo")]
141macro_rules! lnf_int {
142 ($id:ident) => {
143 0
145 };
146}
147
148macro_rules! lnf_int_variable {
149 ($atom:expr, $id:ident, $ctor:ident) => {{
150 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
151 VariableValue::$ctor(lnf_int!($id), url_data)
152 }
153 make_variable!($atom, __eval)
154 }};
155}
156
157fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
158 let int_pixels = lnf_int!(TitlebarRadius);
159 let unzoomed_scale =
160 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
161 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
162}
163
164static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
165 make_variable!(
166 atom!("-moz-gtk-csd-titlebar-radius"),
167 eval_gtk_csd_titlebar_radius
168 ),
169 lnf_int_variable!(
170 atom!("-moz-gtk-csd-tooltip-radius"),
171 TooltipRadius,
172 int_pixels
173 ),
174 lnf_int_variable!(
175 atom!("-moz-gtk-csd-close-button-position"),
176 GTKCSDCloseButtonPosition,
177 integer
178 ),
179 lnf_int_variable!(
180 atom!("-moz-gtk-csd-minimize-button-position"),
181 GTKCSDMinimizeButtonPosition,
182 integer
183 ),
184 lnf_int_variable!(
185 atom!("-moz-gtk-csd-maximize-button-position"),
186 GTKCSDMaximizeButtonPosition,
187 integer
188 ),
189 lnf_int_variable!(
190 atom!("-moz-overlay-scrollbar-fade-duration"),
191 ScrollbarFadeDuration,
192 int_ms
193 ),
194 make_variable!(
195 atom!("-moz-content-preferred-color-scheme"),
196 get_content_preferred_color_scheme
197 ),
198 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
199 make_variable!(atom!("hairline"), get_hairline),
200];
201
202impl CssEnvironment {
203 #[inline]
204 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
205 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
206 return Some((var.evaluator)(device, url_data));
207 }
208 if !url_data.chrome_rules_enabled() {
209 return None;
210 }
211 let var = CHROME_ENVIRONMENT_VARIABLES
212 .iter()
213 .find(|var| var.name == *name)?;
214 Some((var.evaluator)(device, url_data))
215 }
216}
217
218pub type Name = Atom;
222
223pub fn parse_name(s: &str) -> Result<&str, ()> {
227 if s.starts_with("--") && s.len() > 2 {
228 Ok(&s[2..])
229 } else {
230 Err(())
231 }
232}
233
234#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
239pub struct VariableValue {
240 pub css: String,
242
243 pub url_data: UrlExtraData,
245
246 first_token_type: TokenSerializationType,
247 last_token_type: TokenSerializationType,
248
249 references: References,
251}
252
253trivial_to_computed_value!(VariableValue);
254
255pub fn compute_variable_value(
257 value: &Arc<VariableValue>,
258 registration: &PropertyRegistrationData,
259 computed_context: &computed::Context,
260) -> Option<ComputedRegisteredValue> {
261 if registration.syntax.is_universal() {
262 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
263 }
264 compute_value(&value.css, &value.url_data, registration, computed_context).ok()
265}
266
267impl PartialEq for VariableValue {
269 fn eq(&self, other: &Self) -> bool {
270 self.css == other.css
271 }
272}
273
274impl Eq for VariableValue {}
275
276impl ToCss for SpecifiedValue {
277 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
278 where
279 W: Write,
280 {
281 dest.write_str(&self.css)
282 }
283}
284
285#[repr(C)]
288#[derive(Clone, Debug, Default, PartialEq)]
289pub struct ComputedCustomProperties {
290 pub inherited: CustomPropertiesMap,
293 pub non_inherited: CustomPropertiesMap,
295}
296
297impl ComputedCustomProperties {
298 pub fn is_empty(&self) -> bool {
300 self.inherited.is_empty() && self.non_inherited.is_empty()
301 }
302
303 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
305 self.inherited
308 .get_index(index)
309 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
310 }
311
312 pub(crate) fn insert(
315 &mut self,
316 registration: &PropertyRegistrationData,
317 name: &Name,
318 value: ComputedRegisteredValue,
319 ) {
320 self.map_mut(registration).insert(name, value)
321 }
322
323 pub(crate) fn remove(&mut self, registration: &PropertyRegistrationData, name: &Name) {
326 self.map_mut(registration).remove(name);
327 }
328
329 fn shrink_to_fit(&mut self) {
331 self.inherited.shrink_to_fit();
332 self.non_inherited.shrink_to_fit();
333 }
334
335 fn map_mut(&mut self, registration: &PropertyRegistrationData) -> &mut CustomPropertiesMap {
336 if registration.inherits() {
337 &mut self.inherited
338 } else {
339 &mut self.non_inherited
340 }
341 }
342
343 pub fn get(
345 &self,
346 registration: &PropertyRegistrationData,
347 name: &Name,
348 ) -> Option<&ComputedRegisteredValue> {
349 if registration.inherits() {
350 self.inherited.get(name)
351 } else {
352 self.non_inherited.get(name)
353 }
354 }
355}
356
357pub type SpecifiedValue = VariableValue;
360pub type ComputedValue = VariableValue;
363
364#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
366struct NonCustomReferences(u8);
367
368bitflags! {
369 impl NonCustomReferences: u8 {
370 const FONT_UNITS = 1 << 0;
372 const ROOT_FONT_UNITS = 1 << 1;
374 const LH_UNITS = 1 << 2;
376 const ROOT_LH_UNITS = 1 << 3;
378 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
380 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
382 }
383}
384
385impl NonCustomReferences {
386 fn for_each<F>(&self, mut f: F)
387 where
388 F: FnMut(SingleNonCustomReference),
389 {
390 for (_, r) in self.iter_names() {
391 let single = match r {
392 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
393 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
394 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
395 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
396 _ => unreachable!("Unexpected single bit value"),
397 };
398 f(single);
399 }
400 }
401
402 fn from_unit(value: &CowRcStr) -> Self {
403 if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
408 return Self::FONT_UNITS | Self::LH_UNITS;
409 }
410 if value.eq_ignore_ascii_case(FontRelativeLength::EM)
411 || value.eq_ignore_ascii_case(FontRelativeLength::EX)
412 || value.eq_ignore_ascii_case(FontRelativeLength::CAP)
413 || value.eq_ignore_ascii_case(FontRelativeLength::CH)
414 || value.eq_ignore_ascii_case(FontRelativeLength::IC)
415 {
416 return Self::FONT_UNITS;
417 }
418 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
419 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
420 }
421 if value.eq_ignore_ascii_case(FontRelativeLength::REM)
422 || value.eq_ignore_ascii_case(FontRelativeLength::REX)
423 || value.eq_ignore_ascii_case(FontRelativeLength::RCH)
424 || value.eq_ignore_ascii_case(FontRelativeLength::RCAP)
425 || value.eq_ignore_ascii_case(FontRelativeLength::RIC)
426 {
427 return Self::ROOT_FONT_UNITS;
428 }
429 Self::empty()
430 }
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
434enum SingleNonCustomReference {
435 FontUnits = 0,
436 RootFontUnits,
437 LhUnits,
438 RootLhUnits,
439}
440
441struct NonCustomReferenceMap<T>([Option<T>; 4]);
442
443impl<T> Default for NonCustomReferenceMap<T> {
444 fn default() -> Self {
445 NonCustomReferenceMap(Default::default())
446 }
447}
448
449impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
450 type Output = Option<T>;
451
452 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
453 &self.0[reference as usize]
454 }
455}
456
457impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
458 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
459 &mut self.0[reference as usize]
460 }
461}
462
463#[derive(Clone, Copy, PartialEq, Eq)]
465#[allow(missing_docs)]
466pub enum DeferFontRelativeCustomPropertyResolution {
467 Yes,
468 No,
469}
470
471#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
472enum SubstitutionFunctionKind {
473 Var,
474 Env,
475 Attr,
476}
477
478#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
479enum AttributeType {
480 None,
481 RawString,
482 Type(Descriptor),
483 Unit(AttrUnit),
484}
485
486#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
487struct AttributeData {
488 kind: AttributeType,
489 namespace: ParsedNamespace,
490}
491
492#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
493struct VariableFallback {
494 start: num::NonZeroUsize,
498 first_token_type: TokenSerializationType,
499 last_token_type: TokenSerializationType,
500}
501
502#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
503struct SubstitutionFunctionReference {
504 name: Name,
505 start: usize,
506 end: usize,
507 fallback: Option<VariableFallback>,
508 attribute_data: AttributeData,
509 prev_token_type: TokenSerializationType,
510 next_token_type: TokenSerializationType,
511 substitution_kind: SubstitutionFunctionKind,
512}
513
514#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
517struct References {
518 refs: Vec<SubstitutionFunctionReference>,
519 non_custom_references: NonCustomReferences,
520 any_env: bool,
521 any_var: bool,
522 any_attr: bool,
523}
524
525impl References {
526 fn has_references(&self) -> bool {
527 !self.refs.is_empty()
528 }
529
530 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
531 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
532 if is_root_element {
533 mask |= NonCustomReferences::ROOT_DEPENDENCIES
534 }
535 self.non_custom_references & mask
536 }
537}
538
539impl VariableValue {
540 fn empty(url_data: &UrlExtraData) -> Self {
541 Self {
542 css: String::new(),
543 last_token_type: Default::default(),
544 first_token_type: Default::default(),
545 url_data: url_data.clone(),
546 references: Default::default(),
547 }
548 }
549
550 pub fn new(
553 css: String,
554 url_data: &UrlExtraData,
555 first_token_type: TokenSerializationType,
556 last_token_type: TokenSerializationType,
557 ) -> Self {
558 Self {
559 css,
560 url_data: url_data.clone(),
561 first_token_type,
562 last_token_type,
563 references: Default::default(),
564 }
565 }
566
567 fn push<'i>(
568 &mut self,
569 css: &str,
570 css_first_token_type: TokenSerializationType,
571 css_last_token_type: TokenSerializationType,
572 ) -> Result<(), ()> {
573 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
581
582 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
583 return Err(());
584 }
585
586 if css.is_empty() {
591 return Ok(());
592 }
593
594 self.first_token_type.set_if_nothing(css_first_token_type);
595 if self
598 .last_token_type
599 .needs_separator_when_before(css_first_token_type)
600 {
601 self.css.push_str("/**/")
602 }
603 self.css.push_str(css);
604 self.last_token_type = css_last_token_type;
605 Ok(())
606 }
607
608 pub fn parse<'i, 't>(
610 input: &mut Parser<'i, 't>,
611 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
612 url_data: &UrlExtraData,
613 ) -> Result<Self, ParseError<'i>> {
614 let mut references = References::default();
615 let mut missing_closing_characters = String::new();
616 let start_position = input.position();
617 let (first_token_type, last_token_type) = parse_declaration_value(
618 input,
619 start_position,
620 namespaces,
621 &mut references,
622 &mut missing_closing_characters,
623 )?;
624 let mut css = input
625 .slice_from(start_position)
626 .trim_ascii_start()
627 .to_owned();
628 if !missing_closing_characters.is_empty() {
629 if css.ends_with("\\")
631 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
632 {
633 css.pop();
634 }
635 css.push_str(&missing_closing_characters);
636 }
637
638 css.truncate(css.trim_ascii_end().len());
639 css.shrink_to_fit();
640 references.refs.shrink_to_fit();
641
642 Ok(Self {
643 css,
644 url_data: url_data.clone(),
645 first_token_type,
646 last_token_type,
647 references,
648 })
649 }
650
651 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
653 Self::from_token(
654 Token::Number {
655 has_sign: false,
656 value: number as f32,
657 int_value: Some(number),
658 },
659 url_data,
660 )
661 }
662
663 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
665 Self::from_token(Token::Ident(ident.into()), url_data)
666 }
667
668 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
670 Self::from_token(
674 Token::Dimension {
675 has_sign: false,
676 value: number,
677 int_value: None,
678 unit: CowRcStr::from("px"),
679 },
680 url_data,
681 )
682 }
683
684 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
686 Self::from_token(
687 Token::Dimension {
688 has_sign: false,
689 value: number as f32,
690 int_value: Some(number),
691 unit: CowRcStr::from("ms"),
692 },
693 url_data,
694 )
695 }
696
697 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
699 Self::from_token(
700 Token::Dimension {
701 has_sign: false,
702 value: number as f32,
703 int_value: Some(number),
704 unit: CowRcStr::from("px"),
705 },
706 url_data,
707 )
708 }
709
710 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
711 let token_type = token.serialization_type();
712 let mut css = token.to_css_string();
713 css.shrink_to_fit();
714
715 VariableValue {
716 css,
717 url_data: url_data.clone(),
718 first_token_type: token_type,
719 last_token_type: token_type,
720 references: Default::default(),
721 }
722 }
723
724 pub fn css_text(&self) -> &str {
726 &self.css
727 }
728
729 pub fn has_references(&self) -> bool {
732 self.references.has_references()
733 }
734}
735
736fn parse_declaration_value<'i, 't>(
738 input: &mut Parser<'i, 't>,
739 input_start: SourcePosition,
740 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
741 references: &mut References,
742 missing_closing_characters: &mut String,
743) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
744 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
745 parse_declaration_value_block(
746 input,
747 input_start,
748 namespaces,
749 references,
750 missing_closing_characters,
751 )
752 })
753}
754
755fn parse_declaration_value_block<'i, 't>(
757 input: &mut Parser<'i, 't>,
758 input_start: SourcePosition,
759 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
760 references: &mut References,
761 missing_closing_characters: &mut String,
762) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
763 let mut is_first = true;
764 let mut first_token_type = TokenSerializationType::Nothing;
765 let mut last_token_type = TokenSerializationType::Nothing;
766 let mut prev_reference_index: Option<usize> = None;
767 loop {
768 let token_start = input.position();
769 let Ok(token) = input.next_including_whitespace_and_comments() else {
770 break;
771 };
772
773 let prev_token_type = last_token_type;
774 let serialization_type = token.serialization_type();
775 last_token_type = serialization_type;
776 if is_first {
777 first_token_type = last_token_type;
778 is_first = false;
779 }
780
781 macro_rules! nested {
782 ($closing:expr) => {{
783 let mut inner_end_position = None;
784 let result = input.parse_nested_block(|input| {
785 let result = parse_declaration_value_block(
786 input,
787 input_start,
788 namespaces,
789 references,
790 missing_closing_characters,
791 )?;
792 inner_end_position = Some(input.position());
793 Ok(result)
794 })?;
795 if inner_end_position.unwrap() == input.position() {
796 missing_closing_characters.push_str($closing);
797 }
798 result
799 }};
800 }
801 if let Some(index) = prev_reference_index.take() {
802 references.refs[index].next_token_type = serialization_type;
803 }
804 match *token {
805 Token::Comment(_) => {
806 let token_slice = input.slice_from(token_start);
807 if !token_slice.ends_with("*/") {
808 missing_closing_characters.push_str(if token_slice.ends_with('*') {
809 "/"
810 } else {
811 "*/"
812 })
813 }
814 },
815 Token::BadUrl(ref u) => {
816 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
817 return Err(input.new_custom_error(e));
818 },
819 Token::BadString(ref s) => {
820 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
821 return Err(input.new_custom_error(e));
822 },
823 Token::CloseParenthesis => {
824 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
825 return Err(input.new_custom_error(e));
826 },
827 Token::CloseSquareBracket => {
828 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
829 return Err(input.new_custom_error(e));
830 },
831 Token::CloseCurlyBracket => {
832 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
833 return Err(input.new_custom_error(e));
834 },
835 Token::Function(ref name) => {
836 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
837 Some(SubstitutionFunctionKind::Attr) => {
838 if static_prefs::pref!("layout.css.attr.enabled") {
839 Some(SubstitutionFunctionKind::Attr)
840 } else {
841 None
842 }
843 },
844 kind => kind,
845 };
846 if let Some(substitution_kind) = substitution_kind {
847 let our_ref_index = references.refs.len();
848 let mut input_end_position = None;
849 let fallback = input.parse_nested_block(|input| {
850 let mut namespace = ParsedNamespace::Known(Namespace::default());
851 if substitution_kind == SubstitutionFunctionKind::Attr {
852 if let Some(namespaces) = namespaces {
853 if let Ok(ns) = input
854 .try_parse(|input| ParsedNamespace::parse(namespaces, input))
855 {
856 namespace = ns;
857 }
858 }
859 }
860 let name = input.expect_ident()?;
863 let name =
864 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
865 match parse_name(name.as_ref()) {
866 Ok(name) => name,
867 Err(()) => {
868 let name = name.clone();
869 return Err(input.new_custom_error(
870 SelectorParseErrorKind::UnexpectedIdent(name),
871 ));
872 },
873 }
874 } else {
875 name.as_ref()
876 });
877
878 let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
879 {
880 parse_attr_type(input)
881 } else {
882 AttributeType::None
883 };
884
885 let start = token_start.byte_index() - input_start.byte_index();
889 references.refs.push(SubstitutionFunctionReference {
890 name,
891 start,
892 end: start,
894 prev_token_type,
895 next_token_type: TokenSerializationType::Nothing,
897 fallback: None,
899 attribute_data: AttributeData {
900 kind: attribute_kind,
901 namespace,
902 },
903 substitution_kind: substitution_kind.clone(),
904 });
905
906 let mut fallback = None;
907 if input.try_parse(|input| input.expect_comma()).is_ok() {
908 input.skip_whitespace();
909 let fallback_start = num::NonZeroUsize::new(
910 input.position().byte_index() - input_start.byte_index(),
911 )
912 .unwrap();
913 let (first, last) = parse_declaration_value(
916 input,
917 input_start,
918 namespaces,
919 references,
920 missing_closing_characters,
921 )?;
922 fallback = Some(VariableFallback {
923 start: fallback_start,
924 first_token_type: first,
925 last_token_type: last,
926 });
927 input_end_position = Some(input.position());
928 } else {
929 let state = input.state();
930 parse_declaration_value_block(
934 input,
935 input_start,
936 namespaces,
937 references,
938 missing_closing_characters,
939 )?;
940 input_end_position = Some(input.position());
941 input.reset(&state);
942 }
943 Ok(fallback)
944 })?;
945 if input_end_position.unwrap() == input.position() {
946 missing_closing_characters.push_str(")");
947 }
948 prev_reference_index = Some(our_ref_index);
949 let reference = &mut references.refs[our_ref_index];
950 reference.end = input.position().byte_index() - input_start.byte_index()
951 + missing_closing_characters.len();
952 reference.fallback = fallback;
953 match substitution_kind {
954 SubstitutionFunctionKind::Var => references.any_var = true,
955 SubstitutionFunctionKind::Env => references.any_env = true,
956 SubstitutionFunctionKind::Attr => references.any_attr = true,
957 };
958 } else {
959 nested!(")");
960 }
961 },
962 Token::ParenthesisBlock => {
963 nested!(")");
964 },
965 Token::CurlyBracketBlock => {
966 nested!("}");
967 },
968 Token::SquareBracketBlock => {
969 nested!("]");
970 },
971 Token::QuotedString(_) => {
972 let token_slice = input.slice_from(token_start);
973 let quote = &token_slice[..1];
974 debug_assert!(matches!(quote, "\"" | "'"));
975 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
976 missing_closing_characters.push_str(quote)
977 }
978 },
979 Token::Ident(ref value)
980 | Token::AtKeyword(ref value)
981 | Token::Hash(ref value)
982 | Token::IDHash(ref value)
983 | Token::UnquotedUrl(ref value)
984 | Token::Dimension {
985 unit: ref value, ..
986 } => {
987 references
988 .non_custom_references
989 .insert(NonCustomReferences::from_unit(value));
990 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
991 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
992 missing_closing_characters.push_str("�")
997 }
998 if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
999 missing_closing_characters.push_str(")");
1000 }
1001 },
1002 _ => {},
1003 };
1004 }
1005 Ok((first_token_type, last_token_type))
1006}
1007
1008fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1011 input
1012 .try_parse(|input| {
1013 Ok(match input.next()? {
1014 Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1015 AttributeType::Type(input.parse_nested_block(Descriptor::from_css_parser)?)
1016 },
1017 Token::Ident(ref ident) => {
1018 if ident.eq_ignore_ascii_case("raw-string") {
1019 AttributeType::RawString
1020 } else {
1021 let unit = AttrUnit::from_ident(ident).map_err(|_| {
1022 input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
1023 })?;
1024 AttributeType::Unit(unit)
1025 }
1026 },
1027 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1028 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1029 })
1030 })
1031 .unwrap_or(AttributeType::None)
1032}
1033
1034pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
1036 seen: PrecomputedHashSet<&'a Name>,
1037 may_have_cycles: bool,
1038 has_color_scheme: bool,
1039 custom_properties: ComputedCustomProperties,
1040 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
1041 stylist: &'a Stylist,
1042 computed_context: &'a mut computed::Context<'b>,
1043 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
1044}
1045
1046fn find_non_custom_references(
1047 registration: &PropertyRegistrationData,
1048 value: &VariableValue,
1049 may_have_color_scheme: bool,
1050 is_root_element: bool,
1051 include_universal: bool,
1052) -> Option<NonCustomReferences> {
1053 let dependent_types = registration.syntax.dependent_types();
1054 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
1055 || (include_universal && registration.syntax.is_universal());
1056 if may_reference_length {
1057 let value_dependencies = value.references.non_custom_references(is_root_element);
1058 if !value_dependencies.is_empty() {
1059 return Some(value_dependencies);
1060 }
1061 }
1062 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
1063 return Some(NonCustomReferences::empty());
1067 }
1068 None
1069}
1070
1071impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
1072 pub fn new_with_properties(
1076 stylist: &'a Stylist,
1077 custom_properties: ComputedCustomProperties,
1078 computed_context: &'a mut computed::Context<'b>,
1079 ) -> Self {
1080 Self {
1081 seen: PrecomputedHashSet::default(),
1082 reverted: Default::default(),
1083 may_have_cycles: false,
1084 has_color_scheme: false,
1085 custom_properties,
1086 stylist,
1087 computed_context,
1088 references_from_non_custom_properties: NonCustomReferenceMap::default(),
1089 }
1090 }
1091
1092 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1094 let is_root_element = context.is_root_element();
1095
1096 let inherited = context.inherited_custom_properties();
1097 let initial_values = stylist.get_custom_property_initial_values();
1098 let properties = ComputedCustomProperties {
1099 inherited: if is_root_element {
1100 debug_assert!(inherited.is_empty());
1101 initial_values.inherited.clone()
1102 } else {
1103 inherited.inherited.clone()
1104 },
1105 non_inherited: initial_values.non_inherited.clone(),
1106 };
1107
1108 context
1111 .style()
1112 .add_flags(stylist.get_custom_property_initial_values_flags());
1113 Self::new_with_properties(stylist, properties, context)
1114 }
1115
1116 pub fn cascade(
1118 &mut self,
1119 declaration: &'a CustomDeclaration,
1120 priority: CascadePriority,
1121 attribute_tracker: &mut AttributeTracker,
1122 ) {
1123 let CustomDeclaration {
1124 ref name,
1125 ref value,
1126 } = *declaration;
1127
1128 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
1129 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
1130 return;
1131 }
1132 }
1133
1134 let was_already_present = !self.seen.insert(name);
1135 if was_already_present {
1136 return;
1137 }
1138
1139 if !self.value_may_affect_style(name, value) {
1140 return;
1141 }
1142
1143 let map = &mut self.custom_properties;
1144 let registration = self.stylist.get_custom_property_registration(&name);
1145 match value {
1146 CustomDeclarationValue::Unparsed(unparsed_value) => {
1147 let may_have_color_scheme = true;
1152 let has_dependency = unparsed_value.references.any_var
1155 || unparsed_value.references.any_attr
1156 || find_non_custom_references(
1157 registration,
1158 unparsed_value,
1159 may_have_color_scheme,
1160 self.computed_context.is_root_element(),
1161 false,
1162 )
1163 .is_some();
1164 if !has_dependency {
1168 return substitute_references_if_needed_and_apply(
1169 name,
1170 unparsed_value,
1171 map,
1172 self.stylist,
1173 self.computed_context,
1174 attribute_tracker,
1175 );
1176 }
1177 self.may_have_cycles = true;
1178 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1179 map.insert(registration, name, value);
1180 },
1181 CustomDeclarationValue::Parsed(parsed_value) => {
1182 let value = parsed_value.to_computed_value(&self.computed_context);
1183 map.insert(registration, name, value);
1184 },
1185 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
1186 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
1187 let origin_revert = matches!(keyword, CSSWideKeyword::Revert);
1188 self.seen.remove(name);
1189 self.reverted.insert(name, (priority, origin_revert));
1190 },
1191 CSSWideKeyword::Initial => {
1192 debug_assert!(registration.inherits(), "Should've been handled earlier");
1194 remove_and_insert_initial_value(name, registration, map);
1195 },
1196 CSSWideKeyword::Inherit => {
1197 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1199 if let Some(inherited_value) = self
1200 .computed_context
1201 .inherited_custom_properties()
1202 .non_inherited
1203 .get(name)
1204 {
1205 map.insert(registration, name, inherited_value.clone());
1206 }
1207 },
1208 CSSWideKeyword::Unset => unreachable!(),
1210 },
1211 }
1212 }
1213
1214 #[inline]
1216 pub fn might_have_non_custom_dependency(id: LonghandId, decl: &PropertyDeclaration) -> bool {
1217 if id == LonghandId::ColorScheme {
1218 return true;
1219 }
1220 if matches!(id, LonghandId::LineHeight | LonghandId::FontSize) {
1221 return matches!(decl, PropertyDeclaration::WithVariables(..));
1222 }
1223 false
1224 }
1225
1226 pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) {
1229 debug_assert!(Self::might_have_non_custom_dependency(id, decl));
1230 if id == LonghandId::ColorScheme {
1231 self.has_color_scheme = true;
1233 return;
1234 }
1235
1236 let refs = match decl {
1237 PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references,
1238 _ => return,
1239 };
1240
1241 if !refs.any_var && !refs.any_attr {
1242 return;
1243 }
1244
1245 let references = match id {
1249 LonghandId::FontSize => {
1250 if self.computed_context.is_root_element() {
1251 NonCustomReferences::ROOT_FONT_UNITS
1252 } else {
1253 NonCustomReferences::FONT_UNITS
1254 }
1255 },
1256 LonghandId::LineHeight => {
1257 if self.computed_context.is_root_element() {
1258 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1259 } else {
1260 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1261 }
1262 },
1263 _ => return,
1264 };
1265
1266 let variables: Vec<Atom> = refs
1267 .refs
1268 .iter()
1269 .filter_map(|reference| {
1270 if reference.substitution_kind != SubstitutionFunctionKind::Var {
1271 return None;
1272 }
1273 let registration = self
1274 .stylist
1275 .get_custom_property_registration(&reference.name);
1276 if !registration
1277 .syntax
1278 .dependent_types()
1279 .intersects(DependentDataTypes::LENGTH)
1280 {
1281 return None;
1282 }
1283 Some(reference.name.clone())
1284 })
1285 .collect();
1286 references.for_each(|idx| {
1287 let entry = &mut self.references_from_non_custom_properties[idx];
1288 let was_none = entry.is_none();
1289 let v = entry.get_or_insert_with(|| variables.clone());
1290 if was_none {
1291 return;
1292 }
1293 v.extend(variables.iter().cloned());
1294 });
1295 }
1296
1297 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1298 let registration = self.stylist.get_custom_property_registration(&name);
1299 match *value {
1300 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1301 if registration.inherits() {
1305 return false;
1306 }
1307 },
1308 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1309 if !registration.inherits() {
1312 return false;
1313 }
1314 },
1315 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1316 return false;
1320 },
1321 _ => {},
1322 }
1323
1324 let existing_value = self.custom_properties.get(registration, &name);
1325 let existing_value = match existing_value {
1326 None => {
1327 if matches!(
1328 value,
1329 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1330 ) {
1331 debug_assert!(registration.inherits(), "Should've been handled earlier");
1332 if registration.initial_value.is_none() {
1336 return false;
1337 }
1338 }
1339 return true;
1340 },
1341 Some(v) => v,
1342 };
1343 let computed_value = match value {
1344 CustomDeclarationValue::Unparsed(value) => {
1345 if let Some(existing_value) = existing_value.as_universal() {
1348 return existing_value != value;
1349 }
1350 if !registration.syntax.is_universal() {
1351 compute_value(
1352 &value.css,
1353 &value.url_data,
1354 registration,
1355 self.computed_context,
1356 )
1357 .ok()
1358 } else {
1359 None
1360 }
1361 },
1362 CustomDeclarationValue::Parsed(value) => {
1363 Some(value.to_computed_value(&self.computed_context))
1364 },
1365 CustomDeclarationValue::CSSWideKeyword(kw) => {
1366 match kw {
1367 CSSWideKeyword::Inherit => {
1368 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1369 if self
1373 .computed_context
1374 .inherited_custom_properties()
1375 .non_inherited
1376 .get(name)
1377 .is_none()
1378 {
1379 return false;
1380 }
1381 },
1382 CSSWideKeyword::Initial => {
1383 debug_assert!(registration.inherits(), "Should've been handled earlier");
1384 if let Some(initial_value) = self
1387 .stylist
1388 .get_custom_property_initial_values()
1389 .get(registration, name)
1390 {
1391 return existing_value != initial_value;
1392 }
1393 },
1394 CSSWideKeyword::Unset => {
1395 debug_assert!(false, "Should've been handled earlier");
1396 },
1397 CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => {},
1398 }
1399 None
1400 },
1401 };
1402
1403 if let Some(value) = computed_value {
1404 return existing_value.v != value.v;
1405 }
1406
1407 true
1408 }
1409
1410 pub fn build(
1426 mut self,
1427 defer: DeferFontRelativeCustomPropertyResolution,
1428 attribute_tracker: &mut AttributeTracker,
1429 ) -> Option<CustomPropertiesMap> {
1430 let mut deferred_custom_properties = None;
1431 if self.may_have_cycles {
1432 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1433 deferred_custom_properties = Some(CustomPropertiesMap::default());
1434 }
1435 let mut invalid_non_custom_properties = LonghandIdSet::default();
1436 substitute_all(
1437 &mut self.custom_properties,
1438 deferred_custom_properties.as_mut(),
1439 &mut invalid_non_custom_properties,
1440 self.has_color_scheme,
1441 &self.seen,
1442 &self.references_from_non_custom_properties,
1443 self.stylist,
1444 self.computed_context,
1445 attribute_tracker,
1446 );
1447 self.computed_context.builder.invalid_non_custom_properties =
1448 invalid_non_custom_properties;
1449 }
1450
1451 self.custom_properties.shrink_to_fit();
1452
1453 let initial_values = self.stylist.get_custom_property_initial_values();
1458 self.computed_context.builder.custom_properties = ComputedCustomProperties {
1459 inherited: if self
1460 .computed_context
1461 .inherited_custom_properties()
1462 .inherited
1463 == self.custom_properties.inherited
1464 {
1465 self.computed_context
1466 .inherited_custom_properties()
1467 .inherited
1468 .clone()
1469 } else {
1470 self.custom_properties.inherited
1471 },
1472 non_inherited: if initial_values.non_inherited == self.custom_properties.non_inherited {
1473 initial_values.non_inherited.clone()
1474 } else {
1475 self.custom_properties.non_inherited
1476 },
1477 };
1478
1479 deferred_custom_properties
1480 }
1481
1482 pub fn build_deferred(
1485 deferred: CustomPropertiesMap,
1486 stylist: &Stylist,
1487 computed_context: &mut computed::Context,
1488 attribute_tracker: &mut AttributeTracker,
1489 ) {
1490 if deferred.is_empty() {
1491 return;
1492 }
1493 let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
1494 for (k, v) in deferred.iter() {
1497 let Some(v) = v else { continue };
1498 let Some(v) = v.as_universal() else {
1499 unreachable!("Computing should have been deferred!")
1500 };
1501 substitute_references_if_needed_and_apply(
1502 k,
1503 v,
1504 &mut custom_properties,
1505 stylist,
1506 computed_context,
1507 attribute_tracker,
1508 );
1509 }
1510 computed_context.builder.custom_properties = custom_properties;
1511 }
1512}
1513
1514fn substitute_all(
1519 custom_properties_map: &mut ComputedCustomProperties,
1520 mut deferred_properties_map: Option<&mut CustomPropertiesMap>,
1521 invalid_non_custom_properties: &mut LonghandIdSet,
1522 has_color_scheme: bool,
1523 seen: &PrecomputedHashSet<&Name>,
1524 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1525 stylist: &Stylist,
1526 computed_context: &computed::Context,
1527 attr_provider: &mut AttributeTracker,
1528) {
1529 #[derive(Clone, Eq, PartialEq, Debug)]
1536 enum VarType {
1537 Custom(Name),
1538 NonCustom(SingleNonCustomReference),
1539 }
1540
1541 #[derive(Debug)]
1543 struct VarInfo {
1544 var: Option<VarType>,
1549 lowlink: usize,
1554 }
1555 struct Context<'a, 'b: 'a> {
1558 count: usize,
1561 index_map: PrecomputedHashMap<Name, usize>,
1563 non_custom_index_map: NonCustomReferenceMap<usize>,
1565 var_info: SmallVec<[VarInfo; 5]>,
1567 stack: SmallVec<[usize; 5]>,
1570 non_custom_references: NonCustomReferences,
1572 has_color_scheme: bool,
1574 contains_computed_custom_property: bool,
1577 map: &'a mut ComputedCustomProperties,
1578 stylist: &'a Stylist,
1581 computed_context: &'a computed::Context<'b>,
1584 invalid_non_custom_properties: &'a mut LonghandIdSet,
1586 deferred_properties: Option<&'a mut CustomPropertiesMap>,
1590 }
1591
1592 fn traverse<'a, 'b>(
1611 var: VarType,
1612 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1613 context: &mut Context<'a, 'b>,
1614 attribute_tracker: &mut AttributeTracker,
1615 ) -> Option<usize> {
1616 let value = match var {
1618 VarType::Custom(ref name) => {
1619 let registration = context.stylist.get_custom_property_registration(name);
1620 let value = context.map.get(registration, name)?.as_universal()?;
1621 let is_root = context.computed_context.is_root_element();
1622 let non_custom_refs = find_non_custom_references(
1625 registration,
1626 value,
1627 context.has_color_scheme,
1628 is_root,
1629 true,
1630 );
1631 context.non_custom_references |= non_custom_refs.unwrap_or_default();
1632 let has_dependency = value.references.any_var
1633 || value.references.any_attr
1634 || non_custom_refs.is_some();
1635 if !has_dependency {
1637 debug_assert!(!value.references.any_env, "Should've been handled earlier");
1638 if !registration.syntax.is_universal() {
1639 debug_assert!(
1644 registration
1645 .syntax
1646 .dependent_types()
1647 .intersects(DependentDataTypes::COLOR),
1648 "How did an unresolved value get here otherwise?",
1649 );
1650 let value = value.clone();
1651 substitute_references_if_needed_and_apply(
1652 name,
1653 &value,
1654 &mut context.map,
1655 context.stylist,
1656 context.computed_context,
1657 attribute_tracker,
1658 );
1659 }
1660 return None;
1661 }
1662
1663 match context.index_map.entry(name.clone()) {
1665 Entry::Occupied(entry) => {
1666 return Some(*entry.get());
1667 },
1668 Entry::Vacant(entry) => {
1669 entry.insert(context.count);
1670 },
1671 }
1672 context.contains_computed_custom_property |= !registration.syntax.is_universal();
1673
1674 Some(value.clone())
1677 },
1678 VarType::NonCustom(ref non_custom) => {
1679 let entry = &mut context.non_custom_index_map[*non_custom];
1680 if let Some(v) = entry {
1681 return Some(*v);
1682 }
1683 *entry = Some(context.count);
1684 None
1685 },
1686 };
1687
1688 let index = context.count;
1690 context.count += 1;
1691 debug_assert_eq!(index, context.var_info.len());
1692 context.var_info.push(VarInfo {
1693 var: Some(var.clone()),
1694 lowlink: index,
1695 });
1696 context.stack.push(index);
1697
1698 let mut self_ref = false;
1699 let mut lowlink = index;
1700 let mut visit_link =
1701 |var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| {
1702 let next_index =
1703 match traverse(var, non_custom_references, context, attribute_tracker) {
1704 Some(index) => index,
1705 None => {
1708 return;
1709 },
1710 };
1711 let next_info = &context.var_info[next_index];
1712 if next_index > index {
1713 *lowlink = cmp::min(*lowlink, next_info.lowlink);
1717 } else if next_index == index {
1718 *self_ref = true;
1719 } else if next_info.var.is_some() {
1720 *lowlink = cmp::min(*lowlink, next_index);
1723 }
1724 };
1725 if let Some(ref v) = value.as_ref() {
1726 debug_assert!(
1727 matches!(var, VarType::Custom(_)),
1728 "Non-custom property has references?"
1729 );
1730
1731 for next in &v.references.refs {
1734 if next.substitution_kind != SubstitutionFunctionKind::Var {
1735 continue;
1736 }
1737 visit_link(
1738 VarType::Custom(next.name.clone()),
1739 context,
1740 &mut lowlink,
1741 &mut self_ref,
1742 );
1743 }
1744
1745 v.references.non_custom_references.for_each(|r| {
1747 visit_link(VarType::NonCustom(r), context, &mut lowlink, &mut self_ref);
1748 });
1749 } else if let VarType::NonCustom(non_custom) = var {
1750 let entry = &non_custom_references[non_custom];
1751 if let Some(deps) = entry.as_ref() {
1752 for d in deps {
1753 visit_link(
1755 VarType::Custom(d.clone()),
1756 context,
1757 &mut lowlink,
1758 &mut self_ref,
1759 );
1760 }
1761 }
1762 }
1763
1764 context.var_info[index].lowlink = lowlink;
1765 if lowlink != index {
1766 return Some(index);
1774 }
1775
1776 let mut in_loop = self_ref;
1778 let name;
1779
1780 let handle_variable_in_loop = |name: &Name, context: &mut Context<'a, 'b>| {
1781 if context.contains_computed_custom_property {
1782 if context.non_custom_references.intersects(
1785 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
1786 ) {
1787 context
1788 .invalid_non_custom_properties
1789 .insert(LonghandId::FontSize);
1790 }
1791 if context
1792 .non_custom_references
1793 .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS)
1794 {
1795 context
1796 .invalid_non_custom_properties
1797 .insert(LonghandId::LineHeight);
1798 }
1799 }
1800 handle_invalid_at_computed_value_time(name, context.map, context.computed_context);
1802 };
1803 loop {
1804 let var_index = context
1805 .stack
1806 .pop()
1807 .expect("The current variable should still be in stack");
1808 let var_info = &mut context.var_info[var_index];
1809 let var_name = var_info
1813 .var
1814 .take()
1815 .expect("Variable should not be poped from stack twice");
1816 if var_index == index {
1817 name = match var_name {
1818 VarType::Custom(name) => name,
1819 VarType::NonCustom(..) => return None,
1823 };
1824 break;
1825 }
1826 if let VarType::Custom(name) = var_name {
1827 handle_variable_in_loop(&name, context);
1831 }
1832 in_loop = true;
1833 }
1834 if in_loop {
1839 handle_variable_in_loop(&name, context);
1840 context.non_custom_references = NonCustomReferences::default();
1841 return None;
1842 }
1843
1844 if let Some(ref v) = value {
1845 let registration = context.stylist.get_custom_property_registration(&name);
1846
1847 let mut defer = false;
1848 if let Some(ref mut deferred) = context.deferred_properties {
1849 defer = find_non_custom_references(
1852 registration,
1853 v,
1854 context.has_color_scheme,
1855 context.computed_context.is_root_element(),
1856 false,
1857 )
1858 .is_some()
1859 || v.references.refs.iter().any(|reference| {
1860 (reference.substitution_kind == SubstitutionFunctionKind::Var
1861 && deferred.get(&reference.name).is_some())
1862 || reference.substitution_kind == SubstitutionFunctionKind::Attr
1863 });
1864
1865 if defer {
1866 let value = ComputedRegisteredValue::universal(Arc::clone(v));
1867 deferred.insert(&name, value);
1868 context.map.remove(registration, &name);
1869 }
1870 }
1871
1872 if !defer && (v.references.any_var || v.references.any_attr) {
1874 substitute_references_if_needed_and_apply(
1875 &name,
1876 v,
1877 &mut context.map,
1878 context.stylist,
1879 context.computed_context,
1880 attribute_tracker,
1881 );
1882 }
1883 }
1884 context.non_custom_references = NonCustomReferences::default();
1885
1886 None
1888 }
1889
1890 for name in seen {
1894 let mut context = Context {
1895 count: 0,
1896 index_map: PrecomputedHashMap::default(),
1897 non_custom_index_map: NonCustomReferenceMap::default(),
1898 stack: SmallVec::new(),
1899 var_info: SmallVec::new(),
1900 map: custom_properties_map,
1901 non_custom_references: NonCustomReferences::default(),
1902 has_color_scheme,
1903 stylist,
1904 computed_context,
1905 invalid_non_custom_properties,
1906 deferred_properties: deferred_properties_map.as_deref_mut(),
1907 contains_computed_custom_property: false,
1908 };
1909 traverse(
1910 VarType::Custom((*name).clone()),
1911 references_from_non_custom_properties,
1912 &mut context,
1913 attr_provider,
1914 );
1915 }
1916}
1917
1918fn handle_invalid_at_computed_value_time(
1920 name: &Name,
1921 custom_properties: &mut ComputedCustomProperties,
1922 computed_context: &computed::Context,
1923) {
1924 let stylist = computed_context.style().stylist.unwrap();
1925 let registration = stylist.get_custom_property_registration(&name);
1926 if !registration.syntax.is_universal() {
1927 if registration.inherits() && !computed_context.is_root_element() {
1930 let inherited = computed_context.inherited_custom_properties();
1931 if let Some(value) = inherited.get(registration, name) {
1932 custom_properties.insert(registration, name, value.clone());
1933 return;
1934 }
1935 } else if let Some(ref initial_value) = registration.initial_value {
1936 if let Ok(initial_value) = compute_value(
1937 &initial_value.css,
1938 &initial_value.url_data,
1939 registration,
1940 computed_context,
1941 ) {
1942 custom_properties.insert(registration, name, initial_value);
1943 return;
1944 }
1945 }
1946 }
1947 custom_properties.remove(registration, name);
1948}
1949
1950fn substitute_references_if_needed_and_apply(
1952 name: &Name,
1953 value: &Arc<VariableValue>,
1954 custom_properties: &mut ComputedCustomProperties,
1955 stylist: &Stylist,
1956 computed_context: &computed::Context,
1957 attribute_tracker: &mut AttributeTracker,
1958) {
1959 let registration = stylist.get_custom_property_registration(&name);
1960 if !value.has_references() && registration.syntax.is_universal() {
1961 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
1963 custom_properties.insert(registration, name, computed_value);
1964 return;
1965 }
1966
1967 let inherited = computed_context.inherited_custom_properties();
1968 let url_data = &value.url_data;
1969 let substitution = match substitute_internal(
1970 value,
1971 custom_properties,
1972 stylist,
1973 computed_context,
1974 attribute_tracker,
1975 ) {
1976 Ok(v) => v,
1977 Err(..) => {
1978 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
1979 return;
1980 },
1981 };
1982
1983 {
1985 let css = &substitution.css;
1986 let css_wide_kw = {
1987 let mut input = ParserInput::new(&css);
1988 let mut input = Parser::new(&mut input);
1989 input.try_parse(CSSWideKeyword::parse)
1990 };
1991
1992 if let Ok(kw) = css_wide_kw {
1993 match (
1997 kw,
1998 registration.inherits(),
1999 computed_context.is_root_element(),
2000 ) {
2001 (CSSWideKeyword::Initial, _, _)
2002 | (CSSWideKeyword::Revert, false, _)
2003 | (CSSWideKeyword::RevertLayer, false, _)
2004 | (CSSWideKeyword::Unset, false, _)
2005 | (CSSWideKeyword::Revert, true, true)
2006 | (CSSWideKeyword::RevertLayer, true, true)
2007 | (CSSWideKeyword::Unset, true, true)
2008 | (CSSWideKeyword::Inherit, _, true) => {
2009 remove_and_insert_initial_value(name, registration, custom_properties);
2010 },
2011 (CSSWideKeyword::Revert, true, false)
2012 | (CSSWideKeyword::RevertLayer, true, false)
2013 | (CSSWideKeyword::Inherit, _, false)
2014 | (CSSWideKeyword::Unset, true, false) => {
2015 match inherited.get(registration, name) {
2016 Some(value) => {
2017 custom_properties.insert(registration, name, value.clone());
2018 },
2019 None => {
2020 custom_properties.remove(registration, name);
2021 },
2022 };
2023 },
2024 }
2025 return;
2026 }
2027 }
2028
2029 let value = match substitution.into_value(url_data, registration, computed_context) {
2030 Ok(v) => v,
2031 Err(()) => {
2032 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
2033 return;
2034 },
2035 };
2036
2037 custom_properties.insert(registration, name, value);
2038}
2039
2040#[derive(Default)]
2041struct Substitution<'a> {
2042 css: Cow<'a, str>,
2043 first_token_type: TokenSerializationType,
2044 last_token_type: TokenSerializationType,
2045}
2046
2047impl<'a> Substitution<'a> {
2048 fn from_value(v: VariableValue) -> Self {
2049 Substitution {
2050 css: v.css.into(),
2051 first_token_type: v.first_token_type,
2052 last_token_type: v.last_token_type,
2053 }
2054 }
2055
2056 fn into_value(
2057 self,
2058 url_data: &UrlExtraData,
2059 registration: &PropertyRegistrationData,
2060 computed_context: &computed::Context,
2061 ) -> Result<ComputedRegisteredValue, ()> {
2062 if registration.syntax.is_universal() {
2063 return Ok(ComputedRegisteredValue::universal(Arc::new(
2064 VariableValue {
2065 css: self.css.into_owned(),
2066 first_token_type: self.first_token_type,
2067 last_token_type: self.last_token_type,
2068 url_data: url_data.clone(),
2069 references: Default::default(),
2070 },
2071 )));
2072 }
2073 compute_value(&self.css, url_data, registration, computed_context)
2074 }
2075
2076 fn new(
2077 css: Cow<'a, str>,
2078 first_token_type: TokenSerializationType,
2079 last_token_type: TokenSerializationType,
2080 ) -> Self {
2081 Self {
2082 css,
2083 first_token_type,
2084 last_token_type,
2085 }
2086 }
2087}
2088
2089fn compute_value(
2090 css: &str,
2091 url_data: &UrlExtraData,
2092 registration: &PropertyRegistrationData,
2093 computed_context: &computed::Context,
2094) -> Result<ComputedRegisteredValue, ()> {
2095 debug_assert!(!registration.syntax.is_universal());
2096
2097 let mut input = ParserInput::new(&css);
2098 let mut input = Parser::new(&mut input);
2099
2100 SpecifiedRegisteredValue::compute(
2101 &mut input,
2102 registration,
2103 None,
2104 url_data,
2105 computed_context,
2106 AllowComputationallyDependent::Yes,
2107 )
2108}
2109
2110fn remove_and_insert_initial_value(
2112 name: &Name,
2113 registration: &PropertyRegistrationData,
2114 custom_properties: &mut ComputedCustomProperties,
2115) {
2116 custom_properties.remove(registration, name);
2117 if let Some(ref initial_value) = registration.initial_value {
2118 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2119 custom_properties.insert(registration, name, value);
2120 }
2121}
2122
2123fn do_substitute_chunk<'a>(
2124 css: &'a str,
2125 start: usize,
2126 end: usize,
2127 first_token_type: TokenSerializationType,
2128 last_token_type: TokenSerializationType,
2129 url_data: &UrlExtraData,
2130 custom_properties: &'a ComputedCustomProperties,
2131 stylist: &Stylist,
2132 computed_context: &computed::Context,
2133 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2134 attribute_tracker: &mut AttributeTracker,
2135) -> Result<Substitution<'a>, ()> {
2136 if start == end {
2137 return Ok(Substitution::default());
2139 }
2140 if references
2142 .peek()
2143 .map_or(true, |reference| reference.end > end)
2144 {
2145 let result = &css[start..end];
2146 return Ok(Substitution::new(
2147 Cow::Borrowed(result),
2148 first_token_type,
2149 last_token_type,
2150 ));
2151 }
2152
2153 let mut substituted = ComputedValue::empty(url_data);
2154 let mut next_token_type = first_token_type;
2155 let mut cur_pos = start;
2156 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2157 if reference.start != cur_pos {
2158 substituted.push(
2159 &css[cur_pos..reference.start],
2160 next_token_type,
2161 reference.prev_token_type,
2162 )?;
2163 }
2164
2165 let substitution = substitute_one_reference(
2166 css,
2167 url_data,
2168 custom_properties,
2169 reference,
2170 stylist,
2171 computed_context,
2172 references,
2173 attribute_tracker,
2174 )?;
2175
2176 if reference.start == start && reference.end == end {
2178 return Ok(substitution);
2179 }
2180
2181 substituted.push(
2182 &substitution.css,
2183 substitution.first_token_type,
2184 substitution.last_token_type,
2185 )?;
2186 next_token_type = reference.next_token_type;
2187 cur_pos = reference.end;
2188 }
2189 if cur_pos != end {
2191 substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
2192 }
2193 Ok(Substitution::from_value(substituted))
2194}
2195
2196fn quoted_css_string(src: &str) -> String {
2197 let mut dest = String::with_capacity(src.len() + 2);
2198 cssparser::serialize_string(src, &mut dest).unwrap();
2199 dest
2200}
2201
2202fn substitute_one_reference<'a>(
2203 css: &'a str,
2204 url_data: &UrlExtraData,
2205 custom_properties: &'a ComputedCustomProperties,
2206 reference: &SubstitutionFunctionReference,
2207 stylist: &Stylist,
2208 computed_context: &computed::Context,
2209 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2210 attribute_tracker: &mut AttributeTracker,
2211) -> Result<Substitution<'a>, ()> {
2212 let simple_subst = |s: &str| {
2213 Some(Substitution::new(
2214 Cow::Owned(quoted_css_string(s)),
2215 TokenSerializationType::Nothing,
2216 TokenSerializationType::Nothing,
2217 ))
2218 };
2219 let substitution: Option<_> = match reference.substitution_kind {
2220 SubstitutionFunctionKind::Var => {
2221 let registration = stylist.get_custom_property_registration(&reference.name);
2222 custom_properties
2223 .get(registration, &reference.name)
2224 .map(|v| Substitution::from_value(v.to_variable_value()))
2225 },
2226 SubstitutionFunctionKind::Env => {
2227 let device = stylist.device();
2228 device
2229 .environment()
2230 .get(&reference.name, device, url_data)
2231 .map(Substitution::from_value)
2232 },
2233 SubstitutionFunctionKind::Attr => {
2235 #[cfg(feature = "gecko")]
2236 let local_name = LocalName::cast(&reference.name);
2237 #[cfg(feature = "servo")]
2238 let local_name = LocalName::from(reference.name.as_ref());
2239 let namespace = match reference.attribute_data.namespace {
2240 ParsedNamespace::Known(ref ns) => Some(ns),
2241 ParsedNamespace::Unknown => None,
2242 };
2243 namespace
2244 .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
2245 .map_or_else(
2246 || {
2247 if reference.fallback.is_none()
2250 && reference.attribute_data.kind == AttributeType::None
2251 {
2252 simple_subst("")
2253 } else {
2254 None
2255 }
2256 },
2257 |attr| {
2258 let mut input = ParserInput::new(&attr);
2259 let mut parser = Parser::new(&mut input);
2260 match &reference.attribute_data.kind {
2261 AttributeType::Unit(unit) => {
2262 let css = {
2263 parser.expect_number().ok()?;
2265 let mut s = attr.clone();
2266 s.push_str(unit.as_ref());
2267 s
2268 };
2269 let serialization = match unit {
2270 AttrUnit::Number => TokenSerializationType::Number,
2271 AttrUnit::Percentage => TokenSerializationType::Percentage,
2272 _ => TokenSerializationType::Dimension,
2273 };
2274 let value =
2275 ComputedValue::new(css, url_data, serialization, serialization);
2276 Some(Substitution::from_value(value))
2277 },
2278 AttributeType::Type(syntax) => {
2279 let value = SpecifiedRegisteredValue::parse(
2280 &mut parser,
2281 &syntax,
2282 url_data,
2283 None,
2284 AllowComputationallyDependent::Yes,
2285 )
2286 .ok()?;
2287 Some(Substitution::from_value(value.to_variable_value()))
2288 },
2289 AttributeType::RawString | AttributeType::None => simple_subst(&attr),
2290 }
2291 },
2292 )
2293 },
2294 };
2295
2296 if let Some(s) = substitution {
2297 while references
2299 .next_if(|next_ref| next_ref.end <= reference.end)
2300 .is_some()
2301 {}
2302 return Ok(s);
2303 }
2304
2305 let Some(ref fallback) = reference.fallback else {
2306 return Err(());
2307 };
2308
2309 do_substitute_chunk(
2310 css,
2311 fallback.start.get(),
2312 reference.end - 1, fallback.first_token_type,
2314 fallback.last_token_type,
2315 url_data,
2316 custom_properties,
2317 stylist,
2318 computed_context,
2319 references,
2320 attribute_tracker,
2321 )
2322}
2323
2324fn substitute_internal<'a>(
2326 variable_value: &'a VariableValue,
2327 custom_properties: &'a ComputedCustomProperties,
2328 stylist: &Stylist,
2329 computed_context: &computed::Context,
2330 attribute_tracker: &mut AttributeTracker,
2331) -> Result<Substitution<'a>, ()> {
2332 let mut refs = variable_value.references.refs.iter().peekable();
2333 do_substitute_chunk(
2334 &variable_value.css,
2335 0,
2336 variable_value.css.len(),
2337 variable_value.first_token_type,
2338 variable_value.last_token_type,
2339 &variable_value.url_data,
2340 custom_properties,
2341 stylist,
2342 computed_context,
2343 &mut refs,
2344 attribute_tracker,
2345 )
2346}
2347
2348pub fn substitute<'a>(
2350 variable_value: &'a VariableValue,
2351 custom_properties: &'a ComputedCustomProperties,
2352 stylist: &Stylist,
2353 computed_context: &computed::Context,
2354 attribute_tracker: &mut AttributeTracker,
2355) -> Result<Cow<'a, str>, ()> {
2356 debug_assert!(variable_value.has_references());
2357 let v = substitute_internal(
2358 variable_value,
2359 custom_properties,
2360 stylist,
2361 computed_context,
2362 attribute_tracker,
2363 )?;
2364 Ok(v.css)
2365}