Skip to main content

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