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