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
168fn background_clip_text_enabled(_: &ParserContext) -> bool {
169    crate::pref!("layout.css.background-clip.text.enabled", gecko = true)
170}
171
172/// The specified value of the `background-clip` and `mask-clip` properties.
173///
174/// This is the union of the keywords both properties accept; each property restricts the set it
175/// actually allows during parsing (see `valid_for_background` / `valid_for_mask`).
176///
177/// https://drafts.csswg.org/css-backgrounds-4/#background-clip
178/// https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
179#[allow(missing_docs)]
180#[derive(
181    Clone,
182    Copy,
183    Debug,
184    Eq,
185    MallocSizeOf,
186    Parse,
187    PartialEq,
188    SpecifiedValueInfo,
189    ToComputedValue,
190    ToCss,
191    ToResolvedValue,
192    ToShmem,
193    ToTyped,
194)]
195#[repr(u8)]
196pub enum BackgroundClip {
197    BorderBox,
198    PaddingBox,
199    ContentBox,
200    // TODO(emilio): We should expose the svg values in SpecifiedValueInfo or so but only for
201    // mask-clip... Maybe we need a newtype thing, or to rejigger the painting code / storage
202    // further.
203    #[cfg(feature = "gecko")]
204    #[value_info(skip)]
205    FillBox,
206    #[cfg(feature = "gecko")]
207    #[value_info(skip)]
208    StrokeBox,
209    #[cfg(feature = "gecko")]
210    #[value_info(skip)]
211    ViewBox,
212    #[cfg(feature = "gecko")]
213    #[value_info(skip)]
214    NoClip,
215    // TODO: text and border-area are supposed to combine in backgrounds-4...
216    #[parse(condition = "background_clip_text_enabled")]
217    #[cfg_attr(feature = "servo", value_info(skip))]
218    Text,
219    #[parse(condition = "background_clip_border_area_enabled")]
220    #[value_info(skip)]
221    BorderArea,
222}
223
224bitflags! {
225    /// Whether a value is valid for background-clip, mask-clip, or both.
226    #[derive(Clone, Copy)]
227    struct ClipValidity: u8 {
228        const BACKGROUND = 1 << 0;
229        const MASK = 1 << 1;
230        const BOTH = Self::BACKGROUND.bits() | Self::MASK.bits();
231    }
232}
233
234impl BackgroundClip {
235    fn validity(&self) -> ClipValidity {
236        match *self {
237            Self::BorderBox => ClipValidity::BOTH,
238            Self::PaddingBox => ClipValidity::BOTH,
239            Self::ContentBox => ClipValidity::BOTH,
240            #[cfg(feature = "gecko")]
241            Self::FillBox => ClipValidity::MASK,
242            #[cfg(feature = "gecko")]
243            Self::StrokeBox => ClipValidity::MASK,
244            #[cfg(feature = "gecko")]
245            Self::ViewBox => ClipValidity::MASK,
246            #[cfg(feature = "gecko")]
247            Self::NoClip => ClipValidity::MASK,
248            Self::Text => ClipValidity::BACKGROUND,
249            Self::BorderArea => ClipValidity::BACKGROUND,
250        }
251    }
252
253    /// Parse the value of the `background-clip` property.
254    pub fn parse_for_background(
255        context: &ParserContext,
256        input: &mut Parser,
257    ) -> Result<Self, ParseError> {
258        let clip = Self::parse(context, input)?;
259        if !clip.validity().intersects(ClipValidity::BACKGROUND) {
260            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
261        }
262        Ok(clip)
263    }
264
265    /// Parse the value of the `mask-clip` property.
266    pub fn parse_for_mask(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
267        let clip = Self::parse(context, input)?;
268        if !clip.validity().intersects(ClipValidity::MASK) {
269            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
270        }
271        Ok(clip)
272    }
273}
274
275/// https://drafts.csswg.org/css-backgrounds/#the-background-origin
276///
277/// Also used by mask-origin, which additionally accepts the SVG boxes.
278#[allow(missing_docs)]
279#[derive(
280    Clone,
281    Copy,
282    Debug,
283    Deserialize,
284    Eq,
285    FromPrimitive,
286    Hash,
287    MallocSizeOf,
288    Parse,
289    PartialEq,
290    Serialize,
291    SpecifiedValueInfo,
292    ToComputedValue,
293    ToCss,
294    ToResolvedValue,
295    ToShmem,
296    ToTyped,
297)]
298#[repr(u8)]
299pub enum BackgroundOrigin {
300    BorderBox,
301    PaddingBox,
302    ContentBox,
303    // TODO(emilio): Same caveat as BackgroundClip regarding SpecifiedValueInfo.
304    #[cfg(feature = "gecko")]
305    #[value_info(skip)]
306    FillBox,
307    #[cfg(feature = "gecko")]
308    #[value_info(skip)]
309    StrokeBox,
310    #[cfg(feature = "gecko")]
311    #[value_info(skip)]
312    ViewBox,
313}
314
315impl BackgroundOrigin {
316    /// Parse the value of the `background-origin` property, which doesn't
317    /// accept the SVG boxes that `mask-origin` does.
318    pub fn parse_for_background(
319        _context: &ParserContext,
320        input: &mut Parser,
321    ) -> Result<Self, ParseError> {
322        let origin = Self::parse(input)?;
323        #[cfg(feature = "gecko")]
324        if matches!(origin, Self::FillBox | Self::StrokeBox | Self::ViewBox) {
325            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
326        }
327        Ok(origin)
328    }
329}
330
331impl From<BackgroundOrigin> for BackgroundClip {
332    fn from(origin: BackgroundOrigin) -> Self {
333        match origin {
334            BackgroundOrigin::BorderBox => Self::BorderBox,
335            BackgroundOrigin::PaddingBox => Self::PaddingBox,
336            BackgroundOrigin::ContentBox => Self::ContentBox,
337            #[cfg(feature = "gecko")]
338            BackgroundOrigin::FillBox => Self::FillBox,
339            #[cfg(feature = "gecko")]
340            BackgroundOrigin::StrokeBox => Self::StrokeBox,
341            #[cfg(feature = "gecko")]
342            BackgroundOrigin::ViewBox => Self::ViewBox,
343        }
344    }
345}
346
347/// https://drafts.csswg.org/css-backgrounds/#the-background-attachment
348#[allow(missing_docs)]
349#[derive(
350    Clone,
351    Copy,
352    Debug,
353    Deserialize,
354    Eq,
355    FromPrimitive,
356    Hash,
357    MallocSizeOf,
358    Parse,
359    PartialEq,
360    Serialize,
361    SpecifiedValueInfo,
362    ToComputedValue,
363    ToCss,
364    ToResolvedValue,
365    ToShmem,
366    ToTyped,
367)]
368#[repr(u8)]
369pub enum ImageLayerAttachment {
370    Scroll,
371    Fixed,
372    #[cfg(feature = "gecko")]
373    Local,
374}