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