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 
7 use super::AllowQuirks;
8 use crate::parser::{Parse, ParserContext};
9 use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
10 use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
11 use crate::values::specified::calc::CalcNode;
12 use crate::values::specified::Percentage;
13 use crate::values::CustomIdent;
14 use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA};
15 use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind};
16 use itoa;
17 use std::fmt::{self, Write};
18 use std::io::Write as IoWrite;
19 use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
20 use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
21 
22 /// A color space as defined in [1].
23 ///
24 /// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
25 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
26 pub enum ColorSpaceKind {
27     /// The sRGB color space.
28     Srgb,
29     /// The CIEXYZ color space.
30     Xyz,
31     /// The CIELAB color space.
32     Lab,
33     /// The CIELAB color space, expressed in cylindrical coordinates.
34     Lch,
35 }
36 
37 /// A hue adjuster as defined in [1].
38 ///
39 /// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
40 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
41 pub enum HueAdjuster {
42     /// The "shorter" angle adjustment.
43     Shorter,
44     /// The "longer" angle adjustment.
45     Longer,
46     /// The "increasing" angle adjustment.
47     Increasing,
48     /// The "decreasing" angle adjustment.
49     Decreasing,
50     /// The "specified" angle adjustment.
51     Specified,
52 }
53 
54 /// A restricted version of the css `color-mix()` function, which only supports
55 /// percentages.
56 ///
57 /// https://drafts.csswg.org/css-color-5/#color-mix
58 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
59 #[allow(missing_docs)]
60 pub struct ColorMix {
61     pub color_space: ColorSpaceKind,
62     pub left: Color,
63     pub left_percentage: Percentage,
64     pub right: Color,
65     pub right_percentage: Percentage,
66     pub hue_adjuster: HueAdjuster,
67 }
68 
69 // NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem
70 // particularly complete, and disagrees with the examples.
71 //
72 // [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed
73 impl Parse for ColorMix {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>74     fn parse<'i, 't>(
75         context: &ParserContext,
76         input: &mut Parser<'i, 't>,
77     ) -> Result<Self, ParseError<'i>> {
78         let enabled =
79             context.chrome_rules_enabled() || static_prefs::pref!("layout.css.color-mix.enabled");
80 
81         if !enabled {
82             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
83         }
84 
85         let color_spaces_enabled = context.chrome_rules_enabled() ||
86             static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
87 
88         input.expect_function_matching("color-mix")?;
89 
90         // NOTE(emilio): This implements the syntax described here for now,
91         // might need to get updated in the future.
92         //
93         // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
94         input.parse_nested_block(|input| {
95             input.expect_ident_matching("in")?;
96             let color_space = if color_spaces_enabled {
97                 ColorSpaceKind::parse(input)?
98             } else {
99                 input.expect_ident_matching("srgb")?;
100                 ColorSpaceKind::Srgb
101             };
102             input.expect_comma()?;
103 
104             let left = Color::parse(context, input)?;
105             let left_percentage = input
106                 .try_parse(|input| Percentage::parse(context, input))
107                 .ok();
108 
109             input.expect_comma()?;
110 
111             let right = Color::parse(context, input)?;
112             let right_percentage = input
113                 .try_parse(|input| Percentage::parse(context, input))
114                 .unwrap_or_else(|_| {
115                     Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
116                 });
117 
118             let left_percentage =
119                 left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
120 
121             let hue_adjuster = input
122                 .try_parse(|input| HueAdjuster::parse(input))
123                 .unwrap_or(HueAdjuster::Shorter);
124 
125             Ok(ColorMix {
126                 color_space,
127                 left,
128                 left_percentage,
129                 right,
130                 right_percentage,
131                 hue_adjuster,
132             })
133         })
134     }
135 }
136 
137 impl ToCss for ColorMix {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,138     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
139     where
140         W: Write,
141     {
142         fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
143             if percent.is_calc() {
144                 return false;
145             }
146             if percent.get() == 0.5 {
147                 return other.get() == 0.5;
148             }
149             if is_left {
150                 return false;
151             }
152             (1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
153         }
154 
155         dest.write_str("color-mix(in ")?;
156         self.color_space.to_css(dest)?;
157         dest.write_str(", ")?;
158         self.left.to_css(dest)?;
159         if !can_omit(&self.left_percentage, &self.right_percentage, true) {
160             dest.write_str(" ")?;
161             self.left_percentage.to_css(dest)?;
162         }
163         dest.write_str(", ")?;
164         self.right.to_css(dest)?;
165         if !can_omit(&self.right_percentage, &self.left_percentage, false) {
166             dest.write_str(" ")?;
167             self.right_percentage.to_css(dest)?;
168         }
169 
170         if self.hue_adjuster != HueAdjuster::Shorter {
171             dest.write_str(" ")?;
172             self.hue_adjuster.to_css(dest)?;
173         }
174 
175         dest.write_str(")")
176     }
177 }
178 
179 /// Specified color value
180 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
181 pub enum Color {
182     /// The 'currentColor' keyword
183     CurrentColor,
184     /// A specific RGBA color
185     Numeric {
186         /// Parsed RGBA color
187         parsed: RGBA,
188         /// Authored representation
189         authored: Option<Box<str>>,
190     },
191     /// A complex color value from computed value
192     Complex(ComputedColor),
193     /// A system color.
194     #[cfg(feature = "gecko")]
195     System(SystemColor),
196     /// A color mix.
197     ColorMix(Box<ColorMix>),
198     /// Quirksmode-only rule for inheriting color from the body
199     #[cfg(feature = "gecko")]
200     InheritFromBodyQuirk,
201 }
202 
203 /// System colors. A bunch of these are ad-hoc, others come from Windows:
204 ///
205 ///   https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor
206 ///
207 /// Others are HTML/CSS specific. Spec is:
208 ///
209 ///   https://drafts.csswg.org/css-color/#css-system-colors
210 ///   https://drafts.csswg.org/css-color/#deprecated-system-colors
211 #[allow(missing_docs)]
212 #[cfg(feature = "gecko")]
213 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
214 #[repr(u8)]
215 pub enum SystemColor {
216     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
217     TextSelectDisabledBackground,
218     #[css(skip)]
219     TextSelectAttentionBackground,
220     #[css(skip)]
221     TextSelectAttentionForeground,
222     #[css(skip)]
223     TextHighlightBackground,
224     #[css(skip)]
225     TextHighlightForeground,
226     #[css(skip)]
227     IMERawInputBackground,
228     #[css(skip)]
229     IMERawInputForeground,
230     #[css(skip)]
231     IMERawInputUnderline,
232     #[css(skip)]
233     IMESelectedRawTextBackground,
234     #[css(skip)]
235     IMESelectedRawTextForeground,
236     #[css(skip)]
237     IMESelectedRawTextUnderline,
238     #[css(skip)]
239     IMEConvertedTextBackground,
240     #[css(skip)]
241     IMEConvertedTextForeground,
242     #[css(skip)]
243     IMEConvertedTextUnderline,
244     #[css(skip)]
245     IMESelectedConvertedTextBackground,
246     #[css(skip)]
247     IMESelectedConvertedTextForeground,
248     #[css(skip)]
249     IMESelectedConvertedTextUnderline,
250     #[css(skip)]
251     SpellCheckerUnderline,
252     #[css(skip)]
253     ThemedScrollbar,
254     #[css(skip)]
255     ThemedScrollbarInactive,
256     #[css(skip)]
257     ThemedScrollbarThumb,
258     #[css(skip)]
259     ThemedScrollbarThumbHover,
260     #[css(skip)]
261     ThemedScrollbarThumbActive,
262     #[css(skip)]
263     ThemedScrollbarThumbInactive,
264     Activeborder,
265     /// Background in the (active) titlebar.
266     Activecaption,
267     Appworkspace,
268     Background,
269     Buttonface,
270     Buttonhighlight,
271     Buttonshadow,
272     Buttontext,
273     /// Text color in the (active) titlebar.
274     Captiontext,
275     #[parse(aliases = "-moz-field")]
276     Field,
277     /// Used for disabled field backgrounds.
278     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
279     MozDisabledfield,
280     #[parse(aliases = "-moz-fieldtext")]
281     Fieldtext,
282 
283     Graytext,
284     Highlight,
285     Highlighttext,
286     Inactiveborder,
287     /// Background in the (inactive) titlebar.
288     Inactivecaption,
289     /// Text color in the (inactive) titlebar.
290     Inactivecaptiontext,
291     Infobackground,
292     Infotext,
293     Menu,
294     Menutext,
295     Scrollbar,
296     Threeddarkshadow,
297     Threedface,
298     Threedhighlight,
299     Threedlightshadow,
300     Threedshadow,
301     Window,
302     Windowframe,
303     Windowtext,
304     MozButtondefault,
305     #[parse(aliases = "-moz-default-color")]
306     Canvastext,
307     #[parse(aliases = "-moz-default-background-color")]
308     Canvas,
309     MozDialog,
310     MozDialogtext,
311     /// Used to highlight valid regions to drop something onto.
312     MozDragtargetzone,
313     /// Used for selected but not focused cell backgrounds.
314     #[parse(aliases = "-moz-html-cellhighlight")]
315     MozCellhighlight,
316     /// Used for selected but not focused cell text.
317     #[parse(aliases = "-moz-html-cellhighlighttext")]
318     MozCellhighlighttext,
319     /// Used for selected and focused html cell backgrounds.
320     Selecteditem,
321     /// Used for selected and focused html cell text.
322     Selecteditemtext,
323     /// Used to button text background when hovered.
324     MozButtonhoverface,
325     /// Used to button text color when hovered.
326     MozButtonhovertext,
327     /// Used for menu item backgrounds when hovered.
328     MozMenuhover,
329     /// Used for menu item text when hovered.
330     MozMenuhovertext,
331     /// Used for menubar item text.
332     MozMenubartext,
333     /// Used for menubar item text when hovered.
334     MozMenubarhovertext,
335 
336     /// On platforms where these colors are the same as -moz-field, use
337     /// -moz-fieldtext as foreground color
338     MozEventreerow,
339     MozOddtreerow,
340 
341     /// Used for button text when pressed.
342     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
343     MozButtonactivetext,
344 
345     /// Used for button background when pressed.
346     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
347     MozButtonactiveface,
348 
349     /// Used for button background when disabled.
350     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
351     MozButtondisabledface,
352 
353     /// Background color of chrome toolbars in active windows.
354     MozMacChromeActive,
355     /// Background color of chrome toolbars in inactive windows.
356     MozMacChromeInactive,
357     /// Foreground color of default buttons.
358     MozMacDefaultbuttontext,
359     /// Ring color around text fields and lists.
360     MozMacFocusring,
361     /// Color used when mouse is over a menu item.
362     MozMacMenuselect,
363     /// Color used to do shadows on menu items.
364     MozMacMenushadow,
365     /// Color used to display text for disabled menu items.
366     MozMacMenutextdisable,
367     /// Color used to display text while mouse is over a menu item.
368     MozMacMenutextselect,
369     /// Text color of disabled text on toolbars.
370     MozMacDisabledtoolbartext,
371     /// Inactive light hightlight
372     MozMacSecondaryhighlight,
373 
374     /// Font smoothing background colors needed by the Mac OS X theme, based on
375     /// -moz-appearance names.
376     MozMacVibrantTitlebarLight,
377     MozMacVibrantTitlebarDark,
378     MozMacMenupopup,
379     MozMacMenuitem,
380     MozMacActiveMenuitem,
381     MozMacSourceList,
382     MozMacSourceListSelection,
383     MozMacActiveSourceListSelection,
384     MozMacTooltip,
385 
386     /// Theme accent color.
387     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
388     MozAccentColor,
389 
390     /// Foreground for the accent color.
391     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
392     MozAccentColorForeground,
393 
394     /// The background-color for :autofill-ed inputs.
395     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
396     MozAutofillBackground,
397 
398     /// Media rebar text.
399     MozWinMediatext,
400     /// Communications rebar text.
401     MozWinCommunicationstext,
402 
403     /// Hyperlink color extracted from the system, not affected by the
404     /// browser.anchor_color user pref.
405     ///
406     /// There is no OS-specified safe background color for this text, but it is
407     /// used regularly within Windows and the Gnome DE on Dialog and Window
408     /// colors.
409     MozNativehyperlinktext,
410 
411     /// As above, but visited link color.
412     #[css(skip)]
413     MozNativevisitedhyperlinktext,
414 
415     #[parse(aliases = "-moz-hyperlinktext")]
416     Linktext,
417     #[parse(aliases = "-moz-activehyperlinktext")]
418     Activetext,
419     #[parse(aliases = "-moz-visitedhyperlinktext")]
420     Visitedtext,
421 
422     /// Combobox widgets
423     MozComboboxtext,
424     MozCombobox,
425 
426     /// Color of tree column headers
427     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
428     MozColheadertext,
429     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
430     MozColheaderhovertext,
431 
432     #[css(skip)]
433     End, // Just for array-indexing purposes.
434 }
435 
436 #[cfg(feature = "gecko")]
437 impl SystemColor {
438     #[inline]
compute(&self, cx: &Context) -> ComputedColor439     fn compute(&self, cx: &Context) -> ComputedColor {
440         use crate::gecko::values::convert_nscolor_to_rgba;
441         use crate::gecko_bindings::bindings;
442 
443         // TODO: We should avoid cloning here most likely, though it's
444         // cheap-ish.
445         let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
446         let color = cx.device().system_nscolor(*self, &style_color_scheme);
447         if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
448             return ComputedColor::currentcolor();
449         }
450         ComputedColor::rgba(convert_nscolor_to_rgba(color))
451     }
452 }
453 
454 impl From<RGBA> for Color {
from(value: RGBA) -> Self455     fn from(value: RGBA) -> Self {
456         Color::rgba(value)
457     }
458 }
459 
460 struct ColorComponentParser<'a, 'b: 'a>(&'a ParserContext<'b>);
461 impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponentParser<'a, 'b> {
462     type Error = StyleParseErrorKind<'i>;
463 
parse_angle_or_number<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<AngleOrNumber, ParseError<'i>>464     fn parse_angle_or_number<'t>(
465         &self,
466         input: &mut Parser<'i, 't>,
467     ) -> Result<AngleOrNumber, ParseError<'i>> {
468         use crate::values::specified::Angle;
469 
470         let location = input.current_source_location();
471         let token = input.next()?.clone();
472         match token {
473             Token::Dimension {
474                 value, ref unit, ..
475             } => {
476                 let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false);
477 
478                 let degrees = match angle {
479                     Ok(angle) => angle.degrees(),
480                     Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
481                 };
482 
483                 Ok(AngleOrNumber::Angle { degrees })
484             },
485             Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
486             Token::Function(ref name) => {
487                 let function = CalcNode::math_function(name, location)?;
488                 CalcNode::parse_angle_or_number(self.0, input, function)
489             },
490             t => return Err(location.new_unexpected_token_error(t)),
491         }
492     }
493 
parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>494     fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
495         Ok(Percentage::parse(self.0, input)?.get())
496     }
497 
parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>498     fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
499         use crate::values::specified::Number;
500 
501         Ok(Number::parse(self.0, input)?.get())
502     }
503 
parse_number_or_percentage<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<NumberOrPercentage, ParseError<'i>>504     fn parse_number_or_percentage<'t>(
505         &self,
506         input: &mut Parser<'i, 't>,
507     ) -> Result<NumberOrPercentage, ParseError<'i>> {
508         let location = input.current_source_location();
509 
510         match *input.next()? {
511             Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
512             Token::Percentage { unit_value, .. } => {
513                 Ok(NumberOrPercentage::Percentage { unit_value })
514             },
515             Token::Function(ref name) => {
516                 let function = CalcNode::math_function(name, location)?;
517                 CalcNode::parse_number_or_percentage(self.0, input, function)
518             },
519             ref t => return Err(location.new_unexpected_token_error(t.clone())),
520         }
521     }
522 }
523 
524 impl Parse for Color {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>525     fn parse<'i, 't>(
526         context: &ParserContext,
527         input: &mut Parser<'i, 't>,
528     ) -> Result<Self, ParseError<'i>> {
529         // Currently we only store authored value for color keywords,
530         // because all browsers serialize those values as keywords for
531         // specified value.
532         let start = input.state();
533         let authored = input.expect_ident_cloned().ok();
534         input.reset(&start);
535 
536         let compontent_parser = ColorComponentParser(&*context);
537         match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) {
538             Ok(value) => Ok(match value {
539                 CSSParserColor::CurrentColor => Color::CurrentColor,
540                 CSSParserColor::RGBA(rgba) => Color::Numeric {
541                     parsed: rgba,
542                     authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()),
543                 },
544             }),
545             Err(e) => {
546                 #[cfg(feature = "gecko")]
547                 {
548                     if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
549                         return Ok(Color::System(system));
550                     }
551                 }
552 
553                 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i)) {
554                     return Ok(Color::ColorMix(Box::new(mix)));
555                 }
556 
557                 match e.kind {
558                     ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
559                         Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
560                             ValueParseErrorKind::InvalidColor(t),
561                         )))
562                     },
563                     _ => Err(e),
564                 }
565             },
566         }
567     }
568 }
569 
570 impl ToCss for Color {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,571     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
572     where
573         W: Write,
574     {
575         match *self {
576             Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
577             Color::Numeric {
578                 authored: Some(ref authored),
579                 ..
580             } => dest.write_str(authored),
581             Color::Numeric {
582                 parsed: ref rgba, ..
583             } => rgba.to_css(dest),
584             // TODO: Could represent this as a color-mix() instead.
585             Color::Complex(_) => Ok(()),
586             Color::ColorMix(ref mix) => mix.to_css(dest),
587             #[cfg(feature = "gecko")]
588             Color::System(system) => system.to_css(dest),
589             #[cfg(feature = "gecko")]
590             Color::InheritFromBodyQuirk => Ok(()),
591         }
592     }
593 }
594 
595 /// A wrapper of cssparser::Color::parse_hash.
596 ///
597 /// That function should never return CurrentColor, so it makes no sense to
598 /// handle a cssparser::Color here. This should really be done in cssparser
599 /// directly rather than here.
parse_hash_color(value: &[u8]) -> Result<RGBA, ()>600 fn parse_hash_color(value: &[u8]) -> Result<RGBA, ()> {
601     CSSParserColor::parse_hash(value).map(|color| match color {
602         CSSParserColor::RGBA(rgba) => rgba,
603         CSSParserColor::CurrentColor => unreachable!("parse_hash should never return currentcolor"),
604     })
605 }
606 
607 impl Color {
608     /// Returns whether this color is a system color.
is_system(&self) -> bool609     pub fn is_system(&self) -> bool {
610         matches!(self, Color::System(..))
611     }
612 
613     /// Returns currentcolor value.
614     #[inline]
currentcolor() -> Color615     pub fn currentcolor() -> Color {
616         Color::CurrentColor
617     }
618 
619     /// Returns transparent value.
620     #[inline]
transparent() -> Color621     pub fn transparent() -> Color {
622         // We should probably set authored to "transparent", but maybe it doesn't matter.
623         Color::rgba(RGBA::transparent())
624     }
625 
626     /// Returns a numeric RGBA color value.
627     #[inline]
rgba(rgba: RGBA) -> Self628     pub fn rgba(rgba: RGBA) -> Self {
629         Color::Numeric {
630             parsed: rgba,
631             authored: None,
632         }
633     }
634 
635     /// Parse a color, with quirks.
636     ///
637     /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>638     pub fn parse_quirky<'i, 't>(
639         context: &ParserContext,
640         input: &mut Parser<'i, 't>,
641         allow_quirks: AllowQuirks,
642     ) -> Result<Self, ParseError<'i>> {
643         input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
644             if !allow_quirks.allowed(context.quirks_mode) {
645                 return Err(e);
646             }
647             Color::parse_quirky_color(input)
648                 .map(Color::rgba)
649                 .map_err(|_| e)
650         })
651     }
652 
653     /// Parse a <quirky-color> value.
654     ///
655     /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>>656     fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>> {
657         let location = input.current_source_location();
658         let (value, unit) = match *input.next()? {
659             Token::Number {
660                 int_value: Some(integer),
661                 ..
662             } => (integer, None),
663             Token::Dimension {
664                 int_value: Some(integer),
665                 ref unit,
666                 ..
667             } => (integer, Some(unit)),
668             Token::Ident(ref ident) => {
669                 if ident.len() != 3 && ident.len() != 6 {
670                     return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
671                 }
672                 return parse_hash_color(ident.as_bytes()).map_err(|()| {
673                     location.new_custom_error(StyleParseErrorKind::UnspecifiedError)
674                 });
675             },
676             ref t => {
677                 return Err(location.new_unexpected_token_error(t.clone()));
678             },
679         };
680         if value < 0 {
681             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
682         }
683         let length = if value <= 9 {
684             1
685         } else if value <= 99 {
686             2
687         } else if value <= 999 {
688             3
689         } else if value <= 9999 {
690             4
691         } else if value <= 99999 {
692             5
693         } else if value <= 999999 {
694             6
695         } else {
696             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
697         };
698         let total = length + unit.as_ref().map_or(0, |d| d.len());
699         if total > 6 {
700             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
701         }
702         let mut serialization = [b'0'; 6];
703         let space_padding = 6 - total;
704         let mut written = space_padding;
705         written += itoa::write(&mut serialization[written..], value).unwrap();
706         if let Some(unit) = unit {
707             written += (&mut serialization[written..])
708                 .write(unit.as_bytes())
709                 .unwrap();
710         }
711         debug_assert_eq!(written, 6);
712         parse_hash_color(&serialization)
713             .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
714     }
715 }
716 
717 impl Color {
718     /// Converts this Color into a ComputedColor.
719     ///
720     /// If `context` is `None`, and the specified color requires data from
721     /// the context to resolve, then `None` is returned.
to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor>722     pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
723         Some(match *self {
724             Color::CurrentColor => ComputedColor::currentcolor(),
725             Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
726             Color::Complex(ref complex) => *complex,
727             Color::ColorMix(ref mix) => {
728                 use crate::values::animated::color::Color as AnimatedColor;
729                 use crate::values::animated::ToAnimatedValue;
730 
731                 let left = mix.left.to_computed_color(context)?.to_animated_value();
732                 let right = mix.right.to_computed_color(context)?.to_animated_value();
733                 ToAnimatedValue::from_animated_value(AnimatedColor::mix(
734                     mix.color_space,
735                     &left,
736                     mix.left_percentage.get(),
737                     &right,
738                     mix.right_percentage.get(),
739                     mix.hue_adjuster,
740                 ))
741             },
742             #[cfg(feature = "gecko")]
743             Color::System(system) => system.compute(context?),
744             #[cfg(feature = "gecko")]
745             Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
746         })
747     }
748 }
749 
750 impl ToComputedValue for Color {
751     type ComputedValue = ComputedColor;
752 
to_computed_value(&self, context: &Context) -> ComputedColor753     fn to_computed_value(&self, context: &Context) -> ComputedColor {
754         self.to_computed_color(Some(context)).unwrap()
755     }
756 
from_computed_value(computed: &ComputedColor) -> Self757     fn from_computed_value(computed: &ComputedColor) -> Self {
758         if computed.is_numeric() {
759             return Color::rgba(computed.color);
760         }
761         if computed.is_currentcolor() {
762             return Color::currentcolor();
763         }
764         Color::Complex(*computed)
765     }
766 }
767 
768 /// Specified color value for `-moz-font-smoothing-background-color`.
769 ///
770 /// This property does not support `currentcolor`. We could drop it at
771 /// parse-time, but it's not exposed to the web so it doesn't really matter.
772 ///
773 /// We resolve it to `transparent` instead.
774 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
775 pub struct MozFontSmoothingBackgroundColor(pub Color);
776 
777 impl Parse for MozFontSmoothingBackgroundColor {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>778     fn parse<'i, 't>(
779         context: &ParserContext,
780         input: &mut Parser<'i, 't>,
781     ) -> Result<Self, ParseError<'i>> {
782         Color::parse(context, input).map(MozFontSmoothingBackgroundColor)
783     }
784 }
785 
786 impl ToComputedValue for MozFontSmoothingBackgroundColor {
787     type ComputedValue = RGBA;
788 
to_computed_value(&self, context: &Context) -> RGBA789     fn to_computed_value(&self, context: &Context) -> RGBA {
790         self.0
791             .to_computed_value(context)
792             .to_rgba(RGBA::transparent())
793     }
794 
from_computed_value(computed: &RGBA) -> Self795     fn from_computed_value(computed: &RGBA) -> Self {
796         MozFontSmoothingBackgroundColor(Color::rgba(*computed))
797     }
798 }
799 
800 impl SpecifiedValueInfo for Color {
801     const SUPPORTED_TYPES: u8 = CssType::COLOR;
802 
collect_completion_keywords(f: KeywordsCollectFn)803     fn collect_completion_keywords(f: KeywordsCollectFn) {
804         // We are not going to insert all the color names here. Caller and
805         // devtools should take care of them. XXX Actually, transparent
806         // should probably be handled that way as well.
807         // XXX `currentColor` should really be `currentcolor`. But let's
808         // keep it consistent with the old system for now.
809         f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]);
810     }
811 }
812 
813 /// Specified value for the "color" property, which resolves the `currentcolor`
814 /// keyword to the parent color instead of self's color.
815 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
816 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
817 pub struct ColorPropertyValue(pub Color);
818 
819 impl ToComputedValue for ColorPropertyValue {
820     type ComputedValue = RGBA;
821 
822     #[inline]
to_computed_value(&self, context: &Context) -> RGBA823     fn to_computed_value(&self, context: &Context) -> RGBA {
824         self.0
825             .to_computed_value(context)
826             .to_rgba(context.builder.get_parent_inherited_text().clone_color())
827     }
828 
829     #[inline]
from_computed_value(computed: &RGBA) -> Self830     fn from_computed_value(computed: &RGBA) -> Self {
831         ColorPropertyValue(Color::rgba(*computed).into())
832     }
833 }
834 
835 impl Parse for ColorPropertyValue {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>836     fn parse<'i, 't>(
837         context: &ParserContext,
838         input: &mut Parser<'i, 't>,
839     ) -> Result<Self, ParseError<'i>> {
840         Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
841     }
842 }
843 
844 /// auto | <color>
845 pub type ColorOrAuto = GenericColorOrAuto<Color>;
846 
847 /// caret-color
848 pub type CaretColor = GenericCaretColor<Color>;
849 
850 impl Parse for CaretColor {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>851     fn parse<'i, 't>(
852         context: &ParserContext,
853         input: &mut Parser<'i, 't>,
854     ) -> Result<Self, ParseError<'i>> {
855         ColorOrAuto::parse(context, input).map(GenericCaretColor)
856     }
857 }
858 
859 bitflags! {
860     /// Various flags to represent the color-scheme property in an efficient
861     /// way.
862     #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
863     #[repr(C)]
864     #[value_info(other_values = "light,dark,only")]
865     pub struct ColorSchemeFlags: u8 {
866         /// Whether the author specified `light`.
867         const LIGHT = 1 << 0;
868         /// Whether the author specified `dark`.
869         const DARK = 1 << 1;
870         /// Whether the author specified `only`.
871         const ONLY = 1 << 2;
872     }
873 }
874 
875 /// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
876 #[derive(
877     Clone,
878     Debug,
879     Default,
880     MallocSizeOf,
881     PartialEq,
882     SpecifiedValueInfo,
883     ToComputedValue,
884     ToResolvedValue,
885     ToShmem,
886 )]
887 #[repr(C)]
888 #[value_info(other_values = "normal")]
889 pub struct ColorScheme {
890     #[ignore_malloc_size_of = "Arc"]
891     idents: crate::ArcSlice<CustomIdent>,
892     bits: ColorSchemeFlags,
893 }
894 
895 impl ColorScheme {
896     /// Returns the `normal` value.
normal() -> Self897     pub fn normal() -> Self {
898         Self {
899             idents: Default::default(),
900             bits: ColorSchemeFlags::empty(),
901         }
902     }
903 
904     /// Returns the raw bitfield.
raw_bits(&self) -> u8905     pub fn raw_bits(&self) -> u8 {
906         self.bits.bits
907     }
908 }
909 
910 impl Parse for ColorScheme {
parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>911     fn parse<'i, 't>(
912         _: &ParserContext,
913         input: &mut Parser<'i, 't>,
914     ) -> Result<Self, ParseError<'i>> {
915         let mut idents = vec![];
916         let mut bits = ColorSchemeFlags::empty();
917 
918         let mut location = input.current_source_location();
919         while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
920             let mut is_only = false;
921             match_ignore_ascii_case! { &ident,
922                 "normal" => {
923                     if idents.is_empty() && bits.is_empty() {
924                         return Ok(Self::normal());
925                     }
926                     return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
927                 },
928                 "light" => bits.insert(ColorSchemeFlags::LIGHT),
929                 "dark" => bits.insert(ColorSchemeFlags::DARK),
930                 "only" => {
931                     if bits.intersects(ColorSchemeFlags::ONLY) {
932                         return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
933                     }
934                     bits.insert(ColorSchemeFlags::ONLY);
935                     is_only = true;
936                 },
937                 _ => {},
938             };
939 
940             if is_only {
941                 if !idents.is_empty() {
942                     // Only is allowed either at the beginning or at the end,
943                     // but not in the middle.
944                     break;
945                 }
946             } else {
947                 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
948             }
949             location = input.current_source_location();
950         }
951 
952         if idents.is_empty() {
953             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
954         }
955 
956         Ok(Self {
957             idents: crate::ArcSlice::from_iter(idents.into_iter()),
958             bits,
959         })
960     }
961 }
962 
963 impl ToCss for ColorScheme {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,964     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
965     where
966         W: Write,
967     {
968         if self.idents.is_empty() {
969             debug_assert!(self.bits.is_empty());
970             return dest.write_str("normal");
971         }
972         let mut first = true;
973         for ident in self.idents.iter() {
974             if !first {
975                 dest.write_char(' ')?;
976             }
977             first = false;
978             ident.to_css(dest)?;
979         }
980         if self.bits.intersects(ColorSchemeFlags::ONLY) {
981             dest.write_str(" only")?;
982         }
983         Ok(())
984     }
985 }
986 
987 /// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
988 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
989 #[repr(u8)]
990 pub enum PrintColorAdjust {
991     /// Ignore backgrounds and darken text.
992     Economy,
993     /// Respect specified colors.
994     Exact,
995 }
996