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