1use crate::FxHashMap;
10use crate::custom_properties_map::{CustomPropertiesMap, OwnMap};
11use crate::device::Device;
12use crate::dom::AttributeTracker;
13use crate::properties::{CSSWideKeyword, PrioritaryPropertyId, PropertyIdRef};
14use crate::properties_and_values::{
15 rule::Descriptors as PropertyDescriptors,
16 syntax::Descriptor as SyntaxDescriptor,
17 value::{
18 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
19 SpecifiedValue as SpecifiedRegisteredValue,
20 },
21};
22use crate::stylesheets::UrlExtraData;
23use crate::stylesheets::container_rule::AttrReferenceSet;
24use crate::stylist::Stylist;
25use crate::typed_om::{
26 ToTyped, TypedValue, UnparsedSegment, UnparsedValue, VariableReferenceValue,
27};
28use crate::values::computed;
29use crate::values::generics::calc::SortKey as AttrUnit;
30use crate::values::specified::{NoCalcLength, ParsedNamespace};
31use crate::{Atom, LocalName, Namespace, Prefix, derives::*};
32use cssparser::{CowRcStr, Delimiter, Parser, SourcePosition, Token, TokenSerializationType};
33use selectors::parser::SelectorParseErrorKind;
34use servo_arc::Arc;
35use smallvec::SmallVec;
36use std::borrow::Cow;
37use std::fmt::{self, Write};
38use std::num;
39use std::ops::{Index, IndexMut};
40use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
41use thin_vec::ThinVec;
42
43#[derive(Debug, MallocSizeOf)]
48pub struct CssEnvironment;
49
50type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
51
52struct EnvironmentVariable {
53 name: Atom,
54 evaluator: EnvironmentEvaluator,
55}
56
57macro_rules! make_variable {
58 ($name:expr, $evaluator:expr) => {{
59 EnvironmentVariable {
60 name: $name,
61 evaluator: $evaluator,
62 }
63 }};
64}
65
66fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
67 VariableValue::pixels(device.safe_area_insets().top, url_data)
68}
69
70fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
71 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
72}
73
74fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
75 VariableValue::pixels(device.safe_area_insets().left, url_data)
76}
77
78fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
79 VariableValue::pixels(device.safe_area_insets().right, url_data)
80}
81
82#[cfg(feature = "gecko")]
83fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
84 use crate::queries::values::PrefersColorScheme;
85 let prefers_color_scheme = unsafe {
86 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
87 device.document(),
88 true,
89 )
90 };
91 VariableValue::ident(
92 match prefers_color_scheme {
93 PrefersColorScheme::Light => "light",
94 PrefersColorScheme::Dark => "dark",
95 },
96 url_data,
97 )
98}
99
100#[cfg(feature = "servo")]
101fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
102 VariableValue::ident("light", url_data)
104}
105
106fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
107 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
108}
109
110fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
111 VariableValue::pixels(
112 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
113 url_data,
114 )
115}
116
117static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
118 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
119 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
120 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
121 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
122];
123
124#[cfg(feature = "gecko")]
125macro_rules! lnf_int {
126 ($id:ident) => {
127 unsafe {
128 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
129 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
130 )
131 }
132 };
133}
134
135#[cfg(feature = "servo")]
136macro_rules! lnf_int {
137 ($id:ident) => {
138 0
140 };
141}
142
143macro_rules! lnf_int_variable {
144 ($atom:expr, $id:ident, $ctor:ident) => {{
145 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
146 VariableValue::$ctor(lnf_int!($id), url_data)
147 }
148 make_variable!($atom, __eval)
149 }};
150}
151
152fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
153 let int_pixels = lnf_int!(TitlebarRadius);
154 let unzoomed_scale =
155 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
156 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
157}
158
159static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
160 make_variable!(
161 atom!("-moz-gtk-csd-titlebar-radius"),
162 eval_gtk_csd_titlebar_radius
163 ),
164 lnf_int_variable!(
165 atom!("-moz-gtk-csd-tooltip-radius"),
166 TooltipRadius,
167 int_pixels
168 ),
169 lnf_int_variable!(
170 atom!("-moz-gtk-csd-close-button-position"),
171 GTKCSDCloseButtonPosition,
172 integer
173 ),
174 lnf_int_variable!(
175 atom!("-moz-gtk-csd-minimize-button-position"),
176 GTKCSDMinimizeButtonPosition,
177 integer
178 ),
179 lnf_int_variable!(
180 atom!("-moz-gtk-csd-maximize-button-position"),
181 GTKCSDMaximizeButtonPosition,
182 integer
183 ),
184 lnf_int_variable!(
185 atom!("-moz-overlay-scrollbar-fade-duration"),
186 ScrollbarFadeDuration,
187 int_ms
188 ),
189 make_variable!(
190 atom!("-moz-content-preferred-color-scheme"),
191 get_content_preferred_color_scheme
192 ),
193 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
194 make_variable!(atom!("hairline"), get_hairline),
195];
196
197impl CssEnvironment {
198 #[inline]
200 pub fn get(
201 &self,
202 name: &Atom,
203 device: &Device,
204 url_data: &UrlExtraData,
205 ) -> Option<VariableValue> {
206 #[cfg(feature = "gecko")]
207 let is_link_parameter = name.as_slice().starts_with(&[b'-' as u16, b'-' as u16]);
208 #[cfg(feature = "servo")]
209 let is_link_parameter = name.starts_with("--");
210 if is_link_parameter {
211 let param = device
212 .link_parameters()?
213 .0
214 .iter()
215 .find(|p| p.name.0 == *name)?;
216 let mut parser = cssparser::Parser::new(param.value.0.as_ref());
217 return VariableValue::parse(&mut parser, None, url_data).ok();
219 }
220
221 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
222 return Some((var.evaluator)(device, url_data));
223 }
224 if !url_data.chrome_rules_enabled() {
225 return None;
226 }
227 let var = CHROME_ENVIRONMENT_VARIABLES
228 .iter()
229 .find(|var| var.name == *name)?;
230 Some((var.evaluator)(device, url_data))
231 }
232}
233
234pub type Name = Atom;
238
239impl LocalName {
240 #[cfg(feature = "gecko")]
241 fn with_name<R>(name: &Name, callback: impl FnOnce(&Self) -> R) -> R {
242 callback(Self::cast(name))
243 }
244
245 #[cfg(feature = "servo")]
246 fn with_name<'a, R>(name: &'a Name, callback: impl FnOnce(&Self) -> R) -> R {
247 callback(&name.as_str().into())
248 }
249}
250
251impl From<Name> for LocalName {
252 #[cfg(feature = "gecko")]
253 fn from(name: Name) -> Self {
254 Self::new(name)
255 }
256
257 #[cfg(feature = "servo")]
258 fn from(name: Name) -> Self {
259 name.as_str().into()
260 }
261}
262
263pub fn parse_name(s: &str) -> Result<&str, ()> {
267 if s.starts_with("--") && s.len() > 2 {
268 Ok(&s[2..])
269 } else {
270 Err(())
271 }
272}
273
274#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
279pub struct VariableValue {
280 pub css: String,
282
283 pub url_data: UrlExtraData,
285
286 first_token_type: TokenSerializationType,
287 last_token_type: TokenSerializationType,
288
289 pub references: References,
291
292 pub explicitly_attr_tainted: bool,
296}
297
298trivial_to_computed_value!(VariableValue);
299
300pub(crate) fn compute_variable_value(
302 value: &Arc<VariableValue>,
303 registration: &PropertyDescriptors,
304 computed_context: &computed::Context,
305 property_id: Option<PropertyIdRef>,
306) -> Option<ComputedRegisteredValue> {
307 if registration.is_universal() {
308 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
309 }
310 compute_value(
311 &value.css,
312 &value.url_data,
313 registration,
314 computed_context,
315 AttrTaint::default(),
316 property_id,
317 )
318 .ok()
319}
320
321impl PartialEq for VariableValue {
323 fn eq(&self, other: &Self) -> bool {
324 self.css == other.css
325 }
326}
327
328impl Eq for VariableValue {}
329
330impl ToCss for SpecifiedValue {
331 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
332 where
333 W: Write,
334 {
335 dest.write_str(&self.css)
336 }
337}
338
339impl ToTyped for SpecifiedValue {
340 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
341 let unparsed_value = reify_variable_value(self)?;
342 dest.push(TypedValue::Unparsed(unparsed_value));
343 Ok(())
344 }
345}
346
347fn reify_variable_value(value: &VariableValue) -> Result<UnparsedValue, ()> {
348 let mut reference_index = 0;
349 reify_variable_value_range(
350 &value.css,
351 &value.references.refs,
352 &mut reference_index,
353 0,
354 value.css.len(),
355 )
356}
357
358fn reify_variable_value_range(
365 css: &str,
366 references: &[SubstitutionFunctionReference],
367 reference_index: &mut usize,
368 start: usize,
369 end: usize,
370) -> Result<UnparsedValue, ()> {
371 debug_assert!(start <= end);
372 debug_assert!(end <= css.len());
373
374 let mut values = ThinVec::new();
375 let mut cur_pos = start;
376
377 while *reference_index < references.len() {
378 let reference = &references[*reference_index];
379
380 if reference.start >= end {
381 break;
382 }
383
384 debug_assert!(reference.start >= cur_pos);
385 debug_assert!(reference.start <= reference.end);
386 debug_assert!(reference.end <= css.len());
387
388 if cur_pos < reference.start {
389 values.push(UnparsedSegment::String(CssString::from(
390 &css[cur_pos..reference.start],
391 )));
392 }
393
394 *reference_index += 1;
395
396 if reference.substitution_kind != SubstitutionFunctionKind::Var {
397 return Err(());
398 }
399
400 let (fallback, has_fallback) = if let Some(fallback) = &reference.fallback {
401 debug_assert!(fallback.start.get() < reference.end);
402
403 (
404 reify_variable_value_range(
405 css,
406 references,
407 reference_index,
408 fallback.start.get(),
409 reference.end - 1, )?,
411 true,
412 )
413 } else {
414 (ThinVec::new(), false)
415 };
416
417 values.push(UnparsedSegment::VariableReference(VariableReferenceValue {
418 variable: CssString::from(format!("--{}", reference.name)),
419 fallback,
420 has_fallback,
421 }));
422
423 cur_pos = reference.end;
424 }
425
426 if cur_pos < end {
427 values.push(UnparsedSegment::String(CssString::from(&css[cur_pos..end])));
428 }
429
430 Ok(values)
431}
432
433#[repr(C)]
436#[derive(Clone, Debug, Default, PartialEq)]
437pub struct ComputedCustomProperties {
438 pub inherited: CustomPropertiesMap,
441 pub non_inherited: CustomPropertiesMap,
443}
444
445impl ComputedCustomProperties {
446 pub fn is_empty(&self) -> bool {
448 self.inherited.is_empty() && self.non_inherited.is_empty()
449 }
450
451 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
453 self.inherited
456 .get_index(index)
457 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
458 }
459
460 pub fn insert(
463 &mut self,
464 registration: &PropertyDescriptors,
465 name: &Name,
466 value: ComputedRegisteredValue,
467 ) {
468 self.map_mut(registration).insert(name, value)
469 }
470
471 pub fn remove(&mut self, registration: &PropertyDescriptors, name: &Name) {
474 self.map_mut(registration).remove(name);
475 }
476
477 pub fn shrink_to_fit(&mut self) {
479 self.inherited.shrink_to_fit();
480 self.non_inherited.shrink_to_fit();
481 }
482
483 fn map_mut(&mut self, registration: &PropertyDescriptors) -> &mut CustomPropertiesMap {
484 if registration.inherits() {
485 &mut self.inherited
486 } else {
487 &mut self.non_inherited
488 }
489 }
490
491 pub fn get(
493 &self,
494 registration: &PropertyDescriptors,
495 name: &Name,
496 ) -> Option<&ComputedRegisteredValue> {
497 if registration.inherits() {
498 self.inherited.get(name)
499 } else {
500 self.non_inherited.get(name)
501 }
502 }
503
504 pub fn get_for_cssom(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
507 self.inherited
508 .get(name)
509 .or_else(|| self.non_inherited.get(name))
510 }
511}
512
513pub type SpecifiedValue = VariableValue;
516pub type ComputedValue = VariableValue;
519
520#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
522pub struct ReferenceFlags(u8);
523
524bitflags! {
525 impl ReferenceFlags : u8 {
526 const FONT_UNITS = 1 << 0;
528 const ROOT_FONT_UNITS = 1 << 1;
530 const LH_UNITS = 1 << 2;
532 const ROOT_LH_UNITS = 1 << 3;
534 const COLOR_SCHEME = 1 << 4;
536 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
538 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
540 const NON_CUSTOM = Self::NON_ROOT_DEPENDENCIES.0 | Self::ROOT_DEPENDENCIES.0;
542 const ATTR = 1 << 5;
544 const ENV = 1 << 6;
546 const VAR = 1 << 7;
548 }
549}
550
551impl ReferenceFlags {
552 pub fn for_each_non_custom<F>(mut self, is_root_element: bool, mut f: F)
554 where
555 F: FnMut(SingleNonCustomReference),
556 {
557 if is_root_element {
559 if self.intersects(Self::ROOT_FONT_UNITS) {
560 self.remove(Self::ROOT_FONT_UNITS);
561 self |= Self::FONT_UNITS;
562 }
563 if self.intersects(Self::ROOT_LH_UNITS) {
564 self.remove(Self::ROOT_FONT_UNITS);
565 self |= Self::LH_UNITS;
566 }
567 }
568
569 for (_, r) in self.iter_names() {
570 let single = match r {
571 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
572 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
573 Self::COLOR_SCHEME => SingleNonCustomReference::ColorScheme,
574 Self::ROOT_FONT_UNITS
575 | Self::ROOT_LH_UNITS
576 | Self::VAR
577 | Self::ENV
578 | Self::ATTR => continue,
579 _ => unreachable!("Unexpected single bit value"),
580 };
581 f(single);
582 }
583 }
584
585 fn from_unit(value: &CowRcStr) -> Self {
586 if value.eq_ignore_ascii_case(NoCalcLength::LH) {
591 return Self::FONT_UNITS | Self::LH_UNITS;
592 }
593 if value.eq_ignore_ascii_case(NoCalcLength::EM)
594 || value.eq_ignore_ascii_case(NoCalcLength::EX)
595 || value.eq_ignore_ascii_case(NoCalcLength::CAP)
596 || value.eq_ignore_ascii_case(NoCalcLength::CH)
597 || value.eq_ignore_ascii_case(NoCalcLength::IC)
598 {
599 return Self::FONT_UNITS;
600 }
601 if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
602 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
603 }
604 if value.eq_ignore_ascii_case(NoCalcLength::REM)
605 || value.eq_ignore_ascii_case(NoCalcLength::REX)
606 || value.eq_ignore_ascii_case(NoCalcLength::RCH)
607 || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
608 || value.eq_ignore_ascii_case(NoCalcLength::RIC)
609 {
610 return Self::ROOT_FONT_UNITS;
611 }
612 Self::empty()
613 }
614}
615
616#[derive(Clone, Copy, Debug, Eq, PartialEq)]
619#[allow(missing_docs)]
620pub enum SingleNonCustomReference {
621 FontUnits = 0,
622 LhUnits,
623 ColorScheme,
624}
625
626impl SingleNonCustomReference {
627 pub fn to_prioritary_id(self) -> PrioritaryPropertyId {
629 match self {
630 Self::FontUnits => PrioritaryPropertyId::FontSize,
631 Self::LhUnits => PrioritaryPropertyId::LineHeight,
632 Self::ColorScheme => PrioritaryPropertyId::ColorScheme,
633 }
634 }
635}
636
637pub struct NonCustomReferenceMap<T>([Option<T>; 3]);
639
640impl<T> Default for NonCustomReferenceMap<T> {
641 fn default() -> Self {
642 NonCustomReferenceMap(Default::default())
643 }
644}
645
646impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
647 type Output = Option<T>;
648
649 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
650 &self.0[reference as usize]
651 }
652}
653
654impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
655 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
656 &mut self.0[reference as usize]
657 }
658}
659
660#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse)]
662pub enum SubstitutionFunctionKind {
663 Var,
665 Env,
667 Attr,
669}
670
671#[repr(C)]
674#[derive(Clone, Debug, Default, PartialEq)]
675pub struct ComputedSubstitutionFunctions {
676 pub custom_properties: ComputedCustomProperties,
678 pub attributes: OwnMap,
680}
681
682impl ComputedSubstitutionFunctions {
683 #[inline(always)]
686 pub fn new(
687 custom_properties: Option<ComputedCustomProperties>,
688 attributes: Option<OwnMap>,
689 ) -> Self {
690 Self {
691 custom_properties: custom_properties.unwrap_or_default(),
692 attributes: attributes.unwrap_or_default(),
693 }
694 }
695
696 #[inline(always)]
697 pub(crate) fn insert_var(
698 &mut self,
699 registration: &PropertyDescriptors,
700 name: &Name,
701 value: ComputedRegisteredValue,
702 ) {
703 self.custom_properties.insert(registration, name, value);
704 }
705
706 #[inline(always)]
707 pub(crate) fn insert_attr(&mut self, name: &Name, value: ComputedRegisteredValue) {
708 self.attributes.insert(name.clone(), Some(value));
709 }
710
711 #[inline(always)]
712 pub(crate) fn remove_var(&mut self, registration: &PropertyDescriptors, name: &Name) {
713 self.custom_properties.remove(registration, name);
714 }
715
716 #[inline(always)]
717 pub(crate) fn remove_attr(&mut self, name: &Name) {
718 self.attributes.insert(name.clone(), None);
719 }
720
721 #[inline(always)]
722 pub(crate) fn get_var(
723 &self,
724 registration: &PropertyDescriptors,
725 name: &Name,
726 ) -> Option<&ComputedRegisteredValue> {
727 self.custom_properties.get(registration, name)
728 }
729
730 #[inline(always)]
731 pub(crate) fn get_attr(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
732 self.attributes.get(name).and_then(|p| p.as_ref())
733 }
734}
735
736#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
737enum AttributeType {
738 Invalid,
739 None,
740 RawString,
741 Type(SyntaxDescriptor),
742 Unit(AttrUnit),
743}
744
745#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
747pub struct AttributeData {
748 kind: AttributeType,
749 namespace: ParsedNamespace,
750}
751
752#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
754pub struct AttrTaintedRange {
755 start: usize,
757 end: usize,
759}
760
761impl AttrTaintedRange {
762 #[inline(always)]
764 pub fn new(start: usize, end: usize) -> Self {
765 debug_assert!(start <= end);
766 Self { start, end }
767 }
768}
769
770#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
775pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
776
777impl AttrTaint {
778 #[inline(always)]
781 pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
782 self.0
783 .iter()
784 .any(|r| r.start <= range.end && r.end >= range.start)
785 }
786
787 #[inline(always)]
789 pub fn is_empty(&self) -> bool {
790 self.0.is_empty()
791 }
792
793 #[inline(always)]
794 fn new_fully_tainted(end: usize) -> Self {
795 let mut taint = Self::default();
796 taint.push(0, end);
797 taint
798 }
799
800 #[inline(always)]
801 fn push(&mut self, start: usize, end: usize) {
802 self.0.push(AttrTaintedRange::new(start, end));
803 }
804}
805
806#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
808pub struct VariableFallback {
809 start: num::NonZeroUsize,
813 first_token_type: TokenSerializationType,
814 last_token_type: TokenSerializationType,
815 pub references: References,
817}
818
819#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
821pub struct SubstitutionFunctionReference {
822 pub name: Name,
824 start: usize,
825 end: usize,
826 pub fallback: Option<VariableFallback>,
828 pub attribute_data: AttributeData,
830 prev_token_type: TokenSerializationType,
831 next_token_type: TokenSerializationType,
832 pub substitution_kind: SubstitutionFunctionKind,
834}
835
836impl SubstitutionFunctionReference {
837 pub fn is_attr_with_type(&self) -> bool {
839 self.substitution_kind == SubstitutionFunctionKind::Attr
840 && matches!(self.attribute_data.kind, AttributeType::Type(..))
841 }
842}
843
844#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
847pub struct References {
848 pub refs: Vec<SubstitutionFunctionReference>,
850 pub flags: ReferenceFlags,
855}
856
857impl References {
858 fn has_references(&self) -> bool {
859 !self.refs.is_empty()
860 }
861
862 pub(crate) fn non_custom_references(&self, is_root_element: bool) -> ReferenceFlags {
863 let mut mask = ReferenceFlags::NON_ROOT_DEPENDENCIES;
864 if is_root_element {
865 mask |= ReferenceFlags::ROOT_DEPENDENCIES
866 }
867 self.flags & mask
868 }
869}
870
871impl VariableValue {
872 fn empty(url_data: &UrlExtraData) -> Self {
873 Self {
874 css: String::new(),
875 last_token_type: Default::default(),
876 first_token_type: Default::default(),
877 url_data: url_data.clone(),
878 references: Default::default(),
879 explicitly_attr_tainted: false,
880 }
881 }
882
883 pub fn new(
886 css: String,
887 url_data: &UrlExtraData,
888 first_token_type: TokenSerializationType,
889 last_token_type: TokenSerializationType,
890 ) -> Self {
891 Self {
892 css,
893 url_data: url_data.clone(),
894 first_token_type,
895 last_token_type,
896 references: References::default(),
897 explicitly_attr_tainted: false,
898 }
899 }
900
901 fn push(
902 &mut self,
903 css: &str,
904 css_first_token_type: TokenSerializationType,
905 css_last_token_type: TokenSerializationType,
906 attr_taint: Option<&mut AttrTaint>,
907 ) -> Result<(), ()> {
908 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
916
917 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
918 return Err(());
919 }
920
921 if css.is_empty() {
926 return Ok(());
927 }
928
929 self.first_token_type.set_if_nothing(css_first_token_type);
930 if self
933 .last_token_type
934 .needs_separator_when_before(css_first_token_type)
935 {
936 self.css.push_str("/**/")
937 }
938 let start = self.css.len();
939 self.css.push_str(css);
940 let end = self.css.len();
941 if let Some(taint) = attr_taint {
942 taint.push(start, end);
943 }
944 self.last_token_type = css_last_token_type;
945 Ok(())
946 }
947
948 pub fn parse(
950 input: &mut Parser,
951 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
952 url_data: &UrlExtraData,
953 ) -> Result<Self, ParseError> {
954 let mut references = References::default();
955 let mut missing_closing_characters = String::new();
956 let start_position = input.position();
957 let (first_token_type, last_token_type) = parse_declaration_value(
958 input,
959 start_position,
960 namespaces,
961 &mut references,
962 &mut missing_closing_characters,
963 )?;
964 let mut css = input
965 .slice_from(start_position)
966 .trim_ascii_start()
967 .to_owned();
968 if !missing_closing_characters.is_empty() {
969 if css.ends_with("\\")
971 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
972 {
973 css.pop();
974 }
975 css.push_str(&missing_closing_characters);
976 }
977
978 css.truncate(css.trim_ascii_end().len());
979 css.shrink_to_fit();
980 references.refs.shrink_to_fit();
981
982 Ok(Self {
983 css,
984 url_data: url_data.clone(),
985 first_token_type,
986 last_token_type,
987 references,
988 explicitly_attr_tainted: false,
989 })
990 }
991
992 pub fn is_attr_tainted(&self) -> bool {
994 self.references.flags.intersects(ReferenceFlags::ATTR) | self.explicitly_attr_tainted
995 }
996
997 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
999 Self::from_token(
1000 Token::Number {
1001 has_sign: false,
1002 value: number as f32,
1003 int_value: Some(number),
1004 },
1005 url_data,
1006 )
1007 }
1008
1009 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
1011 Self::from_token(Token::Ident(ident.into()), url_data)
1012 }
1013
1014 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
1016 Self::from_token(
1020 Token::Dimension {
1021 has_sign: false,
1022 value: number,
1023 int_value: None,
1024 unit: CowRcStr::from("px"),
1025 },
1026 url_data,
1027 )
1028 }
1029
1030 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
1032 Self::from_token(
1033 Token::Dimension {
1034 has_sign: false,
1035 value: number as f32,
1036 int_value: Some(number),
1037 unit: CowRcStr::from("ms"),
1038 },
1039 url_data,
1040 )
1041 }
1042
1043 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
1045 Self::from_token(
1046 Token::Dimension {
1047 has_sign: false,
1048 value: number as f32,
1049 int_value: Some(number),
1050 unit: CowRcStr::from("px"),
1051 },
1052 url_data,
1053 )
1054 }
1055
1056 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
1057 let token_type = token.serialization_type();
1058 let mut css = token.to_css_string();
1059 css.shrink_to_fit();
1060
1061 VariableValue {
1062 css,
1063 url_data: url_data.clone(),
1064 first_token_type: token_type,
1065 last_token_type: token_type,
1066 references: Default::default(),
1067 explicitly_attr_tainted: false,
1068 }
1069 }
1070
1071 pub fn css_text(&self) -> &str {
1073 &self.css
1074 }
1075
1076 pub fn has_references(&self) -> bool {
1079 self.references.has_references()
1080 }
1081
1082 pub fn collect_attribute_references(&self, references: &mut AttrReferenceSet) {
1084 self.references.refs.iter().for_each(|r| {
1085 if r.substitution_kind == SubstitutionFunctionKind::Attr {
1086 references.insert(r.name.clone().into());
1092 let lowercase = r.name.to_ascii_lowercase();
1093 if *lowercase != *r.name {
1094 references.insert(Atom::from(lowercase).into());
1095 }
1096 }
1097 })
1098 }
1099}
1100
1101fn parse_declaration_value(
1103 input: &mut Parser,
1104 input_start: SourcePosition,
1105 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1106 references: &mut References,
1107 missing_closing_characters: &mut String,
1108) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
1109 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
1110 parse_declaration_value_block(
1111 input,
1112 input_start,
1113 namespaces,
1114 references,
1115 missing_closing_characters,
1116 )
1117 })
1118}
1119
1120fn parse_declaration_value_block(
1122 input: &mut Parser,
1123 input_start: SourcePosition,
1124 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1125 references: &mut References,
1126 missing_closing_characters: &mut String,
1127) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
1128 let mut is_first = true;
1129 let mut first_token_type = TokenSerializationType::Nothing;
1130 let mut last_token_type = TokenSerializationType::Nothing;
1131 let mut prev_reference_index: Option<usize> = None;
1132 loop {
1133 let token_start = input.position();
1134 let Ok(token) = input.next_including_whitespace_and_comments() else {
1135 break;
1136 };
1137
1138 let prev_token_type = last_token_type;
1139 let serialization_type = token.serialization_type();
1140 last_token_type = serialization_type;
1141 if is_first {
1142 first_token_type = last_token_type;
1143 is_first = false;
1144 }
1145
1146 macro_rules! nested {
1147 ($closing:expr) => {{
1148 let mut inner_end_position = None;
1149 let result = input.parse_nested_block(|input| {
1150 let result = parse_declaration_value_block(
1151 input,
1152 input_start,
1153 namespaces,
1154 references,
1155 missing_closing_characters,
1156 )?;
1157 inner_end_position = Some(input.position());
1158 Ok(result)
1159 })?;
1160 if inner_end_position.unwrap() == input.position() {
1161 missing_closing_characters.push_str($closing);
1162 }
1163 result
1164 }};
1165 }
1166 if let Some(index) = prev_reference_index.take() {
1167 references.refs[index].next_token_type = serialization_type;
1168 }
1169 match *token {
1170 Token::Comment(_) => {
1171 let token_slice = input.slice_from(token_start);
1172 if !token_slice.ends_with("*/") {
1173 missing_closing_characters.push_str(if token_slice.ends_with('*') {
1174 "/"
1175 } else {
1176 "*/"
1177 })
1178 }
1179 },
1180 Token::BadUrl(..) => {
1181 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock;
1182 return Err(ParseError::custom(e));
1183 },
1184 Token::BadString(..) => {
1185 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock;
1186 return Err(ParseError::custom(e));
1187 },
1188 Token::CloseParenthesis => {
1189 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1190 return Err(ParseError::custom(e));
1191 },
1192 Token::CloseSquareBracket => {
1193 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1194 return Err(ParseError::custom(e));
1195 },
1196 Token::CloseCurlyBracket => {
1197 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1198 return Err(ParseError::custom(e));
1199 },
1200 Token::Function(ref name) => {
1201 let substitution_kind = SubstitutionFunctionKind::from_ident(name).ok();
1202 if let Some(substitution_kind) = substitution_kind {
1203 let our_ref_index = references.refs.len();
1204 let mut input_end_position = None;
1205 let fallback = input.parse_nested_block(|input| {
1206 let mut namespace = ParsedNamespace::Known(Namespace::default());
1207 if substitution_kind == SubstitutionFunctionKind::Attr
1208 && let Some(namespaces) = namespaces
1209 && let Ok(ns) =
1210 input.try_parse(|input| ParsedNamespace::parse(namespaces, input))
1211 {
1212 namespace = ns;
1213 let prev = input.state();
1214 let next = match *input.next_including_whitespace()? {
1215 Token::Ident(_) => Ok(()),
1216 _ => Err(ParseError::unexpected_token()),
1217 };
1218 input.reset(&prev);
1219 next?;
1220 }
1221 let name = input.expect_ident()?;
1224 let name =
1225 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
1226 match parse_name(name.as_ref()) {
1227 Ok(name) => name,
1228 Err(()) => {
1229 return Err(ParseError::custom(
1230 SelectorParseErrorKind::UnexpectedIdent,
1231 ));
1232 },
1233 }
1234 } else {
1235 name.as_ref()
1236 });
1237
1238 let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
1239 {
1240 parse_attr_type(input)
1241 } else {
1242 AttributeType::None
1243 };
1244
1245 let start = token_start.byte_index() - input_start.byte_index();
1249 references.refs.push(SubstitutionFunctionReference {
1250 name,
1251 start,
1252 end: start,
1254 prev_token_type,
1255 next_token_type: TokenSerializationType::Nothing,
1257 fallback: None,
1259 attribute_data: AttributeData {
1260 kind: attribute_kind,
1261 namespace,
1262 },
1263 substitution_kind,
1264 });
1265
1266 let mut fallback = None;
1267 if input.try_parse(|input| input.expect_comma()).is_ok() {
1268 input.skip_whitespace();
1269 let fallback_start = num::NonZeroUsize::new(
1270 input.position().byte_index() - input_start.byte_index(),
1271 )
1272 .unwrap();
1273 let mut references = References::default();
1274 let (first, last) = parse_declaration_value(
1277 input,
1278 input_start,
1279 namespaces,
1280 &mut references,
1281 missing_closing_characters,
1282 )?;
1283 fallback = Some(VariableFallback {
1284 start: fallback_start,
1285 first_token_type: first,
1286 last_token_type: last,
1287 references,
1288 });
1289 input_end_position = Some(input.position());
1290 } else {
1291 let state = input.state();
1292 parse_declaration_value_block(
1296 input,
1297 input_start,
1298 namespaces,
1299 references,
1300 missing_closing_characters,
1301 )?;
1302 input_end_position = Some(input.position());
1303 input.reset(&state);
1304 }
1305 Ok(fallback)
1306 })?;
1307 if input_end_position.unwrap() == input.position() {
1308 missing_closing_characters.push(')');
1309 }
1310 prev_reference_index = Some(our_ref_index);
1311 let reference = &mut references.refs[our_ref_index];
1312 reference.end = input.position().byte_index() - input_start.byte_index()
1313 + missing_closing_characters.len();
1314 reference.fallback = fallback;
1315 references.flags |= match substitution_kind {
1316 SubstitutionFunctionKind::Var => ReferenceFlags::VAR,
1317 SubstitutionFunctionKind::Env => ReferenceFlags::ENV,
1318 SubstitutionFunctionKind::Attr => ReferenceFlags::ATTR,
1319 };
1320 if let Some(ref fb) = reference.fallback {
1322 references.flags |= fb.references.flags;
1323 }
1324 } else {
1325 nested!(")");
1326 }
1327 },
1328 Token::ParenthesisBlock => {
1329 nested!(")");
1330 },
1331 Token::CurlyBracketBlock => {
1332 nested!("}");
1333 },
1334 Token::SquareBracketBlock => {
1335 nested!("]");
1336 },
1337 Token::QuotedString(_) => {
1338 let token_slice = input.slice_from(token_start);
1339 let quote = &token_slice[..1];
1340 debug_assert!(matches!(quote, "\"" | "'"));
1341 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1342 missing_closing_characters.push_str(quote)
1343 }
1344 },
1345 Token::Ident(ref value)
1346 | Token::AtKeyword(ref value)
1347 | Token::Hash(ref value)
1348 | Token::IDHash(ref value)
1349 | Token::UnquotedUrl(ref value)
1350 | Token::Dimension {
1351 unit: ref value, ..
1352 } => {
1353 references.flags.insert(ReferenceFlags::from_unit(value));
1354 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1355 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1356 missing_closing_characters.push('�')
1361 }
1362 if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1363 missing_closing_characters.push(')');
1364 }
1365 },
1366 _ => {},
1367 };
1368 }
1369 Ok((first_token_type, last_token_type))
1370}
1371
1372fn parse_attr_type(input: &mut Parser) -> AttributeType {
1375 input
1376 .try_parse(|input| {
1377 Ok(match input.next()? {
1378 Token::Function(name) if name.eq_ignore_ascii_case("type") => AttributeType::Type(
1379 input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1380 ),
1381 Token::Ident(ident) => {
1382 if ident.eq_ignore_ascii_case("raw-string") {
1383 AttributeType::RawString
1384 } else if let Ok(unit) = AttrUnit::from_ident(ident) {
1385 AttributeType::Unit(unit)
1386 } else {
1387 AttributeType::Invalid
1388 }
1389 },
1390 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1391 _ => return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
1392 })
1393 })
1394 .unwrap_or(AttributeType::None)
1395}
1396
1397pub fn get_attr_value_for_cycle_resolution(
1400 name: &Name,
1401 attribute_data: &AttributeData,
1402 url_data: &UrlExtraData,
1403 attribute_tracker: &mut AttributeTracker,
1404) -> Result<ComputedRegisteredValue, ()> {
1405 let namespace = match attribute_data.namespace {
1406 ParsedNamespace::Known(ref ns) => ns,
1407 ParsedNamespace::Unknown => return Err(()),
1408 };
1409 let attr = LocalName::with_name(name, |local_name| {
1410 attribute_tracker.query(local_name, namespace).ok_or(())
1411 })?;
1412 let mut parser = Parser::new(&attr);
1413 let value = VariableValue::parse(&mut parser, None, url_data).map_err(|_| ())?;
1415 Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1416}
1417
1418pub fn handle_invalid_at_computed_value_time(
1420 name: &Name,
1421 registration: &PropertyDescriptors,
1422 context: &mut computed::Context,
1423) {
1424 if !registration.is_universal() {
1425 if registration.inherits() && !context.builder.is_root_element {
1428 let inherited = context.builder.inherited_style.custom_properties();
1429 if let Some(value) = inherited.get(registration, name) {
1430 context.builder.substitution_functions.insert_var(
1431 registration,
1432 name,
1433 value.clone(),
1434 );
1435 return;
1436 }
1437 } else if let Some(ref initial_value) = registration.initial_value
1438 && let Ok(initial_value) = compute_value(
1439 &initial_value.css,
1440 &initial_value.url_data,
1441 registration,
1442 context,
1443 AttrTaint::default(),
1444 Some(PropertyIdRef::from(name)),
1445 )
1446 {
1447 context
1448 .builder
1449 .substitution_functions
1450 .insert_var(registration, name, initial_value);
1451 return;
1452 }
1453 }
1454 context
1455 .builder
1456 .substitution_functions
1457 .remove_var(registration, name);
1458}
1459
1460pub fn substitute_references_if_needed_and_apply(
1462 name: &Name,
1463 kind: SubstitutionFunctionKind,
1464 value: &Arc<VariableValue>,
1465 stylist: &Stylist,
1466 context: &mut computed::Context,
1467 attribute_tracker: &mut AttributeTracker,
1468) {
1469 debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
1470 let is_var = matches!(kind, SubstitutionFunctionKind::Var);
1471 let registration = stylist.get_custom_property_registration(name);
1472 if is_var && !value.has_references() && registration.is_universal() {
1473 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
1475 context
1476 .builder
1477 .substitution_functions
1478 .insert_var(registration, name, computed_value);
1479 return;
1480 }
1481
1482 let url_data = &value.url_data;
1483 let substitution = substitute_internal(
1484 value,
1485 Some(PropertyIdRef::from(name)),
1486 &context.builder.substitution_functions,
1487 stylist,
1488 context,
1489 attribute_tracker,
1490 &mut SmallVec::new(),
1491 None,
1492 );
1493
1494 let Ok(substitution) = substitution else {
1495 if is_var {
1496 handle_invalid_at_computed_value_time(name, registration, context);
1497 } else {
1498 context.builder.substitution_functions.remove_attr(name);
1499 }
1500 return;
1501 };
1502
1503 let inherited = context.builder.inherited_style.custom_properties();
1505 if is_var {
1506 let css = &substitution.css;
1507 let css_wide_kw = {
1508 let mut input = Parser::new(css);
1509 input.try_parse(CSSWideKeyword::parse)
1510 };
1511
1512 if let Ok(kw) = css_wide_kw {
1513 match (kw, registration.inherits(), context.is_root_element()) {
1517 (CSSWideKeyword::Initial, _, _)
1518 | (CSSWideKeyword::Revert, false, _)
1519 | (CSSWideKeyword::RevertLayer, false, _)
1520 | (CSSWideKeyword::RevertRule, false, _)
1521 | (CSSWideKeyword::Unset, false, _)
1522 | (CSSWideKeyword::Revert, true, true)
1523 | (CSSWideKeyword::RevertLayer, true, true)
1524 | (CSSWideKeyword::RevertRule, true, true)
1525 | (CSSWideKeyword::Unset, true, true)
1526 | (CSSWideKeyword::Inherit, _, true) => {
1527 remove_and_insert_initial_value(
1528 name,
1529 registration,
1530 &mut context.builder.substitution_functions,
1531 );
1532 },
1533 (CSSWideKeyword::Revert, true, false)
1534 | (CSSWideKeyword::RevertLayer, true, false)
1535 | (CSSWideKeyword::RevertRule, true, false)
1536 | (CSSWideKeyword::Inherit, _, false)
1537 | (CSSWideKeyword::Unset, true, false) => {
1538 match inherited.get(registration, name) {
1539 Some(value) => {
1540 context.builder.substitution_functions.insert_var(
1541 registration,
1542 name,
1543 value.clone(),
1544 );
1545 },
1546 None => {
1547 context
1548 .builder
1549 .substitution_functions
1550 .remove_var(registration, name);
1551 },
1552 };
1553 },
1554 }
1555 return;
1556 }
1557 }
1558
1559 match kind {
1560 SubstitutionFunctionKind::Var => {
1561 let value = match substitution.into_value(
1562 url_data,
1563 registration,
1564 context,
1565 Some(PropertyIdRef::from(name)),
1566 ) {
1567 Ok(v) => v,
1568 Err(()) => {
1569 handle_invalid_at_computed_value_time(name, registration, context);
1570 return;
1571 },
1572 };
1573 context
1574 .builder
1575 .substitution_functions
1576 .insert_var(registration, name, value);
1577 },
1578 SubstitutionFunctionKind::Attr => {
1579 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1580 substitution.css.into_owned(),
1581 url_data,
1582 substitution.first_token_type,
1583 substitution.last_token_type,
1584 )));
1585 value.attr_tainted |= substitution.attr_tainted;
1586 context
1587 .builder
1588 .substitution_functions
1589 .insert_attr(name, value);
1590 },
1591 SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
1592 }
1593}
1594
1595#[derive(Default, Debug)]
1596struct Substitution<'a> {
1597 css: Cow<'a, str>,
1598 first_token_type: TokenSerializationType,
1599 last_token_type: TokenSerializationType,
1600 attr_tainted: bool,
1601}
1602
1603impl<'a> Substitution<'a> {
1604 fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
1605 Substitution {
1606 css: v.css.into(),
1607 first_token_type: v.first_token_type,
1608 last_token_type: v.last_token_type,
1609 attr_tainted,
1610 }
1611 }
1612
1613 fn into_value(
1614 self,
1615 url_data: &UrlExtraData,
1616 registration: &PropertyDescriptors,
1617 computed_context: &computed::Context,
1618 property_id: Option<PropertyIdRef>,
1619 ) -> Result<ComputedRegisteredValue, ()> {
1620 if registration.is_universal() {
1621 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1622 self.css.into_owned(),
1623 url_data,
1624 self.first_token_type,
1625 self.last_token_type,
1626 )));
1627 value.attr_tainted |= self.attr_tainted;
1628 return Ok(value);
1629 }
1630 let taint = if self.attr_tainted {
1631 AttrTaint::new_fully_tainted(self.css.len())
1636 } else {
1637 AttrTaint::default()
1638 };
1639 let mut v = compute_value(
1640 &self.css,
1641 url_data,
1642 registration,
1643 computed_context,
1644 taint,
1645 property_id,
1646 )?;
1647 v.attr_tainted |= self.attr_tainted;
1648 Ok(v)
1649 }
1650
1651 fn new(
1652 css: Cow<'a, str>,
1653 first_token_type: TokenSerializationType,
1654 last_token_type: TokenSerializationType,
1655 attr_tainted: bool,
1656 ) -> Self {
1657 Self {
1658 css,
1659 first_token_type,
1660 last_token_type,
1661 attr_tainted,
1662 }
1663 }
1664}
1665
1666#[derive(Debug)]
1668pub struct SubstitutionResult<'a> {
1669 pub css: Cow<'a, str>,
1671 pub attr_taint: AttrTaint,
1673}
1674
1675fn compute_value(
1676 css: &str,
1677 url_data: &UrlExtraData,
1678 registration: &PropertyDescriptors,
1679 computed_context: &computed::Context,
1680 attr_taint: AttrTaint,
1681 property_id: Option<PropertyIdRef>,
1682) -> Result<ComputedRegisteredValue, ()> {
1683 debug_assert!(!registration.is_universal());
1684
1685 let mut input = Parser::new(css);
1686 SpecifiedRegisteredValue::compute(
1687 &mut input,
1688 registration,
1689 None,
1690 url_data,
1691 computed_context,
1692 AllowComputationallyDependent::Yes,
1693 attr_taint,
1694 property_id,
1695 )
1696}
1697
1698pub(crate) fn remove_and_insert_initial_value(
1700 name: &Name,
1701 registration: &PropertyDescriptors,
1702 substitution_functions: &mut ComputedSubstitutionFunctions,
1703) {
1704 substitution_functions.remove_var(registration, name);
1705 if let Some(ref initial_value) = registration.initial_value {
1706 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
1707 substitution_functions.insert_var(registration, name, value);
1708 }
1709}
1710
1711fn do_substitute_chunk<'a>(
1712 css: &'a str,
1713 start: usize,
1714 end: usize,
1715 first_token_type: TokenSerializationType,
1716 last_token_type: TokenSerializationType,
1717 url_data: &UrlExtraData,
1718 property_id: Option<PropertyIdRef>,
1719 substitution_functions: &'a ComputedSubstitutionFunctions,
1720 stylist: &Stylist,
1721 computed_context: &computed::Context,
1722 references: &'a [SubstitutionFunctionReference],
1723 attribute_tracker: &mut AttributeTracker,
1724 seen: &mut SmallVec<[&'a Name; 8]>,
1725 mut attr_taint: Option<&mut AttrTaint>,
1726) -> Result<Substitution<'a>, ()> {
1727 if start == end {
1728 return Ok(Substitution::default());
1730 }
1731 if references.is_empty() {
1733 let result = &css[start..end];
1734 return Ok(Substitution::new(
1735 Cow::Borrowed(result),
1736 first_token_type,
1737 last_token_type,
1738 Default::default(),
1739 ));
1740 }
1741
1742 let mut substituted = ComputedValue::empty(url_data);
1743 let mut next_token_type = first_token_type;
1744 let mut cur_pos = start;
1745 let mut attr_tainted = false;
1746 let references = references.iter();
1747 for reference in references {
1748 if reference.start != cur_pos {
1749 substituted.push(
1750 &css[cur_pos..reference.start],
1751 next_token_type,
1752 reference.prev_token_type,
1753 None,
1754 )?;
1755 }
1756
1757 let substitution = substitute_one_reference(
1758 css,
1759 url_data,
1760 property_id,
1761 substitution_functions,
1762 reference,
1763 stylist,
1764 computed_context,
1765 attribute_tracker,
1766 seen,
1767 )?;
1768
1769 if reference.start == start && reference.end == end {
1771 if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
1772 taint.push(start, substitution.css.len());
1773 }
1774 return Ok(substitution);
1775 }
1776
1777 substituted.push(
1778 &substitution.css,
1779 substitution.first_token_type,
1780 substitution.last_token_type,
1781 attr_taint
1782 .as_deref_mut()
1783 .filter(|_| substitution.attr_tainted),
1784 )?;
1785 attr_tainted |= substitution.attr_tainted;
1786 next_token_type = reference.next_token_type;
1787 cur_pos = reference.end;
1788 }
1789 if cur_pos != end {
1791 substituted.push(
1792 &css[cur_pos..end],
1793 next_token_type,
1794 last_token_type,
1795 None,
1796 )?;
1797 }
1798 Ok(Substitution::from_value(substituted, attr_tainted))
1799}
1800
1801fn quoted_css_string(src: &str) -> String {
1802 let mut dest = String::with_capacity(src.len() + 2);
1803 cssparser::serialize_string(src, &mut dest).unwrap();
1804 dest
1805}
1806
1807fn substitute_one_reference<'a>(
1808 css: &'a str,
1809 url_data: &UrlExtraData,
1810 property_id: Option<PropertyIdRef>,
1811 substitution_functions: &'a ComputedSubstitutionFunctions,
1812 reference: &'a SubstitutionFunctionReference,
1813 stylist: &Stylist,
1814 computed_context: &computed::Context,
1815 attribute_tracker: &mut AttributeTracker,
1816 seen: &mut SmallVec<[&'a Name; 8]>,
1817) -> Result<Substitution<'a>, ()> {
1818 let simple_attr_subst = |s: &str| {
1819 Some(Substitution::new(
1820 Cow::Owned(quoted_css_string(s)),
1821 TokenSerializationType::Nothing,
1822 TokenSerializationType::Nothing,
1823 true,
1824 ))
1825 };
1826 let substitution: Option<_> = match reference.substitution_kind {
1827 SubstitutionFunctionKind::Var => {
1828 let registration = stylist.get_custom_property_registration(&reference.name);
1829 match substitution_functions.get_var(registration, &reference.name) {
1830 None => None,
1831 Some(v) => match v.as_universal() {
1837 Some(u) if u.has_references() => {
1838 if seen.contains(&&reference.name) {
1839 None
1842 } else {
1843 seen.push(&reference.name);
1844 let result = substitute_internal(
1845 u,
1846 property_id,
1847 substitution_functions,
1848 stylist,
1849 computed_context,
1850 attribute_tracker,
1851 seen,
1852 None,
1853 );
1854 seen.pop();
1855 match result {
1856 Ok(mut substitution) => {
1857 substitution.attr_tainted |= v.attr_tainted;
1858 Some(substitution)
1859 },
1860 Err(()) => None,
1862 }
1863 }
1864 },
1865 _ => Some(Substitution::from_value(
1866 v.to_variable_value(),
1867 v.attr_tainted,
1868 )),
1869 },
1870 }
1871 },
1872 SubstitutionFunctionKind::Env => {
1873 let device = stylist.device();
1874 device
1875 .environment()
1876 .get(&reference.name, device, url_data)
1877 .map(|v| Substitution::from_value(v, false))
1878 },
1879 SubstitutionFunctionKind::Attr => {
1881 let namespace = match reference.attribute_data.namespace {
1882 ParsedNamespace::Known(ref ns) => Some(ns),
1883 ParsedNamespace::Unknown => None,
1884 };
1885 namespace
1886 .and_then(|namespace| {
1887 LocalName::with_name(&reference.name, |local_name| {
1888 attribute_tracker.query(local_name, namespace)
1889 })
1890 })
1891 .map_or_else(
1892 || {
1893 if reference.fallback.is_none()
1896 && reference.attribute_data.kind == AttributeType::None
1897 {
1898 simple_attr_subst("")
1899 } else {
1900 None
1901 }
1902 },
1903 |attr| {
1904 let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
1905 if computed_context.in_container_query {
1912 attr
1913 } else {
1914 substitution_functions
1915 .get_attr(&reference.name)
1916 .map(|v| v.to_variable_value())?
1917 .css
1918 }
1919 } else {
1920 attr
1921 };
1922 let mut parser = Parser::new(&attr);
1923 match &reference.attribute_data.kind {
1924 AttributeType::Unit(unit) => {
1925 let css = {
1926 parser.expect_number().ok()?;
1928 let mut s = attr.clone();
1929 s.push_str(unit.as_ref());
1930 s
1931 };
1932 let serialization = match unit {
1933 AttrUnit::Number => TokenSerializationType::Number,
1934 AttrUnit::Percentage => TokenSerializationType::Percentage,
1935 _ => TokenSerializationType::Dimension,
1936 };
1937 let value =
1938 ComputedValue::new(css, url_data, serialization, serialization);
1939 Some(Substitution::from_value(
1940 value, true,
1941 ))
1942 },
1943 AttributeType::Type(syntax) => {
1944 let value = SpecifiedRegisteredValue::parse(
1945 &mut parser,
1946 syntax,
1947 url_data,
1948 None,
1949 AllowComputationallyDependent::Yes,
1950 AttrTaint::default(),
1951 property_id,
1952 )
1953 .ok()?;
1954 let value = value.to_variable_value();
1955 Some(Substitution::from_value(
1956 value, true,
1957 ))
1958 },
1959 AttributeType::RawString | AttributeType::None => {
1960 simple_attr_subst(&attr)
1961 },
1962 AttributeType::Invalid => None,
1963 }
1964 },
1965 )
1966 },
1967 };
1968
1969 if let Some(s) = substitution {
1970 return Ok(s);
1971 }
1972
1973 let Some(ref fallback) = reference.fallback else {
1974 return Err(());
1975 };
1976
1977 do_substitute_chunk(
1978 css,
1979 fallback.start.get(),
1980 reference.end - 1, fallback.first_token_type,
1982 fallback.last_token_type,
1983 url_data,
1984 property_id,
1985 substitution_functions,
1986 stylist,
1987 computed_context,
1988 &fallback.references.refs,
1989 attribute_tracker,
1990 seen,
1991 None,
1992 )
1993}
1994
1995fn substitute_internal<'a>(
1997 variable_value: &'a VariableValue,
1998 property_id: Option<PropertyIdRef>,
1999 substitution_functions: &'a ComputedSubstitutionFunctions,
2000 stylist: &Stylist,
2001 computed_context: &computed::Context,
2002 attribute_tracker: &mut AttributeTracker,
2003 seen: &mut SmallVec<[&'a Name; 8]>,
2004 attr_taint: Option<&mut AttrTaint>,
2005) -> Result<Substitution<'a>, ()> {
2006 do_substitute_chunk(
2007 &variable_value.css,
2008 0,
2009 variable_value.css.len(),
2010 variable_value.first_token_type,
2011 variable_value.last_token_type,
2012 &variable_value.url_data,
2013 property_id,
2014 substitution_functions,
2015 stylist,
2016 computed_context,
2017 &variable_value.references.refs,
2018 attribute_tracker,
2019 seen,
2020 attr_taint,
2021 )
2022}
2023
2024pub fn substitute<'a>(
2026 variable_value: &'a VariableValue,
2027 property_id: Option<PropertyIdRef>,
2028 substitution_functions: &'a ComputedSubstitutionFunctions,
2029 stylist: &Stylist,
2030 computed_context: &computed::Context,
2031 attribute_tracker: &mut AttributeTracker,
2032) -> Result<SubstitutionResult<'a>, ()> {
2033 debug_assert!(variable_value.has_references());
2034 let mut attr_taint = AttrTaint::default();
2035 let v = substitute_internal(
2036 variable_value,
2037 property_id,
2038 substitution_functions,
2039 stylist,
2040 computed_context,
2041 attribute_tracker,
2042 &mut SmallVec::new(),
2043 Some(&mut attr_taint),
2044 )?;
2045 Ok(SubstitutionResult {
2046 css: v.css,
2047 attr_taint,
2048 })
2049}