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