1use super::AllowQuirks;
8use crate::color::mix::ColorInterpolationMethod;
9use crate::color::{parsing, AbsoluteColor, ColorFunction, ColorMixItemList, ColorSpace};
10use crate::derives::*;
11use crate::device::Device;
12use crate::parser::{Parse, ParserContext};
13use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
14use crate::values::generics::color::{
15 ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorMixItem, GenericColorOrAuto,
16 GenericLightDark,
17};
18use crate::values::specified::percentage::ToPercentage;
19use crate::values::specified::Percentage;
20use crate::values::{normalize, CustomIdent};
21use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, ParseErrorKind, Parser, Token};
22use std::fmt::{self, Write};
23use std::io::Write as IoWrite;
24use style_traits::{
25 owned_slice::OwnedSlice, CssType, CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo,
26 StyleParseErrorKind, ToCss, ValueParseErrorKind,
27};
28
29pub type ColorMix = GenericColorMix<Color, Percentage>;
31
32impl ColorMix {
33 fn parse<'i, 't>(
34 context: &ParserContext,
35 input: &mut Parser<'i, 't>,
36 preserve_authored: PreserveAuthored,
37 ) -> Result<Self, ParseError<'i>> {
38 input.expect_function_matching("color-mix")?;
39
40 input.parse_nested_block(|input| {
41 let interpolation = input
44 .try_parse(|input| -> Result<_, ParseError<'i>> {
45 let interpolation = ColorInterpolationMethod::parse(context, input)?;
46 input.expect_comma()?;
47 Ok(interpolation)
48 })
49 .unwrap_or_default();
50
51 let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
52 input
53 .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
54 .ok()
55 };
56
57 let allow_multiple_items =
58 static_prefs::pref!("layout.css.color-mix-multi-color.enabled");
59
60 let mut items = ColorMixItemList::default();
61
62 loop {
63 let mut percentage = try_parse_percentage(input);
64
65 let color = Color::parse_internal(context, input, preserve_authored)?;
66
67 if percentage.is_none() {
68 percentage = try_parse_percentage(input);
69 }
70
71 items.push((color, percentage));
72
73 if input.try_parse(|i| i.expect_comma()).is_err() {
74 break;
75 }
76
77 if !allow_multiple_items && items.len() == 2 {
79 break;
80 }
81 }
82
83 let min_item_count = if allow_multiple_items { 1 } else { 2 };
86 if items.len() < min_item_count {
87 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
88 }
89
90 let (mut sum_specified, mut missing) = (0.0, 0);
93 for (_, percentage) in items.iter() {
94 if let Some(p) = percentage {
95 sum_specified += p.to_percentage();
96 } else {
97 missing += 1;
98 }
99 }
100
101 let default_for_missing_items = match missing {
102 0 => None,
103 m if m == items.len() => Some(Percentage::new(1.0 / items.len() as f32)),
104 m => Some(Percentage::new((1.0 - sum_specified) / m as f32)),
105 };
106
107 if let Some(default) = default_for_missing_items {
108 for (_, percentage) in items.iter_mut() {
109 if percentage.is_none() {
110 *percentage = Some(default);
111 }
112 }
113 }
114
115 let mut total = 0.0;
116 let finalized = items
117 .into_iter()
118 .map(|(color, percentage)| {
119 let percentage = percentage.expect("percentage filled above");
120 total += percentage.to_percentage();
121 GenericColorMixItem { color, percentage }
122 })
123 .collect::<ColorMixItemList<_>>();
124
125 if total <= 0.0 {
126 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
127 }
128
129 Ok(ColorMix {
133 interpolation,
134 items: OwnedSlice::from_slice(&finalized),
135 flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
136 })
137 })
138 }
139}
140
141#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
143pub struct Absolute {
144 pub color: AbsoluteColor,
146 pub authored: Option<Box<str>>,
148}
149
150impl ToCss for Absolute {
151 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
152 where
153 W: Write,
154 {
155 if let Some(ref authored) = self.authored {
156 dest.write_str(authored)
157 } else {
158 self.color.to_css(dest)
159 }
160 }
161}
162
163#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)]
165#[typed(todo_derive_fields)]
166pub enum Color {
167 CurrentColor,
169 Absolute(Box<Absolute>),
172 ColorFunction(Box<ColorFunction<Self>>),
175 System(SystemColor),
177 ColorMix(Box<ColorMix>),
179 LightDark(Box<GenericLightDark<Self>>),
181 ContrastColor(Box<Color>),
183 InheritFromBodyQuirk,
185}
186
187impl From<AbsoluteColor> for Color {
188 #[inline]
189 fn from(value: AbsoluteColor) -> Self {
190 Self::from_absolute_color(value)
191 }
192}
193
194#[allow(missing_docs)]
203#[cfg(feature = "gecko")]
204#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
205#[repr(u8)]
206pub enum SystemColor {
207 Activeborder,
208 Activecaption,
210 Appworkspace,
211 Background,
212 Buttonface,
213 Buttonhighlight,
214 Buttonshadow,
215 Buttontext,
216 Buttonborder,
217 Captiontext,
219 #[parse(aliases = "-moz-field")]
220 Field,
221 #[parse(condition = "ParserContext::chrome_rules_enabled")]
223 MozDisabledfield,
224 #[parse(aliases = "-moz-fieldtext")]
225 Fieldtext,
226
227 Mark,
228 Marktext,
229
230 MozComboboxtext,
232 MozCombobox,
233
234 Graytext,
235 Highlight,
236 Highlighttext,
237 Inactiveborder,
238 Inactivecaption,
240 Inactivecaptiontext,
242 Infobackground,
243 Infotext,
244 Menu,
245 Menutext,
246 Scrollbar,
247 Threeddarkshadow,
248 Threedface,
249 Threedhighlight,
250 Threedlightshadow,
251 Threedshadow,
252 Window,
253 Windowframe,
254 Windowtext,
255 #[parse(aliases = "-moz-default-color")]
256 Canvastext,
257 #[parse(aliases = "-moz-default-background-color")]
258 Canvas,
259 MozDialog,
260 MozDialogtext,
261 #[parse(aliases = "-moz-html-cellhighlight")]
263 MozCellhighlight,
264 #[parse(aliases = "-moz-html-cellhighlighttext")]
266 MozCellhighlighttext,
267 Selecteditem,
269 Selecteditemtext,
271 MozMenuhover,
273 #[parse(condition = "ParserContext::chrome_rules_enabled")]
275 MozMenuhoverdisabled,
276 MozMenuhovertext,
278 MozMenubarhovertext,
280
281 MozOddtreerow,
284
285 #[parse(condition = "ParserContext::chrome_rules_enabled")]
287 MozButtonhoverface,
288 #[parse(condition = "ParserContext::chrome_rules_enabled")]
290 MozButtonhovertext,
291 #[parse(condition = "ParserContext::chrome_rules_enabled")]
293 MozButtonhoverborder,
294 #[parse(condition = "ParserContext::chrome_rules_enabled")]
296 MozButtonactiveface,
297 #[parse(condition = "ParserContext::chrome_rules_enabled")]
299 MozButtonactivetext,
300 #[parse(condition = "ParserContext::chrome_rules_enabled")]
302 MozButtonactiveborder,
303
304 #[parse(condition = "ParserContext::chrome_rules_enabled")]
306 MozButtondisabledface,
307 #[parse(condition = "ParserContext::chrome_rules_enabled")]
309 MozButtondisabledborder,
310
311 #[parse(condition = "ParserContext::chrome_rules_enabled")]
313 MozHeaderbar,
314 #[parse(condition = "ParserContext::chrome_rules_enabled")]
315 MozHeaderbartext,
316 #[parse(condition = "ParserContext::chrome_rules_enabled")]
317 MozHeaderbarinactive,
318 #[parse(condition = "ParserContext::chrome_rules_enabled")]
319 MozHeaderbarinactivetext,
320
321 #[parse(condition = "ParserContext::chrome_rules_enabled")]
323 MozMacDefaultbuttontext,
324 #[parse(condition = "ParserContext::chrome_rules_enabled")]
326 MozMacFocusring,
327 #[parse(condition = "ParserContext::chrome_rules_enabled")]
329 MozMacDisabledtoolbartext,
330 #[parse(condition = "ParserContext::chrome_rules_enabled")]
332 MozSidebar,
333 #[parse(condition = "ParserContext::chrome_rules_enabled")]
335 MozSidebartext,
336 #[parse(condition = "ParserContext::chrome_rules_enabled")]
338 MozSidebarborder,
339
340 Accentcolor,
343
344 Accentcolortext,
347
348 #[parse(condition = "ParserContext::chrome_rules_enabled")]
350 MozAutofillBackground,
351
352 #[parse(aliases = "-moz-hyperlinktext")]
353 Linktext,
354 #[parse(aliases = "-moz-activehyperlinktext")]
355 Activetext,
356 #[parse(aliases = "-moz-visitedhyperlinktext")]
357 Visitedtext,
358
359 #[parse(condition = "ParserContext::chrome_rules_enabled")]
361 MozColheader,
362 #[parse(condition = "ParserContext::chrome_rules_enabled")]
363 MozColheadertext,
364 #[parse(condition = "ParserContext::chrome_rules_enabled")]
365 MozColheaderhover,
366 #[parse(condition = "ParserContext::chrome_rules_enabled")]
367 MozColheaderhovertext,
368 #[parse(condition = "ParserContext::chrome_rules_enabled")]
369 MozColheaderactive,
370 #[parse(condition = "ParserContext::chrome_rules_enabled")]
371 MozColheaderactivetext,
372
373 #[parse(condition = "ParserContext::chrome_rules_enabled")]
374 TextSelectDisabledBackground,
375 #[css(skip)]
376 TextSelectAttentionBackground,
377 #[css(skip)]
378 TextSelectAttentionForeground,
379 #[css(skip)]
380 TextHighlightBackground,
381 #[css(skip)]
382 TextHighlightForeground,
383 #[css(skip)]
384 TargetTextBackground,
385 #[css(skip)]
386 TargetTextForeground,
387 #[css(skip)]
388 IMERawInputBackground,
389 #[css(skip)]
390 IMERawInputForeground,
391 #[css(skip)]
392 IMERawInputUnderline,
393 #[css(skip)]
394 IMESelectedRawTextBackground,
395 #[css(skip)]
396 IMESelectedRawTextForeground,
397 #[css(skip)]
398 IMESelectedRawTextUnderline,
399 #[css(skip)]
400 IMEConvertedTextBackground,
401 #[css(skip)]
402 IMEConvertedTextForeground,
403 #[css(skip)]
404 IMEConvertedTextUnderline,
405 #[css(skip)]
406 IMESelectedConvertedTextBackground,
407 #[css(skip)]
408 IMESelectedConvertedTextForeground,
409 #[css(skip)]
410 IMESelectedConvertedTextUnderline,
411 #[css(skip)]
412 SpellCheckerUnderline,
413 #[css(skip)]
414 ThemedScrollbar,
415 #[css(skip)]
416 ThemedScrollbarThumb,
417 #[css(skip)]
418 ThemedScrollbarThumbHover,
419 #[css(skip)]
420 ThemedScrollbarThumbActive,
421
422 #[css(skip)]
423 End, }
425
426#[cfg(feature = "gecko")]
427impl SystemColor {
428 #[inline]
429 fn compute(&self, cx: &Context) -> ComputedColor {
430 use crate::gecko_bindings::bindings;
431
432 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
433 if cx.for_non_inherited_property {
434 cx.rule_cache_conditions
435 .borrow_mut()
436 .set_color_scheme_dependency(cx.builder.color_scheme);
437 }
438 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
439 return ComputedColor::currentcolor();
440 }
441 ComputedColor::Absolute(AbsoluteColor::from_nscolor(color))
442 }
443}
444
445#[allow(missing_docs)]
454#[cfg(feature = "servo")]
455#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
456#[repr(u8)]
457pub enum SystemColor {
458 Accentcolor,
459 Accentcolortext,
460 Activetext,
461 Linktext,
462 Visitedtext,
463 Buttonborder,
464 Buttonface,
465 Buttontext,
466 Canvas,
467 Canvastext,
468 Field,
469 Fieldtext,
470 Graytext,
471 Highlight,
472 Highlighttext,
473 Mark,
474 Marktext,
475 Selecteditem,
476 Selecteditemtext,
477
478 Activeborder,
480 Inactiveborder,
481 Threeddarkshadow,
482 Threedhighlight,
483 Threedlightshadow,
484 Threedshadow,
485 Windowframe,
486 Buttonhighlight,
487 Buttonshadow,
488 Threedface,
489 Activecaption,
490 Appworkspace,
491 Background,
492 Inactivecaption,
493 Infobackground,
494 Menu,
495 Scrollbar,
496 Window,
497 Captiontext,
498 Infotext,
499 Menutext,
500 Windowtext,
501 Inactivecaptiontext,
502}
503
504#[cfg(feature = "servo")]
505impl SystemColor {
506 #[inline]
507 fn compute(&self, cx: &Context) -> ComputedColor {
508 if cx.for_non_inherited_property {
509 cx.rule_cache_conditions
510 .borrow_mut()
511 .set_color_scheme_dependency(cx.builder.color_scheme);
512 }
513
514 ComputedColor::Absolute(cx.device().system_color(*self, cx.builder.color_scheme))
515 }
516}
517
518#[derive(Copy, Clone)]
521enum PreserveAuthored {
522 No,
523 Yes,
524}
525
526impl Parse for Color {
527 fn parse<'i, 't>(
528 context: &ParserContext,
529 input: &mut Parser<'i, 't>,
530 ) -> Result<Self, ParseError<'i>> {
531 Self::parse_internal(context, input, PreserveAuthored::Yes)
532 }
533}
534
535impl Color {
536 fn parse_internal<'i, 't>(
537 context: &ParserContext,
538 input: &mut Parser<'i, 't>,
539 preserve_authored: PreserveAuthored,
540 ) -> Result<Self, ParseError<'i>> {
541 let authored = match preserve_authored {
542 PreserveAuthored::No => None,
543 PreserveAuthored::Yes => {
544 let start = input.state();
548 let authored = input.expect_ident_cloned().ok();
549 input.reset(&start);
550 authored
551 },
552 };
553
554 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
555 Ok(mut color) => {
556 if let Color::Absolute(ref mut absolute) = color {
557 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
560 }
561 Ok(color)
562 },
563 Err(e) => {
564 {
565 #[cfg(feature = "gecko")]
566 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
567 return Ok(Color::System(system));
568 }
569 #[cfg(feature = "servo")]
570 if let Ok(system) = input.try_parse(SystemColor::parse) {
571 return Ok(Color::System(system));
572 }
573 }
574 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
575 {
576 return Ok(Color::ColorMix(Box::new(mix)));
577 }
578
579 if let Ok(ld) = input.try_parse(|i| {
580 GenericLightDark::parse_with(i, |i| {
581 Self::parse_internal(context, i, preserve_authored)
582 })
583 }) {
584 return Ok(Color::LightDark(Box::new(ld)));
585 }
586
587 if static_prefs::pref!("layout.css.contrast-color.enabled") {
588 if let Ok(c) = input.try_parse(|i| {
589 i.expect_function_matching("contrast-color")?;
590 i.parse_nested_block(|i| {
591 Self::parse_internal(context, i, preserve_authored)
592 })
593 }) {
594 return Ok(Color::ContrastColor(Box::new(c)));
595 }
596 }
597
598 match e.kind {
599 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
600 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
601 ValueParseErrorKind::InvalidColor(t),
602 )))
603 },
604 _ => Err(e),
605 }
606 },
607 }
608 }
609
610 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
612 input
613 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
614 .is_ok()
615 }
616
617 pub fn parse_and_compute(
619 context: &ParserContext,
620 input: &mut Parser,
621 device: Option<&Device>,
622 ) -> Result<ComputedColor, ()> {
623 use crate::error_reporting::ContextualParseError;
624 let start = input.position();
625 let result = input
626 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
627
628 let specified = match result {
629 Ok(s) => s,
630 Err(e) => {
631 if context.error_reporting_enabled() {
632 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
640 let location = e.location.clone();
641 let error =
642 ContextualParseError::UnsupportedValue(input.slice_from(start), e);
643 context.log_css_error(location, error);
644 }
645 }
646 return Err(());
647 },
648 };
649
650 match device {
651 Some(device) => {
652 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
653 specified.to_computed_color(Some(&context))
654 })
655 },
656 None => specified.to_computed_color(None),
657 }
658 }
659}
660
661impl ToCss for Color {
662 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
663 where
664 W: Write,
665 {
666 match *self {
667 Color::CurrentColor => dest.write_str("currentcolor"),
668 Color::Absolute(ref absolute) => absolute.to_css(dest),
669 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
670 Color::ColorMix(ref mix) => mix.to_css(dest),
671 Color::LightDark(ref ld) => ld.to_css(dest),
672 Color::ContrastColor(ref c) => {
673 dest.write_str("contrast-color(")?;
674 c.to_css(dest)?;
675 dest.write_char(')')
676 },
677 Color::System(system) => system.to_css(dest),
678 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
679 }
680 }
681}
682
683impl Color {
684 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
686 match *self {
687 Self::InheritFromBodyQuirk => false,
688 Self::CurrentColor => true,
689 Self::System(..) => true,
690 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
691 Self::ColorFunction(ref color_function) => {
692 color_function
695 .resolve_to_absolute()
696 .map(|resolved| allow_transparent && resolved.is_transparent())
697 .unwrap_or(false)
698 },
699 Self::LightDark(ref ld) => {
700 ld.light.honored_in_forced_colors_mode(allow_transparent)
701 && ld.dark.honored_in_forced_colors_mode(allow_transparent)
702 },
703 Self::ColorMix(ref mix) => mix
704 .items
705 .iter()
706 .all(|item| item.color.honored_in_forced_colors_mode(allow_transparent)),
707 Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent),
708 }
709 }
710
711 #[inline]
713 pub fn currentcolor() -> Self {
714 Self::CurrentColor
715 }
716
717 #[inline]
719 pub fn transparent() -> Self {
720 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
722 }
723
724 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
726 Color::Absolute(Box::new(Absolute {
727 color,
728 authored: None,
729 }))
730 }
731
732 pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
737 use crate::values::specified::percentage::ToPercentage;
738
739 match self {
740 Self::Absolute(c) => Ok(c.color),
741 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute(),
742 Self::ColorMix(ref mix) => {
743 use crate::color::mix;
744
745 let mut items = ColorMixItemList::with_capacity(mix.items.len());
746 for item in mix.items.iter() {
747 items.push(mix::ColorMixItem::new(
748 item.color.resolve_to_absolute()?,
749 item.percentage.to_percentage(),
750 ))
751 }
752
753 Ok(mix::mix_many(mix.interpolation, items, mix.flags))
754 },
755 _ => Err(()),
756 }
757 }
758
759 pub fn parse_quirky<'i, 't>(
763 context: &ParserContext,
764 input: &mut Parser<'i, 't>,
765 allow_quirks: AllowQuirks,
766 ) -> Result<Self, ParseError<'i>> {
767 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
768 if !allow_quirks.allowed(context.quirks_mode) {
769 return Err(e);
770 }
771 Color::parse_quirky_color(input).map_err(|_| e)
772 })
773 }
774
775 fn parse_hash<'i>(
776 bytes: &[u8],
777 loc: &cssparser::SourceLocation,
778 ) -> Result<Self, ParseError<'i>> {
779 match cssparser::color::parse_hash_color(bytes) {
780 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
781 r, g, b, a,
782 ))),
783 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
784 }
785 }
786
787 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
791 let location = input.current_source_location();
792 let (value, unit) = match *input.next()? {
793 Token::Number {
794 int_value: Some(integer),
795 ..
796 } => (integer, None),
797 Token::Dimension {
798 int_value: Some(integer),
799 ref unit,
800 ..
801 } => (integer, Some(unit)),
802 Token::Ident(ref ident) => {
803 if ident.len() != 3 && ident.len() != 6 {
804 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
805 }
806 return Self::parse_hash(ident.as_bytes(), &location);
807 },
808 ref t => {
809 return Err(location.new_unexpected_token_error(t.clone()));
810 },
811 };
812 if value < 0 {
813 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
814 }
815 let length = if value <= 9 {
816 1
817 } else if value <= 99 {
818 2
819 } else if value <= 999 {
820 3
821 } else if value <= 9999 {
822 4
823 } else if value <= 99999 {
824 5
825 } else if value <= 999999 {
826 6
827 } else {
828 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
829 };
830 let total = length + unit.as_ref().map_or(0, |d| d.len());
831 if total > 6 {
832 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
833 }
834 let mut serialization = [b'0'; 6];
835 let space_padding = 6 - total;
836 let mut written = space_padding;
837 let mut buf = itoa::Buffer::new();
838 let s = buf.format(value);
839 (&mut serialization[written..])
840 .write_all(s.as_bytes())
841 .unwrap();
842 written += s.len();
843 if let Some(unit) = unit {
844 written += (&mut serialization[written..])
845 .write(unit.as_bytes())
846 .unwrap();
847 }
848 debug_assert_eq!(written, 6);
849 Self::parse_hash(&serialization, &location)
850 }
851}
852
853impl Color {
854 pub fn to_computed_color(&self, context: Option<&Context>) -> Result<ComputedColor, ()> {
859 macro_rules! adjust_absolute_color {
860 ($color:expr) => {{
861 if matches!(
863 $color.color_space,
864 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
865 ) {
866 $color.components.0 = normalize($color.components.0);
867 }
868
869 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
871 $color.components = $color.components.map(normalize);
872 }
873
874 $color.alpha = normalize($color.alpha);
875 }};
876 }
877
878 Ok(match *self {
879 Color::CurrentColor => ComputedColor::CurrentColor,
880 Color::Absolute(ref absolute) => {
881 let mut color = absolute.color;
882 adjust_absolute_color!(color);
883 ComputedColor::Absolute(color)
884 },
885 Color::ColorFunction(ref color_function) => {
886 debug_assert!(color_function.has_origin_color(),
887 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
888
889 if let Ok(absolute) = color_function.resolve_to_absolute() {
891 ComputedColor::Absolute(absolute)
892 } else {
893 let color_function = color_function
894 .map_origin_color(|origin_color| origin_color.to_computed_color(context))?;
895 ComputedColor::ColorFunction(Box::new(color_function))
896 }
897 },
898 Color::LightDark(ref ld) => ld.compute(context.ok_or(())?),
899 Color::ColorMix(ref mix) => {
900 use crate::values::computed::percentage::Percentage;
901
902 let mut items = ColorMixItemList::with_capacity(mix.items.len());
903 for item in mix.items.iter() {
904 items.push(GenericColorMixItem {
905 color: item.color.to_computed_color(context)?,
906 percentage: Percentage(item.percentage.get()),
907 });
908 }
909
910 ComputedColor::from_color_mix(GenericColorMix {
911 interpolation: mix.interpolation,
912 items: OwnedSlice::from_slice(items.as_slice()),
913 flags: mix.flags,
914 })
915 },
916 Color::ContrastColor(ref c) => {
917 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
918 },
919 Color::System(system) => system.compute(context.ok_or(())?),
920 Color::InheritFromBodyQuirk => {
921 ComputedColor::Absolute(context.ok_or(())?.device().body_text_color())
922 },
923 })
924 }
925}
926
927impl ToComputedValue for Color {
928 type ComputedValue = ComputedColor;
929
930 fn to_computed_value(&self, context: &Context) -> ComputedColor {
931 self.to_computed_color(Some(context)).unwrap_or_else(|_| {
932 debug_assert!(
933 false,
934 "Specified color could not be resolved to a computed color!"
935 );
936 ComputedColor::Absolute(AbsoluteColor::BLACK)
937 })
938 }
939
940 fn from_computed_value(computed: &ComputedColor) -> Self {
941 match *computed {
942 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
943 ComputedColor::ColorFunction(ref color_function) => {
944 let color_function = color_function
945 .map_origin_color(|o| Ok(Self::from_computed_value(o)))
946 .unwrap();
947 Self::ColorFunction(Box::new(color_function))
948 },
949 ComputedColor::CurrentColor => Color::CurrentColor,
950 ComputedColor::ColorMix(ref mix) => {
951 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
952 },
953 ComputedColor::ContrastColor(ref c) => {
954 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
955 },
956 }
957 }
958}
959
960impl SpecifiedValueInfo for Color {
961 const SUPPORTED_TYPES: u8 = CssType::COLOR;
962
963 fn collect_completion_keywords(f: KeywordsCollectFn) {
964 f(&[
970 "currentColor",
971 "transparent",
972 "rgb",
973 "rgba",
974 "hsl",
975 "hsla",
976 "hwb",
977 "color",
978 "lab",
979 "lch",
980 "oklab",
981 "oklch",
982 "color-mix",
983 "contrast-color",
984 "light-dark",
985 ]);
986 }
987}
988
989#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
992#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
993pub struct ColorPropertyValue(pub Color);
994
995impl ToComputedValue for ColorPropertyValue {
996 type ComputedValue = AbsoluteColor;
997
998 #[inline]
999 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1000 let current_color = context.builder.get_parent_inherited_text().clone_color();
1001 self.0
1002 .to_computed_value(context)
1003 .resolve_to_absolute(¤t_color)
1004 }
1005
1006 #[inline]
1007 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1008 ColorPropertyValue(Color::from_absolute_color(*computed).into())
1009 }
1010}
1011
1012impl Parse for ColorPropertyValue {
1013 fn parse<'i, 't>(
1014 context: &ParserContext,
1015 input: &mut Parser<'i, 't>,
1016 ) -> Result<Self, ParseError<'i>> {
1017 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
1018 }
1019}
1020
1021pub type ColorOrAuto = GenericColorOrAuto<Color>;
1023
1024pub type CaretColor = GenericCaretColor<Color>;
1026
1027impl Parse for CaretColor {
1028 fn parse<'i, 't>(
1029 context: &ParserContext,
1030 input: &mut Parser<'i, 't>,
1031 ) -> Result<Self, ParseError<'i>> {
1032 ColorOrAuto::parse(context, input).map(GenericCaretColor)
1033 }
1034}
1035
1036#[derive(
1039 Clone,
1040 Copy,
1041 Debug,
1042 Default,
1043 Eq,
1044 MallocSizeOf,
1045 PartialEq,
1046 SpecifiedValueInfo,
1047 ToComputedValue,
1048 ToResolvedValue,
1049 ToShmem,
1050)]
1051#[repr(C)]
1052#[value_info(other_values = "light,dark,only")]
1053pub struct ColorSchemeFlags(u8);
1054bitflags! {
1055 impl ColorSchemeFlags: u8 {
1056 const LIGHT = 1 << 0;
1058 const DARK = 1 << 1;
1060 const ONLY = 1 << 2;
1062 }
1063}
1064
1065#[derive(
1067 Clone,
1068 Debug,
1069 Default,
1070 MallocSizeOf,
1071 PartialEq,
1072 SpecifiedValueInfo,
1073 ToComputedValue,
1074 ToResolvedValue,
1075 ToShmem,
1076 ToTyped,
1077)]
1078#[repr(C)]
1079#[typed(todo_derive_fields)]
1080#[value_info(other_values = "normal")]
1081pub struct ColorScheme {
1082 #[ignore_malloc_size_of = "Arc"]
1083 idents: crate::ArcSlice<CustomIdent>,
1084 pub bits: ColorSchemeFlags,
1086}
1087
1088impl ColorScheme {
1089 pub fn normal() -> Self {
1091 Self {
1092 idents: Default::default(),
1093 bits: ColorSchemeFlags::empty(),
1094 }
1095 }
1096
1097 pub fn raw_bits(&self) -> u8 {
1099 self.bits.bits()
1100 }
1101}
1102
1103impl Parse for ColorScheme {
1104 fn parse<'i, 't>(
1105 _: &ParserContext,
1106 input: &mut Parser<'i, 't>,
1107 ) -> Result<Self, ParseError<'i>> {
1108 let mut idents = vec![];
1109 let mut bits = ColorSchemeFlags::empty();
1110
1111 let mut location = input.current_source_location();
1112 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
1113 let mut is_only = false;
1114 match_ignore_ascii_case! { &ident,
1115 "normal" => {
1116 if idents.is_empty() && bits.is_empty() {
1117 return Ok(Self::normal());
1118 }
1119 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1120 },
1121 "light" => bits.insert(ColorSchemeFlags::LIGHT),
1122 "dark" => bits.insert(ColorSchemeFlags::DARK),
1123 "only" => {
1124 if bits.intersects(ColorSchemeFlags::ONLY) {
1125 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1126 }
1127 bits.insert(ColorSchemeFlags::ONLY);
1128 is_only = true;
1129 },
1130 _ => {},
1131 };
1132
1133 if is_only {
1134 if !idents.is_empty() {
1135 break;
1138 }
1139 } else {
1140 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
1141 }
1142 location = input.current_source_location();
1143 }
1144
1145 if idents.is_empty() {
1146 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1147 }
1148
1149 Ok(Self {
1150 idents: crate::ArcSlice::from_iter(idents.into_iter()),
1151 bits,
1152 })
1153 }
1154}
1155
1156impl ToCss for ColorScheme {
1157 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1158 where
1159 W: Write,
1160 {
1161 if self.idents.is_empty() {
1162 debug_assert!(self.bits.is_empty());
1163 return dest.write_str("normal");
1164 }
1165 let mut first = true;
1166 for ident in self.idents.iter() {
1167 if !first {
1168 dest.write_char(' ')?;
1169 }
1170 first = false;
1171 ident.to_css(dest)?;
1172 }
1173 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1174 dest.write_str(" only")?;
1175 }
1176 Ok(())
1177 }
1178}
1179
1180#[derive(
1182 Clone,
1183 Copy,
1184 Debug,
1185 MallocSizeOf,
1186 Parse,
1187 PartialEq,
1188 SpecifiedValueInfo,
1189 ToCss,
1190 ToComputedValue,
1191 ToResolvedValue,
1192 ToShmem,
1193 ToTyped,
1194)]
1195#[repr(u8)]
1196pub enum PrintColorAdjust {
1197 Economy,
1199 Exact,
1201}
1202
1203#[derive(
1205 Clone,
1206 Copy,
1207 Debug,
1208 MallocSizeOf,
1209 Parse,
1210 PartialEq,
1211 SpecifiedValueInfo,
1212 ToCss,
1213 ToComputedValue,
1214 ToResolvedValue,
1215 ToShmem,
1216 ToTyped,
1217)]
1218#[repr(u8)]
1219pub enum ForcedColorAdjust {
1220 Auto,
1222 None,
1224}
1225
1226#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1229#[repr(u8)]
1230pub enum ForcedColors {
1231 None,
1233 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1235 Requested,
1236 Active,
1238}
1239
1240impl ForcedColors {
1241 pub fn is_active(self) -> bool {
1243 matches!(self, Self::Active)
1244 }
1245}