custom_properties.rs (91156B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Support for [custom properties for cascading variables][custom]. 6 //! 7 //! [custom]: https://drafts.csswg.org/css-variables/ 8 9 use crate::applicable_declarations::CascadePriority; 10 use crate::custom_properties_map::CustomPropertiesMap; 11 use crate::derives::*; 12 use crate::dom::AttributeProvider; 13 use crate::media_queries::Device; 14 use crate::properties::{ 15 CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet, 16 PropertyDeclaration, 17 }; 18 use crate::properties_and_values::{ 19 registry::PropertyRegistrationData, 20 syntax::{data_type::DependentDataTypes, Descriptor}, 21 value::{ 22 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue, 23 SpecifiedValue as SpecifiedRegisteredValue, 24 }, 25 }; 26 use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet}; 27 use crate::stylesheets::UrlExtraData; 28 use crate::stylist::Stylist; 29 use crate::values::computed::{self, ToComputedValue}; 30 use crate::values::generics::calc::SortKey as AttrUnit; 31 use crate::values::specified::FontRelativeLength; 32 use crate::values::AtomIdent; 33 use crate::Atom; 34 use cssparser::{ 35 CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, 36 }; 37 use selectors::parser::SelectorParseErrorKind; 38 use servo_arc::Arc; 39 use smallvec::SmallVec; 40 use std::borrow::Cow; 41 use std::collections::hash_map::Entry; 42 use std::fmt::{self, Write}; 43 use std::ops::{Index, IndexMut}; 44 use std::{cmp, num}; 45 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 46 47 /// The environment from which to get `env` function values. 48 /// 49 /// TODO(emilio): If this becomes a bit more complex we should probably move it 50 /// to the `media_queries` module, or something. 51 #[derive(Debug, MallocSizeOf)] 52 pub struct CssEnvironment; 53 54 type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue; 55 56 struct EnvironmentVariable { 57 name: Atom, 58 evaluator: EnvironmentEvaluator, 59 } 60 61 macro_rules! make_variable { 62 ($name:expr, $evaluator:expr) => {{ 63 EnvironmentVariable { 64 name: $name, 65 evaluator: $evaluator, 66 } 67 }}; 68 } 69 70 fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue { 71 VariableValue::pixels(device.safe_area_insets().top, url_data) 72 } 73 74 fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue { 75 VariableValue::pixels(device.safe_area_insets().bottom, url_data) 76 } 77 78 fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue { 79 VariableValue::pixels(device.safe_area_insets().left, url_data) 80 } 81 82 fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue { 83 VariableValue::pixels(device.safe_area_insets().right, url_data) 84 } 85 86 #[cfg(feature = "gecko")] 87 fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue { 88 use crate::queries::values::PrefersColorScheme; 89 let prefers_color_scheme = unsafe { 90 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme( 91 device.document(), 92 /* use_content = */ true, 93 ) 94 }; 95 VariableValue::ident( 96 match prefers_color_scheme { 97 PrefersColorScheme::Light => "light", 98 PrefersColorScheme::Dark => "dark", 99 }, 100 url_data, 101 ) 102 } 103 104 #[cfg(feature = "servo")] 105 fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue { 106 // TODO: Add an implementation for Servo. 107 VariableValue::ident("light", url_data) 108 } 109 110 fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue { 111 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data) 112 } 113 114 fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue { 115 VariableValue::pixels( 116 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(), 117 url_data, 118 ) 119 } 120 121 static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ 122 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top), 123 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom), 124 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left), 125 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), 126 ]; 127 128 #[cfg(feature = "gecko")] 129 macro_rules! lnf_int { 130 ($id:ident) => { 131 unsafe { 132 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt( 133 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32, 134 ) 135 } 136 }; 137 } 138 139 #[cfg(feature = "servo")] 140 macro_rules! lnf_int { 141 ($id:ident) => { 142 // TODO: Add an implementation for Servo. 143 0 144 }; 145 } 146 147 macro_rules! lnf_int_variable { 148 ($atom:expr, $id:ident, $ctor:ident) => {{ 149 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue { 150 VariableValue::$ctor(lnf_int!($id), url_data) 151 } 152 make_variable!($atom, __eval) 153 }}; 154 } 155 156 fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue { 157 let int_pixels = lnf_int!(TitlebarRadius); 158 let unzoomed_scale = 159 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get(); 160 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data) 161 } 162 163 static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [ 164 make_variable!( 165 atom!("-moz-gtk-csd-titlebar-radius"), 166 eval_gtk_csd_titlebar_radius 167 ), 168 lnf_int_variable!( 169 atom!("-moz-gtk-csd-tooltip-radius"), 170 TooltipRadius, 171 int_pixels 172 ), 173 lnf_int_variable!( 174 atom!("-moz-gtk-csd-close-button-position"), 175 GTKCSDCloseButtonPosition, 176 integer 177 ), 178 lnf_int_variable!( 179 atom!("-moz-gtk-csd-minimize-button-position"), 180 GTKCSDMinimizeButtonPosition, 181 integer 182 ), 183 lnf_int_variable!( 184 atom!("-moz-gtk-csd-maximize-button-position"), 185 GTKCSDMaximizeButtonPosition, 186 integer 187 ), 188 lnf_int_variable!( 189 atom!("-moz-overlay-scrollbar-fade-duration"), 190 ScrollbarFadeDuration, 191 int_ms 192 ), 193 make_variable!( 194 atom!("-moz-content-preferred-color-scheme"), 195 get_content_preferred_color_scheme 196 ), 197 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size), 198 make_variable!(atom!("hairline"), get_hairline), 199 ]; 200 201 impl CssEnvironment { 202 #[inline] 203 fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> { 204 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) { 205 return Some((var.evaluator)(device, url_data)); 206 } 207 if !url_data.chrome_rules_enabled() { 208 return None; 209 } 210 let var = CHROME_ENVIRONMENT_VARIABLES 211 .iter() 212 .find(|var| var.name == *name)?; 213 Some((var.evaluator)(device, url_data)) 214 } 215 } 216 217 /// A custom property name is just an `Atom`. 218 /// 219 /// Note that this does not include the `--` prefix 220 pub type Name = Atom; 221 222 /// Parse a custom property name. 223 /// 224 /// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name> 225 pub fn parse_name(s: &str) -> Result<&str, ()> { 226 if s.starts_with("--") && s.len() > 2 { 227 Ok(&s[2..]) 228 } else { 229 Err(()) 230 } 231 } 232 233 /// A value for a custom property is just a set of tokens. 234 /// 235 /// We preserve the original CSS for serialization, and also the variable 236 /// references to other custom property names. 237 #[derive(Clone, Debug, MallocSizeOf, ToShmem)] 238 pub struct VariableValue { 239 /// The raw CSS string. 240 pub css: String, 241 242 /// The url data of the stylesheet where this value came from. 243 pub url_data: UrlExtraData, 244 245 first_token_type: TokenSerializationType, 246 last_token_type: TokenSerializationType, 247 248 /// var(), env(), attr() or non-custom property (e.g. through `em`) references. 249 references: References, 250 } 251 252 trivial_to_computed_value!(VariableValue); 253 254 /// Given a potentially registered variable value turn it into a computed custom property value. 255 pub fn compute_variable_value( 256 value: &Arc<VariableValue>, 257 registration: &PropertyRegistrationData, 258 computed_context: &computed::Context, 259 ) -> Option<ComputedRegisteredValue> { 260 if registration.syntax.is_universal() { 261 return Some(ComputedRegisteredValue::universal(Arc::clone(value))); 262 } 263 compute_value(&value.css, &value.url_data, registration, computed_context).ok() 264 } 265 266 // For all purposes, we want values to be considered equal if their css text is equal. 267 impl PartialEq for VariableValue { 268 fn eq(&self, other: &Self) -> bool { 269 self.css == other.css 270 } 271 } 272 273 impl Eq for VariableValue {} 274 275 impl ToCss for SpecifiedValue { 276 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 277 where 278 W: Write, 279 { 280 dest.write_str(&self.css) 281 } 282 } 283 284 /// A pair of separate CustomPropertiesMaps, split between custom properties 285 /// that have the inherit flag set and those with the flag unset. 286 #[repr(C)] 287 #[derive(Clone, Debug, Default, PartialEq)] 288 pub struct ComputedCustomProperties { 289 /// Map for custom properties with inherit flag set, including non-registered 290 /// ones. 291 pub inherited: CustomPropertiesMap, 292 /// Map for custom properties with inherit flag unset. 293 pub non_inherited: CustomPropertiesMap, 294 } 295 296 impl ComputedCustomProperties { 297 /// Return whether the inherited and non_inherited maps are none. 298 pub fn is_empty(&self) -> bool { 299 self.inherited.is_empty() && self.non_inherited.is_empty() 300 } 301 302 /// Return the name and value of the property at specified index, if any. 303 pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> { 304 // Just expose the custom property items from custom_properties.inherited, followed 305 // by custom property items from custom_properties.non_inherited. 306 self.inherited 307 .get_index(index) 308 .or_else(|| self.non_inherited.get_index(index - self.inherited.len())) 309 } 310 311 /// Insert a custom property in the corresponding inherited/non_inherited 312 /// map, depending on whether the inherit flag is set or unset. 313 fn insert( 314 &mut self, 315 registration: &PropertyRegistrationData, 316 name: &Name, 317 value: ComputedRegisteredValue, 318 ) { 319 self.map_mut(registration).insert(name, value) 320 } 321 322 /// Remove a custom property from the corresponding inherited/non_inherited 323 /// map, depending on whether the inherit flag is set or unset. 324 fn remove(&mut self, registration: &PropertyRegistrationData, name: &Name) { 325 self.map_mut(registration).remove(name); 326 } 327 328 /// Shrink the capacity of the inherited maps as much as possible. 329 fn shrink_to_fit(&mut self) { 330 self.inherited.shrink_to_fit(); 331 self.non_inherited.shrink_to_fit(); 332 } 333 334 fn map_mut(&mut self, registration: &PropertyRegistrationData) -> &mut CustomPropertiesMap { 335 if registration.inherits() { 336 &mut self.inherited 337 } else { 338 &mut self.non_inherited 339 } 340 } 341 342 /// Returns the relevant custom property value given a registration. 343 pub fn get( 344 &self, 345 registration: &PropertyRegistrationData, 346 name: &Name, 347 ) -> Option<&ComputedRegisteredValue> { 348 if registration.inherits() { 349 self.inherited.get(name) 350 } else { 351 self.non_inherited.get(name) 352 } 353 } 354 } 355 356 /// Both specified and computed values are VariableValues, the difference is 357 /// whether var() functions are expanded. 358 pub type SpecifiedValue = VariableValue; 359 /// Both specified and computed values are VariableValues, the difference is 360 /// whether var() functions are expanded. 361 pub type ComputedValue = VariableValue; 362 363 /// Set of flags to non-custom references this custom property makes. 364 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)] 365 struct NonCustomReferences(u8); 366 367 bitflags! { 368 impl NonCustomReferences: u8 { 369 /// At least one custom property depends on font-relative units. 370 const FONT_UNITS = 1 << 0; 371 /// At least one custom property depends on root element's font-relative units. 372 const ROOT_FONT_UNITS = 1 << 1; 373 /// At least one custom property depends on line height units. 374 const LH_UNITS = 1 << 2; 375 /// At least one custom property depends on root element's line height units. 376 const ROOT_LH_UNITS = 1 << 3; 377 /// All dependencies not depending on the root element. 378 const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0; 379 /// All dependencies depending on the root element. 380 const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0; 381 } 382 } 383 384 impl NonCustomReferences { 385 fn for_each<F>(&self, mut f: F) 386 where 387 F: FnMut(SingleNonCustomReference), 388 { 389 for (_, r) in self.iter_names() { 390 let single = match r { 391 Self::FONT_UNITS => SingleNonCustomReference::FontUnits, 392 Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits, 393 Self::LH_UNITS => SingleNonCustomReference::LhUnits, 394 Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits, 395 _ => unreachable!("Unexpected single bit value"), 396 }; 397 f(single); 398 } 399 } 400 401 fn from_unit(value: &CowRcStr) -> Self { 402 // For registered properties, any reference to font-relative dimensions 403 // make it dependent on font-related properties. 404 // TODO(dshin): When we unit algebra gets implemented and handled - 405 // Is it valid to say that `calc(1em / 2em * 3px)` triggers this? 406 if value.eq_ignore_ascii_case(FontRelativeLength::LH) { 407 return Self::FONT_UNITS | Self::LH_UNITS; 408 } 409 if value.eq_ignore_ascii_case(FontRelativeLength::EM) 410 || value.eq_ignore_ascii_case(FontRelativeLength::EX) 411 || value.eq_ignore_ascii_case(FontRelativeLength::CAP) 412 || value.eq_ignore_ascii_case(FontRelativeLength::CH) 413 || value.eq_ignore_ascii_case(FontRelativeLength::IC) 414 { 415 return Self::FONT_UNITS; 416 } 417 if value.eq_ignore_ascii_case(FontRelativeLength::RLH) { 418 return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS; 419 } 420 if value.eq_ignore_ascii_case(FontRelativeLength::REM) 421 || value.eq_ignore_ascii_case(FontRelativeLength::REX) 422 || value.eq_ignore_ascii_case(FontRelativeLength::RCH) 423 || value.eq_ignore_ascii_case(FontRelativeLength::RCAP) 424 || value.eq_ignore_ascii_case(FontRelativeLength::RIC) 425 { 426 return Self::ROOT_FONT_UNITS; 427 } 428 Self::empty() 429 } 430 } 431 432 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 433 enum SingleNonCustomReference { 434 FontUnits = 0, 435 RootFontUnits, 436 LhUnits, 437 RootLhUnits, 438 } 439 440 struct NonCustomReferenceMap<T>([Option<T>; 4]); 441 442 impl<T> Default for NonCustomReferenceMap<T> { 443 fn default() -> Self { 444 NonCustomReferenceMap(Default::default()) 445 } 446 } 447 448 impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> { 449 type Output = Option<T>; 450 451 fn index(&self, reference: SingleNonCustomReference) -> &Self::Output { 452 &self.0[reference as usize] 453 } 454 } 455 456 impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> { 457 fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output { 458 &mut self.0[reference as usize] 459 } 460 } 461 462 /// Whether to defer resolving custom properties referencing font relative units. 463 #[derive(Clone, Copy, PartialEq, Eq)] 464 #[allow(missing_docs)] 465 pub enum DeferFontRelativeCustomPropertyResolution { 466 Yes, 467 No, 468 } 469 470 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)] 471 enum SubstitutionFunctionKind { 472 Var, 473 Env, 474 Attr, 475 } 476 477 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)] 478 enum AttributeType { 479 None, 480 RawString, 481 Type(Descriptor), 482 Unit(AttrUnit), 483 } 484 485 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 486 struct VariableFallback { 487 start: num::NonZeroUsize, 488 first_token_type: TokenSerializationType, 489 last_token_type: TokenSerializationType, 490 } 491 492 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 493 struct SubstitutionFunctionReference { 494 name: Name, 495 start: usize, 496 end: usize, 497 fallback: Option<VariableFallback>, 498 attribute_syntax: AttributeType, 499 prev_token_type: TokenSerializationType, 500 next_token_type: TokenSerializationType, 501 substitution_kind: SubstitutionFunctionKind, 502 } 503 504 /// A struct holding information about the external references to that a custom 505 /// property value may have. 506 #[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)] 507 struct References { 508 refs: Vec<SubstitutionFunctionReference>, 509 non_custom_references: NonCustomReferences, 510 any_env: bool, 511 any_var: bool, 512 any_attr: bool, 513 } 514 515 impl References { 516 fn has_references(&self) -> bool { 517 !self.refs.is_empty() 518 } 519 520 fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences { 521 let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES; 522 if is_root_element { 523 mask |= NonCustomReferences::ROOT_DEPENDENCIES 524 } 525 self.non_custom_references & mask 526 } 527 } 528 529 impl VariableValue { 530 fn empty(url_data: &UrlExtraData) -> Self { 531 Self { 532 css: String::new(), 533 last_token_type: Default::default(), 534 first_token_type: Default::default(), 535 url_data: url_data.clone(), 536 references: Default::default(), 537 } 538 } 539 540 /// Create a new custom property without parsing if the CSS is known to be valid and contain no 541 /// references. 542 pub fn new( 543 css: String, 544 url_data: &UrlExtraData, 545 first_token_type: TokenSerializationType, 546 last_token_type: TokenSerializationType, 547 ) -> Self { 548 Self { 549 css, 550 url_data: url_data.clone(), 551 first_token_type, 552 last_token_type, 553 references: Default::default(), 554 } 555 } 556 557 fn push<'i>( 558 &mut self, 559 css: &str, 560 css_first_token_type: TokenSerializationType, 561 css_last_token_type: TokenSerializationType, 562 ) -> Result<(), ()> { 563 /// Prevent values from getting terribly big since you can use custom 564 /// properties exponentially. 565 /// 566 /// This number (2MB) is somewhat arbitrary, but silly enough that no 567 /// reasonable page should hit it. We could limit by number of total 568 /// substitutions, but that was very easy to work around in practice 569 /// (just choose a larger initial value and boom). 570 const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024; 571 572 if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES { 573 return Err(()); 574 } 575 576 // This happens e.g. between two subsequent var() functions: 577 // `var(--a)var(--b)`. 578 // 579 // In that case, css_*_token_type is nonsensical. 580 if css.is_empty() { 581 return Ok(()); 582 } 583 584 self.first_token_type.set_if_nothing(css_first_token_type); 585 // If self.first_token_type was nothing, 586 // self.last_token_type is also nothing and this will be false: 587 if self 588 .last_token_type 589 .needs_separator_when_before(css_first_token_type) 590 { 591 self.css.push_str("/**/") 592 } 593 self.css.push_str(css); 594 self.last_token_type = css_last_token_type; 595 Ok(()) 596 } 597 598 /// Parse a custom property value. 599 pub fn parse<'i, 't>( 600 input: &mut Parser<'i, 't>, 601 url_data: &UrlExtraData, 602 ) -> Result<Self, ParseError<'i>> { 603 let mut references = References::default(); 604 let mut missing_closing_characters = String::new(); 605 let start_position = input.position(); 606 let (first_token_type, last_token_type) = parse_declaration_value( 607 input, 608 start_position, 609 &mut references, 610 &mut missing_closing_characters, 611 )?; 612 let mut css = input 613 .slice_from(start_position) 614 .trim_ascii_start() 615 .to_owned(); 616 if !missing_closing_characters.is_empty() { 617 // Unescaped backslash at EOF in a quoted string is ignored. 618 if css.ends_with("\\") 619 && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') 620 { 621 css.pop(); 622 } 623 css.push_str(&missing_closing_characters); 624 } 625 626 css.truncate(css.trim_ascii_end().len()); 627 css.shrink_to_fit(); 628 references.refs.shrink_to_fit(); 629 630 Ok(Self { 631 css, 632 url_data: url_data.clone(), 633 first_token_type, 634 last_token_type, 635 references, 636 }) 637 } 638 639 /// Create VariableValue from an int. 640 fn integer(number: i32, url_data: &UrlExtraData) -> Self { 641 Self::from_token( 642 Token::Number { 643 has_sign: false, 644 value: number as f32, 645 int_value: Some(number), 646 }, 647 url_data, 648 ) 649 } 650 651 /// Create VariableValue from an int. 652 fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self { 653 Self::from_token(Token::Ident(ident.into()), url_data) 654 } 655 656 /// Create VariableValue from a float amount of CSS pixels. 657 fn pixels(number: f32, url_data: &UrlExtraData) -> Self { 658 // FIXME (https://github.com/servo/rust-cssparser/issues/266): 659 // No way to get TokenSerializationType::Dimension without creating 660 // Token object. 661 Self::from_token( 662 Token::Dimension { 663 has_sign: false, 664 value: number, 665 int_value: None, 666 unit: CowRcStr::from("px"), 667 }, 668 url_data, 669 ) 670 } 671 672 /// Create VariableValue from an integer amount of milliseconds. 673 fn int_ms(number: i32, url_data: &UrlExtraData) -> Self { 674 Self::from_token( 675 Token::Dimension { 676 has_sign: false, 677 value: number as f32, 678 int_value: Some(number), 679 unit: CowRcStr::from("ms"), 680 }, 681 url_data, 682 ) 683 } 684 685 /// Create VariableValue from an integer amount of CSS pixels. 686 fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self { 687 Self::from_token( 688 Token::Dimension { 689 has_sign: false, 690 value: number as f32, 691 int_value: Some(number), 692 unit: CowRcStr::from("px"), 693 }, 694 url_data, 695 ) 696 } 697 698 fn from_token(token: Token, url_data: &UrlExtraData) -> Self { 699 let token_type = token.serialization_type(); 700 let mut css = token.to_css_string(); 701 css.shrink_to_fit(); 702 703 VariableValue { 704 css, 705 url_data: url_data.clone(), 706 first_token_type: token_type, 707 last_token_type: token_type, 708 references: Default::default(), 709 } 710 } 711 712 /// Returns the raw CSS text from this VariableValue 713 pub fn css_text(&self) -> &str { 714 &self.css 715 } 716 717 /// Returns whether this variable value has any reference to the environment or other 718 /// variables. 719 pub fn has_references(&self) -> bool { 720 self.references.has_references() 721 } 722 } 723 724 /// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value> 725 fn parse_declaration_value<'i, 't>( 726 input: &mut Parser<'i, 't>, 727 input_start: SourcePosition, 728 references: &mut References, 729 missing_closing_characters: &mut String, 730 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { 731 input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { 732 parse_declaration_value_block(input, input_start, references, missing_closing_characters) 733 }) 734 } 735 736 /// Like parse_declaration_value, but accept `!` and `;` since they are only invalid at the top level. 737 fn parse_declaration_value_block<'i, 't>( 738 input: &mut Parser<'i, 't>, 739 input_start: SourcePosition, 740 references: &mut References, 741 missing_closing_characters: &mut String, 742 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { 743 let mut is_first = true; 744 let mut first_token_type = TokenSerializationType::Nothing; 745 let mut last_token_type = TokenSerializationType::Nothing; 746 let mut prev_reference_index: Option<usize> = None; 747 loop { 748 let token_start = input.position(); 749 let Ok(token) = input.next_including_whitespace_and_comments() else { 750 break; 751 }; 752 753 let prev_token_type = last_token_type; 754 let serialization_type = token.serialization_type(); 755 last_token_type = serialization_type; 756 if is_first { 757 first_token_type = last_token_type; 758 is_first = false; 759 } 760 761 macro_rules! nested { 762 () => { 763 input.parse_nested_block(|input| { 764 parse_declaration_value_block( 765 input, 766 input_start, 767 references, 768 missing_closing_characters, 769 ) 770 })? 771 }; 772 } 773 macro_rules! check_closed { 774 ($closing:expr) => { 775 if !input.slice_from(token_start).ends_with($closing) { 776 missing_closing_characters.push_str($closing) 777 } 778 }; 779 } 780 if let Some(index) = prev_reference_index.take() { 781 references.refs[index].next_token_type = serialization_type; 782 } 783 match *token { 784 Token::Comment(_) => { 785 let token_slice = input.slice_from(token_start); 786 if !token_slice.ends_with("*/") { 787 missing_closing_characters.push_str(if token_slice.ends_with('*') { 788 "/" 789 } else { 790 "*/" 791 }) 792 } 793 }, 794 Token::BadUrl(ref u) => { 795 let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone()); 796 return Err(input.new_custom_error(e)); 797 }, 798 Token::BadString(ref s) => { 799 let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone()); 800 return Err(input.new_custom_error(e)); 801 }, 802 Token::CloseParenthesis => { 803 let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock; 804 return Err(input.new_custom_error(e)); 805 }, 806 Token::CloseSquareBracket => { 807 let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock; 808 return Err(input.new_custom_error(e)); 809 }, 810 Token::CloseCurlyBracket => { 811 let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock; 812 return Err(input.new_custom_error(e)); 813 }, 814 Token::Function(ref name) => { 815 let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() { 816 Some(SubstitutionFunctionKind::Attr) => { 817 if static_prefs::pref!("layout.css.attr.enabled") { 818 Some(SubstitutionFunctionKind::Attr) 819 } else { 820 None 821 } 822 }, 823 kind => kind, 824 }; 825 if let Some(substitution_kind) = substitution_kind { 826 let our_ref_index = references.refs.len(); 827 let fallback = input.parse_nested_block(|input| { 828 // TODO(emilio): For env() this should be <custom-ident> per spec, but no other browser does 829 // that, see https://github.com/w3c/csswg-drafts/issues/3262. 830 let name = input.expect_ident()?; 831 let name = 832 Atom::from(if substitution_kind == SubstitutionFunctionKind::Var { 833 match parse_name(name.as_ref()) { 834 Ok(name) => name, 835 Err(()) => { 836 let name = name.clone(); 837 return Err(input.new_custom_error( 838 SelectorParseErrorKind::UnexpectedIdent(name), 839 )); 840 }, 841 } 842 } else { 843 name.as_ref() 844 }); 845 846 let attribute_syntax = 847 if substitution_kind == SubstitutionFunctionKind::Attr { 848 parse_attr_type(input) 849 } else { 850 AttributeType::None 851 }; 852 853 // We want the order of the references to match source order. So we need to reserve our slot 854 // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since 855 // if this fails we discard the whole result anyways. 856 let start = token_start.byte_index() - input_start.byte_index(); 857 references.refs.push(SubstitutionFunctionReference { 858 name, 859 start, 860 // To be fixed up after parsing fallback and auto-closing via our_ref_index. 861 end: start, 862 prev_token_type, 863 // To be fixed up (if needed) on the next loop iteration via prev_reference_index. 864 next_token_type: TokenSerializationType::Nothing, 865 // To be fixed up after parsing fallback. 866 fallback: None, 867 attribute_syntax, 868 substitution_kind: substitution_kind.clone(), 869 }); 870 871 let mut fallback = None; 872 if input.try_parse(|input| input.expect_comma()).is_ok() { 873 input.skip_whitespace(); 874 let fallback_start = num::NonZeroUsize::new( 875 input.position().byte_index() - input_start.byte_index(), 876 ) 877 .unwrap(); 878 // NOTE(emilio): Intentionally using parse_declaration_value rather than 879 // parse_declaration_value_block, since that's what parse_fallback used to do. 880 let (first, last) = parse_declaration_value( 881 input, 882 input_start, 883 references, 884 missing_closing_characters, 885 )?; 886 fallback = Some(VariableFallback { 887 start: fallback_start, 888 first_token_type: first, 889 last_token_type: last, 890 }); 891 } else { 892 let state = input.state(); 893 // We still need to consume the rest of the potentially-unclosed 894 // tokens, but make sure to not consume tokens that would otherwise be 895 // invalid, by calling reset(). 896 parse_declaration_value_block( 897 input, 898 input_start, 899 references, 900 missing_closing_characters, 901 )?; 902 input.reset(&state); 903 } 904 Ok(fallback) 905 })?; 906 check_closed!(")"); 907 prev_reference_index = Some(our_ref_index); 908 let reference = &mut references.refs[our_ref_index]; 909 reference.end = input.position().byte_index() - input_start.byte_index() 910 + missing_closing_characters.len(); 911 reference.fallback = fallback; 912 match substitution_kind { 913 SubstitutionFunctionKind::Var => references.any_var = true, 914 SubstitutionFunctionKind::Env => references.any_env = true, 915 SubstitutionFunctionKind::Attr => references.any_attr = true, 916 }; 917 } else { 918 nested!(); 919 check_closed!(")"); 920 } 921 }, 922 Token::ParenthesisBlock => { 923 nested!(); 924 check_closed!(")"); 925 }, 926 Token::CurlyBracketBlock => { 927 nested!(); 928 check_closed!("}"); 929 }, 930 Token::SquareBracketBlock => { 931 nested!(); 932 check_closed!("]"); 933 }, 934 Token::QuotedString(_) => { 935 let token_slice = input.slice_from(token_start); 936 let quote = &token_slice[..1]; 937 debug_assert!(matches!(quote, "\"" | "'")); 938 if !(token_slice.ends_with(quote) && token_slice.len() > 1) { 939 missing_closing_characters.push_str(quote) 940 } 941 }, 942 Token::Ident(ref value) 943 | Token::AtKeyword(ref value) 944 | Token::Hash(ref value) 945 | Token::IDHash(ref value) 946 | Token::UnquotedUrl(ref value) 947 | Token::Dimension { 948 unit: ref value, .. 949 } => { 950 references 951 .non_custom_references 952 .insert(NonCustomReferences::from_unit(value)); 953 let is_unquoted_url = matches!(token, Token::UnquotedUrl(_)); 954 if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") { 955 // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD 956 // Check the value in case the final backslash was itself escaped. 957 // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD. 958 // (Unescaped U+FFFD would also work, but removing the backslash is annoying.) 959 missing_closing_characters.push_str("�") 960 } 961 if is_unquoted_url { 962 check_closed!(")"); 963 } 964 }, 965 _ => {}, 966 }; 967 } 968 Ok((first_token_type, last_token_type)) 969 } 970 971 /// Parse <attr-type> = type( <syntax> ) | raw-string | number | <attr-unit>. 972 /// https://drafts.csswg.org/css-values-5/#attr-notation 973 fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType { 974 input 975 .try_parse(|input| { 976 Ok(match input.next()? { 977 Token::Function(ref name) if name.eq_ignore_ascii_case("type") => { 978 AttributeType::Type(input.parse_nested_block(Descriptor::from_css_parser)?) 979 }, 980 Token::Ident(ref ident) => { 981 if ident.eq_ignore_ascii_case("raw-string") { 982 AttributeType::RawString 983 } else { 984 let unit = AttrUnit::from_ident(ident).map_err(|_| { 985 input.new_custom_error(StyleParseErrorKind::UnspecifiedError) 986 })?; 987 AttributeType::Unit(unit) 988 } 989 }, 990 Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage), 991 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), 992 }) 993 }) 994 .unwrap_or(AttributeType::None) 995 } 996 997 /// A struct that takes care of encapsulating the cascade process for custom properties. 998 pub struct CustomPropertiesBuilder<'a, 'b: 'a> { 999 seen: PrecomputedHashSet<&'a Name>, 1000 may_have_cycles: bool, 1001 has_color_scheme: bool, 1002 custom_properties: ComputedCustomProperties, 1003 reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, 1004 stylist: &'a Stylist, 1005 computed_context: &'a mut computed::Context<'b>, 1006 references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>, 1007 } 1008 1009 fn find_non_custom_references( 1010 registration: &PropertyRegistrationData, 1011 value: &VariableValue, 1012 may_have_color_scheme: bool, 1013 is_root_element: bool, 1014 include_universal: bool, 1015 ) -> Option<NonCustomReferences> { 1016 let dependent_types = registration.syntax.dependent_types(); 1017 let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH) 1018 || (include_universal && registration.syntax.is_universal()); 1019 if may_reference_length { 1020 let value_dependencies = value.references.non_custom_references(is_root_element); 1021 if !value_dependencies.is_empty() { 1022 return Some(value_dependencies); 1023 } 1024 } 1025 if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme { 1026 // NOTE(emilio): We might want to add a NonCustomReferences::COLOR_SCHEME or something but 1027 // it's not really needed for correctness, so for now we use an Option for that to signal 1028 // that there might be a dependencies. 1029 return Some(NonCustomReferences::empty()); 1030 } 1031 None 1032 } 1033 1034 impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> { 1035 /// Create a new builder, inheriting from a given custom properties map. 1036 /// 1037 /// We expose this publicly mostly for @keyframe blocks. 1038 pub fn new_with_properties( 1039 stylist: &'a Stylist, 1040 custom_properties: ComputedCustomProperties, 1041 computed_context: &'a mut computed::Context<'b>, 1042 ) -> Self { 1043 Self { 1044 seen: PrecomputedHashSet::default(), 1045 reverted: Default::default(), 1046 may_have_cycles: false, 1047 has_color_scheme: false, 1048 custom_properties, 1049 stylist, 1050 computed_context, 1051 references_from_non_custom_properties: NonCustomReferenceMap::default(), 1052 } 1053 } 1054 1055 /// Create a new builder, inheriting from the right style given context. 1056 pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self { 1057 let is_root_element = context.is_root_element(); 1058 1059 let inherited = context.inherited_custom_properties(); 1060 let initial_values = stylist.get_custom_property_initial_values(); 1061 let properties = ComputedCustomProperties { 1062 inherited: if is_root_element { 1063 debug_assert!(inherited.is_empty()); 1064 initial_values.inherited.clone() 1065 } else { 1066 inherited.inherited.clone() 1067 }, 1068 non_inherited: initial_values.non_inherited.clone(), 1069 }; 1070 1071 // Reuse flags from computing registered custom properties initial values, such as 1072 // whether they depend on viewport units. 1073 context 1074 .style() 1075 .add_flags(stylist.get_custom_property_initial_values_flags()); 1076 Self::new_with_properties(stylist, properties, context) 1077 } 1078 1079 /// Cascade a given custom property declaration. 1080 pub fn cascade( 1081 &mut self, 1082 declaration: &'a CustomDeclaration, 1083 priority: CascadePriority, 1084 attr_provider: &dyn AttributeProvider, 1085 ) { 1086 let CustomDeclaration { 1087 ref name, 1088 ref value, 1089 } = *declaration; 1090 1091 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { 1092 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { 1093 return; 1094 } 1095 } 1096 1097 let was_already_present = !self.seen.insert(name); 1098 if was_already_present { 1099 return; 1100 } 1101 1102 if !self.value_may_affect_style(name, value) { 1103 return; 1104 } 1105 1106 let map = &mut self.custom_properties; 1107 let registration = self.stylist.get_custom_property_registration(&name); 1108 match value { 1109 CustomDeclarationValue::Unparsed(unparsed_value) => { 1110 // At this point of the cascade we're not guaranteed to have seen the color-scheme 1111 // declaration, so need to assume the worst. We could track all system color 1112 // keyword tokens + the light-dark() function, but that seems non-trivial / 1113 // probably overkill. 1114 let may_have_color_scheme = true; 1115 // Non-custom dependency is really relevant for registered custom properties 1116 // that require computed value of such dependencies. 1117 let has_dependency = unparsed_value.references.any_var 1118 || unparsed_value.references.any_attr 1119 || find_non_custom_references( 1120 registration, 1121 unparsed_value, 1122 may_have_color_scheme, 1123 self.computed_context.is_root_element(), 1124 /* include_unregistered = */ false, 1125 ) 1126 .is_some(); 1127 // If the variable value has no references to other properties, perform 1128 // substitution here instead of forcing a full traversal in `substitute_all` 1129 // afterwards. 1130 if !has_dependency { 1131 return substitute_references_if_needed_and_apply( 1132 name, 1133 unparsed_value, 1134 map, 1135 self.stylist, 1136 self.computed_context, 1137 attr_provider, 1138 ); 1139 } 1140 self.may_have_cycles = true; 1141 let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value)); 1142 map.insert(registration, name, value); 1143 }, 1144 CustomDeclarationValue::Parsed(parsed_value) => { 1145 let value = parsed_value.to_computed_value(&self.computed_context); 1146 map.insert(registration, name, value); 1147 }, 1148 CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { 1149 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { 1150 let origin_revert = matches!(keyword, CSSWideKeyword::Revert); 1151 self.seen.remove(name); 1152 self.reverted.insert(name, (priority, origin_revert)); 1153 }, 1154 CSSWideKeyword::Initial => { 1155 // For non-inherited custom properties, 'initial' was handled in value_may_affect_style. 1156 debug_assert!(registration.inherits(), "Should've been handled earlier"); 1157 remove_and_insert_initial_value(name, registration, map); 1158 }, 1159 CSSWideKeyword::Inherit => { 1160 // For inherited custom properties, 'inherit' was handled in value_may_affect_style. 1161 debug_assert!(!registration.inherits(), "Should've been handled earlier"); 1162 if let Some(inherited_value) = self 1163 .computed_context 1164 .inherited_custom_properties() 1165 .non_inherited 1166 .get(name) 1167 { 1168 map.insert(registration, name, inherited_value.clone()); 1169 } 1170 }, 1171 // handled in value_may_affect_style 1172 CSSWideKeyword::Unset => unreachable!(), 1173 }, 1174 } 1175 } 1176 1177 /// Fast check to avoid calling maybe_note_non_custom_dependency in ~all cases. 1178 #[inline] 1179 pub fn might_have_non_custom_dependency(id: LonghandId, decl: &PropertyDeclaration) -> bool { 1180 if id == LonghandId::ColorScheme { 1181 return true; 1182 } 1183 if matches!(id, LonghandId::LineHeight | LonghandId::FontSize) { 1184 return matches!(decl, PropertyDeclaration::WithVariables(..)); 1185 } 1186 false 1187 } 1188 1189 /// Note a non-custom property with variable reference that may in turn depend on that property. 1190 /// e.g. `font-size` depending on a custom property that may be a registered property using `em`. 1191 pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) { 1192 debug_assert!(Self::might_have_non_custom_dependency(id, decl)); 1193 if id == LonghandId::ColorScheme { 1194 // If we might change the color-scheme, we need to defer computation of colors. 1195 self.has_color_scheme = true; 1196 return; 1197 } 1198 1199 let refs = match decl { 1200 PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references, 1201 _ => return, 1202 }; 1203 1204 if !refs.any_var && !refs.any_attr { 1205 return; 1206 } 1207 1208 // With unit algebra in `calc()`, references aren't limited to `font-size`. 1209 // For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`, 1210 // or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);` 1211 let references = match id { 1212 LonghandId::FontSize => { 1213 if self.computed_context.is_root_element() { 1214 NonCustomReferences::ROOT_FONT_UNITS 1215 } else { 1216 NonCustomReferences::FONT_UNITS 1217 } 1218 }, 1219 LonghandId::LineHeight => { 1220 if self.computed_context.is_root_element() { 1221 NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS 1222 } else { 1223 NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS 1224 } 1225 }, 1226 _ => return, 1227 }; 1228 1229 let variables: Vec<Atom> = refs 1230 .refs 1231 .iter() 1232 .filter_map(|reference| { 1233 if reference.substitution_kind != SubstitutionFunctionKind::Var { 1234 return None; 1235 } 1236 let registration = self 1237 .stylist 1238 .get_custom_property_registration(&reference.name); 1239 if !registration 1240 .syntax 1241 .dependent_types() 1242 .intersects(DependentDataTypes::LENGTH) 1243 { 1244 return None; 1245 } 1246 Some(reference.name.clone()) 1247 }) 1248 .collect(); 1249 references.for_each(|idx| { 1250 let entry = &mut self.references_from_non_custom_properties[idx]; 1251 let was_none = entry.is_none(); 1252 let v = entry.get_or_insert_with(|| variables.clone()); 1253 if was_none { 1254 return; 1255 } 1256 v.extend(variables.iter().cloned()); 1257 }); 1258 } 1259 1260 fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool { 1261 let registration = self.stylist.get_custom_property_registration(&name); 1262 match *value { 1263 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => { 1264 // For inherited custom properties, explicit 'inherit' means we 1265 // can just use any existing value in the inherited 1266 // CustomPropertiesMap. 1267 if registration.inherits() { 1268 return false; 1269 } 1270 }, 1271 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => { 1272 // For non-inherited custom properties, explicit 'initial' means 1273 // we can just use any initial value in the registration. 1274 if !registration.inherits() { 1275 return false; 1276 } 1277 }, 1278 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => { 1279 // Explicit 'unset' means we can either just use any existing 1280 // value in the inherited CustomPropertiesMap or the initial 1281 // value in the registration. 1282 return false; 1283 }, 1284 _ => {}, 1285 } 1286 1287 let existing_value = self.custom_properties.get(registration, &name); 1288 let existing_value = match existing_value { 1289 None => { 1290 if matches!( 1291 value, 1292 CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) 1293 ) { 1294 debug_assert!(registration.inherits(), "Should've been handled earlier"); 1295 // The initial value of a custom property without a 1296 // guaranteed-invalid initial value is the same as it 1297 // not existing in the map. 1298 if registration.initial_value.is_none() { 1299 return false; 1300 } 1301 } 1302 return true; 1303 }, 1304 Some(v) => v, 1305 }; 1306 let computed_value = match value { 1307 CustomDeclarationValue::Unparsed(value) => { 1308 // Don't bother overwriting an existing value with the same 1309 // specified value. 1310 if let Some(existing_value) = existing_value.as_universal() { 1311 return existing_value != value; 1312 } 1313 if !registration.syntax.is_universal() { 1314 compute_value( 1315 &value.css, 1316 &value.url_data, 1317 registration, 1318 self.computed_context, 1319 ) 1320 .ok() 1321 } else { 1322 None 1323 } 1324 }, 1325 CustomDeclarationValue::Parsed(value) => { 1326 Some(value.to_computed_value(&self.computed_context)) 1327 }, 1328 CustomDeclarationValue::CSSWideKeyword(kw) => { 1329 match kw { 1330 CSSWideKeyword::Inherit => { 1331 debug_assert!(!registration.inherits(), "Should've been handled earlier"); 1332 // existing_value is the registered initial value. 1333 // Don't bother adding it to self.custom_properties.non_inherited 1334 // if the key is also absent from self.inherited.non_inherited. 1335 if self 1336 .computed_context 1337 .inherited_custom_properties() 1338 .non_inherited 1339 .get(name) 1340 .is_none() 1341 { 1342 return false; 1343 } 1344 }, 1345 CSSWideKeyword::Initial => { 1346 debug_assert!(registration.inherits(), "Should've been handled earlier"); 1347 // Don't bother overwriting an existing value with the initial value specified in 1348 // the registration. 1349 if let Some(initial_value) = self 1350 .stylist 1351 .get_custom_property_initial_values() 1352 .get(registration, name) 1353 { 1354 return existing_value != initial_value; 1355 } 1356 }, 1357 CSSWideKeyword::Unset => { 1358 debug_assert!(false, "Should've been handled earlier"); 1359 }, 1360 CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => {}, 1361 } 1362 None 1363 }, 1364 }; 1365 1366 if let Some(value) = computed_value { 1367 return existing_value.v != value.v; 1368 } 1369 1370 true 1371 } 1372 1373 /// Computes the map of applicable custom properties, as well as 1374 /// longhand properties that are now considered invalid-at-compute time. 1375 /// The result is saved into the computed context. 1376 /// 1377 /// If there was any specified property or non-inherited custom property 1378 /// with an initial value, we've created a new map and now we 1379 /// need to remove any potential cycles (And marking non-custom 1380 /// properties), and wrap it in an arc. 1381 /// 1382 /// Some registered custom properties may require font-related properties 1383 /// be resolved to resolve. If these properties are not resolved at this time, 1384 /// `defer` should be set to `Yes`, which will leave such custom properties, 1385 /// and other properties referencing them, untouched. These properties are 1386 /// returned separately, to be resolved by `build_deferred` to fully resolve 1387 /// all custom properties after all necessary non-custom properties are resolved. 1388 pub fn build( 1389 mut self, 1390 defer: DeferFontRelativeCustomPropertyResolution, 1391 attr_provider: &dyn AttributeProvider, 1392 ) -> Option<CustomPropertiesMap> { 1393 let mut deferred_custom_properties = None; 1394 if self.may_have_cycles { 1395 if defer == DeferFontRelativeCustomPropertyResolution::Yes { 1396 deferred_custom_properties = Some(CustomPropertiesMap::default()); 1397 } 1398 let mut invalid_non_custom_properties = LonghandIdSet::default(); 1399 substitute_all( 1400 &mut self.custom_properties, 1401 deferred_custom_properties.as_mut(), 1402 &mut invalid_non_custom_properties, 1403 self.has_color_scheme, 1404 &self.seen, 1405 &self.references_from_non_custom_properties, 1406 self.stylist, 1407 self.computed_context, 1408 attr_provider, 1409 ); 1410 self.computed_context.builder.invalid_non_custom_properties = 1411 invalid_non_custom_properties; 1412 } 1413 1414 self.custom_properties.shrink_to_fit(); 1415 1416 // Some pages apply a lot of redundant custom properties, see e.g. 1417 // bug 1758974 comment 5. Try to detect the case where the values 1418 // haven't really changed, and save some memory by reusing the inherited 1419 // map in that case. 1420 let initial_values = self.stylist.get_custom_property_initial_values(); 1421 self.computed_context.builder.custom_properties = ComputedCustomProperties { 1422 inherited: if self 1423 .computed_context 1424 .inherited_custom_properties() 1425 .inherited 1426 == self.custom_properties.inherited 1427 { 1428 self.computed_context 1429 .inherited_custom_properties() 1430 .inherited 1431 .clone() 1432 } else { 1433 self.custom_properties.inherited 1434 }, 1435 non_inherited: if initial_values.non_inherited == self.custom_properties.non_inherited { 1436 initial_values.non_inherited.clone() 1437 } else { 1438 self.custom_properties.non_inherited 1439 }, 1440 }; 1441 1442 deferred_custom_properties 1443 } 1444 1445 /// Fully resolve all deferred custom properties, assuming that the incoming context 1446 /// has necessary properties resolved. 1447 pub fn build_deferred( 1448 deferred: CustomPropertiesMap, 1449 stylist: &Stylist, 1450 computed_context: &mut computed::Context, 1451 attr_provider: &dyn AttributeProvider, 1452 ) { 1453 if deferred.is_empty() { 1454 return; 1455 } 1456 let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties); 1457 // Since `CustomPropertiesMap` preserves insertion order, we shouldn't have to worry about 1458 // resolving in a wrong order. 1459 for (k, v) in deferred.iter() { 1460 let Some(v) = v else { continue }; 1461 let Some(v) = v.as_universal() else { 1462 unreachable!("Computing should have been deferred!") 1463 }; 1464 substitute_references_if_needed_and_apply( 1465 k, 1466 v, 1467 &mut custom_properties, 1468 stylist, 1469 computed_context, 1470 attr_provider, 1471 ); 1472 } 1473 computed_context.builder.custom_properties = custom_properties; 1474 } 1475 } 1476 1477 /// Resolve all custom properties to either substituted, invalid, or unset 1478 /// (meaning we should use the inherited value). 1479 /// 1480 /// It does cycle dependencies removal at the same time as substitution. 1481 fn substitute_all( 1482 custom_properties_map: &mut ComputedCustomProperties, 1483 mut deferred_properties_map: Option<&mut CustomPropertiesMap>, 1484 invalid_non_custom_properties: &mut LonghandIdSet, 1485 has_color_scheme: bool, 1486 seen: &PrecomputedHashSet<&Name>, 1487 references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>, 1488 stylist: &Stylist, 1489 computed_context: &computed::Context, 1490 attr_provider: &dyn AttributeProvider, 1491 ) { 1492 // The cycle dependencies removal in this function is a variant 1493 // of Tarjan's algorithm. It is mostly based on the pseudo-code 1494 // listed in 1495 // https://en.wikipedia.org/w/index.php? 1496 // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495 1497 1498 #[derive(Clone, Eq, PartialEq, Debug)] 1499 enum VarType { 1500 Custom(Name), 1501 NonCustom(SingleNonCustomReference), 1502 } 1503 1504 /// Struct recording necessary information for each variable. 1505 #[derive(Debug)] 1506 struct VarInfo { 1507 /// The name of the variable. It will be taken to save addref 1508 /// when the corresponding variable is popped from the stack. 1509 /// This also serves as a mark for whether the variable is 1510 /// currently in the stack below. 1511 var: Option<VarType>, 1512 /// If the variable is in a dependency cycle, lowlink represents 1513 /// a smaller index which corresponds to a variable in the same 1514 /// strong connected component, which is known to be accessible 1515 /// from this variable. It is not necessarily the root, though. 1516 lowlink: usize, 1517 } 1518 /// Context struct for traversing the variable graph, so that we can 1519 /// avoid referencing all the fields multiple times. 1520 struct Context<'a, 'b: 'a> { 1521 /// Number of variables visited. This is used as the order index 1522 /// when we visit a new unresolved variable. 1523 count: usize, 1524 /// The map from custom property name to its order index. 1525 index_map: PrecomputedHashMap<Name, usize>, 1526 /// Mapping from a non-custom dependency to its order index. 1527 non_custom_index_map: NonCustomReferenceMap<usize>, 1528 /// Information of each variable indexed by the order index. 1529 var_info: SmallVec<[VarInfo; 5]>, 1530 /// The stack of order index of visited variables. It contains 1531 /// all unfinished strong connected components. 1532 stack: SmallVec<[usize; 5]>, 1533 /// References to non-custom properties in this strongly connected component. 1534 non_custom_references: NonCustomReferences, 1535 /// Whether the builder has seen a non-custom color-scheme reference. 1536 has_color_scheme: bool, 1537 /// Whether this strongly connected component contains any custom properties involving 1538 /// value computation. 1539 contains_computed_custom_property: bool, 1540 map: &'a mut ComputedCustomProperties, 1541 /// The stylist is used to get registered properties, and to resolve the environment to 1542 /// substitute `env()` variables. 1543 stylist: &'a Stylist, 1544 /// The computed context is used to get inherited custom 1545 /// properties and compute registered custom properties. 1546 computed_context: &'a computed::Context<'b>, 1547 /// Longhand IDs that became invalid due to dependency cycle(s). 1548 invalid_non_custom_properties: &'a mut LonghandIdSet, 1549 /// Properties that cannot yet be substituted. Note we store both inherited and 1550 /// non-inherited properties in the same map, since we need to make sure we iterate through 1551 /// them in the right order. 1552 deferred_properties: Option<&'a mut CustomPropertiesMap>, 1553 } 1554 1555 /// This function combines the traversal for cycle removal and value 1556 /// substitution. It returns either a signal None if this variable 1557 /// has been fully resolved (to either having no reference or being 1558 /// marked invalid), or the order index for the given name. 1559 /// 1560 /// When it returns, the variable corresponds to the name would be 1561 /// in one of the following states: 1562 /// * It is still in context.stack, which means it is part of an 1563 /// potentially incomplete dependency circle. 1564 /// * It has been removed from the map. It can be either that the 1565 /// substitution failed, or it is inside a dependency circle. 1566 /// When this function removes a variable from the map because 1567 /// of dependency circle, it would put all variables in the same 1568 /// strong connected component to the set together. 1569 /// * It doesn't have any reference, because either this variable 1570 /// doesn't have reference at all in specified value, or it has 1571 /// been completely resolved. 1572 /// * There is no such variable at all. 1573 fn traverse<'a, 'b>( 1574 var: VarType, 1575 non_custom_references: &NonCustomReferenceMap<Vec<Name>>, 1576 context: &mut Context<'a, 'b>, 1577 attr_provider: &dyn AttributeProvider, 1578 ) -> Option<usize> { 1579 // Some shortcut checks. 1580 let value = match var { 1581 VarType::Custom(ref name) => { 1582 let registration = context.stylist.get_custom_property_registration(name); 1583 let value = context.map.get(registration, name)?.as_universal()?; 1584 let is_root = context.computed_context.is_root_element(); 1585 // We need to keep track of potential non-custom-references even on unregistered 1586 // properties for cycle-detection purposes. 1587 let non_custom_refs = find_non_custom_references( 1588 registration, 1589 value, 1590 context.has_color_scheme, 1591 is_root, 1592 /* include_unregistered = */ true, 1593 ); 1594 context.non_custom_references |= non_custom_refs.unwrap_or_default(); 1595 let has_dependency = value.references.any_var 1596 || value.references.any_attr 1597 || non_custom_refs.is_some(); 1598 // Nothing to resolve. 1599 if !has_dependency { 1600 debug_assert!(!value.references.any_env, "Should've been handled earlier"); 1601 if !registration.syntax.is_universal() { 1602 // We might still need to compute the value if this is not an universal 1603 // registration if we thought this had a dependency before but turned out 1604 // not to be (due to has_color_scheme, for example). Note that if this was 1605 // already computed we would've bailed out in the as_universal() check. 1606 debug_assert!( 1607 registration 1608 .syntax 1609 .dependent_types() 1610 .intersects(DependentDataTypes::COLOR), 1611 "How did an unresolved value get here otherwise?", 1612 ); 1613 let value = value.clone(); 1614 substitute_references_if_needed_and_apply( 1615 name, 1616 &value, 1617 &mut context.map, 1618 context.stylist, 1619 context.computed_context, 1620 attr_provider, 1621 ); 1622 } 1623 return None; 1624 } 1625 1626 // Has this variable been visited? 1627 match context.index_map.entry(name.clone()) { 1628 Entry::Occupied(entry) => { 1629 return Some(*entry.get()); 1630 }, 1631 Entry::Vacant(entry) => { 1632 entry.insert(context.count); 1633 }, 1634 } 1635 context.contains_computed_custom_property |= !registration.syntax.is_universal(); 1636 1637 // Hold a strong reference to the value so that we don't 1638 // need to keep reference to context.map. 1639 Some(value.clone()) 1640 }, 1641 VarType::NonCustom(ref non_custom) => { 1642 let entry = &mut context.non_custom_index_map[*non_custom]; 1643 if let Some(v) = entry { 1644 return Some(*v); 1645 } 1646 *entry = Some(context.count); 1647 None 1648 }, 1649 }; 1650 1651 // Add new entry to the information table. 1652 let index = context.count; 1653 context.count += 1; 1654 debug_assert_eq!(index, context.var_info.len()); 1655 context.var_info.push(VarInfo { 1656 var: Some(var.clone()), 1657 lowlink: index, 1658 }); 1659 context.stack.push(index); 1660 1661 let mut self_ref = false; 1662 let mut lowlink = index; 1663 let visit_link = 1664 |var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| { 1665 let next_index = match traverse(var, non_custom_references, context, attr_provider) 1666 { 1667 Some(index) => index, 1668 // There is nothing to do if the next variable has been 1669 // fully resolved at this point. 1670 None => { 1671 return; 1672 }, 1673 }; 1674 let next_info = &context.var_info[next_index]; 1675 if next_index > index { 1676 // The next variable has a larger index than us, so it 1677 // must be inserted in the recursive call above. We want 1678 // to get its lowlink. 1679 *lowlink = cmp::min(*lowlink, next_info.lowlink); 1680 } else if next_index == index { 1681 *self_ref = true; 1682 } else if next_info.var.is_some() { 1683 // The next variable has a smaller order index and it is 1684 // in the stack, so we are at the same component. 1685 *lowlink = cmp::min(*lowlink, next_index); 1686 } 1687 }; 1688 if let Some(ref v) = value.as_ref() { 1689 debug_assert!( 1690 matches!(var, VarType::Custom(_)), 1691 "Non-custom property has references?" 1692 ); 1693 1694 // Visit other custom properties... 1695 // FIXME: Maybe avoid visiting the same var twice if not needed? 1696 for next in &v.references.refs { 1697 if next.substitution_kind != SubstitutionFunctionKind::Var { 1698 continue; 1699 } 1700 visit_link( 1701 VarType::Custom(next.name.clone()), 1702 context, 1703 &mut lowlink, 1704 &mut self_ref, 1705 ); 1706 } 1707 1708 // ... Then non-custom properties. 1709 v.references.non_custom_references.for_each(|r| { 1710 visit_link(VarType::NonCustom(r), context, &mut lowlink, &mut self_ref); 1711 }); 1712 } else if let VarType::NonCustom(non_custom) = var { 1713 let entry = &non_custom_references[non_custom]; 1714 if let Some(deps) = entry.as_ref() { 1715 for d in deps { 1716 // Visit any reference from this non-custom property to custom properties. 1717 visit_link( 1718 VarType::Custom(d.clone()), 1719 context, 1720 &mut lowlink, 1721 &mut self_ref, 1722 ); 1723 } 1724 } 1725 } 1726 1727 context.var_info[index].lowlink = lowlink; 1728 if lowlink != index { 1729 // This variable is in a loop, but it is not the root of 1730 // this strong connected component. We simply return for 1731 // now, and the root would remove it from the map. 1732 // 1733 // This cannot be removed from the map here, because 1734 // otherwise the shortcut check at the beginning of this 1735 // function would return the wrong value. 1736 return Some(index); 1737 } 1738 1739 // This is the root of a strong-connected component. 1740 let mut in_loop = self_ref; 1741 let name; 1742 1743 let handle_variable_in_loop = |name: &Name, context: &mut Context<'a, 'b>| { 1744 if context.contains_computed_custom_property { 1745 // These non-custom properties can't become invalid-at-compute-time from 1746 // cyclic dependencies purely consisting of non-registered properties. 1747 if context.non_custom_references.intersects( 1748 NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS, 1749 ) { 1750 context 1751 .invalid_non_custom_properties 1752 .insert(LonghandId::FontSize); 1753 } 1754 if context 1755 .non_custom_references 1756 .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS) 1757 { 1758 context 1759 .invalid_non_custom_properties 1760 .insert(LonghandId::LineHeight); 1761 } 1762 } 1763 // This variable is in loop. Resolve to invalid. 1764 handle_invalid_at_computed_value_time(name, context.map, context.computed_context); 1765 }; 1766 loop { 1767 let var_index = context 1768 .stack 1769 .pop() 1770 .expect("The current variable should still be in stack"); 1771 let var_info = &mut context.var_info[var_index]; 1772 // We should never visit the variable again, so it's safe 1773 // to take the name away, so that we don't do additional 1774 // reference count. 1775 let var_name = var_info 1776 .var 1777 .take() 1778 .expect("Variable should not be poped from stack twice"); 1779 if var_index == index { 1780 name = match var_name { 1781 VarType::Custom(name) => name, 1782 // At the root of this component, and it's a non-custom 1783 // reference - we have nothing to substitute, so 1784 // it's effectively resolved. 1785 VarType::NonCustom(..) => return None, 1786 }; 1787 break; 1788 } 1789 if let VarType::Custom(name) = var_name { 1790 // Anything here is in a loop which can traverse to the 1791 // variable we are handling, so it's invalid at 1792 // computed-value time. 1793 handle_variable_in_loop(&name, context); 1794 } 1795 in_loop = true; 1796 } 1797 // We've gotten to the root of this strongly connected component, so clear 1798 // whether or not it involved non-custom references. 1799 // It's fine to track it like this, because non-custom properties currently 1800 // being tracked can only participate in any loop only once. 1801 if in_loop { 1802 handle_variable_in_loop(&name, context); 1803 context.non_custom_references = NonCustomReferences::default(); 1804 return None; 1805 } 1806 1807 if let Some(ref v) = value { 1808 let registration = context.stylist.get_custom_property_registration(&name); 1809 1810 let mut defer = false; 1811 if let Some(ref mut deferred) = context.deferred_properties { 1812 // We need to defer this property if it has a non-custom property dependency, or 1813 // any variable that it references is already deferred. 1814 defer = find_non_custom_references( 1815 registration, 1816 v, 1817 context.has_color_scheme, 1818 context.computed_context.is_root_element(), 1819 /* include_unregistered = */ false, 1820 ) 1821 .is_some() 1822 || v.references.refs.iter().any(|reference| { 1823 (reference.substitution_kind == SubstitutionFunctionKind::Var 1824 && deferred.get(&reference.name).is_some()) 1825 || reference.substitution_kind == SubstitutionFunctionKind::Attr 1826 }); 1827 1828 if defer { 1829 let value = ComputedRegisteredValue::universal(Arc::clone(v)); 1830 deferred.insert(&name, value); 1831 context.map.remove(registration, &name); 1832 } 1833 } 1834 1835 // If there are no var references we should already be computed and substituted by now. 1836 if !defer && (v.references.any_var || v.references.any_attr) { 1837 substitute_references_if_needed_and_apply( 1838 &name, 1839 v, 1840 &mut context.map, 1841 context.stylist, 1842 context.computed_context, 1843 attr_provider, 1844 ); 1845 } 1846 } 1847 context.non_custom_references = NonCustomReferences::default(); 1848 1849 // All resolved, so return the signal value. 1850 None 1851 } 1852 1853 // Note that `seen` doesn't contain names inherited from our parent, but 1854 // those can't have variable references (since we inherit the computed 1855 // variables) so we don't want to spend cycles traversing them anyway. 1856 for name in seen { 1857 let mut context = Context { 1858 count: 0, 1859 index_map: PrecomputedHashMap::default(), 1860 non_custom_index_map: NonCustomReferenceMap::default(), 1861 stack: SmallVec::new(), 1862 var_info: SmallVec::new(), 1863 map: custom_properties_map, 1864 non_custom_references: NonCustomReferences::default(), 1865 has_color_scheme, 1866 stylist, 1867 computed_context, 1868 invalid_non_custom_properties, 1869 deferred_properties: deferred_properties_map.as_deref_mut(), 1870 contains_computed_custom_property: false, 1871 }; 1872 traverse( 1873 VarType::Custom((*name).clone()), 1874 references_from_non_custom_properties, 1875 &mut context, 1876 attr_provider, 1877 ); 1878 } 1879 } 1880 1881 // See https://drafts.csswg.org/css-variables-2/#invalid-at-computed-value-time 1882 fn handle_invalid_at_computed_value_time( 1883 name: &Name, 1884 custom_properties: &mut ComputedCustomProperties, 1885 computed_context: &computed::Context, 1886 ) { 1887 let stylist = computed_context.style().stylist.unwrap(); 1888 let registration = stylist.get_custom_property_registration(&name); 1889 if !registration.syntax.is_universal() { 1890 // For the root element, inherited maps are empty. We should just 1891 // use the initial value if any, rather than removing the name. 1892 if registration.inherits() && !computed_context.is_root_element() { 1893 let inherited = computed_context.inherited_custom_properties(); 1894 if let Some(value) = inherited.get(registration, name) { 1895 custom_properties.insert(registration, name, value.clone()); 1896 return; 1897 } 1898 } else if let Some(ref initial_value) = registration.initial_value { 1899 if let Ok(initial_value) = compute_value( 1900 &initial_value.css, 1901 &initial_value.url_data, 1902 registration, 1903 computed_context, 1904 ) { 1905 custom_properties.insert(registration, name, initial_value); 1906 return; 1907 } 1908 } 1909 } 1910 custom_properties.remove(registration, name); 1911 } 1912 1913 /// Replace `var()`, `env()`, and `attr()` functions in a pre-existing variable value. 1914 fn substitute_references_if_needed_and_apply( 1915 name: &Name, 1916 value: &Arc<VariableValue>, 1917 custom_properties: &mut ComputedCustomProperties, 1918 stylist: &Stylist, 1919 computed_context: &computed::Context, 1920 attr_provider: &dyn AttributeProvider, 1921 ) { 1922 let registration = stylist.get_custom_property_registration(&name); 1923 if !value.has_references() && registration.syntax.is_universal() { 1924 // Trivial path: no references and no need to compute the value, just apply it directly. 1925 let computed_value = ComputedRegisteredValue::universal(Arc::clone(value)); 1926 custom_properties.insert(registration, name, computed_value); 1927 return; 1928 } 1929 1930 let inherited = computed_context.inherited_custom_properties(); 1931 let url_data = &value.url_data; 1932 let substitution = match substitute_internal( 1933 value, 1934 custom_properties, 1935 stylist, 1936 computed_context, 1937 attr_provider, 1938 ) { 1939 Ok(v) => v, 1940 Err(..) => { 1941 handle_invalid_at_computed_value_time(name, custom_properties, computed_context); 1942 return; 1943 }, 1944 }; 1945 1946 // If variable fallback results in a wide keyword, deal with it now. 1947 { 1948 let css = &substitution.css; 1949 let css_wide_kw = { 1950 let mut input = ParserInput::new(&css); 1951 let mut input = Parser::new(&mut input); 1952 input.try_parse(CSSWideKeyword::parse) 1953 }; 1954 1955 if let Ok(kw) = css_wide_kw { 1956 // TODO: It's unclear what this should do for revert / revert-layer, see 1957 // https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset 1958 // seems fine? 1959 match ( 1960 kw, 1961 registration.inherits(), 1962 computed_context.is_root_element(), 1963 ) { 1964 (CSSWideKeyword::Initial, _, _) 1965 | (CSSWideKeyword::Revert, false, _) 1966 | (CSSWideKeyword::RevertLayer, false, _) 1967 | (CSSWideKeyword::Unset, false, _) 1968 | (CSSWideKeyword::Revert, true, true) 1969 | (CSSWideKeyword::RevertLayer, true, true) 1970 | (CSSWideKeyword::Unset, true, true) 1971 | (CSSWideKeyword::Inherit, _, true) => { 1972 remove_and_insert_initial_value(name, registration, custom_properties); 1973 }, 1974 (CSSWideKeyword::Revert, true, false) 1975 | (CSSWideKeyword::RevertLayer, true, false) 1976 | (CSSWideKeyword::Inherit, _, false) 1977 | (CSSWideKeyword::Unset, true, false) => { 1978 match inherited.get(registration, name) { 1979 Some(value) => { 1980 custom_properties.insert(registration, name, value.clone()); 1981 }, 1982 None => { 1983 custom_properties.remove(registration, name); 1984 }, 1985 }; 1986 }, 1987 } 1988 return; 1989 } 1990 } 1991 1992 let value = match substitution.into_value(url_data, registration, computed_context) { 1993 Ok(v) => v, 1994 Err(()) => { 1995 handle_invalid_at_computed_value_time(name, custom_properties, computed_context); 1996 return; 1997 }, 1998 }; 1999 2000 custom_properties.insert(registration, name, value); 2001 } 2002 2003 #[derive(Default)] 2004 struct Substitution<'a> { 2005 css: Cow<'a, str>, 2006 first_token_type: TokenSerializationType, 2007 last_token_type: TokenSerializationType, 2008 } 2009 2010 impl<'a> Substitution<'a> { 2011 fn from_value(v: VariableValue) -> Self { 2012 Substitution { 2013 css: v.css.into(), 2014 first_token_type: v.first_token_type, 2015 last_token_type: v.last_token_type, 2016 } 2017 } 2018 2019 fn into_value( 2020 self, 2021 url_data: &UrlExtraData, 2022 registration: &PropertyRegistrationData, 2023 computed_context: &computed::Context, 2024 ) -> Result<ComputedRegisteredValue, ()> { 2025 if registration.syntax.is_universal() { 2026 return Ok(ComputedRegisteredValue::universal(Arc::new( 2027 VariableValue { 2028 css: self.css.into_owned(), 2029 first_token_type: self.first_token_type, 2030 last_token_type: self.last_token_type, 2031 url_data: url_data.clone(), 2032 references: Default::default(), 2033 }, 2034 ))); 2035 } 2036 compute_value(&self.css, url_data, registration, computed_context) 2037 } 2038 2039 fn new( 2040 css: Cow<'a, str>, 2041 first_token_type: TokenSerializationType, 2042 last_token_type: TokenSerializationType, 2043 ) -> Self { 2044 Self { 2045 css, 2046 first_token_type, 2047 last_token_type, 2048 } 2049 } 2050 } 2051 2052 fn compute_value( 2053 css: &str, 2054 url_data: &UrlExtraData, 2055 registration: &PropertyRegistrationData, 2056 computed_context: &computed::Context, 2057 ) -> Result<ComputedRegisteredValue, ()> { 2058 debug_assert!(!registration.syntax.is_universal()); 2059 2060 let mut input = ParserInput::new(&css); 2061 let mut input = Parser::new(&mut input); 2062 2063 SpecifiedRegisteredValue::compute( 2064 &mut input, 2065 registration, 2066 url_data, 2067 computed_context, 2068 AllowComputationallyDependent::Yes, 2069 ) 2070 } 2071 2072 /// Removes the named registered custom property and inserts its uncomputed initial value. 2073 fn remove_and_insert_initial_value( 2074 name: &Name, 2075 registration: &PropertyRegistrationData, 2076 custom_properties: &mut ComputedCustomProperties, 2077 ) { 2078 custom_properties.remove(registration, name); 2079 if let Some(ref initial_value) = registration.initial_value { 2080 let value = ComputedRegisteredValue::universal(Arc::clone(initial_value)); 2081 custom_properties.insert(registration, name, value); 2082 } 2083 } 2084 2085 fn do_substitute_chunk<'a>( 2086 css: &'a str, 2087 start: usize, 2088 end: usize, 2089 first_token_type: TokenSerializationType, 2090 last_token_type: TokenSerializationType, 2091 url_data: &UrlExtraData, 2092 custom_properties: &'a ComputedCustomProperties, 2093 stylist: &Stylist, 2094 computed_context: &computed::Context, 2095 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>, 2096 attr_provider: &dyn AttributeProvider, 2097 ) -> Result<Substitution<'a>, ()> { 2098 if start == end { 2099 // Empty string. Easy. 2100 return Ok(Substitution::default()); 2101 } 2102 // Easy case: no references involved. 2103 if references 2104 .peek() 2105 .map_or(true, |reference| reference.end > end) 2106 { 2107 let result = &css[start..end]; 2108 return Ok(Substitution::new( 2109 Cow::Borrowed(result), 2110 first_token_type, 2111 last_token_type, 2112 )); 2113 } 2114 2115 let mut substituted = ComputedValue::empty(url_data); 2116 let mut next_token_type = first_token_type; 2117 let mut cur_pos = start; 2118 while let Some(reference) = references.next_if(|reference| reference.end <= end) { 2119 if reference.start != cur_pos { 2120 substituted.push( 2121 &css[cur_pos..reference.start], 2122 next_token_type, 2123 reference.prev_token_type, 2124 )?; 2125 } 2126 2127 let substitution = substitute_one_reference( 2128 css, 2129 url_data, 2130 custom_properties, 2131 reference, 2132 stylist, 2133 computed_context, 2134 references, 2135 attr_provider, 2136 )?; 2137 2138 // Optimize the property: var(--...) case to avoid allocating at all. 2139 if reference.start == start && reference.end == end { 2140 return Ok(substitution); 2141 } 2142 2143 substituted.push( 2144 &substitution.css, 2145 substitution.first_token_type, 2146 substitution.last_token_type, 2147 )?; 2148 next_token_type = reference.next_token_type; 2149 cur_pos = reference.end; 2150 } 2151 // Push the rest of the value if needed. 2152 if cur_pos != end { 2153 substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?; 2154 } 2155 Ok(Substitution::from_value(substituted)) 2156 } 2157 2158 fn quoted_css_string(src: &str) -> String { 2159 let mut dest = String::with_capacity(src.len() + 2); 2160 cssparser::serialize_string(src, &mut dest).unwrap(); 2161 dest 2162 } 2163 2164 fn substitute_one_reference<'a>( 2165 css: &'a str, 2166 url_data: &UrlExtraData, 2167 custom_properties: &'a ComputedCustomProperties, 2168 reference: &SubstitutionFunctionReference, 2169 stylist: &Stylist, 2170 computed_context: &computed::Context, 2171 references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>, 2172 attr_provider: &dyn AttributeProvider, 2173 ) -> Result<Substitution<'a>, ()> { 2174 let simple_subst = |s: &str| { 2175 Some(Substitution::new( 2176 Cow::Owned(quoted_css_string(s)), 2177 TokenSerializationType::Nothing, 2178 TokenSerializationType::Nothing, 2179 )) 2180 }; 2181 let substitution: Option<_> = match reference.substitution_kind { 2182 SubstitutionFunctionKind::Var => { 2183 let registration = stylist.get_custom_property_registration(&reference.name); 2184 custom_properties 2185 .get(registration, &reference.name) 2186 .map(|v| Substitution::from_value(v.to_variable_value())) 2187 }, 2188 SubstitutionFunctionKind::Env => { 2189 let device = stylist.device(); 2190 device 2191 .environment() 2192 .get(&reference.name, device, url_data) 2193 .map(Substitution::from_value) 2194 }, 2195 // https://drafts.csswg.org/css-values-5/#attr-substitution 2196 SubstitutionFunctionKind::Attr => attr_provider 2197 .get_attr(AtomIdent::cast(&reference.name)) 2198 .map_or_else( 2199 || { 2200 // Special case when fallback and <attr-type> are omitted. 2201 // See FAILURE: https://drafts.csswg.org/css-values-5/#attr-substitution 2202 if reference.fallback.is_none() 2203 && reference.attribute_syntax == AttributeType::None 2204 { 2205 simple_subst("") 2206 } else { 2207 None 2208 } 2209 }, 2210 |attr| { 2211 let mut input = ParserInput::new(&attr); 2212 let mut parser = Parser::new(&mut input); 2213 match &reference.attribute_syntax { 2214 AttributeType::Unit(unit) => { 2215 let css = { 2216 // Verify that attribute data is a <number-token>. 2217 parser.expect_number().ok()?; 2218 let mut s = attr.clone(); 2219 s.push_str(unit.as_ref()); 2220 s 2221 }; 2222 let serialization = match unit { 2223 AttrUnit::Number => TokenSerializationType::Number, 2224 AttrUnit::Percentage => TokenSerializationType::Percentage, 2225 _ => TokenSerializationType::Dimension, 2226 }; 2227 let value = 2228 ComputedValue::new(css, url_data, serialization, serialization); 2229 Some(Substitution::from_value(value)) 2230 }, 2231 AttributeType::Type(syntax) => { 2232 let value = SpecifiedRegisteredValue::parse( 2233 &mut parser, 2234 syntax, 2235 url_data, 2236 AllowComputationallyDependent::Yes, 2237 ) 2238 .ok()?; 2239 Some(Substitution::from_value(value.to_variable_value())) 2240 }, 2241 AttributeType::RawString | AttributeType::None => simple_subst(&attr), 2242 } 2243 }, 2244 ), 2245 }; 2246 2247 if let Some(s) = substitution { 2248 while references 2249 .next_if(|next_ref| next_ref.end <= reference.end) 2250 .is_some() 2251 {} 2252 2253 return Ok(s); 2254 } 2255 2256 let Some(ref fallback) = reference.fallback else { 2257 return Err(()); 2258 }; 2259 2260 do_substitute_chunk( 2261 css, 2262 fallback.start.get(), 2263 reference.end - 1, // Skip the closing parenthesis of the reference value. 2264 fallback.first_token_type, 2265 fallback.last_token_type, 2266 url_data, 2267 custom_properties, 2268 stylist, 2269 computed_context, 2270 references, 2271 attr_provider, 2272 ) 2273 } 2274 2275 /// Replace `var()`, `env()`, and `attr()` functions. Return `Err(..)` for invalid at computed time. 2276 fn substitute_internal<'a>( 2277 variable_value: &'a VariableValue, 2278 custom_properties: &'a ComputedCustomProperties, 2279 stylist: &Stylist, 2280 computed_context: &computed::Context, 2281 attr_provider: &dyn AttributeProvider, 2282 ) -> Result<Substitution<'a>, ()> { 2283 let mut refs = variable_value.references.refs.iter().peekable(); 2284 do_substitute_chunk( 2285 &variable_value.css, 2286 /* start = */ 0, 2287 /* end = */ variable_value.css.len(), 2288 variable_value.first_token_type, 2289 variable_value.last_token_type, 2290 &variable_value.url_data, 2291 custom_properties, 2292 stylist, 2293 computed_context, 2294 &mut refs, 2295 attr_provider, 2296 ) 2297 } 2298 2299 /// Replace var(), env(), and attr() functions, returning the resulting CSS string. 2300 pub fn substitute<'a>( 2301 variable_value: &'a VariableValue, 2302 custom_properties: &'a ComputedCustomProperties, 2303 stylist: &Stylist, 2304 computed_context: &computed::Context, 2305 attr_provider: &dyn AttributeProvider, 2306 ) -> Result<Cow<'a, str>, ()> { 2307 debug_assert!(variable_value.has_references()); 2308 let v = substitute_internal( 2309 variable_value, 2310 custom_properties, 2311 stylist, 2312 computed_context, 2313 attr_provider, 2314 )?; 2315 Ok(v.css) 2316 }