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