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