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