1use crate::applicable_declarations::{CascadePriority, RevertKind};
10use crate::computed_value_flags::ComputedValueFlags;
11use crate::custom_properties_map::{AllSubstitutionFunctions, CustomPropertiesMap, OwnMap};
12use crate::device::Device;
13use crate::dom::AttributeTracker;
14use crate::properties::{
15 CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
16 PropertyDeclaration,
17};
18use crate::properties_and_values::{
19 rule::Descriptors as PropertyDescriptors,
20 syntax::{data_type::DependentDataTypes, Descriptor as SyntaxDescriptor},
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::values::specified::ParsedNamespace;
33use crate::{derives::*, Namespace, Prefix};
34use crate::{Atom, LocalName};
35use cssparser::{
36 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
37};
38use rustc_hash::FxHashMap;
39use selectors::parser::SelectorParseErrorKind;
40use servo_arc::Arc;
41use smallvec::SmallVec;
42use std::borrow::Cow;
43use std::collections::hash_map::Entry;
44use std::fmt::{self, Write};
45use std::ops::{Index, IndexMut};
46use std::{cmp, num};
47use style_traits::{
48 CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss, ToTyped, TypedValue,
49 UnparsedSegment, UnparsedValue, VariableReferenceValue,
50};
51use thin_vec::ThinVec;
52
53#[derive(Debug, MallocSizeOf)]
58pub struct CssEnvironment;
59
60type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
61
62struct EnvironmentVariable {
63 name: Atom,
64 evaluator: EnvironmentEvaluator,
65}
66
67macro_rules! make_variable {
68 ($name:expr, $evaluator:expr) => {{
69 EnvironmentVariable {
70 name: $name,
71 evaluator: $evaluator,
72 }
73 }};
74}
75
76fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
77 VariableValue::pixels(device.safe_area_insets().top, url_data)
78}
79
80fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
81 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
82}
83
84fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
85 VariableValue::pixels(device.safe_area_insets().left, url_data)
86}
87
88fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
89 VariableValue::pixels(device.safe_area_insets().right, url_data)
90}
91
92#[cfg(feature = "gecko")]
93fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
94 use crate::queries::values::PrefersColorScheme;
95 let prefers_color_scheme = unsafe {
96 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
97 device.document(),
98 true,
99 )
100 };
101 VariableValue::ident(
102 match prefers_color_scheme {
103 PrefersColorScheme::Light => "light",
104 PrefersColorScheme::Dark => "dark",
105 },
106 url_data,
107 )
108}
109
110#[cfg(feature = "servo")]
111fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
112 VariableValue::ident("light", url_data)
114}
115
116fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
117 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
118}
119
120fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
121 VariableValue::pixels(
122 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
123 url_data,
124 )
125}
126
127static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
128 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
129 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
130 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
131 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
132];
133
134#[cfg(feature = "gecko")]
135macro_rules! lnf_int {
136 ($id:ident) => {
137 unsafe {
138 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
139 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
140 )
141 }
142 };
143}
144
145#[cfg(feature = "servo")]
146macro_rules! lnf_int {
147 ($id:ident) => {
148 0
150 };
151}
152
153macro_rules! lnf_int_variable {
154 ($atom:expr, $id:ident, $ctor:ident) => {{
155 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
156 VariableValue::$ctor(lnf_int!($id), url_data)
157 }
158 make_variable!($atom, __eval)
159 }};
160}
161
162fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
163 let int_pixels = lnf_int!(TitlebarRadius);
164 let unzoomed_scale =
165 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
166 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
167}
168
169static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
170 make_variable!(
171 atom!("-moz-gtk-csd-titlebar-radius"),
172 eval_gtk_csd_titlebar_radius
173 ),
174 lnf_int_variable!(
175 atom!("-moz-gtk-csd-tooltip-radius"),
176 TooltipRadius,
177 int_pixels
178 ),
179 lnf_int_variable!(
180 atom!("-moz-gtk-csd-close-button-position"),
181 GTKCSDCloseButtonPosition,
182 integer
183 ),
184 lnf_int_variable!(
185 atom!("-moz-gtk-csd-minimize-button-position"),
186 GTKCSDMinimizeButtonPosition,
187 integer
188 ),
189 lnf_int_variable!(
190 atom!("-moz-gtk-csd-maximize-button-position"),
191 GTKCSDMaximizeButtonPosition,
192 integer
193 ),
194 lnf_int_variable!(
195 atom!("-moz-overlay-scrollbar-fade-duration"),
196 ScrollbarFadeDuration,
197 int_ms
198 ),
199 make_variable!(
200 atom!("-moz-content-preferred-color-scheme"),
201 get_content_preferred_color_scheme
202 ),
203 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
204 make_variable!(atom!("hairline"), get_hairline),
205];
206
207impl CssEnvironment {
208 #[inline]
209 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
210 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
211 return Some((var.evaluator)(device, url_data));
212 }
213 if !url_data.chrome_rules_enabled() {
214 return None;
215 }
216 let var = CHROME_ENVIRONMENT_VARIABLES
217 .iter()
218 .find(|var| var.name == *name)?;
219 Some((var.evaluator)(device, url_data))
220 }
221}
222
223pub type Name = Atom;
227
228pub fn parse_name(s: &str) -> Result<&str, ()> {
232 if s.starts_with("--") && s.len() > 2 {
233 Ok(&s[2..])
234 } else {
235 Err(())
236 }
237}
238
239#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
244pub struct VariableValue {
245 pub css: String,
247
248 pub url_data: UrlExtraData,
250
251 first_token_type: TokenSerializationType,
252 last_token_type: TokenSerializationType,
253
254 references: References,
256}
257
258trivial_to_computed_value!(VariableValue);
259
260pub fn compute_variable_value(
262 value: &Arc<VariableValue>,
263 registration: &PropertyDescriptors,
264 computed_context: &computed::Context,
265) -> Option<ComputedRegisteredValue> {
266 if registration.is_universal() {
267 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
268 }
269 compute_value(
270 &value.css,
271 &value.url_data,
272 registration,
273 computed_context,
274 AttrTaint::default(),
275 )
276 .ok()
277}
278
279impl PartialEq for VariableValue {
281 fn eq(&self, other: &Self) -> bool {
282 self.css == other.css
283 }
284}
285
286impl Eq for VariableValue {}
287
288impl ToCss for SpecifiedValue {
289 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
290 where
291 W: Write,
292 {
293 dest.write_str(&self.css)
294 }
295}
296
297impl ToTyped for SpecifiedValue {
298 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
299 let unparsed_value = reify_variable_value(self)?;
300 dest.push(TypedValue::Unparsed(unparsed_value));
301 Ok(())
302 }
303}
304
305fn reify_variable_value(value: &VariableValue) -> Result<UnparsedValue, ()> {
306 let mut reference_index = 0;
307 reify_variable_value_range(
308 &value.css,
309 &value.references.refs,
310 &mut reference_index,
311 0,
312 value.css.len(),
313 )
314}
315
316fn reify_variable_value_range(
323 css: &str,
324 references: &[SubstitutionFunctionReference],
325 reference_index: &mut usize,
326 start: usize,
327 end: usize,
328) -> Result<UnparsedValue, ()> {
329 debug_assert!(start <= end);
330 debug_assert!(end <= css.len());
331
332 let mut values = ThinVec::new();
333 let mut cur_pos = start;
334
335 while *reference_index < references.len() {
336 let reference = &references[*reference_index];
337
338 if reference.start >= end {
339 break;
340 }
341
342 debug_assert!(reference.start >= cur_pos);
343 debug_assert!(reference.start <= reference.end);
344 debug_assert!(reference.end <= css.len());
345
346 if cur_pos < reference.start {
347 values.push(UnparsedSegment::String(CssString::from(
348 &css[cur_pos..reference.start],
349 )));
350 }
351
352 *reference_index += 1;
353
354 if reference.substitution_kind != SubstitutionFunctionKind::Var {
355 return Err(());
356 }
357
358 let (fallback, has_fallback) = if let Some(fallback) = &reference.fallback {
359 debug_assert!(fallback.start.get() <= reference.end - 1);
360
361 (
362 reify_variable_value_range(
363 css,
364 references,
365 reference_index,
366 fallback.start.get(),
367 reference.end - 1, )?,
369 true,
370 )
371 } else {
372 (ThinVec::new(), false)
373 };
374
375 values.push(UnparsedSegment::VariableReference(VariableReferenceValue {
376 variable: CssString::from(format!("--{}", reference.name)),
377 fallback,
378 has_fallback,
379 }));
380
381 cur_pos = reference.end;
382 }
383
384 if cur_pos < end {
385 values.push(UnparsedSegment::String(CssString::from(&css[cur_pos..end])));
386 }
387
388 Ok(values)
389}
390
391#[repr(C)]
394#[derive(Clone, Debug, Default, PartialEq)]
395pub struct ComputedCustomProperties {
396 pub inherited: CustomPropertiesMap,
399 pub non_inherited: CustomPropertiesMap,
401}
402
403impl ComputedCustomProperties {
404 pub fn is_empty(&self) -> bool {
406 self.inherited.is_empty() && self.non_inherited.is_empty()
407 }
408
409 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
411 self.inherited
414 .get_index(index)
415 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
416 }
417
418 pub(crate) fn insert(
421 &mut self,
422 registration: &PropertyDescriptors,
423 name: &Name,
424 value: ComputedRegisteredValue,
425 ) {
426 self.map_mut(registration).insert(name, value)
427 }
428
429 pub(crate) fn remove(&mut self, registration: &PropertyDescriptors, name: &Name) {
432 self.map_mut(registration).remove(name);
433 }
434
435 fn shrink_to_fit(&mut self) {
437 self.inherited.shrink_to_fit();
438 self.non_inherited.shrink_to_fit();
439 }
440
441 fn map_mut(&mut self, registration: &PropertyDescriptors) -> &mut CustomPropertiesMap {
442 if registration.inherits() {
443 &mut self.inherited
444 } else {
445 &mut self.non_inherited
446 }
447 }
448
449 pub fn get(
451 &self,
452 registration: &PropertyDescriptors,
453 name: &Name,
454 ) -> Option<&ComputedRegisteredValue> {
455 if registration.inherits() {
456 self.inherited.get(name)
457 } else {
458 self.non_inherited.get(name)
459 }
460 }
461}
462
463pub type SpecifiedValue = VariableValue;
466pub type ComputedValue = VariableValue;
469
470#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
472struct NonCustomReferences(u8);
473
474bitflags! {
475 impl NonCustomReferences: u8 {
476 const FONT_UNITS = 1 << 0;
478 const ROOT_FONT_UNITS = 1 << 1;
480 const LH_UNITS = 1 << 2;
482 const ROOT_LH_UNITS = 1 << 3;
484 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
486 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
488 }
489}
490
491impl NonCustomReferences {
492 fn for_each<F>(&self, mut f: F)
493 where
494 F: FnMut(SingleNonCustomReference),
495 {
496 for (_, r) in self.iter_names() {
497 let single = match r {
498 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
499 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
500 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
501 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
502 _ => unreachable!("Unexpected single bit value"),
503 };
504 f(single);
505 }
506 }
507
508 fn from_unit(value: &CowRcStr) -> Self {
509 if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
514 return Self::FONT_UNITS | Self::LH_UNITS;
515 }
516 if value.eq_ignore_ascii_case(FontRelativeLength::EM)
517 || value.eq_ignore_ascii_case(FontRelativeLength::EX)
518 || value.eq_ignore_ascii_case(FontRelativeLength::CAP)
519 || value.eq_ignore_ascii_case(FontRelativeLength::CH)
520 || value.eq_ignore_ascii_case(FontRelativeLength::IC)
521 {
522 return Self::FONT_UNITS;
523 }
524 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
525 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
526 }
527 if value.eq_ignore_ascii_case(FontRelativeLength::REM)
528 || value.eq_ignore_ascii_case(FontRelativeLength::REX)
529 || value.eq_ignore_ascii_case(FontRelativeLength::RCH)
530 || value.eq_ignore_ascii_case(FontRelativeLength::RCAP)
531 || value.eq_ignore_ascii_case(FontRelativeLength::RIC)
532 {
533 return Self::ROOT_FONT_UNITS;
534 }
535 Self::empty()
536 }
537}
538
539#[derive(Clone, Copy, Debug, Eq, PartialEq)]
540enum SingleNonCustomReference {
541 FontUnits = 0,
542 RootFontUnits,
543 LhUnits,
544 RootLhUnits,
545}
546
547struct NonCustomReferenceMap<T>([Option<T>; 4]);
548
549impl<T> Default for NonCustomReferenceMap<T> {
550 fn default() -> Self {
551 NonCustomReferenceMap(Default::default())
552 }
553}
554
555impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
556 type Output = Option<T>;
557
558 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
559 &self.0[reference as usize]
560 }
561}
562
563impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
564 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
565 &mut self.0[reference as usize]
566 }
567}
568
569#[derive(Clone, Copy, PartialEq, Eq)]
571#[allow(missing_docs)]
572pub enum DeferFontRelativeCustomPropertyResolution {
573 Yes,
574 No,
575}
576
577#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse)]
579pub enum SubstitutionFunctionKind {
580 Var,
582 Env,
584 Attr,
586}
587
588#[repr(C)]
591#[derive(Clone, Debug, Default, PartialEq)]
592pub struct ComputedSubstitutionFunctions {
593 pub custom_properties: ComputedCustomProperties,
595 pub attributes: OwnMap,
597}
598
599impl ComputedSubstitutionFunctions {
600 #[inline(always)]
603 pub fn new(
604 custom_properties: Option<ComputedCustomProperties>,
605 attributes: Option<OwnMap>,
606 ) -> Self {
607 Self {
608 custom_properties: custom_properties.unwrap_or_default(),
609 attributes: attributes.unwrap_or_default(),
610 }
611 }
612
613 #[inline(always)]
614 fn insert_var(
615 &mut self,
616 registration: &PropertyDescriptors,
617 name: &Name,
618 value: ComputedRegisteredValue,
619 ) {
620 self.custom_properties.insert(registration, name, value);
621 }
622
623 #[inline(always)]
624 fn insert_attr(&mut self, name: &Name, value: ComputedRegisteredValue) {
625 self.attributes.insert(name.clone(), Some(value));
626 }
627
628 #[inline(always)]
629 fn remove_var(&mut self, registration: &PropertyDescriptors, name: &Name) {
630 self.custom_properties.remove(registration, name);
631 }
632
633 #[inline(always)]
634 fn remove_attr(&mut self, name: &Name) {
635 self.attributes.insert(name.clone(), None);
636 }
637
638 #[inline(always)]
639 fn get_var(
640 &self,
641 registration: &PropertyDescriptors,
642 name: &Name,
643 ) -> Option<&ComputedRegisteredValue> {
644 self.custom_properties.get(registration, name)
645 }
646
647 #[inline(always)]
648 fn get_attr(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
649 self.attributes.get(name).and_then(|p| p.as_ref())
650 }
651}
652
653#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
654enum AttributeType {
655 None,
656 RawString,
657 Type(SyntaxDescriptor),
658 Unit(AttrUnit),
659}
660
661#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
662struct AttributeData {
663 kind: AttributeType,
664 namespace: ParsedNamespace,
665}
666
667#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
669pub struct AttrTaintedRange {
670 start: usize,
672 end: usize,
674}
675
676impl AttrTaintedRange {
677 #[inline(always)]
679 pub fn new(start: usize, end: usize) -> Self {
680 debug_assert!(start <= end);
681 Self { start, end }
682 }
683}
684
685#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
690pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
691
692impl AttrTaint {
693 #[inline(always)]
696 pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
697 self.0
698 .iter()
699 .any(|r| r.start <= range.end && r.end >= range.start)
700 }
701
702 #[inline(always)]
704 pub fn is_empty(&self) -> bool {
705 self.0.is_empty()
706 }
707
708 #[inline(always)]
709 fn new_fully_tainted(end: usize) -> Self {
710 let mut taint = Self::default();
711 taint.push(0, end);
712 taint
713 }
714
715 #[inline(always)]
716 fn push(&mut self, start: usize, end: usize) {
717 self.0.push(AttrTaintedRange::new(start, end));
718 }
719}
720
721#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
722struct VariableFallback {
723 start: num::NonZeroUsize,
727 first_token_type: TokenSerializationType,
728 last_token_type: TokenSerializationType,
729}
730
731#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
732struct SubstitutionFunctionReference {
733 name: Name,
734 start: usize,
735 end: usize,
736 fallback: Option<VariableFallback>,
737 attribute_data: AttributeData,
738 prev_token_type: TokenSerializationType,
739 next_token_type: TokenSerializationType,
740 substitution_kind: SubstitutionFunctionKind,
741}
742
743#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
746struct References {
747 refs: Vec<SubstitutionFunctionReference>,
748 non_custom_references: NonCustomReferences,
749 any_env: bool,
750 any_var: bool,
751 any_attr: bool,
752}
753
754impl References {
755 fn has_references(&self) -> bool {
756 !self.refs.is_empty()
757 }
758
759 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
760 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
761 if is_root_element {
762 mask |= NonCustomReferences::ROOT_DEPENDENCIES
763 }
764 self.non_custom_references & mask
765 }
766}
767
768impl VariableValue {
769 fn empty(url_data: &UrlExtraData) -> Self {
770 Self {
771 css: String::new(),
772 last_token_type: Default::default(),
773 first_token_type: Default::default(),
774 url_data: url_data.clone(),
775 references: Default::default(),
776 }
777 }
778
779 pub fn new(
782 css: String,
783 url_data: &UrlExtraData,
784 first_token_type: TokenSerializationType,
785 last_token_type: TokenSerializationType,
786 ) -> Self {
787 Self {
788 css,
789 url_data: url_data.clone(),
790 first_token_type,
791 last_token_type,
792 references: References::default(),
793 }
794 }
795
796 fn push<'i>(
797 &mut self,
798 css: &str,
799 css_first_token_type: TokenSerializationType,
800 css_last_token_type: TokenSerializationType,
801 attr_taint: Option<&mut AttrTaint>,
802 ) -> Result<(), ()> {
803 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
811
812 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
813 return Err(());
814 }
815
816 if css.is_empty() {
821 return Ok(());
822 }
823
824 self.first_token_type.set_if_nothing(css_first_token_type);
825 if self
828 .last_token_type
829 .needs_separator_when_before(css_first_token_type)
830 {
831 self.css.push_str("/**/")
832 }
833 let start = self.css.len();
834 self.css.push_str(css);
835 let end = self.css.len();
836 if let Some(taint) = attr_taint {
837 taint.push(start, end);
838 }
839 self.last_token_type = css_last_token_type;
840 Ok(())
841 }
842
843 pub fn parse<'i, 't>(
845 input: &mut Parser<'i, 't>,
846 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
847 url_data: &UrlExtraData,
848 ) -> Result<Self, ParseError<'i>> {
849 let mut references = References::default();
850 let mut missing_closing_characters = String::new();
851 let start_position = input.position();
852 let (first_token_type, last_token_type) = parse_declaration_value(
853 input,
854 start_position,
855 namespaces,
856 &mut references,
857 &mut missing_closing_characters,
858 )?;
859 let mut css = input
860 .slice_from(start_position)
861 .trim_ascii_start()
862 .to_owned();
863 if !missing_closing_characters.is_empty() {
864 if css.ends_with("\\")
866 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
867 {
868 css.pop();
869 }
870 css.push_str(&missing_closing_characters);
871 }
872
873 css.truncate(css.trim_ascii_end().len());
874 css.shrink_to_fit();
875 references.refs.shrink_to_fit();
876
877 Ok(Self {
878 css,
879 url_data: url_data.clone(),
880 first_token_type,
881 last_token_type,
882 references,
883 })
884 }
885
886 pub fn is_attr_tainted(&self) -> bool {
888 self.references.any_attr
889 }
890
891 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
893 Self::from_token(
894 Token::Number {
895 has_sign: false,
896 value: number as f32,
897 int_value: Some(number),
898 },
899 url_data,
900 )
901 }
902
903 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
905 Self::from_token(Token::Ident(ident.into()), url_data)
906 }
907
908 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
910 Self::from_token(
914 Token::Dimension {
915 has_sign: false,
916 value: number,
917 int_value: None,
918 unit: CowRcStr::from("px"),
919 },
920 url_data,
921 )
922 }
923
924 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
926 Self::from_token(
927 Token::Dimension {
928 has_sign: false,
929 value: number as f32,
930 int_value: Some(number),
931 unit: CowRcStr::from("ms"),
932 },
933 url_data,
934 )
935 }
936
937 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
939 Self::from_token(
940 Token::Dimension {
941 has_sign: false,
942 value: number as f32,
943 int_value: Some(number),
944 unit: CowRcStr::from("px"),
945 },
946 url_data,
947 )
948 }
949
950 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
951 let token_type = token.serialization_type();
952 let mut css = token.to_css_string();
953 css.shrink_to_fit();
954
955 VariableValue {
956 css,
957 url_data: url_data.clone(),
958 first_token_type: token_type,
959 last_token_type: token_type,
960 references: Default::default(),
961 }
962 }
963
964 pub fn css_text(&self) -> &str {
966 &self.css
967 }
968
969 pub fn has_references(&self) -> bool {
972 self.references.has_references()
973 }
974}
975
976fn parse_declaration_value<'i, 't>(
978 input: &mut Parser<'i, 't>,
979 input_start: SourcePosition,
980 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
981 references: &mut References,
982 missing_closing_characters: &mut String,
983) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
984 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
985 parse_declaration_value_block(
986 input,
987 input_start,
988 namespaces,
989 references,
990 missing_closing_characters,
991 )
992 })
993}
994
995fn parse_declaration_value_block<'i, 't>(
997 input: &mut Parser<'i, 't>,
998 input_start: SourcePosition,
999 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1000 references: &mut References,
1001 missing_closing_characters: &mut String,
1002) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
1003 let mut is_first = true;
1004 let mut first_token_type = TokenSerializationType::Nothing;
1005 let mut last_token_type = TokenSerializationType::Nothing;
1006 let mut prev_reference_index: Option<usize> = None;
1007 loop {
1008 let token_start = input.position();
1009 let Ok(token) = input.next_including_whitespace_and_comments() else {
1010 break;
1011 };
1012
1013 let prev_token_type = last_token_type;
1014 let serialization_type = token.serialization_type();
1015 last_token_type = serialization_type;
1016 if is_first {
1017 first_token_type = last_token_type;
1018 is_first = false;
1019 }
1020
1021 macro_rules! nested {
1022 ($closing:expr) => {{
1023 let mut inner_end_position = None;
1024 let result = input.parse_nested_block(|input| {
1025 let result = parse_declaration_value_block(
1026 input,
1027 input_start,
1028 namespaces,
1029 references,
1030 missing_closing_characters,
1031 )?;
1032 inner_end_position = Some(input.position());
1033 Ok(result)
1034 })?;
1035 if inner_end_position.unwrap() == input.position() {
1036 missing_closing_characters.push_str($closing);
1037 }
1038 result
1039 }};
1040 }
1041 if let Some(index) = prev_reference_index.take() {
1042 references.refs[index].next_token_type = serialization_type;
1043 }
1044 match *token {
1045 Token::Comment(_) => {
1046 let token_slice = input.slice_from(token_start);
1047 if !token_slice.ends_with("*/") {
1048 missing_closing_characters.push_str(if token_slice.ends_with('*') {
1049 "/"
1050 } else {
1051 "*/"
1052 })
1053 }
1054 },
1055 Token::BadUrl(ref u) => {
1056 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
1057 return Err(input.new_custom_error(e));
1058 },
1059 Token::BadString(ref s) => {
1060 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
1061 return Err(input.new_custom_error(e));
1062 },
1063 Token::CloseParenthesis => {
1064 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1065 return Err(input.new_custom_error(e));
1066 },
1067 Token::CloseSquareBracket => {
1068 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1069 return Err(input.new_custom_error(e));
1070 },
1071 Token::CloseCurlyBracket => {
1072 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1073 return Err(input.new_custom_error(e));
1074 },
1075 Token::Function(ref name) => {
1076 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
1077 Some(SubstitutionFunctionKind::Attr) => {
1078 if static_prefs::pref!("layout.css.attr.enabled") {
1079 Some(SubstitutionFunctionKind::Attr)
1080 } else {
1081 None
1082 }
1083 },
1084 kind => kind,
1085 };
1086 if let Some(substitution_kind) = substitution_kind {
1087 let our_ref_index = references.refs.len();
1088 let mut input_end_position = None;
1089 let fallback = input.parse_nested_block(|input| {
1090 let mut namespace = ParsedNamespace::Known(Namespace::default());
1091 if substitution_kind == SubstitutionFunctionKind::Attr {
1092 if let Some(namespaces) = namespaces {
1093 if let Ok(ns) = input
1094 .try_parse(|input| ParsedNamespace::parse(namespaces, input))
1095 {
1096 namespace = ns;
1097 }
1098 }
1099 }
1100 let name = input.expect_ident()?;
1103 let name =
1104 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
1105 match parse_name(name.as_ref()) {
1106 Ok(name) => name,
1107 Err(()) => {
1108 let name = name.clone();
1109 return Err(input.new_custom_error(
1110 SelectorParseErrorKind::UnexpectedIdent(name),
1111 ));
1112 },
1113 }
1114 } else {
1115 name.as_ref()
1116 });
1117
1118 let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
1119 {
1120 parse_attr_type(input)
1121 } else {
1122 AttributeType::None
1123 };
1124
1125 let start = token_start.byte_index() - input_start.byte_index();
1129 references.refs.push(SubstitutionFunctionReference {
1130 name,
1131 start,
1132 end: start,
1134 prev_token_type,
1135 next_token_type: TokenSerializationType::Nothing,
1137 fallback: None,
1139 attribute_data: AttributeData {
1140 kind: attribute_kind,
1141 namespace,
1142 },
1143 substitution_kind: substitution_kind.clone(),
1144 });
1145
1146 let mut fallback = None;
1147 if input.try_parse(|input| input.expect_comma()).is_ok() {
1148 input.skip_whitespace();
1149 let fallback_start = num::NonZeroUsize::new(
1150 input.position().byte_index() - input_start.byte_index(),
1151 )
1152 .unwrap();
1153 let (first, last) = parse_declaration_value(
1156 input,
1157 input_start,
1158 namespaces,
1159 references,
1160 missing_closing_characters,
1161 )?;
1162 fallback = Some(VariableFallback {
1163 start: fallback_start,
1164 first_token_type: first,
1165 last_token_type: last,
1166 });
1167 input_end_position = Some(input.position());
1168 } else {
1169 let state = input.state();
1170 parse_declaration_value_block(
1174 input,
1175 input_start,
1176 namespaces,
1177 references,
1178 missing_closing_characters,
1179 )?;
1180 input_end_position = Some(input.position());
1181 input.reset(&state);
1182 }
1183 Ok(fallback)
1184 })?;
1185 if input_end_position.unwrap() == input.position() {
1186 missing_closing_characters.push_str(")");
1187 }
1188 prev_reference_index = Some(our_ref_index);
1189 let reference = &mut references.refs[our_ref_index];
1190 reference.end = input.position().byte_index() - input_start.byte_index()
1191 + missing_closing_characters.len();
1192 reference.fallback = fallback;
1193 match substitution_kind {
1194 SubstitutionFunctionKind::Var => references.any_var = true,
1195 SubstitutionFunctionKind::Env => references.any_env = true,
1196 SubstitutionFunctionKind::Attr => references.any_attr = true,
1197 };
1198 } else {
1199 nested!(")");
1200 }
1201 },
1202 Token::ParenthesisBlock => {
1203 nested!(")");
1204 },
1205 Token::CurlyBracketBlock => {
1206 nested!("}");
1207 },
1208 Token::SquareBracketBlock => {
1209 nested!("]");
1210 },
1211 Token::QuotedString(_) => {
1212 let token_slice = input.slice_from(token_start);
1213 let quote = &token_slice[..1];
1214 debug_assert!(matches!(quote, "\"" | "'"));
1215 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1216 missing_closing_characters.push_str(quote)
1217 }
1218 },
1219 Token::Ident(ref value)
1220 | Token::AtKeyword(ref value)
1221 | Token::Hash(ref value)
1222 | Token::IDHash(ref value)
1223 | Token::UnquotedUrl(ref value)
1224 | Token::Dimension {
1225 unit: ref value, ..
1226 } => {
1227 references
1228 .non_custom_references
1229 .insert(NonCustomReferences::from_unit(value));
1230 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1231 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1232 missing_closing_characters.push_str("�")
1237 }
1238 if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1239 missing_closing_characters.push_str(")");
1240 }
1241 },
1242 _ => {},
1243 };
1244 }
1245 Ok((first_token_type, last_token_type))
1246}
1247
1248fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1251 input
1252 .try_parse(|input| {
1253 Ok(match input.next()? {
1254 Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1255 AttributeType::Type(
1256 input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1257 )
1258 },
1259 Token::Ident(ref ident) => {
1260 if ident.eq_ignore_ascii_case("raw-string") {
1261 AttributeType::RawString
1262 } else {
1263 let unit = AttrUnit::from_ident(ident).map_err(|_| {
1264 input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
1265 })?;
1266 AttributeType::Unit(unit)
1267 }
1268 },
1269 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1270 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1271 })
1272 })
1273 .unwrap_or(AttributeType::None)
1274}
1275
1276fn parse_attribute_value(
1279 name: &Atom,
1280 attribute_data: &AttributeData,
1281 url_data: &UrlExtraData,
1282 attribute_tracker: &mut AttributeTracker,
1283) -> Result<ComputedRegisteredValue, ()> {
1284 #[cfg(feature = "gecko")]
1285 let local_name = LocalName::cast(name);
1286 #[cfg(feature = "servo")]
1287 let local_name = &LocalName::from(name.as_ref());
1288 let namespace = match attribute_data.namespace {
1289 ParsedNamespace::Known(ref ns) => ns,
1290 ParsedNamespace::Unknown => return Err(()),
1291 };
1292 let attr = attribute_tracker.query(local_name, namespace).ok_or(())?;
1293 let mut input = ParserInput::new(&attr);
1294 let mut parser = Parser::new(&mut input);
1295 let value = VariableValue::parse(&mut parser, None, &url_data).map_err(|_| ())?;
1297 Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1298}
1299
1300#[derive(Default)]
1301struct SeenSubstitutionFunctions<'a> {
1302 var: PrecomputedHashSet<&'a Name>,
1303 attr: PrecomputedHashSet<&'a Name>,
1304}
1305
1306pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
1308 seen: SeenSubstitutionFunctions<'a>,
1309 may_have_cycles: bool,
1310 has_color_scheme: bool,
1311 substitution_functions: ComputedSubstitutionFunctions,
1312 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, RevertKind)>,
1313 stylist: &'a Stylist,
1314 computed_context: &'a mut computed::Context<'b>,
1315 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
1316}
1317
1318fn find_non_custom_references(
1319 registration: &PropertyDescriptors,
1320 value: &VariableValue,
1321 may_have_color_scheme: bool,
1322 is_root_element: bool,
1323 include_universal: bool,
1324) -> Option<NonCustomReferences> {
1325 let syntax = registration.syntax.as_ref()?;
1326 let dependent_types = syntax.dependent_types();
1327 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
1328 || (include_universal && syntax.is_universal());
1329 if may_reference_length {
1330 let value_dependencies = value.references.non_custom_references(is_root_element);
1331 if !value_dependencies.is_empty() {
1332 return Some(value_dependencies);
1333 }
1334 }
1335 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
1336 return Some(NonCustomReferences::empty());
1340 }
1341 None
1342}
1343
1344impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
1345 pub fn new_with_properties(
1349 stylist: &'a Stylist,
1350 custom_properties: ComputedCustomProperties,
1351 computed_context: &'a mut computed::Context<'b>,
1352 ) -> Self {
1353 Self {
1354 seen: SeenSubstitutionFunctions::default(),
1355 reverted: Default::default(),
1356 may_have_cycles: false,
1357 has_color_scheme: false,
1358 substitution_functions: ComputedSubstitutionFunctions::new(
1359 Some(custom_properties),
1360 None,
1361 ),
1362 stylist,
1363 computed_context,
1364 references_from_non_custom_properties: NonCustomReferenceMap::default(),
1365 }
1366 }
1367
1368 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1370 let is_root_element = context.is_root_element();
1371
1372 let inherited = context.inherited_custom_properties();
1373 let initial_values = stylist.get_custom_property_initial_values();
1374 let properties = ComputedCustomProperties {
1375 inherited: if is_root_element {
1376 debug_assert!(inherited.is_empty());
1377 initial_values.inherited.clone()
1378 } else {
1379 inherited.inherited.clone()
1380 },
1381 non_inherited: initial_values.non_inherited.clone(),
1382 };
1383
1384 context
1387 .style()
1388 .add_flags(stylist.get_custom_property_initial_values_flags());
1389 Self::new_with_properties(stylist, properties, context)
1390 }
1391
1392 pub fn cascade(
1394 &mut self,
1395 declaration: &'a CustomDeclaration,
1396 priority: CascadePriority,
1397 attribute_tracker: &mut AttributeTracker,
1398 ) {
1399 let CustomDeclaration {
1400 ref name,
1401 ref value,
1402 } = *declaration;
1403
1404 if let Some(&(reverted_priority, revert_kind)) = self.reverted.get(&name) {
1405 if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1406 return;
1407 }
1408 }
1409
1410 if !(priority.flags() - self.computed_context.included_cascade_flags).is_empty() {
1411 return;
1412 }
1413
1414 let was_already_present = !self.seen.var.insert(name);
1415 if was_already_present {
1416 return;
1417 }
1418
1419 if !self.value_may_affect_style(name, value) {
1420 return;
1421 }
1422
1423 let kind = SubstitutionFunctionKind::Var;
1424 let map = &mut self.substitution_functions;
1425 let registration = self.stylist.get_custom_property_registration(&name);
1426 match value {
1427 CustomDeclarationValue::Unparsed(unparsed_value) => {
1428 let may_have_color_scheme = true;
1433 let has_dependency = unparsed_value.references.any_var
1436 || unparsed_value.references.any_attr
1437 || find_non_custom_references(
1438 registration,
1439 unparsed_value,
1440 may_have_color_scheme,
1441 self.computed_context.is_root_element(),
1442 false,
1443 )
1444 .is_some();
1445 if !has_dependency {
1449 return substitute_references_if_needed_and_apply(
1450 name,
1451 kind,
1452 unparsed_value,
1453 &mut self.substitution_functions,
1454 self.stylist,
1455 self.computed_context,
1456 attribute_tracker,
1457 );
1458 }
1459 self.may_have_cycles = true;
1460 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1461 map.insert_var(registration, name, value);
1462 },
1463 CustomDeclarationValue::Parsed(parsed_value) => {
1464 let value = parsed_value.to_computed_value(&self.computed_context);
1465 map.insert_var(registration, name, value);
1466 },
1467 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword.revert_kind() {
1468 Some(revert_kind) => {
1469 self.seen.var.remove(name);
1470 self.reverted.insert(name, (priority, revert_kind));
1471 },
1472 None => match keyword {
1473 CSSWideKeyword::Initial => {
1474 debug_assert!(registration.inherits(), "Should've been handled earlier");
1476 remove_and_insert_initial_value(name, registration, map);
1477 },
1478 CSSWideKeyword::Inherit => {
1479 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1481 self.computed_context
1482 .style()
1483 .add_flags(ComputedValueFlags::INHERITS_RESET_STYLE);
1484 if let Some(inherited_value) = self
1485 .computed_context
1486 .inherited_custom_properties()
1487 .non_inherited
1488 .get(name)
1489 {
1490 map.insert_var(registration, name, inherited_value.clone());
1491 }
1492 },
1493 CSSWideKeyword::Revert
1495 | CSSWideKeyword::RevertLayer
1496 | CSSWideKeyword::RevertRule
1497 | CSSWideKeyword::Unset => unreachable!(),
1498 },
1499 },
1500 }
1501 }
1502
1503 #[inline]
1505 pub fn might_have_non_custom_or_attr_dependency(
1506 id: LonghandId,
1507 decl: &PropertyDeclaration,
1508 ) -> bool {
1509 if id == LonghandId::ColorScheme {
1510 return true;
1511 }
1512 if let PropertyDeclaration::WithVariables(v) = decl {
1513 return matches!(id, LonghandId::LineHeight | LonghandId::FontSize)
1514 || v.value.variable_value.references.any_attr;
1515 }
1516 false
1517 }
1518
1519 pub fn maybe_note_non_custom_dependency(
1522 &mut self,
1523 id: LonghandId,
1524 decl: &'a PropertyDeclaration,
1525 attribute_tracker: &mut AttributeTracker,
1526 ) {
1527 debug_assert!(Self::might_have_non_custom_or_attr_dependency(id, decl));
1528 if id == LonghandId::ColorScheme {
1529 self.has_color_scheme = true;
1531 return;
1532 }
1533
1534 let PropertyDeclaration::WithVariables(v) = decl else {
1535 return;
1536 };
1537 let value = &v.value.variable_value;
1538 let refs = &value.references;
1539
1540 if !refs.any_var && !refs.any_attr {
1541 return;
1542 }
1543
1544 if refs.any_attr {
1548 self.update_attributes_map(value, attribute_tracker);
1549 if !refs.any_var {
1550 return;
1551 }
1552 }
1553
1554 let references = match id {
1558 LonghandId::FontSize => {
1559 if self.computed_context.is_root_element() {
1560 NonCustomReferences::ROOT_FONT_UNITS
1561 } else {
1562 NonCustomReferences::FONT_UNITS
1563 }
1564 },
1565 LonghandId::LineHeight => {
1566 if self.computed_context.is_root_element() {
1567 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1568 } else {
1569 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1570 }
1571 },
1572 _ => return,
1573 };
1574
1575 let variables: Vec<Atom> = refs
1576 .refs
1577 .iter()
1578 .filter_map(|reference| {
1579 if reference.substitution_kind != SubstitutionFunctionKind::Var {
1580 return None;
1581 }
1582 let registration = self
1583 .stylist
1584 .get_custom_property_registration(&reference.name);
1585 if !registration
1586 .syntax
1587 .as_ref()?
1588 .dependent_types()
1589 .intersects(DependentDataTypes::LENGTH)
1590 {
1591 return None;
1592 }
1593 Some(reference.name.clone())
1594 })
1595 .collect();
1596 references.for_each(|idx| {
1597 let entry = &mut self.references_from_non_custom_properties[idx];
1598 let was_none = entry.is_none();
1599 let v = entry.get_or_insert_with(|| variables.clone());
1600 if was_none {
1601 return;
1602 }
1603 v.extend(variables.iter().cloned());
1604 });
1605 }
1606
1607 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1608 let registration = self.stylist.get_custom_property_registration(&name);
1609 match *value {
1610 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1611 if registration.inherits() {
1615 return false;
1616 }
1617 },
1618 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1619 if !registration.inherits() {
1622 return false;
1623 }
1624 },
1625 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1626 return false;
1630 },
1631 _ => {},
1632 }
1633
1634 let existing_value = self.substitution_functions.get_var(registration, &name);
1635 let existing_value = match existing_value {
1636 None => {
1637 if matches!(
1638 value,
1639 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1640 ) {
1641 debug_assert!(registration.inherits(), "Should've been handled earlier");
1642 if registration.initial_value.is_none() {
1646 return false;
1647 }
1648 }
1649 return true;
1650 },
1651 Some(v) => v,
1652 };
1653 let computed_value = match value {
1654 CustomDeclarationValue::Unparsed(value) => {
1655 if let Some(existing_value) = existing_value.as_universal() {
1658 return existing_value != value;
1659 }
1660 if !registration.is_universal() {
1661 compute_value(
1662 &value.css,
1663 &value.url_data,
1664 registration,
1665 self.computed_context,
1666 AttrTaint::default(),
1667 )
1668 .ok()
1669 } else {
1670 None
1671 }
1672 },
1673 CustomDeclarationValue::Parsed(value) => {
1674 Some(value.to_computed_value(&self.computed_context))
1675 },
1676 CustomDeclarationValue::CSSWideKeyword(kw) => {
1677 match kw {
1678 CSSWideKeyword::Inherit => {
1679 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1680 if self
1684 .computed_context
1685 .inherited_custom_properties()
1686 .non_inherited
1687 .get(name)
1688 .is_none()
1689 {
1690 return false;
1691 }
1692 },
1693 CSSWideKeyword::Initial => {
1694 debug_assert!(registration.inherits(), "Should've been handled earlier");
1695 if let Some(initial_value) = self
1698 .stylist
1699 .get_custom_property_initial_values()
1700 .get(registration, name)
1701 {
1702 return existing_value != initial_value;
1703 }
1704 },
1705 CSSWideKeyword::Unset => {
1706 debug_assert!(false, "Should've been handled earlier");
1707 },
1708 CSSWideKeyword::Revert
1709 | CSSWideKeyword::RevertLayer
1710 | CSSWideKeyword::RevertRule => {},
1711 }
1712 None
1713 },
1714 };
1715
1716 if let Some(value) = computed_value {
1717 return existing_value.v != value.v;
1718 }
1719
1720 true
1721 }
1722
1723 pub fn update_attributes_map(
1725 &mut self,
1726 value: &'a VariableValue,
1727 attribute_tracker: &mut AttributeTracker,
1728 ) {
1729 let refs = &value.references;
1730 if !refs.any_attr {
1731 return;
1732 }
1733 self.may_have_cycles = true;
1734
1735 for next in &refs.refs {
1736 if next.substitution_kind != SubstitutionFunctionKind::Attr
1738 || !self.seen.attr.insert(&next.name)
1739 {
1740 continue;
1741 }
1742 if let Ok(v) = parse_attribute_value(
1743 &next.name,
1744 &next.attribute_data,
1745 &value.url_data,
1746 attribute_tracker,
1747 ) {
1748 self.substitution_functions.insert_attr(&next.name, v);
1749 }
1750 }
1751 }
1752
1753 pub fn build(
1769 mut self,
1770 defer: DeferFontRelativeCustomPropertyResolution,
1771 attribute_tracker: &mut AttributeTracker,
1772 ) -> Option<AllSubstitutionFunctions> {
1773 let mut deferred_substitution_functions = None;
1774 if self.may_have_cycles {
1775 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1776 deferred_substitution_functions = Some(AllSubstitutionFunctions::default());
1777 }
1778 let mut invalid_non_custom_properties = LonghandIdSet::default();
1779 substitute_all(
1780 &mut self.substitution_functions,
1781 deferred_substitution_functions.as_mut(),
1782 &mut invalid_non_custom_properties,
1783 self.has_color_scheme,
1784 &self.seen,
1785 &self.references_from_non_custom_properties,
1786 self.stylist,
1787 self.computed_context,
1788 attribute_tracker,
1789 );
1790 self.computed_context.builder.invalid_non_custom_properties =
1791 invalid_non_custom_properties;
1792 }
1793 self.substitution_functions
1794 .custom_properties
1795 .shrink_to_fit();
1796
1797 let initial_values = self.stylist.get_custom_property_initial_values();
1802 let custom_properties = self.substitution_functions.custom_properties;
1803 self.computed_context
1804 .builder
1805 .substitution_functions
1806 .custom_properties = ComputedCustomProperties {
1807 inherited: if self
1808 .computed_context
1809 .inherited_custom_properties()
1810 .inherited
1811 == custom_properties.inherited
1812 {
1813 self.computed_context
1814 .inherited_custom_properties()
1815 .inherited
1816 .clone()
1817 } else {
1818 custom_properties.inherited
1819 },
1820 non_inherited: if initial_values.non_inherited == custom_properties.non_inherited {
1821 initial_values.non_inherited.clone()
1822 } else {
1823 custom_properties.non_inherited
1824 },
1825 };
1826 self.computed_context
1827 .builder
1828 .substitution_functions
1829 .attributes = self.substitution_functions.attributes;
1830
1831 deferred_substitution_functions
1832 }
1833
1834 pub fn build_deferred(
1837 deferred: AllSubstitutionFunctions,
1838 stylist: &Stylist,
1839 computed_context: &mut computed::Context,
1840 attribute_tracker: &mut AttributeTracker,
1841 ) {
1842 if deferred.is_empty() {
1843 return;
1844 }
1845 let mut map = std::mem::take(&mut computed_context.builder.substitution_functions);
1846 for (name, kind, v) in deferred.iter() {
1849 let Some(v) = v.as_universal() else {
1850 unreachable!("Computing should have been deferred!")
1851 };
1852 substitute_references_if_needed_and_apply(
1853 name,
1854 kind,
1855 v,
1856 &mut map,
1857 stylist,
1858 computed_context,
1859 attribute_tracker,
1860 );
1861 }
1862 computed_context.builder.substitution_functions = map;
1863 }
1864}
1865
1866fn substitute_all(
1871 substitution_function_map: &mut ComputedSubstitutionFunctions,
1872 mut deferred_substituted_functions_map: Option<&mut AllSubstitutionFunctions>,
1873 invalid_non_custom_properties: &mut LonghandIdSet,
1874 has_color_scheme: bool,
1875 seen: &SeenSubstitutionFunctions,
1876 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1877 stylist: &Stylist,
1878 computed_context: &computed::Context,
1879 attr_tracker: &mut AttributeTracker,
1880) {
1881 #[derive(Clone, Eq, PartialEq, Debug)]
1888 enum VarType {
1889 Attr(Name),
1890 Custom(Name),
1891 NonCustom(SingleNonCustomReference),
1892 }
1893
1894 #[derive(Debug)]
1896 struct VarInfo {
1897 var: Option<VarType>,
1902 lowlink: usize,
1907 }
1908 struct Context<'a, 'b: 'a> {
1911 count: usize,
1914 index_map: PrecomputedHashMap<Name, usize>,
1916 non_custom_index_map: NonCustomReferenceMap<usize>,
1918 var_info: SmallVec<[VarInfo; 5]>,
1920 stack: SmallVec<[usize; 5]>,
1923 non_custom_references: NonCustomReferences,
1925 has_color_scheme: bool,
1927 contains_computed_custom_property: bool,
1930 map: &'a mut ComputedSubstitutionFunctions,
1931 stylist: &'a Stylist,
1934 computed_context: &'a computed::Context<'b>,
1937 invalid_non_custom_properties: &'a mut LonghandIdSet,
1939 deferred_substitution_functions: Option<&'a mut AllSubstitutionFunctions>,
1943 }
1944
1945 fn traverse<'a, 'b>(
1964 var: VarType,
1965 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1966 context: &mut Context<'a, 'b>,
1967 attribute_tracker: &mut AttributeTracker,
1968 ) -> Option<usize> {
1969 let kind = if matches!(var, VarType::Custom(_)) {
1970 SubstitutionFunctionKind::Var
1971 } else {
1972 SubstitutionFunctionKind::Attr
1973 };
1974 let value = match var {
1976 VarType::Custom(ref name) | VarType::Attr(ref name) => {
1977 let registration;
1978 let value;
1979 match kind {
1980 SubstitutionFunctionKind::Var => {
1981 registration = context.stylist.get_custom_property_registration(name);
1982 value = context.map.get_var(registration, name)?.as_universal()?;
1983 },
1984 SubstitutionFunctionKind::Attr => {
1985 registration = PropertyDescriptors::unregistered();
1988 value = context.map.get_attr(name)?.as_universal()?;
1989 },
1990 _ => unreachable!("Substitution kind must be var or attr for VarType::Custom."),
1991 }
1992 let is_var = kind == SubstitutionFunctionKind::Var;
1993 let is_attr = kind == SubstitutionFunctionKind::Attr;
1994 let is_root = context.computed_context.is_root_element();
1995 let non_custom_refs = find_non_custom_references(
1998 registration,
1999 value,
2000 context.has_color_scheme,
2001 is_root,
2002 true,
2003 );
2004 context.non_custom_references |= non_custom_refs.unwrap_or_default();
2005 let has_dependency = value.references.any_var
2006 || value.references.any_attr
2007 || non_custom_refs.is_some();
2008 if !has_dependency {
2010 debug_assert!(!value.references.any_env, "Should've been handled earlier");
2011 if is_attr || !registration.is_universal() {
2012 if is_var {
2017 debug_assert!(
2018 registration
2019 .syntax
2020 .as_ref()
2021 .unwrap()
2022 .dependent_types()
2023 .intersects(DependentDataTypes::COLOR),
2024 "How did an unresolved value get here otherwise?",
2025 );
2026 }
2027 let value = value.clone();
2028 substitute_references_if_needed_and_apply(
2029 name,
2030 kind,
2031 &value,
2032 &mut context.map,
2033 context.stylist,
2034 context.computed_context,
2035 attribute_tracker,
2036 );
2037 }
2038 return None;
2039 }
2040
2041 match context.index_map.entry(name.clone()) {
2046 Entry::Occupied(entry) => {
2047 return Some(*entry.get());
2048 },
2049 Entry::Vacant(entry) => {
2050 entry.insert(context.count);
2051 },
2052 }
2053 context.contains_computed_custom_property |= is_var && !registration.is_universal();
2054
2055 Some(value.clone())
2058 },
2059 VarType::NonCustom(ref non_custom) => {
2060 let entry = &mut context.non_custom_index_map[*non_custom];
2061 if let Some(v) = entry {
2062 return Some(*v);
2063 }
2064 *entry = Some(context.count);
2065 None
2066 },
2067 };
2068
2069 let index = context.count;
2071 context.count += 1;
2072 debug_assert_eq!(index, context.var_info.len());
2073 context.var_info.push(VarInfo {
2074 var: Some(var.clone()),
2075 lowlink: index,
2076 });
2077 context.stack.push(index);
2078
2079 let mut self_ref = false;
2080 let mut lowlink = index;
2081 let visit_link = |var: VarType,
2082 context: &mut Context,
2083 lowlink: &mut usize,
2084 self_ref: &mut bool,
2085 attr_tracker: &mut AttributeTracker| {
2086 let next_index = match traverse(var, non_custom_references, context, attr_tracker) {
2087 Some(index) => index,
2088 None => {
2091 return;
2092 },
2093 };
2094 let next_info = &context.var_info[next_index];
2095 if next_index > index {
2096 *lowlink = cmp::min(*lowlink, next_info.lowlink);
2100 } else if next_index == index {
2101 *self_ref = true;
2102 } else if next_info.var.is_some() {
2103 *lowlink = cmp::min(*lowlink, next_index);
2106 }
2107 };
2108 if let Some(ref v) = value.as_ref() {
2109 debug_assert!(
2110 matches!(var, VarType::Custom(_) | VarType::Attr(_)),
2111 "Non-custom property has references?"
2112 );
2113
2114 for next in &v.references.refs {
2117 if next.substitution_kind == SubstitutionFunctionKind::Env {
2118 continue;
2119 }
2120
2121 let next_var = if next.substitution_kind == SubstitutionFunctionKind::Attr {
2122 if context.map.get_attr(&next.name).is_none() {
2123 let Ok(val) = parse_attribute_value(
2124 &next.name,
2125 &next.attribute_data,
2126 &v.url_data,
2127 attribute_tracker,
2128 ) else {
2129 continue;
2130 };
2131 context.map.insert_attr(&next.name, val);
2132 }
2133 VarType::Attr(next.name.clone())
2134 } else {
2135 VarType::Custom(next.name.clone())
2136 };
2137
2138 visit_link(
2139 next_var,
2140 context,
2141 &mut lowlink,
2142 &mut self_ref,
2143 attribute_tracker,
2144 );
2145 }
2146
2147 v.references.non_custom_references.for_each(|r| {
2149 visit_link(
2150 VarType::NonCustom(r),
2151 context,
2152 &mut lowlink,
2153 &mut self_ref,
2154 attribute_tracker,
2155 );
2156 });
2157 } else if let VarType::NonCustom(non_custom) = var {
2158 let entry = &non_custom_references[non_custom];
2159 if let Some(deps) = entry.as_ref() {
2160 for d in deps {
2161 visit_link(
2164 VarType::Custom(d.clone()),
2165 context,
2166 &mut lowlink,
2167 &mut self_ref,
2168 attribute_tracker,
2169 );
2170 }
2171 }
2172 }
2173
2174 context.var_info[index].lowlink = lowlink;
2175 if lowlink != index {
2176 return Some(index);
2184 }
2185
2186 let mut in_loop = self_ref;
2188 let name;
2189
2190 let handle_variable_in_loop =
2191 |name: &Name, context: &mut Context<'a, 'b>, kind: SubstitutionFunctionKind| {
2192 if context.contains_computed_custom_property {
2193 if context.non_custom_references.intersects(
2196 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
2197 ) {
2198 context
2199 .invalid_non_custom_properties
2200 .insert(LonghandId::FontSize);
2201 }
2202 if context.non_custom_references.intersects(
2203 NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS,
2204 ) {
2205 context
2206 .invalid_non_custom_properties
2207 .insert(LonghandId::LineHeight);
2208 }
2209 }
2210 handle_invalid_at_computed_value_time(
2212 name,
2213 kind,
2214 &mut context.map,
2215 context.computed_context,
2216 );
2217 };
2218 loop {
2219 let var_index = context
2220 .stack
2221 .pop()
2222 .expect("The current variable should still be in stack");
2223 let var_info = &mut context.var_info[var_index];
2224 let var_name = var_info
2228 .var
2229 .take()
2230 .expect("Variable should not be poped from stack twice");
2231 if var_index == index {
2232 name = match var_name {
2233 VarType::Custom(name) | VarType::Attr(name) => name,
2234 VarType::NonCustom(..) => return None,
2238 };
2239 break;
2240 }
2241 if let VarType::Custom(name) | VarType::Attr(name) = var_name {
2242 handle_variable_in_loop(&name, context, kind);
2246 }
2247 in_loop = true;
2248 }
2249 if in_loop {
2254 handle_variable_in_loop(&name, context, kind);
2255 context.non_custom_references = NonCustomReferences::default();
2256 return None;
2257 }
2258
2259 if let Some(ref v) = value {
2260 let registration = context.stylist.get_custom_property_registration(&name);
2261
2262 let mut defer = false;
2263 if let Some(ref mut deferred) = context.deferred_substitution_functions {
2264 defer = find_non_custom_references(
2267 registration,
2268 v,
2269 context.has_color_scheme,
2270 context.computed_context.is_root_element(),
2271 false,
2272 )
2273 .is_some()
2274 || v.references.refs.iter().any(|reference| {
2275 (reference.substitution_kind == SubstitutionFunctionKind::Var
2276 && deferred
2277 .get(&reference.name, SubstitutionFunctionKind::Var)
2278 .is_some())
2279 || reference.substitution_kind == SubstitutionFunctionKind::Attr
2280 });
2281 if defer {
2282 let value = ComputedRegisteredValue::universal(Arc::clone(v));
2283 deferred.insert(&name, kind, value);
2284 if kind == SubstitutionFunctionKind::Var {
2285 context.map.remove_var(registration, &name);
2286 } else {
2287 context.map.remove_attr(&name);
2288 }
2289 }
2290 }
2291
2292 if !defer && (v.references.any_var || v.references.any_attr) {
2294 substitute_references_if_needed_and_apply(
2295 &name,
2296 kind,
2297 v,
2298 &mut context.map,
2299 context.stylist,
2300 context.computed_context,
2301 attribute_tracker,
2302 );
2303 }
2304 }
2305 context.non_custom_references = NonCustomReferences::default();
2306
2307 None
2309 }
2310
2311 let mut run = |make_var: fn(Name) -> VarType, seen: &PrecomputedHashSet<&Name>| {
2312 for name in seen {
2313 let mut context = Context {
2314 count: 0,
2315 index_map: PrecomputedHashMap::default(),
2316 non_custom_index_map: NonCustomReferenceMap::default(),
2317 stack: SmallVec::new(),
2318 var_info: SmallVec::new(),
2319 map: substitution_function_map,
2320 non_custom_references: NonCustomReferences::default(),
2321 has_color_scheme,
2322 stylist,
2323 computed_context,
2324 invalid_non_custom_properties,
2325 deferred_substitution_functions: deferred_substituted_functions_map.as_deref_mut(),
2326 contains_computed_custom_property: false,
2327 };
2328
2329 traverse(
2330 make_var((*name).clone()),
2331 references_from_non_custom_properties,
2332 &mut context,
2333 attr_tracker,
2334 );
2335 }
2336 };
2337
2338 run(VarType::Custom, &seen.var);
2342 run(VarType::Attr, &seen.attr);
2345}
2346
2347fn handle_invalid_at_computed_value_time(
2349 name: &Name,
2350 kind: SubstitutionFunctionKind,
2351 substitution_functions: &mut ComputedSubstitutionFunctions,
2352 computed_context: &computed::Context,
2353) {
2354 if kind == SubstitutionFunctionKind::Attr {
2355 substitution_functions.remove_attr(name);
2357 return;
2358 }
2359
2360 let stylist = computed_context.style().stylist.unwrap();
2361 let registration = stylist.get_custom_property_registration(&name);
2362 if !registration.is_universal() {
2363 if registration.inherits() && !computed_context.is_root_element() {
2366 let inherited = computed_context.inherited_custom_properties();
2367 if let Some(value) = inherited.get(registration, name) {
2368 substitution_functions.insert_var(registration, name, value.clone());
2369 return;
2370 }
2371 } else if let Some(ref initial_value) = registration.initial_value {
2372 if let Ok(initial_value) = compute_value(
2373 &initial_value.css,
2374 &initial_value.url_data,
2375 registration,
2376 computed_context,
2377 AttrTaint::default(),
2378 ) {
2379 substitution_functions.insert_var(registration, name, initial_value);
2380 return;
2381 }
2382 }
2383 }
2384 substitution_functions.remove_var(registration, name);
2385}
2386
2387fn substitute_references_if_needed_and_apply(
2389 name: &Name,
2390 kind: SubstitutionFunctionKind,
2391 value: &Arc<VariableValue>,
2392 substitution_functions: &mut ComputedSubstitutionFunctions,
2393 stylist: &Stylist,
2394 computed_context: &computed::Context,
2395 attribute_tracker: &mut AttributeTracker,
2396) {
2397 debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
2398 let is_var = kind == SubstitutionFunctionKind::Var;
2399 let registration = stylist.get_custom_property_registration(&name);
2400 if is_var && !value.has_references() && registration.is_universal() {
2401 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
2403 substitution_functions.insert_var(registration, name, computed_value);
2404 return;
2405 }
2406
2407 let inherited = computed_context.inherited_custom_properties();
2408 let url_data = &value.url_data;
2409 let substitution = match substitute_internal(
2410 value,
2411 substitution_functions,
2412 stylist,
2413 computed_context,
2414 attribute_tracker,
2415 None,
2416 ) {
2417 Ok(v) => v,
2418 Err(..) => {
2419 handle_invalid_at_computed_value_time(
2420 name,
2421 kind,
2422 substitution_functions,
2423 computed_context,
2424 );
2425 return;
2426 },
2427 };
2428
2429 {
2432 let css = &substitution.css;
2433 let css_wide_kw = {
2434 let mut input = ParserInput::new(&css);
2435 let mut input = Parser::new(&mut input);
2436 input.try_parse(CSSWideKeyword::parse)
2437 };
2438
2439 if let Ok(kw) = css_wide_kw {
2440 match (
2444 kw,
2445 registration.inherits(),
2446 computed_context.is_root_element(),
2447 ) {
2448 (CSSWideKeyword::Initial, _, _)
2449 | (CSSWideKeyword::Revert, false, _)
2450 | (CSSWideKeyword::RevertLayer, false, _)
2451 | (CSSWideKeyword::RevertRule, false, _)
2452 | (CSSWideKeyword::Unset, false, _)
2453 | (CSSWideKeyword::Revert, true, true)
2454 | (CSSWideKeyword::RevertLayer, true, true)
2455 | (CSSWideKeyword::RevertRule, true, true)
2456 | (CSSWideKeyword::Unset, true, true)
2457 | (CSSWideKeyword::Inherit, _, true) => {
2458 remove_and_insert_initial_value(name, registration, substitution_functions);
2459 },
2460 (CSSWideKeyword::Revert, true, false)
2461 | (CSSWideKeyword::RevertLayer, true, false)
2462 | (CSSWideKeyword::RevertRule, true, false)
2463 | (CSSWideKeyword::Inherit, _, false)
2464 | (CSSWideKeyword::Unset, true, false) => {
2465 match inherited.get(registration, name) {
2466 Some(value) => {
2467 substitution_functions.insert_var(registration, name, value.clone());
2468 },
2469 None => {
2470 substitution_functions.remove_var(registration, name);
2471 },
2472 };
2473 },
2474 }
2475 return;
2476 }
2477 }
2478
2479 match kind {
2480 SubstitutionFunctionKind::Var => {
2481 let value = match substitution.into_value(url_data, registration, computed_context) {
2482 Ok(v) => v,
2483 Err(()) => {
2484 handle_invalid_at_computed_value_time(
2485 name,
2486 kind,
2487 substitution_functions,
2488 computed_context,
2489 );
2490 return;
2491 },
2492 };
2493 substitution_functions.insert_var(registration, name, value);
2494 },
2495 SubstitutionFunctionKind::Attr => {
2496 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2497 substitution.css.into_owned(),
2498 url_data,
2499 substitution.first_token_type,
2500 substitution.last_token_type,
2501 )));
2502 value.attr_tainted |= substitution.attr_tainted;
2503 substitution_functions.insert_attr(name, value);
2504 },
2505 SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
2506 }
2507}
2508
2509#[derive(Default, Debug)]
2510struct Substitution<'a> {
2511 css: Cow<'a, str>,
2512 first_token_type: TokenSerializationType,
2513 last_token_type: TokenSerializationType,
2514 attr_tainted: bool,
2515}
2516
2517impl<'a> Substitution<'a> {
2518 fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
2519 Substitution {
2520 css: v.css.into(),
2521 first_token_type: v.first_token_type,
2522 last_token_type: v.last_token_type,
2523 attr_tainted,
2524 }
2525 }
2526
2527 fn into_value(
2528 self,
2529 url_data: &UrlExtraData,
2530 registration: &PropertyDescriptors,
2531 computed_context: &computed::Context,
2532 ) -> Result<ComputedRegisteredValue, ()> {
2533 if registration.is_universal() {
2534 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2535 self.css.into_owned(),
2536 url_data,
2537 self.first_token_type,
2538 self.last_token_type,
2539 )));
2540 value.attr_tainted |= self.attr_tainted;
2541 return Ok(value);
2542 }
2543 let taint = if self.attr_tainted {
2544 AttrTaint::new_fully_tainted(self.css.len())
2549 } else {
2550 AttrTaint::default()
2551 };
2552 let mut v = compute_value(&self.css, url_data, registration, computed_context, taint)?;
2553 v.attr_tainted |= self.attr_tainted;
2554 Ok(v)
2555 }
2556
2557 fn new(
2558 css: Cow<'a, str>,
2559 first_token_type: TokenSerializationType,
2560 last_token_type: TokenSerializationType,
2561 attr_tainted: bool,
2562 ) -> Self {
2563 Self {
2564 css,
2565 first_token_type,
2566 last_token_type,
2567 attr_tainted,
2568 }
2569 }
2570}
2571
2572#[derive(Debug)]
2574pub struct SubstitutionResult<'a> {
2575 pub css: Cow<'a, str>,
2577 pub attr_taint: AttrTaint,
2579}
2580
2581fn compute_value(
2582 css: &str,
2583 url_data: &UrlExtraData,
2584 registration: &PropertyDescriptors,
2585 computed_context: &computed::Context,
2586 attr_taint: AttrTaint,
2587) -> Result<ComputedRegisteredValue, ()> {
2588 debug_assert!(!registration.is_universal());
2589
2590 let mut input = ParserInput::new(&css);
2591 let mut input = Parser::new(&mut input);
2592
2593 SpecifiedRegisteredValue::compute(
2594 &mut input,
2595 registration,
2596 None,
2597 url_data,
2598 computed_context,
2599 AllowComputationallyDependent::Yes,
2600 attr_taint,
2601 )
2602}
2603
2604fn remove_and_insert_initial_value(
2606 name: &Name,
2607 registration: &PropertyDescriptors,
2608 substitution_functions: &mut ComputedSubstitutionFunctions,
2609) {
2610 substitution_functions.remove_var(registration, name);
2611 if let Some(ref initial_value) = registration.initial_value {
2612 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2613 substitution_functions.insert_var(registration, name, value);
2614 }
2615}
2616
2617fn do_substitute_chunk<'a>(
2618 css: &'a str,
2619 start: usize,
2620 end: usize,
2621 first_token_type: TokenSerializationType,
2622 last_token_type: TokenSerializationType,
2623 url_data: &UrlExtraData,
2624 substitution_functions: &'a ComputedSubstitutionFunctions,
2625 stylist: &Stylist,
2626 computed_context: &computed::Context,
2627 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2628 attribute_tracker: &mut AttributeTracker,
2629 mut attr_taint: Option<&mut AttrTaint>,
2630) -> Result<Substitution<'a>, ()> {
2631 if start == end {
2632 return Ok(Substitution::default());
2634 }
2635 if references
2637 .peek()
2638 .map_or(true, |reference| reference.end > end)
2639 {
2640 let result = &css[start..end];
2641 return Ok(Substitution::new(
2642 Cow::Borrowed(result),
2643 first_token_type,
2644 last_token_type,
2645 Default::default(),
2646 ));
2647 }
2648
2649 let mut substituted = ComputedValue::empty(url_data);
2650 let mut next_token_type = first_token_type;
2651 let mut cur_pos = start;
2652 let mut attr_tainted = false;
2653 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2654 if reference.start != cur_pos {
2655 substituted.push(
2656 &css[cur_pos..reference.start],
2657 next_token_type,
2658 reference.prev_token_type,
2659 None,
2660 )?;
2661 }
2662
2663 let substitution = substitute_one_reference(
2664 css,
2665 url_data,
2666 substitution_functions,
2667 reference,
2668 stylist,
2669 computed_context,
2670 references,
2671 attribute_tracker,
2672 )?;
2673
2674 if reference.start == start && reference.end == end {
2676 if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
2677 taint.push(start, end);
2678 }
2679 return Ok(substitution);
2680 }
2681
2682 substituted.push(
2683 &substitution.css,
2684 substitution.first_token_type,
2685 substitution.last_token_type,
2686 attr_taint
2687 .as_deref_mut()
2688 .filter(|_| substitution.attr_tainted),
2689 )?;
2690 attr_tainted |= substitution.attr_tainted;
2691 next_token_type = reference.next_token_type;
2692 cur_pos = reference.end;
2693 }
2694 if cur_pos != end {
2696 substituted.push(
2697 &css[cur_pos..end],
2698 next_token_type,
2699 last_token_type,
2700 None,
2701 )?;
2702 }
2703 Ok(Substitution::from_value(substituted, attr_tainted))
2704}
2705
2706fn quoted_css_string(src: &str) -> String {
2707 let mut dest = String::with_capacity(src.len() + 2);
2708 cssparser::serialize_string(src, &mut dest).unwrap();
2709 dest
2710}
2711
2712fn substitute_one_reference<'a>(
2713 css: &'a str,
2714 url_data: &UrlExtraData,
2715 substitution_functions: &'a ComputedSubstitutionFunctions,
2716 reference: &SubstitutionFunctionReference,
2717 stylist: &Stylist,
2718 computed_context: &computed::Context,
2719 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2720 attribute_tracker: &mut AttributeTracker,
2721) -> Result<Substitution<'a>, ()> {
2722 let simple_attr_subst = |s: &str| {
2723 Some(Substitution::new(
2724 Cow::Owned(quoted_css_string(s)),
2725 TokenSerializationType::Nothing,
2726 TokenSerializationType::Nothing,
2727 true,
2728 ))
2729 };
2730 let substitution: Option<_> = match reference.substitution_kind {
2731 SubstitutionFunctionKind::Var => {
2732 let registration = stylist.get_custom_property_registration(&reference.name);
2733 substitution_functions
2734 .get_var(registration, &reference.name)
2735 .map(|v| Substitution::from_value(v.to_variable_value(), v.attr_tainted))
2736 },
2737 SubstitutionFunctionKind::Env => {
2738 let device = stylist.device();
2739 device
2740 .environment()
2741 .get(&reference.name, device, url_data)
2742 .map(|v| Substitution::from_value(v, false))
2743 },
2744 SubstitutionFunctionKind::Attr => {
2746 #[cfg(feature = "gecko")]
2747 let local_name = LocalName::cast(&reference.name);
2748 #[cfg(feature = "servo")]
2749 let local_name = LocalName::from(reference.name.as_ref());
2750 let namespace = match reference.attribute_data.namespace {
2751 ParsedNamespace::Known(ref ns) => Some(ns),
2752 ParsedNamespace::Unknown => None,
2753 };
2754 namespace
2755 .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
2756 .map_or_else(
2757 || {
2758 if reference.fallback.is_none()
2761 && reference.attribute_data.kind == AttributeType::None
2762 {
2763 simple_attr_subst("")
2764 } else {
2765 None
2766 }
2767 },
2768 |attr| {
2769 let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
2770 if computed_context.in_container_query {
2777 attr
2778 } else {
2779 substitution_functions
2780 .get_attr(&reference.name)
2781 .map(|v| v.to_variable_value())?
2782 .css
2783 }
2784 } else {
2785 attr
2786 };
2787 let mut input = ParserInput::new(&attr);
2788 let mut parser = Parser::new(&mut input);
2789 match &reference.attribute_data.kind {
2790 AttributeType::Unit(unit) => {
2791 let css = {
2792 parser.expect_number().ok()?;
2794 let mut s = attr.clone();
2795 s.push_str(unit.as_ref());
2796 s
2797 };
2798 let serialization = match unit {
2799 AttrUnit::Number => TokenSerializationType::Number,
2800 AttrUnit::Percentage => TokenSerializationType::Percentage,
2801 _ => TokenSerializationType::Dimension,
2802 };
2803 let value =
2804 ComputedValue::new(css, url_data, serialization, serialization);
2805 Some(Substitution::from_value(
2806 value, true,
2807 ))
2808 },
2809 AttributeType::Type(syntax) => {
2810 let value = SpecifiedRegisteredValue::parse(
2811 &mut parser,
2812 &syntax,
2813 url_data,
2814 None,
2815 AllowComputationallyDependent::Yes,
2816 AttrTaint::default(),
2817 )
2818 .ok()?;
2819 let value = value.to_variable_value();
2820 Some(Substitution::from_value(
2821 value, true,
2822 ))
2823 },
2824 AttributeType::RawString | AttributeType::None => {
2825 simple_attr_subst(&attr)
2826 },
2827 }
2828 },
2829 )
2830 },
2831 };
2832
2833 if let Some(s) = substitution {
2834 while references
2836 .next_if(|next_ref| next_ref.end <= reference.end)
2837 .is_some()
2838 {}
2839 return Ok(s);
2840 }
2841
2842 let Some(ref fallback) = reference.fallback else {
2843 return Err(());
2844 };
2845
2846 do_substitute_chunk(
2847 css,
2848 fallback.start.get(),
2849 reference.end - 1, fallback.first_token_type,
2851 fallback.last_token_type,
2852 url_data,
2853 substitution_functions,
2854 stylist,
2855 computed_context,
2856 references,
2857 attribute_tracker,
2858 None,
2859 )
2860}
2861
2862fn substitute_internal<'a>(
2864 variable_value: &'a VariableValue,
2865 substitution_functions: &'a ComputedSubstitutionFunctions,
2866 stylist: &Stylist,
2867 computed_context: &computed::Context,
2868 attribute_tracker: &mut AttributeTracker,
2869 mut attr_taint: Option<&mut AttrTaint>,
2870) -> Result<Substitution<'a>, ()> {
2871 let mut refs = variable_value.references.refs.iter().peekable();
2872 do_substitute_chunk(
2873 &variable_value.css,
2874 0,
2875 variable_value.css.len(),
2876 variable_value.first_token_type,
2877 variable_value.last_token_type,
2878 &variable_value.url_data,
2879 substitution_functions,
2880 stylist,
2881 computed_context,
2882 &mut refs,
2883 attribute_tracker,
2884 attr_taint.as_deref_mut(),
2885 )
2886}
2887
2888pub fn substitute<'a>(
2890 variable_value: &'a VariableValue,
2891 substitution_functions: &'a ComputedSubstitutionFunctions,
2892 stylist: &Stylist,
2893 computed_context: &computed::Context,
2894 attribute_tracker: &mut AttributeTracker,
2895) -> Result<SubstitutionResult<'a>, ()> {
2896 debug_assert!(variable_value.has_references());
2897 let mut attr_taint = AttrTaint::default();
2898 let v = substitute_internal(
2899 variable_value,
2900 substitution_functions,
2901 stylist,
2902 computed_context,
2903 attribute_tracker,
2904 Some(&mut attr_taint),
2905 )?;
2906 Ok(SubstitutionResult {
2907 css: v.css,
2908 attr_taint,
2909 })
2910}