1use super::AllowQuirks;
8use crate::color::mix::ColorInterpolationMethod;
9use crate::color::{parsing, AbsoluteColor, ColorFunction, ColorMixItemList, ColorSpace};
10use crate::derives::*;
11use crate::media_queries::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 #[cfg(feature = "gecko")]
179 InheritFromBodyQuirk,
180}
181
182impl From<AbsoluteColor> for Color {
183 #[inline]
184 fn from(value: AbsoluteColor) -> Self {
185 Self::from_absolute_color(value)
186 }
187}
188
189#[allow(missing_docs)]
198#[cfg(feature = "gecko")]
199#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
200#[repr(u8)]
201pub enum SystemColor {
202 Activeborder,
203 Activecaption,
205 Appworkspace,
206 Background,
207 Buttonface,
208 Buttonhighlight,
209 Buttonshadow,
210 Buttontext,
211 Buttonborder,
212 Captiontext,
214 #[parse(aliases = "-moz-field")]
215 Field,
216 #[parse(condition = "ParserContext::chrome_rules_enabled")]
218 MozDisabledfield,
219 #[parse(aliases = "-moz-fieldtext")]
220 Fieldtext,
221
222 Mark,
223 Marktext,
224
225 MozComboboxtext,
227 MozCombobox,
228
229 Graytext,
230 Highlight,
231 Highlighttext,
232 Inactiveborder,
233 Inactivecaption,
235 Inactivecaptiontext,
237 Infobackground,
238 Infotext,
239 Menu,
240 Menutext,
241 Scrollbar,
242 Threeddarkshadow,
243 Threedface,
244 Threedhighlight,
245 Threedlightshadow,
246 Threedshadow,
247 Window,
248 Windowframe,
249 Windowtext,
250 #[parse(aliases = "-moz-default-color")]
251 Canvastext,
252 #[parse(aliases = "-moz-default-background-color")]
253 Canvas,
254 MozDialog,
255 MozDialogtext,
256 #[parse(aliases = "-moz-html-cellhighlight")]
258 MozCellhighlight,
259 #[parse(aliases = "-moz-html-cellhighlighttext")]
261 MozCellhighlighttext,
262 Selecteditem,
264 Selecteditemtext,
266 MozMenuhover,
268 #[parse(condition = "ParserContext::chrome_rules_enabled")]
270 MozMenuhoverdisabled,
271 MozMenuhovertext,
273 MozMenubarhovertext,
275
276 MozOddtreerow,
279
280 #[parse(condition = "ParserContext::chrome_rules_enabled")]
282 MozButtonhoverface,
283 #[parse(condition = "ParserContext::chrome_rules_enabled")]
285 MozButtonhovertext,
286 #[parse(condition = "ParserContext::chrome_rules_enabled")]
288 MozButtonhoverborder,
289 #[parse(condition = "ParserContext::chrome_rules_enabled")]
291 MozButtonactiveface,
292 #[parse(condition = "ParserContext::chrome_rules_enabled")]
294 MozButtonactivetext,
295 #[parse(condition = "ParserContext::chrome_rules_enabled")]
297 MozButtonactiveborder,
298
299 #[parse(condition = "ParserContext::chrome_rules_enabled")]
301 MozButtondisabledface,
302 #[parse(condition = "ParserContext::chrome_rules_enabled")]
304 MozButtondisabledborder,
305
306 #[parse(condition = "ParserContext::chrome_rules_enabled")]
308 MozHeaderbar,
309 #[parse(condition = "ParserContext::chrome_rules_enabled")]
310 MozHeaderbartext,
311 #[parse(condition = "ParserContext::chrome_rules_enabled")]
312 MozHeaderbarinactive,
313 #[parse(condition = "ParserContext::chrome_rules_enabled")]
314 MozHeaderbarinactivetext,
315
316 #[parse(condition = "ParserContext::chrome_rules_enabled")]
318 MozMacDefaultbuttontext,
319 #[parse(condition = "ParserContext::chrome_rules_enabled")]
321 MozMacFocusring,
322 #[parse(condition = "ParserContext::chrome_rules_enabled")]
324 MozMacDisabledtoolbartext,
325 #[parse(condition = "ParserContext::chrome_rules_enabled")]
327 MozSidebar,
328 #[parse(condition = "ParserContext::chrome_rules_enabled")]
330 MozSidebartext,
331 #[parse(condition = "ParserContext::chrome_rules_enabled")]
333 MozSidebarborder,
334
335 Accentcolor,
338
339 Accentcolortext,
342
343 #[parse(condition = "ParserContext::chrome_rules_enabled")]
345 MozAutofillBackground,
346
347 #[parse(aliases = "-moz-hyperlinktext")]
348 Linktext,
349 #[parse(aliases = "-moz-activehyperlinktext")]
350 Activetext,
351 #[parse(aliases = "-moz-visitedhyperlinktext")]
352 Visitedtext,
353
354 #[parse(condition = "ParserContext::chrome_rules_enabled")]
356 MozColheader,
357 #[parse(condition = "ParserContext::chrome_rules_enabled")]
358 MozColheadertext,
359 #[parse(condition = "ParserContext::chrome_rules_enabled")]
360 MozColheaderhover,
361 #[parse(condition = "ParserContext::chrome_rules_enabled")]
362 MozColheaderhovertext,
363 #[parse(condition = "ParserContext::chrome_rules_enabled")]
364 MozColheaderactive,
365 #[parse(condition = "ParserContext::chrome_rules_enabled")]
366 MozColheaderactivetext,
367
368 #[parse(condition = "ParserContext::chrome_rules_enabled")]
369 TextSelectDisabledBackground,
370 #[css(skip)]
371 TextSelectAttentionBackground,
372 #[css(skip)]
373 TextSelectAttentionForeground,
374 #[css(skip)]
375 TextHighlightBackground,
376 #[css(skip)]
377 TextHighlightForeground,
378 #[css(skip)]
379 TargetTextBackground,
380 #[css(skip)]
381 TargetTextForeground,
382 #[css(skip)]
383 IMERawInputBackground,
384 #[css(skip)]
385 IMERawInputForeground,
386 #[css(skip)]
387 IMERawInputUnderline,
388 #[css(skip)]
389 IMESelectedRawTextBackground,
390 #[css(skip)]
391 IMESelectedRawTextForeground,
392 #[css(skip)]
393 IMESelectedRawTextUnderline,
394 #[css(skip)]
395 IMEConvertedTextBackground,
396 #[css(skip)]
397 IMEConvertedTextForeground,
398 #[css(skip)]
399 IMEConvertedTextUnderline,
400 #[css(skip)]
401 IMESelectedConvertedTextBackground,
402 #[css(skip)]
403 IMESelectedConvertedTextForeground,
404 #[css(skip)]
405 IMESelectedConvertedTextUnderline,
406 #[css(skip)]
407 SpellCheckerUnderline,
408 #[css(skip)]
409 ThemedScrollbar,
410 #[css(skip)]
411 ThemedScrollbarThumb,
412 #[css(skip)]
413 ThemedScrollbarThumbHover,
414 #[css(skip)]
415 ThemedScrollbarThumbActive,
416
417 #[css(skip)]
418 End, }
420
421#[cfg(feature = "gecko")]
422impl SystemColor {
423 #[inline]
424 fn compute(&self, cx: &Context) -> ComputedColor {
425 use crate::gecko::values::convert_nscolor_to_absolute_color;
426 use crate::gecko_bindings::bindings;
427
428 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
429 if cx.for_non_inherited_property {
430 cx.rule_cache_conditions
431 .borrow_mut()
432 .set_color_scheme_dependency(cx.builder.color_scheme);
433 }
434 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
435 return ComputedColor::currentcolor();
436 }
437 ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
438 }
439}
440
441#[allow(missing_docs)]
450#[cfg(feature = "servo")]
451#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
452#[repr(u8)]
453pub enum SystemColor {
454 Accentcolor,
455 Accentcolortext,
456 Activetext,
457 Linktext,
458 Visitedtext,
459 Buttonborder,
460 Buttonface,
461 Buttontext,
462 Canvas,
463 Canvastext,
464 Field,
465 Fieldtext,
466 Graytext,
467 Highlight,
468 Highlighttext,
469 Mark,
470 Marktext,
471 Selecteditem,
472 Selecteditemtext,
473
474 Activeborder,
476 Inactiveborder,
477 Threeddarkshadow,
478 Threedhighlight,
479 Threedlightshadow,
480 Threedshadow,
481 Windowframe,
482 Buttonhighlight,
483 Buttonshadow,
484 Threedface,
485 Activecaption,
486 Appworkspace,
487 Background,
488 Inactivecaption,
489 Infobackground,
490 Menu,
491 Scrollbar,
492 Window,
493 Captiontext,
494 Infotext,
495 Menutext,
496 Windowtext,
497 Inactivecaptiontext,
498}
499
500#[cfg(feature = "servo")]
501impl SystemColor {
502 #[inline]
503 fn compute(&self, cx: &Context) -> ComputedColor {
504 if cx.for_non_inherited_property {
505 cx.rule_cache_conditions
506 .borrow_mut()
507 .set_color_scheme_dependency(cx.builder.color_scheme);
508 }
509
510 ComputedColor::Absolute(cx.device().system_color(*self, cx.builder.color_scheme))
511 }
512}
513
514#[derive(Copy, Clone)]
517enum PreserveAuthored {
518 No,
519 Yes,
520}
521
522impl Parse for Color {
523 fn parse<'i, 't>(
524 context: &ParserContext,
525 input: &mut Parser<'i, 't>,
526 ) -> Result<Self, ParseError<'i>> {
527 Self::parse_internal(context, input, PreserveAuthored::Yes)
528 }
529}
530
531impl Color {
532 fn parse_internal<'i, 't>(
533 context: &ParserContext,
534 input: &mut Parser<'i, 't>,
535 preserve_authored: PreserveAuthored,
536 ) -> Result<Self, ParseError<'i>> {
537 let authored = match preserve_authored {
538 PreserveAuthored::No => None,
539 PreserveAuthored::Yes => {
540 let start = input.state();
544 let authored = input.expect_ident_cloned().ok();
545 input.reset(&start);
546 authored
547 },
548 };
549
550 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
551 Ok(mut color) => {
552 if let Color::Absolute(ref mut absolute) = color {
553 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
556 }
557 Ok(color)
558 },
559 Err(e) => {
560 {
561 #[cfg(feature = "gecko")]
562 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
563 return Ok(Color::System(system));
564 }
565 #[cfg(feature = "servo")]
566 if let Ok(system) = input.try_parse(SystemColor::parse) {
567 return Ok(Color::System(system));
568 }
569 }
570 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
571 {
572 return Ok(Color::ColorMix(Box::new(mix)));
573 }
574
575 if let Ok(ld) = input.try_parse(|i| {
576 GenericLightDark::parse_with(i, |i| {
577 Self::parse_internal(context, i, preserve_authored)
578 })
579 }) {
580 return Ok(Color::LightDark(Box::new(ld)));
581 }
582
583 if static_prefs::pref!("layout.css.contrast-color.enabled") {
584 if let Ok(c) = input.try_parse(|i| {
585 i.expect_function_matching("contrast-color")?;
586 i.parse_nested_block(|i| {
587 Self::parse_internal(context, i, preserve_authored)
588 })
589 }) {
590 return Ok(Color::ContrastColor(Box::new(c)));
591 }
592 }
593
594 match e.kind {
595 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
596 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
597 ValueParseErrorKind::InvalidColor(t),
598 )))
599 },
600 _ => Err(e),
601 }
602 },
603 }
604 }
605
606 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
608 input
609 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
610 .is_ok()
611 }
612
613 pub fn parse_and_compute(
615 context: &ParserContext,
616 input: &mut Parser,
617 device: Option<&Device>,
618 ) -> Option<ComputedColor> {
619 use crate::error_reporting::ContextualParseError;
620 let start = input.position();
621 let result = input
622 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
623
624 let specified = match result {
625 Ok(s) => s,
626 Err(e) => {
627 if !context.error_reporting_enabled() {
628 return None;
629 }
630 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
638 let location = e.location.clone();
639 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
640 context.log_css_error(location, error);
641 }
642 return None;
643 },
644 };
645
646 match device {
647 Some(device) => {
648 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
649 specified.to_computed_color(Some(&context))
650 })
651 },
652 None => specified.to_computed_color(None),
653 }
654 }
655}
656
657impl ToCss for Color {
658 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
659 where
660 W: Write,
661 {
662 match *self {
663 Color::CurrentColor => dest.write_str("currentcolor"),
664 Color::Absolute(ref absolute) => absolute.to_css(dest),
665 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
666 Color::ColorMix(ref mix) => mix.to_css(dest),
667 Color::LightDark(ref ld) => ld.to_css(dest),
668 Color::ContrastColor(ref c) => {
669 dest.write_str("contrast-color(")?;
670 c.to_css(dest)?;
671 dest.write_char(')')
672 },
673 Color::System(system) => system.to_css(dest),
674 #[cfg(feature = "gecko")]
675 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
676 }
677 }
678}
679
680impl Color {
681 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
683 match *self {
684 #[cfg(feature = "gecko")]
685 Self::InheritFromBodyQuirk => false,
686 Self::CurrentColor => true,
687 Self::System(..) => true,
688 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
689 Self::ColorFunction(ref color_function) => {
690 color_function
693 .resolve_to_absolute()
694 .map(|resolved| allow_transparent && resolved.is_transparent())
695 .unwrap_or(false)
696 },
697 Self::LightDark(ref ld) => {
698 ld.light.honored_in_forced_colors_mode(allow_transparent)
699 && ld.dark.honored_in_forced_colors_mode(allow_transparent)
700 },
701 Self::ColorMix(ref mix) => mix
702 .items
703 .iter()
704 .all(|item| item.color.honored_in_forced_colors_mode(allow_transparent)),
705 Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent),
706 }
707 }
708
709 #[inline]
711 pub fn currentcolor() -> Self {
712 Self::CurrentColor
713 }
714
715 #[inline]
717 pub fn transparent() -> Self {
718 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
720 }
721
722 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
724 Color::Absolute(Box::new(Absolute {
725 color,
726 authored: None,
727 }))
728 }
729
730 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
735 use crate::values::specified::percentage::ToPercentage;
736
737 match self {
738 Self::Absolute(c) => Some(c.color),
739 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
740 Self::ColorMix(ref mix) => {
741 use crate::color::mix;
742
743 let mut items = ColorMixItemList::with_capacity(mix.items.len());
744 for item in mix.items.iter() {
745 items.push(mix::ColorMixItem::new(
746 item.color.resolve_to_absolute()?,
747 item.percentage.to_percentage(),
748 ))
749 }
750
751 Some(mix::mix_many(mix.interpolation, items, mix.flags))
752 },
753 _ => None,
754 }
755 }
756
757 pub fn parse_quirky<'i, 't>(
761 context: &ParserContext,
762 input: &mut Parser<'i, 't>,
763 allow_quirks: AllowQuirks,
764 ) -> Result<Self, ParseError<'i>> {
765 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
766 if !allow_quirks.allowed(context.quirks_mode) {
767 return Err(e);
768 }
769 Color::parse_quirky_color(input).map_err(|_| e)
770 })
771 }
772
773 fn parse_hash<'i>(
774 bytes: &[u8],
775 loc: &cssparser::SourceLocation,
776 ) -> Result<Self, ParseError<'i>> {
777 match cssparser::color::parse_hash_color(bytes) {
778 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
779 r, g, b, a,
780 ))),
781 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
782 }
783 }
784
785 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
789 let location = input.current_source_location();
790 let (value, unit) = match *input.next()? {
791 Token::Number {
792 int_value: Some(integer),
793 ..
794 } => (integer, None),
795 Token::Dimension {
796 int_value: Some(integer),
797 ref unit,
798 ..
799 } => (integer, Some(unit)),
800 Token::Ident(ref ident) => {
801 if ident.len() != 3 && ident.len() != 6 {
802 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
803 }
804 return Self::parse_hash(ident.as_bytes(), &location);
805 },
806 ref t => {
807 return Err(location.new_unexpected_token_error(t.clone()));
808 },
809 };
810 if value < 0 {
811 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
812 }
813 let length = if value <= 9 {
814 1
815 } else if value <= 99 {
816 2
817 } else if value <= 999 {
818 3
819 } else if value <= 9999 {
820 4
821 } else if value <= 99999 {
822 5
823 } else if value <= 999999 {
824 6
825 } else {
826 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
827 };
828 let total = length + unit.as_ref().map_or(0, |d| d.len());
829 if total > 6 {
830 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
831 }
832 let mut serialization = [b'0'; 6];
833 let space_padding = 6 - total;
834 let mut written = space_padding;
835 let mut buf = itoa::Buffer::new();
836 let s = buf.format(value);
837 (&mut serialization[written..])
838 .write_all(s.as_bytes())
839 .unwrap();
840 written += s.len();
841 if let Some(unit) = unit {
842 written += (&mut serialization[written..])
843 .write(unit.as_bytes())
844 .unwrap();
845 }
846 debug_assert_eq!(written, 6);
847 Self::parse_hash(&serialization, &location)
848 }
849}
850
851impl Color {
852 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
857 macro_rules! adjust_absolute_color {
858 ($color:expr) => {{
859 if matches!(
861 $color.color_space,
862 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
863 ) {
864 $color.components.0 = normalize($color.components.0);
865 }
866
867 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
869 $color.components = $color.components.map(normalize);
870 }
871
872 $color.alpha = normalize($color.alpha);
873 }};
874 }
875
876 Some(match *self {
877 Color::CurrentColor => ComputedColor::CurrentColor,
878 Color::Absolute(ref absolute) => {
879 let mut color = absolute.color;
880 adjust_absolute_color!(color);
881 ComputedColor::Absolute(color)
882 },
883 Color::ColorFunction(ref color_function) => {
884 debug_assert!(color_function.has_origin_color(),
885 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
886
887 if let Ok(absolute) = color_function.resolve_to_absolute() {
889 ComputedColor::Absolute(absolute)
890 } else {
891 let color_function = color_function
892 .map_origin_color(|origin_color| origin_color.to_computed_color(context));
893 ComputedColor::ColorFunction(Box::new(color_function))
894 }
895 },
896 Color::LightDark(ref ld) => ld.compute(context?),
897 Color::ColorMix(ref mix) => {
898 use crate::values::computed::percentage::Percentage;
899
900 let mut items = ColorMixItemList::with_capacity(mix.items.len());
901 for item in mix.items.iter() {
902 items.push(GenericColorMixItem {
903 color: item.color.to_computed_color(context)?,
904 percentage: Percentage(item.percentage.get()),
905 });
906 }
907
908 ComputedColor::from_color_mix(GenericColorMix {
909 interpolation: mix.interpolation,
910 items: OwnedSlice::from_slice(items.as_slice()),
911 flags: mix.flags,
912 })
913 },
914 Color::ContrastColor(ref c) => {
915 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
916 },
917 Color::System(system) => system.compute(context?),
918 #[cfg(feature = "gecko")]
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}