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