position.rs (67564B)
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 //! CSS handling for the specified value of 6 //! [`position`][position]s 7 //! 8 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position 9 10 use crate::derives::*; 11 use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode}; 12 use crate::parser::{Parse, ParserContext}; 13 use crate::selector_map::PrecomputedHashMap; 14 use crate::str::HTML_SPACE_CHARACTERS; 15 use crate::values::computed::LengthPercentage as ComputedLengthPercentage; 16 use crate::values::computed::{Context, Percentage, ToComputedValue}; 17 use crate::values::generics::length::GenericAnchorSizeFunction; 18 use crate::values::generics::position::Position as GenericPosition; 19 use crate::values::generics::position::PositionComponent as GenericPositionComponent; 20 use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; 21 use crate::values::generics::position::ZIndex as GenericZIndex; 22 use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide}; 23 use crate::values::generics::position::{GenericAnchorFunction, GenericInset}; 24 use crate::values::specified; 25 use crate::values::specified::align::AlignFlags; 26 use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber}; 27 use crate::values::DashedIdent; 28 use crate::{Atom, Zero}; 29 use cssparser::{match_ignore_ascii_case, Parser}; 30 use num_traits::FromPrimitive; 31 use selectors::parser::SelectorParseErrorKind; 32 use servo_arc::Arc; 33 use smallvec::{smallvec, SmallVec}; 34 use std::collections::hash_map::Entry; 35 use std::fmt::{self, Write}; 36 use style_traits::arc_slice::ArcSlice; 37 use style_traits::values::specified::AllowedNumericType; 38 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 39 use thin_vec::ThinVec; 40 41 /// The specified value of a CSS `<position>` 42 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>; 43 44 /// The specified value of an `auto | <position>`. 45 pub type PositionOrAuto = GenericPositionOrAuto<Position>; 46 47 /// The specified value of a horizontal position. 48 pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>; 49 50 /// The specified value of a vertical position. 51 pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>; 52 53 /// The specified value of a component of a CSS `<position>`. 54 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 55 pub enum PositionComponent<S> { 56 /// `center` 57 Center, 58 /// `<length-percentage>` 59 Length(LengthPercentage), 60 /// `<side> <length-percentage>?` 61 Side(S, Option<LengthPercentage>), 62 } 63 64 /// A keyword for the X direction. 65 #[derive( 66 Clone, 67 Copy, 68 Debug, 69 Eq, 70 Hash, 71 MallocSizeOf, 72 Parse, 73 PartialEq, 74 SpecifiedValueInfo, 75 ToComputedValue, 76 ToCss, 77 ToResolvedValue, 78 ToShmem, 79 )] 80 #[allow(missing_docs)] 81 #[repr(u8)] 82 pub enum HorizontalPositionKeyword { 83 Left, 84 Right, 85 } 86 87 /// A keyword for the Y direction. 88 #[derive( 89 Clone, 90 Copy, 91 Debug, 92 Eq, 93 Hash, 94 MallocSizeOf, 95 Parse, 96 PartialEq, 97 SpecifiedValueInfo, 98 ToComputedValue, 99 ToCss, 100 ToResolvedValue, 101 ToShmem, 102 )] 103 #[allow(missing_docs)] 104 #[repr(u8)] 105 pub enum VerticalPositionKeyword { 106 Top, 107 Bottom, 108 } 109 110 impl Parse for Position { 111 fn parse<'i, 't>( 112 context: &ParserContext, 113 input: &mut Parser<'i, 't>, 114 ) -> Result<Self, ParseError<'i>> { 115 let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?; 116 if position.is_three_value_syntax() { 117 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 118 } 119 Ok(position) 120 } 121 } 122 123 impl Position { 124 /// Parses a `<bg-position>`, with quirks. 125 pub fn parse_three_value_quirky<'i, 't>( 126 context: &ParserContext, 127 input: &mut Parser<'i, 't>, 128 allow_quirks: AllowQuirks, 129 ) -> Result<Self, ParseError<'i>> { 130 match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) { 131 Ok(x_pos @ PositionComponent::Center) => { 132 if let Ok(y_pos) = 133 input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) 134 { 135 return Ok(Self::new(x_pos, y_pos)); 136 } 137 let x_pos = input 138 .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) 139 .unwrap_or(x_pos); 140 let y_pos = PositionComponent::Center; 141 return Ok(Self::new(x_pos, y_pos)); 142 }, 143 Ok(PositionComponent::Side(x_keyword, lp)) => { 144 if input 145 .try_parse(|i| i.expect_ident_matching("center")) 146 .is_ok() 147 { 148 let x_pos = PositionComponent::Side(x_keyword, lp); 149 let y_pos = PositionComponent::Center; 150 return Ok(Self::new(x_pos, y_pos)); 151 } 152 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) { 153 let y_lp = input 154 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 155 .ok(); 156 let x_pos = PositionComponent::Side(x_keyword, lp); 157 let y_pos = PositionComponent::Side(y_keyword, y_lp); 158 return Ok(Self::new(x_pos, y_pos)); 159 } 160 let x_pos = PositionComponent::Side(x_keyword, None); 161 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length); 162 return Ok(Self::new(x_pos, y_pos)); 163 }, 164 Ok(x_pos @ PositionComponent::Length(_)) => { 165 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) { 166 let y_pos = PositionComponent::Side(y_keyword, None); 167 return Ok(Self::new(x_pos, y_pos)); 168 } 169 if let Ok(y_lp) = 170 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 171 { 172 let y_pos = PositionComponent::Length(y_lp); 173 return Ok(Self::new(x_pos, y_pos)); 174 } 175 let y_pos = PositionComponent::Center; 176 let _ = input.try_parse(|i| i.expect_ident_matching("center")); 177 return Ok(Self::new(x_pos, y_pos)); 178 }, 179 Err(_) => {}, 180 } 181 let y_keyword = VerticalPositionKeyword::parse(input)?; 182 let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| { 183 let y_lp = i 184 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 185 .ok(); 186 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) { 187 let x_lp = i 188 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 189 .ok(); 190 let x_pos = PositionComponent::Side(x_keyword, x_lp); 191 return Ok((y_lp, x_pos)); 192 }; 193 i.expect_ident_matching("center")?; 194 let x_pos = PositionComponent::Center; 195 Ok((y_lp, x_pos)) 196 }); 197 if let Ok((y_lp, x_pos)) = lp_and_x_pos { 198 let y_pos = PositionComponent::Side(y_keyword, y_lp); 199 return Ok(Self::new(x_pos, y_pos)); 200 } 201 let x_pos = PositionComponent::Center; 202 let y_pos = PositionComponent::Side(y_keyword, None); 203 Ok(Self::new(x_pos, y_pos)) 204 } 205 206 /// `center center` 207 #[inline] 208 pub fn center() -> Self { 209 Self::new(PositionComponent::Center, PositionComponent::Center) 210 } 211 212 /// Returns true if this uses a 3 value syntax. 213 #[inline] 214 fn is_three_value_syntax(&self) -> bool { 215 self.horizontal.component_count() != self.vertical.component_count() 216 } 217 } 218 219 impl ToCss for Position { 220 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 221 where 222 W: Write, 223 { 224 match (&self.horizontal, &self.vertical) { 225 ( 226 x_pos @ &PositionComponent::Side(_, Some(_)), 227 &PositionComponent::Length(ref y_lp), 228 ) => { 229 x_pos.to_css(dest)?; 230 dest.write_str(" top ")?; 231 y_lp.to_css(dest) 232 }, 233 ( 234 &PositionComponent::Length(ref x_lp), 235 y_pos @ &PositionComponent::Side(_, Some(_)), 236 ) => { 237 dest.write_str("left ")?; 238 x_lp.to_css(dest)?; 239 dest.write_char(' ')?; 240 y_pos.to_css(dest) 241 }, 242 (x_pos, y_pos) => { 243 x_pos.to_css(dest)?; 244 dest.write_char(' ')?; 245 y_pos.to_css(dest) 246 }, 247 } 248 } 249 } 250 251 impl<S: Parse> Parse for PositionComponent<S> { 252 fn parse<'i, 't>( 253 context: &ParserContext, 254 input: &mut Parser<'i, 't>, 255 ) -> Result<Self, ParseError<'i>> { 256 Self::parse_quirky(context, input, AllowQuirks::No) 257 } 258 } 259 260 impl<S: Parse> PositionComponent<S> { 261 /// Parses a component of a CSS position, with quirks. 262 pub fn parse_quirky<'i, 't>( 263 context: &ParserContext, 264 input: &mut Parser<'i, 't>, 265 allow_quirks: AllowQuirks, 266 ) -> Result<Self, ParseError<'i>> { 267 if input 268 .try_parse(|i| i.expect_ident_matching("center")) 269 .is_ok() 270 { 271 return Ok(PositionComponent::Center); 272 } 273 if let Ok(lp) = 274 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 275 { 276 return Ok(PositionComponent::Length(lp)); 277 } 278 let keyword = S::parse(context, input)?; 279 let lp = input 280 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 281 .ok(); 282 Ok(PositionComponent::Side(keyword, lp)) 283 } 284 } 285 286 impl<S> GenericPositionComponent for PositionComponent<S> { 287 fn is_center(&self) -> bool { 288 match *self { 289 PositionComponent::Center => true, 290 PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.0 == 0.5, 291 // 50% from any side is still the center. 292 PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => per.0 == 0.5, 293 _ => false, 294 } 295 } 296 } 297 298 impl<S> PositionComponent<S> { 299 /// `0%` 300 pub fn zero() -> Self { 301 PositionComponent::Length(LengthPercentage::Percentage(Percentage::zero())) 302 } 303 304 /// Returns the count of this component. 305 fn component_count(&self) -> usize { 306 match *self { 307 PositionComponent::Length(..) | PositionComponent::Center => 1, 308 PositionComponent::Side(_, ref lp) => { 309 if lp.is_some() { 310 2 311 } else { 312 1 313 } 314 }, 315 } 316 } 317 } 318 319 impl<S: Side> ToComputedValue for PositionComponent<S> { 320 type ComputedValue = ComputedLengthPercentage; 321 322 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 323 match *self { 324 PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)), 325 PositionComponent::Side(ref keyword, None) => { 326 let p = Percentage(if keyword.is_start() { 0. } else { 1. }); 327 ComputedLengthPercentage::new_percent(p) 328 }, 329 PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => { 330 let length = length.to_computed_value(context); 331 // We represent `<end-side> <length>` as `calc(100% - <length>)`. 332 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All) 333 }, 334 PositionComponent::Side(_, Some(ref length)) 335 | PositionComponent::Length(ref length) => length.to_computed_value(context), 336 } 337 } 338 339 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 340 PositionComponent::Length(ToComputedValue::from_computed_value(computed)) 341 } 342 } 343 344 impl<S: Side> PositionComponent<S> { 345 /// The initial specified value of a position component, i.e. the start side. 346 pub fn initial_specified_value() -> Self { 347 PositionComponent::Side(S::start(), None) 348 } 349 } 350 351 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-anchor-name 352 #[repr(transparent)] 353 #[derive( 354 Clone, 355 Debug, 356 MallocSizeOf, 357 PartialEq, 358 SpecifiedValueInfo, 359 ToComputedValue, 360 ToCss, 361 ToResolvedValue, 362 ToShmem, 363 ToTyped, 364 )] 365 #[css(comma)] 366 pub struct AnchorName( 367 #[css(iterable, if_empty = "none")] 368 #[ignore_malloc_size_of = "Arc"] 369 pub crate::ArcSlice<DashedIdent>, 370 ); 371 372 impl AnchorName { 373 /// Return the `none` value. 374 pub fn none() -> Self { 375 Self(Default::default()) 376 } 377 378 /// Returns whether this is the `none` value. 379 pub fn is_none(&self) -> bool { 380 self.0.is_empty() 381 } 382 } 383 384 impl Parse for AnchorName { 385 fn parse<'i, 't>( 386 context: &ParserContext, 387 input: &mut Parser<'i, 't>, 388 ) -> Result<Self, ParseError<'i>> { 389 let location = input.current_source_location(); 390 let first = input.expect_ident()?; 391 if first.eq_ignore_ascii_case("none") { 392 return Ok(Self::none()); 393 } 394 // The common case is probably just to have a single anchor name, so 395 // space for four on the stack should be plenty. 396 let mut idents: SmallVec<[DashedIdent; 4]> = 397 smallvec![DashedIdent::from_ident(location, first,)?]; 398 while input.try_parse(|input| input.expect_comma()).is_ok() { 399 idents.push(DashedIdent::parse(context, input)?); 400 } 401 Ok(AnchorName(ArcSlice::from_iter(idents.drain(..)))) 402 } 403 } 404 405 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-scope 406 #[derive( 407 Clone, 408 Debug, 409 MallocSizeOf, 410 PartialEq, 411 SpecifiedValueInfo, 412 ToComputedValue, 413 ToCss, 414 ToResolvedValue, 415 ToShmem, 416 ToTyped, 417 )] 418 #[repr(u8)] 419 pub enum AnchorScope { 420 /// `none` 421 None, 422 /// `all` 423 All, 424 /// `<dashed-ident>#` 425 #[css(comma)] 426 Idents( 427 #[css(iterable)] 428 #[ignore_malloc_size_of = "Arc"] 429 crate::ArcSlice<DashedIdent>, 430 ), 431 } 432 433 impl AnchorScope { 434 /// Return the `none` value. 435 pub fn none() -> Self { 436 Self::None 437 } 438 439 /// Returns whether this is the `none` value. 440 pub fn is_none(&self) -> bool { 441 *self == Self::None 442 } 443 } 444 445 impl Parse for AnchorScope { 446 fn parse<'i, 't>( 447 context: &ParserContext, 448 input: &mut Parser<'i, 't>, 449 ) -> Result<Self, ParseError<'i>> { 450 let location = input.current_source_location(); 451 let first = input.expect_ident()?; 452 if first.eq_ignore_ascii_case("none") { 453 return Ok(Self::None); 454 } 455 if first.eq_ignore_ascii_case("all") { 456 return Ok(Self::All); 457 } 458 // Authors using more than a handful of anchored elements is likely 459 // uncommon, so we only pre-allocate for 8 on the stack here. 460 let mut idents: SmallVec<[DashedIdent; 8]> = 461 smallvec![DashedIdent::from_ident(location, first,)?]; 462 while input.try_parse(|input| input.expect_comma()).is_ok() { 463 idents.push(DashedIdent::parse(context, input)?); 464 } 465 Ok(AnchorScope::Idents(ArcSlice::from_iter(idents.drain(..)))) 466 } 467 } 468 469 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-anchor 470 #[derive( 471 Clone, 472 Debug, 473 MallocSizeOf, 474 Parse, 475 PartialEq, 476 SpecifiedValueInfo, 477 ToComputedValue, 478 ToCss, 479 ToResolvedValue, 480 ToShmem, 481 ToTyped, 482 )] 483 #[repr(u8)] 484 pub enum PositionAnchor { 485 /// `none` 486 None, 487 /// `auto` 488 Auto, 489 /// `<dashed-ident>` 490 Ident(DashedIdent), 491 } 492 493 #[derive( 494 Clone, 495 Copy, 496 Debug, 497 Eq, 498 MallocSizeOf, 499 Parse, 500 PartialEq, 501 Serialize, 502 SpecifiedValueInfo, 503 ToComputedValue, 504 ToCss, 505 ToResolvedValue, 506 ToShmem, 507 )] 508 #[repr(u8)] 509 /// How to swap values for the automatically-generated position tactic. 510 pub enum PositionTryFallbacksTryTacticKeyword { 511 /// Swap the values in the block axis. 512 FlipBlock, 513 /// Swap the values in the inline axis. 514 FlipInline, 515 /// Swap the values in the start properties. 516 FlipStart, 517 /// Swap the values in the X axis. 518 FlipX, 519 /// Swap the values in the Y axis. 520 FlipY, 521 } 522 523 #[derive( 524 Clone, 525 Debug, 526 Default, 527 Eq, 528 MallocSizeOf, 529 PartialEq, 530 SpecifiedValueInfo, 531 ToComputedValue, 532 ToCss, 533 ToResolvedValue, 534 ToShmem, 535 )] 536 #[repr(transparent)] 537 /// Changes for the automatically-generated position option. 538 /// Note that this is order-dependent - e.g. `flip-start flip-inline` != `flip-inline flip-start`. 539 /// 540 /// https://drafts.csswg.org/css-anchor-position-1/#typedef-position-try-fallbacks-try-tactic 541 pub struct PositionTryFallbacksTryTactic( 542 #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>, 543 ); 544 545 impl Parse for PositionTryFallbacksTryTactic { 546 fn parse<'i, 't>( 547 _context: &ParserContext, 548 input: &mut Parser<'i, 't>, 549 ) -> Result<Self, ParseError<'i>> { 550 let mut result = ThinVec::with_capacity(5); 551 // Collect up to 5 keywords, disallowing duplicates. 552 for _ in 0..5 { 553 if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) { 554 if result.contains(&kw) { 555 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 556 } 557 result.push(kw); 558 } else { 559 break; 560 } 561 } 562 if result.is_empty() { 563 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 564 } 565 Ok(Self(result)) 566 } 567 } 568 569 impl PositionTryFallbacksTryTactic { 570 /// Returns whether there's any tactic. 571 #[inline] 572 pub fn is_empty(&self) -> bool { 573 self.0.is_empty() 574 } 575 576 /// Iterates over the fallbacks in order. 577 #[inline] 578 pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> { 579 self.0.iter() 580 } 581 } 582 583 #[derive( 584 Clone, 585 Debug, 586 MallocSizeOf, 587 PartialEq, 588 SpecifiedValueInfo, 589 ToComputedValue, 590 ToCss, 591 ToResolvedValue, 592 ToShmem, 593 )] 594 #[repr(C)] 595 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks 596 /// <dashed-ident> || <try-tactic> 597 pub struct DashedIdentAndOrTryTactic { 598 /// `<dashed-ident>` 599 pub ident: DashedIdent, 600 /// `<try-tactic>` 601 pub try_tactic: PositionTryFallbacksTryTactic, 602 } 603 604 impl Parse for DashedIdentAndOrTryTactic { 605 fn parse<'i, 't>( 606 context: &ParserContext, 607 input: &mut Parser<'i, 't>, 608 ) -> Result<Self, ParseError<'i>> { 609 let mut result = Self { 610 ident: DashedIdent::empty(), 611 try_tactic: PositionTryFallbacksTryTactic::default(), 612 }; 613 614 loop { 615 if result.ident.is_empty() { 616 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) { 617 result.ident = ident; 618 continue; 619 } 620 } 621 if result.try_tactic.is_empty() { 622 if let Ok(try_tactic) = 623 input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i)) 624 { 625 result.try_tactic = try_tactic; 626 continue; 627 } 628 } 629 break; 630 } 631 632 if result.ident.is_empty() && result.try_tactic.is_empty() { 633 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 634 } 635 return Ok(result); 636 } 637 } 638 639 #[derive( 640 Clone, 641 Debug, 642 MallocSizeOf, 643 Parse, 644 PartialEq, 645 SpecifiedValueInfo, 646 ToComputedValue, 647 ToCss, 648 ToResolvedValue, 649 ToShmem, 650 )] 651 #[repr(u8)] 652 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-try-fallbacks 653 /// [ [<dashed-ident> || <try-tactic>] | <'position-area'> ] 654 pub enum PositionTryFallbacksItem { 655 /// `<dashed-ident> || <try-tactic>` 656 IdentAndOrTactic(DashedIdentAndOrTryTactic), 657 #[parse(parse_fn = "PositionArea::parse_except_none")] 658 /// `<position-area>` 659 PositionArea(PositionArea), 660 } 661 662 #[derive( 663 Clone, 664 Debug, 665 Default, 666 MallocSizeOf, 667 PartialEq, 668 SpecifiedValueInfo, 669 ToComputedValue, 670 ToCss, 671 ToResolvedValue, 672 ToShmem, 673 ToTyped, 674 )] 675 #[css(comma)] 676 #[repr(C)] 677 /// https://drafts.csswg.org/css-anchor-position-1/#position-try-fallbacks 678 pub struct PositionTryFallbacks( 679 #[css(iterable, if_empty = "none")] 680 #[ignore_malloc_size_of = "Arc"] 681 pub crate::ArcSlice<PositionTryFallbacksItem>, 682 ); 683 684 impl PositionTryFallbacks { 685 #[inline] 686 /// Return the `none` value. 687 pub fn none() -> Self { 688 Self(Default::default()) 689 } 690 691 /// Returns whether this is the `none` value. 692 pub fn is_none(&self) -> bool { 693 self.0.is_empty() 694 } 695 } 696 697 impl Parse for PositionTryFallbacks { 698 fn parse<'i, 't>( 699 context: &ParserContext, 700 input: &mut Parser<'i, 't>, 701 ) -> Result<Self, ParseError<'i>> { 702 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 703 return Ok(Self::none()); 704 } 705 // The common case is unlikely to include many alternate positioning 706 // styles, so space for four on the stack should typically be enough. 707 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> = 708 smallvec![PositionTryFallbacksItem::parse(context, input)?]; 709 while input.try_parse(|input| input.expect_comma()).is_ok() { 710 items.push(PositionTryFallbacksItem::parse(context, input)?); 711 } 712 Ok(Self(ArcSlice::from_iter(items.drain(..)))) 713 } 714 } 715 716 /// https://drafts.csswg.org/css-anchor-position-1/#position-try-order-property 717 #[derive( 718 Clone, 719 Copy, 720 Debug, 721 Default, 722 Eq, 723 MallocSizeOf, 724 Parse, 725 PartialEq, 726 SpecifiedValueInfo, 727 ToComputedValue, 728 ToCss, 729 ToResolvedValue, 730 ToShmem, 731 ToTyped, 732 )] 733 #[repr(u8)] 734 pub enum PositionTryOrder { 735 #[default] 736 /// `normal` 737 Normal, 738 /// `most-width` 739 MostWidth, 740 /// `most-height` 741 MostHeight, 742 /// `most-block-size` 743 MostBlockSize, 744 /// `most-inline-size` 745 MostInlineSize, 746 } 747 748 impl PositionTryOrder { 749 #[inline] 750 /// Return the `auto` value. 751 pub fn normal() -> Self { 752 Self::Normal 753 } 754 755 /// Returns whether this is the `auto` value. 756 pub fn is_normal(&self) -> bool { 757 *self == Self::Normal 758 } 759 } 760 761 #[derive( 762 Clone, 763 Copy, 764 Debug, 765 Eq, 766 MallocSizeOf, 767 Parse, 768 PartialEq, 769 Serialize, 770 SpecifiedValueInfo, 771 ToComputedValue, 772 ToCss, 773 ToResolvedValue, 774 ToShmem, 775 ToTyped, 776 )] 777 #[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))] 778 #[repr(C)] 779 /// Specified keyword values for the position-visibility property. 780 pub struct PositionVisibility(u8); 781 bitflags! { 782 impl PositionVisibility: u8 { 783 /// Element is displayed without regard for its anchors or its overflowing status. 784 const ALWAYS = 0; 785 /// anchors-valid 786 const ANCHORS_VALID = 1 << 0; 787 /// anchors-visible 788 const ANCHORS_VISIBLE = 1 << 1; 789 /// no-overflow 790 const NO_OVERFLOW = 1 << 2; 791 } 792 } 793 794 impl Default for PositionVisibility { 795 fn default() -> Self { 796 Self::ALWAYS 797 } 798 } 799 800 impl PositionVisibility { 801 #[inline] 802 /// Returns the initial value of position-visibility 803 pub fn always() -> Self { 804 Self::ALWAYS 805 } 806 } 807 808 /// A value indicating which high level group in the formal grammar a 809 /// PositionAreaKeyword or PositionArea belongs to. 810 #[repr(u8)] 811 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 812 pub enum PositionAreaType { 813 /// X || Y 814 Physical, 815 /// block || inline 816 Logical, 817 /// self-block || self-inline 818 SelfLogical, 819 /// start|end|span-* {1,2} 820 Inferred, 821 /// self-start|self-end|span-self-* {1,2} 822 SelfInferred, 823 /// center, span-all 824 Common, 825 /// none 826 None, 827 } 828 829 /// A three-bit value that represents the axis in which position-area operates on. 830 /// Represented as 4 bits: axis type (physical or logical), direction type (physical or logical), 831 /// axis value. 832 /// 833 /// There are two special values on top (Inferred and None) that represent ambiguous or axis-less 834 /// keywords, respectively. 835 #[repr(u8)] 836 #[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)] 837 #[allow(missing_docs)] 838 pub enum PositionAreaAxis { 839 Horizontal = 0b000, 840 Vertical = 0b001, 841 842 X = 0b010, 843 Y = 0b011, 844 845 Block = 0b110, 846 Inline = 0b111, 847 848 Inferred = 0b100, 849 None = 0b101, 850 } 851 852 impl PositionAreaAxis { 853 /// Whether this axis is physical or not. 854 pub fn is_physical(self) -> bool { 855 (self as u8 & 0b100) == 0 856 } 857 858 /// Whether the direction is logical or not. 859 fn is_flow_relative_direction(self) -> bool { 860 self == Self::Inferred || (self as u8 & 0b10) != 0 861 } 862 863 /// Whether this axis goes first in the canonical syntax. 864 fn is_canonically_first(self) -> bool { 865 self != Self::Inferred && (self as u8) & 1 == 0 866 } 867 868 #[allow(unused)] 869 fn flip(self) -> Self { 870 if matches!(self, Self::Inferred | Self::None) { 871 return self; 872 } 873 Self::from_u8(self as u8 ^ 1u8).unwrap() 874 } 875 876 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> { 877 Some(match self { 878 PositionAreaAxis::Horizontal | PositionAreaAxis::X => { 879 if wm.is_vertical() { 880 LogicalAxis::Block 881 } else { 882 LogicalAxis::Inline 883 } 884 }, 885 PositionAreaAxis::Vertical | PositionAreaAxis::Y => { 886 if wm.is_vertical() { 887 LogicalAxis::Inline 888 } else { 889 LogicalAxis::Block 890 } 891 }, 892 PositionAreaAxis::Block => LogicalAxis::Block, 893 PositionAreaAxis::Inline => LogicalAxis::Inline, 894 PositionAreaAxis::Inferred => inferred, 895 PositionAreaAxis::None => return None, 896 }) 897 } 898 } 899 900 /// Specifies which tracks(s) on the axis that the position-area span occupies. 901 /// Represented as 3 bits: start, center, end track. 902 #[repr(u8)] 903 #[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)] 904 pub enum PositionAreaTrack { 905 /// First track 906 Start = 0b001, 907 /// First and center. 908 SpanStart = 0b011, 909 /// Last track. 910 End = 0b100, 911 /// Last and center. 912 SpanEnd = 0b110, 913 /// Center track. 914 Center = 0b010, 915 /// All tracks 916 SpanAll = 0b111, 917 } 918 919 impl PositionAreaTrack { 920 fn flip(self) -> Self { 921 match self { 922 Self::Start => Self::End, 923 Self::SpanStart => Self::SpanEnd, 924 Self::End => Self::Start, 925 Self::SpanEnd => Self::SpanStart, 926 Self::Center | Self::SpanAll => self, 927 } 928 } 929 930 fn start(self) -> bool { 931 self as u8 & 1 != 0 932 } 933 } 934 935 /// The shift to the left needed to set the axis. 936 pub const AXIS_SHIFT: usize = 3; 937 /// The mask used to extract the axis. 938 pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT; 939 /// The mask used to extract the track. 940 pub const TRACK_MASK: u8 = 0b111u8; 941 /// The self-wm bit. 942 pub const SELF_WM: u8 = 1u8 << 6; 943 944 #[derive( 945 Clone, 946 Copy, 947 Debug, 948 Default, 949 Eq, 950 MallocSizeOf, 951 Parse, 952 PartialEq, 953 SpecifiedValueInfo, 954 ToComputedValue, 955 ToCss, 956 ToResolvedValue, 957 ToShmem, 958 FromPrimitive, 959 )] 960 #[allow(missing_docs)] 961 #[repr(u8)] 962 /// Possible values for the `position-area` property's keywords. 963 /// Represented by [0z xxx yyy], where z means "self wm resolution", xxxx is the axis (as in 964 /// PositionAreaAxis) and yyy is the PositionAreaTrack 965 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area 966 pub enum PositionAreaKeyword { 967 #[default] 968 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT, 969 970 // Common (shared) keywords: 971 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8, 972 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8, 973 974 // Inferred-axis edges: 975 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 976 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 977 SpanStart = 978 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 979 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 980 981 // Purely physical edges: 982 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 983 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 984 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 985 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 986 987 // Flow-relative physical-axis edges: 988 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 989 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 990 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 991 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 992 993 // Logical edges: 994 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 995 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 996 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, 997 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, 998 999 // Composite values with Span: 1000 SpanLeft = 1001 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1002 SpanRight = 1003 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1004 SpanTop = 1005 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1006 SpanBottom = 1007 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1008 1009 // Flow-relative physical-axis edges: 1010 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1011 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1012 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1013 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1014 1015 // Logical edges: 1016 SpanBlockStart = 1017 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1018 SpanBlockEnd = 1019 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1020 SpanInlineStart = 1021 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, 1022 SpanInlineEnd = 1023 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, 1024 1025 // Values using the Self element's writing-mode: 1026 SelfStart = SELF_WM | (Self::Start as u8), 1027 SelfEnd = SELF_WM | (Self::End as u8), 1028 SpanSelfStart = SELF_WM | (Self::SpanStart as u8), 1029 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8), 1030 1031 SelfXStart = SELF_WM | (Self::XStart as u8), 1032 SelfXEnd = SELF_WM | (Self::XEnd as u8), 1033 SelfYStart = SELF_WM | (Self::YStart as u8), 1034 SelfYEnd = SELF_WM | (Self::YEnd as u8), 1035 SelfBlockStart = SELF_WM | (Self::BlockStart as u8), 1036 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8), 1037 SelfInlineStart = SELF_WM | (Self::InlineStart as u8), 1038 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8), 1039 1040 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8), 1041 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8), 1042 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8), 1043 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8), 1044 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8), 1045 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8), 1046 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8), 1047 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8), 1048 } 1049 1050 impl PositionAreaKeyword { 1051 /// Returns the 'none' value. 1052 #[inline] 1053 pub fn none() -> Self { 1054 Self::None 1055 } 1056 1057 /// Returns true if this is the none keyword. 1058 pub fn is_none(&self) -> bool { 1059 *self == Self::None 1060 } 1061 1062 /// Whether we're one of the self-wm keywords. 1063 pub fn self_wm(self) -> bool { 1064 (self as u8 & SELF_WM) != 0 1065 } 1066 1067 /// Get this keyword's axis. 1068 pub fn axis(self) -> PositionAreaAxis { 1069 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap() 1070 } 1071 1072 /// Returns this keyword but with the axis swapped by the argument. 1073 pub fn with_axis(self, axis: PositionAreaAxis) -> Self { 1074 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap() 1075 } 1076 1077 /// If this keyword uses an inferred axis, replaces it. 1078 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self { 1079 if self.axis() == PositionAreaAxis::Inferred { 1080 self.with_axis(axis) 1081 } else { 1082 self 1083 } 1084 } 1085 1086 /// Get this keyword's track, or None if we're the `None` keyword. 1087 pub fn track(self) -> Option<PositionAreaTrack> { 1088 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK); 1089 debug_assert_eq!( 1090 result.is_none(), 1091 self.is_none(), 1092 "Only the none keyword has no track" 1093 ); 1094 result 1095 } 1096 1097 fn group_type(self) -> PositionAreaType { 1098 let axis = self.axis(); 1099 if axis == PositionAreaAxis::None { 1100 if self.is_none() { 1101 return PositionAreaType::None; 1102 } 1103 return PositionAreaType::Common; 1104 } 1105 if axis == PositionAreaAxis::Inferred { 1106 return if self.self_wm() { 1107 PositionAreaType::SelfInferred 1108 } else { 1109 PositionAreaType::Inferred 1110 }; 1111 } 1112 if axis.is_physical() { 1113 return PositionAreaType::Physical; 1114 } 1115 if self.self_wm() { 1116 PositionAreaType::SelfLogical 1117 } else { 1118 PositionAreaType::Logical 1119 } 1120 } 1121 1122 fn to_physical( 1123 self, 1124 cb_wm: WritingMode, 1125 self_wm: WritingMode, 1126 inferred_axis: LogicalAxis, 1127 ) -> Self { 1128 let wm = if self.self_wm() { self_wm } else { cb_wm }; 1129 let axis = self.axis(); 1130 if !axis.is_flow_relative_direction() { 1131 return self; 1132 } 1133 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else { 1134 return self; 1135 }; 1136 let Some(track) = self.track() else { 1137 debug_assert!(false, "How did we end up with no track here? {self:?}"); 1138 return self; 1139 }; 1140 let start = track.start(); 1141 let logical_side = match logical_axis { 1142 LogicalAxis::Block => { 1143 if start { 1144 LogicalSide::BlockStart 1145 } else { 1146 LogicalSide::BlockEnd 1147 } 1148 }, 1149 LogicalAxis::Inline => { 1150 if start { 1151 LogicalSide::InlineStart 1152 } else { 1153 LogicalSide::InlineEnd 1154 } 1155 }, 1156 }; 1157 let physical_side = logical_side.to_physical(wm); 1158 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left); 1159 let new_track = if physical_start != start { 1160 track.flip() 1161 } else { 1162 track 1163 }; 1164 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) { 1165 PositionAreaAxis::Vertical 1166 } else { 1167 PositionAreaAxis::Horizontal 1168 }; 1169 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap() 1170 } 1171 1172 fn flip_track(self) -> Self { 1173 let Some(old_track) = self.track() else { 1174 return self; 1175 }; 1176 let new_track = old_track.flip(); 1177 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap() 1178 } 1179 1180 /// Returns a value for the self-alignment properties in order to resolve 1181 /// `normal`, in terms of the containing block's writing mode. 1182 /// 1183 /// Note that the caller must have converted the position-area to physical 1184 /// values. 1185 /// 1186 /// <https://drafts.csswg.org/css-anchor-position/#position-area-alignment> 1187 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> { 1188 let track = self.track()?; 1189 Some(match track { 1190 // "If the only the center track in an axis is selected, the default alignment in that axis is center." 1191 PositionAreaTrack::Center => AlignFlags::CENTER, 1192 // "If all three tracks are selected, the default alignment in that axis is anchor-center." 1193 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER, 1194 // "Otherwise, the default alignment in that axis is toward the non-specified side track: if it’s 1195 // specifying the “start” track of its axis, the default alignment in that axis is end; etc." 1196 _ => { 1197 debug_assert_eq!(self.group_type(), PositionAreaType::Physical); 1198 if axis == LogicalAxis::Inline { 1199 // For the inline axis, map 'start' to 'end' unless the axis is inline-reversed, 1200 // meaning that its logical flow is counter to physical coordinates and therefore 1201 // physical 'start' already corresponds to logical 'end'. 1202 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) { 1203 AlignFlags::START 1204 } else { 1205 AlignFlags::END 1206 } 1207 } else { 1208 // For the block axis, only vertical-rl has reversed flow and therefore 1209 // does not map 'start' to 'end' here. 1210 if track.start() == cb_wm.is_vertical_rl() { 1211 AlignFlags::START 1212 } else { 1213 AlignFlags::END 1214 } 1215 } 1216 }, 1217 }) 1218 } 1219 } 1220 1221 #[derive( 1222 Clone, 1223 Copy, 1224 Debug, 1225 Eq, 1226 MallocSizeOf, 1227 PartialEq, 1228 SpecifiedValueInfo, 1229 ToCss, 1230 ToResolvedValue, 1231 ToShmem, 1232 ToTyped, 1233 )] 1234 #[repr(C)] 1235 /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area 1236 pub struct PositionArea { 1237 /// First keyword, if any. 1238 pub first: PositionAreaKeyword, 1239 /// Second keyword, if any. 1240 #[css(skip_if = "PositionAreaKeyword::is_none")] 1241 pub second: PositionAreaKeyword, 1242 } 1243 1244 impl PositionArea { 1245 /// Returns the none value. 1246 #[inline] 1247 pub fn none() -> Self { 1248 Self { 1249 first: PositionAreaKeyword::None, 1250 second: PositionAreaKeyword::None, 1251 } 1252 } 1253 1254 /// Returns whether we're the none value. 1255 #[inline] 1256 pub fn is_none(&self) -> bool { 1257 self.first.is_none() 1258 } 1259 1260 /// Parses a <position-area> without allowing `none`. 1261 pub fn parse_except_none<'i, 't>( 1262 context: &ParserContext, 1263 input: &mut Parser<'i, 't>, 1264 ) -> Result<Self, ParseError<'i>> { 1265 Self::parse_internal(context, input, /*allow_none*/ false) 1266 } 1267 1268 /// Get the high-level grammar group of this. 1269 pub fn get_type(&self) -> PositionAreaType { 1270 let first = self.first.group_type(); 1271 let second = self.second.group_type(); 1272 if matches!(second, PositionAreaType::None | PositionAreaType::Common) { 1273 return first; 1274 } 1275 if first == PositionAreaType::Common { 1276 return second; 1277 } 1278 if first != second { 1279 return PositionAreaType::None; 1280 } 1281 let first_axis = self.first.axis(); 1282 if first_axis != PositionAreaAxis::Inferred 1283 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first() 1284 { 1285 return PositionAreaType::None; 1286 } 1287 first 1288 } 1289 1290 fn parse_internal<'i, 't>( 1291 _: &ParserContext, 1292 input: &mut Parser<'i, 't>, 1293 allow_none: bool, 1294 ) -> Result<Self, ParseError<'i>> { 1295 let mut location = input.current_source_location(); 1296 let mut first = PositionAreaKeyword::parse(input)?; 1297 if first.is_none() { 1298 if allow_none { 1299 return Ok(Self::none()); 1300 } 1301 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1302 } 1303 1304 location = input.current_source_location(); 1305 let second = input.try_parse(PositionAreaKeyword::parse); 1306 if let Ok(PositionAreaKeyword::None) = second { 1307 // `none` is only allowed as a single value 1308 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1309 } 1310 let mut second = second.unwrap_or(PositionAreaKeyword::None); 1311 if second.is_none() { 1312 // Either there was no second keyword and try_parse returned a 1313 // BasicParseErrorKind::EndOfInput, or else the second "keyword" 1314 // was invalid. We assume the former case here, and if it's the 1315 // latter case then our caller detects the error (try_parse will, 1316 // have rewound, leaving an unparsed token). 1317 return Ok(Self { first, second }); 1318 } 1319 1320 let pair_type = Self { first, second }.get_type(); 1321 if pair_type == PositionAreaType::None { 1322 // Mismatched types or what not. 1323 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1324 } 1325 // For types that have a canonical order, remove 'span-all' (the default behavior; 1326 // unnecessary for keyword pairs with a known order). 1327 if matches!( 1328 pair_type, 1329 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical 1330 ) { 1331 if second == PositionAreaKeyword::SpanAll { 1332 // Span-all is the default behavior, so specifying `span-all` is 1333 // superfluous. 1334 second = PositionAreaKeyword::None; 1335 } else if first == PositionAreaKeyword::SpanAll { 1336 first = second; 1337 second = PositionAreaKeyword::None; 1338 } 1339 } 1340 if first == second { 1341 second = PositionAreaKeyword::None; 1342 } 1343 let mut result = Self { first, second }; 1344 result.canonicalize_order(); 1345 Ok(result) 1346 } 1347 1348 fn canonicalize_order(&mut self) { 1349 let first_axis = self.first.axis(); 1350 if first_axis.is_canonically_first() || self.second.is_none() { 1351 return; 1352 } 1353 let second_axis = self.second.axis(); 1354 if first_axis == second_axis { 1355 // Inferred or axis-less keywords. 1356 return; 1357 } 1358 if second_axis.is_canonically_first() 1359 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred) 1360 { 1361 std::mem::swap(&mut self.first, &mut self.second); 1362 } 1363 } 1364 1365 fn make_missing_second_explicit(&mut self) { 1366 if !self.second.is_none() { 1367 return; 1368 } 1369 let axis = self.first.axis(); 1370 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) { 1371 self.second = self.first; 1372 return; 1373 } 1374 self.second = PositionAreaKeyword::SpanAll; 1375 if !axis.is_canonically_first() { 1376 std::mem::swap(&mut self.first, &mut self.second); 1377 } 1378 } 1379 1380 /// Turns this <position-area> value into a physical <position-area>. 1381 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self { 1382 self.make_missing_second_explicit(); 1383 // If both axes are None, to_physical and canonicalize_order are not useful. 1384 // The first value refers to the block axis, the second to the inline axis; 1385 // but as a physical type, they will be interpreted as the x- and y-axis 1386 // respectively, so if the writing mode is horizontal we need to swap the 1387 // values (block -> y, inline -> x). 1388 if self.first.axis() == PositionAreaAxis::None && 1389 self.second.axis() == PositionAreaAxis::None && 1390 !cb_wm.is_vertical() { 1391 std::mem::swap(&mut self.first, &mut self.second); 1392 } else { 1393 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block); 1394 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline); 1395 self.canonicalize_order(); 1396 } 1397 self 1398 } 1399 1400 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) { 1401 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) { 1402 self.first = self.first.flip_track(); 1403 } else { 1404 self.second = self.second.flip_track(); 1405 } 1406 } 1407 1408 fn flip_start(&mut self) { 1409 self.first = self.first.with_axis(self.first.axis().flip()); 1410 self.second = self.second.with_axis(self.second.axis().flip()); 1411 } 1412 1413 /// Applies a try tactic to this `<position-area>` value. 1414 pub fn with_tactic( 1415 mut self, 1416 wm: WritingMode, 1417 tactic: PositionTryFallbacksTryTacticKeyword, 1418 ) -> Self { 1419 self.make_missing_second_explicit(); 1420 let axis_to_flip = match tactic { 1421 PositionTryFallbacksTryTacticKeyword::FlipStart => { 1422 self.flip_start(); 1423 return self; 1424 }, 1425 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block, 1426 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline, 1427 PositionTryFallbacksTryTacticKeyword::FlipX => { 1428 if wm.is_horizontal() { 1429 LogicalAxis::Inline 1430 } else { 1431 LogicalAxis::Block 1432 } 1433 }, 1434 PositionTryFallbacksTryTacticKeyword::FlipY => { 1435 if wm.is_vertical() { 1436 LogicalAxis::Inline 1437 } else { 1438 LogicalAxis::Block 1439 } 1440 }, 1441 }; 1442 self.flip_logical_axis(wm, axis_to_flip); 1443 self 1444 } 1445 } 1446 1447 impl Parse for PositionArea { 1448 fn parse<'i, 't>( 1449 context: &ParserContext, 1450 input: &mut Parser<'i, 't>, 1451 ) -> Result<Self, ParseError<'i>> { 1452 Self::parse_internal(context, input, /* allow_none = */ true) 1453 } 1454 } 1455 1456 /// Represents a side, either horizontal or vertical, of a CSS position. 1457 pub trait Side { 1458 /// Returns the start side. 1459 fn start() -> Self; 1460 1461 /// Returns whether this side is the start side. 1462 fn is_start(&self) -> bool; 1463 } 1464 1465 impl Side for HorizontalPositionKeyword { 1466 #[inline] 1467 fn start() -> Self { 1468 HorizontalPositionKeyword::Left 1469 } 1470 1471 #[inline] 1472 fn is_start(&self) -> bool { 1473 *self == Self::start() 1474 } 1475 } 1476 1477 impl Side for VerticalPositionKeyword { 1478 #[inline] 1479 fn start() -> Self { 1480 VerticalPositionKeyword::Top 1481 } 1482 1483 #[inline] 1484 fn is_start(&self) -> bool { 1485 *self == Self::start() 1486 } 1487 } 1488 1489 /// Controls how the auto-placement algorithm works specifying exactly how auto-placed items 1490 /// get flowed into the grid: [ row | column ] || dense 1491 /// https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property 1492 #[derive( 1493 Clone, 1494 Copy, 1495 Debug, 1496 Eq, 1497 MallocSizeOf, 1498 Parse, 1499 PartialEq, 1500 SpecifiedValueInfo, 1501 ToComputedValue, 1502 ToResolvedValue, 1503 ToShmem, 1504 ToTyped, 1505 )] 1506 #[css(bitflags( 1507 mixed = "row,column,dense", 1508 validate_mixed = "Self::validate_and_simplify" 1509 ))] 1510 #[repr(C)] 1511 pub struct GridAutoFlow(u8); 1512 bitflags! { 1513 impl GridAutoFlow: u8 { 1514 /// 'row' - mutually exclusive with 'column' 1515 const ROW = 1 << 0; 1516 /// 'column' - mutually exclusive with 'row' 1517 const COLUMN = 1 << 1; 1518 /// 'dense' 1519 const DENSE = 1 << 2; 1520 } 1521 } 1522 1523 impl GridAutoFlow { 1524 /// [ row | column ] || dense 1525 fn validate_and_simplify(&mut self) -> bool { 1526 if self.contains(Self::ROW | Self::COLUMN) { 1527 // row and column are mutually exclusive. 1528 return false; 1529 } 1530 if *self == Self::DENSE { 1531 // If there's no column, default to row. 1532 self.insert(Self::ROW); 1533 } 1534 true 1535 } 1536 } 1537 1538 impl ToCss for GridAutoFlow { 1539 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1540 where 1541 W: Write, 1542 { 1543 let dense = self.intersects(Self::DENSE); 1544 if self.intersects(Self::ROW) { 1545 return if dense { 1546 dest.write_str("dense") 1547 } else { 1548 dest.write_str("row") 1549 }; 1550 } 1551 debug_assert!(self.intersects(Self::COLUMN)); 1552 if dense { 1553 dest.write_str("column dense") 1554 } else { 1555 dest.write_str("column") 1556 } 1557 } 1558 } 1559 1560 #[repr(u8)] 1561 #[derive( 1562 Clone, 1563 Copy, 1564 Debug, 1565 Eq, 1566 MallocSizeOf, 1567 PartialEq, 1568 SpecifiedValueInfo, 1569 ToComputedValue, 1570 ToCss, 1571 ToResolvedValue, 1572 ToShmem, 1573 )] 1574 /// Masonry auto-placement algorithm packing. 1575 pub enum MasonryPlacement { 1576 /// Place the item in the track(s) with the smallest extent so far. 1577 Pack, 1578 /// Place the item after the last item, from start to end. 1579 Next, 1580 } 1581 1582 #[repr(u8)] 1583 #[derive( 1584 Clone, 1585 Copy, 1586 Debug, 1587 Eq, 1588 MallocSizeOf, 1589 PartialEq, 1590 SpecifiedValueInfo, 1591 ToComputedValue, 1592 ToCss, 1593 ToResolvedValue, 1594 ToShmem, 1595 )] 1596 /// Masonry auto-placement algorithm item sorting option. 1597 pub enum MasonryItemOrder { 1598 /// Place all items with a definite placement before auto-placed items. 1599 DefiniteFirst, 1600 /// Place items in `order-modified document order`. 1601 Ordered, 1602 } 1603 1604 #[derive( 1605 Clone, 1606 Copy, 1607 Debug, 1608 Eq, 1609 MallocSizeOf, 1610 PartialEq, 1611 SpecifiedValueInfo, 1612 ToComputedValue, 1613 ToCss, 1614 ToResolvedValue, 1615 ToShmem, 1616 ToTyped, 1617 )] 1618 #[repr(C)] 1619 /// Controls how the Masonry layout algorithm works 1620 /// specifying exactly how auto-placed items get flowed in the masonry axis. 1621 pub struct MasonryAutoFlow { 1622 /// Specify how to pick a auto-placement track. 1623 #[css(contextual_skip_if = "is_pack_with_non_default_order")] 1624 pub placement: MasonryPlacement, 1625 /// Specify how to pick an item to place. 1626 #[css(skip_if = "is_item_order_definite_first")] 1627 pub order: MasonryItemOrder, 1628 } 1629 1630 #[inline] 1631 fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool { 1632 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst 1633 } 1634 1635 #[inline] 1636 fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool { 1637 *order == MasonryItemOrder::DefiniteFirst 1638 } 1639 1640 impl MasonryAutoFlow { 1641 #[inline] 1642 /// Get initial `masonry-auto-flow` value. 1643 pub fn initial() -> MasonryAutoFlow { 1644 MasonryAutoFlow { 1645 placement: MasonryPlacement::Pack, 1646 order: MasonryItemOrder::DefiniteFirst, 1647 } 1648 } 1649 } 1650 1651 impl Parse for MasonryAutoFlow { 1652 /// [ definite-first | ordered ] || [ pack | next ] 1653 fn parse<'i, 't>( 1654 _context: &ParserContext, 1655 input: &mut Parser<'i, 't>, 1656 ) -> Result<MasonryAutoFlow, ParseError<'i>> { 1657 let mut value = MasonryAutoFlow::initial(); 1658 let mut got_placement = false; 1659 let mut got_order = false; 1660 while !input.is_exhausted() { 1661 let location = input.current_source_location(); 1662 let ident = input.expect_ident()?; 1663 let success = match_ignore_ascii_case! { &ident, 1664 "pack" if !got_placement => { 1665 got_placement = true; 1666 true 1667 }, 1668 "next" if !got_placement => { 1669 value.placement = MasonryPlacement::Next; 1670 got_placement = true; 1671 true 1672 }, 1673 "definite-first" if !got_order => { 1674 got_order = true; 1675 true 1676 }, 1677 "ordered" if !got_order => { 1678 value.order = MasonryItemOrder::Ordered; 1679 got_order = true; 1680 true 1681 }, 1682 _ => false 1683 }; 1684 if !success { 1685 return Err(location 1686 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))); 1687 } 1688 } 1689 1690 if got_placement || got_order { 1691 Ok(value) 1692 } else { 1693 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1694 } 1695 } 1696 } 1697 1698 #[derive( 1699 Clone, 1700 Debug, 1701 MallocSizeOf, 1702 PartialEq, 1703 SpecifiedValueInfo, 1704 ToComputedValue, 1705 ToCss, 1706 ToResolvedValue, 1707 ToShmem, 1708 )] 1709 #[repr(C)] 1710 /// https://drafts.csswg.org/css-grid/#named-grid-area 1711 pub struct TemplateAreas { 1712 /// `named area` containing for each template area 1713 #[css(skip)] 1714 pub areas: crate::OwnedSlice<NamedArea>, 1715 /// The simplified CSS strings for serialization purpose. 1716 /// https://drafts.csswg.org/css-grid/#serialize-template 1717 // Note: We also use the length of `strings` when computing the explicit grid end line number 1718 // (i.e. row number). 1719 #[css(iterable)] 1720 pub strings: crate::OwnedSlice<crate::OwnedStr>, 1721 /// The number of columns of the grid. 1722 #[css(skip)] 1723 pub width: u32, 1724 } 1725 1726 /// Parser for grid template areas. 1727 #[derive(Default)] 1728 pub struct TemplateAreasParser { 1729 areas: Vec<NamedArea>, 1730 area_indices: PrecomputedHashMap<Atom, usize>, 1731 strings: Vec<crate::OwnedStr>, 1732 width: u32, 1733 row: u32, 1734 } 1735 1736 impl TemplateAreasParser { 1737 /// Parse a single string. 1738 pub fn try_parse_string<'i>( 1739 &mut self, 1740 input: &mut Parser<'i, '_>, 1741 ) -> Result<(), ParseError<'i>> { 1742 input.try_parse(|input| { 1743 self.parse_string(input.expect_string()?) 1744 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1745 }) 1746 } 1747 1748 /// Parse a single string. 1749 fn parse_string(&mut self, string: &str) -> Result<(), ()> { 1750 self.row += 1; 1751 let mut simplified_string = String::new(); 1752 let mut current_area_index: Option<usize> = None; 1753 let mut column = 0u32; 1754 for token in TemplateAreasTokenizer(string) { 1755 column += 1; 1756 if column > 1 { 1757 simplified_string.push(' '); 1758 } 1759 let name = if let Some(token) = token? { 1760 simplified_string.push_str(token); 1761 Atom::from(token) 1762 } else { 1763 if let Some(index) = current_area_index.take() { 1764 if self.areas[index].columns.end != column { 1765 return Err(()); 1766 } 1767 } 1768 simplified_string.push('.'); 1769 continue; 1770 }; 1771 if let Some(index) = current_area_index { 1772 if self.areas[index].name == name { 1773 if self.areas[index].rows.start == self.row { 1774 self.areas[index].columns.end += 1; 1775 } 1776 continue; 1777 } 1778 if self.areas[index].columns.end != column { 1779 return Err(()); 1780 } 1781 } 1782 match self.area_indices.entry(name) { 1783 Entry::Occupied(ref e) => { 1784 let index = *e.get(); 1785 if self.areas[index].columns.start != column 1786 || self.areas[index].rows.end != self.row 1787 { 1788 return Err(()); 1789 } 1790 self.areas[index].rows.end += 1; 1791 current_area_index = Some(index); 1792 }, 1793 Entry::Vacant(v) => { 1794 let index = self.areas.len(); 1795 let name = v.key().clone(); 1796 v.insert(index); 1797 self.areas.push(NamedArea { 1798 name, 1799 columns: UnsignedRange { 1800 start: column, 1801 end: column + 1, 1802 }, 1803 rows: UnsignedRange { 1804 start: self.row, 1805 end: self.row + 1, 1806 }, 1807 }); 1808 current_area_index = Some(index); 1809 }, 1810 } 1811 } 1812 if column == 0 { 1813 // Each string must produce a valid token. 1814 // https://github.com/w3c/csswg-drafts/issues/5110 1815 return Err(()); 1816 } 1817 if let Some(index) = current_area_index { 1818 if self.areas[index].columns.end != column + 1 { 1819 debug_assert_ne!(self.areas[index].rows.start, self.row); 1820 return Err(()); 1821 } 1822 } 1823 if self.row == 1 { 1824 self.width = column; 1825 } else if self.width != column { 1826 return Err(()); 1827 } 1828 1829 self.strings.push(simplified_string.into()); 1830 Ok(()) 1831 } 1832 1833 /// Return the parsed template areas. 1834 pub fn finish(self) -> Result<TemplateAreas, ()> { 1835 if self.strings.is_empty() { 1836 return Err(()); 1837 } 1838 Ok(TemplateAreas { 1839 areas: self.areas.into(), 1840 strings: self.strings.into(), 1841 width: self.width, 1842 }) 1843 } 1844 } 1845 1846 impl TemplateAreas { 1847 fn parse_internal(input: &mut Parser) -> Result<Self, ()> { 1848 let mut parser = TemplateAreasParser::default(); 1849 while parser.try_parse_string(input).is_ok() {} 1850 parser.finish() 1851 } 1852 } 1853 1854 impl Parse for TemplateAreas { 1855 fn parse<'i, 't>( 1856 _: &ParserContext, 1857 input: &mut Parser<'i, 't>, 1858 ) -> Result<Self, ParseError<'i>> { 1859 Self::parse_internal(input) 1860 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 1861 } 1862 } 1863 1864 /// Arc type for `Arc<TemplateAreas>` 1865 #[derive( 1866 Clone, 1867 Debug, 1868 MallocSizeOf, 1869 PartialEq, 1870 SpecifiedValueInfo, 1871 ToComputedValue, 1872 ToCss, 1873 ToResolvedValue, 1874 ToShmem, 1875 )] 1876 #[repr(transparent)] 1877 pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>); 1878 1879 impl Parse for TemplateAreasArc { 1880 fn parse<'i, 't>( 1881 context: &ParserContext, 1882 input: &mut Parser<'i, 't>, 1883 ) -> Result<Self, ParseError<'i>> { 1884 let parsed = TemplateAreas::parse(context, input)?; 1885 Ok(TemplateAreasArc(Arc::new(parsed))) 1886 } 1887 } 1888 1889 /// A range of rows or columns. Using this instead of std::ops::Range for FFI 1890 /// purposes. 1891 #[repr(C)] 1892 #[derive( 1893 Clone, 1894 Debug, 1895 MallocSizeOf, 1896 PartialEq, 1897 SpecifiedValueInfo, 1898 ToComputedValue, 1899 ToResolvedValue, 1900 ToShmem, 1901 )] 1902 pub struct UnsignedRange { 1903 /// The start of the range. 1904 pub start: u32, 1905 /// The end of the range. 1906 pub end: u32, 1907 } 1908 1909 #[derive( 1910 Clone, 1911 Debug, 1912 MallocSizeOf, 1913 PartialEq, 1914 SpecifiedValueInfo, 1915 ToComputedValue, 1916 ToResolvedValue, 1917 ToShmem, 1918 )] 1919 #[repr(C)] 1920 /// Not associated with any particular grid item, but can be referenced from the 1921 /// grid-placement properties. 1922 pub struct NamedArea { 1923 /// Name of the `named area` 1924 pub name: Atom, 1925 /// Rows of the `named area` 1926 pub rows: UnsignedRange, 1927 /// Columns of the `named area` 1928 pub columns: UnsignedRange, 1929 } 1930 1931 /// Tokenize the string into a list of the tokens, 1932 /// using longest-match semantics 1933 struct TemplateAreasTokenizer<'a>(&'a str); 1934 1935 impl<'a> Iterator for TemplateAreasTokenizer<'a> { 1936 type Item = Result<Option<&'a str>, ()>; 1937 1938 fn next(&mut self) -> Option<Self::Item> { 1939 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS); 1940 if rest.is_empty() { 1941 return None; 1942 } 1943 if rest.starts_with('.') { 1944 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..]; 1945 return Some(Ok(None)); 1946 } 1947 if !rest.starts_with(is_name_code_point) { 1948 return Some(Err(())); 1949 } 1950 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len()); 1951 let token = &rest[..token_len]; 1952 self.0 = &rest[token_len..]; 1953 Some(Ok(Some(token))) 1954 } 1955 } 1956 1957 fn is_name_code_point(c: char) -> bool { 1958 c >= 'A' && c <= 'Z' 1959 || c >= 'a' && c <= 'z' 1960 || c >= '\u{80}' 1961 || c == '_' 1962 || c >= '0' && c <= '9' 1963 || c == '-' 1964 } 1965 1966 /// This property specifies named grid areas. 1967 /// 1968 /// The syntax of this property also provides a visualization of the structure 1969 /// of the grid, making the overall layout of the grid container easier to 1970 /// understand. 1971 #[repr(C, u8)] 1972 #[derive( 1973 Clone, 1974 Debug, 1975 MallocSizeOf, 1976 Parse, 1977 PartialEq, 1978 SpecifiedValueInfo, 1979 ToComputedValue, 1980 ToCss, 1981 ToResolvedValue, 1982 ToShmem, 1983 ToTyped, 1984 )] 1985 pub enum GridTemplateAreas { 1986 /// The `none` value. 1987 None, 1988 /// The actual value. 1989 Areas(TemplateAreasArc), 1990 } 1991 1992 impl GridTemplateAreas { 1993 #[inline] 1994 /// Get default value as `none` 1995 pub fn none() -> GridTemplateAreas { 1996 GridTemplateAreas::None 1997 } 1998 } 1999 2000 /// A specified value for the `z-index` property. 2001 pub type ZIndex = GenericZIndex<Integer>; 2002 2003 /// A specified value for the `aspect-ratio` property. 2004 pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>; 2005 2006 impl Parse for AspectRatio { 2007 fn parse<'i, 't>( 2008 context: &ParserContext, 2009 input: &mut Parser<'i, 't>, 2010 ) -> Result<Self, ParseError<'i>> { 2011 use crate::values::generics::position::PreferredRatio; 2012 use crate::values::specified::Ratio; 2013 2014 let location = input.current_source_location(); 2015 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto")); 2016 let ratio = input.try_parse(|i| Ratio::parse(context, i)); 2017 if auto.is_err() { 2018 auto = input.try_parse(|i| i.expect_ident_matching("auto")); 2019 } 2020 2021 if auto.is_err() && ratio.is_err() { 2022 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 2023 } 2024 2025 Ok(AspectRatio { 2026 auto: auto.is_ok(), 2027 ratio: match ratio { 2028 Ok(ratio) => PreferredRatio::Ratio(ratio), 2029 Err(..) => PreferredRatio::None, 2030 }, 2031 }) 2032 } 2033 } 2034 2035 impl AspectRatio { 2036 /// Returns Self by a valid ratio. 2037 pub fn from_mapped_ratio(w: f32, h: f32) -> Self { 2038 use crate::values::generics::position::PreferredRatio; 2039 use crate::values::generics::ratio::Ratio; 2040 AspectRatio { 2041 auto: true, 2042 ratio: PreferredRatio::Ratio(Ratio( 2043 NonNegativeNumber::new(w), 2044 NonNegativeNumber::new(h), 2045 )), 2046 } 2047 } 2048 } 2049 2050 /// A specified value for inset types. 2051 pub type Inset = GenericInset<specified::Percentage, LengthPercentage>; 2052 2053 impl Inset { 2054 /// Parses an inset type, allowing the unitless length quirk. 2055 /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> 2056 #[inline] 2057 pub fn parse_quirky<'i, 't>( 2058 context: &ParserContext, 2059 input: &mut Parser<'i, 't>, 2060 allow_quirks: AllowQuirks, 2061 ) -> Result<Self, ParseError<'i>> { 2062 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) 2063 { 2064 return Ok(Self::LengthPercentage(l)); 2065 } 2066 match input.try_parse(|i| i.expect_ident_matching("auto")) { 2067 Ok(_) => return Ok(Self::Auto), 2068 Err(e) if !static_prefs::pref!("layout.css.anchor-positioning.enabled") => { 2069 return Err(e.into()) 2070 }, 2071 Err(_) => (), 2072 }; 2073 Self::parse_anchor_functions_quirky(context, input, allow_quirks) 2074 } 2075 2076 fn parse_as_anchor_function_fallback<'i, 't>( 2077 context: &ParserContext, 2078 input: &mut Parser<'i, 't>, 2079 ) -> Result<Self, ParseError<'i>> { 2080 if let Ok(l) = 2081 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No)) 2082 { 2083 return Ok(Self::LengthPercentage(l)); 2084 } 2085 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No) 2086 } 2087 2088 fn parse_anchor_functions_quirky<'i, 't>( 2089 context: &ParserContext, 2090 input: &mut Parser<'i, 't>, 2091 allow_quirks: AllowQuirks, 2092 ) -> Result<Self, ParseError<'i>> { 2093 debug_assert!( 2094 static_prefs::pref!("layout.css.anchor-positioning.enabled"), 2095 "How are we parsing with pref off?" 2096 ); 2097 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) { 2098 return Ok(Self::AnchorFunction(Box::new(inner))); 2099 } 2100 if let Ok(inner) = 2101 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i)) 2102 { 2103 return Ok(Self::AnchorSizeFunction(Box::new(inner))); 2104 } 2105 Ok(Self::AnchorContainingCalcFunction(input.try_parse( 2106 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks), 2107 )?)) 2108 } 2109 } 2110 2111 impl Parse for Inset { 2112 fn parse<'i, 't>( 2113 context: &ParserContext, 2114 input: &mut Parser<'i, 't>, 2115 ) -> Result<Self, ParseError<'i>> { 2116 Self::parse_quirky(context, input, AllowQuirks::No) 2117 } 2118 } 2119 2120 /// A specified value for `anchor()` function. 2121 pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>; 2122 2123 impl Parse for AnchorFunction { 2124 fn parse<'i, 't>( 2125 context: &ParserContext, 2126 input: &mut Parser<'i, 't>, 2127 ) -> Result<Self, ParseError<'i>> { 2128 if !static_prefs::pref!("layout.css.anchor-positioning.enabled") { 2129 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 2130 } 2131 input.expect_function_matching("anchor")?; 2132 input.parse_nested_block(|i| { 2133 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok(); 2134 let side = GenericAnchorSide::parse(context, i)?; 2135 let target_element = if target_element.is_none() { 2136 i.try_parse(|i| DashedIdent::parse(context, i)).ok() 2137 } else { 2138 target_element 2139 }; 2140 let fallback = i 2141 .try_parse(|i| { 2142 i.expect_comma()?; 2143 Inset::parse_as_anchor_function_fallback(context, i) 2144 }) 2145 .ok(); 2146 Ok(Self { 2147 target_element: target_element.unwrap_or_else(DashedIdent::empty), 2148 side, 2149 fallback: fallback.into(), 2150 }) 2151 }) 2152 } 2153 }