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::typed_om::{
30 ToTyped, TypedValue, UnparsedSegment, UnparsedValue, VariableReferenceValue,
31};
32use crate::values::computed::{self, ToComputedValue};
33use crate::values::generics::calc::SortKey as AttrUnit;
34use crate::values::specified::NoCalcLength;
35use crate::values::specified::ParsedNamespace;
36use crate::{derives::*, Namespace, Prefix};
37use crate::{Atom, LocalName};
38use cssparser::{
39 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
40};
41use rustc_hash::FxHashMap;
42use selectors::parser::SelectorParseErrorKind;
43use servo_arc::Arc;
44use smallvec::SmallVec;
45use std::borrow::Cow;
46use std::collections::hash_map::Entry;
47use std::fmt::{self, Write};
48use std::ops::{Index, IndexMut};
49use std::{cmp, num};
50use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
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(NoCalcLength::LH) {
514 return Self::FONT_UNITS | Self::LH_UNITS;
515 }
516 if value.eq_ignore_ascii_case(NoCalcLength::EM)
517 || value.eq_ignore_ascii_case(NoCalcLength::EX)
518 || value.eq_ignore_ascii_case(NoCalcLength::CAP)
519 || value.eq_ignore_ascii_case(NoCalcLength::CH)
520 || value.eq_ignore_ascii_case(NoCalcLength::IC)
521 {
522 return Self::FONT_UNITS;
523 }
524 if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
525 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
526 }
527 if value.eq_ignore_ascii_case(NoCalcLength::REM)
528 || value.eq_ignore_ascii_case(NoCalcLength::REX)
529 || value.eq_ignore_ascii_case(NoCalcLength::RCH)
530 || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
531 || value.eq_ignore_ascii_case(NoCalcLength::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 Invalid,
656 None,
657 RawString,
658 Type(SyntaxDescriptor),
659 Unit(AttrUnit),
660}
661
662#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
663struct AttributeData {
664 kind: AttributeType,
665 namespace: ParsedNamespace,
666}
667
668#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
670pub struct AttrTaintedRange {
671 start: usize,
673 end: usize,
675}
676
677impl AttrTaintedRange {
678 #[inline(always)]
680 pub fn new(start: usize, end: usize) -> Self {
681 debug_assert!(start <= end);
682 Self { start, end }
683 }
684}
685
686#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
691pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
692
693impl AttrTaint {
694 #[inline(always)]
697 pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
698 self.0
699 .iter()
700 .any(|r| r.start <= range.end && r.end >= range.start)
701 }
702
703 #[inline(always)]
705 pub fn is_empty(&self) -> bool {
706 self.0.is_empty()
707 }
708
709 #[inline(always)]
710 fn new_fully_tainted(end: usize) -> Self {
711 let mut taint = Self::default();
712 taint.push(0, end);
713 taint
714 }
715
716 #[inline(always)]
717 fn push(&mut self, start: usize, end: usize) {
718 self.0.push(AttrTaintedRange::new(start, end));
719 }
720}
721
722#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
723struct VariableFallback {
724 start: num::NonZeroUsize,
728 first_token_type: TokenSerializationType,
729 last_token_type: TokenSerializationType,
730}
731
732#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
733struct SubstitutionFunctionReference {
734 name: Name,
735 start: usize,
736 end: usize,
737 fallback: Option<VariableFallback>,
738 attribute_data: AttributeData,
739 prev_token_type: TokenSerializationType,
740 next_token_type: TokenSerializationType,
741 substitution_kind: SubstitutionFunctionKind,
742}
743
744#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
747struct References {
748 refs: Vec<SubstitutionFunctionReference>,
749 non_custom_references: NonCustomReferences,
750 any_env: bool,
751 any_var: bool,
752 any_attr: bool,
753}
754
755impl References {
756 fn has_references(&self) -> bool {
757 !self.refs.is_empty()
758 }
759
760 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
761 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
762 if is_root_element {
763 mask |= NonCustomReferences::ROOT_DEPENDENCIES
764 }
765 self.non_custom_references & mask
766 }
767}
768
769impl VariableValue {
770 fn empty(url_data: &UrlExtraData) -> Self {
771 Self {
772 css: String::new(),
773 last_token_type: Default::default(),
774 first_token_type: Default::default(),
775 url_data: url_data.clone(),
776 references: Default::default(),
777 }
778 }
779
780 pub fn new(
783 css: String,
784 url_data: &UrlExtraData,
785 first_token_type: TokenSerializationType,
786 last_token_type: TokenSerializationType,
787 ) -> Self {
788 Self {
789 css,
790 url_data: url_data.clone(),
791 first_token_type,
792 last_token_type,
793 references: References::default(),
794 }
795 }
796
797 fn push<'i>(
798 &mut self,
799 css: &str,
800 css_first_token_type: TokenSerializationType,
801 css_last_token_type: TokenSerializationType,
802 attr_taint: Option<&mut AttrTaint>,
803 ) -> Result<(), ()> {
804 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
812
813 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
814 return Err(());
815 }
816
817 if css.is_empty() {
822 return Ok(());
823 }
824
825 self.first_token_type.set_if_nothing(css_first_token_type);
826 if self
829 .last_token_type
830 .needs_separator_when_before(css_first_token_type)
831 {
832 self.css.push_str("/**/")
833 }
834 let start = self.css.len();
835 self.css.push_str(css);
836 let end = self.css.len();
837 if let Some(taint) = attr_taint {
838 taint.push(start, end);
839 }
840 self.last_token_type = css_last_token_type;
841 Ok(())
842 }
843
844 pub fn parse<'i, 't>(
846 input: &mut Parser<'i, 't>,
847 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
848 url_data: &UrlExtraData,
849 ) -> Result<Self, ParseError<'i>> {
850 let mut references = References::default();
851 let mut missing_closing_characters = String::new();
852 let start_position = input.position();
853 let (first_token_type, last_token_type) = parse_declaration_value(
854 input,
855 start_position,
856 namespaces,
857 &mut references,
858 &mut missing_closing_characters,
859 )?;
860 let mut css = input
861 .slice_from(start_position)
862 .trim_ascii_start()
863 .to_owned();
864 if !missing_closing_characters.is_empty() {
865 if css.ends_with("\\")
867 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
868 {
869 css.pop();
870 }
871 css.push_str(&missing_closing_characters);
872 }
873
874 css.truncate(css.trim_ascii_end().len());
875 css.shrink_to_fit();
876 references.refs.shrink_to_fit();
877
878 Ok(Self {
879 css,
880 url_data: url_data.clone(),
881 first_token_type,
882 last_token_type,
883 references,
884 })
885 }
886
887 pub fn is_attr_tainted(&self) -> bool {
889 self.references.any_attr
890 }
891
892 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
894 Self::from_token(
895 Token::Number {
896 has_sign: false,
897 value: number as f32,
898 int_value: Some(number),
899 },
900 url_data,
901 )
902 }
903
904 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
906 Self::from_token(Token::Ident(ident.into()), url_data)
907 }
908
909 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
911 Self::from_token(
915 Token::Dimension {
916 has_sign: false,
917 value: number,
918 int_value: None,
919 unit: CowRcStr::from("px"),
920 },
921 url_data,
922 )
923 }
924
925 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
927 Self::from_token(
928 Token::Dimension {
929 has_sign: false,
930 value: number as f32,
931 int_value: Some(number),
932 unit: CowRcStr::from("ms"),
933 },
934 url_data,
935 )
936 }
937
938 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
940 Self::from_token(
941 Token::Dimension {
942 has_sign: false,
943 value: number as f32,
944 int_value: Some(number),
945 unit: CowRcStr::from("px"),
946 },
947 url_data,
948 )
949 }
950
951 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
952 let token_type = token.serialization_type();
953 let mut css = token.to_css_string();
954 css.shrink_to_fit();
955
956 VariableValue {
957 css,
958 url_data: url_data.clone(),
959 first_token_type: token_type,
960 last_token_type: token_type,
961 references: Default::default(),
962 }
963 }
964
965 pub fn css_text(&self) -> &str {
967 &self.css
968 }
969
970 pub fn has_references(&self) -> bool {
973 self.references.has_references()
974 }
975}
976
977fn parse_declaration_value<'i, 't>(
979 input: &mut Parser<'i, 't>,
980 input_start: SourcePosition,
981 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
982 references: &mut References,
983 missing_closing_characters: &mut String,
984) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
985 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
986 parse_declaration_value_block(
987 input,
988 input_start,
989 namespaces,
990 references,
991 missing_closing_characters,
992 )
993 })
994}
995
996fn parse_declaration_value_block<'i, 't>(
998 input: &mut Parser<'i, 't>,
999 input_start: SourcePosition,
1000 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1001 references: &mut References,
1002 missing_closing_characters: &mut String,
1003) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
1004 let mut is_first = true;
1005 let mut first_token_type = TokenSerializationType::Nothing;
1006 let mut last_token_type = TokenSerializationType::Nothing;
1007 let mut prev_reference_index: Option<usize> = None;
1008 loop {
1009 let token_start = input.position();
1010 let Ok(token) = input.next_including_whitespace_and_comments() else {
1011 break;
1012 };
1013
1014 let prev_token_type = last_token_type;
1015 let serialization_type = token.serialization_type();
1016 last_token_type = serialization_type;
1017 if is_first {
1018 first_token_type = last_token_type;
1019 is_first = false;
1020 }
1021
1022 macro_rules! nested {
1023 ($closing:expr) => {{
1024 let mut inner_end_position = None;
1025 let result = input.parse_nested_block(|input| {
1026 let result = parse_declaration_value_block(
1027 input,
1028 input_start,
1029 namespaces,
1030 references,
1031 missing_closing_characters,
1032 )?;
1033 inner_end_position = Some(input.position());
1034 Ok(result)
1035 })?;
1036 if inner_end_position.unwrap() == input.position() {
1037 missing_closing_characters.push_str($closing);
1038 }
1039 result
1040 }};
1041 }
1042 if let Some(index) = prev_reference_index.take() {
1043 references.refs[index].next_token_type = serialization_type;
1044 }
1045 match *token {
1046 Token::Comment(_) => {
1047 let token_slice = input.slice_from(token_start);
1048 if !token_slice.ends_with("*/") {
1049 missing_closing_characters.push_str(if token_slice.ends_with('*') {
1050 "/"
1051 } else {
1052 "*/"
1053 })
1054 }
1055 },
1056 Token::BadUrl(ref u) => {
1057 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
1058 return Err(input.new_custom_error(e));
1059 },
1060 Token::BadString(ref s) => {
1061 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
1062 return Err(input.new_custom_error(e));
1063 },
1064 Token::CloseParenthesis => {
1065 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1066 return Err(input.new_custom_error(e));
1067 },
1068 Token::CloseSquareBracket => {
1069 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1070 return Err(input.new_custom_error(e));
1071 },
1072 Token::CloseCurlyBracket => {
1073 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1074 return Err(input.new_custom_error(e));
1075 },
1076 Token::Function(ref name) => {
1077 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
1078 Some(SubstitutionFunctionKind::Attr) => {
1079 if static_prefs::pref!("layout.css.attr.enabled") {
1080 Some(SubstitutionFunctionKind::Attr)
1081 } else {
1082 None
1083 }
1084 },
1085 kind => kind,
1086 };
1087 if let Some(substitution_kind) = substitution_kind {
1088 let our_ref_index = references.refs.len();
1089 let mut input_end_position = None;
1090 let fallback = input.parse_nested_block(|input| {
1091 let mut namespace = ParsedNamespace::Known(Namespace::default());
1092 if substitution_kind == SubstitutionFunctionKind::Attr {
1093 if let Some(namespaces) = namespaces {
1094 if let Ok(ns) = input
1095 .try_parse(|input| ParsedNamespace::parse(namespaces, input))
1096 {
1097 namespace = ns;
1098 let prev = input.state();
1099 let next = match *input.next_including_whitespace()? {
1100 Token::Ident(_) => Ok(()),
1101 ref t => Err(prev
1102 .source_location()
1103 .new_unexpected_token_error(t.clone())),
1104 };
1105 input.reset(&prev);
1106 next?;
1107 }
1108 }
1109 }
1110 let name = input.expect_ident()?;
1113 let name =
1114 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
1115 match parse_name(name.as_ref()) {
1116 Ok(name) => name,
1117 Err(()) => {
1118 let name = name.clone();
1119 return Err(input.new_custom_error(
1120 SelectorParseErrorKind::UnexpectedIdent(name),
1121 ));
1122 },
1123 }
1124 } else {
1125 name.as_ref()
1126 });
1127
1128 let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
1129 {
1130 parse_attr_type(input)
1131 } else {
1132 AttributeType::None
1133 };
1134
1135 let start = token_start.byte_index() - input_start.byte_index();
1139 references.refs.push(SubstitutionFunctionReference {
1140 name,
1141 start,
1142 end: start,
1144 prev_token_type,
1145 next_token_type: TokenSerializationType::Nothing,
1147 fallback: None,
1149 attribute_data: AttributeData {
1150 kind: attribute_kind,
1151 namespace,
1152 },
1153 substitution_kind: substitution_kind.clone(),
1154 });
1155
1156 let mut fallback = None;
1157 if input.try_parse(|input| input.expect_comma()).is_ok() {
1158 input.skip_whitespace();
1159 let fallback_start = num::NonZeroUsize::new(
1160 input.position().byte_index() - input_start.byte_index(),
1161 )
1162 .unwrap();
1163 let (first, last) = parse_declaration_value(
1166 input,
1167 input_start,
1168 namespaces,
1169 references,
1170 missing_closing_characters,
1171 )?;
1172 fallback = Some(VariableFallback {
1173 start: fallback_start,
1174 first_token_type: first,
1175 last_token_type: last,
1176 });
1177 input_end_position = Some(input.position());
1178 } else {
1179 let state = input.state();
1180 parse_declaration_value_block(
1184 input,
1185 input_start,
1186 namespaces,
1187 references,
1188 missing_closing_characters,
1189 )?;
1190 input_end_position = Some(input.position());
1191 input.reset(&state);
1192 }
1193 Ok(fallback)
1194 })?;
1195 if input_end_position.unwrap() == input.position() {
1196 missing_closing_characters.push_str(")");
1197 }
1198 prev_reference_index = Some(our_ref_index);
1199 let reference = &mut references.refs[our_ref_index];
1200 reference.end = input.position().byte_index() - input_start.byte_index()
1201 + missing_closing_characters.len();
1202 reference.fallback = fallback;
1203 match substitution_kind {
1204 SubstitutionFunctionKind::Var => references.any_var = true,
1205 SubstitutionFunctionKind::Env => references.any_env = true,
1206 SubstitutionFunctionKind::Attr => references.any_attr = true,
1207 };
1208 } else {
1209 nested!(")");
1210 }
1211 },
1212 Token::ParenthesisBlock => {
1213 nested!(")");
1214 },
1215 Token::CurlyBracketBlock => {
1216 nested!("}");
1217 },
1218 Token::SquareBracketBlock => {
1219 nested!("]");
1220 },
1221 Token::QuotedString(_) => {
1222 let token_slice = input.slice_from(token_start);
1223 let quote = &token_slice[..1];
1224 debug_assert!(matches!(quote, "\"" | "'"));
1225 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1226 missing_closing_characters.push_str(quote)
1227 }
1228 },
1229 Token::Ident(ref value)
1230 | Token::AtKeyword(ref value)
1231 | Token::Hash(ref value)
1232 | Token::IDHash(ref value)
1233 | Token::UnquotedUrl(ref value)
1234 | Token::Dimension {
1235 unit: ref value, ..
1236 } => {
1237 references
1238 .non_custom_references
1239 .insert(NonCustomReferences::from_unit(value));
1240 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1241 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1242 missing_closing_characters.push_str("�")
1247 }
1248 if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1249 missing_closing_characters.push_str(")");
1250 }
1251 },
1252 _ => {},
1253 };
1254 }
1255 Ok((first_token_type, last_token_type))
1256}
1257
1258fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1261 input
1262 .try_parse(|input| {
1263 Ok(match input.next()? {
1264 Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1265 AttributeType::Type(
1266 input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1267 )
1268 },
1269 Token::Ident(ref ident) => {
1270 if ident.eq_ignore_ascii_case("raw-string") {
1271 AttributeType::RawString
1272 } else if let Ok(unit) = AttrUnit::from_ident(ident) {
1273 AttributeType::Unit(unit)
1274 } else {
1275 AttributeType::Invalid
1276 }
1277 },
1278 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1279 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1280 })
1281 })
1282 .unwrap_or(AttributeType::None)
1283}
1284
1285fn get_attr_value_for_cycle_resolution(
1288 name: &Atom,
1289 attribute_data: &AttributeData,
1290 url_data: &UrlExtraData,
1291 attribute_tracker: &mut AttributeTracker,
1292) -> Result<ComputedRegisteredValue, ()> {
1293 if !matches!(&attribute_data.kind, AttributeType::Type(_)) {
1296 return Err(());
1297 }
1298 #[cfg(feature = "gecko")]
1299 let local_name = LocalName::cast(name);
1300 #[cfg(feature = "servo")]
1301 let local_name = &LocalName::from(name.as_ref());
1302 let namespace = match attribute_data.namespace {
1303 ParsedNamespace::Known(ref ns) => ns,
1304 ParsedNamespace::Unknown => return Err(()),
1305 };
1306 let attr = attribute_tracker.query(local_name, namespace).ok_or(())?;
1307 let mut input = ParserInput::new(&attr);
1308 let mut parser = Parser::new(&mut input);
1309 let value = VariableValue::parse(&mut parser, None, &url_data).map_err(|_| ())?;
1311 Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1312}
1313
1314#[derive(Default)]
1315struct SeenSubstitutionFunctions<'a> {
1316 var: PrecomputedHashSet<&'a Name>,
1317 attr: PrecomputedHashSet<&'a Name>,
1318}
1319
1320pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
1322 seen: SeenSubstitutionFunctions<'a>,
1323 may_have_cycles: bool,
1324 has_color_scheme: bool,
1325 substitution_functions: ComputedSubstitutionFunctions,
1326 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, RevertKind)>,
1327 stylist: &'a Stylist,
1328 computed_context: &'a mut computed::Context<'b>,
1329 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
1330}
1331
1332fn find_non_custom_references(
1333 registration: &PropertyDescriptors,
1334 value: &VariableValue,
1335 may_have_color_scheme: bool,
1336 is_root_element: bool,
1337 include_universal: bool,
1338) -> Option<NonCustomReferences> {
1339 let syntax = registration.syntax.as_ref()?;
1340 let dependent_types = syntax.dependent_types();
1341 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
1342 || (include_universal && syntax.is_universal());
1343 if may_reference_length {
1344 let value_dependencies = value.references.non_custom_references(is_root_element);
1345 if !value_dependencies.is_empty() {
1346 return Some(value_dependencies);
1347 }
1348 }
1349 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
1350 return Some(NonCustomReferences::empty());
1354 }
1355 None
1356}
1357
1358impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
1359 pub fn new_with_properties(
1363 stylist: &'a Stylist,
1364 custom_properties: ComputedCustomProperties,
1365 computed_context: &'a mut computed::Context<'b>,
1366 ) -> Self {
1367 Self {
1368 seen: SeenSubstitutionFunctions::default(),
1369 reverted: Default::default(),
1370 may_have_cycles: false,
1371 has_color_scheme: false,
1372 substitution_functions: ComputedSubstitutionFunctions::new(
1373 Some(custom_properties),
1374 None,
1375 ),
1376 stylist,
1377 computed_context,
1378 references_from_non_custom_properties: NonCustomReferenceMap::default(),
1379 }
1380 }
1381
1382 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1384 let is_root_element = context.is_root_element();
1385
1386 let inherited = context.inherited_custom_properties();
1387 let initial_values = stylist.get_custom_property_initial_values();
1388 let properties = ComputedCustomProperties {
1389 inherited: if is_root_element {
1390 debug_assert!(inherited.is_empty());
1391 initial_values.inherited.clone()
1392 } else {
1393 inherited.inherited.clone()
1394 },
1395 non_inherited: initial_values.non_inherited.clone(),
1396 };
1397
1398 context
1401 .style()
1402 .add_flags(stylist.get_custom_property_initial_values_flags());
1403 Self::new_with_properties(stylist, properties, context)
1404 }
1405
1406 pub fn cascade(
1408 &mut self,
1409 declaration: &'a CustomDeclaration,
1410 priority: CascadePriority,
1411 attribute_tracker: &mut AttributeTracker,
1412 ) {
1413 let CustomDeclaration {
1414 ref name,
1415 ref value,
1416 } = *declaration;
1417
1418 if let Some(&(reverted_priority, revert_kind)) = self.reverted.get(&name) {
1419 if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1420 return;
1421 }
1422 }
1423
1424 if !(priority.flags() - self.computed_context.included_cascade_flags).is_empty() {
1425 return;
1426 }
1427
1428 let was_already_present = !self.seen.var.insert(name);
1429 if was_already_present {
1430 return;
1431 }
1432
1433 if !self.value_may_affect_style(name, value) {
1434 return;
1435 }
1436
1437 let kind = SubstitutionFunctionKind::Var;
1438 let map = &mut self.substitution_functions;
1439 let registration = self.stylist.get_custom_property_registration(&name);
1440 match value {
1441 CustomDeclarationValue::Unparsed(unparsed_value) => {
1442 let may_have_color_scheme = true;
1447 let has_dependency = unparsed_value.references.any_var
1450 || unparsed_value.references.any_attr
1451 || find_non_custom_references(
1452 registration,
1453 unparsed_value,
1454 may_have_color_scheme,
1455 self.computed_context.is_root_element(),
1456 false,
1457 )
1458 .is_some();
1459 if !has_dependency {
1463 return substitute_references_if_needed_and_apply(
1464 name,
1465 kind,
1466 unparsed_value,
1467 &mut self.substitution_functions,
1468 self.stylist,
1469 self.computed_context,
1470 attribute_tracker,
1471 );
1472 }
1473 self.may_have_cycles = true;
1474 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1475 map.insert_var(registration, name, value);
1476 },
1477 CustomDeclarationValue::Parsed(parsed_value) => {
1478 let value = parsed_value.to_computed_value(&self.computed_context);
1479 map.insert_var(registration, name, value);
1480 },
1481 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword.revert_kind() {
1482 Some(revert_kind) => {
1483 self.seen.var.remove(name);
1484 self.reverted.insert(name, (priority, revert_kind));
1485 },
1486 None => match keyword {
1487 CSSWideKeyword::Initial => {
1488 debug_assert!(registration.inherits(), "Should've been handled earlier");
1490 remove_and_insert_initial_value(name, registration, map);
1491 },
1492 CSSWideKeyword::Inherit => {
1493 debug_assert!(!registration.inherits(), "Should've been handled earlier");
1495 self.computed_context
1496 .style()
1497 .add_flags(ComputedValueFlags::INHERITS_RESET_STYLE);
1498 if let Some(inherited_value) = self
1499 .computed_context
1500 .inherited_custom_properties()
1501 .non_inherited
1502 .get(name)
1503 {
1504 map.insert_var(registration, name, inherited_value.clone());
1505 }
1506 },
1507 CSSWideKeyword::Revert
1509 | CSSWideKeyword::RevertLayer
1510 | CSSWideKeyword::RevertRule
1511 | CSSWideKeyword::Unset => unreachable!(),
1512 },
1513 },
1514 }
1515 }
1516
1517 #[inline]
1519 pub fn might_have_non_custom_or_attr_dependency(
1520 id: LonghandId,
1521 decl: &PropertyDeclaration,
1522 ) -> bool {
1523 if id == LonghandId::ColorScheme {
1524 return true;
1525 }
1526 if let PropertyDeclaration::WithVariables(v) = decl {
1527 return matches!(id, LonghandId::LineHeight | LonghandId::FontSize)
1528 || v.value.variable_value.references.any_attr;
1529 }
1530 false
1531 }
1532
1533 pub fn maybe_note_non_custom_dependency(
1536 &mut self,
1537 id: LonghandId,
1538 decl: &'a PropertyDeclaration,
1539 attribute_tracker: &mut AttributeTracker,
1540 ) {
1541 debug_assert!(Self::might_have_non_custom_or_attr_dependency(id, decl));
1542 if id == LonghandId::ColorScheme {
1543 self.has_color_scheme = true;
1545 return;
1546 }
1547
1548 let PropertyDeclaration::WithVariables(v) = decl else {
1549 return;
1550 };
1551 let value = &v.value.variable_value;
1552 let refs = &value.references;
1553
1554 if !refs.any_var && !refs.any_attr {
1555 return;
1556 }
1557
1558 if refs.any_attr {
1562 self.update_attributes_map(value, attribute_tracker);
1563 if !refs.any_var {
1564 return;
1565 }
1566 }
1567
1568 let references = match id {
1572 LonghandId::FontSize => {
1573 if self.computed_context.is_root_element() {
1574 NonCustomReferences::ROOT_FONT_UNITS
1575 } else {
1576 NonCustomReferences::FONT_UNITS
1577 }
1578 },
1579 LonghandId::LineHeight => {
1580 if self.computed_context.is_root_element() {
1581 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1582 } else {
1583 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1584 }
1585 },
1586 _ => return,
1587 };
1588
1589 let variables: Vec<Atom> = refs
1590 .refs
1591 .iter()
1592 .filter_map(|reference| {
1593 if reference.substitution_kind != SubstitutionFunctionKind::Var {
1594 return None;
1595 }
1596 let registration = self
1597 .stylist
1598 .get_custom_property_registration(&reference.name);
1599 if !registration
1600 .syntax
1601 .as_ref()?
1602 .dependent_types()
1603 .intersects(DependentDataTypes::LENGTH)
1604 {
1605 return None;
1606 }
1607 Some(reference.name.clone())
1608 })
1609 .collect();
1610 references.for_each(|idx| {
1611 let entry = &mut self.references_from_non_custom_properties[idx];
1612 let was_none = entry.is_none();
1613 let v = entry.get_or_insert_with(|| variables.clone());
1614 if was_none {
1615 return;
1616 }
1617 v.extend(variables.iter().cloned());
1618 });
1619 }
1620
1621 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1622 let registration = self.stylist.get_custom_property_registration(&name);
1623 match *value {
1624 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1625 if registration.inherits() {
1629 return false;
1630 }
1631 },
1632 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1633 if !registration.inherits() {
1636 return false;
1637 }
1638 },
1639 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1640 return false;
1644 },
1645 _ => {},
1646 }
1647
1648 let existing_value = self.substitution_functions.get_var(registration, &name);
1649 let Some(existing_value) = existing_value else {
1650 if matches!(
1651 value,
1652 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1653 ) {
1654 debug_assert!(registration.inherits(), "Should've been handled earlier");
1655 if registration.initial_value.is_none() {
1659 return false;
1660 }
1661 }
1662 return true;
1663 };
1664 match value {
1665 CustomDeclarationValue::Unparsed(value) => {
1666 if let Some(existing_value) = existing_value.as_universal() {
1669 return existing_value != value;
1670 }
1671 },
1672 CustomDeclarationValue::Parsed(..) => {
1673 },
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 },
1713 };
1714
1715 true
1716 }
1717
1718 pub fn update_attributes_map(
1720 &mut self,
1721 value: &'a VariableValue,
1722 attribute_tracker: &mut AttributeTracker,
1723 ) {
1724 let refs = &value.references;
1725 if !refs.any_attr {
1726 return;
1727 }
1728 self.may_have_cycles = true;
1729
1730 for next in &refs.refs {
1731 if next.substitution_kind != SubstitutionFunctionKind::Attr
1733 || !self.seen.attr.insert(&next.name)
1734 {
1735 continue;
1736 }
1737 if let Ok(v) = get_attr_value_for_cycle_resolution(
1738 &next.name,
1739 &next.attribute_data,
1740 &value.url_data,
1741 attribute_tracker,
1742 ) {
1743 self.substitution_functions.insert_attr(&next.name, v);
1744 }
1745 }
1746 }
1747
1748 pub fn build(
1764 mut self,
1765 defer: DeferFontRelativeCustomPropertyResolution,
1766 attribute_tracker: &mut AttributeTracker,
1767 ) -> Option<AllSubstitutionFunctions> {
1768 let mut deferred_substitution_functions = None;
1769 if self.may_have_cycles {
1770 if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1771 deferred_substitution_functions = Some(AllSubstitutionFunctions::default());
1772 }
1773 let mut invalid_non_custom_properties = LonghandIdSet::default();
1774 substitute_all(
1775 &mut self.substitution_functions,
1776 deferred_substitution_functions.as_mut(),
1777 &mut invalid_non_custom_properties,
1778 self.has_color_scheme,
1779 &self.seen,
1780 &self.references_from_non_custom_properties,
1781 self.stylist,
1782 self.computed_context,
1783 attribute_tracker,
1784 );
1785 self.computed_context.builder.invalid_non_custom_properties =
1786 invalid_non_custom_properties;
1787 }
1788 self.substitution_functions
1789 .custom_properties
1790 .shrink_to_fit();
1791
1792 let initial_values = self.stylist.get_custom_property_initial_values();
1797 let custom_properties = self.substitution_functions.custom_properties;
1798 self.computed_context
1799 .builder
1800 .substitution_functions
1801 .custom_properties = ComputedCustomProperties {
1802 inherited: if self
1803 .computed_context
1804 .inherited_custom_properties()
1805 .inherited
1806 == custom_properties.inherited
1807 {
1808 self.computed_context
1809 .inherited_custom_properties()
1810 .inherited
1811 .clone()
1812 } else {
1813 custom_properties.inherited
1814 },
1815 non_inherited: if initial_values.non_inherited == custom_properties.non_inherited {
1816 initial_values.non_inherited.clone()
1817 } else {
1818 custom_properties.non_inherited
1819 },
1820 };
1821 self.computed_context
1822 .builder
1823 .substitution_functions
1824 .attributes = self.substitution_functions.attributes;
1825
1826 deferred_substitution_functions
1827 }
1828
1829 pub fn build_deferred(
1832 deferred: AllSubstitutionFunctions,
1833 stylist: &Stylist,
1834 computed_context: &mut computed::Context,
1835 attribute_tracker: &mut AttributeTracker,
1836 ) {
1837 if deferred.is_empty() {
1838 return;
1839 }
1840 let mut map = std::mem::take(&mut computed_context.builder.substitution_functions);
1841 for (name, kind, v) in deferred.iter() {
1844 let Some(v) = v.as_universal() else {
1845 unreachable!("Computing should have been deferred!")
1846 };
1847 substitute_references_if_needed_and_apply(
1848 name,
1849 kind,
1850 v,
1851 &mut map,
1852 stylist,
1853 computed_context,
1854 attribute_tracker,
1855 );
1856 }
1857 computed_context.builder.substitution_functions = map;
1858 }
1859}
1860
1861fn substitute_all(
1866 substitution_function_map: &mut ComputedSubstitutionFunctions,
1867 mut deferred_substituted_functions_map: Option<&mut AllSubstitutionFunctions>,
1868 invalid_non_custom_properties: &mut LonghandIdSet,
1869 has_color_scheme: bool,
1870 seen: &SeenSubstitutionFunctions,
1871 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1872 stylist: &Stylist,
1873 computed_context: &computed::Context,
1874 attr_tracker: &mut AttributeTracker,
1875) {
1876 #[derive(Clone, Eq, PartialEq, Debug)]
1883 enum VarType {
1884 Attr(Name),
1885 Custom(Name),
1886 NonCustom(SingleNonCustomReference),
1887 }
1888
1889 #[derive(Debug)]
1891 struct VarInfo {
1892 var: Option<VarType>,
1897 lowlink: usize,
1902 }
1903
1904 #[derive(Debug, Default)]
1905 struct OrderIndexMap {
1906 var: PrecomputedHashMap<Name, usize>,
1908 attr: PrecomputedHashMap<Name, usize>,
1910 }
1911
1912 struct Context<'a, 'b: 'a> {
1915 count: usize,
1918 index_map: OrderIndexMap,
1920 non_custom_index_map: NonCustomReferenceMap<usize>,
1922 var_info: SmallVec<[VarInfo; 5]>,
1924 stack: SmallVec<[usize; 5]>,
1927 non_custom_references: NonCustomReferences,
1929 has_color_scheme: bool,
1931 contains_computed_custom_property: bool,
1934 map: &'a mut ComputedSubstitutionFunctions,
1935 stylist: &'a Stylist,
1938 computed_context: &'a computed::Context<'b>,
1941 invalid_non_custom_properties: &'a mut LonghandIdSet,
1943 deferred_substitution_functions: Option<&'a mut AllSubstitutionFunctions>,
1947 }
1948
1949 fn traverse<'a, 'b>(
1968 var: VarType,
1969 non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1970 context: &mut Context<'a, 'b>,
1971 attribute_tracker: &mut AttributeTracker,
1972 ) -> Option<usize> {
1973 let kind = if matches!(var, VarType::Custom(_)) {
1974 SubstitutionFunctionKind::Var
1975 } else {
1976 SubstitutionFunctionKind::Attr
1977 };
1978 let value = match var {
1980 VarType::Custom(ref name) | VarType::Attr(ref name) => {
1981 let registration;
1982 let value;
1983 match kind {
1984 SubstitutionFunctionKind::Var => {
1985 registration = context.stylist.get_custom_property_registration(name);
1986 value = context.map.get_var(registration, name)?.as_universal()?;
1987 },
1988 SubstitutionFunctionKind::Attr => {
1989 registration = PropertyDescriptors::unregistered();
1991 value = context.map.get_attr(name)?.as_universal()?;
1992 },
1993 _ => unreachable!("Substitution kind must be var or attr for VarType::Custom."),
1994 }
1995 let is_var = matches!(kind, SubstitutionFunctionKind::Var);
1996 let is_attr = matches!(kind, SubstitutionFunctionKind::Attr);
1997 let is_root = context.computed_context.is_root_element();
1998 let non_custom_refs = find_non_custom_references(
2001 registration,
2002 value,
2003 context.has_color_scheme,
2004 is_root,
2005 true,
2006 );
2007 context.non_custom_references |= non_custom_refs.unwrap_or_default();
2008 let has_dependency = value.references.any_var
2009 || value.references.any_attr
2010 || non_custom_refs.is_some();
2011 if !has_dependency {
2013 debug_assert!(!value.references.any_env, "Should've been handled earlier");
2014 if is_attr || !registration.is_universal() {
2015 if is_var {
2020 debug_assert!(
2021 registration
2022 .syntax
2023 .as_ref()
2024 .unwrap()
2025 .dependent_types()
2026 .intersects(DependentDataTypes::COLOR),
2027 "How did an unresolved value get here otherwise?",
2028 );
2029 }
2030 let value = value.clone();
2031 substitute_references_if_needed_and_apply(
2032 name,
2033 kind,
2034 &value,
2035 &mut context.map,
2036 context.stylist,
2037 context.computed_context,
2038 attribute_tracker,
2039 );
2040 }
2041 return None;
2042 }
2043
2044 let index_map = if is_var {
2046 &mut context.index_map.var
2047 } else {
2048 &mut context.index_map.attr
2049 };
2050 match index_map.entry(name.clone()) {
2051 Entry::Occupied(entry) => {
2052 return Some(*entry.get());
2053 },
2054 Entry::Vacant(entry) => {
2055 entry.insert(context.count);
2056 },
2057 }
2058 context.contains_computed_custom_property |= is_var && !registration.is_universal();
2059
2060 Some(value.clone())
2063 },
2064 VarType::NonCustom(ref non_custom) => {
2065 let entry = &mut context.non_custom_index_map[*non_custom];
2066 if let Some(v) = entry {
2067 return Some(*v);
2068 }
2069 *entry = Some(context.count);
2070 None
2071 },
2072 };
2073
2074 let index = context.count;
2076 context.count += 1;
2077 debug_assert_eq!(index, context.var_info.len());
2078 context.var_info.push(VarInfo {
2079 var: Some(var.clone()),
2080 lowlink: index,
2081 });
2082 context.stack.push(index);
2083
2084 let mut self_ref = false;
2085 let mut lowlink = index;
2086 let visit_link = |var: VarType,
2087 context: &mut Context,
2088 lowlink: &mut usize,
2089 self_ref: &mut bool,
2090 attr_tracker: &mut AttributeTracker| {
2091 let next_index = match traverse(var, non_custom_references, context, attr_tracker) {
2092 Some(index) => index,
2093 None => {
2096 return;
2097 },
2098 };
2099 let next_info = &context.var_info[next_index];
2100 if next_index > index {
2101 *lowlink = cmp::min(*lowlink, next_info.lowlink);
2105 } else if next_index == index {
2106 *self_ref = true;
2107 } else if next_info.var.is_some() {
2108 *lowlink = cmp::min(*lowlink, next_index);
2111 }
2112 };
2113 if let Some(ref v) = value.as_ref() {
2114 debug_assert!(
2115 matches!(var, VarType::Custom(_) | VarType::Attr(_)),
2116 "Non-custom property has references?"
2117 );
2118
2119 for next in &v.references.refs {
2122 if next.substitution_kind == SubstitutionFunctionKind::Env {
2123 continue;
2124 }
2125
2126 let next_var = if next.substitution_kind == SubstitutionFunctionKind::Attr {
2127 if context.map.get_attr(&next.name).is_none() {
2128 let Ok(val) = get_attr_value_for_cycle_resolution(
2129 &next.name,
2130 &next.attribute_data,
2131 &v.url_data,
2132 attribute_tracker,
2133 ) else {
2134 continue;
2135 };
2136 context.map.insert_attr(&next.name, val);
2137 }
2138 VarType::Attr(next.name.clone())
2139 } else {
2140 VarType::Custom(next.name.clone())
2141 };
2142
2143 visit_link(
2144 next_var,
2145 context,
2146 &mut lowlink,
2147 &mut self_ref,
2148 attribute_tracker,
2149 );
2150 }
2151
2152 v.references.non_custom_references.for_each(|r| {
2154 visit_link(
2155 VarType::NonCustom(r),
2156 context,
2157 &mut lowlink,
2158 &mut self_ref,
2159 attribute_tracker,
2160 );
2161 });
2162 } else if let VarType::NonCustom(non_custom) = var {
2163 let entry = &non_custom_references[non_custom];
2164 if let Some(deps) = entry.as_ref() {
2165 for d in deps {
2166 visit_link(
2169 VarType::Custom(d.clone()),
2170 context,
2171 &mut lowlink,
2172 &mut self_ref,
2173 attribute_tracker,
2174 );
2175 }
2176 }
2177 }
2178
2179 context.var_info[index].lowlink = lowlink;
2180 if lowlink != index {
2181 return Some(index);
2189 }
2190
2191 let mut in_loop = self_ref;
2193 let name;
2194
2195 let handle_variable_in_loop =
2196 |name: &Name, context: &mut Context<'a, 'b>, kind: SubstitutionFunctionKind| {
2197 if context.contains_computed_custom_property {
2198 if context.non_custom_references.intersects(
2201 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
2202 ) {
2203 context
2204 .invalid_non_custom_properties
2205 .insert(LonghandId::FontSize);
2206 }
2207 if context.non_custom_references.intersects(
2208 NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS,
2209 ) {
2210 context
2211 .invalid_non_custom_properties
2212 .insert(LonghandId::LineHeight);
2213 }
2214 }
2215 handle_invalid_at_computed_value_time(
2217 name,
2218 kind,
2219 &mut context.map,
2220 context.computed_context,
2221 );
2222 };
2223 loop {
2224 let var_index = context
2225 .stack
2226 .pop()
2227 .expect("The current variable should still be in stack");
2228 let var_info = &mut context.var_info[var_index];
2229 let var_name = var_info
2233 .var
2234 .take()
2235 .expect("Variable should not be poped from stack twice");
2236 if var_index == index {
2237 name = match var_name {
2238 VarType::Custom(name) | VarType::Attr(name) => name,
2239 VarType::NonCustom(..) => return None,
2243 };
2244 break;
2245 }
2246 if let VarType::Custom(name) | VarType::Attr(name) = var_name {
2247 handle_variable_in_loop(&name, context, kind);
2251 }
2252 in_loop = true;
2253 }
2254 if in_loop {
2259 handle_variable_in_loop(&name, context, kind);
2260 context.non_custom_references = NonCustomReferences::default();
2261 return None;
2262 }
2263
2264 if let Some(ref v) = value {
2265 let registration = if kind == SubstitutionFunctionKind::Var {
2266 context.stylist.get_custom_property_registration(&name)
2267 } else {
2268 PropertyDescriptors::unregistered()
2269 };
2270
2271 let mut defer = false;
2272 if let Some(ref mut deferred) = context.deferred_substitution_functions {
2273 defer = find_non_custom_references(
2276 registration,
2277 v,
2278 context.has_color_scheme,
2279 context.computed_context.is_root_element(),
2280 false,
2281 )
2282 .is_some()
2283 || v.references.refs.iter().any(|reference| {
2284 (reference.substitution_kind == SubstitutionFunctionKind::Var
2285 && deferred
2286 .get(&reference.name, SubstitutionFunctionKind::Var)
2287 .is_some())
2288 || reference.substitution_kind == SubstitutionFunctionKind::Attr
2289 });
2290 if defer {
2291 let value = ComputedRegisteredValue::universal(Arc::clone(v));
2292 deferred.insert(&name, kind, value);
2293 if kind == SubstitutionFunctionKind::Var {
2294 context.map.remove_var(registration, &name);
2295 } else {
2296 context.map.remove_attr(&name);
2297 }
2298 }
2299 }
2300
2301 if !defer && (v.references.any_var || v.references.any_attr) {
2303 substitute_references_if_needed_and_apply(
2304 &name,
2305 kind,
2306 v,
2307 &mut context.map,
2308 context.stylist,
2309 context.computed_context,
2310 attribute_tracker,
2311 );
2312 }
2313 }
2314 context.non_custom_references = NonCustomReferences::default();
2315
2316 None
2318 }
2319
2320 let mut run = |make_var: fn(Name) -> VarType, seen: &PrecomputedHashSet<&Name>| {
2321 for name in seen {
2322 let mut context = Context {
2323 count: 0,
2324 index_map: OrderIndexMap::default(),
2325 non_custom_index_map: NonCustomReferenceMap::default(),
2326 stack: SmallVec::new(),
2327 var_info: SmallVec::new(),
2328 map: substitution_function_map,
2329 non_custom_references: NonCustomReferences::default(),
2330 has_color_scheme,
2331 stylist,
2332 computed_context,
2333 invalid_non_custom_properties,
2334 deferred_substitution_functions: deferred_substituted_functions_map.as_deref_mut(),
2335 contains_computed_custom_property: false,
2336 };
2337
2338 traverse(
2339 make_var((*name).clone()),
2340 references_from_non_custom_properties,
2341 &mut context,
2342 attr_tracker,
2343 );
2344 }
2345 };
2346
2347 run(VarType::Custom, &seen.var);
2351 run(VarType::Attr, &seen.attr);
2354}
2355
2356fn handle_invalid_at_computed_value_time(
2358 name: &Name,
2359 kind: SubstitutionFunctionKind,
2360 substitution_functions: &mut ComputedSubstitutionFunctions,
2361 computed_context: &computed::Context,
2362) {
2363 if kind == SubstitutionFunctionKind::Attr {
2364 substitution_functions.remove_attr(name);
2366 return;
2367 }
2368
2369 let stylist = computed_context.style().stylist.unwrap();
2370 let registration = stylist.get_custom_property_registration(&name);
2371 if !registration.is_universal() {
2372 if registration.inherits() && !computed_context.is_root_element() {
2375 let inherited = computed_context.inherited_custom_properties();
2376 if let Some(value) = inherited.get(registration, name) {
2377 substitution_functions.insert_var(registration, name, value.clone());
2378 return;
2379 }
2380 } else if let Some(ref initial_value) = registration.initial_value {
2381 if let Ok(initial_value) = compute_value(
2382 &initial_value.css,
2383 &initial_value.url_data,
2384 registration,
2385 computed_context,
2386 AttrTaint::default(),
2387 ) {
2388 substitution_functions.insert_var(registration, name, initial_value);
2389 return;
2390 }
2391 }
2392 }
2393 substitution_functions.remove_var(registration, name);
2394}
2395
2396fn substitute_references_if_needed_and_apply(
2398 name: &Name,
2399 kind: SubstitutionFunctionKind,
2400 value: &Arc<VariableValue>,
2401 substitution_functions: &mut ComputedSubstitutionFunctions,
2402 stylist: &Stylist,
2403 computed_context: &computed::Context,
2404 attribute_tracker: &mut AttributeTracker,
2405) {
2406 debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
2407 let is_var = matches!(kind, SubstitutionFunctionKind::Var);
2408 let registration = stylist.get_custom_property_registration(&name);
2409 if is_var && !value.has_references() && registration.is_universal() {
2410 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
2412 substitution_functions.insert_var(registration, name, computed_value);
2413 return;
2414 }
2415
2416 let inherited = computed_context.inherited_custom_properties();
2417 let url_data = &value.url_data;
2418 let substitution = match substitute_internal(
2419 value,
2420 substitution_functions,
2421 stylist,
2422 computed_context,
2423 attribute_tracker,
2424 None,
2425 ) {
2426 Ok(v) => v,
2427 Err(..) => {
2428 handle_invalid_at_computed_value_time(
2429 name,
2430 kind,
2431 substitution_functions,
2432 computed_context,
2433 );
2434 return;
2435 },
2436 };
2437
2438 if is_var {
2440 let css = &substitution.css;
2441 let css_wide_kw = {
2442 let mut input = ParserInput::new(&css);
2443 let mut input = Parser::new(&mut input);
2444 input.try_parse(CSSWideKeyword::parse)
2445 };
2446
2447 if let Ok(kw) = css_wide_kw {
2448 match (
2452 kw,
2453 registration.inherits(),
2454 computed_context.is_root_element(),
2455 ) {
2456 (CSSWideKeyword::Initial, _, _)
2457 | (CSSWideKeyword::Revert, false, _)
2458 | (CSSWideKeyword::RevertLayer, false, _)
2459 | (CSSWideKeyword::RevertRule, false, _)
2460 | (CSSWideKeyword::Unset, false, _)
2461 | (CSSWideKeyword::Revert, true, true)
2462 | (CSSWideKeyword::RevertLayer, true, true)
2463 | (CSSWideKeyword::RevertRule, true, true)
2464 | (CSSWideKeyword::Unset, true, true)
2465 | (CSSWideKeyword::Inherit, _, true) => {
2466 remove_and_insert_initial_value(name, registration, substitution_functions);
2467 },
2468 (CSSWideKeyword::Revert, true, false)
2469 | (CSSWideKeyword::RevertLayer, true, false)
2470 | (CSSWideKeyword::RevertRule, true, false)
2471 | (CSSWideKeyword::Inherit, _, false)
2472 | (CSSWideKeyword::Unset, true, false) => {
2473 match inherited.get(registration, name) {
2474 Some(value) => {
2475 substitution_functions.insert_var(registration, name, value.clone());
2476 },
2477 None => {
2478 substitution_functions.remove_var(registration, name);
2479 },
2480 };
2481 },
2482 }
2483 return;
2484 }
2485 }
2486
2487 match kind {
2488 SubstitutionFunctionKind::Var => {
2489 let value = match substitution.into_value(url_data, registration, computed_context) {
2490 Ok(v) => v,
2491 Err(()) => {
2492 handle_invalid_at_computed_value_time(
2493 name,
2494 kind,
2495 substitution_functions,
2496 computed_context,
2497 );
2498 return;
2499 },
2500 };
2501 substitution_functions.insert_var(registration, name, value);
2502 },
2503 SubstitutionFunctionKind::Attr => {
2504 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2505 substitution.css.into_owned(),
2506 url_data,
2507 substitution.first_token_type,
2508 substitution.last_token_type,
2509 )));
2510 value.attr_tainted |= substitution.attr_tainted;
2511 substitution_functions.insert_attr(name, value);
2512 },
2513 SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
2514 }
2515}
2516
2517#[derive(Default, Debug)]
2518struct Substitution<'a> {
2519 css: Cow<'a, str>,
2520 first_token_type: TokenSerializationType,
2521 last_token_type: TokenSerializationType,
2522 attr_tainted: bool,
2523}
2524
2525impl<'a> Substitution<'a> {
2526 fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
2527 Substitution {
2528 css: v.css.into(),
2529 first_token_type: v.first_token_type,
2530 last_token_type: v.last_token_type,
2531 attr_tainted,
2532 }
2533 }
2534
2535 fn into_value(
2536 self,
2537 url_data: &UrlExtraData,
2538 registration: &PropertyDescriptors,
2539 computed_context: &computed::Context,
2540 ) -> Result<ComputedRegisteredValue, ()> {
2541 if registration.is_universal() {
2542 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2543 self.css.into_owned(),
2544 url_data,
2545 self.first_token_type,
2546 self.last_token_type,
2547 )));
2548 value.attr_tainted |= self.attr_tainted;
2549 return Ok(value);
2550 }
2551 let taint = if self.attr_tainted {
2552 AttrTaint::new_fully_tainted(self.css.len())
2557 } else {
2558 AttrTaint::default()
2559 };
2560 let mut v = compute_value(&self.css, url_data, registration, computed_context, taint)?;
2561 v.attr_tainted |= self.attr_tainted;
2562 Ok(v)
2563 }
2564
2565 fn new(
2566 css: Cow<'a, str>,
2567 first_token_type: TokenSerializationType,
2568 last_token_type: TokenSerializationType,
2569 attr_tainted: bool,
2570 ) -> Self {
2571 Self {
2572 css,
2573 first_token_type,
2574 last_token_type,
2575 attr_tainted,
2576 }
2577 }
2578}
2579
2580#[derive(Debug)]
2582pub struct SubstitutionResult<'a> {
2583 pub css: Cow<'a, str>,
2585 pub attr_taint: AttrTaint,
2587}
2588
2589fn compute_value(
2590 css: &str,
2591 url_data: &UrlExtraData,
2592 registration: &PropertyDescriptors,
2593 computed_context: &computed::Context,
2594 attr_taint: AttrTaint,
2595) -> Result<ComputedRegisteredValue, ()> {
2596 debug_assert!(!registration.is_universal());
2597
2598 let mut input = ParserInput::new(&css);
2599 let mut input = Parser::new(&mut input);
2600
2601 SpecifiedRegisteredValue::compute(
2602 &mut input,
2603 registration,
2604 None,
2605 url_data,
2606 computed_context,
2607 AllowComputationallyDependent::Yes,
2608 attr_taint,
2609 )
2610}
2611
2612fn remove_and_insert_initial_value(
2614 name: &Name,
2615 registration: &PropertyDescriptors,
2616 substitution_functions: &mut ComputedSubstitutionFunctions,
2617) {
2618 substitution_functions.remove_var(registration, name);
2619 if let Some(ref initial_value) = registration.initial_value {
2620 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2621 substitution_functions.insert_var(registration, name, value);
2622 }
2623}
2624
2625fn do_substitute_chunk<'a>(
2626 css: &'a str,
2627 start: usize,
2628 end: usize,
2629 first_token_type: TokenSerializationType,
2630 last_token_type: TokenSerializationType,
2631 url_data: &UrlExtraData,
2632 substitution_functions: &'a ComputedSubstitutionFunctions,
2633 stylist: &Stylist,
2634 computed_context: &computed::Context,
2635 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2636 attribute_tracker: &mut AttributeTracker,
2637 mut attr_taint: Option<&mut AttrTaint>,
2638) -> Result<Substitution<'a>, ()> {
2639 if start == end {
2640 return Ok(Substitution::default());
2642 }
2643 if references
2645 .peek()
2646 .map_or(true, |reference| reference.end > end)
2647 {
2648 let result = &css[start..end];
2649 return Ok(Substitution::new(
2650 Cow::Borrowed(result),
2651 first_token_type,
2652 last_token_type,
2653 Default::default(),
2654 ));
2655 }
2656
2657 let mut substituted = ComputedValue::empty(url_data);
2658 let mut next_token_type = first_token_type;
2659 let mut cur_pos = start;
2660 let mut attr_tainted = false;
2661 while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2662 if reference.start != cur_pos {
2663 substituted.push(
2664 &css[cur_pos..reference.start],
2665 next_token_type,
2666 reference.prev_token_type,
2667 None,
2668 )?;
2669 }
2670
2671 let substitution = substitute_one_reference(
2672 css,
2673 url_data,
2674 substitution_functions,
2675 reference,
2676 stylist,
2677 computed_context,
2678 references,
2679 attribute_tracker,
2680 )?;
2681
2682 if reference.start == start && reference.end == end {
2684 if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
2685 taint.push(start, end);
2686 }
2687 return Ok(substitution);
2688 }
2689
2690 substituted.push(
2691 &substitution.css,
2692 substitution.first_token_type,
2693 substitution.last_token_type,
2694 attr_taint
2695 .as_deref_mut()
2696 .filter(|_| substitution.attr_tainted),
2697 )?;
2698 attr_tainted |= substitution.attr_tainted;
2699 next_token_type = reference.next_token_type;
2700 cur_pos = reference.end;
2701 }
2702 if cur_pos != end {
2704 substituted.push(
2705 &css[cur_pos..end],
2706 next_token_type,
2707 last_token_type,
2708 None,
2709 )?;
2710 }
2711 Ok(Substitution::from_value(substituted, attr_tainted))
2712}
2713
2714fn quoted_css_string(src: &str) -> String {
2715 let mut dest = String::with_capacity(src.len() + 2);
2716 cssparser::serialize_string(src, &mut dest).unwrap();
2717 dest
2718}
2719
2720fn substitute_one_reference<'a>(
2721 css: &'a str,
2722 url_data: &UrlExtraData,
2723 substitution_functions: &'a ComputedSubstitutionFunctions,
2724 reference: &SubstitutionFunctionReference,
2725 stylist: &Stylist,
2726 computed_context: &computed::Context,
2727 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2728 attribute_tracker: &mut AttributeTracker,
2729) -> Result<Substitution<'a>, ()> {
2730 let simple_attr_subst = |s: &str| {
2731 Some(Substitution::new(
2732 Cow::Owned(quoted_css_string(s)),
2733 TokenSerializationType::Nothing,
2734 TokenSerializationType::Nothing,
2735 true,
2736 ))
2737 };
2738 let substitution: Option<_> = match reference.substitution_kind {
2739 SubstitutionFunctionKind::Var => {
2740 let registration = stylist.get_custom_property_registration(&reference.name);
2741 substitution_functions
2742 .get_var(registration, &reference.name)
2743 .map(|v| Substitution::from_value(v.to_variable_value(), v.attr_tainted))
2744 },
2745 SubstitutionFunctionKind::Env => {
2746 let device = stylist.device();
2747 device
2748 .environment()
2749 .get(&reference.name, device, url_data)
2750 .map(|v| Substitution::from_value(v, false))
2751 },
2752 SubstitutionFunctionKind::Attr => {
2754 #[cfg(feature = "gecko")]
2755 let local_name = LocalName::cast(&reference.name);
2756 #[cfg(feature = "servo")]
2757 let local_name = LocalName::from(reference.name.as_ref());
2758 let namespace = match reference.attribute_data.namespace {
2759 ParsedNamespace::Known(ref ns) => Some(ns),
2760 ParsedNamespace::Unknown => None,
2761 };
2762 namespace
2763 .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
2764 .map_or_else(
2765 || {
2766 if reference.fallback.is_none()
2769 && reference.attribute_data.kind == AttributeType::None
2770 {
2771 simple_attr_subst("")
2772 } else {
2773 None
2774 }
2775 },
2776 |attr| {
2777 let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
2778 if computed_context.in_container_query {
2785 attr
2786 } else {
2787 substitution_functions
2788 .get_attr(&reference.name)
2789 .map(|v| v.to_variable_value())?
2790 .css
2791 }
2792 } else {
2793 attr
2794 };
2795 let mut input = ParserInput::new(&attr);
2796 let mut parser = Parser::new(&mut input);
2797 match &reference.attribute_data.kind {
2798 AttributeType::Unit(unit) => {
2799 let css = {
2800 parser.expect_number().ok()?;
2802 let mut s = attr.clone();
2803 s.push_str(unit.as_ref());
2804 s
2805 };
2806 let serialization = match unit {
2807 AttrUnit::Number => TokenSerializationType::Number,
2808 AttrUnit::Percentage => TokenSerializationType::Percentage,
2809 _ => TokenSerializationType::Dimension,
2810 };
2811 let value =
2812 ComputedValue::new(css, url_data, serialization, serialization);
2813 Some(Substitution::from_value(
2814 value, true,
2815 ))
2816 },
2817 AttributeType::Type(syntax) => {
2818 let value = SpecifiedRegisteredValue::parse(
2819 &mut parser,
2820 &syntax,
2821 url_data,
2822 None,
2823 AllowComputationallyDependent::Yes,
2824 AttrTaint::default(),
2825 )
2826 .ok()?;
2827 let value = value.to_variable_value();
2828 Some(Substitution::from_value(
2829 value, true,
2830 ))
2831 },
2832 AttributeType::RawString | AttributeType::None => {
2833 simple_attr_subst(&attr)
2834 },
2835 AttributeType::Invalid => None,
2836 }
2837 },
2838 )
2839 },
2840 };
2841
2842 if let Some(s) = substitution {
2843 while references
2845 .next_if(|next_ref| next_ref.end <= reference.end)
2846 .is_some()
2847 {}
2848 return Ok(s);
2849 }
2850
2851 let Some(ref fallback) = reference.fallback else {
2852 return Err(());
2853 };
2854
2855 do_substitute_chunk(
2856 css,
2857 fallback.start.get(),
2858 reference.end - 1, fallback.first_token_type,
2860 fallback.last_token_type,
2861 url_data,
2862 substitution_functions,
2863 stylist,
2864 computed_context,
2865 references,
2866 attribute_tracker,
2867 None,
2868 )
2869}
2870
2871fn substitute_internal<'a>(
2873 variable_value: &'a VariableValue,
2874 substitution_functions: &'a ComputedSubstitutionFunctions,
2875 stylist: &Stylist,
2876 computed_context: &computed::Context,
2877 attribute_tracker: &mut AttributeTracker,
2878 mut attr_taint: Option<&mut AttrTaint>,
2879) -> Result<Substitution<'a>, ()> {
2880 let mut refs = variable_value.references.refs.iter().peekable();
2881 do_substitute_chunk(
2882 &variable_value.css,
2883 0,
2884 variable_value.css.len(),
2885 variable_value.first_token_type,
2886 variable_value.last_token_type,
2887 &variable_value.url_data,
2888 substitution_functions,
2889 stylist,
2890 computed_context,
2891 &mut refs,
2892 attribute_tracker,
2893 attr_taint.as_deref_mut(),
2894 )
2895}
2896
2897pub fn substitute<'a>(
2899 variable_value: &'a VariableValue,
2900 substitution_functions: &'a ComputedSubstitutionFunctions,
2901 stylist: &Stylist,
2902 computed_context: &computed::Context,
2903 attribute_tracker: &mut AttributeTracker,
2904) -> Result<SubstitutionResult<'a>, ()> {
2905 debug_assert!(variable_value.has_references());
2906 let mut attr_taint = AttrTaint::default();
2907 let v = substitute_internal(
2908 variable_value,
2909 substitution_functions,
2910 stylist,
2911 computed_context,
2912 attribute_tracker,
2913 Some(&mut attr_taint),
2914 )?;
2915 Ok(SubstitutionResult {
2916 css: v.css,
2917 attr_taint,
2918 })
2919}