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