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