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::stylesheets::container_rule::AttrReferenceSet;
23use crate::stylist::Stylist;
24use crate::typed_om::{
25 ToTyped, TypedValue, UnparsedSegment, UnparsedValue, VariableReferenceValue,
26};
27use crate::values::computed;
28use crate::values::generics::calc::SortKey as AttrUnit;
29use crate::values::specified::{NoCalcLength, ParsedNamespace};
30use crate::{Atom, LocalName, Namespace, Prefix, derives::*};
31use cssparser::{CowRcStr, Delimiter, Parser, SourcePosition, Token, TokenSerializationType};
32use rustc_hash::FxHashMap;
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) -> Option<ComputedRegisteredValue> {
306 if registration.is_universal() {
307 return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
308 }
309 compute_value(
310 &value.css,
311 &value.url_data,
312 registration,
313 computed_context,
314 AttrTaint::default(),
315 )
316 .ok()
317}
318
319impl PartialEq for VariableValue {
321 fn eq(&self, other: &Self) -> bool {
322 self.css == other.css
323 }
324}
325
326impl Eq for VariableValue {}
327
328impl ToCss for SpecifiedValue {
329 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
330 where
331 W: Write,
332 {
333 dest.write_str(&self.css)
334 }
335}
336
337impl ToTyped for SpecifiedValue {
338 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
339 let unparsed_value = reify_variable_value(self)?;
340 dest.push(TypedValue::Unparsed(unparsed_value));
341 Ok(())
342 }
343}
344
345fn reify_variable_value(value: &VariableValue) -> Result<UnparsedValue, ()> {
346 let mut reference_index = 0;
347 reify_variable_value_range(
348 &value.css,
349 &value.references.refs,
350 &mut reference_index,
351 0,
352 value.css.len(),
353 )
354}
355
356fn reify_variable_value_range(
363 css: &str,
364 references: &[SubstitutionFunctionReference],
365 reference_index: &mut usize,
366 start: usize,
367 end: usize,
368) -> Result<UnparsedValue, ()> {
369 debug_assert!(start <= end);
370 debug_assert!(end <= css.len());
371
372 let mut values = ThinVec::new();
373 let mut cur_pos = start;
374
375 while *reference_index < references.len() {
376 let reference = &references[*reference_index];
377
378 if reference.start >= end {
379 break;
380 }
381
382 debug_assert!(reference.start >= cur_pos);
383 debug_assert!(reference.start <= reference.end);
384 debug_assert!(reference.end <= css.len());
385
386 if cur_pos < reference.start {
387 values.push(UnparsedSegment::String(CssString::from(
388 &css[cur_pos..reference.start],
389 )));
390 }
391
392 *reference_index += 1;
393
394 if reference.substitution_kind != SubstitutionFunctionKind::Var {
395 return Err(());
396 }
397
398 let (fallback, has_fallback) = if let Some(fallback) = &reference.fallback {
399 debug_assert!(fallback.start.get() < reference.end);
400
401 (
402 reify_variable_value_range(
403 css,
404 references,
405 reference_index,
406 fallback.start.get(),
407 reference.end - 1, )?,
409 true,
410 )
411 } else {
412 (ThinVec::new(), false)
413 };
414
415 values.push(UnparsedSegment::VariableReference(VariableReferenceValue {
416 variable: CssString::from(format!("--{}", reference.name)),
417 fallback,
418 has_fallback,
419 }));
420
421 cur_pos = reference.end;
422 }
423
424 if cur_pos < end {
425 values.push(UnparsedSegment::String(CssString::from(&css[cur_pos..end])));
426 }
427
428 Ok(values)
429}
430
431#[repr(C)]
434#[derive(Clone, Debug, Default, PartialEq)]
435pub struct ComputedCustomProperties {
436 pub inherited: CustomPropertiesMap,
439 pub non_inherited: CustomPropertiesMap,
441}
442
443impl ComputedCustomProperties {
444 pub fn is_empty(&self) -> bool {
446 self.inherited.is_empty() && self.non_inherited.is_empty()
447 }
448
449 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
451 self.inherited
454 .get_index(index)
455 .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
456 }
457
458 pub fn insert(
461 &mut self,
462 registration: &PropertyDescriptors,
463 name: &Name,
464 value: ComputedRegisteredValue,
465 ) {
466 self.map_mut(registration).insert(name, value)
467 }
468
469 pub fn remove(&mut self, registration: &PropertyDescriptors, name: &Name) {
472 self.map_mut(registration).remove(name);
473 }
474
475 pub fn shrink_to_fit(&mut self) {
477 self.inherited.shrink_to_fit();
478 self.non_inherited.shrink_to_fit();
479 }
480
481 fn map_mut(&mut self, registration: &PropertyDescriptors) -> &mut CustomPropertiesMap {
482 if registration.inherits() {
483 &mut self.inherited
484 } else {
485 &mut self.non_inherited
486 }
487 }
488
489 pub fn get(
491 &self,
492 registration: &PropertyDescriptors,
493 name: &Name,
494 ) -> Option<&ComputedRegisteredValue> {
495 if registration.inherits() {
496 self.inherited.get(name)
497 } else {
498 self.non_inherited.get(name)
499 }
500 }
501
502 pub fn get_for_cssom(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
505 self.inherited
506 .get(name)
507 .or_else(|| self.non_inherited.get(name))
508 }
509}
510
511pub type SpecifiedValue = VariableValue;
514pub type ComputedValue = VariableValue;
517
518#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
520pub struct ReferenceFlags(u8);
521
522bitflags! {
523 impl ReferenceFlags : u8 {
524 const FONT_UNITS = 1 << 0;
526 const ROOT_FONT_UNITS = 1 << 1;
528 const LH_UNITS = 1 << 2;
530 const ROOT_LH_UNITS = 1 << 3;
532 const COLOR_SCHEME = 1 << 4;
534 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
536 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
538 const NON_CUSTOM = Self::NON_ROOT_DEPENDENCIES.0 | Self::ROOT_DEPENDENCIES.0;
540 const ATTR = 1 << 5;
542 const ENV = 1 << 6;
544 const VAR = 1 << 7;
546 }
547}
548
549impl ReferenceFlags {
550 pub fn for_each_non_custom<F>(mut self, is_root_element: bool, mut f: F)
552 where
553 F: FnMut(SingleNonCustomReference),
554 {
555 if is_root_element {
557 if self.intersects(Self::ROOT_FONT_UNITS) {
558 self.remove(Self::ROOT_FONT_UNITS);
559 self |= Self::FONT_UNITS;
560 }
561 if self.intersects(Self::ROOT_LH_UNITS) {
562 self.remove(Self::ROOT_FONT_UNITS);
563 self |= Self::LH_UNITS;
564 }
565 }
566
567 for (_, r) in self.iter_names() {
568 let single = match r {
569 Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
570 Self::LH_UNITS => SingleNonCustomReference::LhUnits,
571 Self::COLOR_SCHEME => SingleNonCustomReference::ColorScheme,
572 Self::ROOT_FONT_UNITS
573 | Self::ROOT_LH_UNITS
574 | Self::VAR
575 | Self::ENV
576 | Self::ATTR => continue,
577 _ => unreachable!("Unexpected single bit value"),
578 };
579 f(single);
580 }
581 }
582
583 fn from_unit(value: &CowRcStr) -> Self {
584 if value.eq_ignore_ascii_case(NoCalcLength::LH) {
589 return Self::FONT_UNITS | Self::LH_UNITS;
590 }
591 if value.eq_ignore_ascii_case(NoCalcLength::EM)
592 || value.eq_ignore_ascii_case(NoCalcLength::EX)
593 || value.eq_ignore_ascii_case(NoCalcLength::CAP)
594 || value.eq_ignore_ascii_case(NoCalcLength::CH)
595 || value.eq_ignore_ascii_case(NoCalcLength::IC)
596 {
597 return Self::FONT_UNITS;
598 }
599 if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
600 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
601 }
602 if value.eq_ignore_ascii_case(NoCalcLength::REM)
603 || value.eq_ignore_ascii_case(NoCalcLength::REX)
604 || value.eq_ignore_ascii_case(NoCalcLength::RCH)
605 || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
606 || value.eq_ignore_ascii_case(NoCalcLength::RIC)
607 {
608 return Self::ROOT_FONT_UNITS;
609 }
610 Self::empty()
611 }
612}
613
614#[derive(Clone, Copy, Debug, Eq, PartialEq)]
617#[allow(missing_docs)]
618pub enum SingleNonCustomReference {
619 FontUnits = 0,
620 LhUnits,
621 ColorScheme,
622}
623
624impl SingleNonCustomReference {
625 pub fn to_prioritary_id(self) -> PrioritaryPropertyId {
627 match self {
628 Self::FontUnits => PrioritaryPropertyId::FontSize,
629 Self::LhUnits => PrioritaryPropertyId::LineHeight,
630 Self::ColorScheme => PrioritaryPropertyId::ColorScheme,
631 }
632 }
633}
634
635pub struct NonCustomReferenceMap<T>([Option<T>; 3]);
637
638impl<T> Default for NonCustomReferenceMap<T> {
639 fn default() -> Self {
640 NonCustomReferenceMap(Default::default())
641 }
642}
643
644impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
645 type Output = Option<T>;
646
647 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
648 &self.0[reference as usize]
649 }
650}
651
652impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
653 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
654 &mut self.0[reference as usize]
655 }
656}
657
658#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse)]
660pub enum SubstitutionFunctionKind {
661 Var,
663 Env,
665 Attr,
667}
668
669#[repr(C)]
672#[derive(Clone, Debug, Default, PartialEq)]
673pub struct ComputedSubstitutionFunctions {
674 pub custom_properties: ComputedCustomProperties,
676 pub attributes: OwnMap,
678}
679
680impl ComputedSubstitutionFunctions {
681 #[inline(always)]
684 pub fn new(
685 custom_properties: Option<ComputedCustomProperties>,
686 attributes: Option<OwnMap>,
687 ) -> Self {
688 Self {
689 custom_properties: custom_properties.unwrap_or_default(),
690 attributes: attributes.unwrap_or_default(),
691 }
692 }
693
694 #[inline(always)]
695 pub(crate) fn insert_var(
696 &mut self,
697 registration: &PropertyDescriptors,
698 name: &Name,
699 value: ComputedRegisteredValue,
700 ) {
701 self.custom_properties.insert(registration, name, value);
702 }
703
704 #[inline(always)]
705 pub(crate) fn insert_attr(&mut self, name: &Name, value: ComputedRegisteredValue) {
706 self.attributes.insert(name.clone(), Some(value));
707 }
708
709 #[inline(always)]
710 pub(crate) fn remove_var(&mut self, registration: &PropertyDescriptors, name: &Name) {
711 self.custom_properties.remove(registration, name);
712 }
713
714 #[inline(always)]
715 pub(crate) fn remove_attr(&mut self, name: &Name) {
716 self.attributes.insert(name.clone(), None);
717 }
718
719 #[inline(always)]
720 pub(crate) fn get_var(
721 &self,
722 registration: &PropertyDescriptors,
723 name: &Name,
724 ) -> Option<&ComputedRegisteredValue> {
725 self.custom_properties.get(registration, name)
726 }
727
728 #[inline(always)]
729 pub(crate) fn get_attr(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
730 self.attributes.get(name).and_then(|p| p.as_ref())
731 }
732}
733
734#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
735enum AttributeType {
736 Invalid,
737 None,
738 RawString,
739 Type(SyntaxDescriptor),
740 Unit(AttrUnit),
741}
742
743#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
745pub struct AttributeData {
746 kind: AttributeType,
747 namespace: ParsedNamespace,
748}
749
750#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
752pub struct AttrTaintedRange {
753 start: usize,
755 end: usize,
757}
758
759impl AttrTaintedRange {
760 #[inline(always)]
762 pub fn new(start: usize, end: usize) -> Self {
763 debug_assert!(start <= end);
764 Self { start, end }
765 }
766}
767
768#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
773pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
774
775impl AttrTaint {
776 #[inline(always)]
779 pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
780 self.0
781 .iter()
782 .any(|r| r.start <= range.end && r.end >= range.start)
783 }
784
785 #[inline(always)]
787 pub fn is_empty(&self) -> bool {
788 self.0.is_empty()
789 }
790
791 #[inline(always)]
792 fn new_fully_tainted(end: usize) -> Self {
793 let mut taint = Self::default();
794 taint.push(0, end);
795 taint
796 }
797
798 #[inline(always)]
799 fn push(&mut self, start: usize, end: usize) {
800 self.0.push(AttrTaintedRange::new(start, end));
801 }
802}
803
804#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
806pub struct VariableFallback {
807 start: num::NonZeroUsize,
811 first_token_type: TokenSerializationType,
812 last_token_type: TokenSerializationType,
813 pub references: References,
815}
816
817#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
819pub struct SubstitutionFunctionReference {
820 pub name: Name,
822 start: usize,
823 end: usize,
824 pub fallback: Option<VariableFallback>,
826 pub attribute_data: AttributeData,
828 prev_token_type: TokenSerializationType,
829 next_token_type: TokenSerializationType,
830 pub substitution_kind: SubstitutionFunctionKind,
832}
833
834impl SubstitutionFunctionReference {
835 pub fn is_attr_with_type(&self) -> bool {
837 self.substitution_kind == SubstitutionFunctionKind::Attr
838 && matches!(self.attribute_data.kind, AttributeType::Type(..))
839 }
840}
841
842#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
845pub struct References {
846 pub refs: Vec<SubstitutionFunctionReference>,
848 pub flags: ReferenceFlags,
853}
854
855impl References {
856 fn has_references(&self) -> bool {
857 !self.refs.is_empty()
858 }
859
860 pub(crate) fn non_custom_references(&self, is_root_element: bool) -> ReferenceFlags {
861 let mut mask = ReferenceFlags::NON_ROOT_DEPENDENCIES;
862 if is_root_element {
863 mask |= ReferenceFlags::ROOT_DEPENDENCIES
864 }
865 self.flags & mask
866 }
867}
868
869impl VariableValue {
870 fn empty(url_data: &UrlExtraData) -> Self {
871 Self {
872 css: String::new(),
873 last_token_type: Default::default(),
874 first_token_type: Default::default(),
875 url_data: url_data.clone(),
876 references: Default::default(),
877 explicitly_attr_tainted: false,
878 }
879 }
880
881 pub fn new(
884 css: String,
885 url_data: &UrlExtraData,
886 first_token_type: TokenSerializationType,
887 last_token_type: TokenSerializationType,
888 ) -> Self {
889 Self {
890 css,
891 url_data: url_data.clone(),
892 first_token_type,
893 last_token_type,
894 references: References::default(),
895 explicitly_attr_tainted: false,
896 }
897 }
898
899 fn push(
900 &mut self,
901 css: &str,
902 css_first_token_type: TokenSerializationType,
903 css_last_token_type: TokenSerializationType,
904 attr_taint: Option<&mut AttrTaint>,
905 ) -> Result<(), ()> {
906 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
914
915 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
916 return Err(());
917 }
918
919 if css.is_empty() {
924 return Ok(());
925 }
926
927 self.first_token_type.set_if_nothing(css_first_token_type);
928 if self
931 .last_token_type
932 .needs_separator_when_before(css_first_token_type)
933 {
934 self.css.push_str("/**/")
935 }
936 let start = self.css.len();
937 self.css.push_str(css);
938 let end = self.css.len();
939 if let Some(taint) = attr_taint {
940 taint.push(start, end);
941 }
942 self.last_token_type = css_last_token_type;
943 Ok(())
944 }
945
946 pub fn parse(
948 input: &mut Parser,
949 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
950 url_data: &UrlExtraData,
951 ) -> Result<Self, ParseError> {
952 let mut references = References::default();
953 let mut missing_closing_characters = String::new();
954 let start_position = input.position();
955 let (first_token_type, last_token_type) = parse_declaration_value(
956 input,
957 start_position,
958 namespaces,
959 &mut references,
960 &mut missing_closing_characters,
961 )?;
962 let mut css = input
963 .slice_from(start_position)
964 .trim_ascii_start()
965 .to_owned();
966 if !missing_closing_characters.is_empty() {
967 if css.ends_with("\\")
969 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
970 {
971 css.pop();
972 }
973 css.push_str(&missing_closing_characters);
974 }
975
976 css.truncate(css.trim_ascii_end().len());
977 css.shrink_to_fit();
978 references.refs.shrink_to_fit();
979
980 Ok(Self {
981 css,
982 url_data: url_data.clone(),
983 first_token_type,
984 last_token_type,
985 references,
986 explicitly_attr_tainted: false,
987 })
988 }
989
990 pub fn is_attr_tainted(&self) -> bool {
992 self.references.flags.intersects(ReferenceFlags::ATTR) | self.explicitly_attr_tainted
993 }
994
995 fn integer(number: i32, url_data: &UrlExtraData) -> Self {
997 Self::from_token(
998 Token::Number {
999 has_sign: false,
1000 value: number as f32,
1001 int_value: Some(number),
1002 },
1003 url_data,
1004 )
1005 }
1006
1007 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
1009 Self::from_token(Token::Ident(ident.into()), url_data)
1010 }
1011
1012 fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
1014 Self::from_token(
1018 Token::Dimension {
1019 has_sign: false,
1020 value: number,
1021 int_value: None,
1022 unit: CowRcStr::from("px"),
1023 },
1024 url_data,
1025 )
1026 }
1027
1028 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
1030 Self::from_token(
1031 Token::Dimension {
1032 has_sign: false,
1033 value: number as f32,
1034 int_value: Some(number),
1035 unit: CowRcStr::from("ms"),
1036 },
1037 url_data,
1038 )
1039 }
1040
1041 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
1043 Self::from_token(
1044 Token::Dimension {
1045 has_sign: false,
1046 value: number as f32,
1047 int_value: Some(number),
1048 unit: CowRcStr::from("px"),
1049 },
1050 url_data,
1051 )
1052 }
1053
1054 fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
1055 let token_type = token.serialization_type();
1056 let mut css = token.to_css_string();
1057 css.shrink_to_fit();
1058
1059 VariableValue {
1060 css,
1061 url_data: url_data.clone(),
1062 first_token_type: token_type,
1063 last_token_type: token_type,
1064 references: Default::default(),
1065 explicitly_attr_tainted: false,
1066 }
1067 }
1068
1069 pub fn css_text(&self) -> &str {
1071 &self.css
1072 }
1073
1074 pub fn has_references(&self) -> bool {
1077 self.references.has_references()
1078 }
1079
1080 pub fn collect_attribute_references(&self, references: &mut AttrReferenceSet) {
1082 self.references.refs.iter().for_each(|r| {
1083 if r.substitution_kind == SubstitutionFunctionKind::Attr {
1084 references.insert(r.name.clone().into());
1090 let lowercase = r.name.to_ascii_lowercase();
1091 if r.name != lowercase {
1092 references.insert(lowercase.into());
1093 }
1094 }
1095 })
1096 }
1097}
1098
1099fn parse_declaration_value(
1101 input: &mut Parser,
1102 input_start: SourcePosition,
1103 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1104 references: &mut References,
1105 missing_closing_characters: &mut String,
1106) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
1107 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
1108 parse_declaration_value_block(
1109 input,
1110 input_start,
1111 namespaces,
1112 references,
1113 missing_closing_characters,
1114 )
1115 })
1116}
1117
1118fn parse_declaration_value_block(
1120 input: &mut Parser,
1121 input_start: SourcePosition,
1122 namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1123 references: &mut References,
1124 missing_closing_characters: &mut String,
1125) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
1126 let mut is_first = true;
1127 let mut first_token_type = TokenSerializationType::Nothing;
1128 let mut last_token_type = TokenSerializationType::Nothing;
1129 let mut prev_reference_index: Option<usize> = None;
1130 loop {
1131 let token_start = input.position();
1132 let Ok(token) = input.next_including_whitespace_and_comments() else {
1133 break;
1134 };
1135
1136 let prev_token_type = last_token_type;
1137 let serialization_type = token.serialization_type();
1138 last_token_type = serialization_type;
1139 if is_first {
1140 first_token_type = last_token_type;
1141 is_first = false;
1142 }
1143
1144 macro_rules! nested {
1145 ($closing:expr) => {{
1146 let mut inner_end_position = None;
1147 let result = input.parse_nested_block(|input| {
1148 let result = parse_declaration_value_block(
1149 input,
1150 input_start,
1151 namespaces,
1152 references,
1153 missing_closing_characters,
1154 )?;
1155 inner_end_position = Some(input.position());
1156 Ok(result)
1157 })?;
1158 if inner_end_position.unwrap() == input.position() {
1159 missing_closing_characters.push_str($closing);
1160 }
1161 result
1162 }};
1163 }
1164 if let Some(index) = prev_reference_index.take() {
1165 references.refs[index].next_token_type = serialization_type;
1166 }
1167 match *token {
1168 Token::Comment(_) => {
1169 let token_slice = input.slice_from(token_start);
1170 if !token_slice.ends_with("*/") {
1171 missing_closing_characters.push_str(if token_slice.ends_with('*') {
1172 "/"
1173 } else {
1174 "*/"
1175 })
1176 }
1177 },
1178 Token::BadUrl(..) => {
1179 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock;
1180 return Err(ParseError::custom(e));
1181 },
1182 Token::BadString(..) => {
1183 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock;
1184 return Err(ParseError::custom(e));
1185 },
1186 Token::CloseParenthesis => {
1187 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1188 return Err(ParseError::custom(e));
1189 },
1190 Token::CloseSquareBracket => {
1191 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1192 return Err(ParseError::custom(e));
1193 },
1194 Token::CloseCurlyBracket => {
1195 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1196 return Err(ParseError::custom(e));
1197 },
1198 Token::Function(ref name) => {
1199 let substitution_kind = SubstitutionFunctionKind::from_ident(name).ok();
1200 if let Some(substitution_kind) = substitution_kind {
1201 let our_ref_index = references.refs.len();
1202 let mut input_end_position = None;
1203 let fallback = input.parse_nested_block(|input| {
1204 let mut namespace = ParsedNamespace::Known(Namespace::default());
1205 if substitution_kind == SubstitutionFunctionKind::Attr {
1206 if let Some(namespaces) = namespaces {
1207 if let Ok(ns) = input
1208 .try_parse(|input| ParsedNamespace::parse(namespaces, input))
1209 {
1210 namespace = ns;
1211 let prev = input.state();
1212 let next = match *input.next_including_whitespace()? {
1213 Token::Ident(_) => Ok(()),
1214 _ => Err(ParseError::unexpected_token()),
1215 };
1216 input.reset(&prev);
1217 next?;
1218 }
1219 }
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 if let Ok(initial_value) = compute_value(
1439 &initial_value.css,
1440 &initial_value.url_data,
1441 registration,
1442 context,
1443 AttrTaint::default(),
1444 ) {
1445 context.builder.substitution_functions.insert_var(
1446 registration,
1447 name,
1448 initial_value,
1449 );
1450 return;
1451 }
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 &context.builder.substitution_functions,
1486 stylist,
1487 context,
1488 attribute_tracker,
1489 &mut SmallVec::new(),
1490 None,
1491 );
1492
1493 let Ok(substitution) = substitution else {
1494 if is_var {
1495 handle_invalid_at_computed_value_time(name, registration, context);
1496 } else {
1497 context.builder.substitution_functions.remove_attr(name);
1498 }
1499 return;
1500 };
1501
1502 let inherited = context.builder.inherited_style.custom_properties();
1504 if is_var {
1505 let css = &substitution.css;
1506 let css_wide_kw = {
1507 let mut input = Parser::new(css);
1508 input.try_parse(CSSWideKeyword::parse)
1509 };
1510
1511 if let Ok(kw) = css_wide_kw {
1512 match (kw, registration.inherits(), context.is_root_element()) {
1516 (CSSWideKeyword::Initial, _, _)
1517 | (CSSWideKeyword::Revert, false, _)
1518 | (CSSWideKeyword::RevertLayer, false, _)
1519 | (CSSWideKeyword::RevertRule, false, _)
1520 | (CSSWideKeyword::Unset, false, _)
1521 | (CSSWideKeyword::Revert, true, true)
1522 | (CSSWideKeyword::RevertLayer, true, true)
1523 | (CSSWideKeyword::RevertRule, true, true)
1524 | (CSSWideKeyword::Unset, true, true)
1525 | (CSSWideKeyword::Inherit, _, true) => {
1526 remove_and_insert_initial_value(
1527 name,
1528 registration,
1529 &mut context.builder.substitution_functions,
1530 );
1531 },
1532 (CSSWideKeyword::Revert, true, false)
1533 | (CSSWideKeyword::RevertLayer, true, false)
1534 | (CSSWideKeyword::RevertRule, true, false)
1535 | (CSSWideKeyword::Inherit, _, false)
1536 | (CSSWideKeyword::Unset, true, false) => {
1537 match inherited.get(registration, name) {
1538 Some(value) => {
1539 context.builder.substitution_functions.insert_var(
1540 registration,
1541 name,
1542 value.clone(),
1543 );
1544 },
1545 None => {
1546 context
1547 .builder
1548 .substitution_functions
1549 .remove_var(registration, name);
1550 },
1551 };
1552 },
1553 }
1554 return;
1555 }
1556 }
1557
1558 match kind {
1559 SubstitutionFunctionKind::Var => {
1560 let value = match substitution.into_value(url_data, registration, context) {
1561 Ok(v) => v,
1562 Err(()) => {
1563 handle_invalid_at_computed_value_time(name, registration, context);
1564 return;
1565 },
1566 };
1567 context
1568 .builder
1569 .substitution_functions
1570 .insert_var(registration, name, value);
1571 },
1572 SubstitutionFunctionKind::Attr => {
1573 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1574 substitution.css.into_owned(),
1575 url_data,
1576 substitution.first_token_type,
1577 substitution.last_token_type,
1578 )));
1579 value.attr_tainted |= substitution.attr_tainted;
1580 context
1581 .builder
1582 .substitution_functions
1583 .insert_attr(name, value);
1584 },
1585 SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
1586 }
1587}
1588
1589#[derive(Default, Debug)]
1590struct Substitution<'a> {
1591 css: Cow<'a, str>,
1592 first_token_type: TokenSerializationType,
1593 last_token_type: TokenSerializationType,
1594 attr_tainted: bool,
1595}
1596
1597impl<'a> Substitution<'a> {
1598 fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
1599 Substitution {
1600 css: v.css.into(),
1601 first_token_type: v.first_token_type,
1602 last_token_type: v.last_token_type,
1603 attr_tainted,
1604 }
1605 }
1606
1607 fn into_value(
1608 self,
1609 url_data: &UrlExtraData,
1610 registration: &PropertyDescriptors,
1611 computed_context: &computed::Context,
1612 ) -> Result<ComputedRegisteredValue, ()> {
1613 if registration.is_universal() {
1614 let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1615 self.css.into_owned(),
1616 url_data,
1617 self.first_token_type,
1618 self.last_token_type,
1619 )));
1620 value.attr_tainted |= self.attr_tainted;
1621 return Ok(value);
1622 }
1623 let taint = if self.attr_tainted {
1624 AttrTaint::new_fully_tainted(self.css.len())
1629 } else {
1630 AttrTaint::default()
1631 };
1632 let mut v = compute_value(&self.css, url_data, registration, computed_context, taint)?;
1633 v.attr_tainted |= self.attr_tainted;
1634 Ok(v)
1635 }
1636
1637 fn new(
1638 css: Cow<'a, str>,
1639 first_token_type: TokenSerializationType,
1640 last_token_type: TokenSerializationType,
1641 attr_tainted: bool,
1642 ) -> Self {
1643 Self {
1644 css,
1645 first_token_type,
1646 last_token_type,
1647 attr_tainted,
1648 }
1649 }
1650}
1651
1652#[derive(Debug)]
1654pub struct SubstitutionResult<'a> {
1655 pub css: Cow<'a, str>,
1657 pub attr_taint: AttrTaint,
1659}
1660
1661fn compute_value(
1662 css: &str,
1663 url_data: &UrlExtraData,
1664 registration: &PropertyDescriptors,
1665 computed_context: &computed::Context,
1666 attr_taint: AttrTaint,
1667) -> Result<ComputedRegisteredValue, ()> {
1668 debug_assert!(!registration.is_universal());
1669
1670 let mut input = Parser::new(css);
1671 SpecifiedRegisteredValue::compute(
1672 &mut input,
1673 registration,
1674 None,
1675 url_data,
1676 computed_context,
1677 AllowComputationallyDependent::Yes,
1678 attr_taint,
1679 )
1680}
1681
1682pub(crate) fn remove_and_insert_initial_value(
1684 name: &Name,
1685 registration: &PropertyDescriptors,
1686 substitution_functions: &mut ComputedSubstitutionFunctions,
1687) {
1688 substitution_functions.remove_var(registration, name);
1689 if let Some(ref initial_value) = registration.initial_value {
1690 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
1691 substitution_functions.insert_var(registration, name, value);
1692 }
1693}
1694
1695fn do_substitute_chunk<'a>(
1696 css: &'a str,
1697 start: usize,
1698 end: usize,
1699 first_token_type: TokenSerializationType,
1700 last_token_type: TokenSerializationType,
1701 url_data: &UrlExtraData,
1702 substitution_functions: &'a ComputedSubstitutionFunctions,
1703 stylist: &Stylist,
1704 computed_context: &computed::Context,
1705 references: &'a [SubstitutionFunctionReference],
1706 attribute_tracker: &mut AttributeTracker,
1707 seen: &mut SmallVec<[&'a Name; 8]>,
1708 mut attr_taint: Option<&mut AttrTaint>,
1709) -> Result<Substitution<'a>, ()> {
1710 if start == end {
1711 return Ok(Substitution::default());
1713 }
1714 if references.is_empty() {
1716 let result = &css[start..end];
1717 return Ok(Substitution::new(
1718 Cow::Borrowed(result),
1719 first_token_type,
1720 last_token_type,
1721 Default::default(),
1722 ));
1723 }
1724
1725 let mut substituted = ComputedValue::empty(url_data);
1726 let mut next_token_type = first_token_type;
1727 let mut cur_pos = start;
1728 let mut attr_tainted = false;
1729 let references = references.iter();
1730 for reference in references {
1731 if reference.start != cur_pos {
1732 substituted.push(
1733 &css[cur_pos..reference.start],
1734 next_token_type,
1735 reference.prev_token_type,
1736 None,
1737 )?;
1738 }
1739
1740 let substitution = substitute_one_reference(
1741 css,
1742 url_data,
1743 substitution_functions,
1744 reference,
1745 stylist,
1746 computed_context,
1747 attribute_tracker,
1748 seen,
1749 )?;
1750
1751 if reference.start == start && reference.end == end {
1753 if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
1754 taint.push(start, substitution.css.len());
1755 }
1756 return Ok(substitution);
1757 }
1758
1759 substituted.push(
1760 &substitution.css,
1761 substitution.first_token_type,
1762 substitution.last_token_type,
1763 attr_taint
1764 .as_deref_mut()
1765 .filter(|_| substitution.attr_tainted),
1766 )?;
1767 attr_tainted |= substitution.attr_tainted;
1768 next_token_type = reference.next_token_type;
1769 cur_pos = reference.end;
1770 }
1771 if cur_pos != end {
1773 substituted.push(
1774 &css[cur_pos..end],
1775 next_token_type,
1776 last_token_type,
1777 None,
1778 )?;
1779 }
1780 Ok(Substitution::from_value(substituted, attr_tainted))
1781}
1782
1783fn quoted_css_string(src: &str) -> String {
1784 let mut dest = String::with_capacity(src.len() + 2);
1785 cssparser::serialize_string(src, &mut dest).unwrap();
1786 dest
1787}
1788
1789fn substitute_one_reference<'a>(
1790 css: &'a str,
1791 url_data: &UrlExtraData,
1792 substitution_functions: &'a ComputedSubstitutionFunctions,
1793 reference: &'a SubstitutionFunctionReference,
1794 stylist: &Stylist,
1795 computed_context: &computed::Context,
1796 attribute_tracker: &mut AttributeTracker,
1797 seen: &mut SmallVec<[&'a Name; 8]>,
1798) -> Result<Substitution<'a>, ()> {
1799 let simple_attr_subst = |s: &str| {
1800 Some(Substitution::new(
1801 Cow::Owned(quoted_css_string(s)),
1802 TokenSerializationType::Nothing,
1803 TokenSerializationType::Nothing,
1804 true,
1805 ))
1806 };
1807 let substitution: Option<_> = match reference.substitution_kind {
1808 SubstitutionFunctionKind::Var => {
1809 let registration = stylist.get_custom_property_registration(&reference.name);
1810 match substitution_functions.get_var(registration, &reference.name) {
1811 None => None,
1812 Some(v) => match v.as_universal() {
1818 Some(u) if u.has_references() => {
1819 if seen.contains(&&reference.name) {
1820 None
1823 } else {
1824 seen.push(&reference.name);
1825 let result = substitute_internal(
1826 u,
1827 substitution_functions,
1828 stylist,
1829 computed_context,
1830 attribute_tracker,
1831 seen,
1832 None,
1833 );
1834 seen.pop();
1835 match result {
1836 Ok(mut substitution) => {
1837 substitution.attr_tainted |= v.attr_tainted;
1838 Some(substitution)
1839 },
1840 Err(()) => None,
1842 }
1843 }
1844 },
1845 _ => Some(Substitution::from_value(
1846 v.to_variable_value(),
1847 v.attr_tainted,
1848 )),
1849 },
1850 }
1851 },
1852 SubstitutionFunctionKind::Env => {
1853 let device = stylist.device();
1854 device
1855 .environment()
1856 .get(&reference.name, device, url_data)
1857 .map(|v| Substitution::from_value(v, false))
1858 },
1859 SubstitutionFunctionKind::Attr => {
1861 let namespace = match reference.attribute_data.namespace {
1862 ParsedNamespace::Known(ref ns) => Some(ns),
1863 ParsedNamespace::Unknown => None,
1864 };
1865 namespace
1866 .and_then(|namespace| {
1867 LocalName::with_name(&reference.name, |local_name| {
1868 attribute_tracker.query(local_name, namespace)
1869 })
1870 })
1871 .map_or_else(
1872 || {
1873 if reference.fallback.is_none()
1876 && reference.attribute_data.kind == AttributeType::None
1877 {
1878 simple_attr_subst("")
1879 } else {
1880 None
1881 }
1882 },
1883 |attr| {
1884 let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
1885 if computed_context.in_container_query {
1892 attr
1893 } else {
1894 substitution_functions
1895 .get_attr(&reference.name)
1896 .map(|v| v.to_variable_value())?
1897 .css
1898 }
1899 } else {
1900 attr
1901 };
1902 let mut parser = Parser::new(&attr);
1903 match &reference.attribute_data.kind {
1904 AttributeType::Unit(unit) => {
1905 let css = {
1906 parser.expect_number().ok()?;
1908 let mut s = attr.clone();
1909 s.push_str(unit.as_ref());
1910 s
1911 };
1912 let serialization = match unit {
1913 AttrUnit::Number => TokenSerializationType::Number,
1914 AttrUnit::Percentage => TokenSerializationType::Percentage,
1915 _ => TokenSerializationType::Dimension,
1916 };
1917 let value =
1918 ComputedValue::new(css, url_data, serialization, serialization);
1919 Some(Substitution::from_value(
1920 value, true,
1921 ))
1922 },
1923 AttributeType::Type(syntax) => {
1924 let value = SpecifiedRegisteredValue::parse(
1925 &mut parser,
1926 syntax,
1927 url_data,
1928 None,
1929 AllowComputationallyDependent::Yes,
1930 AttrTaint::default(),
1931 )
1932 .ok()?;
1933 let value = value.to_variable_value();
1934 Some(Substitution::from_value(
1935 value, true,
1936 ))
1937 },
1938 AttributeType::RawString | AttributeType::None => {
1939 simple_attr_subst(&attr)
1940 },
1941 AttributeType::Invalid => None,
1942 }
1943 },
1944 )
1945 },
1946 };
1947
1948 if let Some(s) = substitution {
1949 return Ok(s);
1950 }
1951
1952 let Some(ref fallback) = reference.fallback else {
1953 return Err(());
1954 };
1955
1956 do_substitute_chunk(
1957 css,
1958 fallback.start.get(),
1959 reference.end - 1, fallback.first_token_type,
1961 fallback.last_token_type,
1962 url_data,
1963 substitution_functions,
1964 stylist,
1965 computed_context,
1966 &fallback.references.refs,
1967 attribute_tracker,
1968 seen,
1969 None,
1970 )
1971}
1972
1973fn substitute_internal<'a>(
1975 variable_value: &'a VariableValue,
1976 substitution_functions: &'a ComputedSubstitutionFunctions,
1977 stylist: &Stylist,
1978 computed_context: &computed::Context,
1979 attribute_tracker: &mut AttributeTracker,
1980 seen: &mut SmallVec<[&'a Name; 8]>,
1981 attr_taint: Option<&mut AttrTaint>,
1982) -> Result<Substitution<'a>, ()> {
1983 do_substitute_chunk(
1984 &variable_value.css,
1985 0,
1986 variable_value.css.len(),
1987 variable_value.first_token_type,
1988 variable_value.last_token_type,
1989 &variable_value.url_data,
1990 substitution_functions,
1991 stylist,
1992 computed_context,
1993 &variable_value.references.refs,
1994 attribute_tracker,
1995 seen,
1996 attr_taint,
1997 )
1998}
1999
2000pub fn substitute<'a>(
2002 variable_value: &'a VariableValue,
2003 substitution_functions: &'a ComputedSubstitutionFunctions,
2004 stylist: &Stylist,
2005 computed_context: &computed::Context,
2006 attribute_tracker: &mut AttributeTracker,
2007) -> Result<SubstitutionResult<'a>, ()> {
2008 debug_assert!(variable_value.has_references());
2009 let mut attr_taint = AttrTaint::default();
2010 let v = substitute_internal(
2011 variable_value,
2012 substitution_functions,
2013 stylist,
2014 computed_context,
2015 attribute_tracker,
2016 &mut SmallVec::new(),
2017 Some(&mut attr_taint),
2018 )?;
2019 Ok(SubstitutionResult {
2020 css: v.css,
2021 attr_taint,
2022 })
2023}