1use crate::applicable_declarations::{CascadePriority, RevertKind};
10use crate::custom_properties_map::{AllSubstitutionFunctions, CustomPropertiesMap, OwnMap};
11use crate::device::Device;
12use crate::dom::AttributeTracker;
13use crate::properties::{
14 CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
15 PropertyDeclaration,
16};
17use crate::properties_and_values::{
18 rule::Descriptors as PropertyDescriptors,
19 syntax::{data_type::DependentDataTypes, Descriptor as SyntaxDescriptor},
20 value::{
21 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
22 SpecifiedValue as SpecifiedRegisteredValue,
23 },
24};
25use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
26use crate::stylesheets::UrlExtraData;
27use crate::stylist::Stylist;
28use crate::values::computed::{self, ToComputedValue};
29use crate::values::generics::calc::SortKey as AttrUnit;
30use crate::values::specified::FontRelativeLength;
31use crate::values::specified::ParsedNamespace;
32use crate::{derives::*, Namespace, Prefix};
33use crate::{Atom, LocalName};
34use cssparser::{
35 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
36};
37use rustc_hash::FxHashMap;
38use selectors::parser::SelectorParseErrorKind;
39use servo_arc::Arc;
40use smallvec::SmallVec;
41use std::borrow::Cow;
42use std::collections::hash_map::Entry;
43use std::fmt::{self, Write};
44use std::ops::{Index, IndexMut};
45use std::{cmp, num};
46use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
47
48#[derive(Debug, MallocSizeOf)]
53pub struct CssEnvironment;
54
55type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
56
57struct EnvironmentVariable {
58 name: Atom,
59 evaluator: EnvironmentEvaluator,
60}
61
62macro_rules! make_variable {
63 ($name:expr, $evaluator:expr) => {{
64 EnvironmentVariable {
65 name: $name,
66 evaluator: $evaluator,
67 }
68 }};
69}
70
71fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
72 VariableValue::pixels(device.safe_area_insets().top, url_data)
73}
74
75fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
76 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
77}
78
79fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
80 VariableValue::pixels(device.safe_area_insets().left, url_data)
81}
82
83fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
84 VariableValue::pixels(device.safe_area_insets().right, url_data)
85}
86
87#[cfg(feature = "gecko")]
88fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
89 use crate::queries::values::PrefersColorScheme;
90 let prefers_color_scheme = unsafe {
91 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
92 device.document(),
93 true,
94 )
95 };
96 VariableValue::ident(
97 match prefers_color_scheme {
98 PrefersColorScheme::Light => "light",
99 PrefersColorScheme::Dark => "dark",
100 },
101 url_data,
102 )
103}
104
105#[cfg(feature = "servo")]
106fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
107 VariableValue::ident("light", url_data)
109}
110
111fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
112 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
113}
114
115fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
116 VariableValue::pixels(
117 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
118 url_data,
119 )
120}
121
122static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
123 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
124 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
125 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
126 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
127];
128
129#[cfg(feature = "gecko")]
130macro_rules! lnf_int {
131 ($id:ident) => {
132 unsafe {
133 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
134 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
135 )
136 }
137 };
138}
139
140#[cfg(feature = "servo")]
141macro_rules! lnf_int {
142 ($id:ident) => {
143 0
145 };
146}
147
148macro_rules! lnf_int_variable {
149 ($atom:expr, $id:ident, $ctor:ident) => {{
150 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
151 VariableValue::$ctor(lnf_int!($id), url_data)
152 }
153 make_variable!($atom, __eval)
154 }};
155}
156
157fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
158 let int_pixels = lnf_int!(TitlebarRadius);
159 let unzoomed_scale =
160 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
161 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
162}
163
164static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
165 make_variable!(
166 atom!("-moz-gtk-csd-titlebar-radius"),
167 eval_gtk_csd_titlebar_radius
168 ),
169 lnf_int_variable!(
170 atom!("-moz-gtk-csd-tooltip-radius"),
171 TooltipRadius,
172 int_pixels
173 ),
174 lnf_int_variable!(
175 atom!("-moz-gtk-csd-close-button-position"),
176 GTKCSDCloseButtonPosition,
177 integer
178 ),
179 lnf_int_variable!(
180 atom!("-moz-gtk-csd-minimize-button-position"),
181 GTKCSDMinimizeButtonPosition,
182 integer
183 ),
184 lnf_int_variable!(
185 atom!("-moz-gtk-csd-maximize-button-position"),
186 GTKCSDMaximizeButtonPosition,
187 integer
188 ),
189 lnf_int_variable!(
190 atom!("-moz-overlay-scrollbar-fade-duration"),
191 ScrollbarFadeDuration,
192 int_ms
193 ),
194 make_variable!(
195 atom!("-moz-content-preferred-color-scheme"),
196 get_content_preferred_color_scheme
197 ),
198 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
199 make_variable!(atom!("hairline"), get_hairline),
200];
201
202impl CssEnvironment {
203 #[inline]
204 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
205 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
206 return Some((var.evaluator)(device, url_data));
207 }
208 if !url_data.chrome_rules_enabled() {
209 return None;
210 }
211 let var = CHROME_ENVIRONMENT_VARIABLES
212 .iter()
213 .find(|var| var.name == *name)?;
214 Some((var.evaluator)(device, url_data))
215 }
216}
217
218pub type Name = Atom;
222
223pub fn parse_name(s: &str) -> Result<&str, ()> {
227 if s.starts_with("--") && s.len() > 2 {
228 Ok(&s[2..])
229 } else {
230 Err(())
231 }
232}
233
234#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
239pub struct VariableValue {
240 pub css: String,
242
243 pub url_data: UrlExtraData,
245
246 first_token_type: TokenSerializationType,
247 last_token_type: TokenSerializationType,
248
249 references: References,
251}
252
253trivial_to_computed_value!(VariableValue);
254
255pub fn compute_variable_value(
257 value: &Arc<VariableValue>,
258 registration: &PropertyDescriptors,
259 computed_context: &computed::Context,
260) -> Option<ComputedRegisteredValue> {
261 if registration.is_universal() {
262 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
263 }
264 compute_value(&value.css, &value.url_data, registration, computed_context).ok()
265}
266
267impl PartialEq for VariableValue {
269 fn eq(&self, other: &Self) -> bool {
270 self.css == other.css
271 }
272}
273
274impl Eq for VariableValue {}
275
276impl ToCss for SpecifiedValue {
277 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
278 where
279 W: Write,
280 {
281 dest.write_str(&self.css)
282 }
283}
284
285#[repr(C)]
288#[derive(Clone, Debug, Default, PartialEq)]
289pub struct ComputedCustomProperties {
290 pub inherited: CustomPropertiesMap,
293 pub non_inherited: CustomPropertiesMap,
295}
296
297impl ComputedCustomProperties {
298 pub fn is_empty(&self) -> bool {
300 self.inherited.is_empty() && self.non_inherited.is_empty()
301 }
302
303 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
305 self.inherited
308 .get_index(index)
309 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
310 }
311
312 pub(crate) fn insert(
315 &mut self,
316 registration: &PropertyDescriptors,
317 name: &Name,
318 value: ComputedRegisteredValue,
319 ) {
320 self.map_mut(registration).insert(name, value)
321 }
322
323 pub(crate) fn remove(&mut self, registration: &PropertyDescriptors, name: &Name) {
326 self.map_mut(registration).remove(name);
327 }
328
329 fn shrink_to_fit(&mut self) {
331 self.inherited.shrink_to_fit();
332 self.non_inherited.shrink_to_fit();
333 }
334
335 fn map_mut(&mut self, registration: &PropertyDescriptors) -> &mut CustomPropertiesMap {
336 if registration.inherits() {
337 &mut self.inherited
338 } else {
339 &mut self.non_inherited
340 }
341 }
342
343 pub fn get(
345 &self,
346 registration: &PropertyDescriptors,
347 name: &Name,
348 ) -> Option<&ComputedRegisteredValue> {
349 if registration.inherits() {
350 self.inherited.get(name)
351 } else {
352 self.non_inherited.get(name)
353 }
354 }
355}
356
357pub type SpecifiedValue = VariableValue;
360pub type ComputedValue = VariableValue;
363
364#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
366struct NonCustomReferences(u8);
367
368bitflags! {
369 impl NonCustomReferences: u8 {
370 const FONT_UNITS = 1 << 0;
372 const ROOT_FONT_UNITS = 1 << 1;
374 const LH_UNITS = 1 << 2;
376 const ROOT_LH_UNITS = 1 << 3;
378 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
380 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
382 }
383}
384
385impl NonCustomReferences {
386 fn for_each<F>(&self, mut f: F)
387 where
388 F: FnMut(SingleNonCustomReference),
389 {
390 for (_, r) in self.iter_names() {
391 let single = match r {
392 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
393 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
394 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
395 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
396 _ => unreachable!("Unexpected single bit value"),
397 };
398 f(single);
399 }
400 }
401
402 fn from_unit(value: &CowRcStr) -> Self {
403 if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
408 return Self::FONT_UNITS | Self::LH_UNITS;
409 }
410 if value.eq_ignore_ascii_case(FontRelativeLength::EM)
411 || value.eq_ignore_ascii_case(FontRelativeLength::EX)
412 || value.eq_ignore_ascii_case(FontRelativeLength::CAP)
413 || value.eq_ignore_ascii_case(FontRelativeLength::CH)
414 || value.eq_ignore_ascii_case(FontRelativeLength::IC)
415 {
416 return Self::FONT_UNITS;
417 }
418 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
419 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
420 }
421 if value.eq_ignore_ascii_case(FontRelativeLength::REM)
422 || value.eq_ignore_ascii_case(FontRelativeLength::REX)
423 || value.eq_ignore_ascii_case(FontRelativeLength::RCH)
424 || value.eq_ignore_ascii_case(FontRelativeLength::RCAP)
425 || value.eq_ignore_ascii_case(FontRelativeLength::RIC)
426 {
427 return Self::ROOT_FONT_UNITS;
428 }
429 Self::empty()
430 }
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
434enum SingleNonCustomReference {
435 FontUnits = 0,
436 RootFontUnits,
437 LhUnits,
438 RootLhUnits,
439}
440
441struct NonCustomReferenceMap<T>([Option<T>; 4]);
442
443impl<T> Default for NonCustomReferenceMap<T> {
444 fn default() -> Self {
445 NonCustomReferenceMap(Default::default())
446 }
447}
448
449impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
450 type Output = Option<T>;
451
452 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
453 &self.0[reference as usize]
454 }
455}
456
457impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
458 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
459 &mut self.0[reference as usize]
460 }
461}
462
463#[derive(Clone, Copy, PartialEq, Eq)]
465#[allow(missing_docs)]
466pub enum DeferFontRelativeCustomPropertyResolution {
467 Yes,
468 No,
469}
470
471#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse)]
473pub enum SubstitutionFunctionKind {
474 Var,
476 Env,
478 Attr,
480}
481
482#[repr(C)]
485#[derive(Clone, Debug, Default, PartialEq)]
486pub struct ComputedSubstitutionFunctions {
487 pub custom_properties: ComputedCustomProperties,
489 pub attributes: OwnMap,
491}
492
493impl ComputedSubstitutionFunctions {
494 #[inline(always)]
497 pub fn new(
498 custom_properties: Option<ComputedCustomProperties>,
499 attributes: Option<OwnMap>,
500 ) -> Self {
501 Self {
502 custom_properties: custom_properties.unwrap_or_default(),
503 attributes: attributes.unwrap_or_default(),
504 }
505 }
506
507 #[inline(always)]
508 fn insert_var(
509 &mut self,
510 registration: &PropertyDescriptors,
511 name: &Name,
512 value: ComputedRegisteredValue,
513 ) {
514 self.custom_properties.insert(registration, name, value);
515 }
516
517 #[inline(always)]
518 fn insert_attr(&mut self, name: &Name, value: ComputedRegisteredValue) {
519 self.attributes.insert(name.clone(), Some(value));
520 }
521
522 #[inline(always)]
523 fn remove_var(&mut self, registration: &PropertyDescriptors, name: &Name) {
524 self.custom_properties.remove(registration, name);
525 }
526
527 #[inline(always)]
528 fn remove_attr(&mut self, name: &Name) {
529 self.attributes.insert(name.clone(), None);
530 }
531
532 #[inline(always)]
533 fn get_var(
534 &self,
535 registration: &PropertyDescriptors,
536 name: &Name,
537 ) -> Option<&ComputedRegisteredValue> {
538 self.custom_properties.get(registration, name)
539 }
540
541 #[inline(always)]
542 fn get_attr(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
543 self.attributes.get(name).and_then(|p| p.as_ref())
544 }
545}
546
547#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
548enum AttributeType {
549 None,
550 RawString,
551 Type(SyntaxDescriptor),
552 Unit(AttrUnit),
553}
554
555#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
556struct AttributeData {
557 kind: AttributeType,
558 namespace: ParsedNamespace,
559}
560
561#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
562struct VariableFallback {
563 start: num::NonZeroUsize,
567 first_token_type: TokenSerializationType,
568 last_token_type: TokenSerializationType,
569}
570
571#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
572struct SubstitutionFunctionReference {
573 name: Name,
574 start: usize,
575 end: usize,
576 fallback: Option<VariableFallback>,
577 attribute_data: AttributeData,
578 prev_token_type: TokenSerializationType,
579 next_token_type: TokenSerializationType,
580 substitution_kind: SubstitutionFunctionKind,
581}
582
583#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
586struct References {
587 refs: Vec<SubstitutionFunctionReference>,
588 non_custom_references: NonCustomReferences,
589 any_env: bool,
590 any_var: bool,
591 any_attr: bool,
592}
593
594impl References {
595 fn has_references(&self) -> bool {
596 !self.refs.is_empty()
597 }
598
599 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
600 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
601 if is_root_element {
602 mask |= NonCustomReferences::ROOT_DEPENDENCIES
603 }
604 self.non_custom_references & mask
605 }
606}
607
608impl VariableValue {
609 fn empty(url_data: &UrlExtraData) -> Self {
610 Self {
611 css: String::new(),
612 last_token_type: Default::default(),
613 first_token_type: Default::default(),
614 url_data: url_data.clone(),
615 references: Default::default(),
616 }
617 }
618
619 pub fn new(
622 css: String,
623 url_data: &UrlExtraData,
624 first_token_type: TokenSerializationType,
625 last_token_type: TokenSerializationType,
626 attribute_tainted: bool,
627 ) -> Self {
628 let mut references = References::default();
629 references.any_attr = attribute_tainted;
630 Self {
631 css,
632 url_data: url_data.clone(),
633 first_token_type,
634 last_token_type,
635 references,
636 }
637 }
638
639 fn push<'i>(
640 &mut self,
641 css: &str,
642 css_first_token_type: TokenSerializationType,
643 css_last_token_type: TokenSerializationType,
644 ) -> Result<(), ()> {
645 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
653
654 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
655 return Err(());
656 }
657
658 if css.is_empty() {
663 return Ok(());
664 }
665
666 self.first_token_type.set_if_nothing(css_first_token_type);
667 if self
670 .last_token_type
671 .needs_separator_when_before(css_first_token_type)
672 {
673 self.css.push_str("/**/")
674 }
675 self.css.push_str(css);
676 self.last_token_type = css_last_token_type;
677 Ok(())
678 }
679
680 pub fn parse<'i, 't>(
682 input: &mut Parser<'i, 't>,
683 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
684 url_data: &UrlExtraData,
685 ) -> Result<Self, ParseError<'i>> {
686 let mut references = References::default();
687 let mut missing_closing_characters = String::new();
688 let start_position = input.position();
689 let (first_token_type, last_token_type) = parse_declaration_value(
690 input,
691 start_position,
692 namespaces,
693 &mut references,
694 &mut missing_closing_characters,
695 )?;
696 let mut css = input
697 .slice_from(start_position)
698 .trim_ascii_start()
699 .to_owned();
700 if !missing_closing_characters.is_empty() {
701 if css.ends_with("\\")
703 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
704 {
705 css.pop();
706 }
707 css.push_str(&missing_closing_characters);
708 }
709
710 css.truncate(css.trim_ascii_end().len());
711 css.shrink_to_fit();
712 references.refs.shrink_to_fit();
713
714 Ok(Self {
715 css,
716 url_data: url_data.clone(),
717 first_token_type,
718 last_token_type,
719 references,
720 })
721 }
722
723 pub fn is_tainted_by_attr(&self) -> bool {
725 self.references.any_attr
726 }
727
728 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
730 Self::from_token(
731 Token::Number {
732 has_sign: false,
733 value: number as f32,
734 int_value: Some(number),
735 },
736 url_data,
737 )
738 }
739
740 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
742 Self::from_token(Token::Ident(ident.into()), url_data)
743 }
744
745 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
747 Self::from_token(
751 Token::Dimension {
752 has_sign: false,
753 value: number,
754 int_value: None,
755 unit: CowRcStr::from("px"),
756 },
757 url_data,
758 )
759 }
760
761 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
763 Self::from_token(
764 Token::Dimension {
765 has_sign: false,
766 value: number as f32,
767 int_value: Some(number),
768 unit: CowRcStr::from("ms"),
769 },
770 url_data,
771 )
772 }
773
774 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
776 Self::from_token(
777 Token::Dimension {
778 has_sign: false,
779 value: number as f32,
780 int_value: Some(number),
781 unit: CowRcStr::from("px"),
782 },
783 url_data,
784 )
785 }
786
787 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
788 let token_type = token.serialization_type();
789 let mut css = token.to_css_string();
790 css.shrink_to_fit();
791
792 VariableValue {
793 css,
794 url_data: url_data.clone(),
795 first_token_type: token_type,
796 last_token_type: token_type,
797 references: Default::default(),
798 }
799 }
800
801 pub fn css_text(&self) -> &str {
803 &self.css
804 }
805
806 pub fn has_references(&self) -> bool {
809 self.references.has_references()
810 }
811}
812
813fn parse_declaration_value<'i, 't>(
815 input: &mut Parser<'i, 't>,
816 input_start: SourcePosition,
817 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
818 references: &mut References,
819 missing_closing_characters: &mut String,
820) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
821 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
822 parse_declaration_value_block(
823 input,
824 input_start,
825 namespaces,
826 references,
827 missing_closing_characters,
828 )
829 })
830}
831
832fn parse_declaration_value_block<'i, 't>(
834 input: &mut Parser<'i, 't>,
835 input_start: SourcePosition,
836 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
837 references: &mut References,
838 missing_closing_characters: &mut String,
839) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
840 let mut is_first = true;
841 let mut first_token_type = TokenSerializationType::Nothing;
842 let mut last_token_type = TokenSerializationType::Nothing;
843 let mut prev_reference_index: Option<usize> = None;
844 loop {
845 let token_start = input.position();
846 let Ok(token) = input.next_including_whitespace_and_comments() else {
847 break;
848 };
849
850 let prev_token_type = last_token_type;
851 let serialization_type = token.serialization_type();
852 last_token_type = serialization_type;
853 if is_first {
854 first_token_type = last_token_type;
855 is_first = false;
856 }
857
858 macro_rules! nested {
859 ($closing:expr) => {{
860 let mut inner_end_position = None;
861 let result = input.parse_nested_block(|input| {
862 let result = parse_declaration_value_block(
863 input,
864 input_start,
865 namespaces,
866 references,
867 missing_closing_characters,
868 )?;
869 inner_end_position = Some(input.position());
870 Ok(result)
871 })?;
872 if inner_end_position.unwrap() == input.position() {
873 missing_closing_characters.push_str($closing);
874 }
875 result
876 }};
877 }
878 if let Some(index) = prev_reference_index.take() {
879 references.refs[index].next_token_type = serialization_type;
880 }
881 match *token {
882 Token::Comment(_) => {
883 let token_slice = input.slice_from(token_start);
884 if !token_slice.ends_with("*/") {
885 missing_closing_characters.push_str(if token_slice.ends_with('*') {
886 "/"
887 } else {
888 "*/"
889 })
890 }
891 },
892 Token::BadUrl(ref u) => {
893 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
894 return Err(input.new_custom_error(e));
895 },
896 Token::BadString(ref s) => {
897 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
898 return Err(input.new_custom_error(e));
899 },
900 Token::CloseParenthesis => {
901 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
902 return Err(input.new_custom_error(e));
903 },
904 Token::CloseSquareBracket => {
905 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
906 return Err(input.new_custom_error(e));
907 },
908 Token::CloseCurlyBracket => {
909 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
910 return Err(input.new_custom_error(e));
911 },
912 Token::Function(ref name) => {
913 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
914 Some(SubstitutionFunctionKind::Attr) => {
915 if static_prefs::pref!("layout.css.attr.enabled") {
916 Some(SubstitutionFunctionKind::Attr)
917 } else {
918 None
919 }
920 },
921 kind => kind,
922 };
923 if let Some(substitution_kind) = substitution_kind {
924 let our_ref_index = references.refs.len();
925 let mut input_end_position = None;
926 let fallback = input.parse_nested_block(|input| {
927 let mut namespace = ParsedNamespace::Known(Namespace::default());
928 if substitution_kind == SubstitutionFunctionKind::Attr {
929 if let Some(namespaces) = namespaces {
930 if let Ok(ns) = input
931 .try_parse(|input| ParsedNamespace::parse(namespaces, input))
932 {
933 namespace = ns;
934 }
935 }
936 }
937 let name = input.expect_ident()?;
940 let name =
941 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
942 match parse_name(name.as_ref()) {
943 Ok(name) => name,
944 Err(()) => {
945 let name = name.clone();
946 return Err(input.new_custom_error(
947 SelectorParseErrorKind::UnexpectedIdent(name),
948 ));
949 },
950 }
951 } else {
952 name.as_ref()
953 });
954
955 let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
956 {
957 parse_attr_type(input)
958 } else {
959 AttributeType::None
960 };
961
962 let start = token_start.byte_index() - input_start.byte_index();
966 references.refs.push(SubstitutionFunctionReference {
967 name,
968 start,
969 end: start,
971 prev_token_type,
972 next_token_type: TokenSerializationType::Nothing,
974 fallback: None,
976 attribute_data: AttributeData {
977 kind: attribute_kind,
978 namespace,
979 },
980 substitution_kind: substitution_kind.clone(),
981 });
982
983 let mut fallback = None;
984 if input.try_parse(|input| input.expect_comma()).is_ok() {
985 input.skip_whitespace();
986 let fallback_start = num::NonZeroUsize::new(
987 input.position().byte_index() - input_start.byte_index(),
988 )
989 .unwrap();
990 let (first, last) = parse_declaration_value(
993 input,
994 input_start,
995 namespaces,
996 references,
997 missing_closing_characters,
998 )?;
999 fallback = Some(VariableFallback {
1000 start: fallback_start,
1001 first_token_type: first,
1002 last_token_type: last,
1003 });
1004 input_end_position = Some(input.position());
1005 } else {
1006 let state = input.state();
1007 parse_declaration_value_block(
1011 input,
1012 input_start,
1013 namespaces,
1014 references,
1015 missing_closing_characters,
1016 )?;
1017 input_end_position = Some(input.position());
1018 input.reset(&state);
1019 }
1020 Ok(fallback)
1021 })?;
1022 if input_end_position.unwrap() == input.position() {
1023 missing_closing_characters.push_str(")");
1024 }
1025 prev_reference_index = Some(our_ref_index);
1026 let reference = &mut references.refs[our_ref_index];
1027 reference.end = input.position().byte_index() - input_start.byte_index()
1028 + missing_closing_characters.len();
1029 reference.fallback = fallback;
1030 match substitution_kind {
1031 SubstitutionFunctionKind::Var => references.any_var = true,
1032 SubstitutionFunctionKind::Env => references.any_env = true,
1033 SubstitutionFunctionKind::Attr => references.any_attr = true,
1034 };
1035 } else {
1036 nested!(")");
1037 }
1038 },
1039 Token::ParenthesisBlock => {
1040 nested!(")");
1041 },
1042 Token::CurlyBracketBlock => {
1043 nested!("}");
1044 },
1045 Token::SquareBracketBlock => {
1046 nested!("]");
1047 },
1048 Token::QuotedString(_) => {
1049 let token_slice = input.slice_from(token_start);
1050 let quote = &token_slice[..1];
1051 debug_assert!(matches!(quote, "\"" | "'"));
1052 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1053 missing_closing_characters.push_str(quote)
1054 }
1055 },
1056 Token::Ident(ref value)
1057 | Token::AtKeyword(ref value)
1058 | Token::Hash(ref value)
1059 | Token::IDHash(ref value)
1060 | Token::UnquotedUrl(ref value)
1061 | Token::Dimension {
1062 unit: ref value, ..
1063 } => {
1064 references
1065 .non_custom_references
1066 .insert(NonCustomReferences::from_unit(value));
1067 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1068 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1069 missing_closing_characters.push_str("�")
1074 }
1075 if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1076 missing_closing_characters.push_str(")");
1077 }
1078 },
1079 _ => {},
1080 };
1081 }
1082 Ok((first_token_type, last_token_type))
1083}
1084
1085fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1088 input
1089 .try_parse(|input| {
1090 Ok(match input.next()? {
1091 Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1092 AttributeType::Type(
1093 input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1094 )
1095 },
1096 Token::Ident(ref ident) => {
1097 if ident.eq_ignore_ascii_case("raw-string") {
1098 AttributeType::RawString
1099 } else {
1100 let unit = AttrUnit::from_ident(ident).map_err(|_| {
1101 input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
1102 })?;
1103 AttributeType::Unit(unit)
1104 }
1105 },
1106 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1107 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1108 })
1109 })
1110 .unwrap_or(AttributeType::None)
1111}
1112
1113fn parse_attribute_value(
1116 name: &Atom,
1117 attribute_data: &AttributeData,
1118 url_data: &UrlExtraData,
1119 attribute_tracker: &mut AttributeTracker,
1120) -> Result<ComputedRegisteredValue, ()> {
1121 #[cfg(feature = "gecko")]
1122 let local_name = LocalName::cast(name);
1123 #[cfg(feature = "servo")]
1124 let local_name = &LocalName::from(name.as_ref());
1125 let namespace = match attribute_data.namespace {
1126 ParsedNamespace::Known(ref ns) => ns,
1127 ParsedNamespace::Unknown => return Err(()),
1128 };
1129 let attr = attribute_tracker.query(local_name, namespace).ok_or(())?;
1130 let mut input = ParserInput::new(&attr);
1131 let mut parser = Parser::new(&mut input);
1132 let value = VariableValue::parse(&mut parser, None, &url_data).map_err(|_| ())?;
1134 Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1135}
1136
1137#[derive(Default)]
1138struct SeenSubstitutionFunctions<'a> {
1139 var: PrecomputedHashSet<&'a Name>,
1140 attr: PrecomputedHashSet<&'a Name>,
1141}
1142
1143pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
1145 seen: SeenSubstitutionFunctions<'a>,
1146 may_have_cycles: bool,
1147 has_color_scheme: bool,
1148 substitution_functions: ComputedSubstitutionFunctions,
1149 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, RevertKind)>,
1150 stylist: &'a Stylist,
1151 computed_context: &'a mut computed::Context<'b>,
1152 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
1153}
1154
1155fn find_non_custom_references(
1156 registration: &PropertyDescriptors,
1157 value: &VariableValue,
1158 may_have_color_scheme: bool,
1159 is_root_element: bool,
1160 include_universal: bool,
1161) -> Option<NonCustomReferences> {
1162 let syntax = registration.syntax.as_ref()?;
1163 let dependent_types = syntax.dependent_types();
1164 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
1165 || (include_universal && syntax.is_universal());
1166 if may_reference_length {
1167 let value_dependencies = value.references.non_custom_references(is_root_element);
1168 if !value_dependencies.is_empty() {
1169 return Some(value_dependencies);
1170 }
1171 }
1172 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
1173 return Some(NonCustomReferences::empty());
1177 }
1178 None
1179}
1180
1181impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
1182 pub fn new_with_properties(
1186 stylist: &'a Stylist,
1187 custom_properties: ComputedCustomProperties,
1188 computed_context: &'a mut computed::Context<'b>,
1189 ) -> Self {
1190 Self {
1191 seen: SeenSubstitutionFunctions::default(),
1192 reverted: Default::default(),
1193 may_have_cycles: false,
1194 has_color_scheme: false,
1195 substitution_functions: ComputedSubstitutionFunctions::new(
1196 Some(custom_properties),
1197 None,
1198 ),
1199 stylist,
1200 computed_context,
1201 references_from_non_custom_properties: NonCustomReferenceMap::default(),
1202 }
1203 }
1204
1205 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1207 let is_root_element = context.is_root_element();
1208
1209 let inherited = context.inherited_custom_properties();
1210 let initial_values = stylist.get_custom_property_initial_values();
1211 let properties = ComputedCustomProperties {
1212 inherited: if is_root_element {
1213 debug_assert!(inherited.is_empty());
1214 initial_values.inherited.clone()
1215 } else {
1216 inherited.inherited.clone()
1217 },
1218 non_inherited: initial_values.non_inherited.clone(),
1219 };
1220
1221 context
1224 .style()
1225 .add_flags(stylist.get_custom_property_initial_values_flags());
1226 Self::new_with_properties(stylist, properties, context)
1227 }
1228
1229 pub fn cascade(
1231 &mut self,
1232 declaration: &'a CustomDeclaration,
1233 priority: CascadePriority,
1234 attribute_tracker: &mut AttributeTracker,
1235 ) {
1236 let CustomDeclaration {
1237 ref name,
1238 ref value,
1239 } = *declaration;
1240
1241 if let Some(&(reverted_priority, revert_kind)) = self.reverted.get(&name) {
1242 if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1243 return;
1244 }
1245 }
1246
1247 if !(priority.flags() - self.computed_context.included_cascade_flags).is_empty() {
1248 return;
1249 }
1250
1251 let was_already_present = !self.seen.var.insert(name);
1252 if was_already_present {
1253 return;
1254 }
1255
1256 if !self.value_may_affect_style(name, value) {
1257 return;
1258 }
1259
1260 let kind = SubstitutionFunctionKind::Var;
1261 let map = &mut self.substitution_functions;
1262 let registration = self.stylist.get_custom_property_registration(&name);
1263 match value {
1264 CustomDeclarationValue::Unparsed(unparsed_value) => {
1265 let may_have_color_scheme = true;
1270 let has_dependency = unparsed_value.references.any_var
1273 || unparsed_value.references.any_attr
1274 || find_non_custom_references(
1275 registration,
1276 unparsed_value,
1277 may_have_color_scheme,
1278 self.computed_context.is_root_element(),
1279 false,
1280 )
1281 .is_some();
1282 if !has_dependency {
1286 return substitute_references_if_needed_and_apply(
1287 name,
1288 kind,
1289 unparsed_value,
1290 &mut self.substitution_functions,
1291 self.stylist,
1292 self.computed_context,
1293 attribute_tracker,
1294 );
1295 }
1296 self.may_have_cycles = true;
1297 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1298 map.insert_var(registration, name, value);
1299 },
1300 CustomDeclarationValue::Parsed(parsed_value) => {
1301 let value = parsed_value.to_computed_value(&self.computed_context);
1302 map.insert_var(registration, name, value);
1303 },
1304 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword.revert_kind() {
1305 Some(revert_kind) => {
1306 self.seen.var.remove(name);
1307 self.reverted.insert(name, (priority, revert_kind));
1308 },
1309 None => match keyword {
1310 CSSWideKeyword::Initial => {
1311 debug_assert!(registration.inherits(), "Should've been handled earlier");
1313 remove_and_insert_initial_value(name, registration, map);
1314 },
1315 CSSWideKeyword::Inherit => {
1316 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1318 if let Some(inherited_value) = self
1319 .computed_context
1320 .inherited_custom_properties()
1321 .non_inherited
1322 .get(name)
1323 {
1324 map.insert_var(registration, name, inherited_value.clone());
1325 }
1326 },
1327 CSSWideKeyword::Revert
1329 | CSSWideKeyword::RevertLayer
1330 | CSSWideKeyword::RevertRule
1331 | CSSWideKeyword::Unset => unreachable!(),
1332 },
1333 },
1334 }
1335 }
1336
1337 #[inline]
1339 pub fn might_have_non_custom_or_attr_dependency(
1340 id: LonghandId,
1341 decl: &PropertyDeclaration,
1342 ) -> bool {
1343 if id == LonghandId::ColorScheme {
1344 return true;
1345 }
1346 if let PropertyDeclaration::WithVariables(v) = decl {
1347 return matches!(id, LonghandId::LineHeight | LonghandId::FontSize)
1348 || v.value.variable_value.references.any_attr;
1349 }
1350 false
1351 }
1352
1353 pub fn maybe_note_non_custom_dependency(
1356 &mut self,
1357 id: LonghandId,
1358 decl: &'a PropertyDeclaration,
1359 attribute_tracker: &mut AttributeTracker,
1360 ) {
1361 debug_assert!(Self::might_have_non_custom_or_attr_dependency(id, decl));
1362 if id == LonghandId::ColorScheme {
1363 self.has_color_scheme = true;
1365 return;
1366 }
1367
1368 let PropertyDeclaration::WithVariables(v) = decl else {
1369 return;
1370 };
1371 let value = &v.value.variable_value;
1372 let refs = &value.references;
1373
1374 if !refs.any_var && !refs.any_attr {
1375 return;
1376 }
1377
1378 if refs.any_attr {
1382 self.update_attributes_map(value, attribute_tracker);
1383 if !refs.any_var {
1384 return;
1385 }
1386 }
1387
1388 let references = match id {
1392 LonghandId::FontSize => {
1393 if self.computed_context.is_root_element() {
1394 NonCustomReferences::ROOT_FONT_UNITS
1395 } else {
1396 NonCustomReferences::FONT_UNITS
1397 }
1398 },
1399 LonghandId::LineHeight => {
1400 if self.computed_context.is_root_element() {
1401 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1402 } else {
1403 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1404 }
1405 },
1406 _ => return,
1407 };
1408
1409 let variables: Vec<Atom> = refs
1410 .refs
1411 .iter()
1412 .filter_map(|reference| {
1413 if reference.substitution_kind != SubstitutionFunctionKind::Var {
1414 return None;
1415 }
1416 let registration = self
1417 .stylist
1418 .get_custom_property_registration(&reference.name);
1419 if !registration
1420 .syntax
1421 .as_ref()?
1422 .dependent_types()
1423 .intersects(DependentDataTypes::LENGTH)
1424 {
1425 return None;
1426 }
1427 Some(reference.name.clone())
1428 })
1429 .collect();
1430 references.for_each(|idx| {
1431 let entry = &mut self.references_from_non_custom_properties[idx];
1432 let was_none = entry.is_none();
1433 let v = entry.get_or_insert_with(|| variables.clone());
1434 if was_none {
1435 return;
1436 }
1437 v.extend(variables.iter().cloned());
1438 });
1439 }
1440
1441 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1442 let registration = self.stylist.get_custom_property_registration(&name);
1443 match *value {
1444 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1445 if registration.inherits() {
1449 return false;
1450 }
1451 },
1452 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1453 if !registration.inherits() {
1456 return false;
1457 }
1458 },
1459 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1460 return false;
1464 },
1465 _ => {},
1466 }
1467
1468 let existing_value = self.substitution_functions.get_var(registration, &name);
1469 let existing_value = match existing_value {
1470 None => {
1471 if matches!(
1472 value,
1473 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1474 ) {
1475 debug_assert!(registration.inherits(), "Should've been handled earlier");
1476 if registration.initial_value.is_none() {
1480 return false;
1481 }
1482 }
1483 return true;
1484 },
1485 Some(v) => v,
1486 };
1487 let computed_value = match value {
1488 CustomDeclarationValue::Unparsed(value) => {
1489 if let Some(existing_value) = existing_value.as_universal() {
1492 return existing_value != value;
1493 }
1494 if !registration.is_universal() {
1495 compute_value(
1496 &value.css,
1497 &value.url_data,
1498 registration,
1499 self.computed_context,
1500 )
1501 .ok()
1502 } else {
1503 None
1504 }
1505 },
1506 CustomDeclarationValue::Parsed(value) => {
1507 Some(value.to_computed_value(&self.computed_context))
1508 },
1509 CustomDeclarationValue::CSSWideKeyword(kw) => {
1510 match kw {
1511 CSSWideKeyword::Inherit => {
1512 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1513 if self
1517 .computed_context
1518 .inherited_custom_properties()
1519 .non_inherited
1520 .get(name)
1521 .is_none()
1522 {
1523 return false;
1524 }
1525 },
1526 CSSWideKeyword::Initial => {
1527 debug_assert!(registration.inherits(), "Should've been handled earlier");
1528 if let Some(initial_value) = self
1531 .stylist
1532 .get_custom_property_initial_values()
1533 .get(registration, name)
1534 {
1535 return existing_value != initial_value;
1536 }
1537 },
1538 CSSWideKeyword::Unset => {
1539 debug_assert!(false, "Should've been handled earlier");
1540 },
1541 CSSWideKeyword::Revert
1542 | CSSWideKeyword::RevertLayer
1543 | CSSWideKeyword::RevertRule => {},
1544 }
1545 None
1546 },
1547 };
1548
1549 if let Some(value) = computed_value {
1550 return existing_value.v != value.v;
1551 }
1552
1553 true
1554 }
1555
1556 pub fn update_attributes_map(
1558 &mut self,
1559 value: &'a VariableValue,
1560 attribute_tracker: &mut AttributeTracker,
1561 ) {
1562 let refs = &value.references;
1563 if !refs.any_attr {
1564 return;
1565 }
1566 self.may_have_cycles = true;
1567
1568 for next in &refs.refs {
1569 if next.substitution_kind != SubstitutionFunctionKind::Attr
1571 || !self.seen.attr.insert(&next.name)
1572 {
1573 continue;
1574 }
1575 if let Ok(v) = parse_attribute_value(
1576 &next.name,
1577 &next.attribute_data,
1578 &value.url_data,
1579 attribute_tracker,
1580 ) {
1581 self.substitution_functions.insert_attr(&next.name, v);
1582 }
1583 }
1584 }
1585
1586 pub fn build(
1602 mut self,
1603 defer: DeferFontRelativeCustomPropertyResolution,
1604 attribute_tracker: &mut AttributeTracker,
1605 ) -> Option<AllSubstitutionFunctions> {
1606 let mut deferred_substitution_functions = None;
1607 if self.may_have_cycles {
1608 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1609 deferred_substitution_functions = Some(AllSubstitutionFunctions::default());
1610 }
1611 let mut invalid_non_custom_properties = LonghandIdSet::default();
1612 substitute_all(
1613 &mut self.substitution_functions,
1614 deferred_substitution_functions.as_mut(),
1615 &mut invalid_non_custom_properties,
1616 self.has_color_scheme,
1617 &self.seen,
1618 &self.references_from_non_custom_properties,
1619 self.stylist,
1620 self.computed_context,
1621 attribute_tracker,
1622 );
1623 self.computed_context.builder.invalid_non_custom_properties =
1624 invalid_non_custom_properties;
1625 }
1626 self.substitution_functions
1627 .custom_properties
1628 .shrink_to_fit();
1629
1630 let initial_values = self.stylist.get_custom_property_initial_values();
1635 let custom_properties = self.substitution_functions.custom_properties;
1636 self.computed_context
1637 .builder
1638 .substitution_functions
1639 .custom_properties = ComputedCustomProperties {
1640 inherited: if self
1641 .computed_context
1642 .inherited_custom_properties()
1643 .inherited
1644 == custom_properties.inherited
1645 {
1646 self.computed_context
1647 .inherited_custom_properties()
1648 .inherited
1649 .clone()
1650 } else {
1651 custom_properties.inherited
1652 },
1653 non_inherited: if initial_values.non_inherited == custom_properties.non_inherited {
1654 initial_values.non_inherited.clone()
1655 } else {
1656 custom_properties.non_inherited
1657 },
1658 };
1659 self.computed_context
1660 .builder
1661 .substitution_functions
1662 .attributes = self.substitution_functions.attributes;
1663
1664 deferred_substitution_functions
1665 }
1666
1667 pub fn build_deferred(
1670 deferred: AllSubstitutionFunctions,
1671 stylist: &Stylist,
1672 computed_context: &mut computed::Context,
1673 attribute_tracker: &mut AttributeTracker,
1674 ) {
1675 if deferred.is_empty() {
1676 return;
1677 }
1678 let mut map = std::mem::take(&mut computed_context.builder.substitution_functions);
1679 for (name, kind, v) in deferred.iter() {
1682 let Some(v) = v.as_universal() else {
1683 unreachable!("Computing should have been deferred!")
1684 };
1685 substitute_references_if_needed_and_apply(
1686 name,
1687 kind,
1688 v,
1689 &mut map,
1690 stylist,
1691 computed_context,
1692 attribute_tracker,
1693 );
1694 }
1695 computed_context.builder.substitution_functions = map;
1696 }
1697}
1698
1699fn substitute_all(
1704 substitution_function_map: &mut ComputedSubstitutionFunctions,
1705 mut deferred_substituted_functions_map: Option<&mut AllSubstitutionFunctions>,
1706 invalid_non_custom_properties: &mut LonghandIdSet,
1707 has_color_scheme: bool,
1708 seen: &SeenSubstitutionFunctions,
1709 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1710 stylist: &Stylist,
1711 computed_context: &computed::Context,
1712 attr_tracker: &mut AttributeTracker,
1713) {
1714 #[derive(Clone, Eq, PartialEq, Debug)]
1721 enum VarType {
1722 Attr(Name),
1723 Custom(Name),
1724 NonCustom(SingleNonCustomReference),
1725 }
1726
1727 #[derive(Debug)]
1729 struct VarInfo {
1730 var: Option<VarType>,
1735 lowlink: usize,
1740 }
1741 struct Context<'a, 'b: 'a> {
1744 count: usize,
1747 index_map: PrecomputedHashMap<Name, usize>,
1749 non_custom_index_map: NonCustomReferenceMap<usize>,
1751 var_info: SmallVec<[VarInfo; 5]>,
1753 stack: SmallVec<[usize; 5]>,
1756 non_custom_references: NonCustomReferences,
1758 has_color_scheme: bool,
1760 contains_computed_custom_property: bool,
1763 map: &'a mut ComputedSubstitutionFunctions,
1764 stylist: &'a Stylist,
1767 computed_context: &'a computed::Context<'b>,
1770 invalid_non_custom_properties: &'a mut LonghandIdSet,
1772 deferred_substitution_functions: Option<&'a mut AllSubstitutionFunctions>,
1776 }
1777
1778 fn traverse<'a, 'b>(
1797 var: VarType,
1798 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1799 context: &mut Context<'a, 'b>,
1800 attribute_tracker: &mut AttributeTracker,
1801 ) -> Option<usize> {
1802 let kind = if matches!(var, VarType::Custom(_)) {
1803 SubstitutionFunctionKind::Var
1804 } else {
1805 SubstitutionFunctionKind::Attr
1806 };
1807 let value = match var {
1809 VarType::Custom(ref name) | VarType::Attr(ref name) => {
1810 let registration;
1811 let value;
1812 match kind {
1813 SubstitutionFunctionKind::Var => {
1814 registration = context.stylist.get_custom_property_registration(name);
1815 value = context.map.get_var(registration, name)?.as_universal()?;
1816 },
1817 SubstitutionFunctionKind::Attr => {
1818 registration = PropertyDescriptors::unregistered();
1821 value = context.map.get_attr(name)?.as_universal()?;
1822 },
1823 _ => unreachable!("Substitution kind must be var or attr for VarType::Custom."),
1824 }
1825 let is_var = kind == SubstitutionFunctionKind::Var;
1826 let is_attr = kind == SubstitutionFunctionKind::Attr;
1827 let is_root = context.computed_context.is_root_element();
1828 let non_custom_refs = find_non_custom_references(
1831 registration,
1832 value,
1833 context.has_color_scheme,
1834 is_root,
1835 true,
1836 );
1837 context.non_custom_references |= non_custom_refs.unwrap_or_default();
1838 let has_dependency = value.references.any_var
1839 || value.references.any_attr
1840 || non_custom_refs.is_some();
1841 if !has_dependency {
1843 debug_assert!(!value.references.any_env, "Should've been handled earlier");
1844 if is_attr || !registration.is_universal() {
1845 if is_var {
1850 debug_assert!(
1851 registration
1852 .syntax
1853 .as_ref()
1854 .unwrap()
1855 .dependent_types()
1856 .intersects(DependentDataTypes::COLOR),
1857 "How did an unresolved value get here otherwise?",
1858 );
1859 }
1860 let value = value.clone();
1861 substitute_references_if_needed_and_apply(
1862 name,
1863 kind,
1864 &value,
1865 &mut context.map,
1866 context.stylist,
1867 context.computed_context,
1868 attribute_tracker,
1869 );
1870 }
1871 return None;
1872 }
1873
1874 match context.index_map.entry(name.clone()) {
1879 Entry::Occupied(entry) => {
1880 return Some(*entry.get());
1881 },
1882 Entry::Vacant(entry) => {
1883 entry.insert(context.count);
1884 },
1885 }
1886 context.contains_computed_custom_property |= is_var && !registration.is_universal();
1887
1888 Some(value.clone())
1891 },
1892 VarType::NonCustom(ref non_custom) => {
1893 let entry = &mut context.non_custom_index_map[*non_custom];
1894 if let Some(v) = entry {
1895 return Some(*v);
1896 }
1897 *entry = Some(context.count);
1898 None
1899 },
1900 };
1901
1902 let index = context.count;
1904 context.count += 1;
1905 debug_assert_eq!(index, context.var_info.len());
1906 context.var_info.push(VarInfo {
1907 var: Some(var.clone()),
1908 lowlink: index,
1909 });
1910 context.stack.push(index);
1911
1912 let mut self_ref = false;
1913 let mut lowlink = index;
1914 let visit_link = |var: VarType,
1915 context: &mut Context,
1916 lowlink: &mut usize,
1917 self_ref: &mut bool,
1918 attr_tracker: &mut AttributeTracker| {
1919 let next_index = match traverse(var, non_custom_references, context, attr_tracker) {
1920 Some(index) => index,
1921 None => {
1924 return;
1925 },
1926 };
1927 let next_info = &context.var_info[next_index];
1928 if next_index > index {
1929 *lowlink = cmp::min(*lowlink, next_info.lowlink);
1933 } else if next_index == index {
1934 *self_ref = true;
1935 } else if next_info.var.is_some() {
1936 *lowlink = cmp::min(*lowlink, next_index);
1939 }
1940 };
1941 if let Some(ref v) = value.as_ref() {
1942 debug_assert!(
1943 matches!(var, VarType::Custom(_) | VarType::Attr(_)),
1944 "Non-custom property has references?"
1945 );
1946
1947 for next in &v.references.refs {
1950 if next.substitution_kind == SubstitutionFunctionKind::Env {
1951 continue;
1952 }
1953
1954 let next_var = if next.substitution_kind == SubstitutionFunctionKind::Attr {
1955 if context.map.get_attr(&next.name).is_none() {
1956 let Ok(val) = parse_attribute_value(
1957 &next.name,
1958 &next.attribute_data,
1959 &v.url_data,
1960 attribute_tracker,
1961 ) else {
1962 continue;
1963 };
1964 context.map.insert_attr(&next.name, val);
1965 }
1966 VarType::Attr(next.name.clone())
1967 } else {
1968 VarType::Custom(next.name.clone())
1969 };
1970
1971 visit_link(
1972 next_var,
1973 context,
1974 &mut lowlink,
1975 &mut self_ref,
1976 attribute_tracker,
1977 );
1978 }
1979
1980 v.references.non_custom_references.for_each(|r| {
1982 visit_link(
1983 VarType::NonCustom(r),
1984 context,
1985 &mut lowlink,
1986 &mut self_ref,
1987 attribute_tracker,
1988 );
1989 });
1990 } else if let VarType::NonCustom(non_custom) = var {
1991 let entry = &non_custom_references[non_custom];
1992 if let Some(deps) = entry.as_ref() {
1993 for d in deps {
1994 visit_link(
1997 VarType::Custom(d.clone()),
1998 context,
1999 &mut lowlink,
2000 &mut self_ref,
2001 attribute_tracker,
2002 );
2003 }
2004 }
2005 }
2006
2007 context.var_info[index].lowlink = lowlink;
2008 if lowlink != index {
2009 return Some(index);
2017 }
2018
2019 let mut in_loop = self_ref;
2021 let name;
2022
2023 let handle_variable_in_loop =
2024 |name: &Name, context: &mut Context<'a, 'b>, kind: SubstitutionFunctionKind| {
2025 if context.contains_computed_custom_property {
2026 if context.non_custom_references.intersects(
2029 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
2030 ) {
2031 context
2032 .invalid_non_custom_properties
2033 .insert(LonghandId::FontSize);
2034 }
2035 if context.non_custom_references.intersects(
2036 NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS,
2037 ) {
2038 context
2039 .invalid_non_custom_properties
2040 .insert(LonghandId::LineHeight);
2041 }
2042 }
2043 handle_invalid_at_computed_value_time(
2045 name,
2046 kind,
2047 &mut context.map,
2048 context.computed_context,
2049 );
2050 };
2051 loop {
2052 let var_index = context
2053 .stack
2054 .pop()
2055 .expect("The current variable should still be in stack");
2056 let var_info = &mut context.var_info[var_index];
2057 let var_name = var_info
2061 .var
2062 .take()
2063 .expect("Variable should not be poped from stack twice");
2064 if var_index == index {
2065 name = match var_name {
2066 VarType::Custom(name) | VarType::Attr(name) => name,
2067 VarType::NonCustom(..) => return None,
2071 };
2072 break;
2073 }
2074 if let VarType::Custom(name) | VarType::Attr(name) = var_name {
2075 handle_variable_in_loop(&name, context, kind);
2079 }
2080 in_loop = true;
2081 }
2082 if in_loop {
2087 handle_variable_in_loop(&name, context, kind);
2088 context.non_custom_references = NonCustomReferences::default();
2089 return None;
2090 }
2091
2092 if let Some(ref v) = value {
2093 let registration = context.stylist.get_custom_property_registration(&name);
2094
2095 let mut defer = false;
2096 if let Some(ref mut deferred) = context.deferred_substitution_functions {
2097 defer = find_non_custom_references(
2100 registration,
2101 v,
2102 context.has_color_scheme,
2103 context.computed_context.is_root_element(),
2104 false,
2105 )
2106 .is_some()
2107 || v.references.refs.iter().any(|reference| {
2108 (reference.substitution_kind == SubstitutionFunctionKind::Var
2109 && deferred
2110 .get(&reference.name, SubstitutionFunctionKind::Var)
2111 .is_some())
2112 || reference.substitution_kind == SubstitutionFunctionKind::Attr
2113 });
2114 if defer {
2115 let value = ComputedRegisteredValue::universal(Arc::clone(v));
2116 deferred.insert(&name, kind, value);
2117 if kind == SubstitutionFunctionKind::Var {
2118 context.map.remove_var(registration, &name);
2119 } else {
2120 context.map.remove_attr(&name);
2121 }
2122 }
2123 }
2124
2125 if !defer && (v.references.any_var || v.references.any_attr) {
2127 substitute_references_if_needed_and_apply(
2128 &name,
2129 kind,
2130 v,
2131 &mut context.map,
2132 context.stylist,
2133 context.computed_context,
2134 attribute_tracker,
2135 );
2136 }
2137 }
2138 context.non_custom_references = NonCustomReferences::default();
2139
2140 None
2142 }
2143
2144 let mut run = |make_var: fn(Name) -> VarType, seen: &PrecomputedHashSet<&Name>| {
2145 for name in seen {
2146 let mut context = Context {
2147 count: 0,
2148 index_map: PrecomputedHashMap::default(),
2149 non_custom_index_map: NonCustomReferenceMap::default(),
2150 stack: SmallVec::new(),
2151 var_info: SmallVec::new(),
2152 map: substitution_function_map,
2153 non_custom_references: NonCustomReferences::default(),
2154 has_color_scheme,
2155 stylist,
2156 computed_context,
2157 invalid_non_custom_properties,
2158 deferred_substitution_functions: deferred_substituted_functions_map.as_deref_mut(),
2159 contains_computed_custom_property: false,
2160 };
2161
2162 traverse(
2163 make_var((*name).clone()),
2164 references_from_non_custom_properties,
2165 &mut context,
2166 attr_tracker,
2167 );
2168 }
2169 };
2170
2171 run(VarType::Custom, &seen.var);
2175 run(VarType::Attr, &seen.attr);
2178}
2179
2180fn handle_invalid_at_computed_value_time(
2182 name: &Name,
2183 kind: SubstitutionFunctionKind,
2184 substitution_functions: &mut ComputedSubstitutionFunctions,
2185 computed_context: &computed::Context,
2186) {
2187 if kind == SubstitutionFunctionKind::Attr {
2188 substitution_functions.remove_attr(name);
2190 return;
2191 }
2192
2193 let stylist = computed_context.style().stylist.unwrap();
2194 let registration = stylist.get_custom_property_registration(&name);
2195 if !registration.is_universal() {
2196 if registration.inherits() && !computed_context.is_root_element() {
2199 let inherited = computed_context.inherited_custom_properties();
2200 if let Some(value) = inherited.get(registration, name) {
2201 substitution_functions.insert_var(registration, name, value.clone());
2202 return;
2203 }
2204 } else if let Some(ref initial_value) = registration.initial_value {
2205 if let Ok(initial_value) = compute_value(
2206 &initial_value.css,
2207 &initial_value.url_data,
2208 registration,
2209 computed_context,
2210 ) {
2211 substitution_functions.insert_var(registration, name, initial_value);
2212 return;
2213 }
2214 }
2215 }
2216 substitution_functions.remove_var(registration, name);
2217}
2218
2219fn substitute_references_if_needed_and_apply(
2221 name: &Name,
2222 kind: SubstitutionFunctionKind,
2223 value: &Arc<VariableValue>,
2224 substitution_functions: &mut ComputedSubstitutionFunctions,
2225 stylist: &Stylist,
2226 computed_context: &computed::Context,
2227 attribute_tracker: &mut AttributeTracker,
2228) {
2229 debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
2230 let is_var = kind == SubstitutionFunctionKind::Var;
2231 let registration = stylist.get_custom_property_registration(&name);
2232 if is_var && !value.has_references() && registration.is_universal() {
2233 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
2235 substitution_functions.insert_var(registration, name, computed_value);
2236 return;
2237 }
2238
2239 let inherited = computed_context.inherited_custom_properties();
2240 let url_data = &value.url_data;
2241 let substitution = match substitute_internal(
2242 value,
2243 substitution_functions,
2244 stylist,
2245 computed_context,
2246 attribute_tracker,
2247 ) {
2248 Ok(v) => v,
2249 Err(..) => {
2250 handle_invalid_at_computed_value_time(
2251 name,
2252 kind,
2253 substitution_functions,
2254 computed_context,
2255 );
2256 return;
2257 },
2258 };
2259
2260 {
2263 let css = &substitution.css;
2264 let css_wide_kw = {
2265 let mut input = ParserInput::new(&css);
2266 let mut input = Parser::new(&mut input);
2267 input.try_parse(CSSWideKeyword::parse)
2268 };
2269
2270 if let Ok(kw) = css_wide_kw {
2271 match (
2275 kw,
2276 registration.inherits(),
2277 computed_context.is_root_element(),
2278 ) {
2279 (CSSWideKeyword::Initial, _, _)
2280 | (CSSWideKeyword::Revert, false, _)
2281 | (CSSWideKeyword::RevertLayer, false, _)
2282 | (CSSWideKeyword::RevertRule, false, _)
2283 | (CSSWideKeyword::Unset, false, _)
2284 | (CSSWideKeyword::Revert, true, true)
2285 | (CSSWideKeyword::RevertLayer, true, true)
2286 | (CSSWideKeyword::RevertRule, true, true)
2287 | (CSSWideKeyword::Unset, true, true)
2288 | (CSSWideKeyword::Inherit, _, true) => {
2289 remove_and_insert_initial_value(name, registration, substitution_functions);
2290 },
2291 (CSSWideKeyword::Revert, true, false)
2292 | (CSSWideKeyword::RevertLayer, true, false)
2293 | (CSSWideKeyword::RevertRule, true, false)
2294 | (CSSWideKeyword::Inherit, _, false)
2295 | (CSSWideKeyword::Unset, true, false) => {
2296 match inherited.get(registration, name) {
2297 Some(value) => {
2298 substitution_functions.insert_var(registration, name, value.clone());
2299 },
2300 None => {
2301 substitution_functions.remove_var(registration, name);
2302 },
2303 };
2304 },
2305 }
2306 return;
2307 }
2308 }
2309
2310 match kind {
2311 SubstitutionFunctionKind::Var => {
2312 let value = match substitution.into_value(url_data, registration, computed_context) {
2313 Ok(v) => v,
2314 Err(()) => {
2315 handle_invalid_at_computed_value_time(
2316 name,
2317 kind,
2318 substitution_functions,
2319 computed_context,
2320 );
2321 return;
2322 },
2323 };
2324 substitution_functions.insert_var(registration, name, value);
2325 },
2326 SubstitutionFunctionKind::Attr => {
2327 let value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2328 substitution.css.into_owned(),
2329 url_data,
2330 substitution.first_token_type,
2331 substitution.last_token_type,
2332 substitution.attribute_tainted,
2333 )));
2334 substitution_functions.insert_attr(name, value);
2335 },
2336 SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
2337 }
2338}
2339
2340#[derive(Default)]
2341struct Substitution<'a> {
2342 css: Cow<'a, str>,
2343 first_token_type: TokenSerializationType,
2344 last_token_type: TokenSerializationType,
2345 attribute_tainted: bool,
2346}
2347
2348impl<'a> Substitution<'a> {
2349 fn from_value(v: VariableValue) -> Self {
2350 Substitution {
2351 css: v.css.into(),
2352 first_token_type: v.first_token_type,
2353 last_token_type: v.last_token_type,
2354 attribute_tainted: v.references.any_attr,
2355 }
2356 }
2357
2358 fn into_value(
2359 self,
2360 url_data: &UrlExtraData,
2361 registration: &PropertyDescriptors,
2362 computed_context: &computed::Context,
2363 ) -> Result<ComputedRegisteredValue, ()> {
2364 if registration.is_universal() {
2365 let value = VariableValue::new(
2366 self.css.into_owned(),
2367 url_data,
2368 self.first_token_type,
2369 self.last_token_type,
2370 self.attribute_tainted,
2371 );
2372 return Ok(ComputedRegisteredValue::universal(Arc::new(value)));
2373 }
2374 let mut v = compute_value(&self.css, url_data, registration, computed_context)?;
2375 v.attribute_tainted |= self.attribute_tainted;
2376 Ok(v)
2377 }
2378
2379 fn new(
2380 css: Cow<'a, str>,
2381 first_token_type: TokenSerializationType,
2382 last_token_type: TokenSerializationType,
2383 attribute_tainted: bool,
2384 ) -> Self {
2385 Self {
2386 css,
2387 first_token_type,
2388 last_token_type,
2389 attribute_tainted,
2390 }
2391 }
2392}
2393
2394pub struct SubstitutionResult<'a> {
2396 pub css: Cow<'a, str>,
2398 pub attribute_tainted: bool,
2400}
2401
2402impl<'a> From<Substitution<'a>> for SubstitutionResult<'a> {
2403 fn from(s: Substitution<'a>) -> Self {
2404 Self {
2405 css: s.css,
2406 attribute_tainted: s.attribute_tainted,
2407 }
2408 }
2409}
2410
2411fn compute_value(
2412 css: &str,
2413 url_data: &UrlExtraData,
2414 registration: &PropertyDescriptors,
2415 computed_context: &computed::Context,
2416) -> Result<ComputedRegisteredValue, ()> {
2417 debug_assert!(!registration.is_universal());
2418
2419 let mut input = ParserInput::new(&css);
2420 let mut input = Parser::new(&mut input);
2421
2422 SpecifiedRegisteredValue::compute(
2423 &mut input,
2424 registration,
2425 None,
2426 url_data,
2427 computed_context,
2428 AllowComputationallyDependent::Yes,
2429 )
2430}
2431
2432fn remove_and_insert_initial_value(
2434 name: &Name,
2435 registration: &PropertyDescriptors,
2436 substitution_functions: &mut ComputedSubstitutionFunctions,
2437) {
2438 substitution_functions.remove_var(registration, name);
2439 if let Some(ref initial_value) = registration.initial_value {
2440 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2441 substitution_functions.insert_var(registration, name, value);
2442 }
2443}
2444
2445fn do_substitute_chunk<'a>(
2446 css: &'a str,
2447 start: usize,
2448 end: usize,
2449 first_token_type: TokenSerializationType,
2450 last_token_type: TokenSerializationType,
2451 url_data: &UrlExtraData,
2452 substitution_functions: &'a ComputedSubstitutionFunctions,
2453 stylist: &Stylist,
2454 computed_context: &computed::Context,
2455 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2456 attribute_tracker: &mut AttributeTracker,
2457) -> Result<Substitution<'a>, ()> {
2458 if start == end {
2459 return Ok(Substitution::default());
2461 }
2462 if references
2464 .peek()
2465 .map_or(true, |reference| reference.end > end)
2466 {
2467 let result = &css[start..end];
2468 return Ok(Substitution::new(
2469 Cow::Borrowed(result),
2470 first_token_type,
2471 last_token_type,
2472 Default::default(),
2473 ));
2474 }
2475
2476 let mut substituted = ComputedValue::empty(url_data);
2477 let mut next_token_type = first_token_type;
2478 let mut cur_pos = start;
2479 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2480 if reference.start != cur_pos {
2481 substituted.push(
2482 &css[cur_pos..reference.start],
2483 next_token_type,
2484 reference.prev_token_type,
2485 )?;
2486 }
2487
2488 let substitution = substitute_one_reference(
2489 css,
2490 url_data,
2491 substitution_functions,
2492 reference,
2493 stylist,
2494 computed_context,
2495 references,
2496 attribute_tracker,
2497 )?;
2498
2499 if reference.start == start && reference.end == end {
2501 return Ok(substitution);
2502 }
2503
2504 substituted.push(
2505 &substitution.css,
2506 substitution.first_token_type,
2507 substitution.last_token_type,
2508 )?;
2509 substituted.references.any_attr |= substitution.attribute_tainted;
2510 next_token_type = reference.next_token_type;
2511 cur_pos = reference.end;
2512 }
2513 if cur_pos != end {
2515 substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
2516 }
2517 Ok(Substitution::from_value(substituted))
2518}
2519
2520fn quoted_css_string(src: &str) -> String {
2521 let mut dest = String::with_capacity(src.len() + 2);
2522 cssparser::serialize_string(src, &mut dest).unwrap();
2523 dest
2524}
2525
2526fn substitute_one_reference<'a>(
2527 css: &'a str,
2528 url_data: &UrlExtraData,
2529 substitution_functions: &'a ComputedSubstitutionFunctions,
2530 reference: &SubstitutionFunctionReference,
2531 stylist: &Stylist,
2532 computed_context: &computed::Context,
2533 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2534 attribute_tracker: &mut AttributeTracker,
2535) -> Result<Substitution<'a>, ()> {
2536 let simple_attr_subst = |s: &str| {
2537 Some(Substitution::new(
2538 Cow::Owned(quoted_css_string(s)),
2539 TokenSerializationType::Nothing,
2540 TokenSerializationType::Nothing,
2541 true,
2542 ))
2543 };
2544 let substitution: Option<_> = match reference.substitution_kind {
2545 SubstitutionFunctionKind::Var => {
2546 let registration = stylist.get_custom_property_registration(&reference.name);
2547 substitution_functions
2548 .get_var(registration, &reference.name)
2549 .map(|v| Substitution::from_value(v.to_variable_value()))
2550 },
2551 SubstitutionFunctionKind::Env => {
2552 let device = stylist.device();
2553 device
2554 .environment()
2555 .get(&reference.name, device, url_data)
2556 .map(Substitution::from_value)
2557 },
2558 SubstitutionFunctionKind::Attr => {
2560 #[cfg(feature = "gecko")]
2561 let local_name = LocalName::cast(&reference.name);
2562 #[cfg(feature = "servo")]
2563 let local_name = LocalName::from(reference.name.as_ref());
2564 let namespace = match reference.attribute_data.namespace {
2565 ParsedNamespace::Known(ref ns) => Some(ns),
2566 ParsedNamespace::Unknown => None,
2567 };
2568 namespace
2569 .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
2570 .map_or_else(
2571 || {
2572 if reference.fallback.is_none()
2575 && reference.attribute_data.kind == AttributeType::None
2576 {
2577 simple_attr_subst("")
2578 } else {
2579 None
2580 }
2581 },
2582 |attr| {
2583 let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
2584 substitution_functions
2585 .get_attr(&reference.name)
2586 .map(|v| v.to_variable_value())?
2587 .css
2588 } else {
2589 attr
2590 };
2591 let mut input = ParserInput::new(&attr);
2592 let mut parser = Parser::new(&mut input);
2593 match &reference.attribute_data.kind {
2594 AttributeType::Unit(unit) => {
2595 let css = {
2596 parser.expect_number().ok()?;
2598 let mut s = attr.clone();
2599 s.push_str(unit.as_ref());
2600 s
2601 };
2602 let serialization = match unit {
2603 AttrUnit::Number => TokenSerializationType::Number,
2604 AttrUnit::Percentage => TokenSerializationType::Percentage,
2605 _ => TokenSerializationType::Dimension,
2606 };
2607 let value = ComputedValue::new(
2608 css,
2609 url_data,
2610 serialization,
2611 serialization,
2612 true,
2613 );
2614 Some(Substitution::from_value(value))
2615 },
2616 AttributeType::Type(syntax) => {
2617 let value = SpecifiedRegisteredValue::parse(
2618 &mut parser,
2619 &syntax,
2620 url_data,
2621 None,
2622 AllowComputationallyDependent::Yes,
2623 )
2624 .ok()?;
2625 let mut value = value.to_variable_value();
2626 value.references.any_attr = true;
2627 Some(Substitution::from_value(value))
2628 },
2629 AttributeType::RawString | AttributeType::None => {
2630 simple_attr_subst(&attr)
2631 },
2632 }
2633 },
2634 )
2635 },
2636 };
2637
2638 if let Some(s) = substitution {
2639 while references
2641 .next_if(|next_ref| next_ref.end <= reference.end)
2642 .is_some()
2643 {}
2644 return Ok(s);
2645 }
2646
2647 let Some(ref fallback) = reference.fallback else {
2648 return Err(());
2649 };
2650
2651 do_substitute_chunk(
2652 css,
2653 fallback.start.get(),
2654 reference.end - 1, fallback.first_token_type,
2656 fallback.last_token_type,
2657 url_data,
2658 substitution_functions,
2659 stylist,
2660 computed_context,
2661 references,
2662 attribute_tracker,
2663 )
2664}
2665
2666fn substitute_internal<'a>(
2668 variable_value: &'a VariableValue,
2669 substitution_functions: &'a ComputedSubstitutionFunctions,
2670 stylist: &Stylist,
2671 computed_context: &computed::Context,
2672 attribute_tracker: &mut AttributeTracker,
2673) -> Result<Substitution<'a>, ()> {
2674 let mut refs = variable_value.references.refs.iter().peekable();
2675 do_substitute_chunk(
2676 &variable_value.css,
2677 0,
2678 variable_value.css.len(),
2679 variable_value.first_token_type,
2680 variable_value.last_token_type,
2681 &variable_value.url_data,
2682 substitution_functions,
2683 stylist,
2684 computed_context,
2685 &mut refs,
2686 attribute_tracker,
2687 )
2688}
2689
2690pub fn substitute<'a>(
2692 variable_value: &'a VariableValue,
2693 substitution_functions: &'a ComputedSubstitutionFunctions,
2694 stylist: &Stylist,
2695 computed_context: &computed::Context,
2696 attribute_tracker: &mut AttributeTracker,
2697) -> Result<SubstitutionResult<'a>, ()> {
2698 debug_assert!(variable_value.has_references());
2699 let v = substitute_internal(
2700 variable_value,
2701 substitution_functions,
2702 stylist,
2703 computed_context,
2704 attribute_tracker,
2705 )?;
2706 Ok(v.into())
2707}