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::{Color as GenericColor, ColorOrAuto as GenericColorOrAuto};
13 use crate::values::specified::calc::CalcNode;
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 /// Specified color value
23 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
24 pub enum Color {
25     /// The 'currentColor' keyword
26     CurrentColor,
27     /// A specific RGBA color
28     Numeric {
29         /// Parsed RGBA color
30         parsed: RGBA,
31         /// Authored representation
32         authored: Option<Box<str>>,
33     },
34     /// A complex color value from computed value
35     Complex(ComputedColor),
36     /// A system color
37     #[cfg(feature = "gecko")]
38     System(SystemColor),
39     /// Quirksmode-only rule for inheriting color from the body
40     #[cfg(feature = "gecko")]
41     InheritFromBodyQuirk,
42 }
43 
44 /// System colors.
45 #[allow(missing_docs)]
46 #[cfg(feature = "gecko")]
47 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
48 #[repr(u8)]
49 pub enum SystemColor {
50     #[css(skip)]
51     WindowBackground,
52     #[css(skip)]
53     WindowForeground,
54     #[css(skip)]
55     WidgetBackground,
56     #[css(skip)]
57     WidgetForeground,
58     #[css(skip)]
59     WidgetSelectBackground,
60     #[css(skip)]
61     WidgetSelectForeground,
62     #[css(skip)]
63     Widget3DHighlight,
64     #[css(skip)]
65     Widget3DShadow,
66     #[css(skip)]
67     TextBackground,
68     #[css(skip)]
69     TextForeground,
70     #[css(skip)]
71     TextSelectBackground,
72     #[css(skip)]
73     TextSelectForeground,
74     #[css(skip)]
75     TextSelectForegroundCustom,
76     #[css(skip)]
77     TextSelectBackgroundDisabled,
78     #[css(skip)]
79     TextSelectBackgroundAttention,
80     #[css(skip)]
81     TextHighlightBackground,
82     #[css(skip)]
83     TextHighlightForeground,
84     #[css(skip)]
85     IMERawInputBackground,
86     #[css(skip)]
87     IMERawInputForeground,
88     #[css(skip)]
89     IMERawInputUnderline,
90     #[css(skip)]
91     IMESelectedRawTextBackground,
92     #[css(skip)]
93     IMESelectedRawTextForeground,
94     #[css(skip)]
95     IMESelectedRawTextUnderline,
96     #[css(skip)]
97     IMEConvertedTextBackground,
98     #[css(skip)]
99     IMEConvertedTextForeground,
100     #[css(skip)]
101     IMEConvertedTextUnderline,
102     #[css(skip)]
103     IMESelectedConvertedTextBackground,
104     #[css(skip)]
105     IMESelectedConvertedTextForeground,
106     #[css(skip)]
107     IMESelectedConvertedTextUnderline,
108     #[css(skip)]
109     SpellCheckerUnderline,
110     Activeborder,
111     Activecaption,
112     Appworkspace,
113     Background,
114     Buttonface,
115     Buttonhighlight,
116     Buttonshadow,
117     Buttontext,
118     Captiontext,
119     #[parse(aliases = "-moz-field")]
120     Field,
121     #[parse(aliases = "-moz-fieldtext")]
122     Fieldtext,
123     Graytext,
124     Highlight,
125     Highlighttext,
126     Inactiveborder,
127     Inactivecaption,
128     Inactivecaptiontext,
129     Infobackground,
130     Infotext,
131     Menu,
132     Menutext,
133     Scrollbar,
134     Threeddarkshadow,
135     Threedface,
136     Threedhighlight,
137     Threedlightshadow,
138     Threedshadow,
139     Window,
140     Windowframe,
141     Windowtext,
142     MozButtondefault,
143     #[parse(aliases = "-moz-default-color")]
144     Canvastext,
145     #[parse(aliases = "-moz-default-background-color")]
146     Canvas,
147     MozDialog,
148     MozDialogtext,
149     /// Used to highlight valid regions to drop something onto.
150     MozDragtargetzone,
151     /// Used for selected but not focused cell backgrounds.
152     MozCellhighlight,
153     /// Used for selected but not focused cell text.
154     MozCellhighlighttext,
155     /// Used for selected but not focused html cell backgrounds.
156     MozHtmlCellhighlight,
157     /// Used for selected but not focused html cell text.
158     MozHtmlCellhighlighttext,
159     /// Used to button text background when hovered.
160     MozButtonhoverface,
161     /// Used to button text color when hovered.
162     MozButtonhovertext,
163     /// Used for menu item backgrounds when hovered.
164     MozMenuhover,
165     /// Used for menu item text when hovered.
166     MozMenuhovertext,
167     /// Used for menubar item text.
168     MozMenubartext,
169     /// Used for menubar item text when hovered.
170     MozMenubarhovertext,
171 
172     /// On platforms where these colors are the same as -moz-field, use
173     /// -moz-fieldtext as foreground color
174     MozEventreerow,
175     MozOddtreerow,
176 
177     /// Used for button text when pressed.
178     #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
179     MozGtkButtonactivetext,
180 
181     /// Used for button text when pressed.
182     MozMacButtonactivetext,
183     /// Background color of chrome toolbars in active windows.
184     MozMacChromeActive,
185     /// Background color of chrome toolbars in inactive windows.
186     MozMacChromeInactive,
187     /// Foreground color of default buttons.
188     MozMacDefaultbuttontext,
189     /// Ring color around text fields and lists.
190     MozMacFocusring,
191     /// Color used when mouse is over a menu item.
192     MozMacMenuselect,
193     /// Color used to do shadows on menu items.
194     MozMacMenushadow,
195     /// Color used to display text for disabled menu items.
196     MozMacMenutextdisable,
197     /// Color used to display text while mouse is over a menu item.
198     MozMacMenutextselect,
199     /// Text color of disabled text on toolbars.
200     MozMacDisabledtoolbartext,
201     /// Inactive light hightlight
202     MozMacSecondaryhighlight,
203 
204     /// Font smoothing background colors needed by the Mac OS X theme, based on
205     /// -moz-appearance names.
206     MozMacVibrancyLight,
207     MozMacVibrancyDark,
208     MozMacVibrantTitlebarLight,
209     MozMacVibrantTitlebarDark,
210     MozMacMenupopup,
211     MozMacMenuitem,
212     MozMacActiveMenuitem,
213     MozMacSourceList,
214     MozMacSourceListSelection,
215     MozMacActiveSourceListSelection,
216     MozMacTooltip,
217 
218     /// Accent color for title bar.
219     MozWinAccentcolor,
220     /// Color from drawing text over the accent color.
221     MozWinAccentcolortext,
222     /// Media rebar text.
223     MozWinMediatext,
224     /// Communications rebar text.
225     MozWinCommunicationstext,
226 
227     /// Hyperlink color extracted from the system, not affected by the
228     /// browser.anchor_color user pref.
229     ///
230     /// There is no OS-specified safe background color for this text, but it is
231     /// used regularly within Windows and the Gnome DE on Dialog and Window
232     /// colors.
233     MozNativehyperlinktext,
234 
235     #[parse(aliases = "-moz-hyperlinktext")]
236     Linktext,
237     #[parse(aliases = "-moz-activehyperlinktext")]
238     Activetext,
239     #[parse(aliases = "-moz-visitedhyperlinktext")]
240     Visitedtext,
241 
242     /// Combobox widgets
243     MozComboboxtext,
244     MozCombobox,
245 
246     MozGtkInfoBarText,
247 
248     #[css(skip)]
249     End, // Just for array-indexing purposes.
250 }
251 
252 #[cfg(feature = "gecko")]
253 impl SystemColor {
254     #[inline]
compute(&self, cx: &Context) -> ComputedColor255     fn compute(&self, cx: &Context) -> ComputedColor {
256         use crate::gecko_bindings::bindings;
257 
258         let prefs = cx.device().pref_sheet_prefs();
259 
260         convert_nscolor_to_computedcolor(match *self {
261             SystemColor::Canvastext => prefs.mDefaultColor,
262             SystemColor::Canvas => prefs.mDefaultBackgroundColor,
263             SystemColor::Linktext => prefs.mLinkColor,
264             SystemColor::Activetext => prefs.mActiveLinkColor,
265             SystemColor::Visitedtext => prefs.mVisitedLinkColor,
266 
267             _ => unsafe {
268                 bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document())
269             },
270         })
271     }
272 }
273 
274 impl From<RGBA> for Color {
from(value: RGBA) -> Self275     fn from(value: RGBA) -> Self {
276         Color::rgba(value)
277     }
278 }
279 
280 struct ColorComponentParser<'a, 'b: 'a>(&'a ParserContext<'b>);
281 impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponentParser<'a, 'b> {
282     type Error = StyleParseErrorKind<'i>;
283 
parse_angle_or_number<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<AngleOrNumber, ParseError<'i>>284     fn parse_angle_or_number<'t>(
285         &self,
286         input: &mut Parser<'i, 't>,
287     ) -> Result<AngleOrNumber, ParseError<'i>> {
288         use crate::values::specified::Angle;
289 
290         let location = input.current_source_location();
291         let token = input.next()?.clone();
292         match token {
293             Token::Dimension {
294                 value, ref unit, ..
295             } => {
296                 let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false);
297 
298                 let degrees = match angle {
299                     Ok(angle) => angle.degrees(),
300                     Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
301                 };
302 
303                 Ok(AngleOrNumber::Angle { degrees })
304             },
305             Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
306             Token::Function(ref name) => {
307                 let function = CalcNode::math_function(name, location)?;
308                 CalcNode::parse_angle_or_number(self.0, input, function)
309             },
310             t => return Err(location.new_unexpected_token_error(t)),
311         }
312     }
313 
parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>314     fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
315         use crate::values::specified::Percentage;
316 
317         Ok(Percentage::parse(self.0, input)?.get())
318     }
319 
parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>320     fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
321         use crate::values::specified::Number;
322 
323         Ok(Number::parse(self.0, input)?.get())
324     }
325 
parse_number_or_percentage<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<NumberOrPercentage, ParseError<'i>>326     fn parse_number_or_percentage<'t>(
327         &self,
328         input: &mut Parser<'i, 't>,
329     ) -> Result<NumberOrPercentage, ParseError<'i>> {
330         let location = input.current_source_location();
331 
332         match *input.next()? {
333             Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
334             Token::Percentage { unit_value, .. } => {
335                 Ok(NumberOrPercentage::Percentage { unit_value })
336             },
337             Token::Function(ref name) => {
338                 let function = CalcNode::math_function(name, location)?;
339                 CalcNode::parse_number_or_percentage(self.0, input, function)
340             },
341             ref t => return Err(location.new_unexpected_token_error(t.clone())),
342         }
343     }
344 }
345 
346 impl Parse for Color {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>347     fn parse<'i, 't>(
348         context: &ParserContext,
349         input: &mut Parser<'i, 't>,
350     ) -> Result<Self, ParseError<'i>> {
351         // Currently we only store authored value for color keywords,
352         // because all browsers serialize those values as keywords for
353         // specified value.
354         let start = input.state();
355         let authored = input.expect_ident_cloned().ok();
356         input.reset(&start);
357 
358         let compontent_parser = ColorComponentParser(&*context);
359         match input.try(|i| CSSParserColor::parse_with(&compontent_parser, i)) {
360             Ok(value) => Ok(match value {
361                 CSSParserColor::CurrentColor => Color::CurrentColor,
362                 CSSParserColor::RGBA(rgba) => Color::Numeric {
363                     parsed: rgba,
364                     authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()),
365                 },
366             }),
367             Err(e) => {
368                 #[cfg(feature = "gecko")]
369                 {
370                     if let Ok(system) = input.try(|i| SystemColor::parse(context, i)) {
371                         return Ok(Color::System(system));
372                     }
373                 }
374 
375                 match e.kind {
376                     ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
377                         Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
378                             ValueParseErrorKind::InvalidColor(t),
379                         )))
380                     },
381                     _ => Err(e),
382                 }
383             },
384         }
385     }
386 }
387 
388 impl ToCss for Color {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,389     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
390     where
391         W: Write,
392     {
393         match *self {
394             Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
395             Color::Numeric {
396                 authored: Some(ref authored),
397                 ..
398             } => dest.write_str(authored),
399             Color::Numeric {
400                 parsed: ref rgba, ..
401             } => rgba.to_css(dest),
402             Color::Complex(_) => Ok(()),
403             #[cfg(feature = "gecko")]
404             Color::System(system) => system.to_css(dest),
405             #[cfg(feature = "gecko")]
406             Color::InheritFromBodyQuirk => Ok(()),
407         }
408     }
409 }
410 
411 /// A wrapper of cssparser::Color::parse_hash.
412 ///
413 /// That function should never return CurrentColor, so it makes no sense to
414 /// handle a cssparser::Color here. This should really be done in cssparser
415 /// directly rather than here.
parse_hash_color(value: &[u8]) -> Result<RGBA, ()>416 fn parse_hash_color(value: &[u8]) -> Result<RGBA, ()> {
417     CSSParserColor::parse_hash(value).map(|color| match color {
418         CSSParserColor::RGBA(rgba) => rgba,
419         CSSParserColor::CurrentColor => unreachable!("parse_hash should never return currentcolor"),
420     })
421 }
422 
423 impl Color {
424     /// Returns currentcolor value.
425     #[inline]
currentcolor() -> Color426     pub fn currentcolor() -> Color {
427         Color::CurrentColor
428     }
429 
430     /// Returns transparent value.
431     #[inline]
transparent() -> Color432     pub fn transparent() -> Color {
433         // We should probably set authored to "transparent", but maybe it doesn't matter.
434         Color::rgba(RGBA::transparent())
435     }
436 
437     /// Returns a numeric RGBA color value.
438     #[inline]
rgba(rgba: RGBA) -> Self439     pub fn rgba(rgba: RGBA) -> Self {
440         Color::Numeric {
441             parsed: rgba,
442             authored: None,
443         }
444     }
445 
446     /// Parse a color, with quirks.
447     ///
448     /// <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>>449     pub fn parse_quirky<'i, 't>(
450         context: &ParserContext,
451         input: &mut Parser<'i, 't>,
452         allow_quirks: AllowQuirks,
453     ) -> Result<Self, ParseError<'i>> {
454         input.try(|i| Self::parse(context, i)).or_else(|e| {
455             if !allow_quirks.allowed(context.quirks_mode) {
456                 return Err(e);
457             }
458             Color::parse_quirky_color(input)
459                 .map(Color::rgba)
460                 .map_err(|_| e)
461         })
462     }
463 
464     /// Parse a <quirky-color> value.
465     ///
466     /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>>467     fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>> {
468         let location = input.current_source_location();
469         let (value, unit) = match *input.next()? {
470             Token::Number {
471                 int_value: Some(integer),
472                 ..
473             } => (integer, None),
474             Token::Dimension {
475                 int_value: Some(integer),
476                 ref unit,
477                 ..
478             } => (integer, Some(unit)),
479             Token::Ident(ref ident) => {
480                 if ident.len() != 3 && ident.len() != 6 {
481                     return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
482                 }
483                 return parse_hash_color(ident.as_bytes()).map_err(|()| {
484                     location.new_custom_error(StyleParseErrorKind::UnspecifiedError)
485                 });
486             },
487             ref t => {
488                 return Err(location.new_unexpected_token_error(t.clone()));
489             },
490         };
491         if value < 0 {
492             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
493         }
494         let length = if value <= 9 {
495             1
496         } else if value <= 99 {
497             2
498         } else if value <= 999 {
499             3
500         } else if value <= 9999 {
501             4
502         } else if value <= 99999 {
503             5
504         } else if value <= 999999 {
505             6
506         } else {
507             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
508         };
509         let total = length + unit.as_ref().map_or(0, |d| d.len());
510         if total > 6 {
511             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
512         }
513         let mut serialization = [b'0'; 6];
514         let space_padding = 6 - total;
515         let mut written = space_padding;
516         written += itoa::write(&mut serialization[written..], value).unwrap();
517         if let Some(unit) = unit {
518             written += (&mut serialization[written..])
519                 .write(unit.as_bytes())
520                 .unwrap();
521         }
522         debug_assert_eq!(written, 6);
523         parse_hash_color(&serialization)
524             .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
525     }
526 
527     /// Returns true if the color is completely transparent, and false
528     /// otherwise.
is_transparent(&self) -> bool529     pub fn is_transparent(&self) -> bool {
530         match *self {
531             Color::Numeric { ref parsed, .. } => parsed.alpha == 0,
532             _ => false,
533         }
534     }
535 }
536 
537 #[cfg(feature = "gecko")]
convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor538 fn convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor {
539     use crate::gecko::values::convert_nscolor_to_rgba;
540     ComputedColor::rgba(convert_nscolor_to_rgba(color))
541 }
542 
543 impl Color {
544     /// Converts this Color into a ComputedColor.
545     ///
546     /// If `context` is `None`, and the specified color requires data from
547     /// the context to resolve, then `None` is returned.
to_computed_color(&self, _context: Option<&Context>) -> Option<ComputedColor>548     pub fn to_computed_color(&self, _context: Option<&Context>) -> Option<ComputedColor> {
549         Some(match *self {
550             Color::CurrentColor => ComputedColor::currentcolor(),
551             Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
552             Color::Complex(ref complex) => *complex,
553             #[cfg(feature = "gecko")]
554             Color::System(system) => system.compute(_context?),
555             #[cfg(feature = "gecko")]
556             Color::InheritFromBodyQuirk => {
557                 ComputedColor::rgba(_context?.device().body_text_color())
558             },
559         })
560     }
561 }
562 
563 impl ToComputedValue for Color {
564     type ComputedValue = ComputedColor;
565 
to_computed_value(&self, context: &Context) -> ComputedColor566     fn to_computed_value(&self, context: &Context) -> ComputedColor {
567         self.to_computed_color(Some(context)).unwrap()
568     }
569 
from_computed_value(computed: &ComputedColor) -> Self570     fn from_computed_value(computed: &ComputedColor) -> Self {
571         match *computed {
572             GenericColor::Numeric(color) => Color::rgba(color),
573             GenericColor::CurrentColor => Color::currentcolor(),
574             GenericColor::Complex { .. } => Color::Complex(*computed),
575         }
576     }
577 }
578 
579 /// Specified color value for `-moz-font-smoothing-background-color`.
580 ///
581 /// This property does not support `currentcolor`. We could drop it at
582 /// parse-time, but it's not exposed to the web so it doesn't really matter.
583 ///
584 /// We resolve it to `transparent` instead.
585 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
586 pub struct MozFontSmoothingBackgroundColor(pub Color);
587 
588 impl Parse for MozFontSmoothingBackgroundColor {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>589     fn parse<'i, 't>(
590         context: &ParserContext,
591         input: &mut Parser<'i, 't>,
592     ) -> Result<Self, ParseError<'i>> {
593         Color::parse(context, input).map(MozFontSmoothingBackgroundColor)
594     }
595 }
596 
597 impl ToComputedValue for MozFontSmoothingBackgroundColor {
598     type ComputedValue = RGBA;
599 
to_computed_value(&self, context: &Context) -> RGBA600     fn to_computed_value(&self, context: &Context) -> RGBA {
601         self.0
602             .to_computed_value(context)
603             .to_rgba(RGBA::transparent())
604     }
605 
from_computed_value(computed: &RGBA) -> Self606     fn from_computed_value(computed: &RGBA) -> Self {
607         MozFontSmoothingBackgroundColor(Color::rgba(*computed))
608     }
609 }
610 
611 impl SpecifiedValueInfo for Color {
612     const SUPPORTED_TYPES: u8 = CssType::COLOR;
613 
collect_completion_keywords(f: KeywordsCollectFn)614     fn collect_completion_keywords(f: KeywordsCollectFn) {
615         // We are not going to insert all the color names here. Caller and
616         // devtools should take care of them. XXX Actually, transparent
617         // should probably be handled that way as well.
618         // XXX `currentColor` should really be `currentcolor`. But let's
619         // keep it consistent with the old system for now.
620         f(&["rgb", "rgba", "hsl", "hsla", "currentColor", "transparent"]);
621     }
622 }
623 
624 /// Specified value for the "color" property, which resolves the `currentcolor`
625 /// keyword to the parent color instead of self's color.
626 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
627 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
628 pub struct ColorPropertyValue(pub Color);
629 
630 impl ToComputedValue for ColorPropertyValue {
631     type ComputedValue = RGBA;
632 
633     #[inline]
to_computed_value(&self, context: &Context) -> RGBA634     fn to_computed_value(&self, context: &Context) -> RGBA {
635         self.0
636             .to_computed_value(context)
637             .to_rgba(context.builder.get_parent_inherited_text().clone_color())
638     }
639 
640     #[inline]
from_computed_value(computed: &RGBA) -> Self641     fn from_computed_value(computed: &RGBA) -> Self {
642         ColorPropertyValue(Color::rgba(*computed).into())
643     }
644 }
645 
646 impl Parse for ColorPropertyValue {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>647     fn parse<'i, 't>(
648         context: &ParserContext,
649         input: &mut Parser<'i, 't>,
650     ) -> Result<Self, ParseError<'i>> {
651         Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
652     }
653 }
654 
655 /// auto | <color>
656 pub type ColorOrAuto = GenericColorOrAuto<Color>;
657