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(app_units::Au(device.app_units_per_device_pixel()).to_f32_px(), url_data)
112}
113
114static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
115 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
116 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
117 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
118 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
119];
120
121#[cfg(feature = "gecko")]
122macro_rules! lnf_int {
123 ($id:ident) => {
124 unsafe {
125 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
126 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
127 )
128 }
129 };
130}
131
132#[cfg(feature = "servo")]
133macro_rules! lnf_int {
134 ($id:ident) => {
135 0
137 };
138}
139
140macro_rules! lnf_int_variable {
141 ($atom:expr, $id:ident, $ctor:ident) => {{
142 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
143 VariableValue::$ctor(lnf_int!($id), url_data)
144 }
145 make_variable!($atom, __eval)
146 }};
147}
148
149fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
150 let int_pixels = lnf_int!(TitlebarRadius);
151 let unzoomed_scale =
152 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
153 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
154}
155
156static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 10] = [
157 lnf_int_variable!(
158 atom!("-moz-mac-titlebar-height"),
159 MacTitlebarHeight,
160 int_pixels
161 ),
162 make_variable!(
163 atom!("-moz-gtk-csd-titlebar-radius"),
164 eval_gtk_csd_titlebar_radius
165 ),
166 lnf_int_variable!(
167 atom!("-moz-gtk-csd-tooltip-radius"),
168 TooltipRadius,
169 int_pixels
170 ),
171 lnf_int_variable!(
172 atom!("-moz-gtk-csd-close-button-position"),
173 GTKCSDCloseButtonPosition,
174 integer
175 ),
176 lnf_int_variable!(
177 atom!("-moz-gtk-csd-minimize-button-position"),
178 GTKCSDMinimizeButtonPosition,
179 integer
180 ),
181 lnf_int_variable!(
182 atom!("-moz-gtk-csd-maximize-button-position"),
183 GTKCSDMaximizeButtonPosition,
184 integer
185 ),
186 lnf_int_variable!(
187 atom!("-moz-overlay-scrollbar-fade-duration"),
188 ScrollbarFadeDuration,
189 int_ms
190 ),
191 make_variable!(
192 atom!("-moz-content-preferred-color-scheme"),
193 get_content_preferred_color_scheme
194 ),
195 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
196 make_variable!(atom!("hairline"), get_hairline),
197];
198
199impl CssEnvironment {
200 #[inline]
201 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
202 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
203 return Some((var.evaluator)(device, url_data));
204 }
205 if !url_data.chrome_rules_enabled() {
206 return None;
207 }
208 let var = CHROME_ENVIRONMENT_VARIABLES
209 .iter()
210 .find(|var| var.name == *name)?;
211 Some((var.evaluator)(device, url_data))
212 }
213}
214
215pub type Name = Atom;
219
220pub fn parse_name(s: &str) -> Result<&str, ()> {
224 if s.starts_with("--") && s.len() > 2 {
225 Ok(&s[2..])
226 } else {
227 Err(())
228 }
229}
230
231#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
236pub struct VariableValue {
237 pub css: String,
239
240 pub url_data: UrlExtraData,
242
243 first_token_type: TokenSerializationType,
244 last_token_type: TokenSerializationType,
245
246 references: References,
248}
249
250trivial_to_computed_value!(VariableValue);
251
252pub fn compute_variable_value(
254 value: &Arc<VariableValue>,
255 registration: &PropertyRegistrationData,
256 computed_context: &computed::Context,
257) -> Option<ComputedRegisteredValue> {
258 if registration.syntax.is_universal() {
259 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
260 }
261 compute_value(
262 &value.css,
263 &value.url_data,
264 registration,
265 computed_context,
266 ).ok()
267}
268
269impl PartialEq for VariableValue {
271 fn eq(&self, other: &Self) -> bool {
272 self.css == other.css
273 }
274}
275
276impl Eq for VariableValue {}
277
278impl ToCss for SpecifiedValue {
279 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
280 where
281 W: Write,
282 {
283 dest.write_str(&self.css)
284 }
285}
286
287#[repr(C)]
290#[derive(Clone, Debug, Default, PartialEq)]
291pub struct ComputedCustomProperties {
292 pub inherited: CustomPropertiesMap,
295 pub non_inherited: CustomPropertiesMap,
297}
298
299impl ComputedCustomProperties {
300 pub fn is_empty(&self) -> bool {
302 self.inherited.is_empty() && self.non_inherited.is_empty()
303 }
304
305 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
307 self.inherited
310 .get_index(index)
311 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
312 }
313
314 fn insert(
317 &mut self,
318 registration: &PropertyRegistrationData,
319 name: &Name,
320 value: ComputedRegisteredValue,
321 ) {
322 self.map_mut(registration).insert(name, value)
323 }
324
325 fn remove(&mut self, registration: &PropertyRegistrationData, name: &Name) {
328 self.map_mut(registration).remove(name);
329 }
330
331 fn shrink_to_fit(&mut self) {
333 self.inherited.shrink_to_fit();
334 self.non_inherited.shrink_to_fit();
335 }
336
337 fn map_mut(&mut self, registration: &PropertyRegistrationData) -> &mut CustomPropertiesMap {
338 if registration.inherits() {
339 &mut self.inherited
340 } else {
341 &mut self.non_inherited
342 }
343 }
344
345 pub fn get(
347 &self,
348 registration: &PropertyRegistrationData,
349 name: &Name,
350 ) -> Option<&ComputedRegisteredValue> {
351 if registration.inherits() {
352 self.inherited.get(name)
353 } else {
354 self.non_inherited.get(name)
355 }
356 }
357}
358
359pub type SpecifiedValue = VariableValue;
362pub type ComputedValue = VariableValue;
365
366#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
368struct NonCustomReferences(u8);
369
370bitflags! {
371 impl NonCustomReferences: u8 {
372 const FONT_UNITS = 1 << 0;
374 const ROOT_FONT_UNITS = 1 << 1;
376 const LH_UNITS = 1 << 2;
378 const ROOT_LH_UNITS = 1 << 3;
380 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
382 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
384 }
385}
386
387impl NonCustomReferences {
388 fn for_each<F>(&self, mut f: F)
389 where
390 F: FnMut(SingleNonCustomReference),
391 {
392 for (_, r) in self.iter_names() {
393 let single = match r {
394 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
395 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
396 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
397 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
398 _ => unreachable!("Unexpected single bit value"),
399 };
400 f(single);
401 }
402 }
403
404 fn from_unit(value: &CowRcStr) -> Self {
405 if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
410 return Self::FONT_UNITS | Self::LH_UNITS;
411 }
412 if value.eq_ignore_ascii_case(FontRelativeLength::EM) ||
413 value.eq_ignore_ascii_case(FontRelativeLength::EX) ||
414 value.eq_ignore_ascii_case(FontRelativeLength::CAP) ||
415 value.eq_ignore_ascii_case(FontRelativeLength::CH) ||
416 value.eq_ignore_ascii_case(FontRelativeLength::IC)
417 {
418 return Self::FONT_UNITS;
419 }
420 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
421 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
422 }
423 if value.eq_ignore_ascii_case(FontRelativeLength::REM) {
424 return Self::ROOT_FONT_UNITS;
425 }
426 Self::empty()
427 }
428}
429
430#[derive(Clone, Copy, Debug, Eq, PartialEq)]
431enum SingleNonCustomReference {
432 FontUnits = 0,
433 RootFontUnits,
434 LhUnits,
435 RootLhUnits,
436}
437
438struct NonCustomReferenceMap<T>([Option<T>; 4]);
439
440impl<T> Default for NonCustomReferenceMap<T> {
441 fn default() -> Self {
442 NonCustomReferenceMap(Default::default())
443 }
444}
445
446impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
447 type Output = Option<T>;
448
449 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
450 &self.0[reference as usize]
451 }
452}
453
454impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
455 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
456 &mut self.0[reference as usize]
457 }
458}
459
460#[derive(Clone, Copy, PartialEq, Eq)]
462#[allow(missing_docs)]
463pub enum DeferFontRelativeCustomPropertyResolution {
464 Yes,
465 No,
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 VarOrEnvReference {
477 name: Name,
478 start: usize,
479 end: usize,
480 fallback: Option<VariableFallback>,
481 prev_token_type: TokenSerializationType,
482 next_token_type: TokenSerializationType,
483 is_var: bool,
484}
485
486#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
489struct References {
490 refs: Vec<VarOrEnvReference>,
491 non_custom_references: NonCustomReferences,
492 any_env: bool,
493 any_var: bool,
494}
495
496impl References {
497 fn has_references(&self) -> bool {
498 !self.refs.is_empty()
499 }
500
501 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
502 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
503 if is_root_element {
504 mask |= NonCustomReferences::ROOT_DEPENDENCIES
505 }
506 self.non_custom_references & mask
507 }
508}
509
510impl VariableValue {
511 fn empty(url_data: &UrlExtraData) -> Self {
512 Self {
513 css: String::new(),
514 last_token_type: Default::default(),
515 first_token_type: Default::default(),
516 url_data: url_data.clone(),
517 references: Default::default(),
518 }
519 }
520
521 pub fn new(
524 css: String,
525 url_data: &UrlExtraData,
526 first_token_type: TokenSerializationType,
527 last_token_type: TokenSerializationType,
528 ) -> Self {
529 Self {
530 css,
531 url_data: url_data.clone(),
532 first_token_type,
533 last_token_type,
534 references: Default::default(),
535 }
536 }
537
538 fn push<'i>(
539 &mut self,
540 css: &str,
541 css_first_token_type: TokenSerializationType,
542 css_last_token_type: TokenSerializationType,
543 ) -> Result<(), ()> {
544 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
552
553 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
554 return Err(());
555 }
556
557 if css.is_empty() {
562 return Ok(());
563 }
564
565 self.first_token_type.set_if_nothing(css_first_token_type);
566 if self
569 .last_token_type
570 .needs_separator_when_before(css_first_token_type)
571 {
572 self.css.push_str("/**/")
573 }
574 self.css.push_str(css);
575 self.last_token_type = css_last_token_type;
576 Ok(())
577 }
578
579 pub fn parse<'i, 't>(
581 input: &mut Parser<'i, 't>,
582 url_data: &UrlExtraData,
583 ) -> Result<Self, ParseError<'i>> {
584 input.skip_whitespace();
585
586 let mut references = References::default();
587 let mut missing_closing_characters = String::new();
588 let start_position = input.position();
589 let (first_token_type, last_token_type) = parse_declaration_value(
590 input,
591 start_position,
592 &mut references,
593 &mut missing_closing_characters,
594 )?;
595 let mut css = input.slice_from(start_position).to_owned();
596 if !missing_closing_characters.is_empty() {
597 if css.ends_with("\\") &&
599 matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
600 {
601 css.pop();
602 }
603 css.push_str(&missing_closing_characters);
604 }
605
606 css.shrink_to_fit();
607 references.refs.shrink_to_fit();
608
609 Ok(Self {
610 css,
611 url_data: url_data.clone(),
612 first_token_type,
613 last_token_type,
614 references,
615 })
616 }
617
618 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
620 Self::from_token(
621 Token::Number {
622 has_sign: false,
623 value: number as f32,
624 int_value: Some(number),
625 },
626 url_data,
627 )
628 }
629
630 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
632 Self::from_token(Token::Ident(ident.into()), url_data)
633 }
634
635 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
637 Self::from_token(
641 Token::Dimension {
642 has_sign: false,
643 value: number,
644 int_value: None,
645 unit: CowRcStr::from("px"),
646 },
647 url_data,
648 )
649 }
650
651 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
653 Self::from_token(
654 Token::Dimension {
655 has_sign: false,
656 value: number as f32,
657 int_value: Some(number),
658 unit: CowRcStr::from("ms"),
659 },
660 url_data,
661 )
662 }
663
664 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
666 Self::from_token(
667 Token::Dimension {
668 has_sign: false,
669 value: number as f32,
670 int_value: Some(number),
671 unit: CowRcStr::from("px"),
672 },
673 url_data,
674 )
675 }
676
677 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
678 let token_type = token.serialization_type();
679 let mut css = token.to_css_string();
680 css.shrink_to_fit();
681
682 VariableValue {
683 css,
684 url_data: url_data.clone(),
685 first_token_type: token_type,
686 last_token_type: token_type,
687 references: Default::default(),
688 }
689 }
690
691 pub fn css_text(&self) -> &str {
693 &self.css
694 }
695
696 pub fn has_references(&self) -> bool {
699 self.references.has_references()
700 }
701}
702
703fn parse_declaration_value<'i, 't>(
705 input: &mut Parser<'i, 't>,
706 input_start: SourcePosition,
707 references: &mut References,
708 missing_closing_characters: &mut String,
709) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
710 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
711 parse_declaration_value_block(input, input_start, references, missing_closing_characters)
712 })
713}
714
715fn parse_declaration_value_block<'i, 't>(
717 input: &mut Parser<'i, 't>,
718 input_start: SourcePosition,
719 references: &mut References,
720 missing_closing_characters: &mut String,
721) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
722 let mut is_first = true;
723 let mut first_token_type = TokenSerializationType::Nothing;
724 let mut last_token_type = TokenSerializationType::Nothing;
725 let mut prev_reference_index: Option<usize> = None;
726 loop {
727 let token_start = input.position();
728 let Ok(token) = input.next_including_whitespace_and_comments() else {
729 break;
730 };
731
732 let prev_token_type = last_token_type;
733 let serialization_type = token.serialization_type();
734 last_token_type = serialization_type;
735 if is_first {
736 first_token_type = last_token_type;
737 is_first = false;
738 }
739
740 macro_rules! nested {
741 () => {
742 input.parse_nested_block(|input| {
743 parse_declaration_value_block(
744 input,
745 input_start,
746 references,
747 missing_closing_characters,
748 )
749 })?
750 };
751 }
752 macro_rules! check_closed {
753 ($closing:expr) => {
754 if !input.slice_from(token_start).ends_with($closing) {
755 missing_closing_characters.push_str($closing)
756 }
757 };
758 }
759 if let Some(index) = prev_reference_index.take() {
760 references.refs[index].next_token_type = serialization_type;
761 }
762 match *token {
763 Token::Comment(_) => {
764 let token_slice = input.slice_from(token_start);
765 if !token_slice.ends_with("*/") {
766 missing_closing_characters.push_str(if token_slice.ends_with('*') {
767 "/"
768 } else {
769 "*/"
770 })
771 }
772 },
773 Token::BadUrl(ref u) => {
774 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
775 return Err(input.new_custom_error(e));
776 },
777 Token::BadString(ref s) => {
778 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
779 return Err(input.new_custom_error(e));
780 },
781 Token::CloseParenthesis => {
782 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
783 return Err(input.new_custom_error(e));
784 },
785 Token::CloseSquareBracket => {
786 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
787 return Err(input.new_custom_error(e));
788 },
789 Token::CloseCurlyBracket => {
790 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
791 return Err(input.new_custom_error(e));
792 },
793 Token::Function(ref name) => {
794 let is_var = name.eq_ignore_ascii_case("var");
795 if is_var || name.eq_ignore_ascii_case("env") {
796 let our_ref_index = references.refs.len();
797 let fallback = input.parse_nested_block(|input| {
798 let name = input.expect_ident()?;
801 let name = Atom::from(if is_var {
802 match parse_name(name.as_ref()) {
803 Ok(name) => name,
804 Err(()) => {
805 let name = name.clone();
806 return Err(input.new_custom_error(
807 SelectorParseErrorKind::UnexpectedIdent(name),
808 ));
809 },
810 }
811 } else {
812 name.as_ref()
813 });
814
815 let start = token_start.byte_index() - input_start.byte_index();
819 references.refs.push(VarOrEnvReference {
820 name,
821 start,
822 end: start,
824 prev_token_type,
825 next_token_type: TokenSerializationType::Nothing,
827 fallback: None,
829 is_var,
830 });
831
832 let mut fallback = None;
833 if input.try_parse(|input| input.expect_comma()).is_ok() {
834 input.skip_whitespace();
835 let fallback_start = num::NonZeroUsize::new(
836 input.position().byte_index() - input_start.byte_index(),
837 )
838 .unwrap();
839 let (first, last) = parse_declaration_value(
842 input,
843 input_start,
844 references,
845 missing_closing_characters,
846 )?;
847 fallback = Some(VariableFallback {
848 start: fallback_start,
849 first_token_type: first,
850 last_token_type: last,
851 });
852 } else {
853 let state = input.state();
854 parse_declaration_value_block(
858 input,
859 input_start,
860 references,
861 missing_closing_characters,
862 )?;
863 input.reset(&state);
864 }
865 Ok(fallback)
866 })?;
867 check_closed!(")");
868 prev_reference_index = Some(our_ref_index);
869 let reference = &mut references.refs[our_ref_index];
870 reference.end = input.position().byte_index() - input_start.byte_index() +
871 missing_closing_characters.len();
872 reference.fallback = fallback;
873 if is_var {
874 references.any_var = true;
875 } else {
876 references.any_env = true;
877 }
878 } else {
879 nested!();
880 check_closed!(")");
881 }
882 },
883 Token::ParenthesisBlock => {
884 nested!();
885 check_closed!(")");
886 },
887 Token::CurlyBracketBlock => {
888 nested!();
889 check_closed!("}");
890 },
891 Token::SquareBracketBlock => {
892 nested!();
893 check_closed!("]");
894 },
895 Token::QuotedString(_) => {
896 let token_slice = input.slice_from(token_start);
897 let quote = &token_slice[..1];
898 debug_assert!(matches!(quote, "\"" | "'"));
899 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
900 missing_closing_characters.push_str(quote)
901 }
902 },
903 Token::Ident(ref value) |
904 Token::AtKeyword(ref value) |
905 Token::Hash(ref value) |
906 Token::IDHash(ref value) |
907 Token::UnquotedUrl(ref value) |
908 Token::Dimension {
909 unit: ref value, ..
910 } => {
911 references
912 .non_custom_references
913 .insert(NonCustomReferences::from_unit(value));
914 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
915 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
916 missing_closing_characters.push_str("�")
921 }
922 if is_unquoted_url {
923 check_closed!(")");
924 }
925 },
926 _ => {},
927 };
928 }
929 Ok((first_token_type, last_token_type))
930}
931
932pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
934 seen: PrecomputedHashSet<&'a Name>,
935 may_have_cycles: bool,
936 has_color_scheme: bool,
937 custom_properties: ComputedCustomProperties,
938 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
939 stylist: &'a Stylist,
940 computed_context: &'a mut computed::Context<'b>,
941 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
942}
943
944fn find_non_custom_references(
945 registration: &PropertyRegistrationData,
946 value: &VariableValue,
947 may_have_color_scheme: bool,
948 is_root_element: bool,
949 include_universal: bool,
950) -> Option<NonCustomReferences> {
951 let dependent_types = registration.syntax.dependent_types();
952 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH) ||
953 (include_universal && registration.syntax.is_universal());
954 if may_reference_length {
955 let value_dependencies = value.references.non_custom_references(is_root_element);
956 if !value_dependencies.is_empty() {
957 return Some(value_dependencies);
958 }
959 }
960 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
961 return Some(NonCustomReferences::empty());
965 }
966 None
967}
968
969impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
970 pub fn new_with_properties(
974 stylist: &'a Stylist,
975 custom_properties: ComputedCustomProperties,
976 computed_context: &'a mut computed::Context<'b>,
977 ) -> Self {
978 Self {
979 seen: PrecomputedHashSet::default(),
980 reverted: Default::default(),
981 may_have_cycles: false,
982 has_color_scheme: false,
983 custom_properties,
984 stylist,
985 computed_context,
986 references_from_non_custom_properties: NonCustomReferenceMap::default(),
987 }
988 }
989
990 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
992 let is_root_element = context.is_root_element();
993
994 let inherited = context.inherited_custom_properties();
995 let initial_values = stylist.get_custom_property_initial_values();
996 let properties = ComputedCustomProperties {
997 inherited: if is_root_element {
998 debug_assert!(inherited.is_empty());
999 initial_values.inherited.clone()
1000 } else {
1001 inherited.inherited.clone()
1002 },
1003 non_inherited: initial_values.non_inherited.clone(),
1004 };
1005
1006 context
1009 .style()
1010 .add_flags(stylist.get_custom_property_initial_values_flags());
1011 Self::new_with_properties(stylist, properties, context)
1012 }
1013
1014 pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) {
1016 let CustomDeclaration {
1017 ref name,
1018 ref value,
1019 } = *declaration;
1020
1021 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
1022 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
1023 return;
1024 }
1025 }
1026
1027 let was_already_present = !self.seen.insert(name);
1028 if was_already_present {
1029 return;
1030 }
1031
1032 if !self.value_may_affect_style(name, value) {
1033 return;
1034 }
1035
1036 let map = &mut self.custom_properties;
1037 let registration = self.stylist.get_custom_property_registration(&name);
1038 match value {
1039 CustomDeclarationValue::Unparsed(unparsed_value) => {
1040 let may_have_color_scheme = true;
1045 let has_dependency = unparsed_value.references.any_var ||
1048 find_non_custom_references(
1049 registration,
1050 unparsed_value,
1051 may_have_color_scheme,
1052 self.computed_context.is_root_element(),
1053 false,
1054 )
1055 .is_some();
1056 if !has_dependency {
1060 return substitute_references_if_needed_and_apply(
1061 name,
1062 unparsed_value,
1063 map,
1064 self.stylist,
1065 self.computed_context,
1066 );
1067 }
1068 self.may_have_cycles = true;
1069 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1070 map.insert(registration, name, value);
1071 },
1072 CustomDeclarationValue::Parsed(parsed_value) => {
1073 let value = parsed_value.to_computed_value(&self.computed_context);
1074 map.insert(registration, name, value);
1075 },
1076 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
1077 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
1078 let origin_revert = matches!(keyword, CSSWideKeyword::Revert);
1079 self.seen.remove(name);
1080 self.reverted.insert(name, (priority, origin_revert));
1081 },
1082 CSSWideKeyword::Initial => {
1083 debug_assert!(registration.inherits(), "Should've been handled earlier");
1085 remove_and_insert_initial_value(name, registration, map);
1086 },
1087 CSSWideKeyword::Inherit => {
1088 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1090 if let Some(inherited_value) = self
1091 .computed_context
1092 .inherited_custom_properties()
1093 .non_inherited
1094 .get(name)
1095 {
1096 map.insert(registration, name, inherited_value.clone());
1097 }
1098 },
1099 CSSWideKeyword::Unset => unreachable!(),
1101 },
1102 }
1103 }
1104
1105 #[inline]
1107 pub fn might_have_non_custom_dependency(id: LonghandId, decl: &PropertyDeclaration) -> bool {
1108 if id == LonghandId::ColorScheme {
1109 return true;
1110 }
1111 if matches!(id, LonghandId::LineHeight | LonghandId::FontSize) {
1112 return matches!(decl, PropertyDeclaration::WithVariables(..));
1113 }
1114 false
1115 }
1116
1117 pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) {
1120 debug_assert!(Self::might_have_non_custom_dependency(id, decl));
1121 if id == LonghandId::ColorScheme {
1122 self.has_color_scheme = true;
1124 return;
1125 }
1126
1127 let refs = match decl {
1128 PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references,
1129 _ => return,
1130 };
1131
1132 if !refs.any_var {
1133 return;
1134 }
1135
1136 let references = match id {
1140 LonghandId::FontSize => {
1141 if self.computed_context.is_root_element() {
1142 NonCustomReferences::ROOT_FONT_UNITS
1143 } else {
1144 NonCustomReferences::FONT_UNITS
1145 }
1146 },
1147 LonghandId::LineHeight => {
1148 if self.computed_context.is_root_element() {
1149 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1150 } else {
1151 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1152 }
1153 },
1154 _ => return,
1155 };
1156
1157 let variables: Vec<Atom> = refs
1158 .refs
1159 .iter()
1160 .filter_map(|reference| {
1161 if !reference.is_var {
1162 return None;
1163 }
1164 let registration = self
1165 .stylist
1166 .get_custom_property_registration(&reference.name);
1167 if !registration
1168 .syntax
1169 .dependent_types()
1170 .intersects(DependentDataTypes::LENGTH)
1171 {
1172 return None;
1173 }
1174 Some(reference.name.clone())
1175 })
1176 .collect();
1177 references.for_each(|idx| {
1178 let entry = &mut self.references_from_non_custom_properties[idx];
1179 let was_none = entry.is_none();
1180 let v = entry.get_or_insert_with(|| variables.clone());
1181 if was_none {
1182 return;
1183 }
1184 v.extend(variables.iter().cloned());
1185 });
1186 }
1187
1188 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1189 let registration = self.stylist.get_custom_property_registration(&name);
1190 match *value {
1191 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1192 if registration.inherits() {
1196 return false;
1197 }
1198 },
1199 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1200 if !registration.inherits() {
1203 return false;
1204 }
1205 },
1206 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1207 return false;
1211 },
1212 _ => {},
1213 }
1214
1215 let existing_value = self.custom_properties.get(registration, &name);
1216 let existing_value = match existing_value {
1217 None => {
1218 if matches!(value, CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) {
1219 debug_assert!(registration.inherits(), "Should've been handled earlier");
1220 if registration.initial_value.is_none() {
1224 return false;
1225 }
1226 }
1227 return true;
1228 },
1229 Some(v) => v,
1230 };
1231 let computed_value = match value {
1232 CustomDeclarationValue::Unparsed(value) => {
1233 if let Some(existing_value) = existing_value.as_universal() {
1236 return existing_value != value;
1237 }
1238 if !registration.syntax.is_universal() {
1239 compute_value(
1240 &value.css,
1241 &value.url_data,
1242 registration,
1243 self.computed_context,
1244 ).ok()
1245 } else {
1246 None
1247 }
1248 },
1249 CustomDeclarationValue::Parsed(value) => {
1250 Some(value.to_computed_value(&self.computed_context))
1251 },
1252 CustomDeclarationValue::CSSWideKeyword(kw) => {
1253 match kw {
1254 CSSWideKeyword::Inherit => {
1255 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1256 if self
1260 .computed_context
1261 .inherited_custom_properties()
1262 .non_inherited
1263 .get(name)
1264 .is_none()
1265 {
1266 return false;
1267 }
1268 },
1269 CSSWideKeyword::Initial => {
1270 debug_assert!(registration.inherits(), "Should've been handled earlier");
1271 if let Some(initial_value) = self
1274 .stylist
1275 .get_custom_property_initial_values()
1276 .get(registration, name)
1277 {
1278 return existing_value != initial_value;
1279 }
1280 },
1281 CSSWideKeyword::Unset => {
1282 debug_assert!(false, "Should've been handled earlier");
1283 },
1284 CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => {},
1285 }
1286 None
1287 }
1288 };
1289
1290 if let Some(value) = computed_value {
1291 return existing_value.v != value.v;
1292 }
1293
1294 true
1295 }
1296
1297 pub fn build(
1313 mut self,
1314 defer: DeferFontRelativeCustomPropertyResolution,
1315 ) -> Option<CustomPropertiesMap> {
1316 let mut deferred_custom_properties = None;
1317 if self.may_have_cycles {
1318 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1319 deferred_custom_properties = Some(CustomPropertiesMap::default());
1320 }
1321 let mut invalid_non_custom_properties = LonghandIdSet::default();
1322 substitute_all(
1323 &mut self.custom_properties,
1324 deferred_custom_properties.as_mut(),
1325 &mut invalid_non_custom_properties,
1326 self.has_color_scheme,
1327 &self.seen,
1328 &self.references_from_non_custom_properties,
1329 self.stylist,
1330 self.computed_context,
1331 );
1332 self.computed_context.builder.invalid_non_custom_properties =
1333 invalid_non_custom_properties;
1334 }
1335
1336 self.custom_properties.shrink_to_fit();
1337
1338 let initial_values = self.stylist.get_custom_property_initial_values();
1343 self.computed_context.builder.custom_properties = ComputedCustomProperties {
1344 inherited: if self
1345 .computed_context
1346 .inherited_custom_properties()
1347 .inherited ==
1348 self.custom_properties.inherited
1349 {
1350 self.computed_context
1351 .inherited_custom_properties()
1352 .inherited
1353 .clone()
1354 } else {
1355 self.custom_properties.inherited
1356 },
1357 non_inherited: if initial_values.non_inherited == self.custom_properties.non_inherited {
1358 initial_values.non_inherited.clone()
1359 } else {
1360 self.custom_properties.non_inherited
1361 },
1362 };
1363
1364 deferred_custom_properties
1365 }
1366
1367 pub fn build_deferred(
1370 deferred: CustomPropertiesMap,
1371 stylist: &Stylist,
1372 computed_context: &mut computed::Context,
1373 ) {
1374 if deferred.is_empty() {
1375 return;
1376 }
1377 let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
1378 for (k, v) in deferred.iter() {
1381 let Some(v) = v else { continue };
1382 let Some(v) = v.as_universal() else {
1383 unreachable!("Computing should have been deferred!")
1384 };
1385 substitute_references_if_needed_and_apply(
1386 k,
1387 v,
1388 &mut custom_properties,
1389 stylist,
1390 computed_context,
1391 );
1392 }
1393 computed_context.builder.custom_properties = custom_properties;
1394 }
1395}
1396
1397fn substitute_all(
1402 custom_properties_map: &mut ComputedCustomProperties,
1403 mut deferred_properties_map: Option<&mut CustomPropertiesMap>,
1404 invalid_non_custom_properties: &mut LonghandIdSet,
1405 has_color_scheme: bool,
1406 seen: &PrecomputedHashSet<&Name>,
1407 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1408 stylist: &Stylist,
1409 computed_context: &computed::Context,
1410) {
1411 #[derive(Clone, Eq, PartialEq, Debug)]
1418 enum VarType {
1419 Custom(Name),
1420 NonCustom(SingleNonCustomReference),
1421 }
1422
1423 #[derive(Debug)]
1425 struct VarInfo {
1426 var: Option<VarType>,
1431 lowlink: usize,
1436 }
1437 struct Context<'a, 'b: 'a> {
1440 count: usize,
1443 index_map: PrecomputedHashMap<Name, usize>,
1445 non_custom_index_map: NonCustomReferenceMap<usize>,
1447 var_info: SmallVec<[VarInfo; 5]>,
1449 stack: SmallVec<[usize; 5]>,
1452 non_custom_references: NonCustomReferences,
1454 has_color_scheme: bool,
1456 contains_computed_custom_property: bool,
1459 map: &'a mut ComputedCustomProperties,
1460 stylist: &'a Stylist,
1463 computed_context: &'a computed::Context<'b>,
1466 invalid_non_custom_properties: &'a mut LonghandIdSet,
1468 deferred_properties: Option<&'a mut CustomPropertiesMap>,
1472 }
1473
1474 fn traverse<'a, 'b>(
1493 var: VarType,
1494 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1495 context: &mut Context<'a, 'b>,
1496 ) -> Option<usize> {
1497 let value = match var {
1499 VarType::Custom(ref name) => {
1500 let registration = context.stylist.get_custom_property_registration(name);
1501 let value = context.map.get(registration, name)?.as_universal()?;
1502 let is_root = context.computed_context.is_root_element();
1503 let non_custom_refs = find_non_custom_references(
1506 registration,
1507 value,
1508 context.has_color_scheme,
1509 is_root,
1510 true,
1511 );
1512 context.non_custom_references |= non_custom_refs.unwrap_or_default();
1513 let has_dependency = value.references.any_var || non_custom_refs.is_some();
1514 if !has_dependency {
1516 debug_assert!(!value.references.any_env, "Should've been handled earlier");
1517 if !registration.syntax.is_universal() {
1518 debug_assert!(
1523 registration
1524 .syntax
1525 .dependent_types()
1526 .intersects(DependentDataTypes::COLOR),
1527 "How did an unresolved value get here otherwise?",
1528 );
1529 let value = value.clone();
1530 substitute_references_if_needed_and_apply(
1531 name,
1532 &value,
1533 &mut context.map,
1534 context.stylist,
1535 context.computed_context,
1536 );
1537 }
1538 return None;
1539 }
1540
1541 match context.index_map.entry(name.clone()) {
1543 Entry::Occupied(entry) => {
1544 return Some(*entry.get());
1545 },
1546 Entry::Vacant(entry) => {
1547 entry.insert(context.count);
1548 },
1549 }
1550 context.contains_computed_custom_property |= !registration.syntax.is_universal();
1551
1552 Some(value.clone())
1555 },
1556 VarType::NonCustom(ref non_custom) => {
1557 let entry = &mut context.non_custom_index_map[*non_custom];
1558 if let Some(v) = entry {
1559 return Some(*v);
1560 }
1561 *entry = Some(context.count);
1562 None
1563 },
1564 };
1565
1566 let index = context.count;
1568 context.count += 1;
1569 debug_assert_eq!(index, context.var_info.len());
1570 context.var_info.push(VarInfo {
1571 var: Some(var.clone()),
1572 lowlink: index,
1573 });
1574 context.stack.push(index);
1575
1576 let mut self_ref = false;
1577 let mut lowlink = index;
1578 let visit_link =
1579 |var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| {
1580 let next_index = match traverse(var, non_custom_references, context) {
1581 Some(index) => index,
1582 None => {
1585 return;
1586 },
1587 };
1588 let next_info = &context.var_info[next_index];
1589 if next_index > index {
1590 *lowlink = cmp::min(*lowlink, next_info.lowlink);
1594 } else if next_index == index {
1595 *self_ref = true;
1596 } else if next_info.var.is_some() {
1597 *lowlink = cmp::min(*lowlink, next_index);
1600 }
1601 };
1602 if let Some(ref v) = value.as_ref() {
1603 debug_assert!(
1604 matches!(var, VarType::Custom(_)),
1605 "Non-custom property has references?"
1606 );
1607
1608 for next in &v.references.refs {
1611 if !next.is_var {
1612 continue;
1613 }
1614 visit_link(
1615 VarType::Custom(next.name.clone()),
1616 context,
1617 &mut lowlink,
1618 &mut self_ref,
1619 );
1620 }
1621
1622 v.references.non_custom_references.for_each(|r| {
1624 visit_link(VarType::NonCustom(r), context, &mut lowlink, &mut self_ref);
1625 });
1626 } else if let VarType::NonCustom(non_custom) = var {
1627 let entry = &non_custom_references[non_custom];
1628 if let Some(deps) = entry.as_ref() {
1629 for d in deps {
1630 visit_link(
1632 VarType::Custom(d.clone()),
1633 context,
1634 &mut lowlink,
1635 &mut self_ref,
1636 );
1637 }
1638 }
1639 }
1640
1641 context.var_info[index].lowlink = lowlink;
1642 if lowlink != index {
1643 return Some(index);
1651 }
1652
1653 let mut in_loop = self_ref;
1655 let name;
1656
1657 let handle_variable_in_loop = |name: &Name, context: &mut Context<'a, 'b>| {
1658 if context.contains_computed_custom_property {
1659 if context
1662 .non_custom_references
1663 .intersects(NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS)
1664 {
1665 context
1666 .invalid_non_custom_properties
1667 .insert(LonghandId::FontSize);
1668 }
1669 if context
1670 .non_custom_references
1671 .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS)
1672 {
1673 context
1674 .invalid_non_custom_properties
1675 .insert(LonghandId::LineHeight);
1676 }
1677 }
1678 handle_invalid_at_computed_value_time(name, context.map, context.computed_context);
1680 };
1681 loop {
1682 let var_index = context
1683 .stack
1684 .pop()
1685 .expect("The current variable should still be in stack");
1686 let var_info = &mut context.var_info[var_index];
1687 let var_name = var_info
1691 .var
1692 .take()
1693 .expect("Variable should not be poped from stack twice");
1694 if var_index == index {
1695 name = match var_name {
1696 VarType::Custom(name) => name,
1697 VarType::NonCustom(..) => return None,
1701 };
1702 break;
1703 }
1704 if let VarType::Custom(name) = var_name {
1705 handle_variable_in_loop(&name, context);
1709 }
1710 in_loop = true;
1711 }
1712 if in_loop {
1717 handle_variable_in_loop(&name, context);
1718 context.non_custom_references = NonCustomReferences::default();
1719 return None;
1720 }
1721
1722 if let Some(ref v) = value {
1723 let registration = context.stylist.get_custom_property_registration(&name);
1724
1725 let mut defer = false;
1726 if let Some(ref mut deferred) = context.deferred_properties {
1727 defer = find_non_custom_references(
1730 registration,
1731 v,
1732 context.has_color_scheme,
1733 context.computed_context.is_root_element(),
1734 false,
1735 )
1736 .is_some() ||
1737 v.references.refs.iter().any(|reference| {
1738 reference.is_var && deferred.get(&reference.name).is_some()
1739 });
1740
1741 if defer {
1742 let value = ComputedRegisteredValue::universal(Arc::clone(v));
1743 deferred.insert(&name, value);
1744 context.map.remove(registration, &name);
1745 }
1746 }
1747
1748 if !defer && v.references.any_var {
1750 substitute_references_if_needed_and_apply(
1751 &name,
1752 v,
1753 &mut context.map,
1754 context.stylist,
1755 context.computed_context,
1756 );
1757 }
1758 }
1759 context.non_custom_references = NonCustomReferences::default();
1760
1761 None
1763 }
1764
1765 for name in seen {
1769 let mut context = Context {
1770 count: 0,
1771 index_map: PrecomputedHashMap::default(),
1772 non_custom_index_map: NonCustomReferenceMap::default(),
1773 stack: SmallVec::new(),
1774 var_info: SmallVec::new(),
1775 map: custom_properties_map,
1776 non_custom_references: NonCustomReferences::default(),
1777 has_color_scheme,
1778 stylist,
1779 computed_context,
1780 invalid_non_custom_properties,
1781 deferred_properties: deferred_properties_map.as_deref_mut(),
1782 contains_computed_custom_property: false,
1783 };
1784 traverse(
1785 VarType::Custom((*name).clone()),
1786 references_from_non_custom_properties,
1787 &mut context,
1788 );
1789 }
1790}
1791
1792fn handle_invalid_at_computed_value_time(
1794 name: &Name,
1795 custom_properties: &mut ComputedCustomProperties,
1796 computed_context: &computed::Context,
1797) {
1798 let stylist = computed_context.style().stylist.unwrap();
1799 let registration = stylist.get_custom_property_registration(&name);
1800 if !registration.syntax.is_universal() {
1801 if registration.inherits() && !computed_context.is_root_element() {
1804 let inherited = computed_context.inherited_custom_properties();
1805 if let Some(value) = inherited.get(registration, name) {
1806 custom_properties.insert(registration, name, value.clone());
1807 return;
1808 }
1809 } else if let Some(ref initial_value) = registration.initial_value {
1810 if let Ok(initial_value) = compute_value(
1811 &initial_value.css,
1812 &initial_value.url_data,
1813 registration,
1814 computed_context,
1815 ) {
1816 custom_properties.insert(registration, name, initial_value);
1817 return;
1818 }
1819 }
1820 }
1821 custom_properties.remove(registration, name);
1822}
1823
1824fn substitute_references_if_needed_and_apply(
1826 name: &Name,
1827 value: &Arc<VariableValue>,
1828 custom_properties: &mut ComputedCustomProperties,
1829 stylist: &Stylist,
1830 computed_context: &computed::Context,
1831) {
1832 let registration = stylist.get_custom_property_registration(&name);
1833 if !value.has_references() && registration.syntax.is_universal() {
1834 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
1836 custom_properties.insert(registration, name, computed_value);
1837 return;
1838 }
1839
1840 let inherited = computed_context.inherited_custom_properties();
1841 let url_data = &value.url_data;
1842 let substitution = match substitute_internal(
1843 value,
1844 custom_properties,
1845 stylist,
1846 computed_context,
1847 ) {
1848 Ok(v) => v,
1849 Err(..) => {
1850 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
1851 return;
1852 },
1853 };
1854
1855 {
1857 let css = &substitution.css;
1858 let css_wide_kw = {
1859 let mut input = ParserInput::new(&css);
1860 let mut input = Parser::new(&mut input);
1861 input.try_parse(CSSWideKeyword::parse)
1862 };
1863
1864 if let Ok(kw) = css_wide_kw {
1865 match (
1869 kw,
1870 registration.inherits(),
1871 computed_context.is_root_element(),
1872 ) {
1873 (CSSWideKeyword::Initial, _, _) |
1874 (CSSWideKeyword::Revert, false, _) |
1875 (CSSWideKeyword::RevertLayer, false, _) |
1876 (CSSWideKeyword::Unset, false, _) |
1877 (CSSWideKeyword::Revert, true, true) |
1878 (CSSWideKeyword::RevertLayer, true, true) |
1879 (CSSWideKeyword::Unset, true, true) |
1880 (CSSWideKeyword::Inherit, _, true) => {
1881 remove_and_insert_initial_value(name, registration, custom_properties);
1882 },
1883 (CSSWideKeyword::Revert, true, false) |
1884 (CSSWideKeyword::RevertLayer, true, false) |
1885 (CSSWideKeyword::Inherit, _, false) |
1886 (CSSWideKeyword::Unset, true, false) => {
1887 match inherited.get(registration, name) {
1888 Some(value) => {
1889 custom_properties.insert(registration, name, value.clone());
1890 },
1891 None => {
1892 custom_properties.remove(registration, name);
1893 },
1894 };
1895 },
1896 }
1897 return;
1898 }
1899 }
1900
1901 let value = match substitution.into_value(url_data, registration, computed_context) {
1902 Ok(v) => v,
1903 Err(()) => {
1904 handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
1905 return;
1906 }
1907 };
1908
1909 custom_properties.insert(registration, name, value);
1910}
1911
1912#[derive(Default)]
1913struct Substitution<'a> {
1914 css: Cow<'a, str>,
1915 first_token_type: TokenSerializationType,
1916 last_token_type: TokenSerializationType,
1917}
1918
1919impl<'a> Substitution<'a> {
1920 fn from_value(v: VariableValue) -> Self {
1921 Substitution {
1922 css: v.css.into(),
1923 first_token_type: v.first_token_type,
1924 last_token_type: v.last_token_type,
1925 }
1926 }
1927
1928 fn into_value(
1929 self,
1930 url_data: &UrlExtraData,
1931 registration: &PropertyRegistrationData,
1932 computed_context: &computed::Context,
1933 ) -> Result<ComputedRegisteredValue, ()> {
1934 if registration.syntax.is_universal() {
1935 return Ok(ComputedRegisteredValue::universal(Arc::new(VariableValue {
1936 css: self.css.into_owned(),
1937 first_token_type: self.first_token_type,
1938 last_token_type: self.last_token_type,
1939 url_data: url_data.clone(),
1940 references: Default::default(),
1941 })))
1942 }
1943 compute_value(&self.css, url_data, registration, computed_context)
1944 }
1945
1946 fn new(
1947 css: &'a str,
1948 first_token_type: TokenSerializationType,
1949 last_token_type: TokenSerializationType,
1950 ) -> Self {
1951 Self {
1952 css: Cow::Borrowed(css),
1953 first_token_type,
1954 last_token_type,
1955 }
1956 }
1957}
1958
1959fn compute_value(
1960 css: &str,
1961 url_data: &UrlExtraData,
1962 registration: &PropertyRegistrationData,
1963 computed_context: &computed::Context,
1964) -> Result<ComputedRegisteredValue, ()> {
1965 debug_assert!(!registration.syntax.is_universal());
1966
1967 let mut input = ParserInput::new(&css);
1968 let mut input = Parser::new(&mut input);
1969
1970 SpecifiedRegisteredValue::compute(
1971 &mut input,
1972 registration,
1973 url_data,
1974 computed_context,
1975 AllowComputationallyDependent::Yes,
1976 )
1977}
1978
1979fn remove_and_insert_initial_value(
1981 name: &Name,
1982 registration: &PropertyRegistrationData,
1983 custom_properties: &mut ComputedCustomProperties,
1984) {
1985 custom_properties.remove(registration, name);
1986 if let Some(ref initial_value) = registration.initial_value {
1987 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
1988 custom_properties.insert(registration, name, value);
1989 }
1990}
1991
1992fn do_substitute_chunk<'a>(
1993 css: &'a str,
1994 start: usize,
1995 end: usize,
1996 first_token_type: TokenSerializationType,
1997 last_token_type: TokenSerializationType,
1998 url_data: &UrlExtraData,
1999 custom_properties: &'a ComputedCustomProperties,
2000 stylist: &Stylist,
2001 computed_context: &computed::Context,
2002 references: &mut std::iter::Peekable<std::slice::Iter<VarOrEnvReference>>,
2003) -> Result<Substitution<'a>, ()> {
2004 if start == end {
2005 return Ok(Substitution::default());
2007 }
2008 if references
2010 .peek()
2011 .map_or(true, |reference| reference.end > end)
2012 {
2013 let result = &css[start..end];
2014 return Ok(Substitution::new(result, first_token_type, last_token_type));
2015 }
2016
2017 let mut substituted = ComputedValue::empty(url_data);
2018 let mut next_token_type = first_token_type;
2019 let mut cur_pos = start;
2020 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2021 if reference.start != cur_pos {
2022 substituted.push(
2023 &css[cur_pos..reference.start],
2024 next_token_type,
2025 reference.prev_token_type,
2026 )?;
2027 }
2028
2029 let substitution = substitute_one_reference(
2030 css,
2031 url_data,
2032 custom_properties,
2033 reference,
2034 stylist,
2035 computed_context,
2036 references,
2037 )?;
2038
2039 if reference.start == start && reference.end == end {
2041 return Ok(substitution);
2042 }
2043
2044 substituted.push(
2045 &substitution.css,
2046 substitution.first_token_type,
2047 substitution.last_token_type,
2048 )?;
2049 next_token_type = reference.next_token_type;
2050 cur_pos = reference.end;
2051 }
2052 if cur_pos != end {
2054 substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
2055 }
2056 Ok(Substitution::from_value(substituted))
2057}
2058
2059fn substitute_one_reference<'a>(
2060 css: &'a str,
2061 url_data: &UrlExtraData,
2062 custom_properties: &'a ComputedCustomProperties,
2063 reference: &VarOrEnvReference,
2064 stylist: &Stylist,
2065 computed_context: &computed::Context,
2066 references: &mut std::iter::Peekable<std::slice::Iter<VarOrEnvReference>>,
2067) -> Result<Substitution<'a>, ()> {
2068 if reference.is_var {
2069 let registration = stylist.get_custom_property_registration(&reference.name);
2070 if let Some(v) = custom_properties.get(registration, &reference.name) {
2071 while references
2073 .next_if(|next_ref| next_ref.end <= reference.end)
2074 .is_some()
2075 {}
2076 return Ok(Substitution::from_value(v.to_variable_value()))
2077 }
2078 } else {
2079 let device = stylist.device();
2080 if let Some(v) = device.environment().get(&reference.name, device, url_data) {
2081 while references
2082 .next_if(|next_ref| next_ref.end <= reference.end)
2083 .is_some()
2084 {}
2085 return Ok(Substitution::from_value(v));
2086 }
2087 }
2088
2089 let Some(ref fallback) = reference.fallback else {
2090 return Err(());
2091 };
2092
2093 do_substitute_chunk(
2094 css,
2095 fallback.start.get(),
2096 reference.end - 1, fallback.first_token_type,
2098 fallback.last_token_type,
2099 url_data,
2100 custom_properties,
2101 stylist,
2102 computed_context,
2103 references,
2104 )
2105}
2106
2107fn substitute_internal<'a>(
2109 variable_value: &'a VariableValue,
2110 custom_properties: &'a ComputedCustomProperties,
2111 stylist: &Stylist,
2112 computed_context: &computed::Context,
2113) -> Result<Substitution<'a>, ()> {
2114 let mut refs = variable_value.references.refs.iter().peekable();
2115 do_substitute_chunk(
2116 &variable_value.css,
2117 0,
2118 variable_value.css.len(),
2119 variable_value.first_token_type,
2120 variable_value.last_token_type,
2121 &variable_value.url_data,
2122 custom_properties,
2123 stylist,
2124 computed_context,
2125 &mut refs,
2126 )
2127}
2128
2129pub fn substitute<'a>(
2131 variable_value: &'a VariableValue,
2132 custom_properties: &'a ComputedCustomProperties,
2133 stylist: &Stylist,
2134 computed_context: &computed::Context,
2135) -> Result<Cow<'a, str>, ()> {
2136 debug_assert!(variable_value.has_references());
2137 let v = substitute_internal(
2138 variable_value,
2139 custom_properties,
2140 stylist,
2141 computed_context,
2142 )?;
2143 Ok(v.css)
2144}