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