1use crate::applicable_declarations::CascadePriority;
10use crate::custom_properties_map::CustomPropertiesMap;
11use crate::media_queries::Device;
12use crate::properties::{
13 CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
14 PropertyDeclaration,
15};
16use crate::properties_and_values::{
17 registry::PropertyRegistrationData,
18 syntax::data_type::DependentDataTypes,
19 value::{
20 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
21 SpecifiedValue as SpecifiedRegisteredValue,
22 },
23};
24use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
25use crate::stylesheets::UrlExtraData;
26use crate::stylist::Stylist;
27use crate::values::computed::{self, ToComputedValue};
28use crate::values::specified::FontRelativeLength;
29use crate::Atom;
30use cssparser::{
31 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
32};
33use selectors::parser::SelectorParseErrorKind;
34use servo_arc::Arc;
35use smallvec::SmallVec;
36use std::borrow::Cow;
37use std::collections::hash_map::Entry;
38use std::fmt::{self, Write};
39use std::ops::{Index, IndexMut};
40use std::{cmp, num};
41use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
42
43#[derive(Debug, MallocSizeOf)]
48pub struct CssEnvironment;
49
50type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
51
52struct EnvironmentVariable {
53 name: Atom,
54 evaluator: EnvironmentEvaluator,
55}
56
57macro_rules! make_variable {
58 ($name:expr, $evaluator:expr) => {{
59 EnvironmentVariable {
60 name: $name,
61 evaluator: $evaluator,
62 }
63 }};
64}
65
66fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
67 VariableValue::pixels(device.safe_area_insets().top, url_data)
68}
69
70fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
71 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
72}
73
74fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
75 VariableValue::pixels(device.safe_area_insets().left, url_data)
76}
77
78fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
79 VariableValue::pixels(device.safe_area_insets().right, url_data)
80}
81
82#[cfg(feature = "gecko")]
83fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
84 use crate::queries::values::PrefersColorScheme;
85 let prefers_color_scheme = unsafe {
86 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
87 device.document(),
88 true,
89 )
90 };
91 VariableValue::ident(
92 match prefers_color_scheme {
93 PrefersColorScheme::Light => "light",
94 PrefersColorScheme::Dark => "dark",
95 },
96 url_data,
97 )
98}
99
100#[cfg(feature = "servo")]
101fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
102 VariableValue::ident("light", url_data)
104}
105
106fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
107 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
108}
109
110fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
111 VariableValue::pixels(
112 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
113 url_data,
114 )
115}
116
117static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
118 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
119 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
120 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
121 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
122];
123
124#[cfg(feature = "gecko")]
125macro_rules! lnf_int {
126 ($id:ident) => {
127 unsafe {
128 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
129 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
130 )
131 }
132 };
133}
134
135#[cfg(feature = "servo")]
136macro_rules! lnf_int {
137 ($id:ident) => {
138 0
140 };
141}
142
143macro_rules! lnf_int_variable {
144 ($atom:expr, $id:ident, $ctor:ident) => {{
145 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
146 VariableValue::$ctor(lnf_int!($id), url_data)
147 }
148 make_variable!($atom, __eval)
149 }};
150}
151
152fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
153 let int_pixels = lnf_int!(TitlebarRadius);
154 let unzoomed_scale =
155 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
156 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
157}
158
159static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
160 make_variable!(
161 atom!("-moz-gtk-csd-titlebar-radius"),
162 eval_gtk_csd_titlebar_radius
163 ),
164 lnf_int_variable!(
165 atom!("-moz-gtk-csd-tooltip-radius"),
166 TooltipRadius,
167 int_pixels
168 ),
169 lnf_int_variable!(
170 atom!("-moz-gtk-csd-close-button-position"),
171 GTKCSDCloseButtonPosition,
172 integer
173 ),
174 lnf_int_variable!(
175 atom!("-moz-gtk-csd-minimize-button-position"),
176 GTKCSDMinimizeButtonPosition,
177 integer
178 ),
179 lnf_int_variable!(
180 atom!("-moz-gtk-csd-maximize-button-position"),
181 GTKCSDMaximizeButtonPosition,
182 integer
183 ),
184 lnf_int_variable!(
185 atom!("-moz-overlay-scrollbar-fade-duration"),
186 ScrollbarFadeDuration,
187 int_ms
188 ),
189 make_variable!(
190 atom!("-moz-content-preferred-color-scheme"),
191 get_content_preferred_color_scheme
192 ),
193 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
194 make_variable!(atom!("hairline"), get_hairline),
195];
196
197impl CssEnvironment {
198 #[inline]
199 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
200 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
201 return Some((var.evaluator)(device, url_data));
202 }
203 if !url_data.chrome_rules_enabled() {
204 return None;
205 }
206 let var = CHROME_ENVIRONMENT_VARIABLES
207 .iter()
208 .find(|var| var.name == *name)?;
209 Some((var.evaluator)(device, url_data))
210 }
211}
212
213pub type Name = Atom;
217
218pub fn parse_name(s: &str) -> Result<&str, ()> {
222 if s.starts_with("--") && s.len() > 2 {
223 Ok(&s[2..])
224 } else {
225 Err(())
226 }
227}
228
229#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
234pub struct VariableValue {
235 pub css: String,
237
238 pub url_data: UrlExtraData,
240
241 first_token_type: TokenSerializationType,
242 last_token_type: TokenSerializationType,
243
244 references: References,
246}
247
248trivial_to_computed_value!(VariableValue);
249
250pub fn compute_variable_value(
252 value: &Arc<VariableValue>,
253 registration: &PropertyRegistrationData,
254 computed_context: &computed::Context,
255) -> Option<ComputedRegisteredValue> {
256 if registration.syntax.is_universal() {
257 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
258 }
259 compute_value(&value.css, &value.url_data, registration, computed_context).ok()
260}
261
262impl PartialEq for VariableValue {
264 fn eq(&self, other: &Self) -> bool {
265 self.css == other.css
266 }
267}
268
269impl Eq for VariableValue {}
270
271impl ToCss for SpecifiedValue {
272 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
273 where
274 W: Write,
275 {
276 dest.write_str(&self.css)
277 }
278}
279
280#[repr(C)]
283#[derive(Clone, Debug, Default, PartialEq)]
284pub struct ComputedCustomProperties {
285 pub inherited: CustomPropertiesMap,
288 pub non_inherited: CustomPropertiesMap,
290}
291
292impl ComputedCustomProperties {
293 pub fn is_empty(&self) -> bool {
295 self.inherited.is_empty() && self.non_inherited.is_empty()
296 }
297
298 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
300 self.inherited
303 .get_index(index)
304 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
305 }
306
307 fn insert(
310 &mut self,
311 registration: &PropertyRegistrationData,
312 name: &Name,
313 value: ComputedRegisteredValue,
314 ) {
315 self.map_mut(registration).insert(name, value)
316 }
317
318 fn remove(&mut self, registration: &PropertyRegistrationData, name: &Name) {
321 self.map_mut(registration).remove(name);
322 }
323
324 fn shrink_to_fit(&mut self) {
326 self.inherited.shrink_to_fit();
327 self.non_inherited.shrink_to_fit();
328 }
329
330 fn map_mut(&mut self, registration: &PropertyRegistrationData) -> &mut CustomPropertiesMap {
331 if registration.inherits() {
332 &mut self.inherited
333 } else {
334 &mut self.non_inherited
335 }
336 }
337
338 pub fn get(
340 &self,
341 registration: &PropertyRegistrationData,
342 name: &Name,
343 ) -> Option<&ComputedRegisteredValue> {
344 if registration.inherits() {
345 self.inherited.get(name)
346 } else {
347 self.non_inherited.get(name)
348 }
349 }
350}
351
352pub type SpecifiedValue = VariableValue;
355pub type ComputedValue = VariableValue;
358
359#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
361struct NonCustomReferences(u8);
362
363bitflags! {
364 impl NonCustomReferences: u8 {
365 const FONT_UNITS = 1 << 0;
367 const ROOT_FONT_UNITS = 1 << 1;
369 const LH_UNITS = 1 << 2;
371 const ROOT_LH_UNITS = 1 << 3;
373 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
375 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
377 }
378}
379
380impl NonCustomReferences {
381 fn for_each<F>(&self, mut f: F)
382 where
383 F: FnMut(SingleNonCustomReference),
384 {
385 for (_, r) in self.iter_names() {
386 let single = match r {
387 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
388 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
389 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
390 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
391 _ => unreachable!("Unexpected single bit value"),
392 };
393 f(single);
394 }
395 }
396
397 fn from_unit(value: &CowRcStr) -> Self {
398 if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
403 return Self::FONT_UNITS | Self::LH_UNITS;
404 }
405 if value.eq_ignore_ascii_case(FontRelativeLength::EM)
406 || value.eq_ignore_ascii_case(FontRelativeLength::EX)
407 || value.eq_ignore_ascii_case(FontRelativeLength::CAP)
408 || value.eq_ignore_ascii_case(FontRelativeLength::CH)
409 || value.eq_ignore_ascii_case(FontRelativeLength::IC)
410 {
411 return Self::FONT_UNITS;
412 }
413 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
414 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
415 }
416 if value.eq_ignore_ascii_case(FontRelativeLength::REM) {
417 return Self::ROOT_FONT_UNITS;
418 }
419 Self::empty()
420 }
421}
422
423#[derive(Clone, Copy, Debug, Eq, PartialEq)]
424enum SingleNonCustomReference {
425 FontUnits = 0,
426 RootFontUnits,
427 LhUnits,
428 RootLhUnits,
429}
430
431struct NonCustomReferenceMap<T>([Option<T>; 4]);
432
433impl<T> Default for NonCustomReferenceMap<T> {
434 fn default() -> Self {
435 NonCustomReferenceMap(Default::default())
436 }
437}
438
439impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
440 type Output = Option<T>;
441
442 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
443 &self.0[reference as usize]
444 }
445}
446
447impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
448 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
449 &mut self.0[reference as usize]
450 }
451}
452
453#[derive(Clone, Copy, PartialEq, Eq)]
455#[allow(missing_docs)]
456pub enum DeferFontRelativeCustomPropertyResolution {
457 Yes,
458 No,
459}
460
461#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
462enum SubstitutionFunctionKind {
463 Var,
464 Env,
465 Attr,
466}
467
468#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
469struct VariableFallback {
470 start: num::NonZeroUsize,
471 first_token_type: TokenSerializationType,
472 last_token_type: TokenSerializationType,
473}
474
475#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
476struct SubstitutionFunctionReference {
477 name: Name,
478 start: usize,
479 end: usize,
480 fallback: Option<VariableFallback>,
481 prev_token_type: TokenSerializationType,
482 next_token_type: TokenSerializationType,
483 substitution_kind: SubstitutionFunctionKind,
484}
485
486#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
489struct References {
490 refs: Vec<SubstitutionFunctionReference>,
491 non_custom_references: NonCustomReferences,
492 any_env: bool,
493 any_var: bool,
494 any_attr: bool,
495}
496
497impl References {
498 fn has_references(&self) -> bool {
499 !self.refs.is_empty()
500 }
501
502 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
503 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
504 if is_root_element {
505 mask |= NonCustomReferences::ROOT_DEPENDENCIES
506 }
507 self.non_custom_references & mask
508 }
509}
510
511impl VariableValue {
512 fn empty(url_data: &UrlExtraData) -> Self {
513 Self {
514 css: String::new(),
515 last_token_type: Default::default(),
516 first_token_type: Default::default(),
517 url_data: url_data.clone(),
518 references: Default::default(),
519 }
520 }
521
522 pub fn new(
525 css: String,
526 url_data: &UrlExtraData,
527 first_token_type: TokenSerializationType,
528 last_token_type: TokenSerializationType,
529 ) -> Self {
530 Self {
531 css,
532 url_data: url_data.clone(),
533 first_token_type,
534 last_token_type,
535 references: Default::default(),
536 }
537 }
538
539 fn push<'i>(
540 &mut self,
541 css: &str,
542 css_first_token_type: TokenSerializationType,
543 css_last_token_type: TokenSerializationType,
544 ) -> Result<(), ()> {
545 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
553
554 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
555 return Err(());
556 }
557
558 if css.is_empty() {
563 return Ok(());
564 }
565
566 self.first_token_type.set_if_nothing(css_first_token_type);
567 if self
570 .last_token_type
571 .needs_separator_when_before(css_first_token_type)
572 {
573 self.css.push_str("/**/")
574 }
575 self.css.push_str(css);
576 self.last_token_type = css_last_token_type;
577 Ok(())
578 }
579
580 pub fn parse<'i, 't>(
582 input: &mut Parser<'i, 't>,
583 url_data: &UrlExtraData,
584 ) -> Result<Self, ParseError<'i>> {
585 input.skip_whitespace();
586
587 let mut references = References::default();
588 let mut missing_closing_characters = String::new();
589 let start_position = input.position();
590 let (first_token_type, last_token_type) = parse_declaration_value(
591 input,
592 start_position,
593 &mut references,
594 &mut missing_closing_characters,
595 )?;
596 let mut css = input.slice_from(start_position).to_owned();
597 if !missing_closing_characters.is_empty() {
598 if css.ends_with("\\")
600 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
601 {
602 css.pop();
603 }
604 css.push_str(&missing_closing_characters);
605 }
606
607 css.shrink_to_fit();
608 references.refs.shrink_to_fit();
609
610 Ok(Self {
611 css,
612 url_data: url_data.clone(),
613 first_token_type,
614 last_token_type,
615 references,
616 })
617 }
618
619 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
621 Self::from_token(
622 Token::Number {
623 has_sign: false,
624 value: number as f32,
625 int_value: Some(number),
626 },
627 url_data,
628 )
629 }
630
631 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
633 Self::from_token(Token::Ident(ident.into()), url_data)
634 }
635
636 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
638 Self::from_token(
642 Token::Dimension {
643 has_sign: false,
644 value: number,
645 int_value: None,
646 unit: CowRcStr::from("px"),
647 },
648 url_data,
649 )
650 }
651
652 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
654 Self::from_token(
655 Token::Dimension {
656 has_sign: false,
657 value: number as f32,
658 int_value: Some(number),
659 unit: CowRcStr::from("ms"),
660 },
661 url_data,
662 )
663 }
664
665 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
667 Self::from_token(
668 Token::Dimension {
669 has_sign: false,
670 value: number as f32,
671 int_value: Some(number),
672 unit: CowRcStr::from("px"),
673 },
674 url_data,
675 )
676 }
677
678 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
679 let token_type = token.serialization_type();
680 let mut css = token.to_css_string();
681 css.shrink_to_fit();
682
683 VariableValue {
684 css,
685 url_data: url_data.clone(),
686 first_token_type: token_type,
687 last_token_type: token_type,
688 references: Default::default(),
689 }
690 }
691
692 pub fn css_text(&self) -> &str {
694 &self.css
695 }
696
697 pub fn has_references(&self) -> bool {
700 self.references.has_references()
701 }
702}
703
704fn parse_declaration_value<'i, 't>(
706 input: &mut Parser<'i, 't>,
707 input_start: SourcePosition,
708 references: &mut References,
709 missing_closing_characters: &mut String,
710) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
711 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
712 parse_declaration_value_block(input, input_start, references, missing_closing_characters)
713 })
714}
715
716fn parse_declaration_value_block<'i, 't>(
718 input: &mut Parser<'i, 't>,
719 input_start: SourcePosition,
720 references: &mut References,
721 missing_closing_characters: &mut String,
722) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
723 let mut is_first = true;
724 let mut first_token_type = TokenSerializationType::Nothing;
725 let mut last_token_type = TokenSerializationType::Nothing;
726 let mut prev_reference_index: Option<usize> = None;
727 loop {
728 let token_start = input.position();
729 let Ok(token) = input.next_including_whitespace_and_comments() else {
730 break;
731 };
732
733 let prev_token_type = last_token_type;
734 let serialization_type = token.serialization_type();
735 last_token_type = serialization_type;
736 if is_first {
737 first_token_type = last_token_type;
738 is_first = false;
739 }
740
741 macro_rules! nested {
742 () => {
743 input.parse_nested_block(|input| {
744 parse_declaration_value_block(
745 input,
746 input_start,
747 references,
748 missing_closing_characters,
749 )
750 })?
751 };
752 }
753 macro_rules! check_closed {
754 ($closing:expr) => {
755 if !input.slice_from(token_start).ends_with($closing) {
756 missing_closing_characters.push_str($closing)
757 }
758 };
759 }
760 if let Some(index) = prev_reference_index.take() {
761 references.refs[index].next_token_type = serialization_type;
762 }
763 match *token {
764 Token::Comment(_) => {
765 let token_slice = input.slice_from(token_start);
766 if !token_slice.ends_with("*/") {
767 missing_closing_characters.push_str(if token_slice.ends_with('*') {
768 "/"
769 } else {
770 "*/"
771 })
772 }
773 },
774 Token::BadUrl(ref u) => {
775 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
776 return Err(input.new_custom_error(e));
777 },
778 Token::BadString(ref s) => {
779 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
780 return Err(input.new_custom_error(e));
781 },
782 Token::CloseParenthesis => {
783 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
784 return Err(input.new_custom_error(e));
785 },
786 Token::CloseSquareBracket => {
787 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
788 return Err(input.new_custom_error(e));
789 },
790 Token::CloseCurlyBracket => {
791 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
792 return Err(input.new_custom_error(e));
793 },
794 Token::Function(ref name) => {
795 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
796 Some(SubstitutionFunctionKind::Attr) => {
797 if static_prefs::pref!("layout.css.attr.enabled") {
798 Some(SubstitutionFunctionKind::Attr)
799 } else {
800 None
801 }
802 },
803 kind => kind,
804 };
805 if let Some(substitution_kind) = substitution_kind {
806 let our_ref_index = references.refs.len();
807 let fallback = input.parse_nested_block(|input| {
808 let name = input.expect_ident()?;
811 let name =
812 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
813 match parse_name(name.as_ref()) {
814 Ok(name) => name,
815 Err(()) => {
816 let name = name.clone();
817 return Err(input.new_custom_error(
818 SelectorParseErrorKind::UnexpectedIdent(name),
819 ));
820 },
821 }
822 } else {
823 name.as_ref()
824 });
825
826 let start = token_start.byte_index() - input_start.byte_index();
830 references.refs.push(SubstitutionFunctionReference {
831 name,
832 start,
833 end: start,
835 prev_token_type,
836 next_token_type: TokenSerializationType::Nothing,
838 fallback: None,
840 substitution_kind: substitution_kind.clone(),
841 });
842
843 let mut fallback = None;
844 if input.try_parse(|input| input.expect_comma()).is_ok() {
845 input.skip_whitespace();
846 let fallback_start = num::NonZeroUsize::new(
847 input.position().byte_index() - input_start.byte_index(),
848 )
849 .unwrap();
850 let (first, last) = parse_declaration_value(
853 input,
854 input_start,
855 references,
856 missing_closing_characters,
857 )?;
858 fallback = Some(VariableFallback {
859 start: fallback_start,
860 first_token_type: first,
861 last_token_type: last,
862 });
863 } else {
864 let state = input.state();
865 parse_declaration_value_block(
869 input,
870 input_start,
871 references,
872 missing_closing_characters,
873 )?;
874 input.reset(&state);
875 }
876 Ok(fallback)
877 })?;
878 check_closed!(")");
879 prev_reference_index = Some(our_ref_index);
880 let reference = &mut references.refs[our_ref_index];
881 reference.end = input.position().byte_index() - input_start.byte_index()
882 + missing_closing_characters.len();
883 reference.fallback = fallback;
884 match substitution_kind {
885 SubstitutionFunctionKind::Var => references.any_var = true,
886 SubstitutionFunctionKind::Env => references.any_env = true,
887 SubstitutionFunctionKind::Attr => references.any_attr = true,
888 };
889 } else {
890 nested!();
891 check_closed!(")");
892 }
893 },
894 Token::ParenthesisBlock => {
895 nested!();
896 check_closed!(")");
897 },
898 Token::CurlyBracketBlock => {
899 nested!();
900 check_closed!("}");
901 },
902 Token::SquareBracketBlock => {
903 nested!();
904 check_closed!("]");
905 },
906 Token::QuotedString(_) => {
907 let token_slice = input.slice_from(token_start);
908 let quote = &token_slice[..1];
909 debug_assert!(matches!(quote, "\"" | "'"));
910 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
911 missing_closing_characters.push_str(quote)
912 }
913 },
914 Token::Ident(ref value)
915 | Token::AtKeyword(ref value)
916 | Token::Hash(ref value)
917 | Token::IDHash(ref value)
918 | Token::UnquotedUrl(ref value)
919 | Token::Dimension {
920 unit: ref value, ..
921 } => {
922 references
923 .non_custom_references
924 .insert(NonCustomReferences::from_unit(value));
925 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
926 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
927 missing_closing_characters.push_str("�")
932 }
933 if is_unquoted_url {
934 check_closed!(")");
935 }
936 },
937 _ => {},
938 };
939 }
940 Ok((first_token_type, last_token_type))
941}
942
943pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
945 seen: PrecomputedHashSet<&'a Name>,
946 may_have_cycles: bool,
947 has_color_scheme: bool,
948 custom_properties: ComputedCustomProperties,
949 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
950 stylist: &'a Stylist,
951 computed_context: &'a mut computed::Context<'b>,
952 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
953}
954
955fn find_non_custom_references(
956 registration: &PropertyRegistrationData,
957 value: &VariableValue,
958 may_have_color_scheme: bool,
959 is_root_element: bool,
960 include_universal: bool,
961) -> Option<NonCustomReferences> {
962 let dependent_types = registration.syntax.dependent_types();
963 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
964 || (include_universal && registration.syntax.is_universal());
965 if may_reference_length {
966 let value_dependencies = value.references.non_custom_references(is_root_element);
967 if !value_dependencies.is_empty() {
968 return Some(value_dependencies);
969 }
970 }
971 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
972 return Some(NonCustomReferences::empty());
976 }
977 None
978}
979
980impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
981 pub fn new_with_properties(
985 stylist: &'a Stylist,
986 custom_properties: ComputedCustomProperties,
987 computed_context: &'a mut computed::Context<'b>,
988 ) -> Self {
989 Self {
990 seen: PrecomputedHashSet::default(),
991 reverted: Default::default(),
992 may_have_cycles: false,
993 has_color_scheme: false,
994 custom_properties,
995 stylist,
996 computed_context,
997 references_from_non_custom_properties: NonCustomReferenceMap::default(),
998 }
999 }
1000
1001 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1003 let is_root_element = context.is_root_element();
1004
1005 let inherited = context.inherited_custom_properties();
1006 let initial_values = stylist.get_custom_property_initial_values();
1007 let properties = ComputedCustomProperties {
1008 inherited: if is_root_element {
1009 debug_assert!(inherited.is_empty());
1010 initial_values.inherited.clone()
1011 } else {
1012 inherited.inherited.clone()
1013 },
1014 non_inherited: initial_values.non_inherited.clone(),
1015 };
1016
1017 context
1020 .style()
1021 .add_flags(stylist.get_custom_property_initial_values_flags());
1022 Self::new_with_properties(stylist, properties, context)
1023 }
1024
1025 pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) {
1027 let CustomDeclaration {
1028 ref name,
1029 ref value,
1030 } = *declaration;
1031
1032 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
1033 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
1034 return;
1035 }
1036 }
1037
1038 let was_already_present = !self.seen.insert(name);
1039 if was_already_present {
1040 return;
1041 }
1042
1043 if !self.value_may_affect_style(name, value) {
1044 return;
1045 }
1046
1047 let map = &mut self.custom_properties;
1048 let registration = self.stylist.get_custom_property_registration(&name);
1049 match value {
1050 CustomDeclarationValue::Unparsed(unparsed_value) => {
1051 let may_have_color_scheme = true;
1056 let has_dependency = unparsed_value.references.any_var
1059 || unparsed_value.references.any_attr
1060 || find_non_custom_references(
1061 registration,
1062 unparsed_value,
1063 may_have_color_scheme,
1064 self.computed_context.is_root_element(),
1065 false,
1066 )
1067 .is_some();
1068 if !has_dependency {
1072 return substitute_references_if_needed_and_apply(
1073 name,
1074 unparsed_value,
1075 map,
1076 self.stylist,
1077 self.computed_context,
1078 );
1079 }
1080 self.may_have_cycles = true;
1081 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1082 map.insert(registration, name, value);
1083 },
1084 CustomDeclarationValue::Parsed(parsed_value) => {
1085 let value = parsed_value.to_computed_value(&self.computed_context);
1086 map.insert(registration, name, value);
1087 },
1088 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
1089 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
1090 let origin_revert = matches!(keyword, CSSWideKeyword::Revert);
1091 self.seen.remove(name);
1092 self.reverted.insert(name, (priority, origin_revert));
1093 },
1094 CSSWideKeyword::Initial => {
1095 debug_assert!(registration.inherits(), "Should've been handled earlier");
1097 remove_and_insert_initial_value(name, registration, map);
1098 },
1099 CSSWideKeyword::Inherit => {
1100 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1102 if let Some(inherited_value) = self
1103 .computed_context
1104 .inherited_custom_properties()
1105 .non_inherited
1106 .get(name)
1107 {
1108 map.insert(registration, name, inherited_value.clone());
1109 }
1110 },
1111 CSSWideKeyword::Unset => unreachable!(),
1113 },
1114 }
1115 }
1116
1117 #[inline]
1119 pub fn might_have_non_custom_dependency(id: LonghandId, decl: &PropertyDeclaration) -> bool {
1120 if id == LonghandId::ColorScheme {
1121 return true;
1122 }
1123 if matches!(id, LonghandId::LineHeight | LonghandId::FontSize) {
1124 return matches!(decl, PropertyDeclaration::WithVariables(..));
1125 }
1126 false
1127 }
1128
1129 pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) {
1132 debug_assert!(Self::might_have_non_custom_dependency(id, decl));
1133 if id == LonghandId::ColorScheme {
1134 self.has_color_scheme = true;
1136 return;
1137 }
1138
1139 let refs = match decl {
1140 PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references,
1141 _ => return,
1142 };
1143
1144 if !refs.any_var && !refs.any_attr {
1145 return;
1146 }
1147
1148 let references = match id {
1152 LonghandId::FontSize => {
1153 if self.computed_context.is_root_element() {
1154 NonCustomReferences::ROOT_FONT_UNITS
1155 } else {
1156 NonCustomReferences::FONT_UNITS
1157 }
1158 },
1159 LonghandId::LineHeight => {
1160 if self.computed_context.is_root_element() {
1161 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1162 } else {
1163 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1164 }
1165 },
1166 _ => return,
1167 };
1168
1169 let variables: Vec<Atom> = refs
1170 .refs
1171 .iter()
1172 .filter_map(|reference| {
1173 if reference.substitution_kind != SubstitutionFunctionKind::Var {
1174 return None;
1175 }
1176 let registration = self
1177 .stylist
1178 .get_custom_property_registration(&reference.name);
1179 if !registration
1180 .syntax
1181 .dependent_types()
1182 .intersects(DependentDataTypes::LENGTH)
1183 {
1184 return None;
1185 }
1186 Some(reference.name.clone())
1187 })
1188 .collect();
1189 references.for_each(|idx| {
1190 let entry = &mut self.references_from_non_custom_properties[idx];
1191 let was_none = entry.is_none();
1192 let v = entry.get_or_insert_with(|| variables.clone());
1193 if was_none {
1194 return;
1195 }
1196 v.extend(variables.iter().cloned());
1197 });
1198 }
1199
1200 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1201 let registration = self.stylist.get_custom_property_registration(&name);
1202 match *value {
1203 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1204 if registration.inherits() {
1208 return false;
1209 }
1210 },
1211 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1212 if !registration.inherits() {
1215 return false;
1216 }
1217 },
1218 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1219 return false;
1223 },
1224 _ => {},
1225 }
1226
1227 let existing_value = self.custom_properties.get(registration, &name);
1228 let existing_value = match existing_value {
1229 None => {
1230 if matches!(
1231 value,
1232 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1233 ) {
1234 debug_assert!(registration.inherits(), "Should've been handled earlier");
1235 if registration.initial_value.is_none() {
1239 return false;
1240 }
1241 }
1242 return true;
1243 },
1244 Some(v) => v,
1245 };
1246 let computed_value = match value {
1247 CustomDeclarationValue::Unparsed(value) => {
1248 if let Some(existing_value) = existing_value.as_universal() {
1251 return existing_value != value;
1252 }
1253 if !registration.syntax.is_universal() {
1254 compute_value(
1255 &value.css,
1256 &value.url_data,
1257 registration,
1258 self.computed_context,
1259 )
1260 .ok()
1261 } else {
1262 None
1263 }
1264 },
1265 CustomDeclarationValue::Parsed(value) => {
1266 Some(value.to_computed_value(&self.computed_context))
1267 },
1268 CustomDeclarationValue::CSSWideKeyword(kw) => {
1269 match kw {
1270 CSSWideKeyword::Inherit => {
1271 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1272 if self
1276 .computed_context
1277 .inherited_custom_properties()
1278 .non_inherited
1279 .get(name)
1280 .is_none()
1281 {
1282 return false;
1283 }
1284 },
1285 CSSWideKeyword::Initial => {
1286 debug_assert!(registration.inherits(), "Should've been handled earlier");
1287 if let Some(initial_value) = self
1290 .stylist
1291 .get_custom_property_initial_values()
1292 .get(registration, name)
1293 {
1294 return existing_value != initial_value;
1295 }
1296 },
1297 CSSWideKeyword::Unset => {
1298 debug_assert!(false, "Should've been handled earlier");
1299 },
1300 CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => {},
1301 }
1302 None
1303 },
1304 };
1305
1306 if let Some(value) = computed_value {
1307 return existing_value.v != value.v;
1308 }
1309
1310 true
1311 }
1312
1313 pub fn build(
1329 mut self,
1330 defer: DeferFontRelativeCustomPropertyResolution,
1331 ) -> Option<CustomPropertiesMap> {
1332 let mut deferred_custom_properties = None;
1333 if self.may_have_cycles {
1334 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1335 deferred_custom_properties = Some(CustomPropertiesMap::default());
1336 }
1337 let mut invalid_non_custom_properties = LonghandIdSet::default();
1338 substitute_all(
1339 &mut self.custom_properties,
1340 deferred_custom_properties.as_mut(),
1341 &mut invalid_non_custom_properties,
1342 self.has_color_scheme,
1343 &self.seen,
1344 &self.references_from_non_custom_properties,
1345 self.stylist,
1346 self.computed_context,
1347 );
1348 self.computed_context.builder.invalid_non_custom_properties =
1349 invalid_non_custom_properties;
1350 }
1351
1352 self.custom_properties.shrink_to_fit();
1353
1354 let initial_values = self.stylist.get_custom_property_initial_values();
1359 self.computed_context.builder.custom_properties = ComputedCustomProperties {
1360 inherited: if self
1361 .computed_context
1362 .inherited_custom_properties()
1363 .inherited
1364 == self.custom_properties.inherited
1365 {
1366 self.computed_context
1367 .inherited_custom_properties()
1368 .inherited
1369 .clone()
1370 } else {
1371 self.custom_properties.inherited
1372 },
1373 non_inherited: if initial_values.non_inherited == self.custom_properties.non_inherited {
1374 initial_values.non_inherited.clone()
1375 } else {
1376 self.custom_properties.non_inherited
1377 },
1378 };
1379
1380 deferred_custom_properties
1381 }
1382
1383 pub fn build_deferred(
1386 deferred: CustomPropertiesMap,
1387 stylist: &Stylist,
1388 computed_context: &mut computed::Context,
1389 ) {
1390 if deferred.is_empty() {
1391 return;
1392 }
1393 let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
1394 for (k, v) in deferred.iter() {
1397 let Some(v) = v else { continue };
1398 let Some(v) = v.as_universal() else {
1399 unreachable!("Computing should have been deferred!")
1400 };
1401 substitute_references_if_needed_and_apply(
1402 k,
1403 v,
1404 &mut custom_properties,
1405 stylist,
1406 computed_context,
1407 );
1408 }
1409 computed_context.builder.custom_properties = custom_properties;
1410 }
1411}
1412
1413fn substitute_all(
1418 custom_properties_map: &mut ComputedCustomProperties,
1419 mut deferred_properties_map: Option<&mut CustomPropertiesMap>,
1420 invalid_non_custom_properties: &mut LonghandIdSet,
1421 has_color_scheme: bool,
1422 seen: &PrecomputedHashSet<&Name>,
1423 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1424 stylist: &Stylist,
1425 computed_context: &computed::Context,
1426) {
1427 #[derive(Clone, Eq, PartialEq, Debug)]
1434 enum VarType {
1435 Custom(Name),
1436 NonCustom(SingleNonCustomReference),
1437 }
1438
1439 #[derive(Debug)]
1441 struct VarInfo {
1442 var: Option<VarType>,
1447 lowlink: usize,
1452 }
1453 struct Context<'a, 'b: 'a> {
1456 count: usize,
1459 index_map: PrecomputedHashMap<Name, usize>,
1461 non_custom_index_map: NonCustomReferenceMap<usize>,
1463 var_info: SmallVec<[VarInfo; 5]>,
1465 stack: SmallVec<[usize; 5]>,
1468 non_custom_references: NonCustomReferences,
1470 has_color_scheme: bool,
1472 contains_computed_custom_property: bool,
1475 map: &'a mut ComputedCustomProperties,
1476 stylist: &'a Stylist,
1479 computed_context: &'a computed::Context<'b>,
1482 invalid_non_custom_properties: &'a mut LonghandIdSet,
1484 deferred_properties: Option<&'a mut CustomPropertiesMap>,
1488 }
1489
1490 fn traverse<'a, 'b>(
1509 var: VarType,
1510 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1511 context: &mut Context<'a, 'b>,
1512 ) -> Option<usize> {
1513 let value = match var {
1515 VarType::Custom(ref name) => {
1516 let registration = context.stylist.get_custom_property_registration(name);
1517 let value = context.map.get(registration, name)?.as_universal()?;
1518 let is_root = context.computed_context.is_root_element();
1519 let non_custom_refs = find_non_custom_references(
1522 registration,
1523 value,
1524 context.has_color_scheme,
1525 is_root,
1526 true,
1527 );
1528 context.non_custom_references |= non_custom_refs.unwrap_or_default();
1529 let has_dependency = value.references.any_var
1530 || value.references.any_attr
1531 || non_custom_refs.is_some();
1532 if !has_dependency {
1534 debug_assert!(!value.references.any_env, "Should've been handled earlier");
1535 if !registration.syntax.is_universal() {
1536 debug_assert!(
1541 registration
1542 .syntax
1543 .dependent_types()
1544 .intersects(DependentDataTypes::COLOR),
1545 "How did an unresolved value get here otherwise?",
1546 );
1547 let value = value.clone();
1548 substitute_references_if_needed_and_apply(
1549 name,
1550 &value,
1551 &mut context.map,
1552 context.stylist,
1553 context.computed_context,
1554 );
1555 }
1556 return None;
1557 }
1558
1559 match context.index_map.entry(name.clone()) {
1561 Entry::Occupied(entry) => {
1562 return Some(*entry.get());
1563 },
1564 Entry::Vacant(entry) => {
1565 entry.insert(context.count);
1566 },
1567 }
1568 context.contains_computed_custom_property |= !registration.syntax.is_universal();
1569
1570 Some(value.clone())
1573 },
1574 VarType::NonCustom(ref non_custom) => {
1575 let entry = &mut context.non_custom_index_map[*non_custom];
1576 if let Some(v) = entry {
1577 return Some(*v);
1578 }
1579 *entry = Some(context.count);
1580 None
1581 },
1582 };
1583
1584 let index = context.count;
1586 context.count += 1;
1587 debug_assert_eq!(index, context.var_info.len());
1588 context.var_info.push(VarInfo {
1589 var: Some(var.clone()),
1590 lowlink: index,
1591 });
1592 context.stack.push(index);
1593
1594 let mut self_ref = false;
1595 let mut lowlink = index;
1596 let visit_link =
1597 |var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| {
1598 let next_index = match traverse(var, non_custom_references, context) {
1599 Some(index) => index,
1600 None => {
1603 return;
1604 },
1605 };
1606 let next_info = &context.var_info[next_index];
1607 if next_index > index {
1608 *lowlink = cmp::min(*lowlink, next_info.lowlink);
1612 } else if next_index == index {
1613 *self_ref = true;
1614 } else if next_info.var.is_some() {
1615 *lowlink = cmp::min(*lowlink, next_index);
1618 }
1619 };
1620 if let Some(ref v) = value.as_ref() {
1621 debug_assert!(
1622 matches!(var, VarType::Custom(_)),
1623 "Non-custom property has references?"
1624 );
1625
1626 for next in &v.references.refs {
1629 if next.substitution_kind != SubstitutionFunctionKind::Var {
1630 continue;
1631 }
1632 visit_link(
1633 VarType::Custom(next.name.clone()),
1634 context,
1635 &mut lowlink,
1636 &mut self_ref,
1637 );
1638 }
1639
1640 v.references.non_custom_references.for_each(|r| {
1642 visit_link(VarType::NonCustom(r), context, &mut lowlink, &mut self_ref);
1643 });
1644 } else if let VarType::NonCustom(non_custom) = var {
1645 let entry = &non_custom_references[non_custom];
1646 if let Some(deps) = entry.as_ref() {
1647 for d in deps {
1648 visit_link(
1650 VarType::Custom(d.clone()),
1651 context,
1652 &mut lowlink,
1653 &mut self_ref,
1654 );
1655 }
1656 }
1657 }
1658
1659 context.var_info[index].lowlink = lowlink;
1660 if lowlink != index {
1661 return Some(index);
1669 }
1670
1671 let mut in_loop = self_ref;
1673 let name;
1674
1675 let handle_variable_in_loop = |name: &Name, context: &mut Context<'a, 'b>| {
1676 if context.contains_computed_custom_property {
1677 if context.non_custom_references.intersects(
1680 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
1681 ) {
1682 context
1683 .invalid_non_custom_properties
1684 .insert(LonghandId::FontSize);
1685 }
1686 if context
1687 .non_custom_references
1688 .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS)
1689 {
1690 context
1691 .invalid_non_custom_properties
1692 .insert(LonghandId::LineHeight);
1693 }
1694 }
1695 handle_invalid_at_computed_value_time(name, context.map, context.computed_context);
1697 };
1698 loop {
1699 let var_index = context
1700 .stack
1701 .pop()
1702 .expect("The current variable should still be in stack");
1703 let var_info = &mut context.var_info[var_index];
1704 let var_name = var_info
1708 .var
1709 .take()
1710 .expect("Variable should not be poped from stack twice");
1711 if var_index == index {
1712 name = match var_name {
1713 VarType::Custom(name) => name,
1714 VarType::NonCustom(..) => return None,
1718 };
1719 break;
1720 }
1721 if let VarType::Custom(name) = var_name {
1722 handle_variable_in_loop(&name, context);
1726 }
1727 in_loop = true;
1728 }
1729 if in_loop {
1734 handle_variable_in_loop(&name, context);
1735 context.non_custom_references = NonCustomReferences::default();
1736 return None;
1737 }
1738
1739 if let Some(ref v) = value {
1740 let registration = context.stylist.get_custom_property_registration(&name);
1741
1742 let mut defer = false;
1743 if let Some(ref mut deferred) = context.deferred_properties {
1744 defer = find_non_custom_references(
1747 registration,
1748 v,
1749 context.has_color_scheme,
1750 context.computed_context.is_root_element(),
1751 false,
1752 )
1753 .is_some()
1754 || v.references.refs.iter().any(|reference| {
1755 (reference.substitution_kind == SubstitutionFunctionKind::Var
1756 && deferred.get(&reference.name).is_some())
1757 || reference.substitution_kind == SubstitutionFunctionKind::Attr
1758 });
1759
1760 if defer {
1761 let value = ComputedRegisteredValue::universal(Arc::clone(v));
1762 deferred.insert(&name, value);
1763 context.map.remove(registration, &name);
1764 }
1765 }
1766
1767 if !defer && (v.references.any_var || v.references.any_attr) {
1769 substitute_references_if_needed_and_apply(
1770 &name,
1771 v,
1772 &mut context.map,
1773 context.stylist,
1774 context.computed_context,
1775 );
1776 }
1777 }
1778 context.non_custom_references = NonCustomReferences::default();
1779
1780 None
1782 }
1783
1784 for name in seen {
1788 let mut context = Context {
1789 count: 0,
1790 index_map: PrecomputedHashMap::default(),
1791 non_custom_index_map: NonCustomReferenceMap::default(),
1792 stack: SmallVec::new(),
1793 var_info: SmallVec::new(),
1794 map: custom_properties_map,
1795 non_custom_references: NonCustomReferences::default(),
1796 has_color_scheme,
1797 stylist,
1798 computed_context,
1799 invalid_non_custom_properties,
1800 deferred_properties: deferred_properties_map.as_deref_mut(),
1801 contains_computed_custom_property: false,
1802 };
1803 traverse(
1804 VarType::Custom((*name).clone()),
1805 references_from_non_custom_properties,
1806 &mut context,
1807 );
1808 }
1809}
1810
1811fn handle_invalid_at_computed_value_time(
1813 name: &Name,
1814 custom_properties: &mut ComputedCustomProperties,
1815 computed_context: &computed::Context,
1816) {
1817 let stylist = computed_context.style().stylist.unwrap();
1818 let registration = stylist.get_custom_property_registration(&name);
1819 if !registration.syntax.is_universal() {
1820 if registration.inherits() && !computed_context.is_root_element() {
1823 let inherited = computed_context.inherited_custom_properties();
1824 if let Some(value) = inherited.get(registration, name) {
1825 custom_properties.insert(registration, name, value.clone());
1826 return;
1827 }
1828 } else if let Some(ref initial_value) = registration.initial_value {
1829 if let Ok(initial_value) = compute_value(
1830 &initial_value.css,
1831 &initial_value.url_data,
1832 registration,
1833 computed_context,
1834 ) {
1835 custom_properties.insert(registration, name, initial_value);
1836 return;
1837 }
1838 }
1839 }
1840 custom_properties.remove(registration, name);
1841}
1842
1843fn substitute_references_if_needed_and_apply(
1845 name: &Name,
1846 value: &Arc<VariableValue>,
1847 custom_properties: &mut ComputedCustomProperties,
1848 stylist: &Stylist,
1849 computed_context: &computed::Context,
1850) {
1851 let registration = stylist.get_custom_property_registration(&name);
1852 if !value.has_references() && registration.syntax.is_universal() {
1853 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
1855 custom_properties.insert(registration, name, computed_value);
1856 return;
1857 }
1858
1859 let inherited = computed_context.inherited_custom_properties();
1860 let url_data = &value.url_data;
1861 let substitution =
1862 match substitute_internal(value, custom_properties, stylist, computed_context) {
1863 Ok(v) => v,
1864 Err(..) => {
1865 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
1866 return;
1867 },
1868 };
1869
1870 {
1872 let css = &substitution.css;
1873 let css_wide_kw = {
1874 let mut input = ParserInput::new(&css);
1875 let mut input = Parser::new(&mut input);
1876 input.try_parse(CSSWideKeyword::parse)
1877 };
1878
1879 if let Ok(kw) = css_wide_kw {
1880 match (
1884 kw,
1885 registration.inherits(),
1886 computed_context.is_root_element(),
1887 ) {
1888 (CSSWideKeyword::Initial, _, _)
1889 | (CSSWideKeyword::Revert, false, _)
1890 | (CSSWideKeyword::RevertLayer, false, _)
1891 | (CSSWideKeyword::Unset, false, _)
1892 | (CSSWideKeyword::Revert, true, true)
1893 | (CSSWideKeyword::RevertLayer, true, true)
1894 | (CSSWideKeyword::Unset, true, true)
1895 | (CSSWideKeyword::Inherit, _, true) => {
1896 remove_and_insert_initial_value(name, registration, custom_properties);
1897 },
1898 (CSSWideKeyword::Revert, true, false)
1899 | (CSSWideKeyword::RevertLayer, true, false)
1900 | (CSSWideKeyword::Inherit, _, false)
1901 | (CSSWideKeyword::Unset, true, false) => {
1902 match inherited.get(registration, name) {
1903 Some(value) => {
1904 custom_properties.insert(registration, name, value.clone());
1905 },
1906 None => {
1907 custom_properties.remove(registration, name);
1908 },
1909 };
1910 },
1911 }
1912 return;
1913 }
1914 }
1915
1916 let value = match substitution.into_value(url_data, registration, computed_context) {
1917 Ok(v) => v,
1918 Err(()) => {
1919 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
1920 return;
1921 },
1922 };
1923
1924 custom_properties.insert(registration, name, value);
1925}
1926
1927#[derive(Default)]
1928struct Substitution<'a> {
1929 css: Cow<'a, str>,
1930 first_token_type: TokenSerializationType,
1931 last_token_type: TokenSerializationType,
1932}
1933
1934impl<'a> Substitution<'a> {
1935 fn from_value(v: VariableValue) -> Self {
1936 Substitution {
1937 css: v.css.into(),
1938 first_token_type: v.first_token_type,
1939 last_token_type: v.last_token_type,
1940 }
1941 }
1942
1943 fn into_value(
1944 self,
1945 url_data: &UrlExtraData,
1946 registration: &PropertyRegistrationData,
1947 computed_context: &computed::Context,
1948 ) -> Result<ComputedRegisteredValue, ()> {
1949 if registration.syntax.is_universal() {
1950 return Ok(ComputedRegisteredValue::universal(Arc::new(
1951 VariableValue {
1952 css: self.css.into_owned(),
1953 first_token_type: self.first_token_type,
1954 last_token_type: self.last_token_type,
1955 url_data: url_data.clone(),
1956 references: Default::default(),
1957 },
1958 )));
1959 }
1960 compute_value(&self.css, url_data, registration, computed_context)
1961 }
1962
1963 fn new(
1964 css: &'a str,
1965 first_token_type: TokenSerializationType,
1966 last_token_type: TokenSerializationType,
1967 ) -> Self {
1968 Self {
1969 css: Cow::Borrowed(css),
1970 first_token_type,
1971 last_token_type,
1972 }
1973 }
1974}
1975
1976fn compute_value(
1977 css: &str,
1978 url_data: &UrlExtraData,
1979 registration: &PropertyRegistrationData,
1980 computed_context: &computed::Context,
1981) -> Result<ComputedRegisteredValue, ()> {
1982 debug_assert!(!registration.syntax.is_universal());
1983
1984 let mut input = ParserInput::new(&css);
1985 let mut input = Parser::new(&mut input);
1986
1987 SpecifiedRegisteredValue::compute(
1988 &mut input,
1989 registration,
1990 url_data,
1991 computed_context,
1992 AllowComputationallyDependent::Yes,
1993 )
1994}
1995
1996fn remove_and_insert_initial_value(
1998 name: &Name,
1999 registration: &PropertyRegistrationData,
2000 custom_properties: &mut ComputedCustomProperties,
2001) {
2002 custom_properties.remove(registration, name);
2003 if let Some(ref initial_value) = registration.initial_value {
2004 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2005 custom_properties.insert(registration, name, value);
2006 }
2007}
2008
2009fn do_substitute_chunk<'a>(
2010 css: &'a str,
2011 start: usize,
2012 end: usize,
2013 first_token_type: TokenSerializationType,
2014 last_token_type: TokenSerializationType,
2015 url_data: &UrlExtraData,
2016 custom_properties: &'a ComputedCustomProperties,
2017 stylist: &Stylist,
2018 computed_context: &computed::Context,
2019 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2020) -> Result<Substitution<'a>, ()> {
2021 if start == end {
2022 return Ok(Substitution::default());
2024 }
2025 if references
2027 .peek()
2028 .map_or(true, |reference| reference.end > end)
2029 {
2030 let result = &css[start..end];
2031 return Ok(Substitution::new(result, first_token_type, last_token_type));
2032 }
2033
2034 let mut substituted = ComputedValue::empty(url_data);
2035 let mut next_token_type = first_token_type;
2036 let mut cur_pos = start;
2037 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2038 if reference.start != cur_pos {
2039 substituted.push(
2040 &css[cur_pos..reference.start],
2041 next_token_type,
2042 reference.prev_token_type,
2043 )?;
2044 }
2045
2046 let substitution = substitute_one_reference(
2047 css,
2048 url_data,
2049 custom_properties,
2050 reference,
2051 stylist,
2052 computed_context,
2053 references,
2054 )?;
2055
2056 if reference.start == start && reference.end == end {
2058 return Ok(substitution);
2059 }
2060
2061 substituted.push(
2062 &substitution.css,
2063 substitution.first_token_type,
2064 substitution.last_token_type,
2065 )?;
2066 next_token_type = reference.next_token_type;
2067 cur_pos = reference.end;
2068 }
2069 if cur_pos != end {
2071 substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
2072 }
2073 Ok(Substitution::from_value(substituted))
2074}
2075
2076fn substitute_one_reference<'a>(
2077 css: &'a str,
2078 url_data: &UrlExtraData,
2079 custom_properties: &'a ComputedCustomProperties,
2080 reference: &SubstitutionFunctionReference,
2081 stylist: &Stylist,
2082 computed_context: &computed::Context,
2083 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2084) -> Result<Substitution<'a>, ()> {
2085 match reference.substitution_kind {
2086 SubstitutionFunctionKind::Var => {
2087 let registration = stylist.get_custom_property_registration(&reference.name);
2088 if let Some(v) = custom_properties.get(registration, &reference.name) {
2089 while references
2091 .next_if(|next_ref| next_ref.end <= reference.end)
2092 .is_some()
2093 {}
2094 return Ok(Substitution::from_value(v.to_variable_value()));
2095 }
2096 },
2097 SubstitutionFunctionKind::Env => {
2098 let device = stylist.device();
2099 if let Some(v) = device.environment().get(&reference.name, device, url_data) {
2100 while references
2101 .next_if(|next_ref| next_ref.end <= reference.end)
2102 .is_some()
2103 {}
2104 return Ok(Substitution::from_value(v));
2105 }
2106 },
2107 SubstitutionFunctionKind::Attr => {
2108 },
2110 };
2111
2112 let Some(ref fallback) = reference.fallback else {
2113 return Err(());
2114 };
2115
2116 do_substitute_chunk(
2117 css,
2118 fallback.start.get(),
2119 reference.end - 1, fallback.first_token_type,
2121 fallback.last_token_type,
2122 url_data,
2123 custom_properties,
2124 stylist,
2125 computed_context,
2126 references,
2127 )
2128}
2129
2130fn substitute_internal<'a>(
2132 variable_value: &'a VariableValue,
2133 custom_properties: &'a ComputedCustomProperties,
2134 stylist: &Stylist,
2135 computed_context: &computed::Context,
2136) -> Result<Substitution<'a>, ()> {
2137 let mut refs = variable_value.references.refs.iter().peekable();
2138 do_substitute_chunk(
2139 &variable_value.css,
2140 0,
2141 variable_value.css.len(),
2142 variable_value.first_token_type,
2143 variable_value.last_token_type,
2144 &variable_value.url_data,
2145 custom_properties,
2146 stylist,
2147 computed_context,
2148 &mut refs,
2149 )
2150}
2151
2152pub fn substitute<'a>(
2154 variable_value: &'a VariableValue,
2155 custom_properties: &'a ComputedCustomProperties,
2156 stylist: &Stylist,
2157 computed_context: &computed::Context,
2158) -> Result<Cow<'a, str>, ()> {
2159 debug_assert!(variable_value.has_references());
2160 let v = substitute_internal(variable_value, custom_properties, stylist, computed_context)?;
2161 Ok(v.css)
2162}