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)]
165pub enum Color {
166 CurrentColor,
168 Absolute(Box<Absolute>),
171 ColorFunction(Box<ColorFunction<Self>>),
174 System(SystemColor),
176 ColorMix(Box<ColorMix>),
178 LightDark(Box<GenericLightDark<Self>>),
180 ContrastColor(Box<Color>),
182 InheritFromBodyQuirk,
184}
185
186impl From<AbsoluteColor> for Color {
187 #[inline]
188 fn from(value: AbsoluteColor) -> Self {
189 Self::from_absolute_color(value)
190 }
191}
192
193#[allow(missing_docs)]
202#[cfg(feature = "gecko")]
203#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
204#[repr(u8)]
205pub enum SystemColor {
206 Activeborder,
207 Activecaption,
209 Appworkspace,
210 Background,
211 Buttonface,
212 Buttonhighlight,
213 Buttonshadow,
214 Buttontext,
215 Buttonborder,
216 Captiontext,
218 #[parse(aliases = "-moz-field")]
219 Field,
220 #[parse(condition = "ParserContext::chrome_rules_enabled")]
222 MozDisabledfield,
223 #[parse(aliases = "-moz-fieldtext")]
224 Fieldtext,
225
226 Mark,
227 Marktext,
228
229 MozComboboxtext,
231 MozCombobox,
232
233 Graytext,
234 Highlight,
235 Highlighttext,
236 Inactiveborder,
237 Inactivecaption,
239 Inactivecaptiontext,
241 Infobackground,
242 Infotext,
243 Menu,
244 Menutext,
245 Scrollbar,
246 Threeddarkshadow,
247 Threedface,
248 Threedhighlight,
249 Threedlightshadow,
250 Threedshadow,
251 Window,
252 Windowframe,
253 Windowtext,
254 #[parse(aliases = "-moz-default-color")]
255 Canvastext,
256 #[parse(aliases = "-moz-default-background-color")]
257 Canvas,
258 MozDialog,
259 MozDialogtext,
260 #[parse(aliases = "-moz-html-cellhighlight")]
262 MozCellhighlight,
263 #[parse(aliases = "-moz-html-cellhighlighttext")]
265 MozCellhighlighttext,
266 Selecteditem,
268 Selecteditemtext,
270 MozMenuhover,
272 #[parse(condition = "ParserContext::chrome_rules_enabled")]
274 MozMenuhoverdisabled,
275 MozMenuhovertext,
277 MozMenubarhovertext,
279
280 MozOddtreerow,
283
284 #[parse(condition = "ParserContext::chrome_rules_enabled")]
286 MozButtonhoverface,
287 #[parse(condition = "ParserContext::chrome_rules_enabled")]
289 MozButtonhovertext,
290 #[parse(condition = "ParserContext::chrome_rules_enabled")]
292 MozButtonhoverborder,
293 #[parse(condition = "ParserContext::chrome_rules_enabled")]
295 MozButtonactiveface,
296 #[parse(condition = "ParserContext::chrome_rules_enabled")]
298 MozButtonactivetext,
299 #[parse(condition = "ParserContext::chrome_rules_enabled")]
301 MozButtonactiveborder,
302
303 #[parse(condition = "ParserContext::chrome_rules_enabled")]
305 MozButtondisabledface,
306 #[parse(condition = "ParserContext::chrome_rules_enabled")]
308 MozButtondisabledborder,
309
310 #[parse(condition = "ParserContext::chrome_rules_enabled")]
312 MozHeaderbar,
313 #[parse(condition = "ParserContext::chrome_rules_enabled")]
314 MozHeaderbartext,
315 #[parse(condition = "ParserContext::chrome_rules_enabled")]
316 MozHeaderbarinactive,
317 #[parse(condition = "ParserContext::chrome_rules_enabled")]
318 MozHeaderbarinactivetext,
319
320 #[parse(condition = "ParserContext::chrome_rules_enabled")]
322 MozMacDefaultbuttontext,
323 #[parse(condition = "ParserContext::chrome_rules_enabled")]
325 MozMacFocusring,
326 #[parse(condition = "ParserContext::chrome_rules_enabled")]
328 MozMacDisabledtoolbartext,
329 #[parse(condition = "ParserContext::chrome_rules_enabled")]
331 MozSidebar,
332 #[parse(condition = "ParserContext::chrome_rules_enabled")]
334 MozSidebartext,
335 #[parse(condition = "ParserContext::chrome_rules_enabled")]
337 MozSidebarborder,
338
339 Accentcolor,
342
343 Accentcolortext,
346
347 #[parse(condition = "ParserContext::chrome_rules_enabled")]
349 MozAutofillBackground,
350
351 #[parse(aliases = "-moz-hyperlinktext")]
352 Linktext,
353 #[parse(aliases = "-moz-activehyperlinktext")]
354 Activetext,
355 #[parse(aliases = "-moz-visitedhyperlinktext")]
356 Visitedtext,
357
358 #[parse(condition = "ParserContext::chrome_rules_enabled")]
360 MozColheader,
361 #[parse(condition = "ParserContext::chrome_rules_enabled")]
362 MozColheadertext,
363 #[parse(condition = "ParserContext::chrome_rules_enabled")]
364 MozColheaderhover,
365 #[parse(condition = "ParserContext::chrome_rules_enabled")]
366 MozColheaderhovertext,
367 #[parse(condition = "ParserContext::chrome_rules_enabled")]
368 MozColheaderactive,
369 #[parse(condition = "ParserContext::chrome_rules_enabled")]
370 MozColheaderactivetext,
371
372 #[parse(condition = "ParserContext::chrome_rules_enabled")]
373 TextSelectDisabledBackground,
374 #[css(skip)]
375 TextSelectAttentionBackground,
376 #[css(skip)]
377 TextSelectAttentionForeground,
378 #[css(skip)]
379 TextHighlightBackground,
380 #[css(skip)]
381 TextHighlightForeground,
382 #[css(skip)]
383 TargetTextBackground,
384 #[css(skip)]
385 TargetTextForeground,
386 #[css(skip)]
387 IMERawInputBackground,
388 #[css(skip)]
389 IMERawInputForeground,
390 #[css(skip)]
391 IMERawInputUnderline,
392 #[css(skip)]
393 IMESelectedRawTextBackground,
394 #[css(skip)]
395 IMESelectedRawTextForeground,
396 #[css(skip)]
397 IMESelectedRawTextUnderline,
398 #[css(skip)]
399 IMEConvertedTextBackground,
400 #[css(skip)]
401 IMEConvertedTextForeground,
402 #[css(skip)]
403 IMEConvertedTextUnderline,
404 #[css(skip)]
405 IMESelectedConvertedTextBackground,
406 #[css(skip)]
407 IMESelectedConvertedTextForeground,
408 #[css(skip)]
409 IMESelectedConvertedTextUnderline,
410 #[css(skip)]
411 SpellCheckerUnderline,
412 #[css(skip)]
413 ThemedScrollbar,
414 #[css(skip)]
415 ThemedScrollbarThumb,
416 #[css(skip)]
417 ThemedScrollbarThumbHover,
418 #[css(skip)]
419 ThemedScrollbarThumbActive,
420
421 #[css(skip)]
422 End, }
424
425#[cfg(feature = "gecko")]
426impl SystemColor {
427 #[inline]
428 fn compute(&self, cx: &Context) -> ComputedColor {
429 use crate::gecko_bindings::bindings;
430
431 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
432 if cx.for_non_inherited_property {
433 cx.rule_cache_conditions
434 .borrow_mut()
435 .set_color_scheme_dependency(cx.builder.color_scheme);
436 }
437 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
438 return ComputedColor::currentcolor();
439 }
440 ComputedColor::Absolute(AbsoluteColor::from_nscolor(color))
441 }
442}
443
444#[allow(missing_docs)]
453#[cfg(feature = "servo")]
454#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
455#[repr(u8)]
456pub enum SystemColor {
457 Accentcolor,
458 Accentcolortext,
459 Activetext,
460 Linktext,
461 Visitedtext,
462 Buttonborder,
463 Buttonface,
464 Buttontext,
465 Canvas,
466 Canvastext,
467 Field,
468 Fieldtext,
469 Graytext,
470 Highlight,
471 Highlighttext,
472 Mark,
473 Marktext,
474 Selecteditem,
475 Selecteditemtext,
476
477 Activeborder,
479 Inactiveborder,
480 Threeddarkshadow,
481 Threedhighlight,
482 Threedlightshadow,
483 Threedshadow,
484 Windowframe,
485 Buttonhighlight,
486 Buttonshadow,
487 Threedface,
488 Activecaption,
489 Appworkspace,
490 Background,
491 Inactivecaption,
492 Infobackground,
493 Menu,
494 Scrollbar,
495 Window,
496 Captiontext,
497 Infotext,
498 Menutext,
499 Windowtext,
500 Inactivecaptiontext,
501}
502
503#[cfg(feature = "servo")]
504impl SystemColor {
505 #[inline]
506 fn compute(&self, cx: &Context) -> ComputedColor {
507 if cx.for_non_inherited_property {
508 cx.rule_cache_conditions
509 .borrow_mut()
510 .set_color_scheme_dependency(cx.builder.color_scheme);
511 }
512
513 ComputedColor::Absolute(cx.device().system_color(*self, cx.builder.color_scheme))
514 }
515}
516
517#[derive(Copy, Clone)]
520enum PreserveAuthored {
521 No,
522 Yes,
523}
524
525impl Parse for Color {
526 fn parse<'i, 't>(
527 context: &ParserContext,
528 input: &mut Parser<'i, 't>,
529 ) -> Result<Self, ParseError<'i>> {
530 Self::parse_internal(context, input, PreserveAuthored::Yes)
531 }
532}
533
534impl Color {
535 fn parse_internal<'i, 't>(
536 context: &ParserContext,
537 input: &mut Parser<'i, 't>,
538 preserve_authored: PreserveAuthored,
539 ) -> Result<Self, ParseError<'i>> {
540 let authored = match preserve_authored {
541 PreserveAuthored::No => None,
542 PreserveAuthored::Yes => {
543 let start = input.state();
547 let authored = input.expect_ident_cloned().ok();
548 input.reset(&start);
549 authored
550 },
551 };
552
553 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
554 Ok(mut color) => {
555 if let Color::Absolute(ref mut absolute) = color {
556 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
559 }
560 Ok(color)
561 },
562 Err(e) => {
563 {
564 #[cfg(feature = "gecko")]
565 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
566 return Ok(Color::System(system));
567 }
568 #[cfg(feature = "servo")]
569 if let Ok(system) = input.try_parse(SystemColor::parse) {
570 return Ok(Color::System(system));
571 }
572 }
573 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
574 {
575 return Ok(Color::ColorMix(Box::new(mix)));
576 }
577
578 if let Ok(ld) = input.try_parse(|i| {
579 GenericLightDark::parse_with(i, |i| {
580 Self::parse_internal(context, i, preserve_authored)
581 })
582 }) {
583 return Ok(Color::LightDark(Box::new(ld)));
584 }
585
586 if static_prefs::pref!("layout.css.contrast-color.enabled") {
587 if let Ok(c) = input.try_parse(|i| {
588 i.expect_function_matching("contrast-color")?;
589 i.parse_nested_block(|i| {
590 Self::parse_internal(context, i, preserve_authored)
591 })
592 }) {
593 return Ok(Color::ContrastColor(Box::new(c)));
594 }
595 }
596
597 match e.kind {
598 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
599 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
600 ValueParseErrorKind::InvalidColor(t),
601 )))
602 },
603 _ => Err(e),
604 }
605 },
606 }
607 }
608
609 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
611 input
612 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
613 .is_ok()
614 }
615
616 pub fn parse_and_compute(
618 context: &ParserContext,
619 input: &mut Parser,
620 device: Option<&Device>,
621 ) -> Option<ComputedColor> {
622 use crate::error_reporting::ContextualParseError;
623 let start = input.position();
624 let result = input
625 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
626
627 let specified = match result {
628 Ok(s) => s,
629 Err(e) => {
630 if !context.error_reporting_enabled() {
631 return None;
632 }
633 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
641 let location = e.location.clone();
642 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
643 context.log_css_error(location, error);
644 }
645 return None;
646 },
647 };
648
649 match device {
650 Some(device) => {
651 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
652 specified.to_computed_color(Some(&context))
653 })
654 },
655 None => specified.to_computed_color(None),
656 }
657 }
658}
659
660impl ToCss for Color {
661 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
662 where
663 W: Write,
664 {
665 match *self {
666 Color::CurrentColor => dest.write_str("currentcolor"),
667 Color::Absolute(ref absolute) => absolute.to_css(dest),
668 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
669 Color::ColorMix(ref mix) => mix.to_css(dest),
670 Color::LightDark(ref ld) => ld.to_css(dest),
671 Color::ContrastColor(ref c) => {
672 dest.write_str("contrast-color(")?;
673 c.to_css(dest)?;
674 dest.write_char(')')
675 },
676 Color::System(system) => system.to_css(dest),
677 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
678 }
679 }
680}
681
682impl Color {
683 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
685 match *self {
686 Self::InheritFromBodyQuirk => false,
687 Self::CurrentColor => true,
688 Self::System(..) => true,
689 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
690 Self::ColorFunction(ref color_function) => {
691 color_function
694 .resolve_to_absolute()
695 .map(|resolved| allow_transparent && resolved.is_transparent())
696 .unwrap_or(false)
697 },
698 Self::LightDark(ref ld) => {
699 ld.light.honored_in_forced_colors_mode(allow_transparent)
700 && ld.dark.honored_in_forced_colors_mode(allow_transparent)
701 },
702 Self::ColorMix(ref mix) => mix
703 .items
704 .iter()
705 .all(|item| item.color.honored_in_forced_colors_mode(allow_transparent)),
706 Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent),
707 }
708 }
709
710 #[inline]
712 pub fn currentcolor() -> Self {
713 Self::CurrentColor
714 }
715
716 #[inline]
718 pub fn transparent() -> Self {
719 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
721 }
722
723 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
725 Color::Absolute(Box::new(Absolute {
726 color,
727 authored: None,
728 }))
729 }
730
731 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
736 use crate::values::specified::percentage::ToPercentage;
737
738 match self {
739 Self::Absolute(c) => Some(c.color),
740 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
741 Self::ColorMix(ref mix) => {
742 use crate::color::mix;
743
744 let mut items = ColorMixItemList::with_capacity(mix.items.len());
745 for item in mix.items.iter() {
746 items.push(mix::ColorMixItem::new(
747 item.color.resolve_to_absolute()?,
748 item.percentage.to_percentage(),
749 ))
750 }
751
752 Some(mix::mix_many(mix.interpolation, items, mix.flags))
753 },
754 _ => None,
755 }
756 }
757
758 pub fn parse_quirky<'i, 't>(
762 context: &ParserContext,
763 input: &mut Parser<'i, 't>,
764 allow_quirks: AllowQuirks,
765 ) -> Result<Self, ParseError<'i>> {
766 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
767 if !allow_quirks.allowed(context.quirks_mode) {
768 return Err(e);
769 }
770 Color::parse_quirky_color(input).map_err(|_| e)
771 })
772 }
773
774 fn parse_hash<'i>(
775 bytes: &[u8],
776 loc: &cssparser::SourceLocation,
777 ) -> Result<Self, ParseError<'i>> {
778 match cssparser::color::parse_hash_color(bytes) {
779 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
780 r, g, b, a,
781 ))),
782 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
783 }
784 }
785
786 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
790 let location = input.current_source_location();
791 let (value, unit) = match *input.next()? {
792 Token::Number {
793 int_value: Some(integer),
794 ..
795 } => (integer, None),
796 Token::Dimension {
797 int_value: Some(integer),
798 ref unit,
799 ..
800 } => (integer, Some(unit)),
801 Token::Ident(ref ident) => {
802 if ident.len() != 3 && ident.len() != 6 {
803 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
804 }
805 return Self::parse_hash(ident.as_bytes(), &location);
806 },
807 ref t => {
808 return Err(location.new_unexpected_token_error(t.clone()));
809 },
810 };
811 if value < 0 {
812 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
813 }
814 let length = if value <= 9 {
815 1
816 } else if value <= 99 {
817 2
818 } else if value <= 999 {
819 3
820 } else if value <= 9999 {
821 4
822 } else if value <= 99999 {
823 5
824 } else if value <= 999999 {
825 6
826 } else {
827 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
828 };
829 let total = length + unit.as_ref().map_or(0, |d| d.len());
830 if total > 6 {
831 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
832 }
833 let mut serialization = [b'0'; 6];
834 let space_padding = 6 - total;
835 let mut written = space_padding;
836 let mut buf = itoa::Buffer::new();
837 let s = buf.format(value);
838 (&mut serialization[written..])
839 .write_all(s.as_bytes())
840 .unwrap();
841 written += s.len();
842 if let Some(unit) = unit {
843 written += (&mut serialization[written..])
844 .write(unit.as_bytes())
845 .unwrap();
846 }
847 debug_assert_eq!(written, 6);
848 Self::parse_hash(&serialization, &location)
849 }
850}
851
852impl Color {
853 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
858 macro_rules! adjust_absolute_color {
859 ($color:expr) => {{
860 if matches!(
862 $color.color_space,
863 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
864 ) {
865 $color.components.0 = normalize($color.components.0);
866 }
867
868 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
870 $color.components = $color.components.map(normalize);
871 }
872
873 $color.alpha = normalize($color.alpha);
874 }};
875 }
876
877 Some(match *self {
878 Color::CurrentColor => ComputedColor::CurrentColor,
879 Color::Absolute(ref absolute) => {
880 let mut color = absolute.color;
881 adjust_absolute_color!(color);
882 ComputedColor::Absolute(color)
883 },
884 Color::ColorFunction(ref color_function) => {
885 debug_assert!(color_function.has_origin_color(),
886 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
887
888 if let Ok(absolute) = color_function.resolve_to_absolute() {
890 ComputedColor::Absolute(absolute)
891 } else {
892 let color_function = color_function
893 .map_origin_color(|origin_color| origin_color.to_computed_color(context));
894 ComputedColor::ColorFunction(Box::new(color_function))
895 }
896 },
897 Color::LightDark(ref ld) => ld.compute(context?),
898 Color::ColorMix(ref mix) => {
899 use crate::values::computed::percentage::Percentage;
900
901 let mut items = ColorMixItemList::with_capacity(mix.items.len());
902 for item in mix.items.iter() {
903 items.push(GenericColorMixItem {
904 color: item.color.to_computed_color(context)?,
905 percentage: Percentage(item.percentage.get()),
906 });
907 }
908
909 ComputedColor::from_color_mix(GenericColorMix {
910 interpolation: mix.interpolation,
911 items: OwnedSlice::from_slice(items.as_slice()),
912 flags: mix.flags,
913 })
914 },
915 Color::ContrastColor(ref c) => {
916 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
917 },
918 Color::System(system) => system.compute(context?),
919 Color::InheritFromBodyQuirk => {
920 ComputedColor::Absolute(context?.device().body_text_color())
921 },
922 })
923 }
924}
925
926impl ToComputedValue for Color {
927 type ComputedValue = ComputedColor;
928
929 fn to_computed_value(&self, context: &Context) -> ComputedColor {
930 self.to_computed_color(Some(context)).unwrap_or_else(|| {
931 debug_assert!(
932 false,
933 "Specified color could not be resolved to a computed color!"
934 );
935 ComputedColor::Absolute(AbsoluteColor::BLACK)
936 })
937 }
938
939 fn from_computed_value(computed: &ComputedColor) -> Self {
940 match *computed {
941 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
942 ComputedColor::ColorFunction(ref color_function) => {
943 let color_function =
944 color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
945 Self::ColorFunction(Box::new(color_function))
946 },
947 ComputedColor::CurrentColor => Color::CurrentColor,
948 ComputedColor::ColorMix(ref mix) => {
949 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
950 },
951 ComputedColor::ContrastColor(ref c) => {
952 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
953 },
954 }
955 }
956}
957
958impl SpecifiedValueInfo for Color {
959 const SUPPORTED_TYPES: u8 = CssType::COLOR;
960
961 fn collect_completion_keywords(f: KeywordsCollectFn) {
962 f(&[
968 "currentColor",
969 "transparent",
970 "rgb",
971 "rgba",
972 "hsl",
973 "hsla",
974 "hwb",
975 "color",
976 "lab",
977 "lch",
978 "oklab",
979 "oklch",
980 "color-mix",
981 "contrast-color",
982 "light-dark",
983 ]);
984 }
985}
986
987#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
990#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
991pub struct ColorPropertyValue(pub Color);
992
993impl ToComputedValue for ColorPropertyValue {
994 type ComputedValue = AbsoluteColor;
995
996 #[inline]
997 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
998 let current_color = context.builder.get_parent_inherited_text().clone_color();
999 self.0
1000 .to_computed_value(context)
1001 .resolve_to_absolute(¤t_color)
1002 }
1003
1004 #[inline]
1005 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1006 ColorPropertyValue(Color::from_absolute_color(*computed).into())
1007 }
1008}
1009
1010impl Parse for ColorPropertyValue {
1011 fn parse<'i, 't>(
1012 context: &ParserContext,
1013 input: &mut Parser<'i, 't>,
1014 ) -> Result<Self, ParseError<'i>> {
1015 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
1016 }
1017}
1018
1019pub type ColorOrAuto = GenericColorOrAuto<Color>;
1021
1022pub type CaretColor = GenericCaretColor<Color>;
1024
1025impl Parse for CaretColor {
1026 fn parse<'i, 't>(
1027 context: &ParserContext,
1028 input: &mut Parser<'i, 't>,
1029 ) -> Result<Self, ParseError<'i>> {
1030 ColorOrAuto::parse(context, input).map(GenericCaretColor)
1031 }
1032}
1033
1034#[derive(
1037 Clone,
1038 Copy,
1039 Debug,
1040 Default,
1041 Eq,
1042 MallocSizeOf,
1043 PartialEq,
1044 SpecifiedValueInfo,
1045 ToComputedValue,
1046 ToResolvedValue,
1047 ToShmem,
1048)]
1049#[repr(C)]
1050#[value_info(other_values = "light,dark,only")]
1051pub struct ColorSchemeFlags(u8);
1052bitflags! {
1053 impl ColorSchemeFlags: u8 {
1054 const LIGHT = 1 << 0;
1056 const DARK = 1 << 1;
1058 const ONLY = 1 << 2;
1060 }
1061}
1062
1063#[derive(
1065 Clone,
1066 Debug,
1067 Default,
1068 MallocSizeOf,
1069 PartialEq,
1070 SpecifiedValueInfo,
1071 ToComputedValue,
1072 ToResolvedValue,
1073 ToShmem,
1074 ToTyped,
1075)]
1076#[repr(C)]
1077#[value_info(other_values = "normal")]
1078pub struct ColorScheme {
1079 #[ignore_malloc_size_of = "Arc"]
1080 idents: crate::ArcSlice<CustomIdent>,
1081 pub bits: ColorSchemeFlags,
1083}
1084
1085impl ColorScheme {
1086 pub fn normal() -> Self {
1088 Self {
1089 idents: Default::default(),
1090 bits: ColorSchemeFlags::empty(),
1091 }
1092 }
1093
1094 pub fn raw_bits(&self) -> u8 {
1096 self.bits.bits()
1097 }
1098}
1099
1100impl Parse for ColorScheme {
1101 fn parse<'i, 't>(
1102 _: &ParserContext,
1103 input: &mut Parser<'i, 't>,
1104 ) -> Result<Self, ParseError<'i>> {
1105 let mut idents = vec![];
1106 let mut bits = ColorSchemeFlags::empty();
1107
1108 let mut location = input.current_source_location();
1109 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
1110 let mut is_only = false;
1111 match_ignore_ascii_case! { &ident,
1112 "normal" => {
1113 if idents.is_empty() && bits.is_empty() {
1114 return Ok(Self::normal());
1115 }
1116 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1117 },
1118 "light" => bits.insert(ColorSchemeFlags::LIGHT),
1119 "dark" => bits.insert(ColorSchemeFlags::DARK),
1120 "only" => {
1121 if bits.intersects(ColorSchemeFlags::ONLY) {
1122 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1123 }
1124 bits.insert(ColorSchemeFlags::ONLY);
1125 is_only = true;
1126 },
1127 _ => {},
1128 };
1129
1130 if is_only {
1131 if !idents.is_empty() {
1132 break;
1135 }
1136 } else {
1137 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
1138 }
1139 location = input.current_source_location();
1140 }
1141
1142 if idents.is_empty() {
1143 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1144 }
1145
1146 Ok(Self {
1147 idents: crate::ArcSlice::from_iter(idents.into_iter()),
1148 bits,
1149 })
1150 }
1151}
1152
1153impl ToCss for ColorScheme {
1154 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1155 where
1156 W: Write,
1157 {
1158 if self.idents.is_empty() {
1159 debug_assert!(self.bits.is_empty());
1160 return dest.write_str("normal");
1161 }
1162 let mut first = true;
1163 for ident in self.idents.iter() {
1164 if !first {
1165 dest.write_char(' ')?;
1166 }
1167 first = false;
1168 ident.to_css(dest)?;
1169 }
1170 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1171 dest.write_str(" only")?;
1172 }
1173 Ok(())
1174 }
1175}
1176
1177#[derive(
1179 Clone,
1180 Copy,
1181 Debug,
1182 MallocSizeOf,
1183 Parse,
1184 PartialEq,
1185 SpecifiedValueInfo,
1186 ToCss,
1187 ToComputedValue,
1188 ToResolvedValue,
1189 ToShmem,
1190 ToTyped,
1191)]
1192#[repr(u8)]
1193pub enum PrintColorAdjust {
1194 Economy,
1196 Exact,
1198}
1199
1200#[derive(
1202 Clone,
1203 Copy,
1204 Debug,
1205 MallocSizeOf,
1206 Parse,
1207 PartialEq,
1208 SpecifiedValueInfo,
1209 ToCss,
1210 ToComputedValue,
1211 ToResolvedValue,
1212 ToShmem,
1213 ToTyped,
1214)]
1215#[repr(u8)]
1216pub enum ForcedColorAdjust {
1217 Auto,
1219 None,
1221}
1222
1223#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1226#[repr(u8)]
1227pub enum ForcedColors {
1228 None,
1230 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1232 Requested,
1233 Active,
1235}
1236
1237impl ForcedColors {
1238 pub fn is_active(self) -> bool {
1240 matches!(self, Self::Active)
1241 }
1242}