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::typed_om::{KeywordValue, ToTyped, TypedValue};
14use crate::values::computed::{
15 Color as ComputedColor, Context, Percentage as ComputedPercentage, ToComputedValue,
16};
17use crate::values::generics::color::{
18 ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorMixItem, GenericColorOrAuto,
19 GenericLightDark,
20};
21use crate::values::generics::Optional;
22use crate::values::specified::percentage::ToPercentage;
23use crate::values::specified::Percentage;
24use crate::values::{normalize, CustomIdent};
25use cssparser::{match_ignore_ascii_case, Parser, Token};
26use std::fmt::{self, Write};
27use std::io::Write as IoWrite;
28use style_traits::{
29 owned_slice::OwnedSlice, CssString, CssType, CssWriter, KeywordsCollectFn, ParseError,
30 SpecifiedValueInfo, StyleParseErrorKind, ToCss,
31};
32use thin_vec::ThinVec;
33
34pub type ColorMix = GenericColorMix<Color, Percentage>;
36
37impl ColorMix {
38 fn parse(
39 context: &ParserContext,
40 input: &mut Parser,
41 preserve_authored: PreserveAuthored,
42 ) -> Result<Self, ParseError> {
43 input.expect_function_matching("color-mix")?;
44
45 input.parse_nested_block(|input| {
46 let interpolation = input
49 .try_parse(|input| -> Result<_, ParseError> {
50 let interpolation = ColorInterpolationMethod::parse(context, input)?;
51 input.expect_comma()?;
52 Ok(interpolation)
53 })
54 .unwrap_or_default();
55
56 let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
57 input
58 .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
59 .ok()
60 };
61
62 let allow_multiple_items =
63 crate::pref!("layout.css.color-mix-multi-color.enabled");
64
65 let mut items = ColorMixItemList::default();
66
67 loop {
68 let mut percentage = try_parse_percentage(input);
69
70 let color = Color::parse_internal(context, input, preserve_authored)?;
71
72 if percentage.is_none() {
73 percentage = try_parse_percentage(input);
74 }
75
76 if matches!(percentage, Some(ref p) if p.to_percentage().is_none()) {
79 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
80 }
81
82 items.push((color, percentage));
83
84 if input.try_parse(|i| i.expect_comma()).is_err() {
85 break;
86 }
87
88 if !allow_multiple_items && items.len() == 2 {
90 break;
91 }
92 }
93
94 let min_item_count = if allow_multiple_items { 1 } else { 2 };
97 if items.len() < min_item_count {
98 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
99 }
100
101 let (mut sum_specified, mut missing) = (0.0, 0);
104 for (_, percentage) in items.iter() {
105 if let Some(p) = percentage {
106 sum_specified += p.to_percentage().unwrap();
108 } else {
109 missing += 1;
110 }
111 }
112
113 let any_calc = items
118 .iter()
119 .any(|(_, p)| matches!(p, Some(p) if p.is_calc()));
120
121 let default_for_missing_items = if any_calc {
122 None
123 } else {
124 match missing {
125 0 => None,
126 m if m == items.len() => Some(Percentage::new(1.0 / items.len() as f32)),
127 m => Some(Percentage::new((1.0 - sum_specified) / m as f32)),
128 }
129 };
130
131 if let Some(default) = default_for_missing_items {
132 for (_, percentage) in items.iter_mut() {
133 if percentage.is_none() {
134 *percentage = Some(default.clone());
135 }
136 }
137 }
138
139 let finalized = items
140 .into_iter()
141 .map(|(color, percentage)| GenericColorMixItem {
142 color,
143 percentage: percentage.into(),
144 })
145 .collect::<ColorMixItemList<_>>();
146
147 Ok(ColorMix {
151 interpolation,
152 items: OwnedSlice::from_slice(&finalized),
153 flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
154 })
155 })
156 }
157}
158
159#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
161pub struct Absolute {
162 pub color: AbsoluteColor,
164 pub authored: Option<Box<str>>,
166}
167
168impl ToCss for Absolute {
169 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
170 where
171 W: Write,
172 {
173 if let Some(ref authored) = self.authored {
174 dest.write_str(authored)
175 } else {
176 self.color.to_css(dest)
177 }
178 }
179}
180
181#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
183pub enum Color {
184 CurrentColor,
186 Absolute(Box<Absolute>),
189 ColorFunction(Box<ColorFunction<Self>>),
191 System(SystemColor),
193 ColorMix(Box<ColorMix>),
195 LightDark(Box<GenericLightDark<Self>>),
197 ContrastColor(Box<Color>),
199 InheritFromBodyQuirk,
201}
202
203impl From<AbsoluteColor> for Color {
204 #[inline]
205 fn from(value: AbsoluteColor) -> Self {
206 Self::from_absolute_color(value)
207 }
208}
209
210#[allow(missing_docs)]
219#[cfg(feature = "gecko")]
220#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
221#[repr(u8)]
222pub enum SystemColor {
223 Activeborder,
224 Activecaption,
226 Appworkspace,
227 Background,
228 Buttonface,
229 Buttonhighlight,
230 Buttonshadow,
231 Buttontext,
232 Buttonborder,
233 Captiontext,
235 #[parse(aliases = "-moz-field")]
236 Field,
237 #[parse(condition = "ParserContext::chrome_rules_enabled")]
239 MozDisabledfield,
240 #[parse(aliases = "-moz-fieldtext")]
241 Fieldtext,
242
243 Mark,
244 Marktext,
245
246 MozComboboxtext,
248 MozCombobox,
249
250 Graytext,
251 Highlight,
252 Highlighttext,
253 Inactiveborder,
254 Inactivecaption,
256 Inactivecaptiontext,
258 Infobackground,
259 Infotext,
260 Menu,
261 Menutext,
262 Scrollbar,
263 Threeddarkshadow,
264 Threedface,
265 Threedhighlight,
266 Threedlightshadow,
267 Threedshadow,
268 Window,
269 Windowframe,
270 Windowtext,
271 #[parse(aliases = "-moz-default-color")]
272 Canvastext,
273 #[parse(aliases = "-moz-default-background-color")]
274 Canvas,
275 MozDialog,
276 MozDialogtext,
277 #[parse(aliases = "-moz-html-cellhighlight")]
279 MozCellhighlight,
280 #[parse(aliases = "-moz-html-cellhighlighttext")]
282 MozCellhighlighttext,
283 Selecteditem,
285 Selecteditemtext,
287 MozMenuhover,
289 #[parse(condition = "ParserContext::chrome_rules_enabled")]
291 MozMenuhoverdisabled,
292 MozMenuhovertext,
294 MozMenubarhovertext,
296
297 MozOddtreerow,
300
301 #[parse(condition = "ParserContext::chrome_rules_enabled")]
303 MozButtonhoverface,
304 #[parse(condition = "ParserContext::chrome_rules_enabled")]
306 MozButtonhovertext,
307 #[parse(condition = "ParserContext::chrome_rules_enabled")]
309 MozButtonhoverborder,
310 #[parse(condition = "ParserContext::chrome_rules_enabled")]
312 MozButtonactiveface,
313 #[parse(condition = "ParserContext::chrome_rules_enabled")]
315 MozButtonactivetext,
316 #[parse(condition = "ParserContext::chrome_rules_enabled")]
318 MozButtonactiveborder,
319
320 #[parse(condition = "ParserContext::chrome_rules_enabled")]
322 MozButtondisabledface,
323 #[parse(condition = "ParserContext::chrome_rules_enabled")]
325 MozButtondisabledborder,
326
327 #[parse(condition = "ParserContext::chrome_rules_enabled")]
329 MozHeaderbar,
330 #[parse(condition = "ParserContext::chrome_rules_enabled")]
331 MozHeaderbartext,
332 #[parse(condition = "ParserContext::chrome_rules_enabled")]
333 MozHeaderbarinactive,
334 #[parse(condition = "ParserContext::chrome_rules_enabled")]
335 MozHeaderbarinactivetext,
336
337 #[parse(condition = "ParserContext::chrome_rules_enabled")]
339 MozMacDefaultbuttontext,
340 #[parse(condition = "ParserContext::chrome_rules_enabled")]
342 MozMacFocusring,
343 #[parse(condition = "ParserContext::chrome_rules_enabled")]
345 MozMacDisabledtoolbartext,
346 #[parse(condition = "ParserContext::chrome_rules_enabled")]
348 MozSidebar,
349 #[parse(condition = "ParserContext::chrome_rules_enabled")]
351 MozSidebartext,
352 #[parse(condition = "ParserContext::chrome_rules_enabled")]
354 MozSidebarborder,
355
356 Accentcolor,
359
360 Accentcolortext,
363
364 #[parse(condition = "ParserContext::chrome_rules_enabled")]
366 MozAutofillBackground,
367
368 #[parse(aliases = "-moz-hyperlinktext")]
369 Linktext,
370 #[parse(aliases = "-moz-activehyperlinktext")]
371 Activetext,
372 #[parse(aliases = "-moz-visitedhyperlinktext")]
373 Visitedtext,
374
375 #[parse(condition = "ParserContext::chrome_rules_enabled")]
377 MozColheader,
378 #[parse(condition = "ParserContext::chrome_rules_enabled")]
379 MozColheadertext,
380 #[parse(condition = "ParserContext::chrome_rules_enabled")]
381 MozColheaderhover,
382 #[parse(condition = "ParserContext::chrome_rules_enabled")]
383 MozColheaderhovertext,
384 #[parse(condition = "ParserContext::chrome_rules_enabled")]
385 MozColheaderactive,
386 #[parse(condition = "ParserContext::chrome_rules_enabled")]
387 MozColheaderactivetext,
388
389 #[parse(condition = "ParserContext::chrome_rules_enabled")]
390 TextSelectDisabledBackground,
391 #[css(skip)]
392 TextSelectAttentionBackground,
393 #[css(skip)]
394 TextSelectAttentionForeground,
395 #[css(skip)]
396 TextHighlightBackground,
397 #[css(skip)]
398 TextHighlightForeground,
399 #[css(skip)]
400 TargetTextBackground,
401 #[css(skip)]
402 TargetTextForeground,
403 #[css(skip)]
404 IMERawInputBackground,
405 #[css(skip)]
406 IMERawInputForeground,
407 #[css(skip)]
408 IMERawInputUnderline,
409 #[css(skip)]
410 IMESelectedRawTextBackground,
411 #[css(skip)]
412 IMESelectedRawTextForeground,
413 #[css(skip)]
414 IMESelectedRawTextUnderline,
415 #[css(skip)]
416 IMEConvertedTextBackground,
417 #[css(skip)]
418 IMEConvertedTextForeground,
419 #[css(skip)]
420 IMEConvertedTextUnderline,
421 #[css(skip)]
422 IMESelectedConvertedTextBackground,
423 #[css(skip)]
424 IMESelectedConvertedTextForeground,
425 #[css(skip)]
426 IMESelectedConvertedTextUnderline,
427 #[css(skip)]
428 SpellCheckerUnderline,
429 #[css(skip)]
430 ThemedScrollbar,
431 #[css(skip)]
432 ThemedScrollbarThumb,
433 #[css(skip)]
434 ThemedScrollbarThumbHover,
435 #[css(skip)]
436 ThemedScrollbarThumbActive,
437
438 #[css(skip)]
439 End, }
441
442#[cfg(feature = "gecko")]
443impl SystemColor {
444 #[inline]
445 fn compute(&self, cx: &Context) -> ComputedColor {
446 use crate::gecko_bindings::bindings;
447
448 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
449 if cx.for_non_inherited_property {
450 cx.rule_cache_conditions
451 .borrow_mut()
452 .set_color_scheme_dependency(cx.builder.color_scheme);
453 }
454 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
455 return ComputedColor::currentcolor();
456 }
457 ComputedColor::Absolute(AbsoluteColor::from_nscolor(color))
458 }
459}
460
461#[allow(missing_docs)]
470#[cfg(feature = "servo")]
471#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
472#[repr(u8)]
473pub enum SystemColor {
474 Accentcolor,
475 Accentcolortext,
476 Activetext,
477 Linktext,
478 Visitedtext,
479 Buttonborder,
480 Buttonface,
481 Buttontext,
482 Canvas,
483 Canvastext,
484 Field,
485 Fieldtext,
486 Graytext,
487 Highlight,
488 Highlighttext,
489 Mark,
490 Marktext,
491 Selecteditem,
492 Selecteditemtext,
493
494 Activeborder,
496 Inactiveborder,
497 Threeddarkshadow,
498 Threedhighlight,
499 Threedlightshadow,
500 Threedshadow,
501 Windowframe,
502 Buttonhighlight,
503 Buttonshadow,
504 Threedface,
505 Activecaption,
506 Appworkspace,
507 Background,
508 Inactivecaption,
509 Infobackground,
510 Menu,
511 Scrollbar,
512 Window,
513 Captiontext,
514 Infotext,
515 Menutext,
516 Windowtext,
517 Inactivecaptiontext,
518}
519
520#[cfg(feature = "servo")]
521impl SystemColor {
522 #[inline]
523 fn compute(&self, cx: &Context) -> ComputedColor {
524 if cx.for_non_inherited_property {
525 cx.rule_cache_conditions
526 .borrow_mut()
527 .set_color_scheme_dependency(cx.builder.color_scheme);
528 }
529
530 ComputedColor::Absolute(cx.device().system_color(*self, cx.builder.color_scheme))
531 }
532}
533
534#[derive(Copy, Clone)]
537enum PreserveAuthored {
538 No,
539 Yes,
540}
541
542impl Parse for Color {
543 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
544 Self::parse_internal(context, input, PreserveAuthored::Yes)
545 }
546}
547
548impl Color {
549 fn parse_internal(
550 context: &ParserContext,
551 input: &mut Parser,
552 preserve_authored: PreserveAuthored,
553 ) -> Result<Self, ParseError> {
554 let authored = match preserve_authored {
555 PreserveAuthored::No => None,
556 PreserveAuthored::Yes => {
557 let start = input.state();
561 let authored = input.expect_ident_cloned().ok();
562 input.reset(&start);
563 authored
564 },
565 };
566
567 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
568 Ok(mut color) => {
569 if let Color::Absolute(ref mut absolute) = color {
570 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
573 }
574 Ok(color)
575 },
576 Err(e) => {
577 {
578 #[cfg(feature = "gecko")]
579 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
580 return Ok(Color::System(system));
581 }
582 #[cfg(feature = "servo")]
583 if let Ok(system) = input.try_parse(SystemColor::parse) {
584 return Ok(Color::System(system));
585 }
586 }
587 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
588 {
589 return Ok(Color::ColorMix(Box::new(mix)));
590 }
591
592 if let Ok(ld) = input.try_parse(|i| {
593 GenericLightDark::parse_with(i, |i| {
594 Self::parse_internal(context, i, preserve_authored)
595 })
596 }) {
597 return Ok(Color::LightDark(Box::new(ld)));
598 }
599
600 if let Ok(c) = input.try_parse(|i| {
601 i.expect_function_matching("contrast-color")?;
602 i.parse_nested_block(|i| Self::parse_internal(context, i, preserve_authored))
603 }) {
604 return Ok(Color::ContrastColor(Box::new(c)));
605 }
606
607 Err(e)
608 },
609 }
610 }
611
612 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
614 input
615 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
616 .is_ok()
617 }
618
619 pub fn parse_and_compute(
621 context: &ParserContext,
622 input: &mut Parser,
623 device: Option<&Device>,
624 ) -> Result<ComputedColor, ()> {
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(..) => return Err(()),
631 };
632
633 match device {
634 Some(device) => {
635 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
636 specified.to_computed_color(Some(context))
637 })
638 },
639 None => specified.to_computed_color(None),
640 }
641 }
642}
643
644impl ToCss for Color {
645 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
646 where
647 W: Write,
648 {
649 match *self {
650 Color::CurrentColor => dest.write_str("currentcolor"),
651 Color::Absolute(ref absolute) => absolute.to_css(dest),
652 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
653 Color::ColorMix(ref mix) => mix.to_css(dest),
654 Color::LightDark(ref ld) => ld.to_css(dest),
655 Color::ContrastColor(ref c) => {
656 dest.write_str("contrast-color(")?;
657 c.to_css(dest)?;
658 dest.write_char(')')
659 },
660 Color::System(system) => system.to_css(dest),
661 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
662 }
663 }
664}
665
666impl ToTyped for Color {
667 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
668 match *self {
669 Color::CurrentColor => {
670 dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
671 "currentcolor",
672 ))));
673 Ok(())
674 },
675 _ => Err(()),
676 }
677 }
678}
679
680impl Color {
681 pub fn honored_in_forced_colors_mode(
683 &self,
684 context: &Context,
685 allow_transparent: bool,
686 ) -> bool {
687 match *self {
688 Self::InheritFromBodyQuirk => false,
689 Self::CurrentColor => true,
690 Self::System(..) => true,
691 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
692 Self::ColorFunction(ref color_function) => {
693 color_function
696 .to_computed_color(Some(context))
697 .ok()
698 .and_then(|c| c.as_absolute().copied())
699 .map(|resolved| allow_transparent && resolved.is_transparent())
700 .unwrap_or(false)
701 },
702 Self::LightDark(ref ld) => {
703 ld.light
704 .honored_in_forced_colors_mode(context, allow_transparent)
705 && ld
706 .dark
707 .honored_in_forced_colors_mode(context, allow_transparent)
708 },
709 Self::ColorMix(ref mix) => mix.items.iter().all(|item| {
710 item.color
711 .honored_in_forced_colors_mode(context, allow_transparent)
712 }),
713 Self::ContrastColor(ref c) => {
714 c.honored_in_forced_colors_mode(context, allow_transparent)
715 },
716 }
717 }
718
719 #[inline]
721 pub fn currentcolor() -> Self {
722 Self::CurrentColor
723 }
724
725 #[inline]
727 pub fn transparent() -> Self {
728 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
730 }
731
732 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
734 Color::Absolute(Box::new(Absolute {
735 color,
736 authored: None,
737 }))
738 }
739
740 pub fn parse_quirky(
744 context: &ParserContext,
745 input: &mut Parser,
746 allow_quirks: AllowQuirks,
747 ) -> Result<Self, ParseError> {
748 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
749 if !allow_quirks.allowed(context.quirks_mode) {
750 return Err(e);
751 }
752 Color::parse_quirky_color(input).map_err(|_| e)
753 })
754 }
755
756 fn parse_hash(bytes: &[u8]) -> Result<Self, ParseError> {
757 match cssparser::color::parse_hash_color(bytes) {
758 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
759 r, g, b, a,
760 ))),
761 Err(()) => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
762 }
763 }
764
765 fn parse_quirky_color(input: &mut Parser) -> Result<Self, ParseError> {
769 let (value, unit) = match *input.next()? {
770 Token::Number {
771 int_value: Some(integer),
772 ..
773 } => (integer, None),
774 Token::Dimension {
775 int_value: Some(integer),
776 ref unit,
777 ..
778 } => (integer, Some(unit)),
779 Token::Ident(ref ident) => {
780 if ident.len() != 3 && ident.len() != 6 {
781 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
782 }
783 return Self::parse_hash(ident.as_bytes());
784 },
785 _ => {
786 return Err(ParseError::unexpected_token());
787 },
788 };
789 if value < 0 {
790 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
791 }
792 let length = if value <= 9 {
793 1
794 } else if value <= 99 {
795 2
796 } else if value <= 999 {
797 3
798 } else if value <= 9999 {
799 4
800 } else if value <= 99999 {
801 5
802 } else if value <= 999999 {
803 6
804 } else {
805 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
806 };
807 let total = length + unit.as_ref().map_or(0, |d| d.len());
808 if total > 6 {
809 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
810 }
811 let mut serialization = [b'0'; 6];
812 let space_padding = 6 - total;
813 let mut written = space_padding;
814 let mut buf = itoa::Buffer::new();
815 let s = buf.format(value);
816 (&mut serialization[written..])
817 .write_all(s.as_bytes())
818 .unwrap();
819 written += s.len();
820 if let Some(unit) = unit {
821 written += (&mut serialization[written..])
822 .write(unit.as_bytes())
823 .unwrap();
824 }
825 debug_assert_eq!(written, 6);
826 Self::parse_hash(&serialization)
827 }
828}
829
830impl Color {
831 pub fn to_computed_color(&self, context: Option<&Context>) -> Result<ComputedColor, ()> {
836 macro_rules! adjust_absolute_color {
837 ($color:expr) => {{
838 if matches!(
840 $color.color_space,
841 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
842 ) {
843 $color.components.0 = normalize($color.components.0);
844 }
845
846 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
848 $color.components = $color.components.map(normalize);
849 }
850
851 $color.alpha = normalize($color.alpha);
852 }};
853 }
854
855 Ok(match *self {
856 Color::CurrentColor => ComputedColor::CurrentColor,
857 Color::Absolute(ref absolute) => {
858 let mut color = absolute.color;
859 adjust_absolute_color!(color);
860 ComputedColor::Absolute(color)
861 },
862 Color::ColorFunction(ref color_function) => {
863 color_function.to_computed_color(context)?
864 },
865 Color::LightDark(ref ld) => ld.compute(context.ok_or(())?),
866 Color::ColorMix(ref mix) => {
867 let mut items = ColorMixItemList::with_capacity(mix.items.len());
868 for item in mix.items.iter() {
869 items.push(GenericColorMixItem {
870 color: item.color.to_computed_color(context)?,
871 percentage: match item.percentage.as_ref() {
872 None => Optional::None,
873 Some(percentage) => Optional::Some(match context {
874 None => ComputedPercentage(percentage.to_percentage().ok_or(())?),
875 Some(ctx) => percentage.to_computed_value(ctx),
876 }),
877 },
878 });
879 }
880
881 ComputedColor::from_color_mix(GenericColorMix {
882 interpolation: mix.interpolation,
883 items: OwnedSlice::from_slice(items.as_slice()),
884 flags: mix.flags,
885 })
886 },
887 Color::ContrastColor(ref c) => {
888 let computed = c.to_computed_color(context)?;
889 if let Some(abs) = computed.as_absolute() {
890 ComputedColor::Absolute(ComputedColor::resolve_contrast_color(abs))
891 } else {
892 ComputedColor::ContrastColor(Box::new(computed))
893 }
894 },
895 Color::System(system) => system.compute(context.ok_or(())?),
896 Color::InheritFromBodyQuirk => {
897 ComputedColor::Absolute(context.ok_or(())?.device().body_text_color())
898 },
899 })
900 }
901}
902
903impl ToComputedValue for Color {
904 type ComputedValue = ComputedColor;
905
906 fn to_computed_value(&self, context: &Context) -> ComputedColor {
907 self.to_computed_color(Some(context)).unwrap_or_else(|_| {
908 debug_assert!(
909 false,
910 "Specified color could not be resolved to a computed color!"
911 );
912 ComputedColor::Absolute(AbsoluteColor::BLACK)
913 })
914 }
915
916 fn from_computed_value(computed: &ComputedColor) -> Self {
917 match *computed {
918 ComputedColor::Absolute(ref color) => Self::from_absolute_color(*color),
919 ComputedColor::ColorFunction(ref color_function) => {
920 let color_function = color_function
921 .map_origin_color(|o| Ok(Self::from_computed_value(o)))
922 .unwrap();
923 Self::ColorFunction(Box::new(color_function))
924 },
925 ComputedColor::CurrentColor => Color::CurrentColor,
926 ComputedColor::ColorMix(ref mix) => {
927 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
928 },
929 ComputedColor::ContrastColor(ref c) => {
930 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
931 },
932 }
933 }
934}
935
936impl SpecifiedValueInfo for Color {
937 const SUPPORTED_TYPES: u8 = CssType::COLOR;
938
939 fn collect_completion_keywords(f: KeywordsCollectFn) {
940 f(&[
946 "currentColor",
947 "transparent",
948 "rgb",
949 "rgba",
950 "hsl",
951 "hsla",
952 "hwb",
953 "color",
954 "lab",
955 "lch",
956 "oklab",
957 "oklch",
958 "color-mix",
959 "contrast-color",
960 "light-dark",
961 ]);
962 }
963}
964
965#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
968#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
969pub struct ColorPropertyValue(pub Color);
970
971impl ToComputedValue for ColorPropertyValue {
972 type ComputedValue = AbsoluteColor;
973
974 #[inline]
975 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
976 let current_color = context.builder.get_parent_inherited_text().clone_color();
977 self.0
978 .to_computed_value(context)
979 .resolve_to_absolute(¤t_color)
980 }
981
982 #[inline]
983 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
984 ColorPropertyValue(Color::from_absolute_color(*computed))
985 }
986}
987
988impl Parse for ColorPropertyValue {
989 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
990 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
991 }
992}
993
994pub type ColorOrAuto = GenericColorOrAuto<Color>;
996
997pub type CaretColor = GenericCaretColor<Color>;
999
1000impl Parse for CaretColor {
1001 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1002 ColorOrAuto::parse(context, input).map(GenericCaretColor)
1003 }
1004}
1005
1006#[derive(
1009 Clone,
1010 Copy,
1011 Debug,
1012 Default,
1013 Eq,
1014 MallocSizeOf,
1015 PartialEq,
1016 SpecifiedValueInfo,
1017 ToComputedValue,
1018 ToResolvedValue,
1019 ToShmem,
1020)]
1021#[repr(C)]
1022#[value_info(other_values = "light,dark,only")]
1023pub struct ColorSchemeFlags(u8);
1024bitflags! {
1025 impl ColorSchemeFlags: u8 {
1026 const LIGHT = 1 << 0;
1028 const DARK = 1 << 1;
1030 const ONLY = 1 << 2;
1032 }
1033}
1034
1035#[derive(
1037 Clone,
1038 Debug,
1039 Default,
1040 MallocSizeOf,
1041 PartialEq,
1042 SpecifiedValueInfo,
1043 ToComputedValue,
1044 ToResolvedValue,
1045 ToShmem,
1046 ToTyped,
1047)]
1048#[repr(C)]
1049#[typed(todo_derive_fields)]
1050#[value_info(other_values = "normal")]
1051pub struct ColorScheme {
1052 #[ignore_malloc_size_of = "Arc"]
1053 idents: crate::ArcSlice<CustomIdent>,
1054 pub bits: ColorSchemeFlags,
1056}
1057
1058impl ColorScheme {
1059 pub fn normal() -> Self {
1061 Self {
1062 idents: Default::default(),
1063 bits: ColorSchemeFlags::empty(),
1064 }
1065 }
1066
1067 pub fn raw_bits(&self) -> u8 {
1069 self.bits.bits()
1070 }
1071}
1072
1073impl Parse for ColorScheme {
1074 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1075 let mut idents = vec![];
1076 let mut bits = ColorSchemeFlags::empty();
1077
1078 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
1079 let mut is_only = false;
1080 match_ignore_ascii_case! { &ident,
1081 "normal" => {
1082 if idents.is_empty() && bits.is_empty() {
1083 return Ok(Self::normal());
1084 }
1085 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1086 },
1087 "light" => bits.insert(ColorSchemeFlags::LIGHT),
1088 "dark" => bits.insert(ColorSchemeFlags::DARK),
1089 "only" => {
1090 if bits.intersects(ColorSchemeFlags::ONLY) {
1091 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1092 }
1093 bits.insert(ColorSchemeFlags::ONLY);
1094 is_only = true;
1095 },
1096 _ => {},
1097 };
1098
1099 if is_only {
1100 if !idents.is_empty() {
1101 break;
1104 }
1105 } else {
1106 idents.push(CustomIdent::from_ident(&ident, &[])?);
1107 }
1108 }
1109
1110 if idents.is_empty() {
1111 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1112 }
1113
1114 Ok(Self {
1115 idents: crate::ArcSlice::from_iter(idents.into_iter()),
1116 bits,
1117 })
1118 }
1119}
1120
1121impl ToCss for ColorScheme {
1122 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1123 where
1124 W: Write,
1125 {
1126 if self.idents.is_empty() {
1127 debug_assert!(self.bits.is_empty());
1128 return dest.write_str("normal");
1129 }
1130 let mut first = true;
1131 for ident in self.idents.iter() {
1132 if !first {
1133 dest.write_char(' ')?;
1134 }
1135 first = false;
1136 ident.to_css(dest)?;
1137 }
1138 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1139 dest.write_str(" only")?;
1140 }
1141 Ok(())
1142 }
1143}
1144
1145#[derive(
1147 Clone,
1148 Copy,
1149 Debug,
1150 MallocSizeOf,
1151 Parse,
1152 PartialEq,
1153 SpecifiedValueInfo,
1154 ToCss,
1155 ToComputedValue,
1156 ToResolvedValue,
1157 ToShmem,
1158 ToTyped,
1159)]
1160#[repr(u8)]
1161pub enum PrintColorAdjust {
1162 Economy,
1164 Exact,
1166}
1167
1168#[derive(
1170 Clone,
1171 Copy,
1172 Debug,
1173 MallocSizeOf,
1174 Parse,
1175 PartialEq,
1176 SpecifiedValueInfo,
1177 ToCss,
1178 ToComputedValue,
1179 ToResolvedValue,
1180 ToShmem,
1181 ToTyped,
1182)]
1183#[repr(u8)]
1184pub enum ForcedColorAdjust {
1185 Auto,
1187 None,
1189}
1190
1191#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1194#[repr(u8)]
1195pub enum ForcedColors {
1196 None,
1198 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1200 Requested,
1201 Active,
1203}
1204
1205impl ForcedColors {
1206 pub fn is_active(self) -> bool {
1208 matches!(self, Self::Active)
1209 }
1210}