1use crate::Atom;
11use crate::derives::*;
12use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode};
13use crate::parser::{Parse, ParserContext};
14use crate::selector_map::PrecomputedHashMap;
15use crate::str::HTML_SPACE_CHARACTERS;
16use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
17use crate::values::computed::{Context, Percentage, ToComputedValue};
18use crate::values::generics::length::GenericAnchorSizeFunction;
19use crate::values::generics::position::PositionComponent as GenericPositionComponent;
20use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
21use crate::values::generics::position::ZIndex as GenericZIndex;
22use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericAnchorSide};
23use crate::values::generics::position::{GenericAnchorFunction, GenericInset, TreeScoped};
24use crate::values::generics::position::{IsTreeScoped, Position as GenericPosition};
25use crate::values::specified;
26use crate::values::specified::align::AlignFlags;
27use crate::values::specified::percentage::NoCalcPercentage;
28use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
29use crate::values::{AtomIdent, DashedIdent};
30use cssparser::Parser;
31use hashbrown::hash_map::Entry;
32use num_traits::FromPrimitive;
33use servo_arc::Arc;
34use smallvec::{SmallVec, smallvec};
35use std::fmt::{self, Write};
36use style_traits::arc_slice::ArcSlice;
37use style_traits::values::specified::AllowedNumericType;
38use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
39use thin_vec::ThinVec;
40
41pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
43
44pub type PositionOrAuto = GenericPositionOrAuto<Position>;
46
47pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
49
50pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
52
53#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
55#[typed(todo_derive_fields)]
56pub enum PositionComponent<S> {
57 Center,
59 Length(LengthPercentage),
61 Side(S, Option<LengthPercentage>),
63}
64
65#[derive(
67 Clone,
68 Copy,
69 Debug,
70 Eq,
71 Hash,
72 MallocSizeOf,
73 Parse,
74 PartialEq,
75 SpecifiedValueInfo,
76 ToComputedValue,
77 ToCss,
78 ToResolvedValue,
79 ToShmem,
80)]
81#[allow(missing_docs)]
82#[repr(u8)]
83pub enum HorizontalPositionKeyword {
84 Left,
85 Right,
86}
87
88#[derive(
90 Clone,
91 Copy,
92 Debug,
93 Eq,
94 Hash,
95 MallocSizeOf,
96 Parse,
97 PartialEq,
98 SpecifiedValueInfo,
99 ToComputedValue,
100 ToCss,
101 ToResolvedValue,
102 ToShmem,
103)]
104#[allow(missing_docs)]
105#[repr(u8)]
106pub enum VerticalPositionKeyword {
107 Top,
108 Bottom,
109}
110
111impl Parse for Position {
112 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
113 let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
114 if position.is_three_value_syntax() {
115 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
116 }
117 Ok(position)
118 }
119}
120
121impl Position {
122 pub fn parse_three_value_quirky(
124 context: &ParserContext,
125 input: &mut Parser,
126 allow_quirks: AllowQuirks,
127 ) -> Result<Self, ParseError> {
128 match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
129 Ok(x_pos @ PositionComponent::Center) => {
130 if let Ok(y_pos) =
131 input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
132 {
133 return Ok(Self::new(x_pos, y_pos));
134 }
135 let x_pos = input
136 .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
137 .unwrap_or(x_pos);
138 let y_pos = PositionComponent::Center;
139 return Ok(Self::new(x_pos, y_pos));
140 },
141 Ok(PositionComponent::Side(x_keyword, lp)) => {
142 if input
143 .try_parse(|i| i.expect_ident_matching("center"))
144 .is_ok()
145 {
146 let x_pos = PositionComponent::Side(x_keyword, lp);
147 let y_pos = PositionComponent::Center;
148 return Ok(Self::new(x_pos, y_pos));
149 }
150 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
151 let y_lp = input
152 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
153 .ok();
154 let x_pos = PositionComponent::Side(x_keyword, lp);
155 let y_pos = PositionComponent::Side(y_keyword, y_lp);
156 return Ok(Self::new(x_pos, y_pos));
157 }
158 let x_pos = PositionComponent::Side(x_keyword, None);
159 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
160 return Ok(Self::new(x_pos, y_pos));
161 },
162 Ok(x_pos @ PositionComponent::Length(_)) => {
163 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
164 let y_pos = PositionComponent::Side(y_keyword, None);
165 return Ok(Self::new(x_pos, y_pos));
166 }
167 if let Ok(y_lp) =
168 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
169 {
170 let y_pos = PositionComponent::Length(y_lp);
171 return Ok(Self::new(x_pos, y_pos));
172 }
173 let y_pos = PositionComponent::Center;
174 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
175 return Ok(Self::new(x_pos, y_pos));
176 },
177 Err(_) => {},
178 }
179 let y_keyword = VerticalPositionKeyword::parse(input)?;
180 let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
181 let y_lp = i
182 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
183 .ok();
184 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
185 let x_lp = i
186 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
187 .ok();
188 let x_pos = PositionComponent::Side(x_keyword, x_lp);
189 return Ok((y_lp, x_pos));
190 };
191 i.expect_ident_matching("center")?;
192 let x_pos = PositionComponent::Center;
193 Ok((y_lp, x_pos))
194 });
195 if let Ok((y_lp, x_pos)) = lp_and_x_pos {
196 let y_pos = PositionComponent::Side(y_keyword, y_lp);
197 return Ok(Self::new(x_pos, y_pos));
198 }
199 let x_pos = PositionComponent::Center;
200 let y_pos = PositionComponent::Side(y_keyword, None);
201 Ok(Self::new(x_pos, y_pos))
202 }
203
204 #[inline]
206 pub fn center() -> Self {
207 Self::new(PositionComponent::Center, PositionComponent::Center)
208 }
209
210 #[inline]
212 fn is_three_value_syntax(&self) -> bool {
213 self.horizontal.component_count() != self.vertical.component_count()
214 }
215}
216
217impl ToCss for Position {
218 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
219 where
220 W: Write,
221 {
222 match (&self.horizontal, &self.vertical) {
223 (x_pos @ &PositionComponent::Side(_, Some(_)), PositionComponent::Length(y_lp)) => {
224 x_pos.to_css(dest)?;
225 dest.write_str(" top ")?;
226 y_lp.to_css(dest)
227 },
228 (PositionComponent::Length(x_lp), y_pos @ &PositionComponent::Side(_, Some(_))) => {
229 dest.write_str("left ")?;
230 x_lp.to_css(dest)?;
231 dest.write_char(' ')?;
232 y_pos.to_css(dest)
233 },
234 (x_pos, y_pos) => {
235 x_pos.to_css(dest)?;
236 dest.write_char(' ')?;
237 y_pos.to_css(dest)
238 },
239 }
240 }
241}
242
243impl<S: Parse> Parse for PositionComponent<S> {
244 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
245 Self::parse_quirky(context, input, AllowQuirks::No)
246 }
247}
248
249impl<S: Parse> PositionComponent<S> {
250 pub fn parse_quirky(
252 context: &ParserContext,
253 input: &mut Parser,
254 allow_quirks: AllowQuirks,
255 ) -> Result<Self, ParseError> {
256 if input
257 .try_parse(|i| i.expect_ident_matching("center"))
258 .is_ok()
259 {
260 return Ok(PositionComponent::Center);
261 }
262 if let Ok(lp) =
263 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
264 {
265 return Ok(PositionComponent::Length(lp));
266 }
267 let keyword = S::parse(context, input)?;
268 let lp = input
269 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
270 .ok();
271 Ok(PositionComponent::Side(keyword, lp))
272 }
273}
274
275impl<S> GenericPositionComponent for PositionComponent<S> {
276 fn is_center(&self) -> bool {
277 match *self {
278 PositionComponent::Center => true,
279 PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.get() == 0.5,
280 PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => {
282 per.get() == 0.5
283 },
284 _ => false,
285 }
286 }
287}
288
289impl<S> PositionComponent<S> {
290 pub fn zero() -> Self {
292 PositionComponent::Length(LengthPercentage::Percentage(NoCalcPercentage::zero()))
293 }
294
295 fn component_count(&self) -> usize {
297 match *self {
298 PositionComponent::Length(..) | PositionComponent::Center => 1,
299 PositionComponent::Side(_, ref lp) => {
300 if lp.is_some() {
301 2
302 } else {
303 1
304 }
305 },
306 }
307 }
308}
309
310impl<S: Side> ToComputedValue for PositionComponent<S> {
311 type ComputedValue = ComputedLengthPercentage;
312
313 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
314 match *self {
315 PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
316 PositionComponent::Side(ref keyword, None) => {
317 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
318 ComputedLengthPercentage::new_percent(p)
319 },
320 PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
321 let length = length.to_computed_value(context);
322 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
324 },
325 PositionComponent::Side(_, Some(ref length))
326 | PositionComponent::Length(ref length) => length.to_computed_value(context),
327 }
328 }
329
330 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
331 PositionComponent::Length(ToComputedValue::from_computed_value(computed))
332 }
333}
334
335impl<S: Side> PositionComponent<S> {
336 pub fn initial_specified_value() -> Self {
338 PositionComponent::Side(S::start(), None)
339 }
340}
341
342#[derive(
344 Animate,
345 Clone,
346 Debug,
347 MallocSizeOf,
348 PartialEq,
349 SpecifiedValueInfo,
350 ToComputedValue,
351 ToCss,
352 ToResolvedValue,
353 ToShmem,
354 ToTyped,
355)]
356#[css(comma)]
357#[repr(transparent)]
358#[typed(todo_derive_fields)]
359pub struct AnchorNameIdent(
360 #[css(iterable, if_empty = "none")]
361 #[ignore_malloc_size_of = "Arc"]
362 #[animation(constant)]
363 pub crate::ArcSlice<DashedIdent>,
364);
365
366impl AnchorNameIdent {
367 pub fn none() -> Self {
369 Self(Default::default())
370 }
371}
372
373impl Parse for AnchorNameIdent {
374 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
375 let first = input.expect_ident()?;
376 if first.eq_ignore_ascii_case("none") {
377 return Ok(Self::none());
378 }
379 let mut idents: SmallVec<[DashedIdent; 4]> = smallvec![DashedIdent::from_ident(first,)?];
382 while input.try_parse(|input| input.expect_comma()).is_ok() {
383 idents.push(DashedIdent::parse(context, input)?);
384 }
385 Ok(AnchorNameIdent(ArcSlice::from_iter(idents.drain(..))))
386 }
387}
388
389impl IsTreeScoped for AnchorNameIdent {
390 fn is_tree_scoped(&self) -> bool {
391 !self.0.is_empty()
392 }
393}
394
395pub type AnchorName = TreeScoped<AnchorNameIdent>;
397
398impl AnchorName {
399 pub fn none() -> Self {
401 Self::with_default_level(AnchorNameIdent::none())
402 }
403}
404
405#[derive(
407 Clone,
408 Debug,
409 MallocSizeOf,
410 PartialEq,
411 SpecifiedValueInfo,
412 ToComputedValue,
413 ToCss,
414 ToResolvedValue,
415 ToShmem,
416 ToTyped,
417)]
418#[repr(transparent)]
419#[css(comma)]
420#[typed(todo_derive_fields)]
421#[value_info(other_values = "all")]
422pub struct ScopedNameList(
423 #[css(iterable, if_empty = "none")]
425 #[ignore_malloc_size_of = "Arc"]
426 crate::ArcSlice<AtomIdent>,
427);
428
429impl ScopedNameList {
430 pub fn none() -> Self {
432 Self(crate::ArcSlice::default())
433 }
434
435 pub fn is_none(&self) -> bool {
437 self.0.is_empty()
438 }
439
440 pub fn all() -> Self {
442 static ALL: std::sync::LazyLock<ScopedNameList> = std::sync::LazyLock::new(|| {
443 ScopedNameList(crate::ArcSlice::from_iter_leaked(std::iter::once(
444 AtomIdent::new(atom!("all")),
445 )))
446 });
447 ALL.clone()
448 }
449}
450
451impl Parse for ScopedNameList {
452 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
453 let first = input.expect_ident()?;
454 if first.eq_ignore_ascii_case("none") {
455 return Ok(Self::none());
456 }
457 if first.eq_ignore_ascii_case("all") {
458 return Ok(Self::all());
459 }
460 let mut idents = SmallVec::<[AtomIdent; 8]>::new();
463 idents.push(AtomIdent::new(DashedIdent::from_ident(first)?.0));
464 while input.try_parse(|input| input.expect_comma()).is_ok() {
465 idents.push(AtomIdent::new(DashedIdent::parse(context, input)?.0));
466 }
467 Ok(Self(ArcSlice::from_iter(idents.drain(..))))
468 }
469}
470
471impl IsTreeScoped for ScopedNameList {
472 fn is_tree_scoped(&self) -> bool {
473 !self.is_none()
474 }
475}
476
477pub type ScopedName = TreeScoped<ScopedNameList>;
480
481impl ScopedName {
482 pub fn none() -> Self {
484 Self::with_default_level(ScopedNameList::none())
485 }
486
487 pub fn is_none(&self) -> bool {
489 self.value.is_none()
490 }
491}
492
493#[derive(
495 Clone,
496 Debug,
497 MallocSizeOf,
498 Parse,
499 PartialEq,
500 SpecifiedValueInfo,
501 ToComputedValue,
502 ToCss,
503 ToResolvedValue,
504 ToShmem,
505 ToTyped,
506)]
507#[repr(u8)]
508#[typed(todo_derive_fields)]
509pub enum PositionAnchorKeyword {
510 Normal,
512 None,
514 Auto,
516 Ident(DashedIdent),
518}
519
520impl IsTreeScoped for PositionAnchorKeyword {
521 fn is_tree_scoped(&self) -> bool {
522 match *self {
523 Self::Normal | Self::None | Self::Auto => false,
524 Self::Ident(_) => true,
525 }
526 }
527}
528
529pub type PositionAnchor = TreeScoped<PositionAnchorKeyword>;
531
532impl PositionAnchor {
533 pub fn normal() -> Self {
535 Self::with_default_level(PositionAnchorKeyword::Normal)
536 }
537}
538
539#[derive(
540 Clone,
541 Copy,
542 Debug,
543 Eq,
544 MallocSizeOf,
545 Parse,
546 PartialEq,
547 Serialize,
548 SpecifiedValueInfo,
549 ToComputedValue,
550 ToCss,
551 ToResolvedValue,
552 ToShmem,
553)]
554#[repr(u8)]
555pub enum PositionTryFallbacksTryTacticKeyword {
557 FlipBlock,
559 FlipInline,
561 FlipStart,
563 FlipX,
565 FlipY,
567}
568
569#[derive(
570 Clone,
571 Debug,
572 Default,
573 Eq,
574 MallocSizeOf,
575 PartialEq,
576 SpecifiedValueInfo,
577 ToComputedValue,
578 ToCss,
579 ToResolvedValue,
580 ToShmem,
581)]
582#[repr(transparent)]
583pub struct PositionTryFallbacksTryTactic(
588 #[css(iterable)] pub ThinVec<PositionTryFallbacksTryTacticKeyword>,
589);
590
591impl Parse for PositionTryFallbacksTryTactic {
592 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
593 let mut result = ThinVec::with_capacity(5);
594 for _ in 0..5 {
596 if let Ok(kw) = input.try_parse(PositionTryFallbacksTryTacticKeyword::parse) {
597 if result.contains(&kw) {
598 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
599 }
600 result.push(kw);
601 } else {
602 break;
603 }
604 }
605 if result.is_empty() {
606 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
607 }
608 Ok(Self(result))
609 }
610}
611
612impl PositionTryFallbacksTryTactic {
613 #[inline]
615 pub fn is_empty(&self) -> bool {
616 self.0.is_empty()
617 }
618
619 #[inline]
621 pub fn iter(&self) -> impl Iterator<Item = &PositionTryFallbacksTryTacticKeyword> {
622 self.0.iter()
623 }
624}
625
626#[derive(
627 Clone,
628 Debug,
629 MallocSizeOf,
630 PartialEq,
631 SpecifiedValueInfo,
632 ToComputedValue,
633 ToCss,
634 ToResolvedValue,
635 ToShmem,
636)]
637#[repr(C)]
638pub struct DashedIdentAndOrTryTactic {
641 pub ident: DashedIdent,
643 pub try_tactic: PositionTryFallbacksTryTactic,
645}
646
647impl Parse for DashedIdentAndOrTryTactic {
648 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
649 let mut result = Self {
650 ident: DashedIdent::empty(),
651 try_tactic: PositionTryFallbacksTryTactic::default(),
652 };
653
654 loop {
655 if result.ident.is_empty()
656 && let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i))
657 {
658 result.ident = ident;
659 continue;
660 }
661 if result.try_tactic.is_empty()
662 && let Ok(try_tactic) =
663 input.try_parse(|i| PositionTryFallbacksTryTactic::parse(context, i))
664 {
665 result.try_tactic = try_tactic;
666 continue;
667 }
668 break;
669 }
670
671 if result.ident.is_empty() && result.try_tactic.is_empty() {
672 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
673 }
674 Ok(result)
675 }
676}
677
678#[derive(
679 Clone,
680 Debug,
681 MallocSizeOf,
682 Parse,
683 PartialEq,
684 SpecifiedValueInfo,
685 ToComputedValue,
686 ToCss,
687 ToResolvedValue,
688 ToShmem,
689)]
690#[repr(u8)]
691pub enum PositionTryFallbacksItem {
694 IdentAndOrTactic(DashedIdentAndOrTryTactic),
696 #[parse(parse_fn = "PositionArea::parse_except_none")]
697 PositionArea(PositionArea),
699}
700
701#[derive(
702 Clone,
703 Debug,
704 Default,
705 MallocSizeOf,
706 PartialEq,
707 SpecifiedValueInfo,
708 ToComputedValue,
709 ToCss,
710 ToResolvedValue,
711 ToShmem,
712 ToTyped,
713)]
714#[css(comma)]
715#[repr(C)]
716#[typed(todo_derive_fields)]
717pub struct PositionTryFallbacksList(
719 #[css(iterable, if_empty = "none")]
720 #[ignore_malloc_size_of = "Arc"]
721 pub crate::ArcSlice<PositionTryFallbacksItem>,
722);
723
724impl IsTreeScoped for PositionTryFallbacksList {
725 fn is_tree_scoped(&self) -> bool {
726 !self.is_none()
727 }
728}
729
730impl PositionTryFallbacksList {
731 #[inline]
732 pub fn none() -> Self {
734 Self(Default::default())
735 }
736
737 pub fn is_none(&self) -> bool {
739 self.0.is_empty()
740 }
741}
742
743impl Parse for PositionTryFallbacksList {
744 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
745 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
746 return Ok(Self::none());
747 }
748 let mut items: SmallVec<[PositionTryFallbacksItem; 4]> =
751 smallvec![PositionTryFallbacksItem::parse(context, input)?];
752 while input.try_parse(|input| input.expect_comma()).is_ok() {
753 items.push(PositionTryFallbacksItem::parse(context, input)?);
754 }
755 Ok(Self(ArcSlice::from_iter(items.drain(..))))
756 }
757}
758
759pub type PositionTryFallbacks = TreeScoped<PositionTryFallbacksList>;
761
762impl PositionTryFallbacks {
763 pub fn none() -> Self {
765 Self::with_default_level(PositionTryFallbacksList::none())
766 }
767}
768
769#[derive(
771 Clone,
772 Copy,
773 Debug,
774 Default,
775 Eq,
776 MallocSizeOf,
777 Parse,
778 PartialEq,
779 SpecifiedValueInfo,
780 ToComputedValue,
781 ToCss,
782 ToResolvedValue,
783 ToShmem,
784 ToTyped,
785)]
786#[repr(u8)]
787pub enum PositionTryOrder {
788 #[default]
789 Normal,
791 MostWidth,
793 MostHeight,
795 MostBlockSize,
797 MostInlineSize,
799}
800
801impl PositionTryOrder {
802 #[inline]
803 pub fn normal() -> Self {
805 Self::Normal
806 }
807
808 pub fn is_normal(&self) -> bool {
810 *self == Self::Normal
811 }
812}
813
814#[derive(
815 Clone,
816 Copy,
817 Debug,
818 Eq,
819 MallocSizeOf,
820 Parse,
821 PartialEq,
822 Serialize,
823 SpecifiedValueInfo,
824 ToComputedValue,
825 ToCss,
826 ToResolvedValue,
827 ToShmem,
828 ToTyped,
829)]
830#[css(bitflags(single = "always", mixed = "anchors-valid,anchors-visible,no-overflow"))]
831#[repr(C)]
832pub struct PositionVisibility(u8);
834bitflags! {
835 impl PositionVisibility: u8 {
836 const ALWAYS = 0;
838 const ANCHORS_VALID = 1 << 0;
840 const ANCHORS_VISIBLE = 1 << 1;
842 const NO_OVERFLOW = 1 << 2;
844 }
845}
846
847impl Default for PositionVisibility {
848 fn default() -> Self {
849 Self::ALWAYS
850 }
851}
852
853impl PositionVisibility {
854 #[inline]
855 pub fn always() -> Self {
857 Self::ALWAYS
858 }
859}
860
861#[repr(u8)]
864#[derive(Clone, Copy, Debug, Eq, PartialEq)]
865pub enum PositionAreaType {
866 Physical,
868 Logical,
870 SelfLogical,
872 Inferred,
874 SelfInferred,
876 Common,
878 None,
880}
881
882#[repr(u8)]
889#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
890#[allow(missing_docs)]
891pub enum PositionAreaAxis {
892 Horizontal = 0b000,
893 Vertical = 0b001,
894
895 X = 0b010,
896 Y = 0b011,
897
898 Block = 0b110,
899 Inline = 0b111,
900
901 Inferred = 0b100,
902 None = 0b101,
903}
904
905impl PositionAreaAxis {
906 pub fn is_physical(self) -> bool {
908 (self as u8 & 0b100) == 0
909 }
910
911 fn is_flow_relative_direction(self) -> bool {
913 self == Self::Inferred || (self as u8 & 0b10) != 0
914 }
915
916 fn is_canonically_first(self) -> bool {
918 self != Self::Inferred && (self as u8) & 1 == 0
919 }
920
921 #[allow(unused)]
922 fn flip(self) -> Self {
923 if matches!(self, Self::Inferred | Self::None) {
924 return self;
925 }
926 Self::from_u8(self as u8 ^ 1u8).unwrap()
927 }
928
929 fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> {
930 Some(match self {
931 PositionAreaAxis::Horizontal | PositionAreaAxis::X => {
932 if wm.is_vertical() {
933 LogicalAxis::Block
934 } else {
935 LogicalAxis::Inline
936 }
937 },
938 PositionAreaAxis::Vertical | PositionAreaAxis::Y => {
939 if wm.is_vertical() {
940 LogicalAxis::Inline
941 } else {
942 LogicalAxis::Block
943 }
944 },
945 PositionAreaAxis::Block => LogicalAxis::Block,
946 PositionAreaAxis::Inline => LogicalAxis::Inline,
947 PositionAreaAxis::Inferred => inferred,
948 PositionAreaAxis::None => return None,
949 })
950 }
951}
952
953#[repr(u8)]
956#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)]
957pub enum PositionAreaTrack {
958 Start = 0b001,
960 SpanStart = 0b011,
962 End = 0b100,
964 SpanEnd = 0b110,
966 Center = 0b010,
968 SpanAll = 0b111,
970}
971
972impl PositionAreaTrack {
973 fn flip(self) -> Self {
974 match self {
975 Self::Start => Self::End,
976 Self::SpanStart => Self::SpanEnd,
977 Self::End => Self::Start,
978 Self::SpanEnd => Self::SpanStart,
979 Self::Center | Self::SpanAll => self,
980 }
981 }
982
983 fn start(self) -> bool {
984 self as u8 & 1 != 0
985 }
986}
987
988pub const AXIS_SHIFT: usize = 3;
990pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT;
992pub const TRACK_MASK: u8 = 0b111u8;
994pub const SELF_WM: u8 = 1u8 << 6;
996
997#[derive(
998 Clone,
999 Copy,
1000 Debug,
1001 Default,
1002 Eq,
1003 MallocSizeOf,
1004 Parse,
1005 PartialEq,
1006 SpecifiedValueInfo,
1007 ToComputedValue,
1008 ToCss,
1009 ToResolvedValue,
1010 ToShmem,
1011 FromPrimitive,
1012)]
1013#[allow(missing_docs)]
1014#[repr(u8)]
1015pub enum PositionAreaKeyword {
1020 #[default]
1021 None = (PositionAreaAxis::None as u8) << AXIS_SHIFT,
1022
1023 Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8,
1025 SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8,
1026
1027 Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1029 End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1030 SpanStart =
1031 ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1032 SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1033
1034 Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1036 Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1037 Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1038 Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1039
1040 XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1042 XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1043 YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1044 YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1045
1046 BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1048 BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1049 InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8,
1050 InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8,
1051
1052 SpanLeft =
1054 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1055 SpanRight =
1056 ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1057 SpanTop =
1058 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1059 SpanBottom =
1060 ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1061
1062 SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1064 SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1065 SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1066 SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1067
1068 SpanBlockStart =
1070 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1071 SpanBlockEnd =
1072 ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1073 SpanInlineStart =
1074 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8,
1075 SpanInlineEnd =
1076 ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8,
1077
1078 SelfStart = SELF_WM | (Self::Start as u8),
1080 SelfEnd = SELF_WM | (Self::End as u8),
1081 SpanSelfStart = SELF_WM | (Self::SpanStart as u8),
1082 SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8),
1083
1084 SelfXStart = SELF_WM | (Self::XStart as u8),
1085 SelfXEnd = SELF_WM | (Self::XEnd as u8),
1086 SelfYStart = SELF_WM | (Self::YStart as u8),
1087 SelfYEnd = SELF_WM | (Self::YEnd as u8),
1088 SelfBlockStart = SELF_WM | (Self::BlockStart as u8),
1089 SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8),
1090 SelfInlineStart = SELF_WM | (Self::InlineStart as u8),
1091 SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8),
1092
1093 SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8),
1094 SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8),
1095 SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8),
1096 SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8),
1097 SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8),
1098 SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8),
1099 SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8),
1100 SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8),
1101}
1102
1103impl PositionAreaKeyword {
1104 #[inline]
1106 pub fn none() -> Self {
1107 Self::None
1108 }
1109
1110 pub fn is_none(&self) -> bool {
1112 *self == Self::None
1113 }
1114
1115 pub fn self_wm(self) -> bool {
1117 (self as u8 & SELF_WM) != 0
1118 }
1119
1120 pub fn axis(self) -> PositionAreaAxis {
1122 PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap()
1123 }
1124
1125 pub fn with_axis(self, axis: PositionAreaAxis) -> Self {
1127 Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap()
1128 }
1129
1130 pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self {
1132 if self.axis() == PositionAreaAxis::Inferred {
1133 self.with_axis(axis)
1134 } else {
1135 self
1136 }
1137 }
1138
1139 pub fn track(self) -> Option<PositionAreaTrack> {
1141 let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK);
1142 debug_assert_eq!(
1143 result.is_none(),
1144 self.is_none(),
1145 "Only the none keyword has no track"
1146 );
1147 result
1148 }
1149
1150 fn group_type(self) -> PositionAreaType {
1151 let axis = self.axis();
1152 if axis == PositionAreaAxis::None {
1153 if self.is_none() {
1154 return PositionAreaType::None;
1155 }
1156 return PositionAreaType::Common;
1157 }
1158 if axis == PositionAreaAxis::Inferred {
1159 return if self.self_wm() {
1160 PositionAreaType::SelfInferred
1161 } else {
1162 PositionAreaType::Inferred
1163 };
1164 }
1165 if axis.is_physical() {
1166 return PositionAreaType::Physical;
1167 }
1168 if self.self_wm() {
1169 PositionAreaType::SelfLogical
1170 } else {
1171 PositionAreaType::Logical
1172 }
1173 }
1174
1175 fn to_physical(
1176 self,
1177 cb_wm: WritingMode,
1178 self_wm: WritingMode,
1179 inferred_axis: LogicalAxis,
1180 ) -> Self {
1181 let wm = if self.self_wm() { self_wm } else { cb_wm };
1182 let axis = self.axis();
1183 if !axis.is_flow_relative_direction() {
1184 return self;
1185 }
1186 let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else {
1187 return self;
1188 };
1189 let Some(track) = self.track() else {
1190 debug_assert!(false, "How did we end up with no track here? {self:?}");
1191 return self;
1192 };
1193 let start = track.start();
1194 let logical_side = match logical_axis {
1195 LogicalAxis::Block => {
1196 if start {
1197 LogicalSide::BlockStart
1198 } else {
1199 LogicalSide::BlockEnd
1200 }
1201 },
1202 LogicalAxis::Inline => {
1203 if start {
1204 LogicalSide::InlineStart
1205 } else {
1206 LogicalSide::InlineEnd
1207 }
1208 },
1209 };
1210 let physical_side = logical_side.to_physical(wm);
1211 let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left);
1212 let new_track = if physical_start != start {
1213 track.flip()
1214 } else {
1215 track
1216 };
1217 let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) {
1218 PositionAreaAxis::Vertical
1219 } else {
1220 PositionAreaAxis::Horizontal
1221 };
1222 Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap()
1223 }
1224
1225 fn flip_track(self) -> Self {
1226 let Some(old_track) = self.track() else {
1227 return self;
1228 };
1229 let new_track = old_track.flip();
1230 Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap()
1231 }
1232
1233 pub fn to_self_alignment(self, axis: LogicalAxis, cb_wm: &WritingMode) -> Option<AlignFlags> {
1241 let track = self.track()?;
1242 Some(match track {
1243 PositionAreaTrack::Center => AlignFlags::CENTER,
1245 PositionAreaTrack::SpanAll => AlignFlags::ANCHOR_CENTER,
1247 _ => {
1250 debug_assert_eq!(self.group_type(), PositionAreaType::Physical);
1251 if axis == LogicalAxis::Inline {
1252 if track.start() == cb_wm.intersects(WritingMode::INLINE_REVERSED) {
1256 AlignFlags::START
1257 } else {
1258 AlignFlags::END
1259 }
1260 } else {
1261 if track.start() == cb_wm.is_vertical_rl() {
1264 AlignFlags::START
1265 } else {
1266 AlignFlags::END
1267 }
1268 }
1269 },
1270 })
1271 }
1272}
1273
1274#[derive(
1275 Clone,
1276 Copy,
1277 Debug,
1278 Eq,
1279 MallocSizeOf,
1280 PartialEq,
1281 SpecifiedValueInfo,
1282 ToCss,
1283 ToResolvedValue,
1284 ToShmem,
1285 ToTyped,
1286)]
1287#[repr(C)]
1288#[typed(todo_derive_fields)]
1289pub struct PositionArea {
1291 pub first: PositionAreaKeyword,
1293 #[css(skip_if = "PositionAreaKeyword::is_none")]
1295 pub second: PositionAreaKeyword,
1296}
1297
1298impl PositionArea {
1299 #[inline]
1301 pub fn none() -> Self {
1302 Self {
1303 first: PositionAreaKeyword::None,
1304 second: PositionAreaKeyword::None,
1305 }
1306 }
1307
1308 #[inline]
1310 pub fn is_none(&self) -> bool {
1311 self.first.is_none()
1312 }
1313
1314 pub fn parse_except_none(
1316 context: &ParserContext,
1317 input: &mut Parser,
1318 ) -> Result<Self, ParseError> {
1319 Self::parse_internal(context, input, false)
1320 }
1321
1322 pub fn get_type(&self) -> PositionAreaType {
1324 let first = self.first.group_type();
1325 let second = self.second.group_type();
1326 if matches!(second, PositionAreaType::None | PositionAreaType::Common) {
1327 return first;
1328 }
1329 if first == PositionAreaType::Common {
1330 return second;
1331 }
1332 if first != second {
1333 return PositionAreaType::None;
1334 }
1335 let first_axis = self.first.axis();
1336 if first_axis != PositionAreaAxis::Inferred
1337 && first_axis.is_canonically_first() == self.second.axis().is_canonically_first()
1338 {
1339 return PositionAreaType::None;
1340 }
1341 first
1342 }
1343
1344 fn parse_internal(
1345 _: &ParserContext,
1346 input: &mut Parser,
1347 allow_none: bool,
1348 ) -> Result<Self, ParseError> {
1349 let mut first = PositionAreaKeyword::parse(input)?;
1350 if first.is_none() {
1351 if allow_none {
1352 return Ok(Self::none());
1353 }
1354 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1355 }
1356
1357 let second = input.try_parse(PositionAreaKeyword::parse);
1358 if let Ok(PositionAreaKeyword::None) = second {
1359 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1361 }
1362 let mut second = second.unwrap_or(PositionAreaKeyword::None);
1363 if second.is_none() {
1364 return Ok(Self { first, second });
1370 }
1371
1372 let pair_type = Self { first, second }.get_type();
1373 if pair_type == PositionAreaType::None {
1374 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1376 }
1377 if matches!(
1380 pair_type,
1381 PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical
1382 ) {
1383 if second == PositionAreaKeyword::SpanAll {
1384 second = PositionAreaKeyword::None;
1387 } else if first == PositionAreaKeyword::SpanAll {
1388 first = second;
1389 second = PositionAreaKeyword::None;
1390 }
1391 }
1392 if first == second {
1393 second = PositionAreaKeyword::None;
1394 }
1395 let mut result = Self { first, second };
1396 result.canonicalize_order();
1397 Ok(result)
1398 }
1399
1400 fn canonicalize_order(&mut self) {
1401 let first_axis = self.first.axis();
1402 if first_axis.is_canonically_first() || self.second.is_none() {
1403 return;
1404 }
1405 let second_axis = self.second.axis();
1406 if first_axis == second_axis {
1407 return;
1409 }
1410 if second_axis.is_canonically_first()
1411 || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred)
1412 {
1413 std::mem::swap(&mut self.first, &mut self.second);
1414 }
1415 }
1416
1417 fn make_missing_second_explicit(&mut self) {
1418 if !self.second.is_none() {
1419 return;
1420 }
1421 let axis = self.first.axis();
1422 if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) {
1423 self.second = self.first;
1424 return;
1425 }
1426 self.second = PositionAreaKeyword::SpanAll;
1427 if !axis.is_canonically_first() {
1428 std::mem::swap(&mut self.first, &mut self.second);
1429 }
1430 }
1431
1432 pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self {
1434 self.make_missing_second_explicit();
1435 if self.first.axis() == PositionAreaAxis::None
1441 && self.second.axis() == PositionAreaAxis::None
1442 && !cb_wm.is_vertical()
1443 {
1444 std::mem::swap(&mut self.first, &mut self.second);
1445 } else {
1446 self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block);
1447 self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline);
1448 self.canonicalize_order();
1449 }
1450 self
1451 }
1452
1453 fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) {
1454 if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) {
1455 self.first = self.first.flip_track();
1456 } else {
1457 self.second = self.second.flip_track();
1458 }
1459 }
1460
1461 fn flip_start(&mut self) {
1462 self.first = self.first.with_axis(self.first.axis().flip());
1463 self.second = self.second.with_axis(self.second.axis().flip());
1464 }
1465
1466 pub fn with_tactic(
1468 mut self,
1469 wm: WritingMode,
1470 tactic: PositionTryFallbacksTryTacticKeyword,
1471 ) -> Self {
1472 self.make_missing_second_explicit();
1473 let axis_to_flip = match tactic {
1474 PositionTryFallbacksTryTacticKeyword::FlipStart => {
1475 self.flip_start();
1476 return self;
1477 },
1478 PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block,
1479 PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline,
1480 PositionTryFallbacksTryTacticKeyword::FlipX => {
1481 if wm.is_horizontal() {
1482 LogicalAxis::Inline
1483 } else {
1484 LogicalAxis::Block
1485 }
1486 },
1487 PositionTryFallbacksTryTacticKeyword::FlipY => {
1488 if wm.is_vertical() {
1489 LogicalAxis::Inline
1490 } else {
1491 LogicalAxis::Block
1492 }
1493 },
1494 };
1495 self.flip_logical_axis(wm, axis_to_flip);
1496 self
1497 }
1498}
1499
1500impl Parse for PositionArea {
1501 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1502 Self::parse_internal(context, input, true)
1503 }
1504}
1505
1506pub trait Side {
1508 fn start() -> Self;
1510
1511 fn is_start(&self) -> bool;
1513}
1514
1515impl Side for HorizontalPositionKeyword {
1516 #[inline]
1517 fn start() -> Self {
1518 HorizontalPositionKeyword::Left
1519 }
1520
1521 #[inline]
1522 fn is_start(&self) -> bool {
1523 *self == Self::start()
1524 }
1525}
1526
1527impl Side for VerticalPositionKeyword {
1528 #[inline]
1529 fn start() -> Self {
1530 VerticalPositionKeyword::Top
1531 }
1532
1533 #[inline]
1534 fn is_start(&self) -> bool {
1535 *self == Self::start()
1536 }
1537}
1538
1539#[derive(
1543 Clone,
1544 Copy,
1545 Debug,
1546 Eq,
1547 MallocSizeOf,
1548 Parse,
1549 PartialEq,
1550 SpecifiedValueInfo,
1551 ToComputedValue,
1552 ToResolvedValue,
1553 ToShmem,
1554 ToTyped,
1555)]
1556#[css(bitflags(
1557 mixed = "row,column,dense",
1558 validate_mixed = "Self::validate_and_simplify"
1559))]
1560#[repr(C)]
1561pub struct GridAutoFlow(u8);
1562bitflags! {
1563 impl GridAutoFlow: u8 {
1564 const ROW = 1 << 0;
1566 const COLUMN = 1 << 1;
1568 const DENSE = 1 << 2;
1570 }
1571}
1572
1573impl GridAutoFlow {
1574 fn validate_and_simplify(&mut self) -> bool {
1576 if self.contains(Self::ROW | Self::COLUMN) {
1577 return false;
1579 }
1580 if *self == Self::DENSE {
1581 self.insert(Self::ROW);
1583 }
1584 true
1585 }
1586}
1587
1588impl ToCss for GridAutoFlow {
1589 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1590 where
1591 W: Write,
1592 {
1593 let dense = self.intersects(Self::DENSE);
1594 if self.intersects(Self::ROW) {
1595 return if dense {
1596 dest.write_str("dense")
1597 } else {
1598 dest.write_str("row")
1599 };
1600 }
1601 debug_assert!(self.intersects(Self::COLUMN));
1602 if dense {
1603 dest.write_str("column dense")
1604 } else {
1605 dest.write_str("column")
1606 }
1607 }
1608}
1609
1610#[inline]
1612fn flex_wrap_balance_enabled() -> bool {
1613 crate::pref!("layout.flexbox.balance", gecko = false)
1614}
1615
1616#[derive(
1621 Clone,
1622 Copy,
1623 Debug,
1624 Eq,
1625 MallocSizeOf,
1626 Parse,
1627 PartialEq,
1628 SpecifiedValueInfo,
1629 ToComputedValue,
1630 ToCss,
1631 ToResolvedValue,
1632 ToShmem,
1633 ToTyped,
1634)]
1635#[css(bitflags(
1636 single = "nowrap",
1637 mixed = "wrap,wrap-reverse,balance",
1638 validate_mixed = "Self::validate_and_simplify"
1639))]
1640#[repr(C)]
1641pub struct FlexWrap(u8);
1642bitflags! {
1643 impl FlexWrap: u8 {
1644 const NOWRAP = 0;
1646 const WRAP = 1 << 0;
1648 const WRAP_REVERSE = 1 << 1;
1650 const BALANCE = 1 << 2;
1652 }
1653}
1654
1655impl FlexWrap {
1656 fn validate_and_simplify(&mut self) -> bool {
1658 if self.contains(Self::WRAP | Self::WRAP_REVERSE) {
1659 return false;
1660 }
1661 if self.contains(Self::BALANCE) {
1662 if !flex_wrap_balance_enabled() {
1663 return false;
1664 }
1665 self.remove(Self::WRAP);
1667 }
1668 true
1669 }
1670}
1671
1672#[derive(
1673 Clone,
1674 Debug,
1675 MallocSizeOf,
1676 PartialEq,
1677 SpecifiedValueInfo,
1678 ToComputedValue,
1679 ToCss,
1680 ToResolvedValue,
1681 ToShmem,
1682)]
1683#[repr(C)]
1684pub struct TemplateAreas {
1686 #[css(skip)]
1688 pub areas: crate::OwnedSlice<NamedArea>,
1689 #[css(iterable)]
1694 pub strings: crate::OwnedSlice<crate::OwnedStr>,
1695 #[css(skip)]
1697 pub width: u32,
1698}
1699
1700#[derive(Default)]
1702pub struct TemplateAreasParser {
1703 areas: Vec<NamedArea>,
1704 area_indices: PrecomputedHashMap<Atom, usize>,
1705 strings: Vec<crate::OwnedStr>,
1706 width: u32,
1707 row: u32,
1708}
1709
1710impl TemplateAreasParser {
1711 pub fn try_parse_string(&mut self, input: &mut Parser) -> Result<(), ParseError> {
1713 input.try_parse(|input| {
1714 self.parse_string(input.expect_string()?)
1715 .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1716 })
1717 }
1718
1719 fn parse_string(&mut self, string: &str) -> Result<(), ()> {
1721 self.row += 1;
1722 let mut simplified_string = String::new();
1723 let mut current_area_index: Option<usize> = None;
1724 let mut column = 0u32;
1725 for token in TemplateAreasTokenizer(string) {
1726 column += 1;
1727 if column > 1 {
1728 simplified_string.push(' ');
1729 }
1730 let name = if let Some(token) = token? {
1731 simplified_string.push_str(token);
1732 Atom::from(token)
1733 } else {
1734 if let Some(index) = current_area_index.take()
1735 && self.areas[index].columns.end != column
1736 {
1737 return Err(());
1738 }
1739 simplified_string.push('.');
1740 continue;
1741 };
1742 if let Some(index) = current_area_index {
1743 if self.areas[index].name == name {
1744 if self.areas[index].rows.start == self.row {
1745 self.areas[index].columns.end += 1;
1746 }
1747 continue;
1748 }
1749 if self.areas[index].columns.end != column {
1750 return Err(());
1751 }
1752 }
1753 match self.area_indices.entry(name) {
1754 Entry::Occupied(ref e) => {
1755 let index = *e.get();
1756 if self.areas[index].columns.start != column
1757 || self.areas[index].rows.end != self.row
1758 {
1759 return Err(());
1760 }
1761 self.areas[index].rows.end += 1;
1762 current_area_index = Some(index);
1763 },
1764 Entry::Vacant(v) => {
1765 let index = self.areas.len();
1766 let name = v.key().clone();
1767 v.insert(index);
1768 self.areas.push(NamedArea {
1769 name,
1770 columns: UnsignedRange {
1771 start: column,
1772 end: column + 1,
1773 },
1774 rows: UnsignedRange {
1775 start: self.row,
1776 end: self.row + 1,
1777 },
1778 });
1779 current_area_index = Some(index);
1780 },
1781 }
1782 }
1783 if column == 0 {
1784 return Err(());
1787 }
1788 if let Some(index) = current_area_index
1789 && self.areas[index].columns.end != column + 1
1790 {
1791 debug_assert_ne!(self.areas[index].rows.start, self.row);
1792 return Err(());
1793 }
1794 if self.row == 1 {
1795 self.width = column;
1796 } else if self.width != column {
1797 return Err(());
1798 }
1799
1800 self.strings.push(simplified_string.into());
1801 Ok(())
1802 }
1803
1804 pub fn finish(self) -> Result<TemplateAreas, ()> {
1806 if self.strings.is_empty() {
1807 return Err(());
1808 }
1809 Ok(TemplateAreas {
1810 areas: self.areas.into(),
1811 strings: self.strings.into(),
1812 width: self.width,
1813 })
1814 }
1815}
1816
1817impl TemplateAreas {
1818 fn parse_internal(input: &mut Parser) -> Result<Self, ()> {
1819 let mut parser = TemplateAreasParser::default();
1820 while parser.try_parse_string(input).is_ok() {}
1821 parser.finish()
1822 }
1823}
1824
1825impl Parse for TemplateAreas {
1826 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1827 Self::parse_internal(input)
1828 .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1829 }
1830}
1831
1832#[derive(
1834 Clone,
1835 Debug,
1836 MallocSizeOf,
1837 PartialEq,
1838 SpecifiedValueInfo,
1839 ToComputedValue,
1840 ToCss,
1841 ToResolvedValue,
1842 ToShmem,
1843)]
1844#[repr(transparent)]
1845pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
1846
1847impl Parse for TemplateAreasArc {
1848 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1849 let parsed = TemplateAreas::parse(context, input)?;
1850 Ok(TemplateAreasArc(Arc::new(parsed)))
1851 }
1852}
1853
1854#[repr(C)]
1857#[derive(
1858 Clone,
1859 Debug,
1860 MallocSizeOf,
1861 PartialEq,
1862 SpecifiedValueInfo,
1863 ToComputedValue,
1864 ToResolvedValue,
1865 ToShmem,
1866)]
1867pub struct UnsignedRange {
1868 pub start: u32,
1870 pub end: u32,
1872}
1873
1874#[derive(
1875 Clone,
1876 Debug,
1877 MallocSizeOf,
1878 PartialEq,
1879 SpecifiedValueInfo,
1880 ToComputedValue,
1881 ToResolvedValue,
1882 ToShmem,
1883)]
1884#[repr(C)]
1885pub struct NamedArea {
1888 pub name: Atom,
1890 pub rows: UnsignedRange,
1892 pub columns: UnsignedRange,
1894}
1895
1896struct TemplateAreasTokenizer<'a>(&'a str);
1899
1900impl<'a> Iterator for TemplateAreasTokenizer<'a> {
1901 type Item = Result<Option<&'a str>, ()>;
1902
1903 fn next(&mut self) -> Option<Self::Item> {
1904 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
1905 if rest.is_empty() {
1906 return None;
1907 }
1908 if rest.starts_with('.') {
1909 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
1910 return Some(Ok(None));
1911 }
1912 if !rest.starts_with(is_name_code_point) {
1913 return Some(Err(()));
1914 }
1915 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
1916 let token = &rest[..token_len];
1917 self.0 = &rest[token_len..];
1918 Some(Ok(Some(token)))
1919 }
1920}
1921
1922fn is_name_code_point(c: char) -> bool {
1923 c.is_ascii_uppercase()
1924 || c.is_ascii_lowercase()
1925 || c >= '\u{80}'
1926 || c == '_'
1927 || c.is_ascii_digit()
1928 || c == '-'
1929}
1930
1931#[repr(C, u8)]
1937#[derive(
1938 Clone,
1939 Debug,
1940 MallocSizeOf,
1941 Parse,
1942 PartialEq,
1943 SpecifiedValueInfo,
1944 ToComputedValue,
1945 ToCss,
1946 ToResolvedValue,
1947 ToShmem,
1948 ToTyped,
1949)]
1950#[typed(todo_derive_fields)]
1951pub enum GridTemplateAreas {
1952 None,
1954 Areas(TemplateAreasArc),
1956}
1957
1958impl GridTemplateAreas {
1959 #[inline]
1960 pub fn none() -> GridTemplateAreas {
1962 GridTemplateAreas::None
1963 }
1964}
1965
1966pub type ZIndex = GenericZIndex<Integer>;
1968
1969pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
1971
1972impl Parse for AspectRatio {
1973 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1974 use crate::values::generics::position::PreferredRatio;
1975 use crate::values::specified::Ratio;
1976
1977 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1978 let ratio = input.try_parse(|i| Ratio::parse(context, i));
1979 if auto.is_err() {
1980 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
1981 }
1982
1983 if auto.is_err() && ratio.is_err() {
1984 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1985 }
1986
1987 Ok(AspectRatio {
1988 auto: auto.is_ok(),
1989 ratio: match ratio {
1990 Ok(ratio) => PreferredRatio::Ratio(ratio),
1991 Err(..) => PreferredRatio::None,
1992 },
1993 })
1994 }
1995}
1996
1997impl AspectRatio {
1998 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
2000 use crate::values::generics::position::PreferredRatio;
2001 use crate::values::generics::ratio::Ratio;
2002 AspectRatio {
2003 auto: true,
2004 ratio: PreferredRatio::Ratio(Ratio(
2005 NonNegativeNumber::new(w),
2006 NonNegativeNumber::new(h),
2007 )),
2008 }
2009 }
2010}
2011
2012pub type Inset = GenericInset<specified::Percentage, LengthPercentage>;
2014
2015impl Inset {
2016 #[inline]
2019 pub fn parse_quirky(
2020 context: &ParserContext,
2021 input: &mut Parser,
2022 allow_quirks: AllowQuirks,
2023 ) -> Result<Self, ParseError> {
2024 if let Ok(l) = input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
2025 {
2026 return Ok(Self::LengthPercentage(l));
2027 }
2028 match input.try_parse(|i| i.expect_ident_matching("auto")) {
2029 Ok(_) => return Ok(Self::Auto),
2030 Err(e) if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) => {
2031 return Err(e.into());
2032 },
2033 Err(_) => (),
2034 };
2035 Self::parse_anchor_functions_quirky(context, input, allow_quirks)
2036 }
2037
2038 fn parse_as_anchor_function_fallback(
2039 context: &ParserContext,
2040 input: &mut Parser,
2041 ) -> Result<Self, ParseError> {
2042 if let Ok(l) =
2043 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::No))
2044 {
2045 return Ok(Self::LengthPercentage(l));
2046 }
2047 Self::parse_anchor_functions_quirky(context, input, AllowQuirks::No)
2048 }
2049
2050 fn parse_anchor_functions_quirky(
2051 context: &ParserContext,
2052 input: &mut Parser,
2053 allow_quirks: AllowQuirks,
2054 ) -> Result<Self, ParseError> {
2055 debug_assert!(
2056 crate::pref!("layout.css.anchor-positioning.enabled", gecko = true),
2057 "How are we parsing with pref off?"
2058 );
2059 if let Ok(inner) = input.try_parse(|i| AnchorFunction::parse(context, i)) {
2060 return Ok(Self::AnchorFunction(Box::new(inner)));
2061 }
2062 if let Ok(inner) =
2063 input.try_parse(|i| GenericAnchorSizeFunction::<Inset>::parse(context, i))
2064 {
2065 return Ok(Self::AnchorSizeFunction(Box::new(inner)));
2066 }
2067 Ok(Self::AnchorContainingCalcFunction(input.try_parse(
2068 |i| LengthPercentage::parse_quirky_with_anchor_functions(context, i, allow_quirks),
2069 )?))
2070 }
2071}
2072
2073impl Parse for Inset {
2074 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2075 Self::parse_quirky(context, input, AllowQuirks::No)
2076 }
2077}
2078
2079pub type AnchorFunction = GenericAnchorFunction<specified::Percentage, Inset>;
2081
2082impl Parse for AnchorFunction {
2083 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
2084 if !crate::pref!("layout.css.anchor-positioning.enabled", gecko = true) {
2085 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2086 }
2087 input.expect_function_matching("anchor")?;
2088 input.parse_nested_block(|i| {
2089 let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
2090 let side = GenericAnchorSide::parse(context, i)?;
2091 let target_element = if target_element.is_none() {
2092 i.try_parse(|i| DashedIdent::parse(context, i)).ok()
2093 } else {
2094 target_element
2095 };
2096 let fallback = i
2097 .try_parse(|i| {
2098 i.expect_comma()?;
2099 Inset::parse_as_anchor_function_fallback(context, i)
2100 })
2101 .ok();
2102 Ok(Self {
2103 target_element: TreeScoped::with_default_level(
2104 target_element.unwrap_or_else(DashedIdent::empty),
2105 ),
2106 side,
2107 fallback: fallback.into(),
2108 })
2109 })
2110 }
2111}
2112
2113#[allow(missing_docs)]
2115#[derive(
2116 Clone,
2117 Copy,
2118 Debug,
2119 Deserialize,
2120 Eq,
2121 FromPrimitive,
2122 Hash,
2123 MallocSizeOf,
2124 Parse,
2125 PartialEq,
2126 Serialize,
2127 SpecifiedValueInfo,
2128 ToComputedValue,
2129 ToCss,
2130 ToResolvedValue,
2131 ToShmem,
2132 ToTyped,
2133)]
2134#[repr(u8)]
2135pub enum FlexDirection {
2136 Row,
2137 RowReverse,
2138 Column,
2139 ColumnReverse,
2140}
2141
2142#[allow(missing_docs)]
2144#[derive(
2145 Clone,
2146 Copy,
2147 Debug,
2148 Deserialize,
2149 Eq,
2150 FromPrimitive,
2151 Hash,
2152 MallocSizeOf,
2153 Parse,
2154 PartialEq,
2155 Serialize,
2156 SpecifiedValueInfo,
2157 ToComputedValue,
2158 ToCss,
2159 ToResolvedValue,
2160 ToShmem,
2161 ToTyped,
2162)]
2163#[repr(u8)]
2164pub enum BoxSizing {
2165 ContentBox,
2166 BorderBox,
2167}
2168
2169#[allow(missing_docs)]
2171#[derive(
2172 Clone,
2173 Copy,
2174 Debug,
2175 Deserialize,
2176 Eq,
2177 FromPrimitive,
2178 Hash,
2179 MallocSizeOf,
2180 Parse,
2181 PartialEq,
2182 Serialize,
2183 SpecifiedValueInfo,
2184 ToComputedValue,
2185 ToCss,
2186 ToResolvedValue,
2187 ToShmem,
2188 ToTyped,
2189)]
2190#[repr(u8)]
2191pub enum ObjectFit {
2192 Fill,
2193 Contain,
2194 Cover,
2195 None,
2196 ScaleDown,
2197}