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    ThemedScrollbarInactive,
363    #[css(skip)]
364    ThemedScrollbarThumb,
365    #[css(skip)]
366    ThemedScrollbarThumbHover,
367    #[css(skip)]
368    ThemedScrollbarThumbActive,
369    #[css(skip)]
370    ThemedScrollbarThumbInactive,
371
372    #[css(skip)]
373    End, // Just for array-indexing purposes.
374}
375
376#[cfg(feature = "gecko")]
377impl SystemColor {
378    #[inline]
379    fn compute(&self, cx: &Context) -> ComputedColor {
380        use crate::gecko::values::convert_nscolor_to_absolute_color;
381        use crate::gecko_bindings::bindings;
382
383        let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
384        if cx.for_non_inherited_property {
385            cx.rule_cache_conditions
386                .borrow_mut()
387                .set_color_scheme_dependency(cx.builder.color_scheme);
388        }
389        if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
390            return ComputedColor::currentcolor();
391        }
392        ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
393    }
394}
395
396/// Whether to preserve authored colors during parsing. That's useful only if we
397/// plan to serialize the color back.
398#[derive(Copy, Clone)]
399enum PreserveAuthored {
400    No,
401    Yes,
402}
403
404impl Parse for Color {
405    fn parse<'i, 't>(
406        context: &ParserContext,
407        input: &mut Parser<'i, 't>,
408    ) -> Result<Self, ParseError<'i>> {
409        Self::parse_internal(context, input, PreserveAuthored::Yes)
410    }
411}
412
413impl Color {
414    fn parse_internal<'i, 't>(
415        context: &ParserContext,
416        input: &mut Parser<'i, 't>,
417        preserve_authored: PreserveAuthored,
418    ) -> Result<Self, ParseError<'i>> {
419        let authored = match preserve_authored {
420            PreserveAuthored::No => None,
421            PreserveAuthored::Yes => {
422                // Currently we only store authored value for color keywords,
423                // because all browsers serialize those values as keywords for
424                // specified value.
425                let start = input.state();
426                let authored = input.expect_ident_cloned().ok();
427                input.reset(&start);
428                authored
429            },
430        };
431
432        match input.try_parse(|i| parsing::parse_color_with(context, i)) {
433            Ok(mut color) => {
434                if let Color::Absolute(ref mut absolute) = color {
435                    // Because we can't set the `authored` value at construction time, we have to set it
436                    // here.
437                    absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
438                }
439                Ok(color)
440            },
441            Err(e) => {
442                #[cfg(feature = "gecko")]
443                {
444                    if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
445                        return Ok(Color::System(system));
446                    }
447                }
448
449                if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
450                {
451                    return Ok(Color::ColorMix(Box::new(mix)));
452                }
453
454                if let Ok(ld) = input.try_parse(|i| GenericLightDark::parse_with(i, |i| Self::parse_internal(context, i, preserve_authored)))
455                {
456                    return Ok(Color::LightDark(Box::new(ld)));
457                }
458
459                match e.kind {
460                    ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
461                        Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
462                            ValueParseErrorKind::InvalidColor(t),
463                        )))
464                    },
465                    _ => Err(e),
466                }
467            },
468        }
469    }
470
471    /// Returns whether a given color is valid for authors.
472    pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
473        input
474            .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
475            .is_ok()
476    }
477
478    /// Tries to parse a color and compute it with a given device.
479    pub fn parse_and_compute(
480        context: &ParserContext,
481        input: &mut Parser,
482        device: Option<&Device>,
483    ) -> Option<ComputedColor> {
484        use crate::error_reporting::ContextualParseError;
485        let start = input.position();
486        let result = input
487            .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
488
489        let specified = match result {
490            Ok(s) => s,
491            Err(e) => {
492                if !context.error_reporting_enabled() {
493                    return None;
494                }
495                // Ignore other kinds of errors that might be reported, such as
496                // ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken),
497                // since Gecko didn't use to report those to the error console.
498                //
499                // TODO(emilio): Revise whether we want to keep this at all, we
500                // use this only for canvas, this warnings are disabled by
501                // default and not available on OffscreenCanvas anyways...
502                if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
503                    let location = e.location.clone();
504                    let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
505                    context.log_css_error(location, error);
506                }
507                return None;
508            },
509        };
510
511        match device {
512            Some(device) => {
513                Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
514                    specified.to_computed_color(Some(&context))
515                })
516            },
517            None => specified.to_computed_color(None),
518        }
519    }
520}
521
522impl ToCss for Color {
523    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
524    where
525        W: Write,
526    {
527        match *self {
528            Color::CurrentColor => dest.write_str("currentcolor"),
529            Color::Absolute(ref absolute) => absolute.to_css(dest),
530            Color::ColorFunction(ref color_function) => color_function.to_css(dest),
531            Color::ColorMix(ref mix) => mix.to_css(dest),
532            Color::LightDark(ref ld) => ld.to_css(dest),
533            #[cfg(feature = "gecko")]
534            Color::System(system) => system.to_css(dest),
535            #[cfg(feature = "gecko")]
536            Color::InheritFromBodyQuirk => Ok(()),
537        }
538    }
539}
540
541impl Color {
542    /// Returns whether this color is allowed in forced-colors mode.
543    pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
544        match *self {
545            #[cfg(feature = "gecko")]
546            Self::InheritFromBodyQuirk => false,
547            Self::CurrentColor => true,
548            #[cfg(feature = "gecko")]
549            Self::System(..) => true,
550            Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
551            Self::ColorFunction(ref color_function) => {
552                // For now we allow transparent colors if we can resolve the color function.
553                // <https://bugzilla.mozilla.org/show_bug.cgi?id=1923053>
554                color_function
555                    .resolve_to_absolute()
556                    .map(|resolved| allow_transparent && resolved.is_transparent())
557                    .unwrap_or(false)
558            },
559            Self::LightDark(ref ld) => {
560                ld.light.honored_in_forced_colors_mode(allow_transparent) &&
561                    ld.dark.honored_in_forced_colors_mode(allow_transparent)
562            },
563            Self::ColorMix(ref mix) => {
564                mix.left.honored_in_forced_colors_mode(allow_transparent) &&
565                    mix.right.honored_in_forced_colors_mode(allow_transparent)
566            },
567        }
568    }
569
570    /// Returns currentcolor value.
571    #[inline]
572    pub fn currentcolor() -> Self {
573        Self::CurrentColor
574    }
575
576    /// Returns transparent value.
577    #[inline]
578    pub fn transparent() -> Self {
579        // We should probably set authored to "transparent", but maybe it doesn't matter.
580        Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
581    }
582
583    /// Create a color from an [`AbsoluteColor`].
584    pub fn from_absolute_color(color: AbsoluteColor) -> Self {
585        Color::Absolute(Box::new(Absolute {
586            color,
587            authored: None,
588        }))
589    }
590
591    /// Resolve this Color into an AbsoluteColor if it does not use any of the
592    /// forms that are invalid in an absolute color.
593    ///   https://drafts.csswg.org/css-color-5/#absolute-color
594    /// Returns None if the specified color is not valid as an absolute color.
595    pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
596        use crate::values::specified::percentage::ToPercentage;
597
598        match self {
599            Self::Absolute(c) => Some(c.color),
600            Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
601            Self::ColorMix(ref mix) => {
602                let left = mix.left.resolve_to_absolute()?;
603                let right = mix.right.resolve_to_absolute()?;
604                Some(crate::color::mix::mix(
605                    mix.interpolation,
606                    &left,
607                    mix.left_percentage.to_percentage(),
608                    &right,
609                    mix.right_percentage.to_percentage(),
610                    mix.flags,
611                ))
612            },
613            _ => None,
614        }
615    }
616
617    /// Parse a color, with quirks.
618    ///
619    /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
620    pub fn parse_quirky<'i, 't>(
621        context: &ParserContext,
622        input: &mut Parser<'i, 't>,
623        allow_quirks: AllowQuirks,
624    ) -> Result<Self, ParseError<'i>> {
625        input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
626            if !allow_quirks.allowed(context.quirks_mode) {
627                return Err(e);
628            }
629            Color::parse_quirky_color(input).map_err(|_| e)
630        })
631    }
632
633    fn parse_hash<'i>(
634        bytes: &[u8],
635        loc: &cssparser::SourceLocation,
636    ) -> Result<Self, ParseError<'i>> {
637        match cssparser::color::parse_hash_color(bytes) {
638            Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
639                r, g, b, a,
640            ))),
641            Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
642        }
643    }
644
645    /// Parse a <quirky-color> value.
646    ///
647    /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
648    fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
649        let location = input.current_source_location();
650        let (value, unit) = match *input.next()? {
651            Token::Number {
652                int_value: Some(integer),
653                ..
654            } => (integer, None),
655            Token::Dimension {
656                int_value: Some(integer),
657                ref unit,
658                ..
659            } => (integer, Some(unit)),
660            Token::Ident(ref ident) => {
661                if ident.len() != 3 && ident.len() != 6 {
662                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
663                }
664                return Self::parse_hash(ident.as_bytes(), &location);
665            },
666            ref t => {
667                return Err(location.new_unexpected_token_error(t.clone()));
668            },
669        };
670        if value < 0 {
671            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
672        }
673        let length = if value <= 9 {
674            1
675        } else if value <= 99 {
676            2
677        } else if value <= 999 {
678            3
679        } else if value <= 9999 {
680            4
681        } else if value <= 99999 {
682            5
683        } else if value <= 999999 {
684            6
685        } else {
686            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
687        };
688        let total = length + unit.as_ref().map_or(0, |d| d.len());
689        if total > 6 {
690            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
691        }
692        let mut serialization = [b'0'; 6];
693        let space_padding = 6 - total;
694        let mut written = space_padding;
695        let mut buf = itoa::Buffer::new();
696        let s = buf.format(value);
697        (&mut serialization[written..])
698            .write_all(s.as_bytes())
699            .unwrap();
700        written += s.len();
701        if let Some(unit) = unit {
702            written += (&mut serialization[written..])
703                .write(unit.as_bytes())
704                .unwrap();
705        }
706        debug_assert_eq!(written, 6);
707        Self::parse_hash(&serialization, &location)
708    }
709}
710
711impl Color {
712    /// Converts this Color into a ComputedColor.
713    ///
714    /// If `context` is `None`, and the specified color requires data from
715    /// the context to resolve, then `None` is returned.
716    pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
717        macro_rules! adjust_absolute_color {
718            ($color:expr) => {{
719                // Computed lightness values can not be NaN.
720                if matches!(
721                    $color.color_space,
722                    ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
723                ) {
724                    $color.components.0 = normalize($color.components.0);
725                }
726
727                // Computed RGB and XYZ components can not be NaN.
728                if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
729                    $color.components = $color.components.map(normalize);
730                }
731
732                $color.alpha = normalize($color.alpha);
733            }};
734        }
735
736        Some(match *self {
737            Color::CurrentColor => ComputedColor::CurrentColor,
738            Color::Absolute(ref absolute) => {
739                let mut color = absolute.color;
740                adjust_absolute_color!(color);
741                ComputedColor::Absolute(color)
742            },
743            Color::ColorFunction(ref color_function) => {
744                debug_assert!(color_function.has_origin_color(),
745                    "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
746
747                // Try to eagerly resolve the color function before making it a computed color.
748                if let Ok(absolute) = color_function.resolve_to_absolute() {
749                    ComputedColor::Absolute(absolute)
750                } else {
751                    let color_function = color_function
752                        .map_origin_color(|origin_color| origin_color.to_computed_color(context));
753                    ComputedColor::ColorFunction(Box::new(color_function))
754                }
755            },
756            Color::LightDark(ref ld) => ld.compute(context?),
757            Color::ColorMix(ref mix) => {
758                use crate::values::computed::percentage::Percentage;
759
760                let left = mix.left.to_computed_color(context)?;
761                let right = mix.right.to_computed_color(context)?;
762
763                ComputedColor::from_color_mix(GenericColorMix {
764                    interpolation: mix.interpolation,
765                    left,
766                    left_percentage: Percentage(mix.left_percentage.get()),
767                    right,
768                    right_percentage: Percentage(mix.right_percentage.get()),
769                    flags: mix.flags,
770                })
771            },
772            #[cfg(feature = "gecko")]
773            Color::System(system) => system.compute(context?),
774            #[cfg(feature = "gecko")]
775            Color::InheritFromBodyQuirk => {
776                ComputedColor::Absolute(context?.device().body_text_color())
777            },
778        })
779    }
780}
781
782impl ToComputedValue for Color {
783    type ComputedValue = ComputedColor;
784
785    fn to_computed_value(&self, context: &Context) -> ComputedColor {
786        self.to_computed_color(Some(context)).unwrap_or_else(|| {
787            debug_assert!(
788                false,
789                "Specified color could not be resolved to a computed color!"
790            );
791            ComputedColor::Absolute(AbsoluteColor::BLACK)
792        })
793    }
794
795    fn from_computed_value(computed: &ComputedColor) -> Self {
796        match *computed {
797            ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
798            ComputedColor::ColorFunction(ref color_function) => {
799                let color_function =
800                    color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
801                Self::ColorFunction(Box::new(color_function))
802            },
803            ComputedColor::CurrentColor => Color::CurrentColor,
804            ComputedColor::ColorMix(ref mix) => {
805                Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
806            },
807        }
808    }
809}
810
811impl SpecifiedValueInfo for Color {
812    const SUPPORTED_TYPES: u8 = CssType::COLOR;
813
814    fn collect_completion_keywords(f: KeywordsCollectFn) {
815        // We are not going to insert all the color names here. Caller and
816        // devtools should take care of them. XXX Actually, transparent
817        // should probably be handled that way as well.
818        // XXX `currentColor` should really be `currentcolor`. But let's
819        // keep it consistent with the old system for now.
820        f(&[
821            "currentColor",
822            "transparent",
823            "rgb",
824            "rgba",
825            "hsl",
826            "hsla",
827            "hwb",
828            "color",
829            "lab",
830            "lch",
831            "oklab",
832            "oklch",
833            "color-mix",
834            "light-dark",
835        ]);
836    }
837}
838
839/// Specified value for the "color" property, which resolves the `currentcolor`
840/// keyword to the parent color instead of self's color.
841#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
842#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
843pub struct ColorPropertyValue(pub Color);
844
845impl ToComputedValue for ColorPropertyValue {
846    type ComputedValue = AbsoluteColor;
847
848    #[inline]
849    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
850        let current_color = context.builder.get_parent_inherited_text().clone_color();
851        self.0
852            .to_computed_value(context)
853            .resolve_to_absolute(&current_color)
854    }
855
856    #[inline]
857    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
858        ColorPropertyValue(Color::from_absolute_color(*computed).into())
859    }
860}
861
862impl Parse for ColorPropertyValue {
863    fn parse<'i, 't>(
864        context: &ParserContext,
865        input: &mut Parser<'i, 't>,
866    ) -> Result<Self, ParseError<'i>> {
867        Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
868    }
869}
870
871/// auto | <color>
872pub type ColorOrAuto = GenericColorOrAuto<Color>;
873
874/// caret-color
875pub type CaretColor = GenericCaretColor<Color>;
876
877impl Parse for CaretColor {
878    fn parse<'i, 't>(
879        context: &ParserContext,
880        input: &mut Parser<'i, 't>,
881    ) -> Result<Self, ParseError<'i>> {
882        ColorOrAuto::parse(context, input).map(GenericCaretColor)
883    }
884}
885
886/// Various flags to represent the color-scheme property in an efficient
887/// way.
888#[derive(
889    Clone,
890    Copy,
891    Debug,
892    Default,
893    Eq,
894    MallocSizeOf,
895    PartialEq,
896    SpecifiedValueInfo,
897    ToComputedValue,
898    ToResolvedValue,
899    ToShmem,
900)]
901#[repr(C)]
902#[value_info(other_values = "light,dark,only")]
903pub struct ColorSchemeFlags(u8);
904bitflags! {
905    impl ColorSchemeFlags: u8 {
906        /// Whether the author specified `light`.
907        const LIGHT = 1 << 0;
908        /// Whether the author specified `dark`.
909        const DARK = 1 << 1;
910        /// Whether the author specified `only`.
911        const ONLY = 1 << 2;
912    }
913}
914
915/// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
916#[derive(
917    Clone,
918    Debug,
919    Default,
920    MallocSizeOf,
921    PartialEq,
922    SpecifiedValueInfo,
923    ToComputedValue,
924    ToResolvedValue,
925    ToShmem,
926)]
927#[repr(C)]
928#[value_info(other_values = "normal")]
929pub struct ColorScheme {
930    #[ignore_malloc_size_of = "Arc"]
931    idents: crate::ArcSlice<CustomIdent>,
932    /// The computed bits for the known color schemes (plus the only keyword).
933    pub bits: ColorSchemeFlags,
934}
935
936impl ColorScheme {
937    /// Returns the `normal` value.
938    pub fn normal() -> Self {
939        Self {
940            idents: Default::default(),
941            bits: ColorSchemeFlags::empty(),
942        }
943    }
944
945    /// Returns the raw bitfield.
946    pub fn raw_bits(&self) -> u8 {
947        self.bits.bits()
948    }
949}
950
951impl Parse for ColorScheme {
952    fn parse<'i, 't>(
953        _: &ParserContext,
954        input: &mut Parser<'i, 't>,
955    ) -> Result<Self, ParseError<'i>> {
956        let mut idents = vec![];
957        let mut bits = ColorSchemeFlags::empty();
958
959        let mut location = input.current_source_location();
960        while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
961            let mut is_only = false;
962            match_ignore_ascii_case! { &ident,
963                "normal" => {
964                    if idents.is_empty() && bits.is_empty() {
965                        return Ok(Self::normal());
966                    }
967                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
968                },
969                "light" => bits.insert(ColorSchemeFlags::LIGHT),
970                "dark" => bits.insert(ColorSchemeFlags::DARK),
971                "only" => {
972                    if bits.intersects(ColorSchemeFlags::ONLY) {
973                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
974                    }
975                    bits.insert(ColorSchemeFlags::ONLY);
976                    is_only = true;
977                },
978                _ => {},
979            };
980
981            if is_only {
982                if !idents.is_empty() {
983                    // Only is allowed either at the beginning or at the end,
984                    // but not in the middle.
985                    break;
986                }
987            } else {
988                idents.push(CustomIdent::from_ident(location, &ident, &[])?);
989            }
990            location = input.current_source_location();
991        }
992
993        if idents.is_empty() {
994            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
995        }
996
997        Ok(Self {
998            idents: crate::ArcSlice::from_iter(idents.into_iter()),
999            bits,
1000        })
1001    }
1002}
1003
1004impl ToCss for ColorScheme {
1005    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1006    where
1007        W: Write,
1008    {
1009        if self.idents.is_empty() {
1010            debug_assert!(self.bits.is_empty());
1011            return dest.write_str("normal");
1012        }
1013        let mut first = true;
1014        for ident in self.idents.iter() {
1015            if !first {
1016                dest.write_char(' ')?;
1017            }
1018            first = false;
1019            ident.to_css(dest)?;
1020        }
1021        if self.bits.intersects(ColorSchemeFlags::ONLY) {
1022            dest.write_str(" only")?;
1023        }
1024        Ok(())
1025    }
1026}
1027
1028/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
1029#[derive(
1030    Clone,
1031    Copy,
1032    Debug,
1033    MallocSizeOf,
1034    Parse,
1035    PartialEq,
1036    SpecifiedValueInfo,
1037    ToCss,
1038    ToComputedValue,
1039    ToResolvedValue,
1040    ToShmem,
1041)]
1042#[repr(u8)]
1043pub enum PrintColorAdjust {
1044    /// Ignore backgrounds and darken text.
1045    Economy,
1046    /// Respect specified colors.
1047    Exact,
1048}
1049
1050/// https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop
1051#[derive(
1052    Clone,
1053    Copy,
1054    Debug,
1055    MallocSizeOf,
1056    Parse,
1057    PartialEq,
1058    SpecifiedValueInfo,
1059    ToCss,
1060    ToComputedValue,
1061    ToResolvedValue,
1062    ToShmem,
1063)]
1064#[repr(u8)]
1065pub enum ForcedColorAdjust {
1066    /// Adjust colors if needed.
1067    Auto,
1068    /// Respect specified colors.
1069    None,
1070}
1071
1072/// Possible values for the forced-colors media query.
1073/// <https://drafts.csswg.org/mediaqueries-5/#forced-colors>
1074#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1075#[repr(u8)]
1076pub enum ForcedColors {
1077    /// Page colors are not being forced.
1078    None,
1079    /// Page colors would be forced in content.
1080    #[parse(condition = "ParserContext::chrome_rules_enabled")]
1081    Requested,
1082    /// Page colors are being forced.
1083    Active,
1084}
1085
1086impl ForcedColors {
1087    /// Returns whether forced-colors is active for this page.
1088    pub fn is_active(self) -> bool {
1089        matches!(self, Self::Active)
1090    }
1091}