mod.rs (55659B)
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 //! Supported CSS properties and the cascade. 6 7 pub mod cascade; 8 pub mod declaration_block; 9 10 pub use self::cascade::*; 11 pub use self::declaration_block::*; 12 pub use self::generated::*; 13 /// The CSS properties supported by the style system. 14 /// Generated from the properties.mako.rs template by build.rs 15 #[macro_use] 16 #[allow(unsafe_code)] 17 #[deny(missing_docs)] 18 pub mod generated { 19 include!(concat!(env!("OUT_DIR"), "/properties.rs")); 20 } 21 22 use crate::custom_properties::{self, ComputedCustomProperties}; 23 use crate::derives::*; 24 use crate::dom::AttributeProvider; 25 #[cfg(feature = "gecko")] 26 use crate::gecko_bindings::structs::{CSSPropertyId, NonCustomCSSPropertyId, RefPtr}; 27 use crate::logical_geometry::WritingMode; 28 use crate::parser::ParserContext; 29 use crate::stylesheets::CssRuleType; 30 use crate::stylesheets::Origin; 31 use crate::stylist::Stylist; 32 use crate::values::{computed, serialize_atom_name}; 33 use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; 34 use cssparser::{match_ignore_ascii_case, Parser, ParserInput}; 35 use rustc_hash::FxHashMap; 36 use servo_arc::Arc; 37 use std::{ 38 borrow::Cow, 39 fmt::{self, Write}, 40 mem, 41 }; 42 use style_traits::{ 43 CssString, CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss, 44 ToTyped, TypedValue, 45 }; 46 47 bitflags! { 48 /// A set of flags for properties. 49 #[derive(Clone, Copy)] 50 pub struct PropertyFlags: u16 { 51 /// This longhand property applies to ::first-letter. 52 const APPLIES_TO_FIRST_LETTER = 1 << 1; 53 /// This longhand property applies to ::first-line. 54 const APPLIES_TO_FIRST_LINE = 1 << 2; 55 /// This longhand property applies to ::placeholder. 56 const APPLIES_TO_PLACEHOLDER = 1 << 3; 57 /// This longhand property applies to ::cue. 58 const APPLIES_TO_CUE = 1 << 4; 59 /// This longhand property applies to ::marker. 60 const APPLIES_TO_MARKER = 1 << 5; 61 /// This property is a legacy shorthand. 62 /// 63 /// https://drafts.csswg.org/css-cascade/#legacy-shorthand 64 const IS_LEGACY_SHORTHAND = 1 << 6; 65 66 /* The following flags are currently not used in Rust code, they 67 * only need to be listed in corresponding properties so that 68 * they can be checked in the C++ side via ServoCSSPropList.h. */ 69 70 /// This property can be animated on the compositor. 71 const CAN_ANIMATE_ON_COMPOSITOR = 0; 72 /// See data.py's documentation about the affects_flags. 73 const AFFECTS_LAYOUT = 0; 74 #[allow(missing_docs)] 75 const AFFECTS_OVERFLOW = 0; 76 #[allow(missing_docs)] 77 const AFFECTS_PAINT = 0; 78 } 79 } 80 81 /// An enum to represent a CSS Wide keyword. 82 #[derive( 83 Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped, 84 )] 85 pub enum CSSWideKeyword { 86 /// The `initial` keyword. 87 Initial, 88 /// The `inherit` keyword. 89 Inherit, 90 /// The `unset` keyword. 91 Unset, 92 /// The `revert` keyword. 93 Revert, 94 /// The `revert-layer` keyword. 95 RevertLayer, 96 } 97 98 impl CSSWideKeyword { 99 /// Returns the string representation of the keyword. 100 pub fn to_str(&self) -> &'static str { 101 match *self { 102 CSSWideKeyword::Initial => "initial", 103 CSSWideKeyword::Inherit => "inherit", 104 CSSWideKeyword::Unset => "unset", 105 CSSWideKeyword::Revert => "revert", 106 CSSWideKeyword::RevertLayer => "revert-layer", 107 } 108 } 109 } 110 111 impl CSSWideKeyword { 112 /// Parses a CSS wide keyword from a CSS identifier. 113 pub fn from_ident(ident: &str) -> Result<Self, ()> { 114 Ok(match_ignore_ascii_case! { ident, 115 "initial" => CSSWideKeyword::Initial, 116 "inherit" => CSSWideKeyword::Inherit, 117 "unset" => CSSWideKeyword::Unset, 118 "revert" => CSSWideKeyword::Revert, 119 "revert-layer" => CSSWideKeyword::RevertLayer, 120 _ => return Err(()), 121 }) 122 } 123 124 /// Parses a CSS wide keyword completely. 125 pub fn parse(input: &mut Parser) -> Result<Self, ()> { 126 let keyword = { 127 let ident = input.expect_ident().map_err(|_| ())?; 128 Self::from_ident(ident)? 129 }; 130 input.expect_exhausted().map_err(|_| ())?; 131 Ok(keyword) 132 } 133 } 134 135 /// A declaration using a CSS-wide keyword. 136 #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] 137 pub struct WideKeywordDeclaration { 138 #[css(skip)] 139 id: LonghandId, 140 /// The CSS-wide keyword. 141 pub keyword: CSSWideKeyword, 142 } 143 144 // XXX Switch back to ToTyped derive once it can automatically handle structs 145 // Tracking in bug 1991631 146 impl ToTyped for WideKeywordDeclaration { 147 fn to_typed(&self) -> Option<TypedValue> { 148 self.keyword.to_typed() 149 } 150 } 151 152 /// An unparsed declaration that contains `var()` functions. 153 #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)] 154 pub struct VariableDeclaration { 155 /// The id of the property this declaration represents. 156 #[css(skip)] 157 id: LonghandId, 158 /// The unparsed value of the variable. 159 #[ignore_malloc_size_of = "Arc"] 160 pub value: Arc<UnparsedValue>, 161 } 162 163 /// A custom property declaration value is either an unparsed value or a CSS 164 /// wide-keyword. 165 #[derive(Clone, PartialEq, ToCss, ToShmem)] 166 pub enum CustomDeclarationValue { 167 /// An unparsed value. 168 Unparsed(Arc<custom_properties::SpecifiedValue>), 169 /// An already-parsed value. 170 Parsed(Arc<crate::properties_and_values::value::SpecifiedValue>), 171 /// A wide keyword. 172 CSSWideKeyword(CSSWideKeyword), 173 } 174 175 /// A custom property declaration with the property name and the declared value. 176 #[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)] 177 pub struct CustomDeclaration { 178 /// The name of the custom property. 179 #[css(skip)] 180 pub name: custom_properties::Name, 181 /// The value of the custom property. 182 #[ignore_malloc_size_of = "Arc"] 183 pub value: CustomDeclarationValue, 184 } 185 186 impl fmt::Debug for PropertyDeclaration { 187 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 188 self.id().to_css(&mut CssWriter::new(f))?; 189 f.write_str(": ")?; 190 191 // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write 192 // it directly to f, and need to allocate an intermediate string. This is 193 // fine for debug-only code. 194 let mut s = CssString::new(); 195 self.to_css(&mut s)?; 196 write!(f, "{}", s) 197 } 198 } 199 200 /// A longhand or shorthand property. 201 #[derive( 202 Clone, Copy, Debug, PartialEq, Eq, Hash, ToComputedValue, ToResolvedValue, ToShmem, MallocSizeOf, 203 )] 204 #[repr(C)] 205 pub struct NonCustomPropertyId(u16); 206 207 impl ToCss for NonCustomPropertyId { 208 #[inline] 209 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 210 where 211 W: Write, 212 { 213 dest.write_str(self.name()) 214 } 215 } 216 217 impl NonCustomPropertyId { 218 /// Returns the underlying index, used for use counter. 219 pub fn bit(self) -> usize { 220 self.0 as usize 221 } 222 223 /// Convert a `NonCustomPropertyId` into a `NonCustomCSSPropertyId`. 224 #[cfg(feature = "gecko")] 225 #[inline] 226 pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId { 227 // unsafe: guaranteed by static_assert_noncustomcsspropertyid. 228 unsafe { mem::transmute(self.0) } 229 } 230 231 /// Convert an `NonCustomCSSPropertyId` into a `NonCustomPropertyId`. 232 #[cfg(feature = "gecko")] 233 #[inline] 234 pub fn from_noncustomcsspropertyid(prop: NonCustomCSSPropertyId) -> Option<Self> { 235 let prop = prop as u16; 236 if prop >= property_counts::NON_CUSTOM as u16 { 237 return None; 238 } 239 // guaranteed by static_assert_noncustomcsspropertyid above. 240 Some(NonCustomPropertyId(prop)) 241 } 242 243 /// Resolves the alias of a given property if needed. 244 pub fn unaliased(self) -> Self { 245 let Some(alias_id) = self.as_alias() else { 246 return self; 247 }; 248 alias_id.aliased_property() 249 } 250 251 /// Turns this `NonCustomPropertyId` into a `PropertyId`. 252 #[inline] 253 pub fn to_property_id(self) -> PropertyId { 254 PropertyId::NonCustom(self) 255 } 256 257 /// Returns a longhand id, if this property is one. 258 #[inline] 259 pub fn as_longhand(self) -> Option<LonghandId> { 260 if self.0 < property_counts::LONGHANDS as u16 { 261 return Some(unsafe { mem::transmute(self.0 as u16) }); 262 } 263 None 264 } 265 266 /// Returns a shorthand id, if this property is one. 267 #[inline] 268 pub fn as_shorthand(self) -> Option<ShorthandId> { 269 if self.0 >= property_counts::LONGHANDS as u16 270 && self.0 < property_counts::LONGHANDS_AND_SHORTHANDS as u16 271 { 272 return Some(unsafe { mem::transmute(self.0 - (property_counts::LONGHANDS as u16)) }); 273 } 274 None 275 } 276 277 /// Returns an alias id, if this property is one. 278 #[inline] 279 pub fn as_alias(self) -> Option<AliasId> { 280 debug_assert!((self.0 as usize) < property_counts::NON_CUSTOM); 281 if self.0 >= property_counts::LONGHANDS_AND_SHORTHANDS as u16 { 282 return Some(unsafe { 283 mem::transmute(self.0 - (property_counts::LONGHANDS_AND_SHORTHANDS as u16)) 284 }); 285 } 286 None 287 } 288 289 /// Returns either a longhand or a shorthand, resolving aliases. 290 #[inline] 291 pub fn longhand_or_shorthand(self) -> Result<LonghandId, ShorthandId> { 292 let id = self.unaliased(); 293 match id.as_longhand() { 294 Some(lh) => Ok(lh), 295 None => Err(id.as_shorthand().unwrap()), 296 } 297 } 298 299 /// Converts a longhand id into a non-custom property id. 300 #[inline] 301 pub const fn from_longhand(id: LonghandId) -> Self { 302 Self(id as u16) 303 } 304 305 /// Converts a shorthand id into a non-custom property id. 306 #[inline] 307 pub const fn from_shorthand(id: ShorthandId) -> Self { 308 Self((id as u16) + (property_counts::LONGHANDS as u16)) 309 } 310 311 /// Converts an alias id into a non-custom property id. 312 #[inline] 313 pub const fn from_alias(id: AliasId) -> Self { 314 Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16)) 315 } 316 } 317 318 impl From<LonghandId> for NonCustomPropertyId { 319 #[inline] 320 fn from(id: LonghandId) -> Self { 321 Self::from_longhand(id) 322 } 323 } 324 325 impl From<ShorthandId> for NonCustomPropertyId { 326 #[inline] 327 fn from(id: ShorthandId) -> Self { 328 Self::from_shorthand(id) 329 } 330 } 331 332 impl From<AliasId> for NonCustomPropertyId { 333 #[inline] 334 fn from(id: AliasId) -> Self { 335 Self::from_alias(id) 336 } 337 } 338 339 /// Representation of a CSS property, that is, either a longhand, a shorthand, or a custom 340 /// property. 341 #[derive(Clone, Eq, PartialEq, Debug)] 342 pub enum PropertyId { 343 /// An alias for a shorthand property. 344 NonCustom(NonCustomPropertyId), 345 /// A custom property. 346 Custom(custom_properties::Name), 347 } 348 349 impl ToCss for PropertyId { 350 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 351 where 352 W: Write, 353 { 354 match *self { 355 PropertyId::NonCustom(id) => dest.write_str(id.name()), 356 PropertyId::Custom(ref name) => { 357 dest.write_str("--")?; 358 serialize_atom_name(name, dest) 359 }, 360 } 361 } 362 } 363 364 impl PropertyId { 365 /// Return the longhand id that this property id represents. 366 #[inline] 367 pub fn longhand_id(&self) -> Option<LonghandId> { 368 self.non_custom_non_alias_id()?.as_longhand() 369 } 370 371 /// Returns true if this property is one of the animatable properties. 372 pub fn is_animatable(&self) -> bool { 373 match self { 374 Self::NonCustom(id) => id.is_animatable(), 375 Self::Custom(_) => cfg!(feature = "gecko"), 376 } 377 } 378 379 /// Returns a given property from the given name, _regardless of whether it is enabled or 380 /// not_, or Err(()) for unknown properties. 381 /// 382 /// Do not use for non-testing purposes. 383 pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> { 384 Self::parse_unchecked(name, None) 385 } 386 387 /// Parses a property name, and returns an error if it's unknown or isn't enabled for all 388 /// content. 389 #[inline] 390 pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> { 391 let id = Self::parse_unchecked(name, None)?; 392 393 if !id.enabled_for_all_content() { 394 return Err(()); 395 } 396 397 Ok(id) 398 } 399 400 /// Parses a property name, and returns an error if it's unknown or isn't allowed in this 401 /// context. 402 #[inline] 403 pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> { 404 let id = Self::parse_unchecked(name, context.use_counters)?; 405 if !id.allowed_in(context) { 406 return Err(()); 407 } 408 Ok(id) 409 } 410 411 /// Parses a property name, and returns an error if it's unknown or isn't allowed in this 412 /// context, ignoring the rule_type checks. 413 /// 414 /// This is useful for parsing stuff from CSS values, for example. 415 #[inline] 416 pub fn parse_ignoring_rule_type(name: &str, context: &ParserContext) -> Result<Self, ()> { 417 let id = Self::parse_unchecked(name, None)?; 418 if !id.allowed_in_ignoring_rule_type(context) { 419 return Err(()); 420 } 421 Ok(id) 422 } 423 424 /// Returns a property id from Gecko's NonCustomCSSPropertyId. 425 #[cfg(feature = "gecko")] 426 #[inline] 427 pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> { 428 Some(NonCustomPropertyId::from_noncustomcsspropertyid(id)?.to_property_id()) 429 } 430 431 /// Returns a property id from Gecko's CSSPropertyId. 432 #[cfg(feature = "gecko")] 433 #[inline] 434 pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> { 435 Some( 436 if property.mId == NonCustomCSSPropertyId::eCSSPropertyExtra_variable { 437 debug_assert!(!property.mCustomName.mRawPtr.is_null()); 438 Self::Custom(unsafe { crate::Atom::from_raw(property.mCustomName.mRawPtr) }) 439 } else { 440 Self::NonCustom(NonCustomPropertyId::from_noncustomcsspropertyid( 441 property.mId, 442 )?) 443 }, 444 ) 445 } 446 447 /// Returns true if the property is a shorthand or shorthand alias. 448 #[inline] 449 pub fn is_shorthand(&self) -> bool { 450 self.as_shorthand().is_ok() 451 } 452 453 /// Given this property id, get it either as a shorthand or as a 454 /// `PropertyDeclarationId`. 455 pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId<'_>> { 456 match *self { 457 Self::NonCustom(id) => match id.longhand_or_shorthand() { 458 Ok(lh) => Err(PropertyDeclarationId::Longhand(lh)), 459 Err(sh) => Ok(sh), 460 }, 461 Self::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)), 462 } 463 } 464 465 /// Returns the `NonCustomPropertyId` corresponding to this property id. 466 pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> { 467 match *self { 468 Self::Custom(_) => None, 469 Self::NonCustom(id) => Some(id), 470 } 471 } 472 473 /// Returns non-alias NonCustomPropertyId corresponding to this 474 /// property id. 475 fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> { 476 self.non_custom_id().map(NonCustomPropertyId::unaliased) 477 } 478 479 /// Whether the property is enabled for all content regardless of the 480 /// stylesheet it was declared on (that is, in practice only checks prefs). 481 #[inline] 482 pub fn enabled_for_all_content(&self) -> bool { 483 let id = match self.non_custom_id() { 484 // Custom properties are allowed everywhere 485 None => return true, 486 Some(id) => id, 487 }; 488 489 id.enabled_for_all_content() 490 } 491 492 /// Converts this PropertyId in NonCustomCSSPropertyId, resolving aliases to the 493 /// resolved property, and returning eCSSPropertyExtra_variable for custom 494 /// properties. 495 #[cfg(feature = "gecko")] 496 #[inline] 497 pub fn to_noncustomcsspropertyid_resolving_aliases(&self) -> NonCustomCSSPropertyId { 498 match self.non_custom_non_alias_id() { 499 Some(id) => id.to_noncustomcsspropertyid(), 500 None => NonCustomCSSPropertyId::eCSSPropertyExtra_variable, 501 } 502 } 503 504 fn allowed_in(&self, context: &ParserContext) -> bool { 505 let id = match self.non_custom_id() { 506 // Custom properties are allowed everywhere, except `position-try`. 507 None => { 508 return !context 509 .nesting_context 510 .rule_types 511 .contains(CssRuleType::PositionTry) 512 }, 513 Some(id) => id, 514 }; 515 id.allowed_in(context) 516 } 517 518 #[inline] 519 fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool { 520 let id = match self.non_custom_id() { 521 // Custom properties are allowed everywhere 522 None => return true, 523 Some(id) => id, 524 }; 525 id.allowed_in_ignoring_rule_type(context) 526 } 527 528 /// Whether the property supports the given CSS type. 529 /// `ty` should a bitflags of constants in style_traits::CssType. 530 pub fn supports_type(&self, ty: u8) -> bool { 531 let id = self.non_custom_non_alias_id(); 532 id.map_or(0, |id| id.supported_types()) & ty != 0 533 } 534 535 /// Collect supported starting word of values of this property. 536 /// 537 /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more 538 /// details. 539 pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { 540 if let Some(id) = self.non_custom_non_alias_id() { 541 id.collect_property_completion_keywords(f); 542 } 543 CSSWideKeyword::collect_completion_keywords(f); 544 } 545 } 546 547 impl ToCss for LonghandId { 548 #[inline] 549 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 550 where 551 W: Write, 552 { 553 dest.write_str(self.name()) 554 } 555 } 556 557 impl fmt::Debug for LonghandId { 558 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 559 formatter.write_str(self.name()) 560 } 561 } 562 563 impl LonghandId { 564 /// Get the name of this longhand property. 565 #[inline] 566 pub fn name(&self) -> &'static str { 567 NonCustomPropertyId::from(*self).name() 568 } 569 570 /// Returns whether the longhand property is inherited by default. 571 #[inline] 572 pub fn inherited(self) -> bool { 573 !LonghandIdSet::reset().contains(self) 574 } 575 576 /// Returns whether the longhand property is zoom-dependent. 577 #[inline] 578 pub fn zoom_dependent(self) -> bool { 579 LonghandIdSet::zoom_dependent().contains(self) 580 } 581 582 /// Returns true if the property is one that is ignored when document 583 /// colors are disabled. 584 #[inline] 585 pub fn ignored_when_document_colors_disabled(self) -> bool { 586 LonghandIdSet::ignored_when_colors_disabled().contains(self) 587 } 588 589 /// Returns whether this longhand is `non_custom` or is a longhand of it. 590 pub fn is_or_is_longhand_of(self, non_custom: NonCustomPropertyId) -> bool { 591 match non_custom.longhand_or_shorthand() { 592 Ok(lh) => self == lh, 593 Err(sh) => self.is_longhand_of(sh), 594 } 595 } 596 597 /// Returns whether this longhand is a longhand of `shorthand`. 598 pub fn is_longhand_of(self, shorthand: ShorthandId) -> bool { 599 self.shorthands().any(|s| s == shorthand) 600 } 601 602 /// Returns whether this property is animatable. 603 #[inline] 604 pub fn is_animatable(self) -> bool { 605 NonCustomPropertyId::from(self).is_animatable() 606 } 607 608 /// Returns whether this property is animatable in a discrete way. 609 #[inline] 610 pub fn is_discrete_animatable(self) -> bool { 611 LonghandIdSet::discrete_animatable().contains(self) 612 } 613 614 /// Converts from a LonghandId to an adequate NonCustomCSSPropertyId. 615 #[cfg(feature = "gecko")] 616 #[inline] 617 pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId { 618 NonCustomPropertyId::from(self).to_noncustomcsspropertyid() 619 } 620 621 #[cfg(feature = "gecko")] 622 /// Returns a longhand id from Gecko's NonCustomCSSPropertyId. 623 pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> { 624 NonCustomPropertyId::from_noncustomcsspropertyid(id)? 625 .unaliased() 626 .as_longhand() 627 } 628 629 /// Return whether this property is logical. 630 #[inline] 631 pub fn is_logical(self) -> bool { 632 LonghandIdSet::logical().contains(self) 633 } 634 } 635 636 impl ToCss for ShorthandId { 637 #[inline] 638 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 639 where 640 W: Write, 641 { 642 dest.write_str(self.name()) 643 } 644 } 645 646 impl ShorthandId { 647 /// Get the name for this shorthand property. 648 #[inline] 649 pub fn name(&self) -> &'static str { 650 NonCustomPropertyId::from(*self).name() 651 } 652 653 /// Converts from a ShorthandId to an adequate NonCustomCSSPropertyId. 654 #[cfg(feature = "gecko")] 655 #[inline] 656 pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId { 657 NonCustomPropertyId::from(self).to_noncustomcsspropertyid() 658 } 659 660 /// Converts from a NonCustomCSSPropertyId to a ShorthandId. 661 #[cfg(feature = "gecko")] 662 #[inline] 663 pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> { 664 NonCustomPropertyId::from_noncustomcsspropertyid(id)? 665 .unaliased() 666 .as_shorthand() 667 } 668 669 /// Finds and returns an appendable value for the given declarations. 670 /// 671 /// Returns the optional appendable value. 672 pub fn get_shorthand_appendable_value<'a, 'b: 'a>( 673 self, 674 declarations: &'a [&'b PropertyDeclaration], 675 ) -> Option<AppendableValue<'a, 'b>> { 676 let first_declaration = declarations.get(0)?; 677 let rest = || declarations.iter().skip(1); 678 679 // https://drafts.csswg.org/css-variables/#variables-in-shorthands 680 if let Some(css) = first_declaration.with_variables_from_shorthand(self) { 681 if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) { 682 return Some(AppendableValue::Css(css)); 683 } 684 return None; 685 } 686 687 // Check whether they are all the same CSS-wide keyword. 688 if let Some(keyword) = first_declaration.get_css_wide_keyword() { 689 if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) { 690 return Some(AppendableValue::Css(keyword.to_str())); 691 } 692 return None; 693 } 694 695 if self == ShorthandId::All { 696 // 'all' only supports variables and CSS wide keywords. 697 return None; 698 } 699 700 // Check whether all declarations can be serialized as part of shorthand. 701 if declarations 702 .iter() 703 .all(|d| d.may_serialize_as_part_of_shorthand()) 704 { 705 return Some(AppendableValue::DeclarationsForShorthand( 706 self, 707 declarations, 708 )); 709 } 710 711 None 712 } 713 714 /// Returns whether this property is a legacy shorthand. 715 #[inline] 716 pub fn is_legacy_shorthand(self) -> bool { 717 self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND) 718 } 719 } 720 721 fn parse_non_custom_property_declaration_value_into<'i>( 722 declarations: &mut SourcePropertyDeclaration, 723 context: &ParserContext, 724 input: &mut Parser<'i, '_>, 725 start: &cssparser::ParserState, 726 parse_entirely_into: impl FnOnce( 727 &mut SourcePropertyDeclaration, 728 &mut Parser<'i, '_>, 729 ) -> Result<(), ParseError<'i>>, 730 parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword), 731 parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue), 732 ) -> Result<(), ParseError<'i>> { 733 let mut starts_with_curly_block = false; 734 if let Ok(token) = input.next() { 735 match token { 736 cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) { 737 Ok(wk) => { 738 if input.expect_exhausted().is_ok() { 739 return Ok(parsed_wide_keyword(declarations, wk)); 740 } 741 }, 742 Err(()) => {}, 743 }, 744 cssparser::Token::CurlyBracketBlock => { 745 starts_with_curly_block = true; 746 }, 747 _ => {}, 748 } 749 }; 750 751 input.reset(&start); 752 input.look_for_arbitrary_substitution_functions( 753 if static_prefs::pref!("layout.css.attr.enabled") { 754 &["var", "env", "attr"] 755 } else { 756 &["var", "env"] 757 }, 758 ); 759 760 let err = match parse_entirely_into(declarations, input) { 761 Ok(()) => { 762 input.seen_arbitrary_substitution_functions(); 763 return Ok(()); 764 }, 765 Err(e) => e, 766 }; 767 768 // Look for var(), env() and top-level curly blocks after the error. 769 let start_pos = start.position(); 770 let mut at_start = start_pos == input.position(); 771 let mut invalid = false; 772 while let Ok(token) = input.next() { 773 if matches!(token, cssparser::Token::CurlyBracketBlock) { 774 if !starts_with_curly_block || !at_start { 775 invalid = true; 776 break; 777 } 778 } else if starts_with_curly_block { 779 invalid = true; 780 break; 781 } 782 at_start = false; 783 } 784 if !input.seen_arbitrary_substitution_functions() || invalid { 785 return Err(err); 786 } 787 input.reset(start); 788 let value = custom_properties::VariableValue::parse(input, &context.url_data)?; 789 parsed_custom(declarations, value); 790 Ok(()) 791 } 792 793 impl PropertyDeclaration { 794 fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> { 795 match *self { 796 PropertyDeclaration::WithVariables(ref declaration) => { 797 let s = declaration.value.from_shorthand?; 798 if s != shorthand { 799 return None; 800 } 801 Some(&*declaration.value.variable_value.css) 802 }, 803 _ => None, 804 } 805 } 806 807 /// Returns a CSS-wide keyword declaration for a given property. 808 #[inline] 809 pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self { 810 Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword }) 811 } 812 813 /// Returns a CSS-wide keyword if the declaration's value is one. 814 #[inline] 815 pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> { 816 match *self { 817 PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword), 818 _ => None, 819 } 820 } 821 822 /// Returns whether the declaration may be serialized as part of a shorthand. 823 /// 824 /// This method returns false if this declaration contains variable or has a 825 /// CSS-wide keyword value, since these values cannot be serialized as part 826 /// of a shorthand. 827 /// 828 /// Caller should check `with_variables_from_shorthand()` and whether all 829 /// needed declarations has the same CSS-wide keyword first. 830 /// 831 /// Note that, serialization of a shorthand may still fail because of other 832 /// property-specific requirement even when this method returns true for all 833 /// the longhand declarations. 834 pub fn may_serialize_as_part_of_shorthand(&self) -> bool { 835 match *self { 836 PropertyDeclaration::CSSWideKeyword(..) | PropertyDeclaration::WithVariables(..) => { 837 false 838 }, 839 PropertyDeclaration::Custom(..) => { 840 unreachable!("Serializing a custom property as part of shorthand?") 841 }, 842 _ => true, 843 } 844 } 845 846 /// Returns true if this property declaration is for one of the animatable properties. 847 pub fn is_animatable(&self) -> bool { 848 self.id().is_animatable() 849 } 850 851 /// Returns true if this property is a custom property, false 852 /// otherwise. 853 pub fn is_custom(&self) -> bool { 854 matches!(*self, PropertyDeclaration::Custom(..)) 855 } 856 857 /// The `context` parameter controls this: 858 /// 859 /// <https://drafts.csswg.org/css-animations/#keyframes> 860 /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property 861 /// > except those defined in this specification, 862 /// > but does accept the `animation-play-state` property and interprets it specially. 863 /// 864 /// This will not actually parse Importance values, and will always set things 865 /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser, 866 /// we only set them here so that we don't have to reallocate 867 pub fn parse_into<'i, 't>( 868 declarations: &mut SourcePropertyDeclaration, 869 id: PropertyId, 870 context: &ParserContext, 871 input: &mut Parser<'i, 't>, 872 ) -> Result<(), ParseError<'i>> { 873 assert!(declarations.is_empty()); 874 debug_assert!(id.allowed_in(context), "{:?}", id); 875 input.skip_whitespace(); 876 877 let start = input.state(); 878 let non_custom_id = match id { 879 PropertyId::Custom(property_name) => { 880 let value = match input.try_parse(CSSWideKeyword::parse) { 881 Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword), 882 Err(()) => CustomDeclarationValue::Unparsed(Arc::new( 883 custom_properties::VariableValue::parse(input, &context.url_data)?, 884 )), 885 }; 886 declarations.push(PropertyDeclaration::Custom(CustomDeclaration { 887 name: property_name, 888 value, 889 })); 890 return Ok(()); 891 }, 892 PropertyId::NonCustom(id) => id, 893 }; 894 match non_custom_id.longhand_or_shorthand() { 895 Ok(longhand_id) => { 896 parse_non_custom_property_declaration_value_into( 897 declarations, 898 context, 899 input, 900 &start, 901 |declarations, input| { 902 let decl = input 903 .parse_entirely(|input| longhand_id.parse_value(context, input))?; 904 declarations.push(decl); 905 Ok(()) 906 }, 907 |declarations, wk| { 908 declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk)); 909 }, 910 |declarations, variable_value| { 911 declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration { 912 id: longhand_id, 913 value: Arc::new(UnparsedValue { 914 variable_value, 915 from_shorthand: None, 916 }), 917 })) 918 }, 919 )?; 920 }, 921 Err(shorthand_id) => { 922 parse_non_custom_property_declaration_value_into( 923 declarations, 924 context, 925 input, 926 &start, 927 // Not using parse_entirely here: each ShorthandId::parse_into function needs 928 // to do so *before* pushing to `declarations`. 929 |declarations, input| shorthand_id.parse_into(declarations, context, input), 930 |declarations, wk| { 931 if shorthand_id == ShorthandId::All { 932 declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk) 933 } else { 934 for longhand in shorthand_id.longhands() { 935 declarations 936 .push(PropertyDeclaration::css_wide_keyword(longhand, wk)); 937 } 938 } 939 }, 940 |declarations, variable_value| { 941 let unparsed = Arc::new(UnparsedValue { 942 variable_value, 943 from_shorthand: Some(shorthand_id), 944 }); 945 if shorthand_id == ShorthandId::All { 946 declarations.all_shorthand = AllShorthand::WithVariables(unparsed) 947 } else { 948 for id in shorthand_id.longhands() { 949 declarations.push(PropertyDeclaration::WithVariables( 950 VariableDeclaration { 951 id, 952 value: unparsed.clone(), 953 }, 954 )) 955 } 956 } 957 }, 958 )?; 959 }, 960 } 961 if let Some(use_counters) = context.use_counters { 962 use_counters.non_custom_properties.record(non_custom_id); 963 } 964 Ok(()) 965 } 966 } 967 968 /// A PropertyDeclarationId without references, for use as a hash map key. 969 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 970 pub enum OwnedPropertyDeclarationId { 971 /// A longhand. 972 Longhand(LonghandId), 973 /// A custom property declaration. 974 Custom(custom_properties::Name), 975 } 976 977 impl OwnedPropertyDeclarationId { 978 /// Return whether this property is logical. 979 #[inline] 980 pub fn is_logical(&self) -> bool { 981 self.as_borrowed().is_logical() 982 } 983 984 /// Returns the corresponding PropertyDeclarationId. 985 #[inline] 986 pub fn as_borrowed(&self) -> PropertyDeclarationId<'_> { 987 match self { 988 Self::Longhand(id) => PropertyDeclarationId::Longhand(*id), 989 Self::Custom(name) => PropertyDeclarationId::Custom(name), 990 } 991 } 992 993 /// Convert an `CSSPropertyId` into an `OwnedPropertyDeclarationId`. 994 #[cfg(feature = "gecko")] 995 #[inline] 996 pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> { 997 Some(match PropertyId::from_gecko_css_property_id(property)? { 998 PropertyId::Custom(name) => Self::Custom(name), 999 PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?), 1000 }) 1001 } 1002 } 1003 1004 /// An identifier for a given property declaration, which can be either a 1005 /// longhand or a custom property. 1006 #[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)] 1007 pub enum PropertyDeclarationId<'a> { 1008 /// A longhand. 1009 Longhand(LonghandId), 1010 /// A custom property declaration. 1011 Custom(&'a custom_properties::Name), 1012 } 1013 1014 impl<'a> ToCss for PropertyDeclarationId<'a> { 1015 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1016 where 1017 W: Write, 1018 { 1019 match *self { 1020 PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), 1021 PropertyDeclarationId::Custom(name) => { 1022 dest.write_str("--")?; 1023 serialize_atom_name(name, dest) 1024 }, 1025 } 1026 } 1027 } 1028 1029 impl<'a> PropertyDeclarationId<'a> { 1030 /// Returns PropertyFlags for given property. 1031 #[inline(always)] 1032 pub fn flags(&self) -> PropertyFlags { 1033 match self { 1034 Self::Longhand(id) => id.flags(), 1035 Self::Custom(_) => PropertyFlags::empty(), 1036 } 1037 } 1038 1039 /// Convert to an OwnedPropertyDeclarationId. 1040 pub fn to_owned(&self) -> OwnedPropertyDeclarationId { 1041 match self { 1042 PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id), 1043 PropertyDeclarationId::Custom(name) => { 1044 OwnedPropertyDeclarationId::Custom((*name).clone()) 1045 }, 1046 } 1047 } 1048 1049 /// Whether a given declaration id is either the same as `other`, or a 1050 /// longhand of it. 1051 pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool { 1052 match *self { 1053 PropertyDeclarationId::Longhand(id) => match *other { 1054 PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id), 1055 PropertyId::Custom(_) => false, 1056 }, 1057 PropertyDeclarationId::Custom(name) => { 1058 matches!(*other, PropertyId::Custom(ref other_name) if name == other_name) 1059 }, 1060 } 1061 } 1062 1063 /// Whether a given declaration id is a longhand belonging to this 1064 /// shorthand. 1065 pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool { 1066 match *self { 1067 PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand), 1068 _ => false, 1069 } 1070 } 1071 1072 /// Returns the name of the property without CSS escaping. 1073 pub fn name(&self) -> Cow<'static, str> { 1074 match *self { 1075 PropertyDeclarationId::Longhand(id) => id.name().into(), 1076 PropertyDeclarationId::Custom(name) => { 1077 let mut s = String::new(); 1078 write!(&mut s, "--{}", name).unwrap(); 1079 s.into() 1080 }, 1081 } 1082 } 1083 1084 /// Returns longhand id if it is, None otherwise. 1085 #[inline] 1086 pub fn as_longhand(&self) -> Option<LonghandId> { 1087 match *self { 1088 PropertyDeclarationId::Longhand(id) => Some(id), 1089 _ => None, 1090 } 1091 } 1092 1093 /// Return whether this property is logical. 1094 #[inline] 1095 pub fn is_logical(&self) -> bool { 1096 match self { 1097 PropertyDeclarationId::Longhand(id) => id.is_logical(), 1098 PropertyDeclarationId::Custom(_) => false, 1099 } 1100 } 1101 1102 /// If this is a logical property, return the corresponding physical one in 1103 /// the given writing mode. 1104 /// 1105 /// Otherwise, return unchanged. 1106 #[inline] 1107 pub fn to_physical(&self, wm: WritingMode) -> Self { 1108 match self { 1109 Self::Longhand(id) => Self::Longhand(id.to_physical(wm)), 1110 Self::Custom(_) => self.clone(), 1111 } 1112 } 1113 1114 /// Returns whether this property is animatable. 1115 #[inline] 1116 pub fn is_animatable(&self) -> bool { 1117 match self { 1118 Self::Longhand(id) => id.is_animatable(), 1119 Self::Custom(_) => cfg!(feature = "gecko"), 1120 } 1121 } 1122 1123 /// Returns whether this property is animatable in a discrete way. 1124 #[inline] 1125 pub fn is_discrete_animatable(&self) -> bool { 1126 match self { 1127 Self::Longhand(longhand) => longhand.is_discrete_animatable(), 1128 // TODO(bug 1885995): Refine this. 1129 Self::Custom(_) => cfg!(feature = "gecko"), 1130 } 1131 } 1132 1133 /// Converts from a to an adequate NonCustomCSSPropertyId, returning 1134 /// eCSSPropertyExtra_variable for custom properties. 1135 #[cfg(feature = "gecko")] 1136 #[inline] 1137 pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId { 1138 match self { 1139 PropertyDeclarationId::Longhand(id) => id.to_noncustomcsspropertyid(), 1140 PropertyDeclarationId::Custom(_) => NonCustomCSSPropertyId::eCSSPropertyExtra_variable, 1141 } 1142 } 1143 1144 /// Convert a `PropertyDeclarationId` into an `CSSPropertyId` 1145 /// 1146 /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id 1147 /// representation or so. 1148 #[cfg(feature = "gecko")] 1149 #[inline] 1150 pub fn to_gecko_css_property_id(&self) -> CSSPropertyId { 1151 match self { 1152 Self::Longhand(id) => CSSPropertyId { 1153 mId: id.to_noncustomcsspropertyid(), 1154 mCustomName: RefPtr::null(), 1155 }, 1156 Self::Custom(name) => { 1157 let mut property_id = CSSPropertyId { 1158 mId: NonCustomCSSPropertyId::eCSSPropertyExtra_variable, 1159 mCustomName: RefPtr::null(), 1160 }; 1161 property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed(); 1162 property_id 1163 }, 1164 } 1165 } 1166 } 1167 1168 /// A set of all properties. 1169 #[derive(Clone, PartialEq, Default)] 1170 pub struct NonCustomPropertyIdSet { 1171 storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32], 1172 } 1173 1174 impl NonCustomPropertyIdSet { 1175 /// Creates an empty `NonCustomPropertyIdSet`. 1176 pub fn new() -> Self { 1177 Self { 1178 storage: Default::default(), 1179 } 1180 } 1181 1182 /// Insert a non-custom-property in the set. 1183 #[inline] 1184 pub fn insert(&mut self, id: NonCustomPropertyId) { 1185 let bit = id.0 as usize; 1186 self.storage[bit / 32] |= 1 << (bit % 32); 1187 } 1188 1189 /// Return whether the given property is in the set 1190 #[inline] 1191 pub fn contains(&self, id: NonCustomPropertyId) -> bool { 1192 let bit = id.0 as usize; 1193 (self.storage[bit / 32] & (1 << (bit % 32))) != 0 1194 } 1195 } 1196 1197 /// A set of longhand properties 1198 #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] 1199 pub struct LonghandIdSet { 1200 storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32], 1201 } 1202 1203 to_shmem::impl_trivial_to_shmem!(LonghandIdSet); 1204 1205 impl LonghandIdSet { 1206 /// Return an empty LonghandIdSet. 1207 #[inline] 1208 pub fn new() -> Self { 1209 Self { 1210 storage: Default::default(), 1211 } 1212 } 1213 1214 /// Iterate over the current longhand id set. 1215 pub fn iter(&self) -> LonghandIdSetIterator<'_> { 1216 LonghandIdSetIterator { 1217 chunks: &self.storage, 1218 cur_chunk: 0, 1219 cur_bit: 0, 1220 } 1221 } 1222 1223 /// Returns whether this set contains at least every longhand that `other` 1224 /// also contains. 1225 pub fn contains_all(&self, other: &Self) -> bool { 1226 for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { 1227 if (*self_cell & *other_cell) != *other_cell { 1228 return false; 1229 } 1230 } 1231 true 1232 } 1233 1234 /// Returns whether this set contains any longhand that `other` also contains. 1235 pub fn contains_any(&self, other: &Self) -> bool { 1236 for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { 1237 if (*self_cell & *other_cell) != 0 { 1238 return true; 1239 } 1240 } 1241 false 1242 } 1243 1244 /// Remove all the given properties from the set. 1245 #[inline] 1246 pub fn remove_all(&mut self, other: &Self) { 1247 for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) { 1248 *self_cell &= !*other_cell; 1249 } 1250 } 1251 1252 /// Return whether the given property is in the set 1253 #[inline] 1254 pub fn contains(&self, id: LonghandId) -> bool { 1255 let bit = id as usize; 1256 (self.storage[bit / 32] & (1 << (bit % 32))) != 0 1257 } 1258 1259 /// Return whether this set contains any reset longhand. 1260 #[inline] 1261 pub fn contains_any_reset(&self) -> bool { 1262 self.contains_any(Self::reset()) 1263 } 1264 1265 /// Add the given property to the set 1266 #[inline] 1267 pub fn insert(&mut self, id: LonghandId) { 1268 let bit = id as usize; 1269 self.storage[bit / 32] |= 1 << (bit % 32); 1270 } 1271 1272 /// Remove the given property from the set 1273 #[inline] 1274 pub fn remove(&mut self, id: LonghandId) { 1275 let bit = id as usize; 1276 self.storage[bit / 32] &= !(1 << (bit % 32)); 1277 } 1278 1279 /// Clear all bits 1280 #[inline] 1281 pub fn clear(&mut self) { 1282 for cell in &mut self.storage { 1283 *cell = 0 1284 } 1285 } 1286 1287 /// Returns whether the set is empty. 1288 #[inline] 1289 pub fn is_empty(&self) -> bool { 1290 self.storage.iter().all(|c| *c == 0) 1291 } 1292 } 1293 1294 /// An iterator over a set of longhand ids. 1295 pub struct LonghandIdSetIterator<'a> { 1296 chunks: &'a [u32], 1297 cur_chunk: u32, 1298 cur_bit: u32, // [0..31], note that zero means the end-most bit 1299 } 1300 1301 impl<'a> Iterator for LonghandIdSetIterator<'a> { 1302 type Item = LonghandId; 1303 1304 fn next(&mut self) -> Option<Self::Item> { 1305 loop { 1306 debug_assert!(self.cur_bit < 32); 1307 let cur_chunk = self.cur_chunk; 1308 let cur_bit = self.cur_bit; 1309 let chunk = *self.chunks.get(cur_chunk as usize)?; 1310 let next_bit = (chunk >> cur_bit).trailing_zeros(); 1311 if next_bit == 32 { 1312 // Totally empty chunk, skip it. 1313 self.cur_bit = 0; 1314 self.cur_chunk += 1; 1315 continue; 1316 } 1317 debug_assert!(cur_bit + next_bit < 32); 1318 let longhand_id = cur_chunk * 32 + cur_bit + next_bit; 1319 debug_assert!(longhand_id as usize <= property_counts::LONGHANDS); 1320 let id: LonghandId = unsafe { mem::transmute(longhand_id as u16) }; 1321 self.cur_bit += next_bit + 1; 1322 if self.cur_bit == 32 { 1323 self.cur_bit = 0; 1324 self.cur_chunk += 1; 1325 } 1326 return Some(id); 1327 } 1328 } 1329 } 1330 1331 /// An ArrayVec of subproperties, contains space for the longest shorthand except all. 1332 pub type SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>; 1333 1334 /// A stack-allocated vector of `PropertyDeclaration` 1335 /// large enough to parse one CSS `key: value` declaration. 1336 /// (Shorthands expand to multiple `PropertyDeclaration`s.) 1337 #[derive(Default)] 1338 pub struct SourcePropertyDeclaration { 1339 /// The storage for the actual declarations (except for all). 1340 pub declarations: SubpropertiesVec<PropertyDeclaration>, 1341 /// Stored separately to keep SubpropertiesVec smaller. 1342 pub all_shorthand: AllShorthand, 1343 } 1344 1345 // This is huge, but we allocate it on the stack and then never move it, 1346 // we only pass `&mut SourcePropertyDeclaration` references around. 1347 #[cfg(feature = "gecko")] 1348 size_of_test!(SourcePropertyDeclaration, 632); 1349 #[cfg(feature = "servo")] 1350 size_of_test!(SourcePropertyDeclaration, 568); 1351 1352 impl SourcePropertyDeclaration { 1353 /// Create one with a single PropertyDeclaration. 1354 #[inline] 1355 pub fn with_one(decl: PropertyDeclaration) -> Self { 1356 let mut result = Self::default(); 1357 result.declarations.push(decl); 1358 result 1359 } 1360 1361 /// Similar to Vec::drain: leaves this empty when the return value is dropped. 1362 pub fn drain(&mut self) -> SourcePropertyDeclarationDrain<'_> { 1363 SourcePropertyDeclarationDrain { 1364 declarations: self.declarations.drain(..), 1365 all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet), 1366 } 1367 } 1368 1369 /// Reset to initial state 1370 pub fn clear(&mut self) { 1371 self.declarations.clear(); 1372 self.all_shorthand = AllShorthand::NotSet; 1373 } 1374 1375 /// Whether we're empty. 1376 pub fn is_empty(&self) -> bool { 1377 self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet) 1378 } 1379 1380 /// Push a single declaration. 1381 pub fn push(&mut self, declaration: PropertyDeclaration) { 1382 let _result = self.declarations.try_push(declaration); 1383 debug_assert!(_result.is_ok()); 1384 } 1385 } 1386 1387 /// Return type of SourcePropertyDeclaration::drain 1388 pub struct SourcePropertyDeclarationDrain<'a> { 1389 /// A drain over the non-all declarations. 1390 pub declarations: 1391 ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>, 1392 /// The all shorthand that was set. 1393 pub all_shorthand: AllShorthand, 1394 } 1395 1396 /// An unparsed property value that contains `var()` functions. 1397 #[derive(Debug, Eq, PartialEq, ToShmem)] 1398 pub struct UnparsedValue { 1399 /// The variable value, references and so on. 1400 pub(super) variable_value: custom_properties::VariableValue, 1401 /// The shorthand this came from. 1402 from_shorthand: Option<ShorthandId>, 1403 } 1404 1405 impl ToCss for UnparsedValue { 1406 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1407 where 1408 W: Write, 1409 { 1410 // https://drafts.csswg.org/css-variables/#variables-in-shorthands 1411 if self.from_shorthand.is_none() { 1412 self.variable_value.to_css(dest)?; 1413 } 1414 Ok(()) 1415 } 1416 } 1417 1418 /// A simple cache for properties that come from a shorthand and have variable 1419 /// references. 1420 /// 1421 /// This cache works because of the fact that you can't have competing values 1422 /// for a given longhand coming from the same shorthand (but note that this is 1423 /// why the shorthand needs to be part of the cache key). 1424 pub type ShorthandsWithPropertyReferencesCache = 1425 FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>; 1426 1427 impl UnparsedValue { 1428 fn substitute_variables<'cache>( 1429 &self, 1430 longhand_id: LonghandId, 1431 custom_properties: &ComputedCustomProperties, 1432 stylist: &Stylist, 1433 computed_context: &computed::Context, 1434 shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, 1435 attr_provider: &dyn AttributeProvider, 1436 ) -> Cow<'cache, PropertyDeclaration> { 1437 let invalid_at_computed_value_time = || { 1438 let keyword = if longhand_id.inherited() { 1439 CSSWideKeyword::Inherit 1440 } else { 1441 CSSWideKeyword::Initial 1442 }; 1443 Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) 1444 }; 1445 1446 if computed_context 1447 .builder 1448 .invalid_non_custom_properties 1449 .contains(longhand_id) 1450 { 1451 return invalid_at_computed_value_time(); 1452 } 1453 1454 if let Some(shorthand_id) = self.from_shorthand { 1455 let key = (shorthand_id, longhand_id); 1456 if shorthand_cache.contains_key(&key) { 1457 // FIXME: This double lookup should be avoidable, but rustc 1458 // doesn't like that, see: 1459 // 1460 // https://github.com/rust-lang/rust/issues/82146 1461 return Cow::Borrowed(&shorthand_cache[&key]); 1462 } 1463 } 1464 1465 let css = match custom_properties::substitute( 1466 &self.variable_value, 1467 custom_properties, 1468 stylist, 1469 computed_context, 1470 attr_provider, 1471 ) { 1472 Ok(css) => css, 1473 Err(..) => return invalid_at_computed_value_time(), 1474 }; 1475 1476 // As of this writing, only the base URL is used for property 1477 // values. 1478 // 1479 // NOTE(emilio): we intentionally pase `None` as the rule type here. 1480 // If something starts depending on it, it's probably a bug, since 1481 // it'd change how values are parsed depending on whether we're in a 1482 // @keyframes rule or not, for example... So think twice about 1483 // whether you want to do this! 1484 // 1485 // FIXME(emilio): ParsingMode is slightly fishy... 1486 let context = ParserContext::new( 1487 Origin::Author, 1488 &self.variable_value.url_data, 1489 None, 1490 ParsingMode::DEFAULT, 1491 computed_context.quirks_mode, 1492 /* namespaces = */ Default::default(), 1493 None, 1494 None, 1495 ); 1496 1497 let mut input = ParserInput::new(&css); 1498 let mut input = Parser::new(&mut input); 1499 input.skip_whitespace(); 1500 1501 if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { 1502 return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)); 1503 } 1504 1505 let shorthand = match self.from_shorthand { 1506 None => { 1507 return match input.parse_entirely(|input| longhand_id.parse_value(&context, input)) 1508 { 1509 Ok(decl) => Cow::Owned(decl), 1510 Err(..) => invalid_at_computed_value_time(), 1511 } 1512 }, 1513 Some(shorthand) => shorthand, 1514 }; 1515 1516 let mut decls = SourcePropertyDeclaration::default(); 1517 // parse_into takes care of doing `parse_entirely` for us. 1518 if shorthand 1519 .parse_into(&mut decls, &context, &mut input) 1520 .is_err() 1521 { 1522 return invalid_at_computed_value_time(); 1523 } 1524 1525 for declaration in decls.declarations.drain(..) { 1526 let longhand = declaration.id().as_longhand().unwrap(); 1527 if longhand.is_logical() { 1528 let writing_mode = computed_context.builder.writing_mode; 1529 shorthand_cache.insert( 1530 (shorthand, longhand.to_physical(writing_mode)), 1531 declaration.clone(), 1532 ); 1533 } 1534 shorthand_cache.insert((shorthand, longhand), declaration); 1535 } 1536 1537 let key = (shorthand, longhand_id); 1538 match shorthand_cache.get(&key) { 1539 Some(decl) => Cow::Borrowed(decl), 1540 // NOTE: Under normal circumstances we should always have a value, but when prefs 1541 // change we might hit this case. Consider something like `animation-timeline`, which 1542 // is a conditionally-enabled longhand of `animation`: 1543 // 1544 // If we have a sheet with `animation: var(--foo)`, and the `animation-timeline` pref 1545 // enabled, then that expands to an `animation-timeline` declaration at parse time. 1546 // 1547 // If the user disables the pref and, some time later, we get here wanting to compute 1548 // `animation-timeline`, parse_into won't generate any declaration for it anymore, so 1549 // we haven't inserted in the cache. Computing to invalid / initial seems like the most 1550 // sensible thing to do here. 1551 None => invalid_at_computed_value_time(), 1552 } 1553 } 1554 } 1555 /// A parsed all-shorthand value. 1556 pub enum AllShorthand { 1557 /// Not present. 1558 NotSet, 1559 /// A CSS-wide keyword. 1560 CSSWideKeyword(CSSWideKeyword), 1561 /// An all shorthand with var() references that we can't resolve right now. 1562 WithVariables(Arc<UnparsedValue>), 1563 } 1564 1565 impl Default for AllShorthand { 1566 fn default() -> Self { 1567 Self::NotSet 1568 } 1569 } 1570 1571 impl AllShorthand { 1572 /// Iterates property declarations from the given all shorthand value. 1573 #[inline] 1574 pub fn declarations(&self) -> AllShorthandDeclarationIterator<'_> { 1575 AllShorthandDeclarationIterator { 1576 all_shorthand: self, 1577 longhands: ShorthandId::All.longhands(), 1578 } 1579 } 1580 } 1581 1582 /// An iterator over the all shorthand's shorthand declarations. 1583 pub struct AllShorthandDeclarationIterator<'a> { 1584 all_shorthand: &'a AllShorthand, 1585 longhands: NonCustomPropertyIterator<LonghandId>, 1586 } 1587 1588 impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { 1589 type Item = PropertyDeclaration; 1590 1591 #[inline] 1592 fn next(&mut self) -> Option<Self::Item> { 1593 match *self.all_shorthand { 1594 AllShorthand::NotSet => None, 1595 AllShorthand::CSSWideKeyword(ref keyword) => Some( 1596 PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword), 1597 ), 1598 AllShorthand::WithVariables(ref unparsed) => { 1599 Some(PropertyDeclaration::WithVariables(VariableDeclaration { 1600 id: self.longhands.next()?, 1601 value: unparsed.clone(), 1602 })) 1603 }, 1604 } 1605 } 1606 } 1607 1608 /// An iterator over all the property ids that are enabled for a given 1609 /// shorthand, if that shorthand is enabled for all content too. 1610 pub struct NonCustomPropertyIterator<Item: 'static> { 1611 filter: bool, 1612 iter: std::slice::Iter<'static, Item>, 1613 } 1614 1615 impl<Item> Iterator for NonCustomPropertyIterator<Item> 1616 where 1617 Item: 'static + Copy + Into<NonCustomPropertyId>, 1618 { 1619 type Item = Item; 1620 1621 fn next(&mut self) -> Option<Self::Item> { 1622 loop { 1623 let id = *self.iter.next()?; 1624 if !self.filter || id.into().enabled_for_all_content() { 1625 return Some(id); 1626 } 1627 } 1628 } 1629 }