style/values/specified/
color.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 color values.
6
7use super::AllowQuirks;
8use crate::color::mix::ColorInterpolationMethod;
9use crate::color::{parsing, AbsoluteColor, ColorFunction, ColorSpace};
10use crate::media_queries::Device;
11use crate::parser::{Parse, ParserContext};
12use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
13use crate::values::generics::color::{
14    ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto, GenericLightDark,
15};
16use crate::values::specified::Percentage;
17use crate::values::{normalize, CustomIdent};
18use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token};
19use std::fmt::{self, Write};
20use std::io::Write as IoWrite;
21use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
22use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
23
24/// A specified color-mix().
25pub type ColorMix = GenericColorMix<Color, Percentage>;
26
27impl ColorMix {
28    fn parse<'i, 't>(
29        context: &ParserContext,
30        input: &mut Parser<'i, 't>,
31        preserve_authored: PreserveAuthored,
32    ) -> Result<Self, ParseError<'i>> {
33        input.expect_function_matching("color-mix")?;
34
35        input.parse_nested_block(|input| {
36            // If the color interpolation method is omitted, default to "in oklab".
37            // See: https://github.com/web-platform-tests/interop/issues/1166
38            let interpolation = input
39                .try_parse(|input| -> Result<_, ParseError<'i>> {
40                    let interpolation = ColorInterpolationMethod::parse(context, input)?;
41                    input.expect_comma()?;
42                    Ok(interpolation)
43                })
44                .unwrap_or_default();
45
46            let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
47                input
48                    .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
49                    .ok()
50            };
51
52            let mut left_percentage = try_parse_percentage(input);
53
54            let left = Color::parse_internal(context, input, preserve_authored)?;
55            if left_percentage.is_none() {
56                left_percentage = try_parse_percentage(input);
57            }
58
59            input.expect_comma()?;
60
61            let mut right_percentage = try_parse_percentage(input);
62
63            let right = Color::parse_internal(context, input, preserve_authored)?;
64
65            if right_percentage.is_none() {
66                right_percentage = try_parse_percentage(input);
67            }
68
69            let right_percentage = right_percentage
70                .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
71
72            let left_percentage =
73                left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
74
75            if left_percentage.get() + right_percentage.get() <= 0.0 {
76                // If the percentages sum to zero, the function is invalid.
77                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
78            }
79
80            // Pass RESULT_IN_MODERN_SYNTAX here, because the result of the color-mix() function
81            // should always be in the modern color syntax to allow for out of gamut results and
82            // to preserve floating point precision.
83            Ok(ColorMix {
84                interpolation,
85                left,
86                left_percentage,
87                right,
88                right_percentage,
89                flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
90            })
91        })
92    }
93}
94
95/// Container holding an absolute color and the text specified by an author.
96#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
97pub struct Absolute {
98    /// The specified color.
99    pub color: AbsoluteColor,
100    /// Authored representation.
101    pub authored: Option<Box<str>>,
102}
103
104impl ToCss for Absolute {
105    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
106    where
107        W: Write,
108    {
109        if let Some(ref authored) = self.authored {
110            dest.write_str(authored)
111        } else {
112            self.color.to_css(dest)
113        }
114    }
115}
116
117/// Specified color value
118#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)]
119pub enum Color {
120    /// The 'currentColor' keyword
121    CurrentColor,
122    /// An absolute color.
123    /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function
124    Absolute(Box<Absolute>),
125    /// A color function that could not be resolved to a [Color::Absolute] color at parse time.
126    /// Right now this is only the case for relative colors with `currentColor` as the origin.
127    ColorFunction(Box<ColorFunction<Self>>),
128    /// A system color.
129    #[cfg(feature = "gecko")]
130    System(SystemColor),
131    /// A color mix.
132    ColorMix(Box<ColorMix>),
133    /// A light-dark() color.
134    LightDark(Box<GenericLightDark<Self>>),
135    /// The contrast-color function.
136    ContrastColor(Box<Color>),
137    /// Quirksmode-only rule for inheriting color from the body
138    #[cfg(feature = "gecko")]
139    InheritFromBodyQuirk,
140}
141
142impl From<AbsoluteColor> for Color {
143    #[inline]
144    fn from(value: AbsoluteColor) -> Self {
145        Self::from_absolute_color(value)
146    }
147}
148
149/// System colors. A bunch of these are ad-hoc, others come from Windows:
150///
151///   https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor
152///
153/// Others are HTML/CSS specific. Spec is:
154///
155///   https://drafts.csswg.org/css-color/#css-system-colors
156///   https://drafts.csswg.org/css-color/#deprecated-system-colors
157#[allow(missing_docs)]
158#[cfg(feature = "gecko")]
159#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
160#[repr(u8)]
161pub enum SystemColor {
162    Activeborder,
163    /// Background in the (active) titlebar.
164    Activecaption,
165    Appworkspace,
166    Background,
167    Buttonface,
168    Buttonhighlight,
169    Buttonshadow,
170    Buttontext,
171    Buttonborder,
172    /// Text color in the (active) titlebar.
173    Captiontext,
174    #[parse(aliases = "-moz-field")]
175    Field,
176    /// Used for disabled field backgrounds.
177    #[parse(condition = "ParserContext::chrome_rules_enabled")]
178    MozDisabledfield,
179    #[parse(aliases = "-moz-fieldtext")]
180    Fieldtext,
181
182    Mark,
183    Marktext,
184
185    /// Combobox widgets
186    MozComboboxtext,
187    MozCombobox,
188
189    Graytext,
190    Highlight,
191    Highlighttext,
192    Inactiveborder,
193    /// Background in the (inactive) titlebar.
194    Inactivecaption,
195    /// Text color in the (inactive) titlebar.
196    Inactivecaptiontext,
197    Infobackground,
198    Infotext,
199    Menu,
200    Menutext,
201    Scrollbar,
202    Threeddarkshadow,
203    Threedface,
204    Threedhighlight,
205    Threedlightshadow,
206    Threedshadow,
207    Window,
208    Windowframe,
209    Windowtext,
210    #[parse(aliases = "-moz-default-color")]
211    Canvastext,
212    #[parse(aliases = "-moz-default-background-color")]
213    Canvas,
214    MozDialog,
215    MozDialogtext,
216    /// Used for selected but not focused cell backgrounds.
217    #[parse(aliases = "-moz-html-cellhighlight")]
218    MozCellhighlight,
219    /// Used for selected but not focused cell text.
220    #[parse(aliases = "-moz-html-cellhighlighttext")]
221    MozCellhighlighttext,
222    /// Used for selected and focused html cell backgrounds.
223    Selecteditem,
224    /// Used for selected and focused html cell text.
225    Selecteditemtext,
226    /// Used for menu item backgrounds when hovered.
227    MozMenuhover,
228    /// Used for menu item backgrounds when hovered and disabled.
229    #[parse(condition = "ParserContext::chrome_rules_enabled")]
230    MozMenuhoverdisabled,
231    /// Used for menu item text when hovered.
232    MozMenuhovertext,
233    /// Used for menubar item text when hovered.
234    MozMenubarhovertext,
235
236    /// On platforms where this color is the same as field, or transparent, use fieldtext as
237    /// foreground color.
238    MozOddtreerow,
239
240    /// Used for button text background when hovered.
241    #[parse(condition = "ParserContext::chrome_rules_enabled")]
242    MozButtonhoverface,
243    /// Used for button text color when hovered.
244    #[parse(condition = "ParserContext::chrome_rules_enabled")]
245    MozButtonhovertext,
246    /// Used for button border color when hovered.
247    #[parse(condition = "ParserContext::chrome_rules_enabled")]
248    MozButtonhoverborder,
249    /// Used for button background when pressed.
250    #[parse(condition = "ParserContext::chrome_rules_enabled")]
251    MozButtonactiveface,
252    /// Used for button text when pressed.
253    #[parse(condition = "ParserContext::chrome_rules_enabled")]
254    MozButtonactivetext,
255    /// Used for button border when pressed.
256    #[parse(condition = "ParserContext::chrome_rules_enabled")]
257    MozButtonactiveborder,
258
259    /// Used for button background when disabled.
260    #[parse(condition = "ParserContext::chrome_rules_enabled")]
261    MozButtondisabledface,
262    /// Used for button border when disabled.
263    #[parse(condition = "ParserContext::chrome_rules_enabled")]
264    MozButtondisabledborder,
265
266    /// Colors used for the header bar (sorta like the tab bar / menubar).
267    #[parse(condition = "ParserContext::chrome_rules_enabled")]
268    MozHeaderbar,
269    #[parse(condition = "ParserContext::chrome_rules_enabled")]
270    MozHeaderbartext,
271    #[parse(condition = "ParserContext::chrome_rules_enabled")]
272    MozHeaderbarinactive,
273    #[parse(condition = "ParserContext::chrome_rules_enabled")]
274    MozHeaderbarinactivetext,
275
276    /// Foreground color of default buttons.
277    #[parse(condition = "ParserContext::chrome_rules_enabled")]
278    MozMacDefaultbuttontext,
279    /// Ring color around text fields and lists.
280    #[parse(condition = "ParserContext::chrome_rules_enabled")]
281    MozMacFocusring,
282    /// Text color of disabled text on toolbars.
283    #[parse(condition = "ParserContext::chrome_rules_enabled")]
284    MozMacDisabledtoolbartext,
285    /// The background of a sidebar.
286    #[parse(condition = "ParserContext::chrome_rules_enabled")]
287    MozSidebar,
288    /// The foreground color of a sidebar.
289    #[parse(condition = "ParserContext::chrome_rules_enabled")]
290    MozSidebartext,
291    /// The border color of a sidebar.
292    #[parse(condition = "ParserContext::chrome_rules_enabled")]
293    MozSidebarborder,
294
295    /// Theme accent color.
296    /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolor
297    Accentcolor,
298
299    /// Foreground for the accent color.
300    /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolortext
301    Accentcolortext,
302
303    /// The background-color for :autofill-ed inputs.
304    #[parse(condition = "ParserContext::chrome_rules_enabled")]
305    MozAutofillBackground,
306
307    #[parse(aliases = "-moz-hyperlinktext")]
308    Linktext,
309    #[parse(aliases = "-moz-activehyperlinktext")]
310    Activetext,
311    #[parse(aliases = "-moz-visitedhyperlinktext")]
312    Visitedtext,
313
314    /// Color of tree column headers
315    #[parse(condition = "ParserContext::chrome_rules_enabled")]
316    MozColheader,
317    #[parse(condition = "ParserContext::chrome_rules_enabled")]
318    MozColheadertext,
319    #[parse(condition = "ParserContext::chrome_rules_enabled")]
320    MozColheaderhover,
321    #[parse(condition = "ParserContext::chrome_rules_enabled")]
322    MozColheaderhovertext,
323    #[parse(condition = "ParserContext::chrome_rules_enabled")]
324    MozColheaderactive,
325    #[parse(condition = "ParserContext::chrome_rules_enabled")]
326    MozColheaderactivetext,
327
328    #[parse(condition = "ParserContext::chrome_rules_enabled")]
329    TextSelectDisabledBackground,
330    #[css(skip)]
331    TextSelectAttentionBackground,
332    #[css(skip)]
333    TextSelectAttentionForeground,
334    #[css(skip)]
335    TextHighlightBackground,
336    #[css(skip)]
337    TextHighlightForeground,
338    #[css(skip)]
339    TargetTextBackground,
340    #[css(skip)]
341    TargetTextForeground,
342    #[css(skip)]
343    IMERawInputBackground,
344    #[css(skip)]
345    IMERawInputForeground,
346    #[css(skip)]
347    IMERawInputUnderline,
348    #[css(skip)]
349    IMESelectedRawTextBackground,
350    #[css(skip)]
351    IMESelectedRawTextForeground,
352    #[css(skip)]
353    IMESelectedRawTextUnderline,
354    #[css(skip)]
355    IMEConvertedTextBackground,
356    #[css(skip)]
357    IMEConvertedTextForeground,
358    #[css(skip)]
359    IMEConvertedTextUnderline,
360    #[css(skip)]
361    IMESelectedConvertedTextBackground,
362    #[css(skip)]
363    IMESelectedConvertedTextForeground,
364    #[css(skip)]
365    IMESelectedConvertedTextUnderline,
366    #[css(skip)]
367    SpellCheckerUnderline,
368    #[css(skip)]
369    ThemedScrollbar,
370    #[css(skip)]
371    ThemedScrollbarThumb,
372    #[css(skip)]
373    ThemedScrollbarThumbHover,
374    #[css(skip)]
375    ThemedScrollbarThumbActive,
376
377    #[css(skip)]
378    End, // Just for array-indexing purposes.
379}
380
381#[cfg(feature = "gecko")]
382impl SystemColor {
383    #[inline]
384    fn compute(&self, cx: &Context) -> ComputedColor {
385        use crate::gecko::values::convert_nscolor_to_absolute_color;
386        use crate::gecko_bindings::bindings;
387
388        let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
389        if cx.for_non_inherited_property {
390            cx.rule_cache_conditions
391                .borrow_mut()
392                .set_color_scheme_dependency(cx.builder.color_scheme);
393        }
394        if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
395            return ComputedColor::currentcolor();
396        }
397        ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
398    }
399}
400
401/// Whether to preserve authored colors during parsing. That's useful only if we
402/// plan to serialize the color back.
403#[derive(Copy, Clone)]
404enum PreserveAuthored {
405    No,
406    Yes,
407}
408
409impl Parse for Color {
410    fn parse<'i, 't>(
411        context: &ParserContext,
412        input: &mut Parser<'i, 't>,
413    ) -> Result<Self, ParseError<'i>> {
414        Self::parse_internal(context, input, PreserveAuthored::Yes)
415    }
416}
417
418impl Color {
419    fn parse_internal<'i, 't>(
420        context: &ParserContext,
421        input: &mut Parser<'i, 't>,
422        preserve_authored: PreserveAuthored,
423    ) -> Result<Self, ParseError<'i>> {
424        let authored = match preserve_authored {
425            PreserveAuthored::No => None,
426            PreserveAuthored::Yes => {
427                // Currently we only store authored value for color keywords,
428                // because all browsers serialize those values as keywords for
429                // specified value.
430                let start = input.state();
431                let authored = input.expect_ident_cloned().ok();
432                input.reset(&start);
433                authored
434            },
435        };
436
437        match input.try_parse(|i| parsing::parse_color_with(context, i)) {
438            Ok(mut color) => {
439                if let Color::Absolute(ref mut absolute) = color {
440                    // Because we can't set the `authored` value at construction time, we have to set it
441                    // here.
442                    absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
443                }
444                Ok(color)
445            },
446            Err(e) => {
447                #[cfg(feature = "gecko")]
448                {
449                    if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
450                        return Ok(Color::System(system));
451                    }
452                }
453
454                if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
455                {
456                    return Ok(Color::ColorMix(Box::new(mix)));
457                }
458
459                if let Ok(ld) = input.try_parse(|i| {
460                    GenericLightDark::parse_with(i, |i| {
461                        Self::parse_internal(context, i, preserve_authored)
462                    })
463                }) {
464                    return Ok(Color::LightDark(Box::new(ld)));
465                }
466
467                if static_prefs::pref!("layout.css.contrast-color.enabled") {
468                    if let Ok(c) = input.try_parse(|i| {
469                        i.expect_function_matching("contrast-color")?;
470                        i.parse_nested_block(|i| {
471                            Self::parse_internal(context, i, preserve_authored)
472                        })
473                    }) {
474                        return Ok(Color::ContrastColor(Box::new(c)));
475                    }
476                }
477
478                match e.kind {
479                    ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
480                        Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
481                            ValueParseErrorKind::InvalidColor(t),
482                        )))
483                    },
484                    _ => Err(e),
485                }
486            },
487        }
488    }
489
490    /// Returns whether a given color is valid for authors.
491    pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
492        input
493            .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
494            .is_ok()
495    }
496
497    /// Tries to parse a color and compute it with a given device.
498    pub fn parse_and_compute(
499        context: &ParserContext,
500        input: &mut Parser,
501        device: Option<&Device>,
502    ) -> Option<ComputedColor> {
503        use crate::error_reporting::ContextualParseError;
504        let start = input.position();
505        let result = input
506            .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
507
508        let specified = match result {
509            Ok(s) => s,
510            Err(e) => {
511                if !context.error_reporting_enabled() {
512                    return None;
513                }
514                // Ignore other kinds of errors that might be reported, such as
515                // ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken),
516                // since Gecko didn't use to report those to the error console.
517                //
518                // TODO(emilio): Revise whether we want to keep this at all, we
519                // use this only for canvas, this warnings are disabled by
520                // default and not available on OffscreenCanvas anyways...
521                if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
522                    let location = e.location.clone();
523                    let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
524                    context.log_css_error(location, error);
525                }
526                return None;
527            },
528        };
529
530        match device {
531            Some(device) => {
532                Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
533                    specified.to_computed_color(Some(&context))
534                })
535            },
536            None => specified.to_computed_color(None),
537        }
538    }
539}
540
541impl ToCss for Color {
542    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
543    where
544        W: Write,
545    {
546        match *self {
547            Color::CurrentColor => dest.write_str("currentcolor"),
548            Color::Absolute(ref absolute) => absolute.to_css(dest),
549            Color::ColorFunction(ref color_function) => color_function.to_css(dest),
550            Color::ColorMix(ref mix) => mix.to_css(dest),
551            Color::LightDark(ref ld) => ld.to_css(dest),
552            Color::ContrastColor(ref c) => {
553                dest.write_str("contrast-color(")?;
554                c.to_css(dest)?;
555                dest.write_char(')')
556            },
557            #[cfg(feature = "gecko")]
558            Color::System(system) => system.to_css(dest),
559            #[cfg(feature = "gecko")]
560            Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
561        }
562    }
563}
564
565impl Color {
566    /// Returns whether this color is allowed in forced-colors mode.
567    pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
568        match *self {
569            #[cfg(feature = "gecko")]
570            Self::InheritFromBodyQuirk => false,
571            Self::CurrentColor => true,
572            #[cfg(feature = "gecko")]
573            Self::System(..) => true,
574            Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
575            Self::ColorFunction(ref color_function) => {
576                // For now we allow transparent colors if we can resolve the color function.
577                // <https://bugzilla.mozilla.org/show_bug.cgi?id=1923053>
578                color_function
579                    .resolve_to_absolute()
580                    .map(|resolved| allow_transparent && resolved.is_transparent())
581                    .unwrap_or(false)
582            },
583            Self::LightDark(ref ld) => {
584                ld.light.honored_in_forced_colors_mode(allow_transparent)
585                    && ld.dark.honored_in_forced_colors_mode(allow_transparent)
586            },
587            Self::ColorMix(ref mix) => {
588                mix.left.honored_in_forced_colors_mode(allow_transparent)
589                    && mix.right.honored_in_forced_colors_mode(allow_transparent)
590            },
591            Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent),
592        }
593    }
594
595    /// Returns currentcolor value.
596    #[inline]
597    pub fn currentcolor() -> Self {
598        Self::CurrentColor
599    }
600
601    /// Returns transparent value.
602    #[inline]
603    pub fn transparent() -> Self {
604        // We should probably set authored to "transparent", but maybe it doesn't matter.
605        Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
606    }
607
608    /// Create a color from an [`AbsoluteColor`].
609    pub fn from_absolute_color(color: AbsoluteColor) -> Self {
610        Color::Absolute(Box::new(Absolute {
611            color,
612            authored: None,
613        }))
614    }
615
616    /// Resolve this Color into an AbsoluteColor if it does not use any of the
617    /// forms that are invalid in an absolute color.
618    ///   https://drafts.csswg.org/css-color-5/#absolute-color
619    /// Returns None if the specified color is not valid as an absolute color.
620    pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
621        use crate::values::specified::percentage::ToPercentage;
622
623        match self {
624            Self::Absolute(c) => Some(c.color),
625            Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
626            Self::ColorMix(ref mix) => {
627                let left = mix.left.resolve_to_absolute()?;
628                let right = mix.right.resolve_to_absolute()?;
629                Some(crate::color::mix::mix(
630                    mix.interpolation,
631                    &left,
632                    mix.left_percentage.to_percentage(),
633                    &right,
634                    mix.right_percentage.to_percentage(),
635                    mix.flags,
636                ))
637            },
638            _ => None,
639        }
640    }
641
642    /// Parse a color, with quirks.
643    ///
644    /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
645    pub fn parse_quirky<'i, 't>(
646        context: &ParserContext,
647        input: &mut Parser<'i, 't>,
648        allow_quirks: AllowQuirks,
649    ) -> Result<Self, ParseError<'i>> {
650        input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
651            if !allow_quirks.allowed(context.quirks_mode) {
652                return Err(e);
653            }
654            Color::parse_quirky_color(input).map_err(|_| e)
655        })
656    }
657
658    fn parse_hash<'i>(
659        bytes: &[u8],
660        loc: &cssparser::SourceLocation,
661    ) -> Result<Self, ParseError<'i>> {
662        match cssparser::color::parse_hash_color(bytes) {
663            Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
664                r, g, b, a,
665            ))),
666            Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
667        }
668    }
669
670    /// Parse a <quirky-color> value.
671    ///
672    /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
673    fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
674        let location = input.current_source_location();
675        let (value, unit) = match *input.next()? {
676            Token::Number {
677                int_value: Some(integer),
678                ..
679            } => (integer, None),
680            Token::Dimension {
681                int_value: Some(integer),
682                ref unit,
683                ..
684            } => (integer, Some(unit)),
685            Token::Ident(ref ident) => {
686                if ident.len() != 3 && ident.len() != 6 {
687                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
688                }
689                return Self::parse_hash(ident.as_bytes(), &location);
690            },
691            ref t => {
692                return Err(location.new_unexpected_token_error(t.clone()));
693            },
694        };
695        if value < 0 {
696            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
697        }
698        let length = if value <= 9 {
699            1
700        } else if value <= 99 {
701            2
702        } else if value <= 999 {
703            3
704        } else if value <= 9999 {
705            4
706        } else if value <= 99999 {
707            5
708        } else if value <= 999999 {
709            6
710        } else {
711            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
712        };
713        let total = length + unit.as_ref().map_or(0, |d| d.len());
714        if total > 6 {
715            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
716        }
717        let mut serialization = [b'0'; 6];
718        let space_padding = 6 - total;
719        let mut written = space_padding;
720        let mut buf = itoa::Buffer::new();
721        let s = buf.format(value);
722        (&mut serialization[written..])
723            .write_all(s.as_bytes())
724            .unwrap();
725        written += s.len();
726        if let Some(unit) = unit {
727            written += (&mut serialization[written..])
728                .write(unit.as_bytes())
729                .unwrap();
730        }
731        debug_assert_eq!(written, 6);
732        Self::parse_hash(&serialization, &location)
733    }
734}
735
736impl Color {
737    /// Converts this Color into a ComputedColor.
738    ///
739    /// If `context` is `None`, and the specified color requires data from
740    /// the context to resolve, then `None` is returned.
741    pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
742        macro_rules! adjust_absolute_color {
743            ($color:expr) => {{
744                // Computed lightness values can not be NaN.
745                if matches!(
746                    $color.color_space,
747                    ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
748                ) {
749                    $color.components.0 = normalize($color.components.0);
750                }
751
752                // Computed RGB and XYZ components can not be NaN.
753                if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
754                    $color.components = $color.components.map(normalize);
755                }
756
757                $color.alpha = normalize($color.alpha);
758            }};
759        }
760
761        Some(match *self {
762            Color::CurrentColor => ComputedColor::CurrentColor,
763            Color::Absolute(ref absolute) => {
764                let mut color = absolute.color;
765                adjust_absolute_color!(color);
766                ComputedColor::Absolute(color)
767            },
768            Color::ColorFunction(ref color_function) => {
769                debug_assert!(color_function.has_origin_color(),
770                    "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
771
772                // Try to eagerly resolve the color function before making it a computed color.
773                if let Ok(absolute) = color_function.resolve_to_absolute() {
774                    ComputedColor::Absolute(absolute)
775                } else {
776                    let color_function = color_function
777                        .map_origin_color(|origin_color| origin_color.to_computed_color(context));
778                    ComputedColor::ColorFunction(Box::new(color_function))
779                }
780            },
781            Color::LightDark(ref ld) => ld.compute(context?),
782            Color::ColorMix(ref mix) => {
783                use crate::values::computed::percentage::Percentage;
784
785                let left = mix.left.to_computed_color(context)?;
786                let right = mix.right.to_computed_color(context)?;
787
788                ComputedColor::from_color_mix(GenericColorMix {
789                    interpolation: mix.interpolation,
790                    left,
791                    left_percentage: Percentage(mix.left_percentage.get()),
792                    right,
793                    right_percentage: Percentage(mix.right_percentage.get()),
794                    flags: mix.flags,
795                })
796            },
797            Color::ContrastColor(ref c) => {
798                ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
799            },
800            #[cfg(feature = "gecko")]
801            Color::System(system) => system.compute(context?),
802            #[cfg(feature = "gecko")]
803            Color::InheritFromBodyQuirk => {
804                ComputedColor::Absolute(context?.device().body_text_color())
805            },
806        })
807    }
808}
809
810impl ToComputedValue for Color {
811    type ComputedValue = ComputedColor;
812
813    fn to_computed_value(&self, context: &Context) -> ComputedColor {
814        self.to_computed_color(Some(context)).unwrap_or_else(|| {
815            debug_assert!(
816                false,
817                "Specified color could not be resolved to a computed color!"
818            );
819            ComputedColor::Absolute(AbsoluteColor::BLACK)
820        })
821    }
822
823    fn from_computed_value(computed: &ComputedColor) -> Self {
824        match *computed {
825            ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
826            ComputedColor::ColorFunction(ref color_function) => {
827                let color_function =
828                    color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
829                Self::ColorFunction(Box::new(color_function))
830            },
831            ComputedColor::CurrentColor => Color::CurrentColor,
832            ComputedColor::ColorMix(ref mix) => {
833                Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
834            },
835            ComputedColor::ContrastColor(ref c) => {
836                Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
837            },
838        }
839    }
840}
841
842impl SpecifiedValueInfo for Color {
843    const SUPPORTED_TYPES: u8 = CssType::COLOR;
844
845    fn collect_completion_keywords(f: KeywordsCollectFn) {
846        // We are not going to insert all the color names here. Caller and
847        // devtools should take care of them. XXX Actually, transparent
848        // should probably be handled that way as well.
849        // XXX `currentColor` should really be `currentcolor`. But let's
850        // keep it consistent with the old system for now.
851        f(&[
852            "currentColor",
853            "transparent",
854            "rgb",
855            "rgba",
856            "hsl",
857            "hsla",
858            "hwb",
859            "color",
860            "lab",
861            "lch",
862            "oklab",
863            "oklch",
864            "color-mix",
865            "contrast-color",
866            "light-dark",
867        ]);
868    }
869}
870
871/// Specified value for the "color" property, which resolves the `currentcolor`
872/// keyword to the parent color instead of self's color.
873#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
874#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
875pub struct ColorPropertyValue(pub Color);
876
877impl ToComputedValue for ColorPropertyValue {
878    type ComputedValue = AbsoluteColor;
879
880    #[inline]
881    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
882        let current_color = context.builder.get_parent_inherited_text().clone_color();
883        self.0
884            .to_computed_value(context)
885            .resolve_to_absolute(&current_color)
886    }
887
888    #[inline]
889    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
890        ColorPropertyValue(Color::from_absolute_color(*computed).into())
891    }
892}
893
894impl Parse for ColorPropertyValue {
895    fn parse<'i, 't>(
896        context: &ParserContext,
897        input: &mut Parser<'i, 't>,
898    ) -> Result<Self, ParseError<'i>> {
899        Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
900    }
901}
902
903/// auto | <color>
904pub type ColorOrAuto = GenericColorOrAuto<Color>;
905
906/// caret-color
907pub type CaretColor = GenericCaretColor<Color>;
908
909impl Parse for CaretColor {
910    fn parse<'i, 't>(
911        context: &ParserContext,
912        input: &mut Parser<'i, 't>,
913    ) -> Result<Self, ParseError<'i>> {
914        ColorOrAuto::parse(context, input).map(GenericCaretColor)
915    }
916}
917
918/// Various flags to represent the color-scheme property in an efficient
919/// way.
920#[derive(
921    Clone,
922    Copy,
923    Debug,
924    Default,
925    Eq,
926    MallocSizeOf,
927    PartialEq,
928    SpecifiedValueInfo,
929    ToComputedValue,
930    ToResolvedValue,
931    ToShmem,
932)]
933#[repr(C)]
934#[value_info(other_values = "light,dark,only")]
935pub struct ColorSchemeFlags(u8);
936bitflags! {
937    impl ColorSchemeFlags: u8 {
938        /// Whether the author specified `light`.
939        const LIGHT = 1 << 0;
940        /// Whether the author specified `dark`.
941        const DARK = 1 << 1;
942        /// Whether the author specified `only`.
943        const ONLY = 1 << 2;
944    }
945}
946
947/// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
948#[derive(
949    Clone,
950    Debug,
951    Default,
952    MallocSizeOf,
953    PartialEq,
954    SpecifiedValueInfo,
955    ToComputedValue,
956    ToResolvedValue,
957    ToShmem,
958    ToTyped,
959)]
960#[repr(C)]
961#[value_info(other_values = "normal")]
962pub struct ColorScheme {
963    #[ignore_malloc_size_of = "Arc"]
964    idents: crate::ArcSlice<CustomIdent>,
965    /// The computed bits for the known color schemes (plus the only keyword).
966    pub bits: ColorSchemeFlags,
967}
968
969impl ColorScheme {
970    /// Returns the `normal` value.
971    pub fn normal() -> Self {
972        Self {
973            idents: Default::default(),
974            bits: ColorSchemeFlags::empty(),
975        }
976    }
977
978    /// Returns the raw bitfield.
979    pub fn raw_bits(&self) -> u8 {
980        self.bits.bits()
981    }
982}
983
984impl Parse for ColorScheme {
985    fn parse<'i, 't>(
986        _: &ParserContext,
987        input: &mut Parser<'i, 't>,
988    ) -> Result<Self, ParseError<'i>> {
989        let mut idents = vec![];
990        let mut bits = ColorSchemeFlags::empty();
991
992        let mut location = input.current_source_location();
993        while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
994            let mut is_only = false;
995            match_ignore_ascii_case! { &ident,
996                "normal" => {
997                    if idents.is_empty() && bits.is_empty() {
998                        return Ok(Self::normal());
999                    }
1000                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1001                },
1002                "light" => bits.insert(ColorSchemeFlags::LIGHT),
1003                "dark" => bits.insert(ColorSchemeFlags::DARK),
1004                "only" => {
1005                    if bits.intersects(ColorSchemeFlags::ONLY) {
1006                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1007                    }
1008                    bits.insert(ColorSchemeFlags::ONLY);
1009                    is_only = true;
1010                },
1011                _ => {},
1012            };
1013
1014            if is_only {
1015                if !idents.is_empty() {
1016                    // Only is allowed either at the beginning or at the end,
1017                    // but not in the middle.
1018                    break;
1019                }
1020            } else {
1021                idents.push(CustomIdent::from_ident(location, &ident, &[])?);
1022            }
1023            location = input.current_source_location();
1024        }
1025
1026        if idents.is_empty() {
1027            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1028        }
1029
1030        Ok(Self {
1031            idents: crate::ArcSlice::from_iter(idents.into_iter()),
1032            bits,
1033        })
1034    }
1035}
1036
1037impl ToCss for ColorScheme {
1038    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1039    where
1040        W: Write,
1041    {
1042        if self.idents.is_empty() {
1043            debug_assert!(self.bits.is_empty());
1044            return dest.write_str("normal");
1045        }
1046        let mut first = true;
1047        for ident in self.idents.iter() {
1048            if !first {
1049                dest.write_char(' ')?;
1050            }
1051            first = false;
1052            ident.to_css(dest)?;
1053        }
1054        if self.bits.intersects(ColorSchemeFlags::ONLY) {
1055            dest.write_str(" only")?;
1056        }
1057        Ok(())
1058    }
1059}
1060
1061/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
1062#[derive(
1063    Clone,
1064    Copy,
1065    Debug,
1066    MallocSizeOf,
1067    Parse,
1068    PartialEq,
1069    SpecifiedValueInfo,
1070    ToCss,
1071    ToComputedValue,
1072    ToResolvedValue,
1073    ToShmem,
1074    ToTyped,
1075)]
1076#[repr(u8)]
1077pub enum PrintColorAdjust {
1078    /// Ignore backgrounds and darken text.
1079    Economy,
1080    /// Respect specified colors.
1081    Exact,
1082}
1083
1084/// https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop
1085#[derive(
1086    Clone,
1087    Copy,
1088    Debug,
1089    MallocSizeOf,
1090    Parse,
1091    PartialEq,
1092    SpecifiedValueInfo,
1093    ToCss,
1094    ToComputedValue,
1095    ToResolvedValue,
1096    ToShmem,
1097    ToTyped,
1098)]
1099#[repr(u8)]
1100pub enum ForcedColorAdjust {
1101    /// Adjust colors if needed.
1102    Auto,
1103    /// Respect specified colors.
1104    None,
1105}
1106
1107/// Possible values for the forced-colors media query.
1108/// <https://drafts.csswg.org/mediaqueries-5/#forced-colors>
1109#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1110#[repr(u8)]
1111pub enum ForcedColors {
1112    /// Page colors are not being forced.
1113    None,
1114    /// Page colors would be forced in content.
1115    #[parse(condition = "ParserContext::chrome_rules_enabled")]
1116    Requested,
1117    /// Page colors are being forced.
1118    Active,
1119}
1120
1121impl ForcedColors {
1122    /// Returns whether forced-colors is active for this page.
1123    pub fn is_active(self) -> bool {
1124        matches!(self, Self::Active)
1125    }
1126}