mod.rs (19312B)
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 //! Common [values][values] used in CSS. 6 //! 7 //! [values]: https://drafts.csswg.org/css-values/ 8 9 #![deny(missing_docs)] 10 11 use crate::derives::*; 12 use crate::parser::{Parse, ParserContext}; 13 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; 14 use crate::Atom; 15 pub use cssparser::{serialize_identifier, serialize_name, CowRcStr, Parser}; 16 pub use cssparser::{SourceLocation, Token}; 17 use precomputed_hash::PrecomputedHash; 18 use selectors::parser::SelectorParseErrorKind; 19 use std::fmt::{self, Debug, Write}; 20 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 21 use to_shmem::impl_trivial_to_shmem; 22 23 #[cfg(feature = "gecko")] 24 pub use crate::gecko::url::CssUrl; 25 #[cfg(feature = "servo")] 26 pub use crate::servo::url::CssUrl; 27 28 pub mod animated; 29 pub mod computed; 30 pub mod distance; 31 pub mod generics; 32 pub mod resolved; 33 pub mod specified; 34 35 /// A CSS float value. 36 pub type CSSFloat = f32; 37 38 /// Normalizes a float value to zero after a set of operations that might turn 39 /// it into NaN. 40 #[inline] 41 pub fn normalize(v: CSSFloat) -> CSSFloat { 42 if v.is_nan() { 43 0.0 44 } else { 45 v 46 } 47 } 48 49 /// A CSS integer value. 50 pub type CSSInteger = i32; 51 52 /// Serialize an identifier which is represented as an atom. 53 #[cfg(feature = "gecko")] 54 pub fn serialize_atom_identifier<W>(ident: &Atom, dest: &mut W) -> fmt::Result 55 where 56 W: Write, 57 { 58 ident.with_str(|s| serialize_identifier(s, dest)) 59 } 60 61 /// Serialize an identifier which is represented as an atom. 62 #[cfg(feature = "servo")] 63 pub fn serialize_atom_identifier<Static, W>( 64 ident: &::string_cache::Atom<Static>, 65 dest: &mut W, 66 ) -> fmt::Result 67 where 68 Static: string_cache::StaticAtomSet, 69 W: Write, 70 { 71 serialize_identifier(&ident, dest) 72 } 73 74 /// Serialize a name which is represented as an Atom. 75 #[cfg(feature = "gecko")] 76 pub fn serialize_atom_name<W>(ident: &Atom, dest: &mut W) -> fmt::Result 77 where 78 W: Write, 79 { 80 ident.with_str(|s| serialize_name(s, dest)) 81 } 82 83 /// Serialize a name which is represented as an Atom. 84 #[cfg(feature = "servo")] 85 pub fn serialize_atom_name<Static, W>( 86 ident: &::string_cache::Atom<Static>, 87 dest: &mut W, 88 ) -> fmt::Result 89 where 90 Static: string_cache::StaticAtomSet, 91 W: Write, 92 { 93 serialize_name(&ident, dest) 94 } 95 96 /// Serialize a number with calc, and NaN/infinity handling (if enabled) 97 pub fn serialize_number<W>(v: f32, was_calc: bool, dest: &mut CssWriter<W>) -> fmt::Result 98 where 99 W: Write, 100 { 101 serialize_specified_dimension(v, "", was_calc, dest) 102 } 103 104 /// Serialize a specified dimension with unit, calc, and NaN/infinity handling (if enabled) 105 pub fn serialize_specified_dimension<W>( 106 v: f32, 107 unit: &str, 108 was_calc: bool, 109 dest: &mut CssWriter<W>, 110 ) -> fmt::Result 111 where 112 W: Write, 113 { 114 if was_calc { 115 dest.write_str("calc(")?; 116 } 117 118 if !v.is_finite() { 119 // https://drafts.csswg.org/css-values/#calc-error-constants: 120 // "While not technically numbers, these keywords act as numeric values, 121 // similar to e and pi. Thus to get an infinite length, for example, 122 // requires an expression like calc(infinity * 1px)." 123 124 if v.is_nan() { 125 dest.write_str("NaN")?; 126 } else if v == f32::INFINITY { 127 dest.write_str("infinity")?; 128 } else if v == f32::NEG_INFINITY { 129 dest.write_str("-infinity")?; 130 } 131 132 if !unit.is_empty() { 133 dest.write_str(" * 1")?; 134 } 135 } else { 136 v.to_css(dest)?; 137 } 138 139 dest.write_str(unit)?; 140 141 if was_calc { 142 dest.write_char(')')?; 143 } 144 Ok(()) 145 } 146 147 /// A CSS string stored as an `Atom`. 148 #[repr(transparent)] 149 #[derive( 150 Clone, 151 Debug, 152 Default, 153 Deref, 154 Eq, 155 Hash, 156 MallocSizeOf, 157 PartialEq, 158 SpecifiedValueInfo, 159 ToComputedValue, 160 ToResolvedValue, 161 ToShmem, 162 )] 163 pub struct AtomString(pub Atom); 164 165 #[cfg(feature = "servo")] 166 impl AsRef<str> for AtomString { 167 fn as_ref(&self) -> &str { 168 &*self.0 169 } 170 } 171 172 impl Parse for AtomString { 173 fn parse<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { 174 Ok(Self(Atom::from(input.expect_string()?.as_ref()))) 175 } 176 } 177 178 impl cssparser::ToCss for AtomString { 179 fn to_css<W>(&self, dest: &mut W) -> fmt::Result 180 where 181 W: Write, 182 { 183 // Wrap in quotes to form a string literal 184 dest.write_char('"')?; 185 #[cfg(feature = "servo")] 186 { 187 cssparser::CssStringWriter::new(dest).write_str(self.as_ref())?; 188 } 189 #[cfg(feature = "gecko")] 190 { 191 self.0 192 .with_str(|s| cssparser::CssStringWriter::new(dest).write_str(s))?; 193 } 194 dest.write_char('"') 195 } 196 } 197 198 impl style_traits::ToCss for AtomString { 199 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 200 where 201 W: Write, 202 { 203 cssparser::ToCss::to_css(self, dest) 204 } 205 } 206 207 impl PrecomputedHash for AtomString { 208 #[inline] 209 fn precomputed_hash(&self) -> u32 { 210 self.0.precomputed_hash() 211 } 212 } 213 214 impl<'a> From<&'a str> for AtomString { 215 #[inline] 216 fn from(string: &str) -> Self { 217 Self(Atom::from(string)) 218 } 219 } 220 221 /// A generic CSS `<ident>` stored as an `Atom`. 222 #[cfg(feature = "servo")] 223 #[repr(transparent)] 224 #[derive(Deref)] 225 pub struct GenericAtomIdent<Set>(pub string_cache::Atom<Set>) 226 where 227 Set: string_cache::StaticAtomSet; 228 229 /// A generic CSS `<ident>` stored as an `Atom`, for the default atom set. 230 #[cfg(feature = "servo")] 231 pub type AtomIdent = GenericAtomIdent<stylo_atoms::AtomStaticSet>; 232 233 #[cfg(feature = "servo")] 234 impl<Set: string_cache::StaticAtomSet> style_traits::SpecifiedValueInfo for GenericAtomIdent<Set> {} 235 236 #[cfg(feature = "servo")] 237 impl<Set: string_cache::StaticAtomSet> Default for GenericAtomIdent<Set> { 238 fn default() -> Self { 239 Self(string_cache::Atom::default()) 240 } 241 } 242 243 #[cfg(feature = "servo")] 244 impl<Set: string_cache::StaticAtomSet> std::fmt::Debug for GenericAtomIdent<Set> { 245 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 246 self.0.fmt(f) 247 } 248 } 249 250 #[cfg(feature = "servo")] 251 impl<Set: string_cache::StaticAtomSet> std::hash::Hash for GenericAtomIdent<Set> { 252 fn hash<H: std::hash::Hasher>(&self, state: &mut H) { 253 self.0.hash(state) 254 } 255 } 256 257 #[cfg(feature = "servo")] 258 impl<Set: string_cache::StaticAtomSet> Eq for GenericAtomIdent<Set> {} 259 260 #[cfg(feature = "servo")] 261 impl<Set: string_cache::StaticAtomSet> PartialEq for GenericAtomIdent<Set> { 262 fn eq(&self, other: &Self) -> bool { 263 self.0 == other.0 264 } 265 } 266 267 #[cfg(feature = "servo")] 268 impl<Set: string_cache::StaticAtomSet> Clone for GenericAtomIdent<Set> { 269 fn clone(&self) -> Self { 270 Self(self.0.clone()) 271 } 272 } 273 274 #[cfg(feature = "servo")] 275 impl<Set: string_cache::StaticAtomSet> to_shmem::ToShmem for GenericAtomIdent<Set> { 276 fn to_shmem(&self, builder: &mut to_shmem::SharedMemoryBuilder) -> to_shmem::Result<Self> { 277 use std::mem::ManuallyDrop; 278 279 let atom = self.0.to_shmem(builder)?; 280 Ok(ManuallyDrop::new(Self(ManuallyDrop::into_inner(atom)))) 281 } 282 } 283 284 #[cfg(feature = "servo")] 285 impl<Set: string_cache::StaticAtomSet> malloc_size_of::MallocSizeOf for GenericAtomIdent<Set> { 286 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { 287 self.0.size_of(ops) 288 } 289 } 290 291 #[cfg(feature = "servo")] 292 impl<Set: string_cache::StaticAtomSet> cssparser::ToCss for GenericAtomIdent<Set> { 293 fn to_css<W>(&self, dest: &mut W) -> fmt::Result 294 where 295 W: Write, 296 { 297 serialize_atom_identifier(&self.0, dest) 298 } 299 } 300 301 #[cfg(feature = "servo")] 302 impl<Set: string_cache::StaticAtomSet> style_traits::ToCss for GenericAtomIdent<Set> { 303 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 304 where 305 W: Write, 306 { 307 serialize_atom_identifier(&self.0, dest) 308 } 309 } 310 311 #[cfg(feature = "servo")] 312 impl<Set: string_cache::StaticAtomSet> PrecomputedHash for GenericAtomIdent<Set> { 313 #[inline] 314 fn precomputed_hash(&self) -> u32 { 315 self.0.precomputed_hash() 316 } 317 } 318 319 #[cfg(feature = "servo")] 320 impl<'a, Set: string_cache::StaticAtomSet> From<&'a str> for GenericAtomIdent<Set> { 321 #[inline] 322 fn from(string: &str) -> Self { 323 Self(string_cache::Atom::from(string)) 324 } 325 } 326 327 #[cfg(feature = "servo")] 328 impl<Set: string_cache::StaticAtomSet> std::borrow::Borrow<string_cache::Atom<Set>> 329 for GenericAtomIdent<Set> 330 { 331 #[inline] 332 fn borrow(&self) -> &string_cache::Atom<Set> { 333 &self.0 334 } 335 } 336 337 #[cfg(feature = "servo")] 338 impl<Set: string_cache::StaticAtomSet> GenericAtomIdent<Set> { 339 /// Constructs a new GenericAtomIdent. 340 #[inline] 341 pub fn new(atom: string_cache::Atom<Set>) -> Self { 342 Self(atom) 343 } 344 345 /// Cast an atom ref to an AtomIdent ref. 346 #[inline] 347 pub fn cast<'a>(atom: &'a string_cache::Atom<Set>) -> &'a Self { 348 let ptr = atom as *const _ as *const Self; 349 // safety: repr(transparent) 350 unsafe { &*ptr } 351 } 352 } 353 354 /// A CSS `<ident>` stored as an `Atom`. 355 #[cfg(feature = "gecko")] 356 #[repr(transparent)] 357 #[derive( 358 Clone, Debug, Default, Deref, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, 359 )] 360 pub struct AtomIdent(pub Atom); 361 362 #[cfg(feature = "gecko")] 363 impl cssparser::ToCss for AtomIdent { 364 fn to_css<W>(&self, dest: &mut W) -> fmt::Result 365 where 366 W: Write, 367 { 368 serialize_atom_identifier(&self.0, dest) 369 } 370 } 371 372 #[cfg(feature = "gecko")] 373 impl style_traits::ToCss for AtomIdent { 374 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 375 where 376 W: Write, 377 { 378 cssparser::ToCss::to_css(self, dest) 379 } 380 } 381 382 #[cfg(feature = "gecko")] 383 impl PrecomputedHash for AtomIdent { 384 #[inline] 385 fn precomputed_hash(&self) -> u32 { 386 self.0.precomputed_hash() 387 } 388 } 389 390 #[cfg(feature = "gecko")] 391 impl<'a> From<&'a str> for AtomIdent { 392 #[inline] 393 fn from(string: &str) -> Self { 394 Self(Atom::from(string)) 395 } 396 } 397 398 #[cfg(feature = "gecko")] 399 impl AtomIdent { 400 /// Constructs a new AtomIdent. 401 #[inline] 402 pub fn new(atom: Atom) -> Self { 403 Self(atom) 404 } 405 406 /// Like `Atom::with` but for `AtomIdent`. 407 pub unsafe fn with<F, R>(ptr: *const crate::gecko_bindings::structs::nsAtom, callback: F) -> R 408 where 409 F: FnOnce(&Self) -> R, 410 { 411 Atom::with(ptr, |atom: &Atom| { 412 // safety: repr(transparent) 413 let atom = atom as *const Atom as *const AtomIdent; 414 callback(&*atom) 415 }) 416 } 417 418 /// Cast an atom ref to an AtomIdent ref. 419 #[inline] 420 pub fn cast<'a>(atom: &'a Atom) -> &'a Self { 421 let ptr = atom as *const _ as *const Self; 422 // safety: repr(transparent) 423 unsafe { &*ptr } 424 } 425 } 426 427 #[cfg(feature = "gecko")] 428 impl std::borrow::Borrow<crate::gecko_string_cache::WeakAtom> for AtomIdent { 429 #[inline] 430 fn borrow(&self) -> &crate::gecko_string_cache::WeakAtom { 431 self.0.borrow() 432 } 433 } 434 435 /// Serialize a value into percentage. 436 pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result 437 where 438 W: Write, 439 { 440 serialize_specified_dimension(value * 100., "%", /* was_calc = */ false, dest) 441 } 442 443 /// Serialize a value into normalized (no NaN/inf serialization) percentage. 444 pub fn serialize_normalized_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result 445 where 446 W: Write, 447 { 448 (value * 100.).to_css(dest)?; 449 dest.write_char('%') 450 } 451 452 /// Convenience void type to disable some properties and values through types. 453 #[cfg_attr(feature = "servo", derive(Deserialize, MallocSizeOf, Serialize))] 454 #[derive( 455 Clone, 456 Copy, 457 Debug, 458 PartialEq, 459 SpecifiedValueInfo, 460 ToAnimatedValue, 461 ToComputedValue, 462 ToCss, 463 ToResolvedValue, 464 )] 465 pub enum Impossible {} 466 467 // FIXME(nox): This should be derived but the derive code cannot cope 468 // with uninhabited enums. 469 impl ComputeSquaredDistance for Impossible { 470 #[inline] 471 fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> { 472 match *self {} 473 } 474 } 475 476 impl_trivial_to_shmem!(Impossible); 477 478 impl Parse for Impossible { 479 fn parse<'i, 't>( 480 _context: &ParserContext, 481 input: &mut Parser<'i, 't>, 482 ) -> Result<Self, ParseError<'i>> { 483 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 484 } 485 } 486 487 /// A struct representing one of two kinds of values. 488 #[derive( 489 Animate, 490 Clone, 491 ComputeSquaredDistance, 492 Copy, 493 MallocSizeOf, 494 PartialEq, 495 Parse, 496 SpecifiedValueInfo, 497 ToAnimatedValue, 498 ToAnimatedZero, 499 ToComputedValue, 500 ToCss, 501 ToResolvedValue, 502 ToShmem, 503 )] 504 pub enum Either<A, B> { 505 /// The first value. 506 First(A), 507 /// The second kind of value. 508 Second(B), 509 } 510 511 impl<A: Debug, B: Debug> Debug for Either<A, B> { 512 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 513 match *self { 514 Either::First(ref v) => v.fmt(f), 515 Either::Second(ref v) => v.fmt(f), 516 } 517 } 518 } 519 520 /// <https://drafts.csswg.org/css-values-4/#custom-idents> 521 #[derive( 522 Clone, 523 Debug, 524 Default, 525 Eq, 526 Hash, 527 MallocSizeOf, 528 PartialEq, 529 SpecifiedValueInfo, 530 ToAnimatedValue, 531 ToComputedValue, 532 ToResolvedValue, 533 ToShmem, 534 )] 535 #[repr(C)] 536 pub struct CustomIdent(pub Atom); 537 538 impl CustomIdent { 539 /// Parse a <custom-ident> 540 /// 541 /// TODO(zrhoffman, bug 1844501): Use CustomIdent::parse in more places instead of 542 /// CustomIdent::from_ident. 543 pub fn parse<'i, 't>( 544 input: &mut Parser<'i, 't>, 545 invalid: &[&str], 546 ) -> Result<Self, ParseError<'i>> { 547 let location = input.current_source_location(); 548 let ident = input.expect_ident()?; 549 CustomIdent::from_ident(location, ident, invalid) 550 } 551 552 /// Parse an already-tokenizer identifier 553 pub fn from_ident<'i>( 554 location: SourceLocation, 555 ident: &CowRcStr<'i>, 556 excluding: &[&str], 557 ) -> Result<Self, ParseError<'i>> { 558 if !Self::is_valid(ident, excluding) { 559 return Err( 560 location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) 561 ); 562 } 563 if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { 564 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 565 } else { 566 Ok(CustomIdent(Atom::from(ident.as_ref()))) 567 } 568 } 569 570 fn is_valid(ident: &str, excluding: &[&str]) -> bool { 571 use crate::properties::CSSWideKeyword; 572 // https://drafts.csswg.org/css-values-4/#custom-idents: 573 // 574 // The CSS-wide keywords are not valid <custom-ident>s. The default 575 // keyword is reserved and is also not a valid <custom-ident>. 576 if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { 577 return false; 578 } 579 580 // https://drafts.csswg.org/css-values-4/#custom-idents: 581 // 582 // Excluded keywords are excluded in all ASCII case permutations. 583 !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) 584 } 585 } 586 587 impl ToCss for CustomIdent { 588 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 589 where 590 W: Write, 591 { 592 serialize_atom_identifier(&self.0, dest) 593 } 594 } 595 596 /// <https://www.w3.org/TR/css-values-4/#dashed-idents> 597 /// This is simply an Atom, but will only parse if the identifier starts with "--". 598 #[repr(transparent)] 599 #[derive( 600 Clone, 601 Debug, 602 Eq, 603 Hash, 604 MallocSizeOf, 605 PartialEq, 606 SpecifiedValueInfo, 607 ToAnimatedValue, 608 ToComputedValue, 609 ToResolvedValue, 610 ToShmem, 611 Serialize, 612 Deserialize, 613 )] 614 pub struct DashedIdent(pub Atom); 615 616 impl DashedIdent { 617 /// Parse an already-tokenizer identifier 618 pub fn from_ident<'i>( 619 location: SourceLocation, 620 ident: &CowRcStr<'i>, 621 ) -> Result<Self, ParseError<'i>> { 622 if !ident.starts_with("--") { 623 return Err( 624 location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) 625 ); 626 } 627 Ok(Self(Atom::from(ident.as_ref()))) 628 } 629 630 /// Special value for internal use. Useful where we can't use Option<>. 631 pub fn empty() -> Self { 632 Self(atom!("")) 633 } 634 635 /// Check for special internal value. 636 pub fn is_empty(&self) -> bool { 637 self.0 == atom!("") 638 } 639 } 640 641 impl Parse for DashedIdent { 642 fn parse<'i, 't>( 643 _: &ParserContext, 644 input: &mut Parser<'i, 't>, 645 ) -> Result<Self, ParseError<'i>> { 646 let location = input.current_source_location(); 647 let ident = input.expect_ident()?; 648 Self::from_ident(location, ident) 649 } 650 } 651 652 impl ToCss for DashedIdent { 653 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 654 where 655 W: Write, 656 { 657 serialize_atom_identifier(&self.0, dest) 658 } 659 } 660 661 /// The <keyframes-name>. 662 /// 663 /// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name> 664 /// 665 /// We use a single atom for this. Empty atom represents `none` animation. 666 #[repr(transparent)] 667 #[derive( 668 Clone, 669 Debug, 670 Eq, 671 Hash, 672 PartialEq, 673 MallocSizeOf, 674 SpecifiedValueInfo, 675 ToComputedValue, 676 ToResolvedValue, 677 ToShmem, 678 )] 679 pub struct KeyframesName(Atom); 680 681 impl KeyframesName { 682 /// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name> 683 pub fn from_ident(value: &str) -> Self { 684 Self(Atom::from(value)) 685 } 686 687 /// Returns the `none` value. 688 pub fn none() -> Self { 689 Self(atom!("")) 690 } 691 692 /// Returns whether this is the special `none` value. 693 pub fn is_none(&self) -> bool { 694 self.0 == atom!("") 695 } 696 697 /// Create a new KeyframesName from Atom. 698 #[cfg(feature = "gecko")] 699 pub fn from_atom(atom: Atom) -> Self { 700 Self(atom) 701 } 702 703 /// The name as an Atom 704 pub fn as_atom(&self) -> &Atom { 705 &self.0 706 } 707 } 708 709 impl Parse for KeyframesName { 710 fn parse<'i, 't>( 711 _: &ParserContext, 712 input: &mut Parser<'i, 't>, 713 ) -> Result<Self, ParseError<'i>> { 714 let location = input.current_source_location(); 715 Ok(match *input.next()? { 716 Token::Ident(ref s) => Self(CustomIdent::from_ident(location, s, &["none"])?.0), 717 Token::QuotedString(ref s) => Self(Atom::from(s.as_ref())), 718 ref t => return Err(location.new_unexpected_token_error(t.clone())), 719 }) 720 } 721 } 722 723 impl ToCss for KeyframesName { 724 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 725 where 726 W: Write, 727 { 728 if self.is_none() { 729 return dest.write_str("none"); 730 } 731 732 fn serialize<W: Write>(string: &str, dest: &mut CssWriter<W>) -> fmt::Result { 733 if CustomIdent::is_valid(string, &["none"]) { 734 serialize_identifier(string, dest) 735 } else { 736 string.to_css(dest) 737 } 738 } 739 740 #[cfg(feature = "gecko")] 741 return self.0.with_str(|s| serialize(s, dest)); 742 743 #[cfg(feature = "servo")] 744 return serialize(self.0.as_ref(), dest); 745 } 746 }