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