Skip to main content

style/properties/
shorthands.rs

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//! Manual shorthand parsing and serialization
6#![allow(missing_docs)]
7
8use crate::parser::{Parse, ParserContext};
9use crate::values::specified;
10use cssparser::Parser;
11use std::fmt::{self, Write};
12use style_traits::{
13    values::SequenceWriter, CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo,
14    StyleParseErrorKind, ToCss,
15};
16
17macro_rules! expanded {
18    ( $( $name: ident: $value: expr ),+ ) => {
19        expanded!( $( $name: $value, )+ )
20    };
21    ( $( $name: ident: $value: expr, )+ ) => {
22        Longhands {
23            $(
24                $name: $crate::properties::MaybeBoxed::maybe_boxed($value),
25            )+
26        }
27    }
28}
29pub(crate) use expanded;
30
31macro_rules! try_parse_one {
32    ($context: expr, $input: expr, $var: ident, $parse_path: path) => {
33        if $var.is_none() {
34            if let Ok(value) = $input.try_parse(|i| $parse_path($context, i)) {
35                $var = Some(value);
36                continue;
37            }
38        }
39    };
40    ($input: expr, $var: ident, $parse_path: path) => {
41        if $var.is_none() {
42            if let Ok(value) = $input.try_parse(|i| $parse_path(i)) {
43                $var = Some(value);
44                continue;
45            }
46        }
47    };
48}
49
50macro_rules! unwrap_or_initial {
51    ($prop: ident) => {
52        unwrap_or_initial!($prop, $prop)
53    };
54    ($prop: ident, $expr: expr) => {
55        $expr.unwrap_or_else(|| $prop::get_initial_specified_value())
56    };
57}
58
59/// Serializes a border shorthand value composed of width/style/color.
60pub fn serialize_directional_border<W>(
61    dest: &mut CssWriter<W>,
62    width: &specified::BorderSideWidth,
63    style: &specified::BorderStyle,
64    color: &specified::Color,
65) -> fmt::Result
66where
67    W: Write,
68{
69    use specified::{BorderSideWidth, BorderStyle, Color};
70    let has_style = *style != BorderStyle::None;
71    let has_color = *color != Color::CurrentColor;
72    let has_width = *width != BorderSideWidth::medium();
73    if !has_style && !has_color && !has_width {
74        return width.to_css(dest);
75    }
76    let mut writer = SequenceWriter::new(dest, " ");
77    if has_width {
78        writer.item(width)?;
79    }
80    if has_style {
81        writer.item(style)?;
82    }
83    if has_color {
84        writer.item(color)?;
85    }
86    Ok(())
87}
88
89pub fn parse_border(
90    context: &ParserContext,
91    input: &mut Parser,
92) -> Result<
93    (
94        specified::BorderSideWidth,
95        specified::BorderStyle,
96        specified::Color,
97    ),
98    ParseError,
99> {
100    use crate::values::specified::{BorderSideWidth, BorderStyle, Color};
101    let mut color = None;
102    let mut style = None;
103    let mut width = None;
104    let mut parsed = 0;
105    loop {
106        parsed += 1;
107        try_parse_one!(context, input, width, BorderSideWidth::parse);
108        try_parse_one!(input, style, BorderStyle::parse);
109        try_parse_one!(context, input, color, Color::parse);
110        parsed -= 1;
111        break;
112    }
113    if parsed == 0 {
114        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
115    }
116    Ok((
117        width.unwrap_or(BorderSideWidth::medium()),
118        style.unwrap_or(BorderStyle::None),
119        color.unwrap_or(Color::CurrentColor),
120    ))
121}
122
123pub mod border_block {
124    use super::*;
125    pub use crate::properties::generated::shorthands::border_block::*;
126
127    pub fn parse_value(
128        context: &ParserContext,
129        input: &mut Parser,
130    ) -> Result<Longhands, ParseError> {
131        let (width, style, color) = super::parse_border(context, input)?;
132        Ok(Longhands {
133            border_block_start_width: width.clone(),
134            border_block_start_style: style,
135            border_block_start_color: color.clone(),
136            border_block_end_width: width,
137            border_block_end_style: style,
138            border_block_end_color: color,
139        })
140    }
141
142    impl<'a> ToCss for LonghandsToSerialize<'a> {
143        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
144        where
145            W: fmt::Write,
146        {
147            // FIXME: Should serialize empty if start != end, right?
148            super::serialize_directional_border(
149                dest,
150                self.border_block_start_width,
151                self.border_block_start_style,
152                self.border_block_start_color,
153            )
154        }
155    }
156}
157
158pub mod border_inline {
159    use super::*;
160    pub use crate::properties::generated::shorthands::border_inline::*;
161
162    pub fn parse_value(
163        context: &ParserContext,
164        input: &mut Parser,
165    ) -> Result<Longhands, ParseError> {
166        let (width, style, color) = super::parse_border(context, input)?;
167        Ok(Longhands {
168            border_inline_start_width: width.clone(),
169            border_inline_start_style: style,
170            border_inline_start_color: color.clone(),
171            border_inline_end_width: width,
172            border_inline_end_style: style,
173            border_inline_end_color: color,
174        })
175    }
176
177    impl<'a> ToCss for LonghandsToSerialize<'a> {
178        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
179        where
180            W: fmt::Write,
181        {
182            // FIXME: Should serialize empty if start != end, right?
183            super::serialize_directional_border(
184                dest,
185                self.border_inline_start_width,
186                self.border_inline_start_style,
187                self.border_inline_start_color,
188            )
189        }
190    }
191}
192
193pub mod border_radius {
194    pub use crate::properties::generated::shorthands::border_radius::*;
195
196    use super::*;
197    use crate::values::generics::border::BorderCornerRadius;
198    use crate::values::generics::rect::Rect;
199    use crate::values::specified::BorderRadius;
200
201    pub fn parse_value(
202        context: &ParserContext,
203        input: &mut Parser,
204    ) -> Result<Longhands, ParseError> {
205        let radii = BorderRadius::parse(context, input)?;
206        Ok(expanded! {
207            border_top_left_radius: radii.top_left,
208            border_top_right_radius: radii.top_right,
209            border_bottom_right_radius: radii.bottom_right,
210            border_bottom_left_radius: radii.bottom_left,
211        })
212    }
213
214    impl<'a> ToCss for LonghandsToSerialize<'a> {
215        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
216        where
217            W: fmt::Write,
218        {
219            let LonghandsToSerialize {
220                border_top_left_radius: BorderCornerRadius(tl),
221                border_top_right_radius: BorderCornerRadius(tr),
222                border_bottom_right_radius: BorderCornerRadius(br),
223                border_bottom_left_radius: BorderCornerRadius(bl),
224            } = *self;
225
226            let widths = Rect::new(tl.width(), tr.width(), br.width(), bl.width());
227            let heights = Rect::new(tl.height(), tr.height(), br.height(), bl.height());
228
229            BorderRadius::serialize_rects(widths, heights, dest)
230        }
231    }
232}
233
234pub mod corner_shape {
235    pub use crate::properties::generated::shorthands::corner_shape::*;
236
237    use super::*;
238    use crate::values::generics::rect::Rect;
239    use crate::values::specified::CornerShape;
240
241    /// Parses 1-4 `<corner-shape-value>` tokens with the standard CSS 4-side
242    /// shorthand expansion (top, right, bottom, left).
243    pub fn parse_value(
244        context: &ParserContext,
245        input: &mut Parser,
246    ) -> Result<Longhands, ParseError> {
247        let rect = Rect::parse_with(context, input, CornerShape::parse)?;
248        Ok(expanded! {
249            corner_top_left_shape: rect.0,
250            corner_top_right_shape: rect.1,
251            corner_bottom_right_shape: rect.2,
252            corner_bottom_left_shape: rect.3,
253        })
254    }
255
256    impl<'a> ToCss for LonghandsToSerialize<'a> {
257        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
258        where
259            W: fmt::Write,
260        {
261            Rect::new(
262                self.corner_top_left_shape,
263                self.corner_top_right_shape,
264                self.corner_bottom_right_shape,
265                self.corner_bottom_left_shape,
266            )
267            .to_css(dest)
268        }
269    }
270}
271
272pub mod border_image {
273    pub use crate::properties::generated::shorthands::border_image::*;
274
275    use super::*;
276    use crate::properties::longhands::{
277        border_image_outset, border_image_repeat, border_image_slice, border_image_source,
278        border_image_width,
279    };
280
281    pub fn parse_value(
282        context: &ParserContext,
283        input: &mut Parser,
284    ) -> Result<Longhands, ParseError> {
285        let mut outset = border_image_outset::get_initial_specified_value();
286        let mut repeat = border_image_repeat::get_initial_specified_value();
287        let mut slice = border_image_slice::get_initial_specified_value();
288        let mut source = border_image_source::get_initial_specified_value();
289        let mut width = border_image_width::get_initial_specified_value();
290        let mut any = false;
291        let mut parsed_slice = false;
292        let mut parsed_source = false;
293        let mut parsed_repeat = false;
294        loop {
295            if !parsed_slice {
296                if let Ok(value) =
297                    input.try_parse(|input| border_image_slice::parse(context, input))
298                {
299                    parsed_slice = true;
300                    any = true;
301                    slice = value;
302                    // Parse border image width and outset, if applicable.
303                    let maybe_width_outset: Result<_, ParseError> = input.try_parse(|input| {
304                        input.expect_delim('/')?;
305
306                        // Parse border image width, if applicable.
307                        let w = input
308                            .try_parse(|input| border_image_width::parse(context, input))
309                            .ok();
310
311                        // Parse border image outset if applicable.
312                        let o = input
313                            .try_parse(|input| {
314                                input.expect_delim('/')?;
315                                border_image_outset::parse(context, input)
316                            })
317                            .ok();
318                        if w.is_none() && o.is_none() {
319                            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
320                        }
321                        Ok((w, o))
322                    });
323                    if let Ok((w, o)) = maybe_width_outset {
324                        if let Some(w) = w {
325                            width = w;
326                        }
327                        if let Some(o) = o {
328                            outset = o;
329                        }
330                    }
331                    continue;
332                }
333            }
334            if !parsed_source {
335                if let Ok(value) =
336                    input.try_parse(|input| border_image_source::parse(context, input))
337                {
338                    source = value;
339                    parsed_source = true;
340                    any = true;
341                    continue;
342                }
343            }
344            if !parsed_repeat {
345                if let Ok(value) =
346                    input.try_parse(|input| border_image_repeat::parse(context, input))
347                {
348                    repeat = value;
349                    parsed_repeat = true;
350                    any = true;
351                    continue;
352                }
353            }
354            break;
355        }
356        if !any {
357            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
358        }
359        Ok(expanded! {
360           border_image_outset: outset,
361           border_image_repeat: repeat,
362           border_image_slice: slice,
363           border_image_source: source,
364           border_image_width: width,
365        })
366    }
367
368    impl<'a> ToCss for LonghandsToSerialize<'a> {
369        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
370        where
371            W: fmt::Write,
372        {
373            let mut has_any = false;
374            let has_source =
375                *self.border_image_source != border_image_source::get_initial_specified_value();
376            has_any = has_any || has_source;
377            let has_slice =
378                *self.border_image_slice != border_image_slice::get_initial_specified_value();
379            has_any = has_any || has_slice;
380            let has_outset =
381                *self.border_image_outset != border_image_outset::get_initial_specified_value();
382            has_any = has_any || has_outset;
383            let has_width =
384                *self.border_image_width != border_image_width::get_initial_specified_value();
385            has_any = has_any || has_width;
386            let has_repeat =
387                *self.border_image_repeat != border_image_repeat::get_initial_specified_value();
388            has_any = has_any || has_repeat;
389            if has_source || !has_any {
390                self.border_image_source.to_css(dest)?;
391                if !has_any {
392                    return Ok(());
393                }
394            }
395            let needs_slice = has_slice || has_width || has_outset;
396            if needs_slice {
397                if has_source {
398                    dest.write_char(' ')?;
399                }
400                self.border_image_slice.to_css(dest)?;
401                if has_width || has_outset {
402                    dest.write_str(" /")?;
403                    if has_width {
404                        dest.write_char(' ')?;
405                        self.border_image_width.to_css(dest)?;
406                    }
407                    if has_outset {
408                        dest.write_str(" / ")?;
409                        self.border_image_outset.to_css(dest)?;
410                    }
411                }
412            }
413            if has_repeat {
414                if has_source || needs_slice {
415                    dest.write_char(' ')?;
416                }
417                self.border_image_repeat.to_css(dest)?;
418            }
419            Ok(())
420        }
421    }
422}
423
424pub mod border {
425    pub use crate::properties::generated::shorthands::border::*;
426
427    use super::*;
428    pub use crate::properties::generated::shorthands::border_left;
429    use crate::properties::longhands::{
430        border_image_outset, border_image_repeat, border_image_slice, border_image_source,
431        border_image_width,
432    };
433
434    pub fn parse_value(
435        context: &ParserContext,
436        input: &mut Parser,
437    ) -> Result<Longhands, ParseError> {
438        let (width, style, color) = super::parse_border(context, input)?;
439        Ok(expanded! {
440            border_top_width: width.clone(),
441            border_top_style: style,
442            border_top_color: color.clone(),
443            border_right_width: width.clone(),
444            border_right_style: style,
445            border_right_color: color.clone(),
446            border_bottom_width: width.clone(),
447            border_bottom_style: style,
448            border_bottom_color: color.clone(),
449            border_left_width: width.clone(),
450            border_left_style: style,
451            border_left_color: color.clone(),
452
453            // The 'border' shorthand resets 'border-image' to its initial value.
454            // See https://drafts.csswg.org/css-backgrounds-3/#the-border-shorthands
455            border_image_outset: border_image_outset::get_initial_specified_value(),
456            border_image_repeat: border_image_repeat::get_initial_specified_value(),
457            border_image_slice: border_image_slice::get_initial_specified_value(),
458            border_image_source: border_image_source::get_initial_specified_value(),
459            border_image_width: border_image_width::get_initial_specified_value(),
460        })
461    }
462
463    impl<'a> ToCss for LonghandsToSerialize<'a> {
464        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
465        where
466            W: fmt::Write,
467        {
468            use crate::properties::longhands;
469
470            // If any of the border-image longhands differ from their initial specified values we should not
471            // invoke serialize_directional_border(), so there is no point in continuing on to compute all_equal.
472            if *self.border_image_outset
473                != longhands::border_image_outset::get_initial_specified_value()
474            {
475                return Ok(());
476            }
477            if *self.border_image_repeat
478                != longhands::border_image_repeat::get_initial_specified_value()
479            {
480                return Ok(());
481            }
482            if *self.border_image_slice
483                != longhands::border_image_slice::get_initial_specified_value()
484            {
485                return Ok(());
486            }
487            if *self.border_image_source
488                != longhands::border_image_source::get_initial_specified_value()
489            {
490                return Ok(());
491            }
492            if *self.border_image_width
493                != longhands::border_image_width::get_initial_specified_value()
494            {
495                return Ok(());
496            }
497
498            let all_equal = {
499                let border_top_width = self.border_top_width;
500                let border_top_style = self.border_top_style;
501                let border_top_color = self.border_top_color;
502                let border_right_width = self.border_right_width;
503                let border_right_style = self.border_right_style;
504                let border_right_color = self.border_right_color;
505                let border_bottom_width = self.border_bottom_width;
506                let border_bottom_style = self.border_bottom_style;
507                let border_bottom_color = self.border_bottom_color;
508                let border_left_width = self.border_left_width;
509                let border_left_style = self.border_left_style;
510                let border_left_color = self.border_left_color;
511
512                border_top_width == border_right_width
513                    && border_right_width == border_bottom_width
514                    && border_bottom_width == border_left_width
515                    && border_top_style == border_right_style
516                    && border_right_style == border_bottom_style
517                    && border_bottom_style == border_left_style
518                    && border_top_color == border_right_color
519                    && border_right_color == border_bottom_color
520                    && border_bottom_color == border_left_color
521            };
522
523            // If all longhands are all present, then all sides should be the same,
524            // so we can just one set of color/style/width
525            if !all_equal {
526                return Ok(());
527            }
528            super::serialize_directional_border(
529                dest,
530                self.border_left_width,
531                self.border_left_style,
532                self.border_left_color,
533            )
534        }
535    }
536
537    // We need to implement this by hand because deriving this would also derive border-image,
538    // which this property only resets. Just use the same as border-left for simplicity.
539    impl SpecifiedValueInfo for Longhands {
540        const SUPPORTED_TYPES: u8 = border_left::Longhands::SUPPORTED_TYPES;
541
542        fn collect_completion_keywords(f: KeywordsCollectFn) {
543            border_left::Longhands::collect_completion_keywords(f);
544        }
545    }
546}
547
548#[cfg(feature = "gecko")]
549pub mod container {
550    use super::*;
551    pub use crate::properties::generated::shorthands::container::*;
552
553    use crate::values::specified::{ContainerName, ContainerType};
554
555    pub fn parse_value(
556        context: &ParserContext,
557        input: &mut Parser,
558    ) -> Result<Longhands, ParseError> {
559        // See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't match the spec.
560        let container_name = ContainerName::parse(context, input)?;
561        let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
562            ContainerType::parse(context, input)?
563        } else {
564            ContainerType::NORMAL
565        };
566        Ok(expanded! {
567            container_name: container_name,
568            container_type: container_type,
569        })
570    }
571
572    impl<'a> ToCss for LonghandsToSerialize<'a> {
573        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
574        where
575            W: fmt::Write,
576        {
577            self.container_name.to_css(dest)?;
578            if !self.container_type.is_normal() {
579                dest.write_str(" / ")?;
580                self.container_type.to_css(dest)?;
581            }
582            Ok(())
583        }
584    }
585}
586
587pub mod vertical_align {
588    use super::*;
589    pub use crate::properties::generated::shorthands::vertical_align::*;
590
591    use crate::values::specified::{AlignmentBaseline, BaselineShift, BaselineSource};
592
593    pub fn parse_value(
594        context: &ParserContext,
595        input: &mut Parser,
596    ) -> Result<Longhands, ParseError> {
597        let mut baseline_source = None;
598        let mut alignment_baseline = None;
599        let mut baseline_shift = None;
600        let mut parsed = 0;
601
602        loop {
603            parsed += 1;
604
605            try_parse_one!(input, baseline_source, BaselineSource::parse_non_auto);
606            try_parse_one!(input, alignment_baseline, AlignmentBaseline::parse);
607            try_parse_one!(context, input, baseline_shift, BaselineShift::parse);
608
609            parsed -= 1;
610            break;
611        }
612
613        if parsed == 0 {
614            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
615        }
616
617        Ok(expanded! {
618            baseline_source: baseline_source.unwrap_or(BaselineSource::Auto),
619            alignment_baseline: alignment_baseline.unwrap_or(AlignmentBaseline::Baseline),
620            baseline_shift: baseline_shift.unwrap_or(BaselineShift::zero()),
621        })
622    }
623
624    impl<'a> ToCss for LonghandsToSerialize<'a> {
625        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
626        where
627            W: fmt::Write,
628        {
629            let mut writer = SequenceWriter::new(dest, " ");
630            if *self.baseline_source != BaselineSource::Auto {
631                writer.item(self.baseline_source)?;
632            }
633            if *self.alignment_baseline != AlignmentBaseline::Baseline {
634                writer.item(self.alignment_baseline)?;
635            }
636            if *self.baseline_shift != BaselineShift::zero() {
637                writer.item(self.baseline_shift)?;
638            }
639            if !writer.has_written() {
640                self.alignment_baseline.to_css(dest)?;
641            }
642            Ok(())
643        }
644    }
645}
646
647#[cfg(feature = "gecko")]
648pub mod offset {
649    use super::*;
650    pub use crate::properties::generated::shorthands::offset::*;
651    use crate::values::specified::{
652        LengthPercentage, OffsetPath, OffsetPosition, OffsetRotate, PositionOrAuto,
653    };
654    use crate::Zero;
655
656    pub fn parse_value(
657        context: &ParserContext,
658        input: &mut Parser,
659    ) -> Result<Longhands, ParseError> {
660        let offset_position = input.try_parse(|i| OffsetPosition::parse(context, i)).ok();
661        let offset_path = input.try_parse(|i| OffsetPath::parse(context, i)).ok();
662
663        // Must have one of [offset-position, offset-path].
664        // FIXME: The syntax is out-of-date after the update of the spec.
665        if offset_position.is_none() && offset_path.is_none() {
666            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
667        }
668
669        let mut offset_distance = None;
670        let mut offset_rotate = None;
671        // offset-distance and offset-rotate are grouped with offset-path.
672        if offset_path.is_some() {
673            loop {
674                if offset_distance.is_none() {
675                    if let Ok(value) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
676                        offset_distance = Some(value);
677                    }
678                }
679
680                if offset_rotate.is_none() {
681                    if let Ok(value) = input.try_parse(|i| OffsetRotate::parse(context, i)) {
682                        offset_rotate = Some(value);
683                        continue;
684                    }
685                }
686                break;
687            }
688        }
689
690        let offset_anchor = input
691            .try_parse(|i| {
692                i.expect_delim('/')?;
693                PositionOrAuto::parse(context, i)
694            })
695            .ok();
696
697        Ok(expanded! {
698            offset_position: offset_position.unwrap_or(OffsetPosition::normal()),
699            offset_path: offset_path.unwrap_or(OffsetPath::none()),
700            offset_distance: offset_distance.unwrap_or(LengthPercentage::zero()),
701            offset_rotate: offset_rotate.unwrap_or(OffsetRotate::auto()),
702            offset_anchor: offset_anchor.unwrap_or(PositionOrAuto::auto()),
703        })
704    }
705
706    impl<'a> ToCss for LonghandsToSerialize<'a> {
707        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
708        where
709            W: fmt::Write,
710        {
711            // The basic concept is: we must serialize offset-position or offset-path group.
712            // offset-path group means "offset-path offset-distance offset-rotate".
713            let must_serialize_path = *self.offset_path != OffsetPath::None
714                || (!self.offset_distance.is_zero() || !self.offset_rotate.is_auto());
715            let position_is_default = matches!(self.offset_position, OffsetPosition::Normal);
716            if !position_is_default || !must_serialize_path {
717                self.offset_position.to_css(dest)?;
718            }
719
720            if must_serialize_path {
721                if !position_is_default {
722                    dest.write_char(' ')?;
723                }
724                self.offset_path.to_css(dest)?;
725            }
726
727            if !self.offset_distance.is_zero() {
728                dest.write_char(' ')?;
729                self.offset_distance.to_css(dest)?;
730            }
731
732            if !self.offset_rotate.is_auto() {
733                dest.write_char(' ')?;
734                self.offset_rotate.to_css(dest)?;
735            }
736
737            if *self.offset_anchor != PositionOrAuto::auto() {
738                dest.write_str(" / ")?;
739                self.offset_anchor.to_css(dest)?;
740            }
741            Ok(())
742        }
743    }
744}
745
746pub mod columns {
747    pub use crate::properties::generated::shorthands::columns::*;
748
749    use super::*;
750    use crate::properties::longhands::{column_count, column_width};
751
752    pub fn parse_value(
753        context: &ParserContext,
754        input: &mut Parser,
755    ) -> Result<Longhands, ParseError> {
756        let mut column_count = None;
757        let mut column_width = None;
758        let mut autos = 0;
759
760        loop {
761            if input
762                .try_parse(|input| input.expect_ident_matching("auto"))
763                .is_ok()
764            {
765                // Leave the options to None, 'auto' is the initial value.
766                autos += 1;
767                continue;
768            }
769
770            if column_count.is_none() {
771                if let Ok(value) = input.try_parse(|input| column_count::parse(context, input)) {
772                    column_count = Some(value);
773                    continue;
774                }
775            }
776
777            if column_width.is_none() {
778                if let Ok(value) = input.try_parse(|input| column_width::parse(context, input)) {
779                    column_width = Some(value);
780                    continue;
781                }
782            }
783
784            break;
785        }
786
787        let values = autos + column_count.iter().len() + column_width.iter().len();
788        if values == 0 || values > 2 {
789            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
790        } else {
791            Ok(expanded! {
792                column_count: unwrap_or_initial!(column_count),
793                column_width: unwrap_or_initial!(column_width),
794            })
795        }
796    }
797
798    impl<'a> ToCss for LonghandsToSerialize<'a> {
799        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
800        where
801            W: fmt::Write,
802        {
803            if self.column_width.is_auto() {
804                return self.column_count.to_css(dest);
805            }
806            self.column_width.to_css(dest)?;
807            if !self.column_count.is_auto() {
808                dest.write_char(' ')?;
809                self.column_count.to_css(dest)?;
810            }
811            Ok(())
812        }
813    }
814}
815
816#[cfg(feature = "gecko")]
817pub mod column_rule {
818    pub use crate::properties::generated::shorthands::column_rule::*;
819
820    use super::*;
821    use crate::properties::longhands::column_rule_color;
822    use crate::properties::longhands::{column_rule_style, column_rule_width};
823
824    pub fn parse_value(
825        context: &ParserContext,
826        input: &mut Parser,
827    ) -> Result<Longhands, ParseError> {
828        let mut column_rule_width = None;
829        let mut column_rule_style = None;
830        let mut column_rule_color = None;
831        let mut parsed = 0;
832        loop {
833            parsed += 1;
834            try_parse_one!(context, input, column_rule_width, column_rule_width::parse);
835            try_parse_one!(context, input, column_rule_style, column_rule_style::parse);
836            try_parse_one!(context, input, column_rule_color, column_rule_color::parse);
837            parsed -= 1;
838            break;
839        }
840        if parsed == 0 {
841            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
842        }
843        Ok(expanded! {
844            column_rule_width: unwrap_or_initial!(column_rule_width),
845            column_rule_style: unwrap_or_initial!(column_rule_style),
846            column_rule_color: unwrap_or_initial!(column_rule_color),
847        })
848    }
849}
850
851#[cfg(feature = "gecko")]
852pub mod text_wrap {
853    pub use crate::properties::generated::shorthands::text_wrap::*;
854
855    use super::*;
856    use crate::properties::longhands::{text_wrap_mode, text_wrap_style};
857
858    pub fn parse_value(
859        context: &ParserContext,
860        input: &mut Parser,
861    ) -> Result<Longhands, ParseError> {
862        let mut mode = None;
863        let mut style = None;
864        let mut parsed = 0;
865        loop {
866            parsed += 1;
867            try_parse_one!(context, input, mode, text_wrap_mode::parse);
868            try_parse_one!(context, input, style, text_wrap_style::parse);
869            parsed -= 1;
870            break;
871        }
872        if parsed == 0 {
873            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
874        }
875        Ok(expanded! {
876            text_wrap_mode: unwrap_or_initial!(text_wrap_mode, mode),
877            text_wrap_style: unwrap_or_initial!(text_wrap_style, style),
878        })
879    }
880
881    impl<'a> ToCss for LonghandsToSerialize<'a> {
882        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
883        where
884            W: fmt::Write,
885        {
886            use text_wrap_mode::computed_value::T as Mode;
887            use text_wrap_style::computed_value::T as Style;
888
889            if matches!(self.text_wrap_style, &Style::Auto) {
890                return self.text_wrap_mode.to_css(dest);
891            }
892
893            if *self.text_wrap_mode != Mode::Wrap {
894                self.text_wrap_mode.to_css(dest)?;
895                dest.write_char(' ')?;
896            }
897
898            self.text_wrap_style.to_css(dest)
899        }
900    }
901}
902
903pub mod white_space {
904    pub use crate::properties::generated::shorthands::white_space::*;
905
906    use super::*;
907    use crate::properties::longhands::{text_wrap_mode, white_space_collapse};
908
909    pub fn parse_value(
910        context: &ParserContext,
911        input: &mut Parser,
912    ) -> Result<Longhands, ParseError> {
913        use text_wrap_mode::computed_value::T as Wrap;
914        use white_space_collapse::computed_value::T as Collapse;
915
916        fn parse_special_shorthands(input: &mut Parser) -> Result<Longhands, ParseError> {
917            let (mode, collapse) = try_match_ident_ignore_ascii_case! { input,
918                "normal" => (Wrap::Wrap, Collapse::Collapse),
919                "pre" => (Wrap::Nowrap, Collapse::Preserve),
920                "pre-wrap" => (Wrap::Wrap, Collapse::Preserve),
921                "pre-line" => (Wrap::Wrap, Collapse::PreserveBreaks),
922            };
923            Ok(expanded! {
924                text_wrap_mode: mode,
925                white_space_collapse: collapse,
926            })
927        }
928
929        if let Ok(result) = input.try_parse(parse_special_shorthands) {
930            return Ok(result);
931        }
932
933        let mut wrap = None;
934        let mut collapse = None;
935        let mut parsed = 0;
936
937        loop {
938            parsed += 1;
939            try_parse_one!(context, input, wrap, text_wrap_mode::parse);
940            try_parse_one!(context, input, collapse, white_space_collapse::parse);
941            parsed -= 1;
942            break;
943        }
944
945        if parsed == 0 {
946            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
947        }
948
949        Ok(expanded! {
950            text_wrap_mode: unwrap_or_initial!(text_wrap_mode, wrap),
951            white_space_collapse: unwrap_or_initial!(white_space_collapse, collapse),
952        })
953    }
954
955    impl<'a> ToCss for LonghandsToSerialize<'a> {
956        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
957        where
958            W: fmt::Write,
959        {
960            use text_wrap_mode::computed_value::T as Wrap;
961            use white_space_collapse::computed_value::T as Collapse;
962
963            match *self.text_wrap_mode {
964                Wrap::Wrap => match *self.white_space_collapse {
965                    Collapse::Collapse => return dest.write_str("normal"),
966                    Collapse::Preserve => return dest.write_str("pre-wrap"),
967                    Collapse::PreserveBreaks => return dest.write_str("pre-line"),
968                    _ => (),
969                },
970                Wrap::Nowrap => {
971                    if let Collapse::Preserve = *self.white_space_collapse {
972                        return dest.write_str("pre");
973                    }
974                },
975            }
976
977            let mut has_value = false;
978            if *self.white_space_collapse != Collapse::Collapse {
979                self.white_space_collapse.to_css(dest)?;
980                has_value = true;
981            }
982
983            if *self.text_wrap_mode != Wrap::Wrap {
984                if has_value {
985                    dest.write_char(' ')?;
986                }
987                self.text_wrap_mode.to_css(dest)?;
988            }
989
990            Ok(())
991        }
992    }
993
994    impl SpecifiedValueInfo for Longhands {
995        fn collect_completion_keywords(f: KeywordsCollectFn) {
996            // Collect keywords from our longhands.
997            text_wrap_mode::SpecifiedValue::collect_completion_keywords(f);
998            white_space_collapse::SpecifiedValue::collect_completion_keywords(f);
999
1000            // Add the special values supported only by the shorthand
1001            // (see parse_special_shorthands() above).
1002            f(&["normal", "pre", "pre-wrap", "pre-line"])
1003        }
1004    }
1005}
1006
1007#[cfg(feature = "gecko")]
1008pub mod _webkit_text_stroke {
1009    pub use crate::properties::generated::shorthands::_webkit_text_stroke::*;
1010
1011    use super::*;
1012    use crate::properties::longhands::{_webkit_text_stroke_color, _webkit_text_stroke_width};
1013
1014    pub fn parse_value(
1015        context: &ParserContext,
1016        input: &mut Parser,
1017    ) -> Result<Longhands, ParseError> {
1018        let mut color = None;
1019        let mut width = None;
1020        let mut parsed = 0;
1021        loop {
1022            parsed += 1;
1023            try_parse_one!(context, input, color, _webkit_text_stroke_color::parse);
1024            try_parse_one!(context, input, width, _webkit_text_stroke_width::parse);
1025            parsed -= 1;
1026            break;
1027        }
1028        if parsed == 0 {
1029            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1030        }
1031        Ok(expanded! {
1032            _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color),
1033            _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width),
1034        })
1035    }
1036}
1037
1038pub mod list_style {
1039    pub use crate::properties::generated::shorthands::list_style::*;
1040
1041    use super::*;
1042    use crate::properties::longhands::{list_style_image, list_style_position, list_style_type};
1043    use crate::values::specified::Image;
1044    use selectors::parser::SelectorParseErrorKind;
1045
1046    pub fn parse_value(
1047        context: &ParserContext,
1048        input: &mut Parser,
1049    ) -> Result<Longhands, ParseError> {
1050        // `none` is ambiguous until we've finished parsing the shorthands, so we count the number
1051        // of times we see it.
1052        let mut nones = 0u8;
1053        let (mut image, mut position, mut list_style_type) = (None, None, None);
1054        let mut parsed = 0;
1055        loop {
1056            parsed += 1;
1057
1058            if input
1059                .try_parse(|input| input.expect_ident_matching("none"))
1060                .is_ok()
1061            {
1062                nones += 1;
1063                if nones > 2 {
1064                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
1065                }
1066                continue;
1067            }
1068
1069            try_parse_one!(context, input, image, list_style_image::parse);
1070            try_parse_one!(context, input, position, list_style_position::parse);
1071            // list-style-type must be checked the last, because it accepts
1072            // arbitrary identifier for custom counter style, and thus may
1073            // affect values of list-style-position.
1074            try_parse_one!(context, input, list_style_type, list_style_type::parse);
1075
1076            parsed -= 1;
1077            break;
1078        }
1079
1080        let position = unwrap_or_initial!(list_style_position, position);
1081
1082        if parsed == 0 {
1083            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1084        }
1085
1086        // If there are two `none`s, then we can't have a type or image; if there is one `none`,
1087        // then we can't have both a type *and* an image; if there is no `none` then we're fine as
1088        // long as we parsed something.
1089        use self::list_style_type::SpecifiedValue as ListStyleType;
1090        match (nones, list_style_type, image) {
1091            (2, None, None) => Ok(expanded! {
1092                list_style_position: position,
1093                list_style_image: Image::None,
1094                list_style_type: ListStyleType::none(),
1095            }),
1096            (1, None, Some(image)) => Ok(expanded! {
1097                list_style_position: position,
1098                list_style_image: image,
1099                list_style_type: ListStyleType::none(),
1100            }),
1101            (1, Some(list_style_type), None) => Ok(expanded! {
1102                list_style_position: position,
1103                list_style_image: Image::None,
1104                list_style_type: list_style_type,
1105            }),
1106            (1, None, None) => Ok(expanded! {
1107                list_style_position: position,
1108                list_style_image: Image::None,
1109                list_style_type: ListStyleType::none(),
1110            }),
1111            (0, list_style_type, image) => Ok(expanded! {
1112                list_style_position: position,
1113                list_style_image: unwrap_or_initial!(list_style_image, image),
1114                list_style_type: unwrap_or_initial!(list_style_type),
1115            }),
1116            _ => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
1117        }
1118    }
1119
1120    impl<'a> ToCss for LonghandsToSerialize<'a> {
1121        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1122        where
1123            W: fmt::Write,
1124        {
1125            use list_style_image::SpecifiedValue as ListStyleImage;
1126            use list_style_position::SpecifiedValue as ListStylePosition;
1127            use list_style_type::SpecifiedValue as ListStyleType;
1128
1129            let mut writer = SequenceWriter::new(dest, " ");
1130            if *self.list_style_position != ListStylePosition::Outside
1131                || self.list_style_type.is_name(&atom!("outside"))
1132            {
1133                writer.item(self.list_style_position)?;
1134            }
1135            if *self.list_style_image != ListStyleImage::None {
1136                writer.item(self.list_style_image)?;
1137            }
1138            if *self.list_style_type != ListStyleType::disc() {
1139                writer.item(self.list_style_type)?;
1140            }
1141            if !writer.has_written() {
1142                self.list_style_position.to_css(dest)?;
1143            }
1144            Ok(())
1145        }
1146    }
1147}
1148
1149pub mod gap {
1150    pub use crate::properties::generated::shorthands::gap::*;
1151
1152    use super::*;
1153    use crate::properties::longhands::{column_gap, row_gap};
1154
1155    pub fn parse_value(
1156        context: &ParserContext,
1157        input: &mut Parser,
1158    ) -> Result<Longhands, ParseError> {
1159        let r_gap = row_gap::parse(context, input)?;
1160        let c_gap = input
1161            .try_parse(|input| column_gap::parse(context, input))
1162            .unwrap_or(r_gap.clone());
1163
1164        Ok(expanded! {
1165            row_gap: r_gap,
1166            column_gap: c_gap,
1167        })
1168    }
1169
1170    impl<'a> ToCss for LonghandsToSerialize<'a> {
1171        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1172        where
1173            W: fmt::Write,
1174        {
1175            self.row_gap.to_css(dest)?;
1176            if self.row_gap != self.column_gap {
1177                dest.write_char(' ')?;
1178                self.column_gap.to_css(dest)?;
1179            }
1180            Ok(())
1181        }
1182    }
1183}
1184
1185#[cfg(feature = "gecko")]
1186pub mod marker {
1187    pub use crate::properties::generated::shorthands::marker::*;
1188
1189    use super::*;
1190    use crate::values::specified::url::UrlOrNone;
1191
1192    pub fn parse_value(
1193        context: &ParserContext,
1194        input: &mut Parser,
1195    ) -> Result<Longhands, ParseError> {
1196        let url = UrlOrNone::parse(context, input)?;
1197
1198        Ok(expanded! {
1199            marker_start: url.clone(),
1200            marker_mid: url.clone(),
1201            marker_end: url,
1202        })
1203    }
1204
1205    impl<'a> ToCss for LonghandsToSerialize<'a> {
1206        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1207        where
1208            W: fmt::Write,
1209        {
1210            if self.marker_start == self.marker_mid && self.marker_mid == self.marker_end {
1211                self.marker_start.to_css(dest)
1212            } else {
1213                Ok(())
1214            }
1215        }
1216    }
1217}
1218
1219pub mod flex_flow {
1220    pub use crate::properties::generated::shorthands::flex_flow::*;
1221
1222    use super::*;
1223    use crate::properties::longhands::{flex_direction, flex_wrap};
1224
1225    pub fn parse_value(
1226        context: &ParserContext,
1227        input: &mut Parser,
1228    ) -> Result<Longhands, ParseError> {
1229        let mut parsed = 0;
1230        let mut direction = None;
1231        let mut wrap = None;
1232        loop {
1233            parsed += 1;
1234            try_parse_one!(context, input, direction, flex_direction::parse);
1235            try_parse_one!(context, input, wrap, flex_wrap::parse);
1236            parsed -= 1;
1237            break;
1238        }
1239        if parsed == 0 {
1240            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1241        }
1242        Ok(expanded! {
1243            flex_direction: unwrap_or_initial!(flex_direction, direction),
1244            flex_wrap: unwrap_or_initial!(flex_wrap, wrap),
1245        })
1246    }
1247
1248    impl<'a> ToCss for LonghandsToSerialize<'a> {
1249        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1250        where
1251            W: fmt::Write,
1252        {
1253            if *self.flex_direction == flex_direction::get_initial_specified_value()
1254                && *self.flex_wrap != flex_wrap::get_initial_specified_value()
1255            {
1256                return self.flex_wrap.to_css(dest);
1257            }
1258            self.flex_direction.to_css(dest)?;
1259            if *self.flex_wrap != flex_wrap::get_initial_specified_value() {
1260                dest.write_char(' ')?;
1261                self.flex_wrap.to_css(dest)?;
1262            }
1263            Ok(())
1264        }
1265    }
1266}
1267
1268pub mod flex {
1269    pub use crate::properties::generated::shorthands::flex::*;
1270
1271    use super::*;
1272    use crate::properties::longhands::flex_basis::SpecifiedValue as FlexBasis;
1273    use crate::values::specified::NonNegativeNumber;
1274
1275    fn parse_flexibility(
1276        context: &ParserContext,
1277        input: &mut Parser,
1278    ) -> Result<(NonNegativeNumber, Option<NonNegativeNumber>), ParseError> {
1279        let grow = NonNegativeNumber::parse(context, input)?;
1280        let shrink = input
1281            .try_parse(|i| NonNegativeNumber::parse(context, i))
1282            .ok();
1283        Ok((grow, shrink))
1284    }
1285
1286    pub fn parse_value(
1287        context: &ParserContext,
1288        input: &mut Parser,
1289    ) -> Result<Longhands, ParseError> {
1290        let mut grow = None;
1291        let mut shrink = None;
1292        let mut basis = None;
1293
1294        if input
1295            .try_parse(|input| input.expect_ident_matching("none"))
1296            .is_ok()
1297        {
1298            return Ok(expanded! {
1299                flex_grow: NonNegativeNumber::new(0.0),
1300                flex_shrink: NonNegativeNumber::new(0.0),
1301                flex_basis: FlexBasis::auto(),
1302            });
1303        }
1304        loop {
1305            if grow.is_none() {
1306                if let Ok((flex_grow, flex_shrink)) =
1307                    input.try_parse(|i| parse_flexibility(context, i))
1308                {
1309                    grow = Some(flex_grow);
1310                    shrink = flex_shrink;
1311                    continue;
1312                }
1313            }
1314            if basis.is_none() {
1315                if let Ok(value) = input.try_parse(|input| FlexBasis::parse(context, input)) {
1316                    basis = Some(value);
1317                    continue;
1318                }
1319            }
1320            break;
1321        }
1322
1323        if grow.is_none() && basis.is_none() {
1324            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1325        }
1326        Ok(expanded! {
1327            flex_grow: grow.unwrap_or(NonNegativeNumber::new(1.0)),
1328            flex_shrink: shrink.unwrap_or(NonNegativeNumber::new(1.0)),
1329            flex_basis: basis.unwrap_or(FlexBasis::zero_percent()),
1330        })
1331    }
1332}
1333
1334pub mod place_content {
1335    pub use crate::properties::generated::shorthands::place_content::*;
1336
1337    use super::*;
1338    use crate::values::specified::align::ContentDistribution;
1339
1340    pub fn parse_value(
1341        context: &ParserContext,
1342        input: &mut Parser,
1343    ) -> Result<Longhands, ParseError> {
1344        let align_content = ContentDistribution::parse_block(context, input)?;
1345        let justify_content =
1346            input.try_parse(|input| ContentDistribution::parse_inline(context, input));
1347
1348        let justify_content = match justify_content {
1349            Ok(v) => v,
1350            Err(..) => {
1351                if !align_content.is_baseline_position() {
1352                    align_content
1353                } else {
1354                    ContentDistribution::start()
1355                }
1356            },
1357        };
1358
1359        Ok(expanded! {
1360            align_content: align_content,
1361            justify_content: justify_content,
1362        })
1363    }
1364
1365    impl<'a> ToCss for LonghandsToSerialize<'a> {
1366        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1367        where
1368            W: fmt::Write,
1369        {
1370            self.align_content.to_css(dest)?;
1371            if self.align_content != self.justify_content {
1372                dest.write_char(' ')?;
1373                self.justify_content.to_css(dest)?;
1374            }
1375            Ok(())
1376        }
1377    }
1378}
1379
1380pub mod place_self {
1381    pub use crate::properties::generated::shorthands::place_self::*;
1382
1383    use super::*;
1384    use crate::values::specified::align::SelfAlignment;
1385
1386    pub fn parse_value(
1387        context: &ParserContext,
1388        input: &mut Parser,
1389    ) -> Result<Longhands, ParseError> {
1390        let align = SelfAlignment::parse_block(context, input)?;
1391        let justify = input.try_parse(|input| SelfAlignment::parse_inline(context, input));
1392
1393        let justify = match justify {
1394            Ok(v) => v,
1395            Err(..) => {
1396                debug_assert!(align.is_valid_on_both_axes());
1397                align
1398            },
1399        };
1400
1401        Ok(expanded! {
1402            align_self: align,
1403            justify_self: justify,
1404        })
1405    }
1406
1407    impl<'a> ToCss for LonghandsToSerialize<'a> {
1408        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1409        where
1410            W: fmt::Write,
1411        {
1412            self.align_self.to_css(dest)?;
1413            if self.align_self != self.justify_self {
1414                dest.write_char(' ')?;
1415                self.justify_self.to_css(dest)?;
1416            }
1417            Ok(())
1418        }
1419    }
1420}
1421
1422pub mod place_items {
1423    pub use crate::properties::generated::shorthands::place_items::*;
1424
1425    use super::*;
1426    use crate::values::specified::align::{ItemPlacement, JustifyItems};
1427
1428    pub fn parse_value(
1429        context: &ParserContext,
1430        input: &mut Parser,
1431    ) -> Result<Longhands, ParseError> {
1432        let align = ItemPlacement::parse_block(context, input)?;
1433        let justify = input
1434            .try_parse(|input| ItemPlacement::parse_inline(context, input))
1435            .unwrap_or(align);
1436
1437        Ok(expanded! {
1438            align_items: align,
1439            justify_items: JustifyItems(justify),
1440        })
1441    }
1442
1443    impl<'a> ToCss for LonghandsToSerialize<'a> {
1444        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1445        where
1446            W: fmt::Write,
1447        {
1448            self.align_items.to_css(dest)?;
1449            if self.align_items != &self.justify_items.0 {
1450                dest.write_char(' ')?;
1451                self.justify_items.to_css(dest)?;
1452            }
1453            Ok(())
1454        }
1455    }
1456}
1457
1458pub mod grid_row {
1459    pub use crate::properties::generated::shorthands::grid_row::*;
1460
1461    use super::*;
1462    use crate::values::specified::GridLine;
1463    use crate::Zero;
1464
1465    pub fn parse_value(
1466        context: &ParserContext,
1467        input: &mut Parser,
1468    ) -> Result<Longhands, ParseError> {
1469        let start = input.try_parse(|i| GridLine::parse(context, i))?;
1470        let end = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
1471            GridLine::parse(context, input)?
1472        } else {
1473            let mut line = GridLine::auto();
1474            if start.line_num.is_zero() && !start.is_span {
1475                line.ident = start.ident.clone();
1476            }
1477
1478            line
1479        };
1480
1481        Ok(expanded! {
1482            grid_row_start: start,
1483            grid_row_end: end,
1484        })
1485    }
1486
1487    impl<'a> ToCss for LonghandsToSerialize<'a> {
1488        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1489        where
1490            W: fmt::Write,
1491        {
1492            self.grid_row_start.to_css(dest)?;
1493            if self.grid_row_start.can_omit(self.grid_row_end) {
1494                return Ok(());
1495            }
1496            dest.write_str(" / ")?;
1497            self.grid_row_end.to_css(dest)
1498        }
1499    }
1500}
1501
1502pub mod grid_column {
1503    pub use crate::properties::generated::shorthands::grid_column::*;
1504
1505    use super::*;
1506    use crate::values::specified::GridLine;
1507    use crate::Zero;
1508
1509    pub fn parse_value(
1510        context: &ParserContext,
1511        input: &mut Parser,
1512    ) -> Result<Longhands, ParseError> {
1513        let start = input.try_parse(|i| GridLine::parse(context, i))?;
1514        let end = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
1515            GridLine::parse(context, input)?
1516        } else {
1517            let mut line = GridLine::auto();
1518            if start.line_num.is_zero() && !start.is_span {
1519                line.ident = start.ident.clone();
1520            }
1521
1522            line
1523        };
1524
1525        Ok(expanded! {
1526            grid_column_start: start,
1527            grid_column_end: end,
1528        })
1529    }
1530
1531    impl<'a> ToCss for LonghandsToSerialize<'a> {
1532        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1533        where
1534            W: fmt::Write,
1535        {
1536            self.grid_column_start.to_css(dest)?;
1537            if self.grid_column_start.can_omit(self.grid_column_end) {
1538                return Ok(());
1539            }
1540            dest.write_str(" / ")?;
1541            self.grid_column_end.to_css(dest)
1542        }
1543    }
1544}
1545
1546pub mod grid_area {
1547    pub use crate::properties::generated::shorthands::grid_area::*;
1548
1549    use super::*;
1550    use crate::values::specified::GridLine;
1551    use crate::Zero;
1552
1553    pub fn parse_value(
1554        context: &ParserContext,
1555        input: &mut Parser,
1556    ) -> Result<Longhands, ParseError> {
1557        fn line_with_ident_from(other: &GridLine) -> GridLine {
1558            let mut this = GridLine::auto();
1559            if other.line_num.is_zero() && !other.is_span {
1560                this.ident = other.ident.clone();
1561            }
1562
1563            this
1564        }
1565
1566        let row_start = input.try_parse(|i| GridLine::parse(context, i))?;
1567        let (column_start, row_end, column_end) =
1568            if input.try_parse(|i| i.expect_delim('/')).is_ok() {
1569                let column_start = GridLine::parse(context, input)?;
1570                let (row_end, column_end) = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
1571                    let row_end = GridLine::parse(context, input)?;
1572                    let column_end = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
1573                        GridLine::parse(context, input)?
1574                    } else {
1575                        line_with_ident_from(&column_start)
1576                    };
1577
1578                    (row_end, column_end)
1579                } else {
1580                    let row_end = line_with_ident_from(&row_start);
1581                    let column_end = line_with_ident_from(&column_start);
1582                    (row_end, column_end)
1583                };
1584
1585                (column_start, row_end, column_end)
1586            } else {
1587                let line = line_with_ident_from(&row_start);
1588                (line.clone(), line.clone(), line)
1589            };
1590
1591        Ok(expanded! {
1592            grid_row_start: row_start,
1593            grid_row_end: row_end,
1594            grid_column_start: column_start,
1595            grid_column_end: column_end,
1596        })
1597    }
1598
1599    impl<'a> ToCss for LonghandsToSerialize<'a> {
1600        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1601        where
1602            W: fmt::Write,
1603        {
1604            self.grid_row_start.to_css(dest)?;
1605            let mut trailing_values = 3;
1606            if self.grid_column_start.can_omit(self.grid_column_end) {
1607                trailing_values -= 1;
1608                if self.grid_row_start.can_omit(self.grid_row_end) {
1609                    trailing_values -= 1;
1610                    if self.grid_row_start.can_omit(self.grid_column_start) {
1611                        trailing_values -= 1;
1612                    }
1613                }
1614            }
1615            let values = [
1616                &self.grid_column_start,
1617                &self.grid_row_end,
1618                &self.grid_column_end,
1619            ];
1620            for value in values.iter().take(trailing_values) {
1621                dest.write_str(" / ")?;
1622                value.to_css(dest)?;
1623            }
1624            Ok(())
1625        }
1626    }
1627}
1628
1629#[cfg(feature = "gecko")]
1630pub mod position_try {
1631    pub use crate::properties::generated::shorthands::position_try::*;
1632
1633    use super::*;
1634    use crate::values::specified::position::{PositionTryFallbacks, PositionTryOrder};
1635
1636    pub fn parse_value(
1637        context: &ParserContext,
1638        input: &mut Parser,
1639    ) -> Result<Longhands, ParseError> {
1640        let order = input.try_parse(PositionTryOrder::parse).ok();
1641        let fallbacks = PositionTryFallbacks::parse(context, input)?;
1642        Ok(expanded! {
1643            position_try_order: order.unwrap_or(PositionTryOrder::normal()),
1644            position_try_fallbacks: fallbacks,
1645        })
1646    }
1647
1648    impl<'a> ToCss for LonghandsToSerialize<'a> {
1649        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1650        where
1651            W: fmt::Write,
1652        {
1653            if !self.position_try_order.is_normal() {
1654                self.position_try_order.to_css(dest)?;
1655                dest.write_char(' ')?;
1656            }
1657            self.position_try_fallbacks.to_css(dest)
1658        }
1659    }
1660}
1661
1662#[cfg(feature = "gecko")]
1663pub mod scroll_timeline {
1664    pub use crate::properties::generated::shorthands::scroll_timeline::*;
1665
1666    use super::*;
1667    use crate::properties::longhands::{scroll_timeline_axis, scroll_timeline_name};
1668
1669    pub fn parse_value(
1670        context: &ParserContext,
1671        input: &mut Parser,
1672    ) -> Result<Longhands, ParseError> {
1673        let mut names = Vec::with_capacity(1);
1674        let mut axes = Vec::with_capacity(1);
1675        input.parse_comma_separated(|input| {
1676            let name = scroll_timeline_name::single_value::parse(context, input)?;
1677            let axis = input.try_parse(|i| scroll_timeline_axis::single_value::parse(context, i));
1678
1679            names.push(name);
1680            axes.push(axis.unwrap_or_default());
1681
1682            Ok(())
1683        })?;
1684
1685        Ok(expanded! {
1686            scroll_timeline_name: scroll_timeline_name::SpecifiedValue(names.into()),
1687            scroll_timeline_axis: scroll_timeline_axis::SpecifiedValue(axes.into()),
1688        })
1689    }
1690
1691    impl<'a> ToCss for LonghandsToSerialize<'a> {
1692        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1693        where
1694            W: fmt::Write,
1695        {
1696            if self.scroll_timeline_name.0.len() != self.scroll_timeline_axis.0.len() {
1697                return Ok(());
1698            }
1699            let mut first = true;
1700            for (name, axis) in std::iter::zip(
1701                self.scroll_timeline_name.0.iter(),
1702                self.scroll_timeline_axis.0.iter(),
1703            ) {
1704                if !first {
1705                    dest.write_str(", ")?;
1706                }
1707                name.to_css(dest)?;
1708                if !axis.is_default() {
1709                    dest.write_char(' ')?;
1710                    axis.to_css(dest)?;
1711                }
1712                first = false;
1713            }
1714            Ok(())
1715        }
1716    }
1717}
1718
1719#[cfg(feature = "gecko")]
1720pub mod view_timeline {
1721    pub use crate::properties::generated::shorthands::view_timeline::*;
1722
1723    use super::*;
1724    use crate::properties::longhands::{
1725        view_timeline_axis, view_timeline_inset, view_timeline_name,
1726    };
1727
1728    pub fn parse_value(
1729        context: &ParserContext,
1730        input: &mut Parser,
1731    ) -> Result<Longhands, ParseError> {
1732        let mut names = Vec::with_capacity(1);
1733        let mut axes = Vec::with_capacity(1);
1734        let mut insets = Vec::with_capacity(1);
1735        input.parse_comma_separated(|input| {
1736            let name = view_timeline_name::single_value::parse(context, input)?;
1737            let mut axis = None;
1738            let mut inset = None;
1739
1740            loop {
1741                try_parse_one!(
1742                    context,
1743                    input,
1744                    axis,
1745                    view_timeline_axis::single_value::parse
1746                );
1747                try_parse_one!(
1748                    context,
1749                    input,
1750                    inset,
1751                    view_timeline_inset::single_value::parse
1752                );
1753                break;
1754            }
1755
1756            names.push(name);
1757            axes.push(axis.unwrap_or_default());
1758            insets.push(inset.unwrap_or_default());
1759
1760            Ok(())
1761        })?;
1762
1763        Ok(expanded! {
1764            view_timeline_name: view_timeline_name::SpecifiedValue(names.into()),
1765            view_timeline_axis: view_timeline_axis::SpecifiedValue(axes.into()),
1766            view_timeline_inset: view_timeline_inset::SpecifiedValue(insets.into()),
1767        })
1768    }
1769
1770    impl<'a> ToCss for LonghandsToSerialize<'a> {
1771        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1772        where
1773            W: fmt::Write,
1774        {
1775            use itertools::izip;
1776            if self.view_timeline_name.0.len() != self.view_timeline_axis.0.len()
1777                || self.view_timeline_name.0.len() != self.view_timeline_inset.0.len()
1778            {
1779                return Ok(());
1780            }
1781            let mut first = true;
1782            for (name, axis, inset) in izip!(
1783                self.view_timeline_name.0.iter(),
1784                self.view_timeline_axis.0.iter(),
1785                self.view_timeline_inset.0.iter(),
1786            ) {
1787                if !first {
1788                    dest.write_str(", ")?;
1789                }
1790                name.to_css(dest)?;
1791                if !axis.is_default() {
1792                    dest.write_char(' ')?;
1793                    axis.to_css(dest)?;
1794                }
1795                if !inset.is_auto() {
1796                    dest.write_char(' ')?;
1797                    inset.to_css(dest)?;
1798                }
1799                first = false;
1800            }
1801            Ok(())
1802        }
1803    }
1804}
1805
1806#[cfg(feature = "gecko")]
1807pub mod animation_range {
1808    pub use crate::properties::generated::shorthands::animation_range::*;
1809
1810    use super::*;
1811    use crate::properties::longhands::{animation_range_end, animation_range_start};
1812    use crate::values::specified::LengthPercentage;
1813
1814    pub fn parse_value(
1815        context: &ParserContext,
1816        input: &mut Parser,
1817    ) -> Result<Longhands, ParseError> {
1818        let mut starts = Vec::with_capacity(1);
1819        let mut ends = Vec::with_capacity(1);
1820        input.parse_comma_separated(|input| {
1821            let start = animation_range_start::single_value::parse(context, input)?;
1822            let end = input
1823                .try_parse(|i| animation_range_end::single_value::parse(context, i))
1824                .unwrap_or_else(|_| {
1825                    use crate::values::generics::animation::AnimationRangeEnd;
1826                    use crate::values::specified::animation::{
1827                        AnimationRangeValue, TimelineRangeName,
1828                    };
1829
1830                    // If `<animation-range-start>` includes a timeline range name,
1831                    // `animation-range-end` is set to that same timeline range name and 100%.
1832                    // Otherwise, any omitted longhand is set to its initial value.
1833                    let name = if start.0.name.is_none() {
1834                        TimelineRangeName::Normal
1835                    } else {
1836                        start.0.name
1837                    };
1838                    AnimationRangeEnd(AnimationRangeValue::new(
1839                        name,
1840                        LengthPercentage::hundred_percent(),
1841                    ))
1842                });
1843
1844            starts.push(start);
1845            ends.push(end);
1846            Ok(())
1847        })?;
1848
1849        Ok(expanded! {
1850            animation_range_start: animation_range_start::SpecifiedValue(starts.into()),
1851            animation_range_end: animation_range_end::SpecifiedValue(ends.into()),
1852        })
1853    }
1854
1855    impl<'a> ToCss for LonghandsToSerialize<'a> {
1856        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1857        where
1858            W: fmt::Write,
1859        {
1860            use crate::values::specified::length::EqualsPercentage;
1861            let starts = &self.animation_range_start.0;
1862            let ends = &self.animation_range_end.0;
1863            if starts.len() != ends.len() {
1864                return Ok(());
1865            }
1866
1867            for (i, (start, end)) in std::iter::zip(starts.iter(), ends.iter()).enumerate() {
1868                if i != 0 {
1869                    dest.write_str(", ")?;
1870                }
1871                start.to_css(dest)?;
1872                let can_omit_end = (start.0.name == end.0.name && end.0.lp.equals_percentage(1.0))
1873                    || (start.0.name.is_none() && end.0.name.is_normal());
1874                if !can_omit_end {
1875                    dest.write_char(' ')?;
1876                    end.to_css(dest)?;
1877                }
1878            }
1879            Ok(())
1880        }
1881    }
1882}
1883
1884pub mod transition {
1885    pub use crate::properties::generated::shorthands::transition::*;
1886
1887    use super::*;
1888    use crate::properties::longhands::{
1889        transition_behavior, transition_delay, transition_duration, transition_property,
1890        transition_timing_function,
1891    };
1892    use crate::values::specified::TransitionProperty;
1893
1894    pub fn parse_value(
1895        context: &ParserContext,
1896        input: &mut Parser,
1897    ) -> Result<Longhands, ParseError> {
1898        struct SingleTransition {
1899            transition_property: transition_property::SingleSpecifiedValue,
1900            transition_duration: transition_duration::SingleSpecifiedValue,
1901            transition_timing_function: transition_timing_function::SingleSpecifiedValue,
1902            transition_delay: transition_delay::SingleSpecifiedValue,
1903            transition_behavior: transition_behavior::SingleSpecifiedValue,
1904        }
1905
1906        fn parse_one_transition(
1907            context: &ParserContext,
1908            input: &mut Parser,
1909            first: bool,
1910        ) -> Result<SingleTransition, ParseError> {
1911            let mut property = None;
1912            let mut duration = None;
1913            let mut timing_function = None;
1914            let mut delay = None;
1915            let mut behavior = None;
1916
1917            let mut parsed = 0;
1918            loop {
1919                parsed += 1;
1920
1921                try_parse_one!(
1922                    context,
1923                    input,
1924                    duration,
1925                    transition_duration::single_value::parse
1926                );
1927                try_parse_one!(
1928                    context,
1929                    input,
1930                    timing_function,
1931                    transition_timing_function::single_value::parse
1932                );
1933                try_parse_one!(context, input, delay, transition_delay::single_value::parse);
1934                try_parse_one!(
1935                    context,
1936                    input,
1937                    behavior,
1938                    transition_behavior::single_value::parse
1939                );
1940                if property.is_none() {
1941                    if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
1942                        property = Some(value);
1943                        continue;
1944                    }
1945
1946                    if first && input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
1947                        property = Some(TransitionProperty::none());
1948                        continue;
1949                    }
1950                }
1951
1952                parsed -= 1;
1953                break;
1954            }
1955
1956            if parsed != 0 {
1957                Ok(SingleTransition {
1958                    transition_property: property.unwrap_or_else(
1959                        transition_property::single_value::get_initial_specified_value,
1960                    ),
1961                    transition_duration: duration.unwrap_or_else(
1962                        transition_duration::single_value::get_initial_specified_value,
1963                    ),
1964                    transition_timing_function: timing_function.unwrap_or_else(
1965                        transition_timing_function::single_value::get_initial_specified_value,
1966                    ),
1967                    transition_delay: delay.unwrap_or_else(
1968                        transition_delay::single_value::get_initial_specified_value,
1969                    ),
1970                    transition_behavior: behavior.unwrap_or_else(
1971                        transition_behavior::single_value::get_initial_specified_value,
1972                    ),
1973                })
1974            } else {
1975                Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1976            }
1977        }
1978
1979        let mut first = true;
1980        let mut has_transition_property_none = false;
1981        let results = input.parse_comma_separated(|i| {
1982            if has_transition_property_none {
1983                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1984            }
1985            let transition = parse_one_transition(context, i, first)?;
1986            first = false;
1987            has_transition_property_none = transition.transition_property.is_none();
1988            Ok(transition)
1989        })?;
1990
1991        let len = results.len();
1992        let mut property = Vec::with_capacity(len);
1993        let mut duration = Vec::with_capacity(len);
1994        let mut timing_function = Vec::with_capacity(len);
1995        let mut delay = Vec::with_capacity(len);
1996        let mut behavior = Vec::with_capacity(len);
1997        for result in results {
1998            property.push(result.transition_property);
1999            duration.push(result.transition_duration);
2000            timing_function.push(result.transition_timing_function);
2001            delay.push(result.transition_delay);
2002            behavior.push(result.transition_behavior);
2003        }
2004
2005        Ok(Longhands {
2006            transition_property: transition_property::SpecifiedValue(property.into()),
2007            transition_duration: transition_duration::SpecifiedValue(duration.into()),
2008            transition_timing_function: transition_timing_function::SpecifiedValue(
2009                timing_function.into(),
2010            ),
2011            transition_delay: transition_delay::SpecifiedValue(delay.into()),
2012            transition_behavior: transition_behavior::SpecifiedValue(behavior.into()),
2013        })
2014    }
2015
2016    impl<'a> ToCss for LonghandsToSerialize<'a> {
2017        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2018        where
2019            W: fmt::Write,
2020        {
2021            use crate::Zero;
2022            use style_traits::values::SequenceWriter;
2023
2024            let len = self.transition_property.0.len();
2025            debug_assert_ne!(
2026                len, 0,
2027                "We should always have at least one transition-property, even if none"
2028            );
2029            if self.transition_duration.0.len() != len {
2030                return Ok(());
2031            }
2032            if self.transition_delay.0.len() != len {
2033                return Ok(());
2034            }
2035            if self.transition_timing_function.0.len() != len {
2036                return Ok(());
2037            }
2038            if self.transition_behavior.0.len() != len {
2039                return Ok(());
2040            }
2041            for i in 0..len {
2042                if i != 0 {
2043                    dest.write_str(", ")?;
2044                }
2045
2046                let has_duration = !self.transition_duration.0[i].is_zero();
2047                let has_timing = !self.transition_timing_function.0[i].is_ease();
2048                let has_delay = !self.transition_delay.0[i].is_zero();
2049                let has_behavior = !self.transition_behavior.0[i].is_normal();
2050                let has_any = has_duration || has_timing || has_delay || has_behavior;
2051
2052                let mut writer = SequenceWriter::new(dest, " ");
2053                if !self.transition_property.0[i].is_all() || !has_any {
2054                    writer.item(&self.transition_property.0[i])?;
2055                }
2056                if has_duration || has_delay {
2057                    writer.item(&self.transition_duration.0[i])?;
2058                }
2059                if has_timing {
2060                    writer.item(&self.transition_timing_function.0[i])?;
2061                }
2062                if has_delay {
2063                    writer.item(&self.transition_delay.0[i])?;
2064                }
2065                if has_behavior {
2066                    writer.item(&self.transition_behavior.0[i])?;
2067                }
2068            }
2069            Ok(())
2070        }
2071    }
2072}
2073
2074pub mod outline {
2075    pub use crate::properties::generated::shorthands::outline::*;
2076
2077    use super::*;
2078    use crate::properties::longhands::{outline_color, outline_style, outline_width};
2079
2080    pub fn parse_value(
2081        context: &ParserContext,
2082        input: &mut Parser,
2083    ) -> Result<Longhands, ParseError> {
2084        let _unused = context;
2085        let mut color = None;
2086        let mut style = None;
2087        let mut width = None;
2088        let mut parsed = 0;
2089        loop {
2090            parsed += 1;
2091            try_parse_one!(context, input, color, specified::Color::parse);
2092            try_parse_one!(context, input, style, outline_style::parse);
2093            try_parse_one!(context, input, width, outline_width::parse);
2094            parsed -= 1;
2095            break;
2096        }
2097        if parsed == 0 {
2098            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2099        }
2100        Ok(expanded! {
2101            outline_color: unwrap_or_initial!(outline_color, color),
2102            outline_style: unwrap_or_initial!(outline_style, style),
2103            outline_width: unwrap_or_initial!(outline_width, width),
2104        })
2105    }
2106
2107    impl<'a> ToCss for LonghandsToSerialize<'a> {
2108        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2109        where
2110            W: fmt::Write,
2111        {
2112            let mut writer = SequenceWriter::new(dest, " ");
2113            if *self.outline_color != outline_color::get_initial_specified_value() {
2114                writer.item(self.outline_color)?;
2115            }
2116            if *self.outline_style != outline_style::get_initial_specified_value() {
2117                writer.item(self.outline_style)?;
2118            }
2119            if *self.outline_width != outline_width::get_initial_specified_value() {
2120                writer.item(self.outline_width)?;
2121            }
2122            if !writer.has_written() {
2123                self.outline_style.to_css(dest)?;
2124            }
2125            Ok(())
2126        }
2127    }
2128}
2129
2130pub mod background_position {
2131    pub use crate::properties::generated::shorthands::background_position::*;
2132
2133    use super::*;
2134    use crate::properties::longhands::{background_position_x, background_position_y};
2135    use crate::values::specified::position::Position;
2136    use crate::values::specified::AllowQuirks;
2137
2138    pub fn parse_value(
2139        context: &ParserContext,
2140        input: &mut Parser,
2141    ) -> Result<Longhands, ParseError> {
2142        let mut position_x = Vec::with_capacity(1);
2143        let mut position_y = Vec::with_capacity(1);
2144        let mut any = false;
2145
2146        input.parse_comma_separated(|input| {
2147            let value = Position::parse_three_value_quirky(context, input, AllowQuirks::Yes)?;
2148            position_x.push(value.horizontal);
2149            position_y.push(value.vertical);
2150            any = true;
2151            Ok(())
2152        })?;
2153        if !any {
2154            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2155        }
2156
2157        Ok(expanded! {
2158            background_position_x: background_position_x::SpecifiedValue(position_x.into()),
2159            background_position_y: background_position_y::SpecifiedValue(position_y.into()),
2160        })
2161    }
2162
2163    impl<'a> ToCss for LonghandsToSerialize<'a> {
2164        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2165        where
2166            W: fmt::Write,
2167        {
2168            let len = self.background_position_x.0.len();
2169            if len == 0 || len != self.background_position_y.0.len() {
2170                return Ok(());
2171            }
2172            for i in 0..len {
2173                Position {
2174                    horizontal: self.background_position_x.0[i].clone(),
2175                    vertical: self.background_position_y.0[i].clone(),
2176                }
2177                .to_css(dest)?;
2178
2179                if i < len - 1 {
2180                    dest.write_str(", ")?;
2181                }
2182            }
2183            Ok(())
2184        }
2185    }
2186}
2187
2188pub mod background {
2189    pub use crate::properties::generated::shorthands::background::*;
2190
2191    use super::*;
2192    use crate::properties::longhands::background_clip;
2193    use crate::properties::longhands::background_clip::single_value::computed_value::T as Clip;
2194    use crate::properties::longhands::background_origin::single_value::computed_value::T as Origin;
2195    use crate::properties::longhands::{
2196        background_attachment, background_color, background_image, background_origin,
2197        background_size,
2198    };
2199    use crate::properties::longhands::{
2200        background_position_x, background_position_y, background_repeat,
2201    };
2202    use crate::values::specified::{AllowQuirks, Color, Position, PositionComponent};
2203
2204    impl From<background_origin::single_value::SpecifiedValue>
2205        for background_clip::single_value::SpecifiedValue
2206    {
2207        fn from(
2208            origin: background_origin::single_value::SpecifiedValue,
2209        ) -> background_clip::single_value::SpecifiedValue {
2210            match origin {
2211                background_origin::single_value::SpecifiedValue::ContentBox => {
2212                    background_clip::single_value::SpecifiedValue::ContentBox
2213                },
2214                background_origin::single_value::SpecifiedValue::PaddingBox => {
2215                    background_clip::single_value::SpecifiedValue::PaddingBox
2216                },
2217                background_origin::single_value::SpecifiedValue::BorderBox => {
2218                    background_clip::single_value::SpecifiedValue::BorderBox
2219                },
2220            }
2221        }
2222    }
2223
2224    pub fn parse_value(
2225        context: &ParserContext,
2226        input: &mut Parser,
2227    ) -> Result<Longhands, ParseError> {
2228        let mut background_color = None;
2229
2230        let mut background_image = Vec::with_capacity(1);
2231        let mut background_position_x = Vec::with_capacity(1);
2232        let mut background_position_y = Vec::with_capacity(1);
2233        let mut background_repeat = Vec::with_capacity(1);
2234        let mut background_size = Vec::with_capacity(1);
2235        let mut background_attachment = Vec::with_capacity(1);
2236        let mut background_origin = Vec::with_capacity(1);
2237        let mut background_clip = Vec::with_capacity(1);
2238        input.parse_comma_separated(|input| {
2239            if background_color.is_some() {
2240                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2241            }
2242
2243            let mut image = None;
2244            let mut position = None;
2245            let mut repeat = None;
2246            let mut size = None;
2247            let mut attachment = None;
2248            let mut origin = None;
2249            let mut clip = None;
2250            let mut parsed = 0;
2251            loop {
2252                parsed += 1;
2253                try_parse_one!(context, input, background_color, Color::parse);
2254                if position.is_none() {
2255                    if let Ok(value) = input.try_parse(|input| {
2256                        Position::parse_three_value_quirky(context, input, AllowQuirks::No)
2257                    }) {
2258                        position = Some(value);
2259
2260                        size = input
2261                            .try_parse(|input| {
2262                                input.expect_delim('/')?;
2263                                background_size::single_value::parse(context, input)
2264                            })
2265                            .ok();
2266
2267                        continue;
2268                    }
2269                }
2270                try_parse_one!(context, input, image, background_image::single_value::parse);
2271                try_parse_one!(
2272                    context,
2273                    input,
2274                    repeat,
2275                    background_repeat::single_value::parse
2276                );
2277                try_parse_one!(
2278                    context,
2279                    input,
2280                    attachment,
2281                    background_attachment::single_value::parse
2282                );
2283                try_parse_one!(
2284                    context,
2285                    input,
2286                    origin,
2287                    background_origin::single_value::parse
2288                );
2289                try_parse_one!(context, input, clip, background_clip::single_value::parse);
2290                parsed -= 1;
2291                break;
2292            }
2293            if parsed == 0 {
2294                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2295            }
2296            if clip.is_none() {
2297                if let Some(origin) = origin {
2298                    clip = Some(background_clip::single_value::SpecifiedValue::from(origin));
2299                }
2300            }
2301            if let Some(position) = position {
2302                background_position_x.push(position.horizontal);
2303                background_position_y.push(position.vertical);
2304            } else {
2305                background_position_x.push(PositionComponent::zero());
2306                background_position_y.push(PositionComponent::zero());
2307            }
2308            if let Some(bg_image) = image {
2309                background_image.push(bg_image);
2310            } else {
2311                background_image
2312                    .push(background_image::single_value::get_initial_specified_value());
2313            }
2314            if let Some(bg_repeat) = repeat {
2315                background_repeat.push(bg_repeat);
2316            } else {
2317                background_repeat
2318                    .push(background_repeat::single_value::get_initial_specified_value());
2319            }
2320            if let Some(bg_size) = size {
2321                background_size.push(bg_size);
2322            } else {
2323                background_size.push(background_size::single_value::get_initial_specified_value());
2324            }
2325            if let Some(bg_attachment) = attachment {
2326                background_attachment.push(bg_attachment);
2327            } else {
2328                background_attachment
2329                    .push(background_attachment::single_value::get_initial_specified_value());
2330            }
2331            if let Some(bg_origin) = origin {
2332                background_origin.push(bg_origin);
2333            } else {
2334                background_origin
2335                    .push(background_origin::single_value::get_initial_specified_value());
2336            }
2337            if let Some(bg_clip) = clip {
2338                background_clip.push(bg_clip);
2339            } else {
2340                background_clip.push(background_clip::single_value::get_initial_specified_value());
2341            }
2342            Ok(())
2343        })?;
2344
2345        Ok(expanded! {
2346            background_color: background_color.unwrap_or(Color::transparent()),
2347            background_image: background_image::SpecifiedValue(background_image.into()),
2348            background_position_x: background_position_x::SpecifiedValue(background_position_x.into()),
2349            background_position_y: background_position_y::SpecifiedValue(background_position_y.into()),
2350            background_repeat: background_repeat::SpecifiedValue(background_repeat.into()),
2351            background_size: background_size::SpecifiedValue(background_size.into()),
2352            background_attachment: background_attachment::SpecifiedValue(background_attachment.into()),
2353            background_origin: background_origin::SpecifiedValue(background_origin.into()),
2354            background_clip: background_clip::SpecifiedValue(background_clip.into()),
2355        })
2356    }
2357
2358    impl<'a> ToCss for LonghandsToSerialize<'a> {
2359        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2360        where
2361            W: fmt::Write,
2362        {
2363            let len = self.background_image.0.len();
2364            if len == 0 {
2365                return Ok(());
2366            }
2367            if len != self.background_image.0.len() {
2368                return Ok(());
2369            }
2370            if len != self.background_position_x.0.len() {
2371                return Ok(());
2372            }
2373            if len != self.background_position_y.0.len() {
2374                return Ok(());
2375            }
2376            if len != self.background_size.0.len() {
2377                return Ok(());
2378            }
2379            if len != self.background_repeat.0.len() {
2380                return Ok(());
2381            }
2382            if len != self.background_origin.0.len() {
2383                return Ok(());
2384            }
2385            if len != self.background_clip.0.len() {
2386                return Ok(());
2387            }
2388            if len != self.background_attachment.0.len() {
2389                return Ok(());
2390            }
2391
2392            for i in 0..len {
2393                let image = &self.background_image.0[i];
2394                let position_x = &self.background_position_x.0[i];
2395                let position_y = &self.background_position_y.0[i];
2396                let repeat = &self.background_repeat.0[i];
2397                let size = &self.background_size.0[i];
2398                let attachment = &self.background_attachment.0[i];
2399                let origin = &self.background_origin.0[i];
2400                let clip = &self.background_clip.0[i];
2401
2402                if i != 0 {
2403                    dest.write_str(", ")?;
2404                }
2405
2406                let mut writer = SequenceWriter::new(dest, " ");
2407                if *image != background_image::single_value::get_initial_specified_value() {
2408                    writer.item(image)?;
2409                }
2410
2411                if *position_x != PositionComponent::zero()
2412                    || *position_y != PositionComponent::zero()
2413                    || *size != background_size::single_value::get_initial_specified_value()
2414                {
2415                    writer.write_item(|dest| {
2416                        Position {
2417                            horizontal: position_x.clone(),
2418                            vertical: position_y.clone(),
2419                        }
2420                        .to_css(dest)?;
2421                        if *size != background_size::single_value::get_initial_specified_value() {
2422                            dest.write_str(" / ")?;
2423                            size.to_css(dest)?;
2424                        }
2425                        Ok(())
2426                    })?;
2427                }
2428                if *repeat != background_repeat::single_value::get_initial_specified_value() {
2429                    writer.item(repeat)?;
2430                }
2431                if *attachment != background_attachment::single_value::get_initial_specified_value()
2432                {
2433                    writer.item(attachment)?;
2434                }
2435
2436                if *origin != Origin::PaddingBox || *clip != Clip::BorderBox {
2437                    writer.item(origin)?;
2438                    if *clip != From::from(*origin) {
2439                        writer.item(clip)?;
2440                    }
2441                }
2442
2443                if i == len - 1
2444                    && *self.background_color != background_color::get_initial_specified_value()
2445                {
2446                    writer.item(self.background_color)?;
2447                }
2448
2449                if !writer.has_written() {
2450                    image.to_css(dest)?;
2451                }
2452            }
2453
2454            Ok(())
2455        }
2456    }
2457}
2458
2459pub mod font {
2460    pub use crate::properties::generated::shorthands::font::*;
2461
2462    use super::*;
2463    #[cfg(feature = "gecko")]
2464    use crate::properties::longhands::{
2465        font_family, font_size, font_size_adjust, font_variant_emoji,
2466    };
2467    use crate::properties::longhands::{
2468        font_feature_settings, font_kerning, font_language_override, font_optical_sizing,
2469        font_style, font_variant_alternates, font_variant_caps, font_variant_east_asian,
2470        font_variant_ligatures, font_variant_numeric, font_variant_position,
2471        font_variation_settings, font_weight, font_width,
2472    };
2473    #[cfg(feature = "gecko")]
2474    use crate::values::specified::font::SystemFont;
2475    use crate::values::specified::font::{
2476        FontFamily, FontSize, FontStyle, FontWeight, FontWidth, FontWidthKeyword, LineHeight,
2477    };
2478
2479    pub fn parse_value(
2480        context: &ParserContext,
2481        input: &mut Parser,
2482    ) -> Result<Longhands, ParseError> {
2483        let mut nb_normals = 0;
2484        let mut style = None;
2485        let mut variant_caps = None;
2486        let mut weight = None;
2487        let mut width = None;
2488        #[cfg(feature = "gecko")]
2489        if let Ok(sys) = input.try_parse(|i| SystemFont::parse(context, i)) {
2490            return Ok(Longhands {
2491                font_family: font_family::SpecifiedValue::system_font(sys),
2492                font_size: font_size::SpecifiedValue::system_font(sys),
2493                font_style: font_style::SpecifiedValue::system_font(sys),
2494                font_width: font_width::SpecifiedValue::system_font(sys),
2495                font_weight: font_weight::SpecifiedValue::system_font(sys),
2496                line_height: LineHeight::normal(),
2497                font_kerning: font_kerning::get_initial_specified_value(),
2498                font_language_override: font_language_override::get_initial_specified_value(),
2499                font_size_adjust: font_size_adjust::get_initial_specified_value(),
2500                font_variant_alternates: font_variant_alternates::get_initial_specified_value(),
2501                font_variant_east_asian: font_variant_east_asian::get_initial_specified_value(),
2502                font_variant_emoji: font_variant_emoji::get_initial_specified_value(),
2503                font_variant_ligatures: font_variant_ligatures::get_initial_specified_value(),
2504                font_variant_numeric: font_variant_numeric::get_initial_specified_value(),
2505                font_variant_position: font_variant_position::get_initial_specified_value(),
2506                font_feature_settings: font_feature_settings::get_initial_specified_value(),
2507                font_optical_sizing: font_optical_sizing::get_initial_specified_value(),
2508                font_variant_caps: font_variant_caps::get_initial_specified_value(),
2509                font_variation_settings: font_variation_settings::get_initial_specified_value(),
2510            });
2511        }
2512
2513        let size;
2514        loop {
2515            if input
2516                .try_parse(|input| input.expect_ident_matching("normal"))
2517                .is_ok()
2518            {
2519                nb_normals += 1;
2520                continue;
2521            }
2522            try_parse_one!(context, input, style, font_style::parse);
2523            try_parse_one!(context, input, weight, font_weight::parse);
2524            if variant_caps.is_none()
2525                && input
2526                    .try_parse(|input| input.expect_ident_matching("small-caps"))
2527                    .is_ok()
2528            {
2529                variant_caps = Some(font_variant_caps::SpecifiedValue::SmallCaps);
2530                continue;
2531            }
2532            try_parse_one!(input, width, FontWidthKeyword::parse);
2533            size = FontSize::parse(context, input)?;
2534            break;
2535        }
2536
2537        let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
2538            Some(LineHeight::parse(context, input)?)
2539        } else {
2540            None
2541        };
2542
2543        #[inline]
2544        fn count<T>(opt: &Option<T>) -> u8 {
2545            if opt.is_some() {
2546                1
2547            } else {
2548                0
2549            }
2550        }
2551
2552        if (count(&style) + count(&weight) + count(&variant_caps) + count(&width) + nb_normals) > 4
2553        {
2554            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2555        }
2556
2557        let family = FontFamily::parse(context, input)?;
2558        let width = width.map(FontWidth::Keyword);
2559        Ok(Longhands {
2560            font_style: unwrap_or_initial!(font_style, style),
2561            font_weight: unwrap_or_initial!(font_weight, weight),
2562            font_width: unwrap_or_initial!(font_width, width),
2563            font_variant_caps: unwrap_or_initial!(font_variant_caps, variant_caps),
2564            font_size: size,
2565            line_height: line_height.unwrap_or(LineHeight::normal()),
2566            font_family: family,
2567            font_optical_sizing: font_optical_sizing::get_initial_specified_value(),
2568            font_variation_settings: font_variation_settings::get_initial_specified_value(),
2569            font_kerning: font_kerning::get_initial_specified_value(),
2570            font_language_override: font_language_override::get_initial_specified_value(),
2571            #[cfg(feature = "gecko")]
2572            font_size_adjust: font_size_adjust::get_initial_specified_value(),
2573            font_variant_alternates: font_variant_alternates::get_initial_specified_value(),
2574            font_variant_east_asian: font_variant_east_asian::get_initial_specified_value(),
2575            #[cfg(feature = "gecko")]
2576            font_variant_emoji: font_variant_emoji::get_initial_specified_value(),
2577            font_variant_ligatures: font_variant_ligatures::get_initial_specified_value(),
2578            font_variant_numeric: font_variant_numeric::get_initial_specified_value(),
2579            font_variant_position: font_variant_position::get_initial_specified_value(),
2580            font_feature_settings: font_feature_settings::get_initial_specified_value(),
2581        })
2582    }
2583
2584    #[cfg(feature = "gecko")]
2585    enum CheckSystemResult {
2586        AllSystem(SystemFont),
2587        SomeSystem,
2588        None,
2589    }
2590
2591    impl<'a> ToCss for LonghandsToSerialize<'a> {
2592        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2593        where
2594            W: fmt::Write,
2595        {
2596            #[cfg(feature = "gecko")]
2597            match self.check_system() {
2598                CheckSystemResult::AllSystem(sys) => return sys.to_css(dest),
2599                CheckSystemResult::SomeSystem => return Ok(()),
2600                CheckSystemResult::None => {},
2601            }
2602
2603            #[cfg(feature = "gecko")]
2604            if self.font_optical_sizing != &font_optical_sizing::get_initial_specified_value() {
2605                return Ok(());
2606            }
2607            #[cfg(feature = "servo")]
2608            if let Some(v) = self.font_optical_sizing {
2609                if v != &font_optical_sizing::get_initial_specified_value() {
2610                    return Ok(());
2611                }
2612            }
2613            #[cfg(feature = "gecko")]
2614            if self.font_variation_settings
2615                != &font_variation_settings::get_initial_specified_value()
2616            {
2617                return Ok(());
2618            }
2619            #[cfg(feature = "servo")]
2620            if let Some(v) = self.font_variation_settings {
2621                if v != &font_variation_settings::get_initial_specified_value() {
2622                    return Ok(());
2623                }
2624            }
2625            #[cfg(feature = "gecko")]
2626            if self.font_variant_emoji != &font_variant_emoji::get_initial_specified_value() {
2627                return Ok(());
2628            }
2629
2630            if self.font_kerning != &font_kerning::get_initial_specified_value() {
2631                return Ok(());
2632            }
2633            if self.font_language_override != &font_language_override::get_initial_specified_value()
2634            {
2635                return Ok(());
2636            }
2637            #[cfg(feature = "gecko")]
2638            if self.font_size_adjust != &font_size_adjust::get_initial_specified_value() {
2639                return Ok(());
2640            }
2641            if self.font_variant_alternates
2642                != &font_variant_alternates::get_initial_specified_value()
2643            {
2644                return Ok(());
2645            }
2646            if self.font_variant_east_asian
2647                != &font_variant_east_asian::get_initial_specified_value()
2648            {
2649                return Ok(());
2650            }
2651            if self.font_variant_ligatures != &font_variant_ligatures::get_initial_specified_value()
2652            {
2653                return Ok(());
2654            }
2655            if self.font_variant_numeric != &font_variant_numeric::get_initial_specified_value() {
2656                return Ok(());
2657            }
2658            if self.font_variant_position != &font_variant_position::get_initial_specified_value() {
2659                return Ok(());
2660            }
2661            if self.font_feature_settings != &font_feature_settings::get_initial_specified_value() {
2662                return Ok(());
2663            }
2664
2665            let font_width = match self.font_width {
2666                FontWidth::Keyword(kw) => *kw,
2667                FontWidth::Width(percentage) => {
2668                    let computed = match percentage.compute() {
2669                        Some(v) => v,
2670                        None => return Ok(()),
2671                    };
2672                    match FontWidthKeyword::from_percentage(computed.0) {
2673                        Some(kw) => kw,
2674                        None => return Ok(()),
2675                    }
2676                },
2677                FontWidth::System(..) => return Ok(()),
2678            };
2679
2680            if self.font_variant_caps != &font_variant_caps::get_initial_specified_value()
2681                && *self.font_variant_caps != font_variant_caps::SpecifiedValue::SmallCaps
2682            {
2683                return Ok(());
2684            }
2685
2686            if self.font_style != &font_style::get_initial_specified_value() {
2687                self.font_style.to_css(dest)?;
2688                dest.write_char(' ')?;
2689            }
2690            if self.font_variant_caps != &font_variant_caps::get_initial_specified_value() {
2691                self.font_variant_caps.to_css(dest)?;
2692                dest.write_char(' ')?;
2693            }
2694
2695            if self.font_weight != &FontWeight::normal()
2696                && self.font_weight != &FontWeight::from_gecko_keyword(400)
2697            {
2698                self.font_weight.to_css(dest)?;
2699                dest.write_char(' ')?;
2700            }
2701
2702            if font_width != FontWidthKeyword::Normal {
2703                font_width.to_css(dest)?;
2704                dest.write_char(' ')?;
2705            }
2706
2707            self.font_size.to_css(dest)?;
2708
2709            if *self.line_height != LineHeight::normal() {
2710                dest.write_str(" / ")?;
2711                self.line_height.to_css(dest)?;
2712            }
2713
2714            dest.write_char(' ')?;
2715            self.font_family.to_css(dest)?;
2716
2717            Ok(())
2718        }
2719    }
2720
2721    impl<'a> LonghandsToSerialize<'a> {
2722        #[cfg(feature = "gecko")]
2723        fn check_system(&self) -> CheckSystemResult {
2724            let mut sys = None;
2725            let mut all = true;
2726
2727            macro_rules! check {
2728                ($v:expr) => {
2729                    match $v.get_system() {
2730                        Some(s) => {
2731                            debug_assert!(sys.is_none() || s == sys.unwrap());
2732                            sys = Some(s);
2733                        },
2734                        None => {
2735                            all = false;
2736                        },
2737                    }
2738                };
2739                ($e:expr, $($es:expr),+) => { check!($e); check!($($es),*); };
2740            }
2741
2742            check!(
2743                self.font_family,
2744                self.font_size,
2745                self.font_style,
2746                self.font_width,
2747                self.font_weight
2748            );
2749
2750            if self.line_height != &LineHeight::normal() {
2751                all = false
2752            }
2753            if all {
2754                CheckSystemResult::AllSystem(sys.unwrap())
2755            } else if sys.is_some() {
2756                CheckSystemResult::SomeSystem
2757            } else {
2758                CheckSystemResult::None
2759            }
2760        }
2761    }
2762
2763    impl SpecifiedValueInfo for Longhands {
2764        const SUPPORTED_TYPES: u8 = FontStyle::SUPPORTED_TYPES
2765            | FontWeight::SUPPORTED_TYPES
2766            | FontWidth::SUPPORTED_TYPES
2767            | font_variant_caps::SpecifiedValue::SUPPORTED_TYPES
2768            | FontSize::SUPPORTED_TYPES
2769            | FontFamily::SUPPORTED_TYPES;
2770
2771        fn collect_completion_keywords(f: KeywordsCollectFn) {
2772            FontStyle::collect_completion_keywords(f);
2773            FontWeight::collect_completion_keywords(f);
2774            FontWidth::collect_completion_keywords(f);
2775            font_variant_caps::SpecifiedValue::collect_completion_keywords(f);
2776            FontSize::collect_completion_keywords(f);
2777            FontFamily::collect_completion_keywords(f);
2778
2779            #[cfg(feature = "gecko")]
2780            SystemFont::collect_completion_keywords(f);
2781        }
2782    }
2783}
2784
2785pub mod font_variant {
2786    pub use crate::properties::generated::shorthands::font_variant::*;
2787
2788    use super::*;
2789    #[cfg(feature = "gecko")]
2790    use crate::properties::longhands::font_variant_emoji;
2791    use crate::properties::longhands::{
2792        font_variant_alternates, font_variant_caps, font_variant_east_asian,
2793        font_variant_ligatures, font_variant_numeric, font_variant_position,
2794    };
2795    use crate::values::specified::FontVariantLigatures;
2796
2797    pub fn parse_value(
2798        context: &ParserContext,
2799        input: &mut Parser,
2800    ) -> Result<Longhands, ParseError> {
2801        let mut ligatures = None;
2802        let mut caps = None;
2803        let mut alternates = None;
2804        let mut numeric = None;
2805        let mut east_asian = None;
2806        let mut position = None;
2807        #[cfg(feature = "gecko")]
2808        let mut emoji = None;
2809
2810        if input
2811            .try_parse(|input| input.expect_ident_matching("normal"))
2812            .is_ok()
2813        {
2814        } else if input
2815            .try_parse(|input| input.expect_ident_matching("none"))
2816            .is_ok()
2817        {
2818            ligatures = Some(FontVariantLigatures::NONE);
2819        } else {
2820            let mut parsed = 0;
2821            loop {
2822                parsed += 1;
2823                if input
2824                    .try_parse(|input| input.expect_ident_matching("normal"))
2825                    .is_ok()
2826                    || input
2827                        .try_parse(|input| input.expect_ident_matching("none"))
2828                        .is_ok()
2829                {
2830                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2831                }
2832                try_parse_one!(context, input, ligatures, font_variant_ligatures::parse);
2833                try_parse_one!(context, input, caps, font_variant_caps::parse);
2834                try_parse_one!(context, input, alternates, font_variant_alternates::parse);
2835                try_parse_one!(context, input, numeric, font_variant_numeric::parse);
2836                try_parse_one!(context, input, east_asian, font_variant_east_asian::parse);
2837                try_parse_one!(context, input, position, font_variant_position::parse);
2838                #[cfg(feature = "gecko")]
2839                try_parse_one!(context, input, emoji, font_variant_emoji::parse);
2840                parsed -= 1;
2841                break;
2842            }
2843
2844            if parsed == 0 {
2845                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2846            }
2847        }
2848
2849        #[cfg(feature = "gecko")]
2850        return Ok(expanded! {
2851            font_variant_ligatures: unwrap_or_initial!(font_variant_ligatures, ligatures),
2852            font_variant_caps: unwrap_or_initial!(font_variant_caps, caps),
2853            font_variant_alternates: unwrap_or_initial!(font_variant_alternates, alternates),
2854            font_variant_numeric: unwrap_or_initial!(font_variant_numeric, numeric),
2855            font_variant_east_asian: unwrap_or_initial!(font_variant_east_asian, east_asian),
2856            font_variant_position: unwrap_or_initial!(font_variant_position, position),
2857            font_variant_emoji: unwrap_or_initial!(font_variant_emoji, emoji),
2858        });
2859        #[cfg(feature = "servo")]
2860        return Ok(expanded! {
2861            font_variant_alternates: unwrap_or_initial!(font_variant_alternates, alternates),
2862            font_variant_caps: unwrap_or_initial!(font_variant_caps, caps),
2863            font_variant_east_asian: unwrap_or_initial!(font_variant_east_asian, east_asian),
2864            font_variant_ligatures: unwrap_or_initial!(font_variant_ligatures, ligatures),
2865            font_variant_numeric: unwrap_or_initial!(font_variant_numeric, numeric),
2866            font_variant_position: unwrap_or_initial!(font_variant_position, position),
2867        });
2868    }
2869
2870    impl<'a> ToCss for LonghandsToSerialize<'a> {
2871        #[allow(unused_assignments)]
2872        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
2873        where
2874            W: fmt::Write,
2875        {
2876            let has_none_ligatures = self.font_variant_ligatures == &FontVariantLigatures::NONE;
2877
2878            #[cfg(feature = "gecko")]
2879            const TOTAL_SUBPROPS: usize = 7;
2880            #[cfg(feature = "servo")]
2881            const TOTAL_SUBPROPS: usize = 6;
2882            let mut nb_normals = 0;
2883            macro_rules! count_normal {
2884                ($e: expr, $p: ident) => {
2885                    if *$e == $p::get_initial_specified_value() {
2886                        nb_normals += 1;
2887                    }
2888                };
2889                ($v: ident) => {
2890                    count_normal!(self.$v, $v);
2891                };
2892            }
2893            count_normal!(font_variant_ligatures);
2894            count_normal!(font_variant_caps);
2895            count_normal!(font_variant_alternates);
2896            count_normal!(font_variant_numeric);
2897            count_normal!(font_variant_east_asian);
2898            count_normal!(font_variant_position);
2899            #[cfg(feature = "gecko")]
2900            count_normal!(font_variant_emoji);
2901
2902            if nb_normals == TOTAL_SUBPROPS {
2903                return dest.write_str("normal");
2904            }
2905            if has_none_ligatures {
2906                if nb_normals == TOTAL_SUBPROPS - 1 {
2907                    dest.write_str("none")?;
2908                }
2909                return Ok(());
2910            }
2911
2912            let mut writer = SequenceWriter::new(dest, " ");
2913            macro_rules! write {
2914                ($e: expr, $p: ident) => {
2915                    if *$e != $p::get_initial_specified_value() {
2916                        writer.item($e)?;
2917                    }
2918                };
2919                ($v: ident) => {
2920                    write!(self.$v, $v);
2921                };
2922            }
2923
2924            write!(font_variant_ligatures);
2925            write!(font_variant_caps);
2926            write!(font_variant_alternates);
2927            write!(font_variant_numeric);
2928            write!(font_variant_east_asian);
2929            write!(font_variant_position);
2930            #[cfg(feature = "gecko")]
2931            write!(font_variant_emoji);
2932            Ok(())
2933        }
2934    }
2935}
2936
2937#[cfg(feature = "gecko")]
2938pub mod font_synthesis {
2939    pub use crate::properties::generated::shorthands::font_synthesis::*;
2940
2941    use super::*;
2942    use crate::values::specified::{FontSynthesis, FontSynthesisStyle};
2943
2944    pub fn parse_value(
2945        _context: &ParserContext,
2946        input: &mut Parser,
2947    ) -> Result<Longhands, ParseError> {
2948        let mut weight = FontSynthesis::None;
2949        let mut style = FontSynthesisStyle::None;
2950        let mut small_caps = FontSynthesis::None;
2951        let mut position = FontSynthesis::None;
2952
2953        if input
2954            .try_parse(|input| input.expect_ident_matching("none"))
2955            .is_err()
2956        {
2957            let mut has_custom_value = false;
2958            while !input.is_exhausted() {
2959                try_match_ident_ignore_ascii_case! { input,
2960                    "weight" if weight == FontSynthesis::None => {
2961                        has_custom_value = true;
2962                        weight = FontSynthesis::Auto;
2963                        continue;
2964                    },
2965                    "style" if style == FontSynthesisStyle::None => {
2966                        has_custom_value = true;
2967                        style = FontSynthesisStyle::Auto;
2968                        continue;
2969                    },
2970                    "small-caps" if small_caps == FontSynthesis::None => {
2971                        has_custom_value = true;
2972                        small_caps = FontSynthesis::Auto;
2973                        continue;
2974                    },
2975                    "position" if position == FontSynthesis::None => {
2976                        has_custom_value = true;
2977                        position = FontSynthesis::Auto;
2978                        continue;
2979                    },
2980                    "oblique-only" if style == FontSynthesisStyle::None => {
2981                        has_custom_value = true;
2982                        style = FontSynthesisStyle::ObliqueOnly;
2983                        continue;
2984                    },
2985                }
2986            }
2987            if !has_custom_value {
2988                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
2989            }
2990        }
2991
2992        Ok(expanded! {
2993            font_synthesis_weight: weight,
2994            font_synthesis_style: style,
2995            font_synthesis_small_caps: small_caps,
2996            font_synthesis_position: position,
2997        })
2998    }
2999
3000    impl<'a> ToCss for LonghandsToSerialize<'a> {
3001        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3002        where
3003            W: fmt::Write,
3004        {
3005            let mut writer = SequenceWriter::new(dest, " ");
3006            if self.font_synthesis_weight == &FontSynthesis::Auto {
3007                writer.raw_item("weight")?;
3008            }
3009            if self.font_synthesis_style != &FontSynthesisStyle::None {
3010                if self.font_synthesis_style == &FontSynthesisStyle::Auto {
3011                    writer.raw_item("style")?;
3012                } else {
3013                    writer.raw_item("oblique-only")?;
3014                }
3015            }
3016            if self.font_synthesis_small_caps == &FontSynthesis::Auto {
3017                writer.raw_item("small-caps")?;
3018            }
3019            if self.font_synthesis_position == &FontSynthesis::Auto {
3020                writer.raw_item("position")?;
3021            }
3022            if !writer.has_written() {
3023                writer.raw_item("none")?;
3024            }
3025            Ok(())
3026        }
3027    }
3028
3029    // The shorthand takes the sub-property names of the longhands, and not the
3030    // 'auto' keyword like they do, so we can't automatically derive this.
3031    impl SpecifiedValueInfo for Longhands {
3032        fn collect_completion_keywords(f: KeywordsCollectFn) {
3033            f(&[
3034                "none",
3035                "oblique-only",
3036                "small-caps",
3037                "position",
3038                "style",
3039                "weight",
3040            ]);
3041        }
3042    }
3043}
3044
3045#[cfg(feature = "gecko")]
3046pub mod text_box {
3047    pub use crate::properties::generated::shorthands::text_box::*;
3048
3049    use super::*;
3050    use crate::values::specified::{TextBoxEdge, TextBoxTrim};
3051
3052    pub fn parse_value(
3053        context: &ParserContext,
3054        input: &mut Parser,
3055    ) -> Result<Longhands, ParseError> {
3056        let mut trim = None;
3057        let mut edge = None;
3058
3059        if input
3060            .try_parse(|input| input.expect_ident_matching("normal"))
3061            .is_ok()
3062        {
3063            return Ok(Longhands {
3064                text_box_trim: TextBoxTrim::NONE,
3065                text_box_edge: TextBoxEdge::Auto,
3066            });
3067        }
3068
3069        loop {
3070            try_parse_one!(context, input, trim, TextBoxTrim::parse);
3071            try_parse_one!(context, input, edge, TextBoxEdge::parse);
3072            break;
3073        }
3074
3075        if trim.is_none() && edge.is_none() {
3076            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3077        }
3078
3079        // From https://drafts.csswg.org/css-inline-3/#text-box-shorthand:
3080        // > Omitting the 'text-box-trim' value sets it to 'trim-both'
3081        // > (not the initial value), while omitting the 'text-box-edge'
3082        // > value sets it to auto (the initial value).
3083        Ok(Longhands {
3084            text_box_trim: trim.unwrap_or(TextBoxTrim::TRIM_BOTH),
3085            text_box_edge: edge.unwrap_or(TextBoxEdge::Auto),
3086        })
3087    }
3088
3089    impl<'a> ToCss for LonghandsToSerialize<'a> {
3090        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3091        where
3092            W: fmt::Write,
3093        {
3094            if *self.text_box_trim == TextBoxTrim::NONE && *self.text_box_edge == TextBoxEdge::Auto
3095            {
3096                return dest.write_str("normal");
3097            }
3098
3099            let mut writer = SequenceWriter::new(dest, " ");
3100            if *self.text_box_trim != specified::TextBoxTrim::TRIM_BOTH {
3101                writer.item(self.text_box_trim)?;
3102            }
3103            if *self.text_box_edge != specified::TextBoxEdge::Auto {
3104                writer.item(self.text_box_edge)?;
3105            }
3106            if !writer.has_written() {
3107                self.text_box_trim.to_css(dest)?;
3108            }
3109            Ok(())
3110        }
3111    }
3112}
3113
3114#[cfg(feature = "gecko")]
3115pub mod text_emphasis {
3116    pub use crate::properties::generated::shorthands::text_emphasis::*;
3117
3118    use super::*;
3119    use crate::properties::longhands::{text_emphasis_color, text_emphasis_style};
3120
3121    pub fn parse_value(
3122        context: &ParserContext,
3123        input: &mut Parser,
3124    ) -> Result<Longhands, ParseError> {
3125        let mut color = None;
3126        let mut style = None;
3127        let mut parsed = 0;
3128        loop {
3129            parsed += 1;
3130            try_parse_one!(context, input, color, text_emphasis_color::parse);
3131            try_parse_one!(context, input, style, text_emphasis_style::parse);
3132            parsed -= 1;
3133            break;
3134        }
3135        if parsed == 0 {
3136            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3137        }
3138        Ok(expanded! {
3139            text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color),
3140            text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style),
3141        })
3142    }
3143}
3144
3145pub mod text_decoration {
3146    pub use crate::properties::generated::shorthands::text_decoration::*;
3147
3148    use super::*;
3149    use crate::properties::longhands::text_decoration_thickness;
3150    use crate::properties::longhands::{
3151        text_decoration_color, text_decoration_line, text_decoration_style,
3152    };
3153
3154    pub fn parse_value(
3155        context: &ParserContext,
3156        input: &mut Parser,
3157    ) -> Result<Longhands, ParseError> {
3158        let mut line = None;
3159        let mut style = None;
3160        let mut color = None;
3161        let mut thickness = None;
3162
3163        let mut parsed = 0;
3164        loop {
3165            parsed += 1;
3166            try_parse_one!(context, input, line, text_decoration_line::parse);
3167            try_parse_one!(context, input, style, text_decoration_style::parse);
3168            try_parse_one!(context, input, color, text_decoration_color::parse);
3169            try_parse_one!(context, input, thickness, text_decoration_thickness::parse);
3170            parsed -= 1;
3171            break;
3172        }
3173
3174        if parsed == 0 {
3175            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3176        }
3177
3178        return Ok(expanded! {
3179            text_decoration_line: unwrap_or_initial!(text_decoration_line, line),
3180            text_decoration_style: unwrap_or_initial!(text_decoration_style, style),
3181            text_decoration_color: unwrap_or_initial!(text_decoration_color, color),
3182            text_decoration_thickness: unwrap_or_initial!(text_decoration_thickness, thickness),
3183        });
3184    }
3185
3186    impl<'a> ToCss for LonghandsToSerialize<'a> {
3187        #[allow(unused)]
3188        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3189        where
3190            W: fmt::Write,
3191        {
3192            use crate::values::specified::Color;
3193            use crate::values::specified::TextDecorationLine;
3194
3195            let is_solid_style =
3196                *self.text_decoration_style == text_decoration_style::SpecifiedValue::Solid;
3197            let is_current_color = *self.text_decoration_color == Color::CurrentColor;
3198            let is_auto_thickness = self.text_decoration_thickness.is_auto();
3199            let is_none = *self.text_decoration_line == TextDecorationLine::none();
3200
3201            let mut writer = SequenceWriter::new(dest, " ");
3202            if (is_solid_style && is_current_color && is_auto_thickness) || !is_none {
3203                writer.item(self.text_decoration_line)?;
3204            }
3205            if !is_auto_thickness {
3206                writer.item(self.text_decoration_thickness)?;
3207            }
3208            if !is_solid_style {
3209                writer.item(self.text_decoration_style)?;
3210            }
3211            if !is_current_color {
3212                writer.item(self.text_decoration_color)?;
3213            }
3214            Ok(())
3215        }
3216    }
3217}
3218
3219pub mod animation {
3220    pub use crate::properties::generated::shorthands::animation::*;
3221
3222    use super::*;
3223    use crate::properties::longhands::{
3224        animation_delay, animation_direction, animation_duration, animation_fill_mode,
3225        animation_iteration_count, animation_name, animation_play_state, animation_range_end,
3226        animation_range_start, animation_timeline, animation_timing_function,
3227    };
3228
3229    pub fn parse_value(
3230        context: &ParserContext,
3231        input: &mut Parser,
3232    ) -> Result<Longhands, ParseError> {
3233        struct SingleAnimation {
3234            animation_name: animation_name::SingleSpecifiedValue,
3235            animation_duration: animation_duration::SingleSpecifiedValue,
3236            animation_timing_function: animation_timing_function::SingleSpecifiedValue,
3237            animation_delay: animation_delay::SingleSpecifiedValue,
3238            animation_iteration_count: animation_iteration_count::SingleSpecifiedValue,
3239            animation_direction: animation_direction::SingleSpecifiedValue,
3240            animation_fill_mode: animation_fill_mode::SingleSpecifiedValue,
3241            animation_play_state: animation_play_state::SingleSpecifiedValue,
3242        }
3243
3244        fn parse_one_animation(
3245            context: &ParserContext,
3246            input: &mut Parser,
3247        ) -> Result<SingleAnimation, ParseError> {
3248            let mut name = None;
3249            let mut duration = None;
3250            let mut timing_function = None;
3251            let mut delay = None;
3252            let mut iteration_count = None;
3253            let mut direction = None;
3254            let mut fill_mode = None;
3255            let mut play_state = None;
3256
3257            let mut parsed = 0;
3258            loop {
3259                parsed += 1;
3260                try_parse_one!(
3261                    context,
3262                    input,
3263                    duration,
3264                    animation_duration::single_value::parse
3265                );
3266                try_parse_one!(
3267                    context,
3268                    input,
3269                    timing_function,
3270                    animation_timing_function::single_value::parse
3271                );
3272                try_parse_one!(context, input, delay, animation_delay::single_value::parse);
3273                try_parse_one!(
3274                    context,
3275                    input,
3276                    iteration_count,
3277                    animation_iteration_count::single_value::parse
3278                );
3279                try_parse_one!(
3280                    context,
3281                    input,
3282                    direction,
3283                    animation_direction::single_value::parse
3284                );
3285                try_parse_one!(
3286                    context,
3287                    input,
3288                    fill_mode,
3289                    animation_fill_mode::single_value::parse
3290                );
3291                try_parse_one!(
3292                    context,
3293                    input,
3294                    play_state,
3295                    animation_play_state::single_value::parse
3296                );
3297                try_parse_one!(context, input, name, animation_name::single_value::parse);
3298                parsed -= 1;
3299                break;
3300            }
3301
3302            if parsed == 0 {
3303                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3304            }
3305            Ok(SingleAnimation {
3306                animation_name: name
3307                    .unwrap_or_else(animation_name::single_value::get_initial_specified_value),
3308                animation_duration: duration
3309                    .unwrap_or_else(animation_duration::single_value::get_initial_specified_value),
3310                animation_timing_function: timing_function.unwrap_or_else(
3311                    animation_timing_function::single_value::get_initial_specified_value,
3312                ),
3313                animation_delay: delay
3314                    .unwrap_or_else(animation_delay::single_value::get_initial_specified_value),
3315                animation_iteration_count: iteration_count.unwrap_or_else(
3316                    animation_iteration_count::single_value::get_initial_specified_value,
3317                ),
3318                animation_direction: direction
3319                    .unwrap_or_else(animation_direction::single_value::get_initial_specified_value),
3320                animation_fill_mode: fill_mode
3321                    .unwrap_or_else(animation_fill_mode::single_value::get_initial_specified_value),
3322                animation_play_state: play_state.unwrap_or_else(
3323                    animation_play_state::single_value::get_initial_specified_value,
3324                ),
3325            })
3326        }
3327
3328        let mut names = vec![];
3329        let mut durations = vec![];
3330        let mut timing_functions = vec![];
3331        let mut delays = vec![];
3332        let mut iteration_counts = vec![];
3333        let mut directions = vec![];
3334        let mut fill_modes = vec![];
3335        let mut play_states = vec![];
3336
3337        let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
3338        for result in results.into_iter() {
3339            names.push(result.animation_name);
3340            durations.push(result.animation_duration);
3341            timing_functions.push(result.animation_timing_function);
3342            delays.push(result.animation_delay);
3343            iteration_counts.push(result.animation_iteration_count);
3344            directions.push(result.animation_direction);
3345            fill_modes.push(result.animation_fill_mode);
3346            play_states.push(result.animation_play_state);
3347        }
3348
3349        Ok(expanded! {
3350            animation_name: animation_name::SpecifiedValue(names.into()),
3351            animation_duration: animation_duration::SpecifiedValue(durations.into()),
3352            animation_timing_function: animation_timing_function::SpecifiedValue(timing_functions.into()),
3353            animation_delay: animation_delay::SpecifiedValue(delays.into()),
3354            animation_iteration_count: animation_iteration_count::SpecifiedValue(iteration_counts.into()),
3355            animation_direction: animation_direction::SpecifiedValue(directions.into()),
3356            animation_fill_mode: animation_fill_mode::SpecifiedValue(fill_modes.into()),
3357            animation_play_state: animation_play_state::SpecifiedValue(play_states.into()),
3358            animation_timeline: animation_timeline::SpecifiedValue(
3359                vec![animation_timeline::single_value::get_initial_specified_value()].into()
3360            ),
3361            // The animation-range properties are reset-only sub-properties of the animation
3362            // shorthand.
3363            // https://drafts.csswg.org/scroll-animations-1/#named-range-animation-declaration
3364            animation_range_start: animation_range_start::SpecifiedValue(
3365                vec![animation_range_start::single_value::get_initial_specified_value()].into()
3366            ),
3367            animation_range_end: animation_range_end::SpecifiedValue(
3368                vec![animation_range_end::single_value::get_initial_specified_value()].into()
3369            ),
3370        })
3371    }
3372
3373    impl<'a> ToCss for LonghandsToSerialize<'a> {
3374        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3375        where
3376            W: fmt::Write,
3377        {
3378            use crate::values::specified::easing::TimingFunction;
3379            use crate::values::specified::{
3380                AnimationDirection, AnimationFillMode, AnimationPlayState,
3381            };
3382            use crate::Zero;
3383            use style_traits::values::SequenceWriter;
3384
3385            let len = self.animation_name.0.len();
3386            if len == 0 {
3387                return Ok(());
3388            }
3389
3390            if len != self.animation_duration.0.len() {
3391                return Ok(());
3392            }
3393            if len != self.animation_timing_function.0.len() {
3394                return Ok(());
3395            }
3396            if len != self.animation_delay.0.len() {
3397                return Ok(());
3398            }
3399            if len != self.animation_iteration_count.0.len() {
3400                return Ok(());
3401            }
3402            if len != self.animation_direction.0.len() {
3403                return Ok(());
3404            }
3405            if len != self.animation_fill_mode.0.len() {
3406                return Ok(());
3407            }
3408            if len != self.animation_play_state.0.len() {
3409                return Ok(());
3410            }
3411
3412            // We don't serialize animation-timeline, animation-range-start and animation-range-end
3413            // if any of them are not the initial value.
3414            if self
3415                .animation_timeline
3416                .is_some_and(|v| v.0.len() != 1 || !v.0[0].is_auto())
3417            {
3418                return Ok(());
3419            }
3420            if self
3421                .animation_range_start
3422                .is_some_and(|v| v.0.len() != 1 || !v.0[0].0.is_normal())
3423            {
3424                return Ok(());
3425            }
3426            if self
3427                .animation_range_end
3428                .is_some_and(|v| v.0.len() != 1 || !v.0[0].0.is_normal())
3429            {
3430                return Ok(());
3431            }
3432
3433            for i in 0..len {
3434                if i != 0 {
3435                    dest.write_str(", ")?;
3436                }
3437
3438                let has_duration = !self.animation_duration.0[i].is_auto()
3439                    && !self.animation_duration.0[i].is_zero();
3440                let has_timing_function = !self.animation_timing_function.0[i].is_ease();
3441                let has_delay = !self.animation_delay.0[i].is_zero();
3442                let has_iteration_count = !self.animation_iteration_count.0[i].is_one();
3443                let has_direction =
3444                    !matches!(self.animation_direction.0[i], AnimationDirection::Normal);
3445                let has_fill_mode =
3446                    !matches!(self.animation_fill_mode.0[i], AnimationFillMode::None);
3447                let has_play_state =
3448                    !matches!(self.animation_play_state.0[i], AnimationPlayState::Running);
3449                let animation_name = &self.animation_name.0[i];
3450                let has_name = !animation_name.is_none();
3451
3452                let mut writer = SequenceWriter::new(dest, " ");
3453
3454                if has_duration || has_delay {
3455                    writer.item(&self.animation_duration.0[i])?;
3456                }
3457
3458                if has_timing_function || TimingFunction::match_keywords(animation_name) {
3459                    writer.item(&self.animation_timing_function.0[i])?;
3460                }
3461
3462                if has_delay {
3463                    writer.item(&self.animation_delay.0[i])?;
3464                }
3465                if has_iteration_count {
3466                    writer.item(&self.animation_iteration_count.0[i])?;
3467                }
3468
3469                if has_direction || AnimationDirection::match_keywords(animation_name) {
3470                    writer.item(&self.animation_direction.0[i])?;
3471                }
3472
3473                if has_fill_mode || AnimationFillMode::match_keywords(animation_name) {
3474                    writer.item(&self.animation_fill_mode.0[i])?;
3475                }
3476
3477                if has_play_state || AnimationPlayState::match_keywords(animation_name) {
3478                    writer.item(&self.animation_play_state.0[i])?;
3479                }
3480
3481                if has_name || !writer.has_written() {
3482                    writer.item(animation_name)?;
3483                }
3484            }
3485            Ok(())
3486        }
3487    }
3488}
3489
3490pub mod mask {
3491    pub use crate::properties::generated::shorthands::mask::*;
3492
3493    use super::*;
3494    use crate::parser::Parse;
3495    use crate::properties::longhands::{
3496        mask_clip, mask_composite, mask_mode, mask_origin, mask_position_x, mask_position_y,
3497        mask_repeat,
3498    };
3499    use crate::properties::longhands::{mask_image, mask_size};
3500    use crate::values::specified::{Position, PositionComponent};
3501
3502    impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue {
3503        fn from(
3504            origin: mask_origin::single_value::SpecifiedValue,
3505        ) -> mask_clip::single_value::SpecifiedValue {
3506            match origin {
3507                mask_origin::single_value::SpecifiedValue::ContentBox => {
3508                    mask_clip::single_value::SpecifiedValue::ContentBox
3509                },
3510                mask_origin::single_value::SpecifiedValue::PaddingBox => {
3511                    mask_clip::single_value::SpecifiedValue::PaddingBox
3512                },
3513                mask_origin::single_value::SpecifiedValue::BorderBox => {
3514                    mask_clip::single_value::SpecifiedValue::BorderBox
3515                },
3516                #[cfg(feature = "gecko")]
3517                mask_origin::single_value::SpecifiedValue::FillBox => {
3518                    mask_clip::single_value::SpecifiedValue::FillBox
3519                },
3520                #[cfg(feature = "gecko")]
3521                mask_origin::single_value::SpecifiedValue::StrokeBox => {
3522                    mask_clip::single_value::SpecifiedValue::StrokeBox
3523                },
3524                #[cfg(feature = "gecko")]
3525                mask_origin::single_value::SpecifiedValue::ViewBox => {
3526                    mask_clip::single_value::SpecifiedValue::ViewBox
3527                },
3528            }
3529        }
3530    }
3531
3532    pub fn parse_value(
3533        context: &ParserContext,
3534        input: &mut Parser,
3535    ) -> Result<Longhands, ParseError> {
3536        let mut mask_image = Vec::with_capacity(1);
3537        let mut mask_mode = Vec::with_capacity(1);
3538        let mut mask_position_x = Vec::with_capacity(1);
3539        let mut mask_position_y = Vec::with_capacity(1);
3540        let mut mask_size = Vec::with_capacity(1);
3541        let mut mask_repeat = Vec::with_capacity(1);
3542        let mut mask_origin = Vec::with_capacity(1);
3543        let mut mask_clip = Vec::with_capacity(1);
3544        let mut mask_composite = Vec::with_capacity(1);
3545
3546        input.parse_comma_separated(|input| {
3547            let mut image = None;
3548            let mut mode = None;
3549            let mut position = None;
3550            let mut size = None;
3551            let mut repeat = None;
3552            let mut origin = None;
3553            let mut clip = None;
3554            let mut composite = None;
3555            let mut parsed = 0;
3556            loop {
3557                parsed += 1;
3558
3559                try_parse_one!(context, input, image, mask_image::single_value::parse);
3560                if position.is_none() {
3561                    if let Ok(value) = input.try_parse(|input| Position::parse(context, input)) {
3562                        position = Some(value);
3563                        size = input
3564                            .try_parse(|input| {
3565                                input.expect_delim('/')?;
3566                                mask_size::single_value::parse(context, input)
3567                            })
3568                            .ok();
3569
3570                        continue;
3571                    }
3572                }
3573                try_parse_one!(context, input, repeat, mask_repeat::single_value::parse);
3574                try_parse_one!(context, input, origin, mask_origin::single_value::parse);
3575                try_parse_one!(context, input, clip, mask_clip::single_value::parse);
3576                try_parse_one!(
3577                    context,
3578                    input,
3579                    composite,
3580                    mask_composite::single_value::parse
3581                );
3582                try_parse_one!(context, input, mode, mask_mode::single_value::parse);
3583
3584                parsed -= 1;
3585                break;
3586            }
3587            if clip.is_none() {
3588                if let Some(origin) = origin {
3589                    clip = Some(mask_clip::single_value::SpecifiedValue::from(origin));
3590                }
3591            }
3592            if parsed == 0 {
3593                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3594            }
3595            if let Some(position) = position {
3596                mask_position_x.push(position.horizontal);
3597                mask_position_y.push(position.vertical);
3598            } else {
3599                mask_position_x.push(PositionComponent::zero());
3600                mask_position_y.push(PositionComponent::zero());
3601            }
3602            if let Some(m_image) = image {
3603                mask_image.push(m_image);
3604            } else {
3605                mask_image.push(mask_image::single_value::get_initial_specified_value());
3606            }
3607            if let Some(m_mode) = mode {
3608                mask_mode.push(m_mode);
3609            } else {
3610                mask_mode.push(mask_mode::single_value::get_initial_specified_value());
3611            }
3612            if let Some(m_size) = size {
3613                mask_size.push(m_size);
3614            } else {
3615                mask_size.push(mask_size::single_value::get_initial_specified_value());
3616            }
3617            if let Some(m_repeat) = repeat {
3618                mask_repeat.push(m_repeat);
3619            } else {
3620                mask_repeat.push(mask_repeat::single_value::get_initial_specified_value());
3621            }
3622            if let Some(m_origin) = origin {
3623                mask_origin.push(m_origin);
3624            } else {
3625                mask_origin.push(mask_origin::single_value::get_initial_specified_value());
3626            }
3627            if let Some(m_clip) = clip {
3628                mask_clip.push(m_clip);
3629            } else {
3630                mask_clip.push(mask_clip::single_value::get_initial_specified_value());
3631            }
3632            if let Some(m_composite) = composite {
3633                mask_composite.push(m_composite);
3634            } else {
3635                mask_composite.push(mask_composite::single_value::get_initial_specified_value());
3636            }
3637            Ok(())
3638        })?;
3639
3640        Ok(expanded! {
3641           mask_image: mask_image::SpecifiedValue(mask_image.into()),
3642           mask_mode: mask_mode::SpecifiedValue(mask_mode.into()),
3643           mask_position_x: mask_position_x::SpecifiedValue(mask_position_x.into()),
3644           mask_position_y: mask_position_y::SpecifiedValue(mask_position_y.into()),
3645           mask_size: mask_size::SpecifiedValue(mask_size.into()),
3646           mask_repeat: mask_repeat::SpecifiedValue(mask_repeat.into()),
3647           mask_origin: mask_origin::SpecifiedValue(mask_origin.into()),
3648           mask_clip: mask_clip::SpecifiedValue(mask_clip.into()),
3649           mask_composite: mask_composite::SpecifiedValue(mask_composite.into()),
3650        })
3651    }
3652
3653    impl<'a> ToCss for LonghandsToSerialize<'a> {
3654        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3655        where
3656            W: fmt::Write,
3657        {
3658            use crate::properties::longhands::mask_clip::single_value::computed_value::T as Clip;
3659            use crate::properties::longhands::mask_origin::single_value::computed_value::T as Origin;
3660            use style_traits::values::SequenceWriter;
3661
3662            let len = self.mask_image.0.len();
3663            if len == 0 {
3664                return Ok(());
3665            }
3666            if self.mask_mode.0.len() != len {
3667                return Ok(());
3668            }
3669            if self.mask_position_x.0.len() != len {
3670                return Ok(());
3671            }
3672            if self.mask_position_y.0.len() != len {
3673                return Ok(());
3674            }
3675            if self.mask_size.0.len() != len {
3676                return Ok(());
3677            }
3678            if self.mask_repeat.0.len() != len {
3679                return Ok(());
3680            }
3681            if self.mask_origin.0.len() != len {
3682                return Ok(());
3683            }
3684            if self.mask_clip.0.len() != len {
3685                return Ok(());
3686            }
3687            if self.mask_composite.0.len() != len {
3688                return Ok(());
3689            }
3690
3691            for i in 0..len {
3692                if i > 0 {
3693                    dest.write_str(", ")?;
3694                }
3695
3696                let image = &self.mask_image.0[i];
3697                let mode = &self.mask_mode.0[i];
3698                let position_x = &self.mask_position_x.0[i];
3699                let position_y = &self.mask_position_y.0[i];
3700                let size = &self.mask_size.0[i];
3701                let repeat = &self.mask_repeat.0[i];
3702                let origin = &self.mask_origin.0[i];
3703                let clip = &self.mask_clip.0[i];
3704                let composite = &self.mask_composite.0[i];
3705
3706                let mut has_other = false;
3707                let has_image = *image != mask_image::single_value::get_initial_specified_value();
3708                has_other |= has_image;
3709                let has_mode = *mode != mask_mode::single_value::get_initial_specified_value();
3710                has_other |= has_mode;
3711                let has_size = *size != mask_size::single_value::get_initial_specified_value();
3712                has_other |= has_size;
3713                let has_repeat =
3714                    *repeat != mask_repeat::single_value::get_initial_specified_value();
3715                has_other |= has_repeat;
3716                let has_composite =
3717                    *composite != mask_composite::single_value::get_initial_specified_value();
3718                has_other |= has_composite;
3719                let has_position = *position_x != PositionComponent::zero()
3720                    || *position_y != PositionComponent::zero();
3721                let has_origin = *origin != Origin::BorderBox;
3722                let has_clip = *clip != Clip::BorderBox;
3723
3724                if !has_other && !has_position && !has_origin && !has_clip {
3725                    return image.to_css(dest);
3726                }
3727
3728                let mut writer = SequenceWriter::new(dest, " ");
3729                if has_image {
3730                    writer.item(image)?;
3731                }
3732                if has_position || has_size {
3733                    writer.write_item(|dest| {
3734                        Position {
3735                            horizontal: position_x.clone(),
3736                            vertical: position_y.clone(),
3737                        }
3738                        .to_css(dest)?;
3739                        if has_size {
3740                            dest.write_str(" / ")?;
3741                            size.to_css(dest)?;
3742                        }
3743                        Ok(())
3744                    })?;
3745                }
3746
3747                if has_repeat {
3748                    writer.item(repeat)?;
3749                }
3750
3751                #[cfg(feature = "gecko")]
3752                {
3753                    if has_origin || (has_clip && *clip != Clip::NoClip) {
3754                        writer.item(origin)?;
3755                    }
3756                }
3757
3758                #[cfg(feature = "servo")]
3759                {
3760                    if has_origin || has_clip {
3761                        writer.item(origin)?;
3762                    }
3763                }
3764
3765                if has_clip && *clip != From::from(*origin) {
3766                    writer.item(clip)?;
3767                }
3768
3769                if has_composite {
3770                    writer.item(composite)?;
3771                }
3772
3773                if has_mode {
3774                    writer.item(mode)?;
3775                }
3776            }
3777
3778            Ok(())
3779        }
3780    }
3781}
3782
3783pub mod mask_position {
3784    pub use crate::properties::generated::shorthands::mask_position::*;
3785
3786    use super::*;
3787    use crate::properties::longhands::{mask_position_x, mask_position_y};
3788    use crate::values::specified::Position;
3789
3790    pub fn parse_value(
3791        context: &ParserContext,
3792        input: &mut Parser,
3793    ) -> Result<Longhands, ParseError> {
3794        // Vec grows from 0 to 4 by default on first push().  So allocate with capacity 1, so in
3795        // the common case of only one item we don't way overallocate, then shrink.  Note that we
3796        // always push at least one item if parsing succeeds.
3797        let mut position_x = Vec::with_capacity(1);
3798        let mut position_y = Vec::with_capacity(1);
3799        input.parse_comma_separated(|input| {
3800            let value = Position::parse(context, input)?;
3801            position_x.push(value.horizontal);
3802            position_y.push(value.vertical);
3803            Ok(())
3804        })?;
3805
3806        if position_x.is_empty() {
3807            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3808        }
3809
3810        Ok(expanded! {
3811            mask_position_x: mask_position_x::SpecifiedValue(position_x.into()),
3812            mask_position_y: mask_position_y::SpecifiedValue(position_y.into()),
3813        })
3814    }
3815
3816    impl<'a> ToCss for LonghandsToSerialize<'a> {
3817        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
3818        where
3819            W: fmt::Write,
3820        {
3821            let len = self.mask_position_x.0.len();
3822            if len == 0 || self.mask_position_y.0.len() != len {
3823                return Ok(());
3824            }
3825
3826            for i in 0..len {
3827                Position {
3828                    horizontal: self.mask_position_x.0[i].clone(),
3829                    vertical: self.mask_position_y.0[i].clone(),
3830                }
3831                .to_css(dest)?;
3832
3833                if i < len - 1 {
3834                    dest.write_str(", ")?;
3835                }
3836            }
3837
3838            Ok(())
3839        }
3840    }
3841}
3842
3843pub mod grid_template {
3844    pub use crate::properties::generated::shorthands::grid_template::*;
3845
3846    use super::*;
3847    use crate::parser::Parse;
3848    use crate::values::generics::grid::{concat_serialize_idents, TrackListValue};
3849    use crate::values::generics::grid::{TrackList, TrackSize};
3850    use crate::values::specified::grid::parse_line_names;
3851    use crate::values::specified::position::{
3852        GridTemplateAreas, TemplateAreasArc, TemplateAreasParser,
3853    };
3854    use crate::values::specified::{GenericGridTemplateComponent, GridTemplateComponent};
3855    use servo_arc::Arc;
3856
3857    pub fn parse_grid_template(
3858        context: &ParserContext,
3859        input: &mut Parser,
3860    ) -> Result<
3861        (
3862            GridTemplateComponent,
3863            GridTemplateComponent,
3864            GridTemplateAreas,
3865        ),
3866        ParseError,
3867    > {
3868        if let Ok(x) = input.try_parse(|i| {
3869            if i.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
3870                if !i.is_exhausted() {
3871                    return Err(());
3872                }
3873                return Ok((
3874                    GenericGridTemplateComponent::None,
3875                    GenericGridTemplateComponent::None,
3876                    GridTemplateAreas::None,
3877                ));
3878            }
3879            Err(())
3880        }) {
3881            return Ok(x);
3882        }
3883
3884        let first_line_names = input.try_parse(parse_line_names).unwrap_or_default();
3885        let mut areas_parser = TemplateAreasParser::default();
3886        if areas_parser.try_parse_string(input).is_ok() {
3887            let mut values = vec![];
3888            let mut line_names = vec![];
3889            line_names.push(first_line_names);
3890            loop {
3891                let size = input
3892                    .try_parse(|i| TrackSize::parse(context, i))
3893                    .unwrap_or_default();
3894                values.push(TrackListValue::TrackSize(size));
3895                let mut names = input.try_parse(parse_line_names).unwrap_or_default();
3896                let more_names = input.try_parse(parse_line_names);
3897
3898                match areas_parser.try_parse_string(input) {
3899                    Ok(()) => {
3900                        if let Ok(v) = more_names {
3901                            let mut names_vec = names.into_vec();
3902                            names_vec.extend(v);
3903                            names = names_vec.into();
3904                        }
3905                        line_names.push(names);
3906                    },
3907                    Err(e) => {
3908                        if more_names.is_ok() {
3909                            return Err(e);
3910                        }
3911                        line_names.push(names);
3912                        break;
3913                    },
3914                };
3915            }
3916
3917            if line_names.len() == values.len() {
3918                line_names.push(Default::default());
3919            }
3920
3921            let template_areas = areas_parser
3922                .finish()
3923                .map_err(|()| ParseError::custom(StyleParseErrorKind::UnspecifiedError))?;
3924            let template_rows = TrackList {
3925                values: values.into(),
3926                line_names: line_names.into(),
3927                auto_repeat_index: usize::MAX,
3928            };
3929
3930            let template_cols = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
3931                let value = GridTemplateComponent::parse_without_none(context, input)?;
3932                if let GenericGridTemplateComponent::TrackList(ref list) = value {
3933                    if !list.is_explicit() {
3934                        return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3935                    }
3936                }
3937
3938                value
3939            } else {
3940                GridTemplateComponent::default()
3941            };
3942
3943            Ok((
3944                GenericGridTemplateComponent::TrackList(Box::new(template_rows)),
3945                template_cols,
3946                GridTemplateAreas::Areas(TemplateAreasArc(Arc::new(template_areas))),
3947            ))
3948        } else {
3949            let mut template_rows = GridTemplateComponent::parse(context, input)?;
3950            if let GenericGridTemplateComponent::TrackList(ref mut list) = template_rows {
3951                if list.line_names[0].is_empty() {
3952                    list.line_names[0] = first_line_names;
3953                } else {
3954                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
3955                }
3956            }
3957
3958            input.expect_delim('/')?;
3959            Ok((
3960                template_rows,
3961                GridTemplateComponent::parse(context, input)?,
3962                GridTemplateAreas::None,
3963            ))
3964        }
3965    }
3966
3967    #[inline]
3968    pub fn parse_value(
3969        context: &ParserContext,
3970        input: &mut Parser,
3971    ) -> Result<Longhands, ParseError> {
3972        let (rows, columns, areas) = parse_grid_template(context, input)?;
3973        Ok(expanded! {
3974            grid_template_rows: rows,
3975            grid_template_columns: columns,
3976            grid_template_areas: areas,
3977        })
3978    }
3979
3980    pub fn serialize_grid_template<W>(
3981        template_rows: &GridTemplateComponent,
3982        template_columns: &GridTemplateComponent,
3983        template_areas: &GridTemplateAreas,
3984        dest: &mut CssWriter<W>,
3985    ) -> fmt::Result
3986    where
3987        W: fmt::Write,
3988    {
3989        match *template_areas {
3990            GridTemplateAreas::None => {
3991                if template_rows.is_initial() && template_columns.is_initial() {
3992                    return GridTemplateComponent::default().to_css(dest);
3993                }
3994                template_rows.to_css(dest)?;
3995                dest.write_str(" / ")?;
3996                template_columns.to_css(dest)
3997            },
3998            GridTemplateAreas::Areas(ref areas) => {
3999                if areas.0.strings.len() != template_rows.track_list_len() {
4000                    return Ok(());
4001                }
4002
4003                let track_list = match *template_rows {
4004                    GenericGridTemplateComponent::TrackList(ref list) => {
4005                        if !list.is_explicit() {
4006                            return Ok(());
4007                        }
4008                        list
4009                    },
4010                    _ => return Ok(()),
4011                };
4012
4013                match *template_columns {
4014                    GenericGridTemplateComponent::TrackList(ref list) => {
4015                        if !list.is_explicit() {
4016                            return Ok(());
4017                        }
4018                    },
4019                    GenericGridTemplateComponent::Subgrid(_) => {
4020                        return Ok(());
4021                    },
4022                    _ => {},
4023                }
4024
4025                let mut names_iter = track_list.line_names.iter();
4026                for (((i, string), names), value) in areas
4027                    .0
4028                    .strings
4029                    .iter()
4030                    .enumerate()
4031                    .zip(&mut names_iter)
4032                    .zip(track_list.values.iter())
4033                {
4034                    if i > 0 {
4035                        dest.write_char(' ')?;
4036                    }
4037
4038                    if !names.is_empty() {
4039                        concat_serialize_idents("[", "] ", names, " ", dest)?;
4040                    }
4041
4042                    string.to_css(dest)?;
4043
4044                    if !value.is_initial() {
4045                        dest.write_char(' ')?;
4046                        value.to_css(dest)?;
4047                    }
4048                }
4049
4050                if let Some(names) = names_iter.next() {
4051                    concat_serialize_idents(" [", "]", names, " ", dest)?;
4052                }
4053
4054                if let GenericGridTemplateComponent::TrackList(ref list) = *template_columns {
4055                    dest.write_str(" / ")?;
4056                    list.to_css(dest)?;
4057                }
4058
4059                Ok(())
4060            },
4061        }
4062    }
4063
4064    impl<'a> ToCss for LonghandsToSerialize<'a> {
4065        #[inline]
4066        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
4067        where
4068            W: fmt::Write,
4069        {
4070            serialize_grid_template(
4071                self.grid_template_rows,
4072                self.grid_template_columns,
4073                self.grid_template_areas,
4074                dest,
4075            )
4076        }
4077    }
4078}
4079
4080pub mod grid {
4081    pub use crate::properties::generated::shorthands::grid::*;
4082
4083    use super::*;
4084    use crate::parser::Parse;
4085    use crate::properties::longhands::{grid_auto_columns, grid_auto_flow, grid_auto_rows};
4086    use crate::values::generics::grid::GridTemplateComponent;
4087    use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas};
4088    use crate::values::specified::{GenericGridTemplateComponent, ImplicitGridTracks};
4089
4090    pub fn parse_value(
4091        context: &ParserContext,
4092        input: &mut Parser,
4093    ) -> Result<Longhands, ParseError> {
4094        let mut temp_rows = GridTemplateComponent::default();
4095        let mut temp_cols = GridTemplateComponent::default();
4096        let mut temp_areas = GridTemplateAreas::None;
4097        let mut auto_rows = ImplicitGridTracks::default();
4098        let mut auto_cols = ImplicitGridTracks::default();
4099        let mut flow = grid_auto_flow::get_initial_value();
4100
4101        fn parse_auto_flow(input: &mut Parser, is_row: bool) -> Result<GridAutoFlow, ParseError> {
4102            let mut track = None;
4103            let mut dense = GridAutoFlow::empty();
4104
4105            for _ in 0..2 {
4106                if input
4107                    .try_parse(|i| i.expect_ident_matching("auto-flow"))
4108                    .is_ok()
4109                {
4110                    track = if is_row {
4111                        Some(GridAutoFlow::ROW)
4112                    } else {
4113                        Some(GridAutoFlow::COLUMN)
4114                    };
4115                } else if input
4116                    .try_parse(|i| i.expect_ident_matching("dense"))
4117                    .is_ok()
4118                {
4119                    dense = GridAutoFlow::DENSE
4120                } else {
4121                    break;
4122                }
4123            }
4124
4125            if track.is_some() {
4126                Ok(track.unwrap() | dense)
4127            } else {
4128                Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
4129            }
4130        }
4131
4132        if let Ok((rows, cols, areas)) =
4133            input.try_parse(|i| super::grid_template::parse_grid_template(context, i))
4134        {
4135            temp_rows = rows;
4136            temp_cols = cols;
4137            temp_areas = areas;
4138        } else if let Ok(rows) = input.try_parse(|i| GridTemplateComponent::parse(context, i)) {
4139            temp_rows = rows;
4140            input.expect_delim('/')?;
4141            flow = parse_auto_flow(input, false)?;
4142            auto_cols = input
4143                .try_parse(|i| grid_auto_columns::parse(context, i))
4144                .unwrap_or_default();
4145        } else {
4146            flow = parse_auto_flow(input, true)?;
4147            auto_rows = input
4148                .try_parse(|i| grid_auto_rows::parse(context, i))
4149                .unwrap_or_default();
4150            input.expect_delim('/')?;
4151            temp_cols = GridTemplateComponent::parse(context, input)?;
4152        }
4153
4154        Ok(expanded! {
4155            grid_template_rows: temp_rows,
4156            grid_template_columns: temp_cols,
4157            grid_template_areas: temp_areas,
4158            grid_auto_rows: auto_rows,
4159            grid_auto_columns: auto_cols,
4160            grid_auto_flow: flow,
4161        })
4162    }
4163
4164    impl<'a> LonghandsToSerialize<'a> {
4165        fn is_grid_template(&self) -> bool {
4166            self.grid_auto_rows.is_initial()
4167                && self.grid_auto_columns.is_initial()
4168                && *self.grid_auto_flow == grid_auto_flow::get_initial_value()
4169        }
4170    }
4171
4172    impl<'a> ToCss for LonghandsToSerialize<'a> {
4173        fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
4174        where
4175            W: fmt::Write,
4176        {
4177            if self.is_grid_template() {
4178                return super::grid_template::serialize_grid_template(
4179                    self.grid_template_rows,
4180                    self.grid_template_columns,
4181                    self.grid_template_areas,
4182                    dest,
4183                );
4184            }
4185
4186            if *self.grid_template_areas != GridTemplateAreas::None {
4187                return Ok(());
4188            }
4189
4190            if self.grid_auto_flow.contains(GridAutoFlow::COLUMN) {
4191                if !self.grid_auto_rows.is_initial() || !self.grid_template_columns.is_initial() {
4192                    return Ok(());
4193                }
4194
4195                if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_rows
4196                {
4197                    if !list.is_explicit() {
4198                        return Ok(());
4199                    }
4200                }
4201
4202                self.grid_template_rows.to_css(dest)?;
4203                dest.write_str(" / auto-flow")?;
4204                if self.grid_auto_flow.contains(GridAutoFlow::DENSE) {
4205                    dest.write_str(" dense")?;
4206                }
4207
4208                if !self.grid_auto_columns.is_initial() {
4209                    dest.write_char(' ')?;
4210                    self.grid_auto_columns.to_css(dest)?;
4211                }
4212
4213                return Ok(());
4214            }
4215
4216            if !self.grid_auto_columns.is_initial() || !self.grid_template_rows.is_initial() {
4217                return Ok(());
4218            }
4219
4220            if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_columns {
4221                if !list.is_explicit() {
4222                    return Ok(());
4223                }
4224            }
4225
4226            dest.write_str("auto-flow")?;
4227            if self.grid_auto_flow.contains(GridAutoFlow::DENSE) {
4228                dest.write_str(" dense")?;
4229            }
4230
4231            if !self.grid_auto_rows.is_initial() {
4232                dest.write_char(' ')?;
4233                self.grid_auto_rows.to_css(dest)?;
4234            }
4235
4236            dest.write_str(" / ")?;
4237            self.grid_template_columns.to_css(dest)?;
4238            Ok(())
4239        }
4240    }
4241}