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