Skip to main content

style/values/specified/
background.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//! Specified types for CSS values related to backgrounds.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{KeywordValue, ToTyped, TypedValue};
10use crate::values::generics::background::BackgroundSize as GenericBackgroundSize;
11use crate::values::specified::length::{
12    NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
13};
14use cssparser::{Parser, match_ignore_ascii_case};
15use selectors::parser::SelectorParseErrorKind;
16use std::fmt::{self, Write};
17use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
18use thin_vec::ThinVec;
19
20/// A specified value for the `background-size` property.
21pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentage>;
22
23impl Parse for BackgroundSize {
24    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
25        if let Ok(width) = input.try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i))
26        {
27            let height = input
28                .try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i))
29                .unwrap_or(NonNegativeLengthPercentageOrAuto::auto());
30            return Ok(GenericBackgroundSize::ExplicitSize { width, height });
31        }
32        Ok(try_match_ident_ignore_ascii_case! { input,
33            "cover" => GenericBackgroundSize::Cover,
34            "contain" => GenericBackgroundSize::Contain,
35        })
36    }
37}
38
39/// One of the keywords for `background-repeat`.
40#[derive(
41    Clone,
42    Copy,
43    Debug,
44    Eq,
45    MallocSizeOf,
46    Parse,
47    PartialEq,
48    SpecifiedValueInfo,
49    ToComputedValue,
50    ToCss,
51    ToResolvedValue,
52    ToShmem,
53    ToTyped,
54)]
55#[allow(missing_docs)]
56#[value_info(other_values = "repeat-x,repeat-y")]
57pub enum BackgroundRepeatKeyword {
58    Repeat,
59    Space,
60    Round,
61    NoRepeat,
62}
63
64/// The value of the `background-repeat` property, with `repeat-x` / `repeat-y`
65/// represented as the combination of `no-repeat` and `repeat` in the opposite
66/// axes.
67///
68/// https://drafts.csswg.org/css-backgrounds/#the-background-repeat
69#[derive(
70    Clone,
71    Debug,
72    MallocSizeOf,
73    PartialEq,
74    SpecifiedValueInfo,
75    ToComputedValue,
76    ToResolvedValue,
77    ToShmem,
78)]
79pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword);
80
81impl BackgroundRepeat {
82    /// Returns the `repeat repeat` value.
83    pub fn repeat() -> Self {
84        BackgroundRepeat(
85            BackgroundRepeatKeyword::Repeat,
86            BackgroundRepeatKeyword::Repeat,
87        )
88    }
89}
90
91impl ToCss for BackgroundRepeat {
92    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
93    where
94        W: Write,
95    {
96        match (self.0, self.1) {
97            (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => {
98                dest.write_str("repeat-x")
99            },
100            (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => {
101                dest.write_str("repeat-y")
102            },
103            (horizontal, vertical) => {
104                horizontal.to_css(dest)?;
105                if horizontal != vertical {
106                    dest.write_char(' ')?;
107                    vertical.to_css(dest)?;
108                }
109                Ok(())
110            },
111        }
112    }
113}
114
115impl ToTyped for BackgroundRepeat {
116    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
117        match (self.0, self.1) {
118            (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => {
119                dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
120                    "repeat-x",
121                ))));
122                Ok(())
123            },
124            (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => {
125                dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
126                    "repeat-y",
127                ))));
128                Ok(())
129            },
130            (horizontal, vertical) if horizontal == vertical => {
131                ToTyped::to_typed(&horizontal, dest)
132            },
133            _ => Err(()),
134        }
135    }
136}
137
138impl Parse for BackgroundRepeat {
139    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
140        let ident = input.expect_ident_cloned()?;
141
142        match_ignore_ascii_case! { &ident,
143            "repeat-x" => {
144                return Ok(BackgroundRepeat(BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat));
145            },
146            "repeat-y" => {
147                return Ok(BackgroundRepeat(BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat));
148            },
149            _ => {},
150        }
151
152        let horizontal = match BackgroundRepeatKeyword::from_ident(&ident) {
153            Ok(h) => h,
154            Err(()) => {
155                return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
156            },
157        };
158
159        let vertical = input.try_parse(BackgroundRepeatKeyword::parse).ok();
160        Ok(BackgroundRepeat(horizontal, vertical.unwrap_or(horizontal)))
161    }
162}
163
164fn background_clip_border_area_enabled(context: &ParserContext) -> bool {
165    context.chrome_rules_enabled() || crate::pref!("layout.css.background-clip.border-area.enabled")
166}
167
168/// The specified value of the `background-clip` and `mask-clip` properties.
169///
170/// This is the union of the keywords both properties accept; each property restricts the set it
171/// actually allows during parsing (see `valid_for_background` / `valid_for_mask`).
172///
173/// https://drafts.csswg.org/css-backgrounds-4/#background-clip
174/// https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
175#[allow(missing_docs)]
176#[derive(
177    Clone,
178    Copy,
179    Debug,
180    Eq,
181    MallocSizeOf,
182    Parse,
183    PartialEq,
184    SpecifiedValueInfo,
185    ToComputedValue,
186    ToCss,
187    ToResolvedValue,
188    ToShmem,
189    ToTyped,
190)]
191#[repr(u8)]
192pub enum BackgroundClip {
193    BorderBox,
194    PaddingBox,
195    ContentBox,
196    // TODO(emilio): We should expose the svg values in SpecifiedValueInfo or so but only for
197    // mask-clip... Maybe we need a newtype thing, or to rejigger the painting code / storage
198    // further.
199    #[cfg(feature = "gecko")]
200    #[value_info(skip)]
201    FillBox,
202    #[cfg(feature = "gecko")]
203    #[value_info(skip)]
204    StrokeBox,
205    #[cfg(feature = "gecko")]
206    #[value_info(skip)]
207    ViewBox,
208    #[cfg(feature = "gecko")]
209    #[value_info(skip)]
210    NoClip,
211    // TODO: text and border-area are supposed to combine in backgrounds-4...
212    #[cfg(feature = "gecko")]
213    Text,
214    #[parse(condition = "background_clip_border_area_enabled")]
215    #[value_info(skip)]
216    BorderArea,
217}
218
219bitflags! {
220    /// Whether a value is valid for background-clip, mask-clip, or both.
221    #[derive(Clone, Copy)]
222    struct ClipValidity: u8 {
223        const BACKGROUND = 1 << 0;
224        const MASK = 1 << 1;
225        const BOTH = Self::BACKGROUND.bits() | Self::MASK.bits();
226    }
227}
228
229impl BackgroundClip {
230    fn validity(&self) -> ClipValidity {
231        match *self {
232            Self::BorderBox => ClipValidity::BOTH,
233            Self::PaddingBox => ClipValidity::BOTH,
234            Self::ContentBox => ClipValidity::BOTH,
235            #[cfg(feature = "gecko")]
236            Self::FillBox => ClipValidity::MASK,
237            #[cfg(feature = "gecko")]
238            Self::StrokeBox => ClipValidity::MASK,
239            #[cfg(feature = "gecko")]
240            Self::ViewBox => ClipValidity::MASK,
241            #[cfg(feature = "gecko")]
242            Self::NoClip => ClipValidity::MASK,
243            #[cfg(feature = "gecko")]
244            Self::Text => ClipValidity::BACKGROUND,
245            Self::BorderArea => ClipValidity::BACKGROUND,
246        }
247    }
248
249    /// Parse the value of the `background-clip` property.
250    pub fn parse_for_background(
251        context: &ParserContext,
252        input: &mut Parser,
253    ) -> Result<Self, ParseError> {
254        let clip = Self::parse(context, input)?;
255        if !clip.validity().intersects(ClipValidity::BACKGROUND) {
256            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
257        }
258        Ok(clip)
259    }
260
261    /// Parse the value of the `mask-clip` property.
262    pub fn parse_for_mask(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
263        let clip = Self::parse(context, input)?;
264        if !clip.validity().intersects(ClipValidity::MASK) {
265            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
266        }
267        Ok(clip)
268    }
269}
270
271/// https://drafts.csswg.org/css-backgrounds/#the-background-origin
272///
273/// Also used by mask-origin, which additionally accepts the SVG boxes.
274#[allow(missing_docs)]
275#[derive(
276    Clone,
277    Copy,
278    Debug,
279    Deserialize,
280    Eq,
281    FromPrimitive,
282    Hash,
283    MallocSizeOf,
284    Parse,
285    PartialEq,
286    Serialize,
287    SpecifiedValueInfo,
288    ToComputedValue,
289    ToCss,
290    ToResolvedValue,
291    ToShmem,
292    ToTyped,
293)]
294#[repr(u8)]
295pub enum BackgroundOrigin {
296    BorderBox,
297    PaddingBox,
298    ContentBox,
299    // TODO(emilio): Same caveat as BackgroundClip regarding SpecifiedValueInfo.
300    #[cfg(feature = "gecko")]
301    #[value_info(skip)]
302    FillBox,
303    #[cfg(feature = "gecko")]
304    #[value_info(skip)]
305    StrokeBox,
306    #[cfg(feature = "gecko")]
307    #[value_info(skip)]
308    ViewBox,
309}
310
311impl BackgroundOrigin {
312    /// Parse the value of the `background-origin` property, which doesn't
313    /// accept the SVG boxes that `mask-origin` does.
314    pub fn parse_for_background(
315        _context: &ParserContext,
316        input: &mut Parser,
317    ) -> Result<Self, ParseError> {
318        let origin = Self::parse(input)?;
319        #[cfg(feature = "gecko")]
320        if matches!(origin, Self::FillBox | Self::StrokeBox | Self::ViewBox) {
321            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
322        }
323        Ok(origin)
324    }
325}
326
327impl From<BackgroundOrigin> for BackgroundClip {
328    fn from(origin: BackgroundOrigin) -> Self {
329        match origin {
330            BackgroundOrigin::BorderBox => Self::BorderBox,
331            BackgroundOrigin::PaddingBox => Self::PaddingBox,
332            BackgroundOrigin::ContentBox => Self::ContentBox,
333            #[cfg(feature = "gecko")]
334            BackgroundOrigin::FillBox => Self::FillBox,
335            #[cfg(feature = "gecko")]
336            BackgroundOrigin::StrokeBox => Self::StrokeBox,
337            #[cfg(feature = "gecko")]
338            BackgroundOrigin::ViewBox => Self::ViewBox,
339        }
340    }
341}
342
343/// https://drafts.csswg.org/css-backgrounds/#the-background-attachment
344#[allow(missing_docs)]
345#[derive(
346    Clone,
347    Copy,
348    Debug,
349    Deserialize,
350    Eq,
351    FromPrimitive,
352    Hash,
353    MallocSizeOf,
354    Parse,
355    PartialEq,
356    Serialize,
357    SpecifiedValueInfo,
358    ToComputedValue,
359    ToCss,
360    ToResolvedValue,
361    ToShmem,
362    ToTyped,
363)]
364#[repr(u8)]
365pub enum ImageLayerAttachment {
366    Scroll,
367    Fixed,
368    #[cfg(feature = "gecko")]
369    Local,
370}