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