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