1use super::AllowQuirks;
8use crate::color::mix::ColorInterpolationMethod;
9use crate::color::{parsing, AbsoluteColor, ColorFunction, 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, GenericColorOrAuto, GenericLightDark,
16};
17use crate::values::specified::Percentage;
18use crate::values::{normalize, CustomIdent};
19use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, ParseErrorKind, Parser, Token};
20use std::fmt::{self, Write};
21use std::io::Write as IoWrite;
22use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
23use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
24
25pub type ColorMix = GenericColorMix<Color, Percentage>;
27
28impl ColorMix {
29 fn parse<'i, 't>(
30 context: &ParserContext,
31 input: &mut Parser<'i, 't>,
32 preserve_authored: PreserveAuthored,
33 ) -> Result<Self, ParseError<'i>> {
34 input.expect_function_matching("color-mix")?;
35
36 input.parse_nested_block(|input| {
37 let interpolation = input
40 .try_parse(|input| -> Result<_, ParseError<'i>> {
41 let interpolation = ColorInterpolationMethod::parse(context, input)?;
42 input.expect_comma()?;
43 Ok(interpolation)
44 })
45 .unwrap_or_default();
46
47 let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
48 input
49 .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
50 .ok()
51 };
52
53 let mut left_percentage = try_parse_percentage(input);
54
55 let left = Color::parse_internal(context, input, preserve_authored)?;
56 if left_percentage.is_none() {
57 left_percentage = try_parse_percentage(input);
58 }
59
60 input.expect_comma()?;
61
62 let mut right_percentage = try_parse_percentage(input);
63
64 let right = Color::parse_internal(context, input, preserve_authored)?;
65
66 if right_percentage.is_none() {
67 right_percentage = try_parse_percentage(input);
68 }
69
70 let right_percentage = right_percentage
71 .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
72
73 let left_percentage =
74 left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
75
76 if left_percentage.get() + right_percentage.get() <= 0.0 {
77 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
79 }
80
81 Ok(ColorMix {
85 interpolation,
86 left,
87 left_percentage,
88 right,
89 right_percentage,
90 flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
91 })
92 })
93 }
94}
95
96#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
98pub struct Absolute {
99 pub color: AbsoluteColor,
101 pub authored: Option<Box<str>>,
103}
104
105impl ToCss for Absolute {
106 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
107 where
108 W: Write,
109 {
110 if let Some(ref authored) = self.authored {
111 dest.write_str(authored)
112 } else {
113 self.color.to_css(dest)
114 }
115 }
116}
117
118#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)]
120pub enum Color {
121 CurrentColor,
123 Absolute(Box<Absolute>),
126 ColorFunction(Box<ColorFunction<Self>>),
129 #[cfg(feature = "gecko")]
131 System(SystemColor),
132 ColorMix(Box<ColorMix>),
134 LightDark(Box<GenericLightDark<Self>>),
136 ContrastColor(Box<Color>),
138 #[cfg(feature = "gecko")]
140 InheritFromBodyQuirk,
141}
142
143impl From<AbsoluteColor> for Color {
144 #[inline]
145 fn from(value: AbsoluteColor) -> Self {
146 Self::from_absolute_color(value)
147 }
148}
149
150#[allow(missing_docs)]
159#[cfg(feature = "gecko")]
160#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
161#[repr(u8)]
162pub enum SystemColor {
163 Activeborder,
164 Activecaption,
166 Appworkspace,
167 Background,
168 Buttonface,
169 Buttonhighlight,
170 Buttonshadow,
171 Buttontext,
172 Buttonborder,
173 Captiontext,
175 #[parse(aliases = "-moz-field")]
176 Field,
177 #[parse(condition = "ParserContext::chrome_rules_enabled")]
179 MozDisabledfield,
180 #[parse(aliases = "-moz-fieldtext")]
181 Fieldtext,
182
183 Mark,
184 Marktext,
185
186 MozComboboxtext,
188 MozCombobox,
189
190 Graytext,
191 Highlight,
192 Highlighttext,
193 Inactiveborder,
194 Inactivecaption,
196 Inactivecaptiontext,
198 Infobackground,
199 Infotext,
200 Menu,
201 Menutext,
202 Scrollbar,
203 Threeddarkshadow,
204 Threedface,
205 Threedhighlight,
206 Threedlightshadow,
207 Threedshadow,
208 Window,
209 Windowframe,
210 Windowtext,
211 #[parse(aliases = "-moz-default-color")]
212 Canvastext,
213 #[parse(aliases = "-moz-default-background-color")]
214 Canvas,
215 MozDialog,
216 MozDialogtext,
217 #[parse(aliases = "-moz-html-cellhighlight")]
219 MozCellhighlight,
220 #[parse(aliases = "-moz-html-cellhighlighttext")]
222 MozCellhighlighttext,
223 Selecteditem,
225 Selecteditemtext,
227 MozMenuhover,
229 #[parse(condition = "ParserContext::chrome_rules_enabled")]
231 MozMenuhoverdisabled,
232 MozMenuhovertext,
234 MozMenubarhovertext,
236
237 MozOddtreerow,
240
241 #[parse(condition = "ParserContext::chrome_rules_enabled")]
243 MozButtonhoverface,
244 #[parse(condition = "ParserContext::chrome_rules_enabled")]
246 MozButtonhovertext,
247 #[parse(condition = "ParserContext::chrome_rules_enabled")]
249 MozButtonhoverborder,
250 #[parse(condition = "ParserContext::chrome_rules_enabled")]
252 MozButtonactiveface,
253 #[parse(condition = "ParserContext::chrome_rules_enabled")]
255 MozButtonactivetext,
256 #[parse(condition = "ParserContext::chrome_rules_enabled")]
258 MozButtonactiveborder,
259
260 #[parse(condition = "ParserContext::chrome_rules_enabled")]
262 MozButtondisabledface,
263 #[parse(condition = "ParserContext::chrome_rules_enabled")]
265 MozButtondisabledborder,
266
267 #[parse(condition = "ParserContext::chrome_rules_enabled")]
269 MozHeaderbar,
270 #[parse(condition = "ParserContext::chrome_rules_enabled")]
271 MozHeaderbartext,
272 #[parse(condition = "ParserContext::chrome_rules_enabled")]
273 MozHeaderbarinactive,
274 #[parse(condition = "ParserContext::chrome_rules_enabled")]
275 MozHeaderbarinactivetext,
276
277 #[parse(condition = "ParserContext::chrome_rules_enabled")]
279 MozMacDefaultbuttontext,
280 #[parse(condition = "ParserContext::chrome_rules_enabled")]
282 MozMacFocusring,
283 #[parse(condition = "ParserContext::chrome_rules_enabled")]
285 MozMacDisabledtoolbartext,
286 #[parse(condition = "ParserContext::chrome_rules_enabled")]
288 MozSidebar,
289 #[parse(condition = "ParserContext::chrome_rules_enabled")]
291 MozSidebartext,
292 #[parse(condition = "ParserContext::chrome_rules_enabled")]
294 MozSidebarborder,
295
296 Accentcolor,
299
300 Accentcolortext,
303
304 #[parse(condition = "ParserContext::chrome_rules_enabled")]
306 MozAutofillBackground,
307
308 #[parse(aliases = "-moz-hyperlinktext")]
309 Linktext,
310 #[parse(aliases = "-moz-activehyperlinktext")]
311 Activetext,
312 #[parse(aliases = "-moz-visitedhyperlinktext")]
313 Visitedtext,
314
315 #[parse(condition = "ParserContext::chrome_rules_enabled")]
317 MozColheader,
318 #[parse(condition = "ParserContext::chrome_rules_enabled")]
319 MozColheadertext,
320 #[parse(condition = "ParserContext::chrome_rules_enabled")]
321 MozColheaderhover,
322 #[parse(condition = "ParserContext::chrome_rules_enabled")]
323 MozColheaderhovertext,
324 #[parse(condition = "ParserContext::chrome_rules_enabled")]
325 MozColheaderactive,
326 #[parse(condition = "ParserContext::chrome_rules_enabled")]
327 MozColheaderactivetext,
328
329 #[parse(condition = "ParserContext::chrome_rules_enabled")]
330 TextSelectDisabledBackground,
331 #[css(skip)]
332 TextSelectAttentionBackground,
333 #[css(skip)]
334 TextSelectAttentionForeground,
335 #[css(skip)]
336 TextHighlightBackground,
337 #[css(skip)]
338 TextHighlightForeground,
339 #[css(skip)]
340 TargetTextBackground,
341 #[css(skip)]
342 TargetTextForeground,
343 #[css(skip)]
344 IMERawInputBackground,
345 #[css(skip)]
346 IMERawInputForeground,
347 #[css(skip)]
348 IMERawInputUnderline,
349 #[css(skip)]
350 IMESelectedRawTextBackground,
351 #[css(skip)]
352 IMESelectedRawTextForeground,
353 #[css(skip)]
354 IMESelectedRawTextUnderline,
355 #[css(skip)]
356 IMEConvertedTextBackground,
357 #[css(skip)]
358 IMEConvertedTextForeground,
359 #[css(skip)]
360 IMEConvertedTextUnderline,
361 #[css(skip)]
362 IMESelectedConvertedTextBackground,
363 #[css(skip)]
364 IMESelectedConvertedTextForeground,
365 #[css(skip)]
366 IMESelectedConvertedTextUnderline,
367 #[css(skip)]
368 SpellCheckerUnderline,
369 #[css(skip)]
370 ThemedScrollbar,
371 #[css(skip)]
372 ThemedScrollbarThumb,
373 #[css(skip)]
374 ThemedScrollbarThumbHover,
375 #[css(skip)]
376 ThemedScrollbarThumbActive,
377
378 #[css(skip)]
379 End, }
381
382#[cfg(feature = "gecko")]
383impl SystemColor {
384 #[inline]
385 fn compute(&self, cx: &Context) -> ComputedColor {
386 use crate::gecko::values::convert_nscolor_to_absolute_color;
387 use crate::gecko_bindings::bindings;
388
389 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
390 if cx.for_non_inherited_property {
391 cx.rule_cache_conditions
392 .borrow_mut()
393 .set_color_scheme_dependency(cx.builder.color_scheme);
394 }
395 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
396 return ComputedColor::currentcolor();
397 }
398 ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
399 }
400}
401
402#[derive(Copy, Clone)]
405enum PreserveAuthored {
406 No,
407 Yes,
408}
409
410impl Parse for Color {
411 fn parse<'i, 't>(
412 context: &ParserContext,
413 input: &mut Parser<'i, 't>,
414 ) -> Result<Self, ParseError<'i>> {
415 Self::parse_internal(context, input, PreserveAuthored::Yes)
416 }
417}
418
419impl Color {
420 fn parse_internal<'i, 't>(
421 context: &ParserContext,
422 input: &mut Parser<'i, 't>,
423 preserve_authored: PreserveAuthored,
424 ) -> Result<Self, ParseError<'i>> {
425 let authored = match preserve_authored {
426 PreserveAuthored::No => None,
427 PreserveAuthored::Yes => {
428 let start = input.state();
432 let authored = input.expect_ident_cloned().ok();
433 input.reset(&start);
434 authored
435 },
436 };
437
438 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
439 Ok(mut color) => {
440 if let Color::Absolute(ref mut absolute) = color {
441 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
444 }
445 Ok(color)
446 },
447 Err(e) => {
448 #[cfg(feature = "gecko")]
449 {
450 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
451 return Ok(Color::System(system));
452 }
453 }
454
455 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
456 {
457 return Ok(Color::ColorMix(Box::new(mix)));
458 }
459
460 if let Ok(ld) = input.try_parse(|i| {
461 GenericLightDark::parse_with(i, |i| {
462 Self::parse_internal(context, i, preserve_authored)
463 })
464 }) {
465 return Ok(Color::LightDark(Box::new(ld)));
466 }
467
468 if static_prefs::pref!("layout.css.contrast-color.enabled") {
469 if let Ok(c) = input.try_parse(|i| {
470 i.expect_function_matching("contrast-color")?;
471 i.parse_nested_block(|i| {
472 Self::parse_internal(context, i, preserve_authored)
473 })
474 }) {
475 return Ok(Color::ContrastColor(Box::new(c)));
476 }
477 }
478
479 match e.kind {
480 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
481 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
482 ValueParseErrorKind::InvalidColor(t),
483 )))
484 },
485 _ => Err(e),
486 }
487 },
488 }
489 }
490
491 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
493 input
494 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
495 .is_ok()
496 }
497
498 pub fn parse_and_compute(
500 context: &ParserContext,
501 input: &mut Parser,
502 device: Option<&Device>,
503 ) -> Option<ComputedColor> {
504 use crate::error_reporting::ContextualParseError;
505 let start = input.position();
506 let result = input
507 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
508
509 let specified = match result {
510 Ok(s) => s,
511 Err(e) => {
512 if !context.error_reporting_enabled() {
513 return None;
514 }
515 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
523 let location = e.location.clone();
524 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
525 context.log_css_error(location, error);
526 }
527 return None;
528 },
529 };
530
531 match device {
532 Some(device) => {
533 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
534 specified.to_computed_color(Some(&context))
535 })
536 },
537 None => specified.to_computed_color(None),
538 }
539 }
540}
541
542impl ToCss for Color {
543 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
544 where
545 W: Write,
546 {
547 match *self {
548 Color::CurrentColor => dest.write_str("currentcolor"),
549 Color::Absolute(ref absolute) => absolute.to_css(dest),
550 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
551 Color::ColorMix(ref mix) => mix.to_css(dest),
552 Color::LightDark(ref ld) => ld.to_css(dest),
553 Color::ContrastColor(ref c) => {
554 dest.write_str("contrast-color(")?;
555 c.to_css(dest)?;
556 dest.write_char(')')
557 },
558 #[cfg(feature = "gecko")]
559 Color::System(system) => system.to_css(dest),
560 #[cfg(feature = "gecko")]
561 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
562 }
563 }
564}
565
566impl Color {
567 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
569 match *self {
570 #[cfg(feature = "gecko")]
571 Self::InheritFromBodyQuirk => false,
572 Self::CurrentColor => true,
573 #[cfg(feature = "gecko")]
574 Self::System(..) => true,
575 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
576 Self::ColorFunction(ref color_function) => {
577 color_function
580 .resolve_to_absolute()
581 .map(|resolved| allow_transparent && resolved.is_transparent())
582 .unwrap_or(false)
583 },
584 Self::LightDark(ref ld) => {
585 ld.light.honored_in_forced_colors_mode(allow_transparent)
586 && ld.dark.honored_in_forced_colors_mode(allow_transparent)
587 },
588 Self::ColorMix(ref mix) => {
589 mix.left.honored_in_forced_colors_mode(allow_transparent)
590 && mix.right.honored_in_forced_colors_mode(allow_transparent)
591 },
592 Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent),
593 }
594 }
595
596 #[inline]
598 pub fn currentcolor() -> Self {
599 Self::CurrentColor
600 }
601
602 #[inline]
604 pub fn transparent() -> Self {
605 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
607 }
608
609 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
611 Color::Absolute(Box::new(Absolute {
612 color,
613 authored: None,
614 }))
615 }
616
617 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
622 use crate::values::specified::percentage::ToPercentage;
623
624 match self {
625 Self::Absolute(c) => Some(c.color),
626 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
627 Self::ColorMix(ref mix) => {
628 let left = mix.left.resolve_to_absolute()?;
629 let right = mix.right.resolve_to_absolute()?;
630 Some(crate::color::mix::mix(
631 mix.interpolation,
632 &left,
633 mix.left_percentage.to_percentage(),
634 &right,
635 mix.right_percentage.to_percentage(),
636 mix.flags,
637 ))
638 },
639 _ => None,
640 }
641 }
642
643 pub fn parse_quirky<'i, 't>(
647 context: &ParserContext,
648 input: &mut Parser<'i, 't>,
649 allow_quirks: AllowQuirks,
650 ) -> Result<Self, ParseError<'i>> {
651 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
652 if !allow_quirks.allowed(context.quirks_mode) {
653 return Err(e);
654 }
655 Color::parse_quirky_color(input).map_err(|_| e)
656 })
657 }
658
659 fn parse_hash<'i>(
660 bytes: &[u8],
661 loc: &cssparser::SourceLocation,
662 ) -> Result<Self, ParseError<'i>> {
663 match cssparser::color::parse_hash_color(bytes) {
664 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
665 r, g, b, a,
666 ))),
667 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
668 }
669 }
670
671 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
675 let location = input.current_source_location();
676 let (value, unit) = match *input.next()? {
677 Token::Number {
678 int_value: Some(integer),
679 ..
680 } => (integer, None),
681 Token::Dimension {
682 int_value: Some(integer),
683 ref unit,
684 ..
685 } => (integer, Some(unit)),
686 Token::Ident(ref ident) => {
687 if ident.len() != 3 && ident.len() != 6 {
688 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
689 }
690 return Self::parse_hash(ident.as_bytes(), &location);
691 },
692 ref t => {
693 return Err(location.new_unexpected_token_error(t.clone()));
694 },
695 };
696 if value < 0 {
697 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
698 }
699 let length = if value <= 9 {
700 1
701 } else if value <= 99 {
702 2
703 } else if value <= 999 {
704 3
705 } else if value <= 9999 {
706 4
707 } else if value <= 99999 {
708 5
709 } else if value <= 999999 {
710 6
711 } else {
712 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
713 };
714 let total = length + unit.as_ref().map_or(0, |d| d.len());
715 if total > 6 {
716 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
717 }
718 let mut serialization = [b'0'; 6];
719 let space_padding = 6 - total;
720 let mut written = space_padding;
721 let mut buf = itoa::Buffer::new();
722 let s = buf.format(value);
723 (&mut serialization[written..])
724 .write_all(s.as_bytes())
725 .unwrap();
726 written += s.len();
727 if let Some(unit) = unit {
728 written += (&mut serialization[written..])
729 .write(unit.as_bytes())
730 .unwrap();
731 }
732 debug_assert_eq!(written, 6);
733 Self::parse_hash(&serialization, &location)
734 }
735}
736
737impl Color {
738 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
743 macro_rules! adjust_absolute_color {
744 ($color:expr) => {{
745 if matches!(
747 $color.color_space,
748 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
749 ) {
750 $color.components.0 = normalize($color.components.0);
751 }
752
753 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
755 $color.components = $color.components.map(normalize);
756 }
757
758 $color.alpha = normalize($color.alpha);
759 }};
760 }
761
762 Some(match *self {
763 Color::CurrentColor => ComputedColor::CurrentColor,
764 Color::Absolute(ref absolute) => {
765 let mut color = absolute.color;
766 adjust_absolute_color!(color);
767 ComputedColor::Absolute(color)
768 },
769 Color::ColorFunction(ref color_function) => {
770 debug_assert!(color_function.has_origin_color(),
771 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
772
773 if let Ok(absolute) = color_function.resolve_to_absolute() {
775 ComputedColor::Absolute(absolute)
776 } else {
777 let color_function = color_function
778 .map_origin_color(|origin_color| origin_color.to_computed_color(context));
779 ComputedColor::ColorFunction(Box::new(color_function))
780 }
781 },
782 Color::LightDark(ref ld) => ld.compute(context?),
783 Color::ColorMix(ref mix) => {
784 use crate::values::computed::percentage::Percentage;
785
786 let left = mix.left.to_computed_color(context)?;
787 let right = mix.right.to_computed_color(context)?;
788
789 ComputedColor::from_color_mix(GenericColorMix {
790 interpolation: mix.interpolation,
791 left,
792 left_percentage: Percentage(mix.left_percentage.get()),
793 right,
794 right_percentage: Percentage(mix.right_percentage.get()),
795 flags: mix.flags,
796 })
797 },
798 Color::ContrastColor(ref c) => {
799 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?))
800 },
801 #[cfg(feature = "gecko")]
802 Color::System(system) => system.compute(context?),
803 #[cfg(feature = "gecko")]
804 Color::InheritFromBodyQuirk => {
805 ComputedColor::Absolute(context?.device().body_text_color())
806 },
807 })
808 }
809}
810
811impl ToComputedValue for Color {
812 type ComputedValue = ComputedColor;
813
814 fn to_computed_value(&self, context: &Context) -> ComputedColor {
815 self.to_computed_color(Some(context)).unwrap_or_else(|| {
816 debug_assert!(
817 false,
818 "Specified color could not be resolved to a computed color!"
819 );
820 ComputedColor::Absolute(AbsoluteColor::BLACK)
821 })
822 }
823
824 fn from_computed_value(computed: &ComputedColor) -> Self {
825 match *computed {
826 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
827 ComputedColor::ColorFunction(ref color_function) => {
828 let color_function =
829 color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
830 Self::ColorFunction(Box::new(color_function))
831 },
832 ComputedColor::CurrentColor => Color::CurrentColor,
833 ComputedColor::ColorMix(ref mix) => {
834 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
835 },
836 ComputedColor::ContrastColor(ref c) => {
837 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c)))
838 },
839 }
840 }
841}
842
843impl SpecifiedValueInfo for Color {
844 const SUPPORTED_TYPES: u8 = CssType::COLOR;
845
846 fn collect_completion_keywords(f: KeywordsCollectFn) {
847 f(&[
853 "currentColor",
854 "transparent",
855 "rgb",
856 "rgba",
857 "hsl",
858 "hsla",
859 "hwb",
860 "color",
861 "lab",
862 "lch",
863 "oklab",
864 "oklch",
865 "color-mix",
866 "contrast-color",
867 "light-dark",
868 ]);
869 }
870}
871
872#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
875#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
876pub struct ColorPropertyValue(pub Color);
877
878impl ToComputedValue for ColorPropertyValue {
879 type ComputedValue = AbsoluteColor;
880
881 #[inline]
882 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
883 let current_color = context.builder.get_parent_inherited_text().clone_color();
884 self.0
885 .to_computed_value(context)
886 .resolve_to_absolute(¤t_color)
887 }
888
889 #[inline]
890 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
891 ColorPropertyValue(Color::from_absolute_color(*computed).into())
892 }
893}
894
895impl Parse for ColorPropertyValue {
896 fn parse<'i, 't>(
897 context: &ParserContext,
898 input: &mut Parser<'i, 't>,
899 ) -> Result<Self, ParseError<'i>> {
900 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
901 }
902}
903
904pub type ColorOrAuto = GenericColorOrAuto<Color>;
906
907pub type CaretColor = GenericCaretColor<Color>;
909
910impl Parse for CaretColor {
911 fn parse<'i, 't>(
912 context: &ParserContext,
913 input: &mut Parser<'i, 't>,
914 ) -> Result<Self, ParseError<'i>> {
915 ColorOrAuto::parse(context, input).map(GenericCaretColor)
916 }
917}
918
919#[derive(
922 Clone,
923 Copy,
924 Debug,
925 Default,
926 Eq,
927 MallocSizeOf,
928 PartialEq,
929 SpecifiedValueInfo,
930 ToComputedValue,
931 ToResolvedValue,
932 ToShmem,
933)]
934#[repr(C)]
935#[value_info(other_values = "light,dark,only")]
936pub struct ColorSchemeFlags(u8);
937bitflags! {
938 impl ColorSchemeFlags: u8 {
939 const LIGHT = 1 << 0;
941 const DARK = 1 << 1;
943 const ONLY = 1 << 2;
945 }
946}
947
948#[derive(
950 Clone,
951 Debug,
952 Default,
953 MallocSizeOf,
954 PartialEq,
955 SpecifiedValueInfo,
956 ToComputedValue,
957 ToResolvedValue,
958 ToShmem,
959 ToTyped,
960)]
961#[repr(C)]
962#[value_info(other_values = "normal")]
963pub struct ColorScheme {
964 #[ignore_malloc_size_of = "Arc"]
965 idents: crate::ArcSlice<CustomIdent>,
966 pub bits: ColorSchemeFlags,
968}
969
970impl ColorScheme {
971 pub fn normal() -> Self {
973 Self {
974 idents: Default::default(),
975 bits: ColorSchemeFlags::empty(),
976 }
977 }
978
979 pub fn raw_bits(&self) -> u8 {
981 self.bits.bits()
982 }
983}
984
985impl Parse for ColorScheme {
986 fn parse<'i, 't>(
987 _: &ParserContext,
988 input: &mut Parser<'i, 't>,
989 ) -> Result<Self, ParseError<'i>> {
990 let mut idents = vec![];
991 let mut bits = ColorSchemeFlags::empty();
992
993 let mut location = input.current_source_location();
994 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
995 let mut is_only = false;
996 match_ignore_ascii_case! { &ident,
997 "normal" => {
998 if idents.is_empty() && bits.is_empty() {
999 return Ok(Self::normal());
1000 }
1001 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1002 },
1003 "light" => bits.insert(ColorSchemeFlags::LIGHT),
1004 "dark" => bits.insert(ColorSchemeFlags::DARK),
1005 "only" => {
1006 if bits.intersects(ColorSchemeFlags::ONLY) {
1007 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1008 }
1009 bits.insert(ColorSchemeFlags::ONLY);
1010 is_only = true;
1011 },
1012 _ => {},
1013 };
1014
1015 if is_only {
1016 if !idents.is_empty() {
1017 break;
1020 }
1021 } else {
1022 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
1023 }
1024 location = input.current_source_location();
1025 }
1026
1027 if idents.is_empty() {
1028 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1029 }
1030
1031 Ok(Self {
1032 idents: crate::ArcSlice::from_iter(idents.into_iter()),
1033 bits,
1034 })
1035 }
1036}
1037
1038impl ToCss for ColorScheme {
1039 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1040 where
1041 W: Write,
1042 {
1043 if self.idents.is_empty() {
1044 debug_assert!(self.bits.is_empty());
1045 return dest.write_str("normal");
1046 }
1047 let mut first = true;
1048 for ident in self.idents.iter() {
1049 if !first {
1050 dest.write_char(' ')?;
1051 }
1052 first = false;
1053 ident.to_css(dest)?;
1054 }
1055 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1056 dest.write_str(" only")?;
1057 }
1058 Ok(())
1059 }
1060}
1061
1062#[derive(
1064 Clone,
1065 Copy,
1066 Debug,
1067 MallocSizeOf,
1068 Parse,
1069 PartialEq,
1070 SpecifiedValueInfo,
1071 ToCss,
1072 ToComputedValue,
1073 ToResolvedValue,
1074 ToShmem,
1075 ToTyped,
1076)]
1077#[repr(u8)]
1078pub enum PrintColorAdjust {
1079 Economy,
1081 Exact,
1083}
1084
1085#[derive(
1087 Clone,
1088 Copy,
1089 Debug,
1090 MallocSizeOf,
1091 Parse,
1092 PartialEq,
1093 SpecifiedValueInfo,
1094 ToCss,
1095 ToComputedValue,
1096 ToResolvedValue,
1097 ToShmem,
1098 ToTyped,
1099)]
1100#[repr(u8)]
1101pub enum ForcedColorAdjust {
1102 Auto,
1104 None,
1106}
1107
1108#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1111#[repr(u8)]
1112pub enum ForcedColors {
1113 None,
1115 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1117 Requested,
1118 Active,
1120}
1121
1122impl ForcedColors {
1123 pub fn is_active(self) -> bool {
1125 matches!(self, Self::Active)
1126 }
1127}