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::specified::percentage::ToPercentage;
22use crate::values::specified::Percentage;
23use crate::values::{normalize, CustomIdent};
24use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, ParseErrorKind, Parser, Token};
25use std::fmt::{self, Write};
26use std::io::Write as IoWrite;
27use style_traits::{
28 owned_slice::OwnedSlice, CssString, CssType, CssWriter, KeywordsCollectFn, ParseError,
29 SpecifiedValueInfo, StyleParseErrorKind, ToCss, ValueParseErrorKind,
30};
31use thin_vec::ThinVec;
32
33pub type ColorMix = GenericColorMix<Color, Percentage>;
35
36impl ColorMix {
37 fn parse<'i, 't>(
38 context: &ParserContext,
39 input: &mut Parser<'i, 't>,
40 preserve_authored: PreserveAuthored,
41 ) -> Result<Self, ParseError<'i>> {
42 input.expect_function_matching("color-mix")?;
43
44 input.parse_nested_block(|input| {
45 let interpolation = input
48 .try_parse(|input| -> Result<_, ParseError<'i>> {
49 let interpolation = ColorInterpolationMethod::parse(context, input)?;
50 input.expect_comma()?;
51 Ok(interpolation)
52 })
53 .unwrap_or_default();
54
55 let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
56 input
57 .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
58 .ok()
59 };
60
61 let allow_multiple_items =
62 static_prefs::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(input.new_custom_error(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(input.new_custom_error(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 default_for_missing_items = match missing {
113 0 => None,
114 m if m == items.len() => Some(Percentage::new(1.0 / items.len() as f32)),
115 m => Some(Percentage::new((1.0 - sum_specified) / m as f32)),
116 };
117
118 if let Some(default) = default_for_missing_items {
119 for (_, percentage) in items.iter_mut() {
120 if percentage.is_none() {
121 *percentage = Some(default.clone());
122 }
123 }
124 }
125
126 let mut total = 0.0;
127 let finalized = items
128 .into_iter()
129 .map(|(color, percentage)| {
130 let percentage = percentage.expect("percentage filled above");
131 total += percentage.to_percentage().unwrap();
133 GenericColorMixItem { color, percentage }
134 })
135 .collect::<ColorMixItemList<_>>();
136
137 if total <= 0.0 {
138 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
139 }
140
141 Ok(ColorMix {
145 interpolation,
146 items: OwnedSlice::from_slice(&finalized),
147 flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
148 })
149 })
150 }
151}
152
153#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
155pub struct Absolute {
156 pub color: AbsoluteColor,
158 pub authored: Option<Box<str>>,
160}
161
162impl ToCss for Absolute {
163 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
164 where
165 W: Write,
166 {
167 if let Some(ref authored) = self.authored {
168 dest.write_str(authored)
169 } else {
170 self.color.to_css(dest)
171 }
172 }
173}
174
175#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
177pub enum Color {
178 CurrentColor,
180 Absolute(Box<Absolute>),
183 ColorFunction(Box<ColorFunction<Self>>),
185 System(SystemColor),
187 ColorMix(Box<ColorMix>),
189 LightDark(Box<GenericLightDark<Self>>),
191 ContrastColor(Box<Color>),
193 InheritFromBodyQuirk,
195}
196
197impl From<AbsoluteColor> for Color {
198 #[inline]
199 fn from(value: AbsoluteColor) -> Self {
200 Self::from_absolute_color(value)
201 }
202}
203
204#[allow(missing_docs)]
213#[cfg(feature = "gecko")]
214#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
215#[repr(u8)]
216pub enum SystemColor {
217 Activeborder,
218 Activecaption,
220 Appworkspace,
221 Background,
222 Buttonface,
223 Buttonhighlight,
224 Buttonshadow,
225 Buttontext,
226 Buttonborder,
227 Captiontext,
229 #[parse(aliases = "-moz-field")]
230 Field,
231 #[parse(condition = "ParserContext::chrome_rules_enabled")]
233 MozDisabledfield,
234 #[parse(aliases = "-moz-fieldtext")]
235 Fieldtext,
236
237 Mark,
238 Marktext,
239
240 MozComboboxtext,
242 MozCombobox,
243
244 Graytext,
245 Highlight,
246 Highlighttext,
247 Inactiveborder,
248 Inactivecaption,
250 Inactivecaptiontext,
252 Infobackground,
253 Infotext,
254 Menu,
255 Menutext,
256 Scrollbar,
257 Threeddarkshadow,
258 Threedface,
259 Threedhighlight,
260 Threedlightshadow,
261 Threedshadow,
262 Window,
263 Windowframe,
264 Windowtext,
265 #[parse(aliases = "-moz-default-color")]
266 Canvastext,
267 #[parse(aliases = "-moz-default-background-color")]
268 Canvas,
269 MozDialog,
270 MozDialogtext,
271 #[parse(aliases = "-moz-html-cellhighlight")]
273 MozCellhighlight,
274 #[parse(aliases = "-moz-html-cellhighlighttext")]
276 MozCellhighlighttext,
277 Selecteditem,
279 Selecteditemtext,
281 MozMenuhover,
283 #[parse(condition = "ParserContext::chrome_rules_enabled")]
285 MozMenuhoverdisabled,
286 MozMenuhovertext,
288 MozMenubarhovertext,
290
291 MozOddtreerow,
294
295 #[parse(condition = "ParserContext::chrome_rules_enabled")]
297 MozButtonhoverface,
298 #[parse(condition = "ParserContext::chrome_rules_enabled")]
300 MozButtonhovertext,
301 #[parse(condition = "ParserContext::chrome_rules_enabled")]
303 MozButtonhoverborder,
304 #[parse(condition = "ParserContext::chrome_rules_enabled")]
306 MozButtonactiveface,
307 #[parse(condition = "ParserContext::chrome_rules_enabled")]
309 MozButtonactivetext,
310 #[parse(condition = "ParserContext::chrome_rules_enabled")]
312 MozButtonactiveborder,
313
314 #[parse(condition = "ParserContext::chrome_rules_enabled")]
316 MozButtondisabledface,
317 #[parse(condition = "ParserContext::chrome_rules_enabled")]
319 MozButtondisabledborder,
320
321 #[parse(condition = "ParserContext::chrome_rules_enabled")]
323 MozHeaderbar,
324 #[parse(condition = "ParserContext::chrome_rules_enabled")]
325 MozHeaderbartext,
326 #[parse(condition = "ParserContext::chrome_rules_enabled")]
327 MozHeaderbarinactive,
328 #[parse(condition = "ParserContext::chrome_rules_enabled")]
329 MozHeaderbarinactivetext,
330
331 #[parse(condition = "ParserContext::chrome_rules_enabled")]
333 MozMacDefaultbuttontext,
334 #[parse(condition = "ParserContext::chrome_rules_enabled")]
336 MozMacFocusring,
337 #[parse(condition = "ParserContext::chrome_rules_enabled")]
339 MozMacDisabledtoolbartext,
340 #[parse(condition = "ParserContext::chrome_rules_enabled")]
342 MozSidebar,
343 #[parse(condition = "ParserContext::chrome_rules_enabled")]
345 MozSidebartext,
346 #[parse(condition = "ParserContext::chrome_rules_enabled")]
348 MozSidebarborder,
349
350 Accentcolor,
353
354 Accentcolortext,
357
358 #[parse(condition = "ParserContext::chrome_rules_enabled")]
360 MozAutofillBackground,
361
362 #[parse(aliases = "-moz-hyperlinktext")]
363 Linktext,
364 #[parse(aliases = "-moz-activehyperlinktext")]
365 Activetext,
366 #[parse(aliases = "-moz-visitedhyperlinktext")]
367 Visitedtext,
368
369 #[parse(condition = "ParserContext::chrome_rules_enabled")]
371 MozColheader,
372 #[parse(condition = "ParserContext::chrome_rules_enabled")]
373 MozColheadertext,
374 #[parse(condition = "ParserContext::chrome_rules_enabled")]
375 MozColheaderhover,
376 #[parse(condition = "ParserContext::chrome_rules_enabled")]
377 MozColheaderhovertext,
378 #[parse(condition = "ParserContext::chrome_rules_enabled")]
379 MozColheaderactive,
380 #[parse(condition = "ParserContext::chrome_rules_enabled")]
381 MozColheaderactivetext,
382
383 #[parse(condition = "ParserContext::chrome_rules_enabled")]
384 TextSelectDisabledBackground,
385 #[css(skip)]
386 TextSelectAttentionBackground,
387 #[css(skip)]
388 TextSelectAttentionForeground,
389 #[css(skip)]
390 TextHighlightBackground,
391 #[css(skip)]
392 TextHighlightForeground,
393 #[css(skip)]
394 TargetTextBackground,
395 #[css(skip)]
396 TargetTextForeground,
397 #[css(skip)]
398 IMERawInputBackground,
399 #[css(skip)]
400 IMERawInputForeground,
401 #[css(skip)]
402 IMERawInputUnderline,
403 #[css(skip)]
404 IMESelectedRawTextBackground,
405 #[css(skip)]
406 IMESelectedRawTextForeground,
407 #[css(skip)]
408 IMESelectedRawTextUnderline,
409 #[css(skip)]
410 IMEConvertedTextBackground,
411 #[css(skip)]
412 IMEConvertedTextForeground,
413 #[css(skip)]
414 IMEConvertedTextUnderline,
415 #[css(skip)]
416 IMESelectedConvertedTextBackground,
417 #[css(skip)]
418 IMESelectedConvertedTextForeground,
419 #[css(skip)]
420 IMESelectedConvertedTextUnderline,
421 #[css(skip)]
422 SpellCheckerUnderline,
423 #[css(skip)]
424 ThemedScrollbar,
425 #[css(skip)]
426 ThemedScrollbarThumb,
427 #[css(skip)]
428 ThemedScrollbarThumbHover,
429 #[css(skip)]
430 ThemedScrollbarThumbActive,
431
432 #[css(skip)]
433 End, }
435
436#[cfg(feature = "gecko")]
437impl SystemColor {
438 #[inline]
439 fn compute(&self, cx: &Context) -> ComputedColor {
440 use crate::gecko_bindings::bindings;
441
442 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
443 if cx.for_non_inherited_property {
444 cx.rule_cache_conditions
445 .borrow_mut()
446 .set_color_scheme_dependency(cx.builder.color_scheme);
447 }
448 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
449 return ComputedColor::currentcolor();
450 }
451 ComputedColor::Absolute(AbsoluteColor::from_nscolor(color))
452 }
453}
454
455#[allow(missing_docs)]
464#[cfg(feature = "servo")]
465#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
466#[repr(u8)]
467pub enum SystemColor {
468 Accentcolor,
469 Accentcolortext,
470 Activetext,
471 Linktext,
472 Visitedtext,
473 Buttonborder,
474 Buttonface,
475 Buttontext,
476 Canvas,
477 Canvastext,
478 Field,
479 Fieldtext,
480 Graytext,
481 Highlight,
482 Highlighttext,
483 Mark,
484 Marktext,
485 Selecteditem,
486 Selecteditemtext,
487
488 Activeborder,
490 Inactiveborder,
491 Threeddarkshadow,
492 Threedhighlight,
493 Threedlightshadow,
494 Threedshadow,
495 Windowframe,
496 Buttonhighlight,
497 Buttonshadow,
498 Threedface,
499 Activecaption,
500 Appworkspace,
501 Background,
502 Inactivecaption,
503 Infobackground,
504 Menu,
505 Scrollbar,
506 Window,
507 Captiontext,
508 Infotext,
509 Menutext,
510 Windowtext,
511 Inactivecaptiontext,
512}
513
514#[cfg(feature = "servo")]
515impl SystemColor {
516 #[inline]
517 fn compute(&self, cx: &Context) -> ComputedColor {
518 if cx.for_non_inherited_property {
519 cx.rule_cache_conditions
520 .borrow_mut()
521 .set_color_scheme_dependency(cx.builder.color_scheme);
522 }
523
524 ComputedColor::Absolute(cx.device().system_color(*self, cx.builder.color_scheme))
525 }
526}
527
528#[derive(Copy, Clone)]
531enum PreserveAuthored {
532 No,
533 Yes,
534}
535
536impl Parse for Color {
537 fn parse<'i, 't>(
538 context: &ParserContext,
539 input: &mut Parser<'i, 't>,
540 ) -> Result<Self, ParseError<'i>> {
541 Self::parse_internal(context, input, PreserveAuthored::Yes)
542 }
543}
544
545impl Color {
546 fn parse_internal<'i, 't>(
547 context: &ParserContext,
548 input: &mut Parser<'i, 't>,
549 preserve_authored: PreserveAuthored,
550 ) -> Result<Self, ParseError<'i>> {
551 let authored = match preserve_authored {
552 PreserveAuthored::No => None,
553 PreserveAuthored::Yes => {
554 let start = input.state();
558 let authored = input.expect_ident_cloned().ok();
559 input.reset(&start);
560 authored
561 },
562 };
563
564 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
565 Ok(mut color) => {
566 if let Color::Absolute(ref mut absolute) = color {
567 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
570 }
571 Ok(color)
572 },
573 Err(e) => {
574 {
575 #[cfg(feature = "gecko")]
576 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
577 return Ok(Color::System(system));
578 }
579 #[cfg(feature = "servo")]
580 if let Ok(system) = input.try_parse(SystemColor::parse) {
581 return Ok(Color::System(system));
582 }
583 }
584 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
585 {
586 return Ok(Color::ColorMix(Box::new(mix)));
587 }
588
589 if let Ok(ld) = input.try_parse(|i| {
590 GenericLightDark::parse_with(i, |i| {
591 Self::parse_internal(context, i, preserve_authored)
592 })
593 }) {
594 return Ok(Color::LightDark(Box::new(ld)));
595 }
596
597 if static_prefs::pref!("layout.css.contrast-color.enabled") {
598 if let Ok(c) = input.try_parse(|i| {
599 i.expect_function_matching("contrast-color")?;
600 i.parse_nested_block(|i| {
601 Self::parse_internal(context, i, preserve_authored)
602 })
603 }) {
604 return Ok(Color::ContrastColor(Box::new(c)));
605 }
606 }
607
608 match e.kind {
609 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
610 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
611 ValueParseErrorKind::InvalidColor(t),
612 )))
613 },
614 _ => Err(e),
615 }
616 },
617 }
618 }
619
620 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
622 input
623 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
624 .is_ok()
625 }
626
627 pub fn parse_and_compute(
629 context: &ParserContext,
630 input: &mut Parser,
631 device: Option<&Device>,
632 ) -> Result<ComputedColor, ()> {
633 use crate::error_reporting::ContextualParseError;
634 let start = input.position();
635 let result = input
636 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
637
638 let specified = match result {
639 Ok(s) => s,
640 Err(e) => {
641 if context.error_reporting_enabled() {
642 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
650 let location = e.location.clone();
651 let error =
652 ContextualParseError::UnsupportedValue(input.slice_from(start), e);
653 context.log_css_error(location, error);
654 }
655 }
656 return Err(());
657 },
658 };
659
660 match device {
661 Some(device) => {
662 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
663 specified.to_computed_color(Some(context))
664 })
665 },
666 None => specified.to_computed_color(None),
667 }
668 }
669}
670
671impl ToCss for Color {
672 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
673 where
674 W: Write,
675 {
676 match *self {
677 Color::CurrentColor => dest.write_str("currentcolor"),
678 Color::Absolute(ref absolute) => absolute.to_css(dest),
679 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
680 Color::ColorMix(ref mix) => mix.to_css(dest),
681 Color::LightDark(ref ld) => ld.to_css(dest),
682 Color::ContrastColor(ref c) => {
683 dest.write_str("contrast-color(")?;
684 c.to_css(dest)?;
685 dest.write_char(')')
686 },
687 Color::System(system) => system.to_css(dest),
688 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
689 }
690 }
691}
692
693impl ToTyped for Color {
694 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
695 match *self {
696 Color::CurrentColor => {
697 dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
698 "currentcolor",
699 ))));
700 Ok(())
701 },
702 _ => Err(()),
703 }
704 }
705}
706
707impl Color {
708 pub fn honored_in_forced_colors_mode(
710 &self,
711 context: &Context,
712 allow_transparent: bool,
713 ) -> bool {
714 match *self {
715 Self::InheritFromBodyQuirk => false,
716 Self::CurrentColor => true,
717 Self::System(..) => true,
718 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
719 Self::ColorFunction(ref color_function) => {
720 color_function
723 .to_computed_color(Some(context))
724 .ok()
725 .and_then(|c| c.as_absolute().copied())
726 .map(|resolved| allow_transparent && resolved.is_transparent())
727 .unwrap_or(false)
728 },
729 Self::LightDark(ref ld) => {
730 ld.light
731 .honored_in_forced_colors_mode(context, allow_transparent)
732 && ld
733 .dark
734 .honored_in_forced_colors_mode(context, allow_transparent)
735 },
736 Self::ColorMix(ref mix) => mix.items.iter().all(|item| {
737 item.color
738 .honored_in_forced_colors_mode(context, allow_transparent)
739 }),
740 Self::ContrastColor(ref c) => {
741 c.honored_in_forced_colors_mode(context, allow_transparent)
742 },
743 }
744 }
745
746 #[inline]
748 pub fn currentcolor() -> Self {
749 Self::CurrentColor
750 }
751
752 #[inline]
754 pub fn transparent() -> Self {
755 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
757 }
758
759 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
761 Color::Absolute(Box::new(Absolute {
762 color,
763 authored: None,
764 }))
765 }
766
767 pub fn parse_quirky<'i, 't>(
771 context: &ParserContext,
772 input: &mut Parser<'i, 't>,
773 allow_quirks: AllowQuirks,
774 ) -> Result<Self, ParseError<'i>> {
775 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
776 if !allow_quirks.allowed(context.quirks_mode) {
777 return Err(e);
778 }
779 Color::parse_quirky_color(input).map_err(|_| e)
780 })
781 }
782
783 fn parse_hash<'i>(
784 bytes: &[u8],
785 loc: &cssparser::SourceLocation,
786 ) -> Result<Self, ParseError<'i>> {
787 match cssparser::color::parse_hash_color(bytes) {
788 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
789 r, g, b, a,
790 ))),
791 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
792 }
793 }
794
795 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
799 let location = input.current_source_location();
800 let (value, unit) = match *input.next()? {
801 Token::Number {
802 int_value: Some(integer),
803 ..
804 } => (integer, None),
805 Token::Dimension {
806 int_value: Some(integer),
807 ref unit,
808 ..
809 } => (integer, Some(unit)),
810 Token::Ident(ref ident) => {
811 if ident.len() != 3 && ident.len() != 6 {
812 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
813 }
814 return Self::parse_hash(ident.as_bytes(), &location);
815 },
816 ref t => {
817 return Err(location.new_unexpected_token_error(t.clone()));
818 },
819 };
820 if value < 0 {
821 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
822 }
823 let length = if value <= 9 {
824 1
825 } else if value <= 99 {
826 2
827 } else if value <= 999 {
828 3
829 } else if value <= 9999 {
830 4
831 } else if value <= 99999 {
832 5
833 } else if value <= 999999 {
834 6
835 } else {
836 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
837 };
838 let total = length + unit.as_ref().map_or(0, |d| d.len());
839 if total > 6 {
840 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
841 }
842 let mut serialization = [b'0'; 6];
843 let space_padding = 6 - total;
844 let mut written = space_padding;
845 let mut buf = itoa::Buffer::new();
846 let s = buf.format(value);
847 (&mut serialization[written..])
848 .write_all(s.as_bytes())
849 .unwrap();
850 written += s.len();
851 if let Some(unit) = unit {
852 written += (&mut serialization[written..])
853 .write(unit.as_bytes())
854 .unwrap();
855 }
856 debug_assert_eq!(written, 6);
857 Self::parse_hash(&serialization, &location)
858 }
859}
860
861impl Color {
862 pub fn to_computed_color(&self, context: Option<&Context>) -> Result<ComputedColor, ()> {
867 macro_rules! adjust_absolute_color {
868 ($color:expr) => {{
869 if matches!(
871 $color.color_space,
872 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
873 ) {
874 $color.components.0 = normalize($color.components.0);
875 }
876
877 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
879 $color.components = $color.components.map(normalize);
880 }
881
882 $color.alpha = normalize($color.alpha);
883 }};
884 }
885
886 Ok(match *self {
887 Color::CurrentColor => ComputedColor::CurrentColor,
888 Color::Absolute(ref absolute) => {
889 let mut color = absolute.color;
890 adjust_absolute_color!(color);
891 ComputedColor::Absolute(color)
892 },
893 Color::ColorFunction(ref color_function) => {
894 color_function.to_computed_color(context)?
895 },
896 Color::LightDark(ref ld) => ld.compute(context.ok_or(())?),
897 Color::ColorMix(ref mix) => {
898 let mut items = ColorMixItemList::with_capacity(mix.items.len());
899 for item in mix.items.iter() {
900 items.push(GenericColorMixItem {
901 color: item.color.to_computed_color(context)?,
902 percentage: match context {
903 None => ComputedPercentage(item.percentage.to_percentage().ok_or(())?),
904 Some(ctx) => item.percentage.to_computed_value(ctx),
905 },
906 });
907 }
908
909 ComputedColor::from_color_mix(GenericColorMix {
910 interpolation: mix.interpolation,
911 items: OwnedSlice::from_slice(items.as_slice()),
912 flags: mix.flags,
913 })
914 },
915 Color::ContrastColor(ref c) => {
916 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
917 },
918 Color::System(system) => system.compute(context.ok_or(())?),
919 Color::InheritFromBodyQuirk => {
920 ComputedColor::Absolute(context.ok_or(())?.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 = color_function
944 .map_origin_color(|o| Ok(Self::from_computed_value(o)))
945 .unwrap();
946 Self::ColorFunction(Box::new(color_function))
947 },
948 ComputedColor::CurrentColor => Color::CurrentColor,
949 ComputedColor::ColorMix(ref mix) => {
950 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
951 },
952 ComputedColor::ContrastColor(ref c) => {
953 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
954 },
955 }
956 }
957}
958
959impl SpecifiedValueInfo for Color {
960 const SUPPORTED_TYPES: u8 = CssType::COLOR;
961
962 fn collect_completion_keywords(f: KeywordsCollectFn) {
963 f(&[
969 "currentColor",
970 "transparent",
971 "rgb",
972 "rgba",
973 "hsl",
974 "hsla",
975 "hwb",
976 "color",
977 "lab",
978 "lch",
979 "oklab",
980 "oklch",
981 "color-mix",
982 "contrast-color",
983 "light-dark",
984 ]);
985 }
986}
987
988#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
991#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
992pub struct ColorPropertyValue(pub Color);
993
994impl ToComputedValue for ColorPropertyValue {
995 type ComputedValue = AbsoluteColor;
996
997 #[inline]
998 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
999 let current_color = context.builder.get_parent_inherited_text().clone_color();
1000 self.0
1001 .to_computed_value(context)
1002 .resolve_to_absolute(¤t_color)
1003 }
1004
1005 #[inline]
1006 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1007 ColorPropertyValue(Color::from_absolute_color(*computed).into())
1008 }
1009}
1010
1011impl Parse for ColorPropertyValue {
1012 fn parse<'i, 't>(
1013 context: &ParserContext,
1014 input: &mut Parser<'i, 't>,
1015 ) -> Result<Self, ParseError<'i>> {
1016 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
1017 }
1018}
1019
1020pub type ColorOrAuto = GenericColorOrAuto<Color>;
1022
1023pub type CaretColor = GenericCaretColor<Color>;
1025
1026impl Parse for CaretColor {
1027 fn parse<'i, 't>(
1028 context: &ParserContext,
1029 input: &mut Parser<'i, 't>,
1030 ) -> Result<Self, ParseError<'i>> {
1031 ColorOrAuto::parse(context, input).map(GenericCaretColor)
1032 }
1033}
1034
1035#[derive(
1038 Clone,
1039 Copy,
1040 Debug,
1041 Default,
1042 Eq,
1043 MallocSizeOf,
1044 PartialEq,
1045 SpecifiedValueInfo,
1046 ToComputedValue,
1047 ToResolvedValue,
1048 ToShmem,
1049)]
1050#[repr(C)]
1051#[value_info(other_values = "light,dark,only")]
1052pub struct ColorSchemeFlags(u8);
1053bitflags! {
1054 impl ColorSchemeFlags: u8 {
1055 const LIGHT = 1 << 0;
1057 const DARK = 1 << 1;
1059 const ONLY = 1 << 2;
1061 }
1062}
1063
1064#[derive(
1066 Clone,
1067 Debug,
1068 Default,
1069 MallocSizeOf,
1070 PartialEq,
1071 SpecifiedValueInfo,
1072 ToComputedValue,
1073 ToResolvedValue,
1074 ToShmem,
1075 ToTyped,
1076)]
1077#[repr(C)]
1078#[typed(todo_derive_fields)]
1079#[value_info(other_values = "normal")]
1080pub struct ColorScheme {
1081 #[ignore_malloc_size_of = "Arc"]
1082 idents: crate::ArcSlice<CustomIdent>,
1083 pub bits: ColorSchemeFlags,
1085}
1086
1087impl ColorScheme {
1088 pub fn normal() -> Self {
1090 Self {
1091 idents: Default::default(),
1092 bits: ColorSchemeFlags::empty(),
1093 }
1094 }
1095
1096 pub fn raw_bits(&self) -> u8 {
1098 self.bits.bits()
1099 }
1100}
1101
1102impl Parse for ColorScheme {
1103 fn parse<'i, 't>(
1104 _: &ParserContext,
1105 input: &mut Parser<'i, 't>,
1106 ) -> Result<Self, ParseError<'i>> {
1107 let mut idents = vec![];
1108 let mut bits = ColorSchemeFlags::empty();
1109
1110 let mut location = input.current_source_location();
1111 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
1112 let mut is_only = false;
1113 match_ignore_ascii_case! { &ident,
1114 "normal" => {
1115 if idents.is_empty() && bits.is_empty() {
1116 return Ok(Self::normal());
1117 }
1118 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1119 },
1120 "light" => bits.insert(ColorSchemeFlags::LIGHT),
1121 "dark" => bits.insert(ColorSchemeFlags::DARK),
1122 "only" => {
1123 if bits.intersects(ColorSchemeFlags::ONLY) {
1124 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1125 }
1126 bits.insert(ColorSchemeFlags::ONLY);
1127 is_only = true;
1128 },
1129 _ => {},
1130 };
1131
1132 if is_only {
1133 if !idents.is_empty() {
1134 break;
1137 }
1138 } else {
1139 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
1140 }
1141 location = input.current_source_location();
1142 }
1143
1144 if idents.is_empty() {
1145 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1146 }
1147
1148 Ok(Self {
1149 idents: crate::ArcSlice::from_iter(idents.into_iter()),
1150 bits,
1151 })
1152 }
1153}
1154
1155impl ToCss for ColorScheme {
1156 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1157 where
1158 W: Write,
1159 {
1160 if self.idents.is_empty() {
1161 debug_assert!(self.bits.is_empty());
1162 return dest.write_str("normal");
1163 }
1164 let mut first = true;
1165 for ident in self.idents.iter() {
1166 if !first {
1167 dest.write_char(' ')?;
1168 }
1169 first = false;
1170 ident.to_css(dest)?;
1171 }
1172 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1173 dest.write_str(" only")?;
1174 }
1175 Ok(())
1176 }
1177}
1178
1179#[derive(
1181 Clone,
1182 Copy,
1183 Debug,
1184 MallocSizeOf,
1185 Parse,
1186 PartialEq,
1187 SpecifiedValueInfo,
1188 ToCss,
1189 ToComputedValue,
1190 ToResolvedValue,
1191 ToShmem,
1192 ToTyped,
1193)]
1194#[repr(u8)]
1195pub enum PrintColorAdjust {
1196 Economy,
1198 Exact,
1200}
1201
1202#[derive(
1204 Clone,
1205 Copy,
1206 Debug,
1207 MallocSizeOf,
1208 Parse,
1209 PartialEq,
1210 SpecifiedValueInfo,
1211 ToCss,
1212 ToComputedValue,
1213 ToResolvedValue,
1214 ToShmem,
1215 ToTyped,
1216)]
1217#[repr(u8)]
1218pub enum ForcedColorAdjust {
1219 Auto,
1221 None,
1223}
1224
1225#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1228#[repr(u8)]
1229pub enum ForcedColors {
1230 None,
1232 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1234 Requested,
1235 Active,
1237}
1238
1239impl ForcedColors {
1240 pub fn is_active(self) -> bool {
1242 matches!(self, Self::Active)
1243 }
1244}