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::parser::{Parse, ParserContext};
11 use crate::selector_map::PrecomputedHashMap;
12 use crate::str::HTML_SPACE_CHARACTERS;
13 use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
14 use crate::values::computed::{Context, Percentage, ToComputedValue};
15 use crate::values::generics::position::AspectRatio as GenericAspectRatio;
16 use crate::values::generics::position::Position as GenericPosition;
17 use crate::values::generics::position::PositionComponent as GenericPositionComponent;
18 use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
19 use crate::values::generics::position::ZIndex as GenericZIndex;
20 use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
21 use crate::{Atom, Zero};
22 use cssparser::Parser;
23 use selectors::parser::SelectorParseErrorKind;
24 use servo_arc::Arc;
25 use std::fmt::{self, Write};
26 use style_traits::values::specified::AllowedNumericType;
27 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
28 
29 /// The specified value of a CSS `<position>`
30 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
31 
32 /// The specified value of an `auto | <position>`.
33 pub type PositionOrAuto = GenericPositionOrAuto<Position>;
34 
35 /// The specified value of a horizontal position.
36 pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
37 
38 /// The specified value of a vertical position.
39 pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
40 
41 /// The specified value of a component of a CSS `<position>`.
42 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
43 pub enum PositionComponent<S> {
44     /// `center`
45     Center,
46     /// `<length-percentage>`
47     Length(LengthPercentage),
48     /// `<side> <length-percentage>?`
49     Side(S, Option<LengthPercentage>),
50 }
51 
52 /// A keyword for the X direction.
53 #[derive(
54     Clone,
55     Copy,
56     Debug,
57     Eq,
58     Hash,
59     MallocSizeOf,
60     Parse,
61     PartialEq,
62     SpecifiedValueInfo,
63     ToComputedValue,
64     ToCss,
65     ToResolvedValue,
66     ToShmem,
67 )]
68 #[allow(missing_docs)]
69 #[repr(u8)]
70 pub enum HorizontalPositionKeyword {
71     Left,
72     Right,
73 }
74 
75 /// A keyword for the Y direction.
76 #[derive(
77     Clone,
78     Copy,
79     Debug,
80     Eq,
81     Hash,
82     MallocSizeOf,
83     Parse,
84     PartialEq,
85     SpecifiedValueInfo,
86     ToComputedValue,
87     ToCss,
88     ToResolvedValue,
89     ToShmem,
90 )]
91 #[allow(missing_docs)]
92 #[repr(u8)]
93 pub enum VerticalPositionKeyword {
94     Top,
95     Bottom,
96 }
97 
98 impl Parse for Position {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>99     fn parse<'i, 't>(
100         context: &ParserContext,
101         input: &mut Parser<'i, 't>,
102     ) -> Result<Self, ParseError<'i>> {
103         let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
104         if position.is_three_value_syntax() {
105             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
106         }
107         Ok(position)
108     }
109 }
110 
111 impl Position {
112     /// Parses a `<bg-position>`, with quirks.
parse_three_value_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>113     pub fn parse_three_value_quirky<'i, 't>(
114         context: &ParserContext,
115         input: &mut Parser<'i, 't>,
116         allow_quirks: AllowQuirks,
117     ) -> Result<Self, ParseError<'i>> {
118         match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
119             Ok(x_pos @ PositionComponent::Center) => {
120                 if let Ok(y_pos) =
121                     input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
122                 {
123                     return Ok(Self::new(x_pos, y_pos));
124                 }
125                 let x_pos = input
126                     .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
127                     .unwrap_or(x_pos);
128                 let y_pos = PositionComponent::Center;
129                 return Ok(Self::new(x_pos, y_pos));
130             },
131             Ok(PositionComponent::Side(x_keyword, lp)) => {
132                 if input
133                     .try_parse(|i| i.expect_ident_matching("center"))
134                     .is_ok()
135                 {
136                     let x_pos = PositionComponent::Side(x_keyword, lp);
137                     let y_pos = PositionComponent::Center;
138                     return Ok(Self::new(x_pos, y_pos));
139                 }
140                 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
141                     let y_lp = input
142                         .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
143                         .ok();
144                     let x_pos = PositionComponent::Side(x_keyword, lp);
145                     let y_pos = PositionComponent::Side(y_keyword, y_lp);
146                     return Ok(Self::new(x_pos, y_pos));
147                 }
148                 let x_pos = PositionComponent::Side(x_keyword, None);
149                 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
150                 return Ok(Self::new(x_pos, y_pos));
151             },
152             Ok(x_pos @ PositionComponent::Length(_)) => {
153                 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
154                     let y_pos = PositionComponent::Side(y_keyword, None);
155                     return Ok(Self::new(x_pos, y_pos));
156                 }
157                 if let Ok(y_lp) =
158                     input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
159                 {
160                     let y_pos = PositionComponent::Length(y_lp);
161                     return Ok(Self::new(x_pos, y_pos));
162                 }
163                 let y_pos = PositionComponent::Center;
164                 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
165                 return Ok(Self::new(x_pos, y_pos));
166             },
167             Err(_) => {},
168         }
169         let y_keyword = VerticalPositionKeyword::parse(input)?;
170         let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
171             let y_lp = i
172                 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
173                 .ok();
174             if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
175                 let x_lp = i
176                     .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
177                     .ok();
178                 let x_pos = PositionComponent::Side(x_keyword, x_lp);
179                 return Ok((y_lp, x_pos));
180             };
181             i.expect_ident_matching("center")?;
182             let x_pos = PositionComponent::Center;
183             Ok((y_lp, x_pos))
184         });
185         if let Ok((y_lp, x_pos)) = lp_and_x_pos {
186             let y_pos = PositionComponent::Side(y_keyword, y_lp);
187             return Ok(Self::new(x_pos, y_pos));
188         }
189         let x_pos = PositionComponent::Center;
190         let y_pos = PositionComponent::Side(y_keyword, None);
191         Ok(Self::new(x_pos, y_pos))
192     }
193 
194     /// `center center`
195     #[inline]
center() -> Self196     pub fn center() -> Self {
197         Self::new(PositionComponent::Center, PositionComponent::Center)
198     }
199 
200     /// Returns true if this uses a 3 value syntax.
201     #[inline]
is_three_value_syntax(&self) -> bool202     fn is_three_value_syntax(&self) -> bool {
203         self.horizontal.component_count() != self.vertical.component_count()
204     }
205 }
206 
207 impl ToCss for Position {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,208     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
209     where
210         W: Write,
211     {
212         match (&self.horizontal, &self.vertical) {
213             (
214                 x_pos @ &PositionComponent::Side(_, Some(_)),
215                 &PositionComponent::Length(ref y_lp),
216             ) => {
217                 x_pos.to_css(dest)?;
218                 dest.write_str(" top ")?;
219                 y_lp.to_css(dest)
220             },
221             (
222                 &PositionComponent::Length(ref x_lp),
223                 y_pos @ &PositionComponent::Side(_, Some(_)),
224             ) => {
225                 dest.write_str("left ")?;
226                 x_lp.to_css(dest)?;
227                 dest.write_str(" ")?;
228                 y_pos.to_css(dest)
229             },
230             (x_pos, y_pos) => {
231                 x_pos.to_css(dest)?;
232                 dest.write_str(" ")?;
233                 y_pos.to_css(dest)
234             },
235         }
236     }
237 }
238 
239 impl<S: Parse> Parse for PositionComponent<S> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>240     fn parse<'i, 't>(
241         context: &ParserContext,
242         input: &mut Parser<'i, 't>,
243     ) -> Result<Self, ParseError<'i>> {
244         Self::parse_quirky(context, input, AllowQuirks::No)
245     }
246 }
247 
248 impl<S: Parse> PositionComponent<S> {
249     /// Parses a component of a CSS position, with quirks.
parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>250     pub fn parse_quirky<'i, 't>(
251         context: &ParserContext,
252         input: &mut Parser<'i, 't>,
253         allow_quirks: AllowQuirks,
254     ) -> Result<Self, ParseError<'i>> {
255         if input
256             .try_parse(|i| i.expect_ident_matching("center"))
257             .is_ok()
258         {
259             return Ok(PositionComponent::Center);
260         }
261         if let Ok(lp) =
262             input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
263         {
264             return Ok(PositionComponent::Length(lp));
265         }
266         let keyword = S::parse(context, input)?;
267         let lp = input
268             .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
269             .ok();
270         Ok(PositionComponent::Side(keyword, lp))
271     }
272 }
273 
274 impl<S> GenericPositionComponent for PositionComponent<S> {
is_center(&self) -> bool275     fn is_center(&self) -> bool {
276         match *self {
277             PositionComponent::Center => true,
278             PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.0 == 0.5,
279             // 50% from any side is still the center.
280             PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => per.0 == 0.5,
281             _ => false,
282         }
283     }
284 }
285 
286 impl<S> PositionComponent<S> {
287     /// `0%`
zero() -> Self288     pub fn zero() -> Self {
289         PositionComponent::Length(LengthPercentage::Percentage(Percentage::zero()))
290     }
291 
292     /// Returns the count of this component.
component_count(&self) -> usize293     fn component_count(&self) -> usize {
294         match *self {
295             PositionComponent::Length(..) | PositionComponent::Center => 1,
296             PositionComponent::Side(_, ref lp) => {
297                 if lp.is_some() {
298                     2
299                 } else {
300                     1
301                 }
302             },
303         }
304     }
305 }
306 
307 impl<S: Side> ToComputedValue for PositionComponent<S> {
308     type ComputedValue = ComputedLengthPercentage;
309 
to_computed_value(&self, context: &Context) -> Self::ComputedValue310     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
311         match *self {
312             PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
313             PositionComponent::Side(ref keyword, None) => {
314                 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
315                 ComputedLengthPercentage::new_percent(p)
316             },
317             PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
318                 let length = length.to_computed_value(context);
319                 // We represent `<end-side> <length>` as `calc(100% - <length>)`.
320                 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
321             },
322             PositionComponent::Side(_, Some(ref length)) |
323             PositionComponent::Length(ref length) => length.to_computed_value(context),
324         }
325     }
326 
from_computed_value(computed: &Self::ComputedValue) -> Self327     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
328         PositionComponent::Length(ToComputedValue::from_computed_value(computed))
329     }
330 }
331 
332 impl<S: Side> PositionComponent<S> {
333     /// The initial specified value of a position component, i.e. the start side.
initial_specified_value() -> Self334     pub fn initial_specified_value() -> Self {
335         PositionComponent::Side(S::start(), None)
336     }
337 }
338 
339 /// Represents a side, either horizontal or vertical, of a CSS position.
340 pub trait Side {
341     /// Returns the start side.
start() -> Self342     fn start() -> Self;
343 
344     /// Returns whether this side is the start side.
is_start(&self) -> bool345     fn is_start(&self) -> bool;
346 }
347 
348 impl Side for HorizontalPositionKeyword {
349     #[inline]
start() -> Self350     fn start() -> Self {
351         HorizontalPositionKeyword::Left
352     }
353 
354     #[inline]
is_start(&self) -> bool355     fn is_start(&self) -> bool {
356         *self == Self::start()
357     }
358 }
359 
360 impl Side for VerticalPositionKeyword {
361     #[inline]
start() -> Self362     fn start() -> Self {
363         VerticalPositionKeyword::Top
364     }
365 
366     #[inline]
is_start(&self) -> bool367     fn is_start(&self) -> bool {
368         *self == Self::start()
369     }
370 }
371 
372 bitflags! {
373     /// Controls how the auto-placement algorithm works
374     /// specifying exactly how auto-placed items get flowed into the grid
375     #[derive(
376         MallocSizeOf,
377         SpecifiedValueInfo,
378         ToComputedValue,
379         ToResolvedValue,
380         ToShmem
381     )]
382     #[value_info(other_values = "row,column,dense")]
383     #[repr(C)]
384     pub struct GridAutoFlow: u8 {
385         /// 'row' - mutually exclusive with 'column'
386         const ROW = 1 << 0;
387         /// 'column' - mutually exclusive with 'row'
388         const COLUMN = 1 << 1;
389         /// 'dense'
390         const DENSE = 1 << 2;
391     }
392 }
393 
394 #[derive(
395     Clone,
396     Copy,
397     Debug,
398     Eq,
399     MallocSizeOf,
400     PartialEq,
401     SpecifiedValueInfo,
402     ToComputedValue,
403     ToCss,
404     ToResolvedValue,
405     ToShmem,
406 )]
407 /// Masonry auto-placement algorithm packing.
408 pub enum MasonryPlacement {
409     /// Place the item in the track(s) with the smallest extent so far.
410     Pack,
411     /// Place the item after the last item, from start to end.
412     Next,
413 }
414 
415 #[derive(
416     Clone,
417     Copy,
418     Debug,
419     Eq,
420     MallocSizeOf,
421     PartialEq,
422     SpecifiedValueInfo,
423     ToComputedValue,
424     ToCss,
425     ToResolvedValue,
426     ToShmem,
427 )]
428 /// Masonry auto-placement algorithm item sorting option.
429 pub enum MasonryItemOrder {
430     /// Place all items with a definite placement before auto-placed items.
431     DefiniteFirst,
432     /// Place items in `order-modified document order`.
433     Ordered,
434 }
435 
436 #[derive(
437     Clone,
438     Copy,
439     Debug,
440     Eq,
441     MallocSizeOf,
442     PartialEq,
443     SpecifiedValueInfo,
444     ToComputedValue,
445     ToCss,
446     ToResolvedValue,
447     ToShmem,
448 )]
449 /// Controls how the Masonry layout algorithm works
450 /// specifying exactly how auto-placed items get flowed in the masonry axis.
451 pub struct MasonryAutoFlow {
452     /// Specify how to pick a auto-placement track.
453     #[css(contextual_skip_if = "is_pack_with_non_default_order")]
454     pub placement: MasonryPlacement,
455     /// Specify how to pick an item to place.
456     #[css(skip_if = "is_item_order_definite_first")]
457     pub order: MasonryItemOrder,
458 }
459 
460 #[inline]
is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool461 fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
462     *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
463 }
464 
465 #[inline]
is_item_order_definite_first(order: &MasonryItemOrder) -> bool466 fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
467     *order == MasonryItemOrder::DefiniteFirst
468 }
469 
470 impl MasonryAutoFlow {
471     #[inline]
472     /// Get initial `masonry-auto-flow` value.
initial() -> MasonryAutoFlow473     pub fn initial() -> MasonryAutoFlow {
474         MasonryAutoFlow {
475             placement: MasonryPlacement::Pack,
476             order: MasonryItemOrder::DefiniteFirst,
477         }
478     }
479 }
480 
481 impl Parse for MasonryAutoFlow {
482     /// [ definite-first | ordered ] || [ pack | next ]
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<MasonryAutoFlow, ParseError<'i>>483     fn parse<'i, 't>(
484         _context: &ParserContext,
485         input: &mut Parser<'i, 't>,
486     ) -> Result<MasonryAutoFlow, ParseError<'i>> {
487         let mut value = MasonryAutoFlow::initial();
488         let mut got_placement = false;
489         let mut got_order = false;
490         while !input.is_exhausted() {
491             let location = input.current_source_location();
492             let ident = input.expect_ident()?;
493             let success = match_ignore_ascii_case! { &ident,
494                 "pack" if !got_placement => {
495                     got_placement = true;
496                     true
497                 },
498                 "next" if !got_placement => {
499                     value.placement = MasonryPlacement::Next;
500                     got_placement = true;
501                     true
502                 },
503                 "definite-first" if !got_order => {
504                     got_order = true;
505                     true
506                 },
507                 "ordered" if !got_order => {
508                     value.order = MasonryItemOrder::Ordered;
509                     got_order = true;
510                     true
511                 },
512                 _ => false
513             };
514             if !success {
515                 return Err(location
516                     .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
517             }
518         }
519 
520         if got_placement || got_order {
521             Ok(value)
522         } else {
523             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
524         }
525     }
526 }
527 
528 #[cfg(feature = "gecko")]
529 impl From<u8> for MasonryAutoFlow {
from(bits: u8) -> MasonryAutoFlow530     fn from(bits: u8) -> MasonryAutoFlow {
531         use crate::gecko_bindings::structs;
532         let mut value = MasonryAutoFlow::initial();
533         if bits & structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8 == 0 {
534             value.placement = MasonryPlacement::Next;
535         }
536         if bits & structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8 == 0 {
537             value.order = MasonryItemOrder::Ordered;
538         }
539         value
540     }
541 }
542 
543 #[cfg(feature = "gecko")]
544 impl From<MasonryAutoFlow> for u8 {
from(v: MasonryAutoFlow) -> u8545     fn from(v: MasonryAutoFlow) -> u8 {
546         use crate::gecko_bindings::structs;
547 
548         let mut result: u8 = 0;
549         if v.placement == MasonryPlacement::Pack {
550             result |= structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8;
551         }
552         if v.order == MasonryItemOrder::DefiniteFirst {
553             result |= structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8;
554         }
555         result
556     }
557 }
558 
559 impl Parse for GridAutoFlow {
560     /// [ row | column ] || dense
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<GridAutoFlow, ParseError<'i>>561     fn parse<'i, 't>(
562         _context: &ParserContext,
563         input: &mut Parser<'i, 't>,
564     ) -> Result<GridAutoFlow, ParseError<'i>> {
565         let mut track = None;
566         let mut dense = GridAutoFlow::empty();
567 
568         while !input.is_exhausted() {
569             let location = input.current_source_location();
570             let ident = input.expect_ident()?;
571             let success = match_ignore_ascii_case! { &ident,
572                 "row" if track.is_none() => {
573                     track = Some(GridAutoFlow::ROW);
574                     true
575                 },
576                 "column" if track.is_none() => {
577                     track = Some(GridAutoFlow::COLUMN);
578                     true
579                 },
580                 "dense" if dense.is_empty() => {
581                     dense = GridAutoFlow::DENSE;
582                     true
583                 },
584                 _ => false,
585             };
586             if !success {
587                 return Err(location
588                     .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
589             }
590         }
591 
592         if track.is_some() || !dense.is_empty() {
593             Ok(track.unwrap_or(GridAutoFlow::ROW) | dense)
594         } else {
595             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
596         }
597     }
598 }
599 
600 impl ToCss for GridAutoFlow {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,601     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
602     where
603         W: Write,
604     {
605         if *self == GridAutoFlow::ROW {
606             return dest.write_str("row");
607         }
608 
609         if *self == GridAutoFlow::COLUMN {
610             return dest.write_str("column");
611         }
612 
613         if *self == GridAutoFlow::ROW | GridAutoFlow::DENSE {
614             return dest.write_str("dense");
615         }
616 
617         if *self == GridAutoFlow::COLUMN | GridAutoFlow::DENSE {
618             return dest.write_str("column dense");
619         }
620 
621         debug_assert!(false, "Unknown or invalid grid-autoflow value");
622         Ok(())
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)]
638 /// https://drafts.csswg.org/css-grid/#named-grid-area
639 pub struct TemplateAreas {
640     /// `named area` containing for each template area
641     #[css(skip)]
642     pub areas: crate::OwnedSlice<NamedArea>,
643     /// The original CSS string value of each template area
644     #[css(iterable)]
645     pub strings: crate::OwnedSlice<crate::OwnedStr>,
646     /// The number of columns of the grid.
647     #[css(skip)]
648     pub width: u32,
649 }
650 
651 impl TemplateAreas {
652     /// Transform `vector` of str into `template area`
from_vec(strings: Vec<crate::OwnedStr>) -> Result<Self, ()>653     pub fn from_vec(strings: Vec<crate::OwnedStr>) -> Result<Self, ()> {
654         if strings.is_empty() {
655             return Err(());
656         }
657         let mut areas: Vec<NamedArea> = vec![];
658         let mut width = 0;
659         {
660             let mut row = 0u32;
661             let mut area_indices = PrecomputedHashMap::<Atom, usize>::default();
662             for string in &strings {
663                 let mut current_area_index: Option<usize> = None;
664                 row += 1;
665                 let mut column = 0u32;
666                 for token in TemplateAreasTokenizer(string) {
667                     column += 1;
668                     let name = if let Some(token) = token? {
669                         Atom::from(token)
670                     } else {
671                         if let Some(index) = current_area_index.take() {
672                             if areas[index].columns.end != column {
673                                 return Err(());
674                             }
675                         }
676                         continue;
677                     };
678                     if let Some(index) = current_area_index {
679                         if areas[index].name == name {
680                             if areas[index].rows.start == row {
681                                 areas[index].columns.end += 1;
682                             }
683                             continue;
684                         }
685                         if areas[index].columns.end != column {
686                             return Err(());
687                         }
688                     }
689                     if let Some(index) = area_indices.get(&name).cloned() {
690                         if areas[index].columns.start != column || areas[index].rows.end != row {
691                             return Err(());
692                         }
693                         areas[index].rows.end += 1;
694                         current_area_index = Some(index);
695                         continue;
696                     }
697                     let index = areas.len();
698                     assert!(area_indices.insert(name.clone(), index).is_none());
699                     areas.push(NamedArea {
700                         name,
701                         columns: UnsignedRange {
702                             start: column,
703                             end: column + 1,
704                         },
705                         rows: UnsignedRange {
706                             start: row,
707                             end: row + 1,
708                         },
709                     });
710                     current_area_index = Some(index);
711                 }
712                 if column == 0 {
713                     // Each string must produce a valid token.
714                     // https://github.com/w3c/csswg-drafts/issues/5110
715                     return Err(());
716                 }
717                 if let Some(index) = current_area_index {
718                     if areas[index].columns.end != column + 1 {
719                         assert_ne!(areas[index].rows.start, row);
720                         return Err(());
721                     }
722                 }
723                 if row == 1 {
724                     width = column;
725                 } else if width != column {
726                     return Err(());
727                 }
728             }
729         }
730         Ok(TemplateAreas {
731             areas: areas.into(),
732             strings: strings.into(),
733             width,
734         })
735     }
736 }
737 
738 impl Parse for TemplateAreas {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>739     fn parse<'i, 't>(
740         _context: &ParserContext,
741         input: &mut Parser<'i, 't>,
742     ) -> Result<Self, ParseError<'i>> {
743         let mut strings = vec![];
744         while let Ok(string) =
745             input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned().into()))
746         {
747             strings.push(string);
748         }
749 
750         TemplateAreas::from_vec(strings)
751             .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
752     }
753 }
754 
755 /// Arc type for `Arc<TemplateAreas>`
756 #[derive(
757     Clone,
758     Debug,
759     MallocSizeOf,
760     PartialEq,
761     SpecifiedValueInfo,
762     ToComputedValue,
763     ToCss,
764     ToResolvedValue,
765     ToShmem,
766 )]
767 #[repr(transparent)]
768 pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
769 
770 impl Parse for TemplateAreasArc {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>771     fn parse<'i, 't>(
772         context: &ParserContext,
773         input: &mut Parser<'i, 't>,
774     ) -> Result<Self, ParseError<'i>> {
775         let parsed = TemplateAreas::parse(context, input)?;
776         Ok(TemplateAreasArc(Arc::new(parsed)))
777     }
778 }
779 
780 /// A range of rows or columns. Using this instead of std::ops::Range for FFI
781 /// purposes.
782 #[repr(C)]
783 #[derive(
784     Clone,
785     Debug,
786     MallocSizeOf,
787     PartialEq,
788     SpecifiedValueInfo,
789     ToComputedValue,
790     ToResolvedValue,
791     ToShmem,
792 )]
793 pub struct UnsignedRange {
794     /// The start of the range.
795     pub start: u32,
796     /// The end of the range.
797     pub end: u32,
798 }
799 
800 #[derive(
801     Clone,
802     Debug,
803     MallocSizeOf,
804     PartialEq,
805     SpecifiedValueInfo,
806     ToComputedValue,
807     ToResolvedValue,
808     ToShmem,
809 )]
810 #[repr(C)]
811 /// Not associated with any particular grid item, but can be referenced from the
812 /// grid-placement properties.
813 pub struct NamedArea {
814     /// Name of the `named area`
815     pub name: Atom,
816     /// Rows of the `named area`
817     pub rows: UnsignedRange,
818     /// Columns of the `named area`
819     pub columns: UnsignedRange,
820 }
821 
822 /// Tokenize the string into a list of the tokens,
823 /// using longest-match semantics
824 struct TemplateAreasTokenizer<'a>(&'a str);
825 
826 impl<'a> Iterator for TemplateAreasTokenizer<'a> {
827     type Item = Result<Option<&'a str>, ()>;
828 
next(&mut self) -> Option<Self::Item>829     fn next(&mut self) -> Option<Self::Item> {
830         let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
831         if rest.is_empty() {
832             return None;
833         }
834         if rest.starts_with('.') {
835             self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
836             return Some(Ok(None));
837         }
838         if !rest.starts_with(is_name_code_point) {
839             return Some(Err(()));
840         }
841         let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
842         let token = &rest[..token_len];
843         self.0 = &rest[token_len..];
844         Some(Ok(Some(token)))
845     }
846 }
847 
is_name_code_point(c: char) -> bool848 fn is_name_code_point(c: char) -> bool {
849     c >= 'A' && c <= 'Z' ||
850         c >= 'a' && c <= 'z' ||
851         c >= '\u{80}' ||
852         c == '_' ||
853         c >= '0' && c <= '9' ||
854         c == '-'
855 }
856 
857 /// This property specifies named grid areas.
858 ///
859 /// The syntax of this property also provides a visualization of the structure
860 /// of the grid, making the overall layout of the grid container easier to
861 /// understand.
862 #[repr(C, u8)]
863 #[derive(
864     Clone,
865     Debug,
866     MallocSizeOf,
867     Parse,
868     PartialEq,
869     SpecifiedValueInfo,
870     ToComputedValue,
871     ToCss,
872     ToResolvedValue,
873     ToShmem,
874 )]
875 pub enum GridTemplateAreas {
876     /// The `none` value.
877     None,
878     /// The actual value.
879     Areas(TemplateAreasArc),
880 }
881 
882 impl GridTemplateAreas {
883     #[inline]
884     /// Get default value as `none`
none() -> GridTemplateAreas885     pub fn none() -> GridTemplateAreas {
886         GridTemplateAreas::None
887     }
888 }
889 
890 /// A specified value for the `z-index` property.
891 pub type ZIndex = GenericZIndex<Integer>;
892 
893 /// A specified value for the `aspect-ratio` property.
894 pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
895 
896 impl Parse for AspectRatio {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>897     fn parse<'i, 't>(
898         context: &ParserContext,
899         input: &mut Parser<'i, 't>,
900     ) -> Result<Self, ParseError<'i>> {
901         use crate::values::generics::position::PreferredRatio;
902         use crate::values::specified::Ratio;
903 
904         let location = input.current_source_location();
905         let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
906         let ratio = input.try_parse(|i| Ratio::parse(context, i));
907         if auto.is_err() {
908             auto = input.try_parse(|i| i.expect_ident_matching("auto"));
909         }
910 
911         if auto.is_err() && ratio.is_err() {
912             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
913         }
914 
915         Ok(AspectRatio {
916             auto: auto.is_ok(),
917             ratio: match ratio {
918                 Ok(ratio) => PreferredRatio::Ratio(ratio),
919                 Err(..) => PreferredRatio::None,
920             },
921         })
922     }
923 }
924 
925 impl AspectRatio {
926     /// Returns Self by a valid ratio.
from_mapped_ratio(w: f32, h: f32) -> Self927     pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
928         use crate::values::generics::position::PreferredRatio;
929         use crate::values::generics::ratio::Ratio;
930         AspectRatio {
931             auto: true,
932             ratio: PreferredRatio::Ratio(Ratio(
933                 NonNegativeNumber::new(w),
934                 NonNegativeNumber::new(h),
935             )),
936         }
937     }
938 }
939