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)]
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 #[cfg(feature = "gecko")]
130 InheritFromBodyQuirk,
131}
132
133impl From<AbsoluteColor> for Color {
134 #[inline]
135 fn from(value: AbsoluteColor) -> Self {
136 Self::from_absolute_color(value)
137 }
138}
139
140#[allow(missing_docs)]
149#[cfg(feature = "gecko")]
150#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
151#[repr(u8)]
152pub enum SystemColor {
153 Activeborder,
154 Activecaption,
156 Appworkspace,
157 Background,
158 Buttonface,
159 Buttonhighlight,
160 Buttonshadow,
161 Buttontext,
162 Buttonborder,
163 Captiontext,
165 #[parse(aliases = "-moz-field")]
166 Field,
167 #[parse(condition = "ParserContext::chrome_rules_enabled")]
169 MozDisabledfield,
170 #[parse(aliases = "-moz-fieldtext")]
171 Fieldtext,
172
173 Mark,
174 Marktext,
175
176 MozComboboxtext,
178 MozCombobox,
179
180 Graytext,
181 Highlight,
182 Highlighttext,
183 Inactiveborder,
184 Inactivecaption,
186 Inactivecaptiontext,
188 Infobackground,
189 Infotext,
190 Menu,
191 Menutext,
192 Scrollbar,
193 Threeddarkshadow,
194 Threedface,
195 Threedhighlight,
196 Threedlightshadow,
197 Threedshadow,
198 Window,
199 Windowframe,
200 Windowtext,
201 #[parse(aliases = "-moz-default-color")]
202 Canvastext,
203 #[parse(aliases = "-moz-default-background-color")]
204 Canvas,
205 MozDialog,
206 MozDialogtext,
207 #[parse(aliases = "-moz-html-cellhighlight")]
209 MozCellhighlight,
210 #[parse(aliases = "-moz-html-cellhighlighttext")]
212 MozCellhighlighttext,
213 Selecteditem,
215 Selecteditemtext,
217 MozMenuhover,
219 #[parse(condition = "ParserContext::chrome_rules_enabled")]
221 MozMenuhoverdisabled,
222 MozMenuhovertext,
224 MozMenubarhovertext,
226
227 MozOddtreerow,
230
231 #[parse(condition = "ParserContext::chrome_rules_enabled")]
233 MozButtonhoverface,
234 #[parse(condition = "ParserContext::chrome_rules_enabled")]
236 MozButtonhovertext,
237 #[parse(condition = "ParserContext::chrome_rules_enabled")]
239 MozButtonhoverborder,
240 #[parse(condition = "ParserContext::chrome_rules_enabled")]
242 MozButtonactiveface,
243 #[parse(condition = "ParserContext::chrome_rules_enabled")]
245 MozButtonactivetext,
246 #[parse(condition = "ParserContext::chrome_rules_enabled")]
248 MozButtonactiveborder,
249
250 #[parse(condition = "ParserContext::chrome_rules_enabled")]
252 MozButtondisabledface,
253 #[parse(condition = "ParserContext::chrome_rules_enabled")]
255 MozButtondisabledborder,
256
257 #[parse(condition = "ParserContext::chrome_rules_enabled")]
259 MozHeaderbar,
260 #[parse(condition = "ParserContext::chrome_rules_enabled")]
261 MozHeaderbartext,
262 #[parse(condition = "ParserContext::chrome_rules_enabled")]
263 MozHeaderbarinactive,
264 #[parse(condition = "ParserContext::chrome_rules_enabled")]
265 MozHeaderbarinactivetext,
266
267 #[parse(condition = "ParserContext::chrome_rules_enabled")]
269 MozMacDefaultbuttontext,
270 #[parse(condition = "ParserContext::chrome_rules_enabled")]
272 MozMacFocusring,
273 #[parse(condition = "ParserContext::chrome_rules_enabled")]
275 MozMacDisabledtoolbartext,
276 #[parse(condition = "ParserContext::chrome_rules_enabled")]
278 MozSidebar,
279 #[parse(condition = "ParserContext::chrome_rules_enabled")]
281 MozSidebartext,
282 #[parse(condition = "ParserContext::chrome_rules_enabled")]
284 MozSidebarborder,
285
286 Accentcolor,
289
290 Accentcolortext,
293
294 #[parse(condition = "ParserContext::chrome_rules_enabled")]
296 MozAutofillBackground,
297
298 #[parse(aliases = "-moz-hyperlinktext")]
299 Linktext,
300 #[parse(aliases = "-moz-activehyperlinktext")]
301 Activetext,
302 #[parse(aliases = "-moz-visitedhyperlinktext")]
303 Visitedtext,
304
305 #[parse(condition = "ParserContext::chrome_rules_enabled")]
307 MozColheader,
308 #[parse(condition = "ParserContext::chrome_rules_enabled")]
309 MozColheadertext,
310 #[parse(condition = "ParserContext::chrome_rules_enabled")]
311 MozColheaderhover,
312 #[parse(condition = "ParserContext::chrome_rules_enabled")]
313 MozColheaderhovertext,
314 #[parse(condition = "ParserContext::chrome_rules_enabled")]
315 MozColheaderactive,
316 #[parse(condition = "ParserContext::chrome_rules_enabled")]
317 MozColheaderactivetext,
318
319 #[parse(condition = "ParserContext::chrome_rules_enabled")]
320 TextSelectDisabledBackground,
321 #[css(skip)]
322 TextSelectAttentionBackground,
323 #[css(skip)]
324 TextSelectAttentionForeground,
325 #[css(skip)]
326 TextHighlightBackground,
327 #[css(skip)]
328 TextHighlightForeground,
329 #[css(skip)]
330 TargetTextBackground,
331 #[css(skip)]
332 TargetTextForeground,
333 #[css(skip)]
334 IMERawInputBackground,
335 #[css(skip)]
336 IMERawInputForeground,
337 #[css(skip)]
338 IMERawInputUnderline,
339 #[css(skip)]
340 IMESelectedRawTextBackground,
341 #[css(skip)]
342 IMESelectedRawTextForeground,
343 #[css(skip)]
344 IMESelectedRawTextUnderline,
345 #[css(skip)]
346 IMEConvertedTextBackground,
347 #[css(skip)]
348 IMEConvertedTextForeground,
349 #[css(skip)]
350 IMEConvertedTextUnderline,
351 #[css(skip)]
352 IMESelectedConvertedTextBackground,
353 #[css(skip)]
354 IMESelectedConvertedTextForeground,
355 #[css(skip)]
356 IMESelectedConvertedTextUnderline,
357 #[css(skip)]
358 SpellCheckerUnderline,
359 #[css(skip)]
360 ThemedScrollbar,
361 #[css(skip)]
362 ThemedScrollbarThumb,
363 #[css(skip)]
364 ThemedScrollbarThumbHover,
365 #[css(skip)]
366 ThemedScrollbarThumbActive,
367
368 #[css(skip)]
369 End, }
371
372#[cfg(feature = "gecko")]
373impl SystemColor {
374 #[inline]
375 fn compute(&self, cx: &Context) -> ComputedColor {
376 use crate::gecko::values::convert_nscolor_to_absolute_color;
377 use crate::gecko_bindings::bindings;
378
379 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
380 if cx.for_non_inherited_property {
381 cx.rule_cache_conditions
382 .borrow_mut()
383 .set_color_scheme_dependency(cx.builder.color_scheme);
384 }
385 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
386 return ComputedColor::currentcolor();
387 }
388 ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
389 }
390}
391
392#[derive(Copy, Clone)]
395enum PreserveAuthored {
396 No,
397 Yes,
398}
399
400impl Parse for Color {
401 fn parse<'i, 't>(
402 context: &ParserContext,
403 input: &mut Parser<'i, 't>,
404 ) -> Result<Self, ParseError<'i>> {
405 Self::parse_internal(context, input, PreserveAuthored::Yes)
406 }
407}
408
409impl Color {
410 fn parse_internal<'i, 't>(
411 context: &ParserContext,
412 input: &mut Parser<'i, 't>,
413 preserve_authored: PreserveAuthored,
414 ) -> Result<Self, ParseError<'i>> {
415 let authored = match preserve_authored {
416 PreserveAuthored::No => None,
417 PreserveAuthored::Yes => {
418 let start = input.state();
422 let authored = input.expect_ident_cloned().ok();
423 input.reset(&start);
424 authored
425 },
426 };
427
428 match input.try_parse(|i| parsing::parse_color_with(context, i)) {
429 Ok(mut color) => {
430 if let Color::Absolute(ref mut absolute) = color {
431 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
434 }
435 Ok(color)
436 },
437 Err(e) => {
438 #[cfg(feature = "gecko")]
439 {
440 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
441 return Ok(Color::System(system));
442 }
443 }
444
445 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
446 {
447 return Ok(Color::ColorMix(Box::new(mix)));
448 }
449
450 if let Ok(ld) = input.try_parse(|i| GenericLightDark::parse_with(i, |i| Self::parse_internal(context, i, preserve_authored)))
451 {
452 return Ok(Color::LightDark(Box::new(ld)));
453 }
454
455 match e.kind {
456 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
457 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
458 ValueParseErrorKind::InvalidColor(t),
459 )))
460 },
461 _ => Err(e),
462 }
463 },
464 }
465 }
466
467 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
469 input
470 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
471 .is_ok()
472 }
473
474 pub fn parse_and_compute(
476 context: &ParserContext,
477 input: &mut Parser,
478 device: Option<&Device>,
479 ) -> Option<ComputedColor> {
480 use crate::error_reporting::ContextualParseError;
481 let start = input.position();
482 let result = input
483 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
484
485 let specified = match result {
486 Ok(s) => s,
487 Err(e) => {
488 if !context.error_reporting_enabled() {
489 return None;
490 }
491 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
499 let location = e.location.clone();
500 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
501 context.log_css_error(location, error);
502 }
503 return None;
504 },
505 };
506
507 match device {
508 Some(device) => {
509 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
510 specified.to_computed_color(Some(&context))
511 })
512 },
513 None => specified.to_computed_color(None),
514 }
515 }
516}
517
518impl ToCss for Color {
519 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
520 where
521 W: Write,
522 {
523 match *self {
524 Color::CurrentColor => dest.write_str("currentcolor"),
525 Color::Absolute(ref absolute) => absolute.to_css(dest),
526 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
527 Color::ColorMix(ref mix) => mix.to_css(dest),
528 Color::LightDark(ref ld) => ld.to_css(dest),
529 #[cfg(feature = "gecko")]
530 Color::System(system) => system.to_css(dest),
531 #[cfg(feature = "gecko")]
532 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
533 }
534 }
535}
536
537impl Color {
538 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
540 match *self {
541 #[cfg(feature = "gecko")]
542 Self::InheritFromBodyQuirk => false,
543 Self::CurrentColor => true,
544 #[cfg(feature = "gecko")]
545 Self::System(..) => true,
546 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
547 Self::ColorFunction(ref color_function) => {
548 color_function
551 .resolve_to_absolute()
552 .map(|resolved| allow_transparent && resolved.is_transparent())
553 .unwrap_or(false)
554 },
555 Self::LightDark(ref ld) => {
556 ld.light.honored_in_forced_colors_mode(allow_transparent) &&
557 ld.dark.honored_in_forced_colors_mode(allow_transparent)
558 },
559 Self::ColorMix(ref mix) => {
560 mix.left.honored_in_forced_colors_mode(allow_transparent) &&
561 mix.right.honored_in_forced_colors_mode(allow_transparent)
562 },
563 }
564 }
565
566 #[inline]
568 pub fn currentcolor() -> Self {
569 Self::CurrentColor
570 }
571
572 #[inline]
574 pub fn transparent() -> Self {
575 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
577 }
578
579 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
581 Color::Absolute(Box::new(Absolute {
582 color,
583 authored: None,
584 }))
585 }
586
587 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
592 use crate::values::specified::percentage::ToPercentage;
593
594 match self {
595 Self::Absolute(c) => Some(c.color),
596 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
597 Self::ColorMix(ref mix) => {
598 let left = mix.left.resolve_to_absolute()?;
599 let right = mix.right.resolve_to_absolute()?;
600 Some(crate::color::mix::mix(
601 mix.interpolation,
602 &left,
603 mix.left_percentage.to_percentage(),
604 &right,
605 mix.right_percentage.to_percentage(),
606 mix.flags,
607 ))
608 },
609 _ => None,
610 }
611 }
612
613 pub fn parse_quirky<'i, 't>(
617 context: &ParserContext,
618 input: &mut Parser<'i, 't>,
619 allow_quirks: AllowQuirks,
620 ) -> Result<Self, ParseError<'i>> {
621 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
622 if !allow_quirks.allowed(context.quirks_mode) {
623 return Err(e);
624 }
625 Color::parse_quirky_color(input).map_err(|_| e)
626 })
627 }
628
629 fn parse_hash<'i>(
630 bytes: &[u8],
631 loc: &cssparser::SourceLocation,
632 ) -> Result<Self, ParseError<'i>> {
633 match cssparser::color::parse_hash_color(bytes) {
634 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
635 r, g, b, a,
636 ))),
637 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
638 }
639 }
640
641 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
645 let location = input.current_source_location();
646 let (value, unit) = match *input.next()? {
647 Token::Number {
648 int_value: Some(integer),
649 ..
650 } => (integer, None),
651 Token::Dimension {
652 int_value: Some(integer),
653 ref unit,
654 ..
655 } => (integer, Some(unit)),
656 Token::Ident(ref ident) => {
657 if ident.len() != 3 && ident.len() != 6 {
658 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
659 }
660 return Self::parse_hash(ident.as_bytes(), &location);
661 },
662 ref t => {
663 return Err(location.new_unexpected_token_error(t.clone()));
664 },
665 };
666 if value < 0 {
667 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
668 }
669 let length = if value <= 9 {
670 1
671 } else if value <= 99 {
672 2
673 } else if value <= 999 {
674 3
675 } else if value <= 9999 {
676 4
677 } else if value <= 99999 {
678 5
679 } else if value <= 999999 {
680 6
681 } else {
682 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
683 };
684 let total = length + unit.as_ref().map_or(0, |d| d.len());
685 if total > 6 {
686 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
687 }
688 let mut serialization = [b'0'; 6];
689 let space_padding = 6 - total;
690 let mut written = space_padding;
691 let mut buf = itoa::Buffer::new();
692 let s = buf.format(value);
693 (&mut serialization[written..])
694 .write_all(s.as_bytes())
695 .unwrap();
696 written += s.len();
697 if let Some(unit) = unit {
698 written += (&mut serialization[written..])
699 .write(unit.as_bytes())
700 .unwrap();
701 }
702 debug_assert_eq!(written, 6);
703 Self::parse_hash(&serialization, &location)
704 }
705}
706
707impl Color {
708 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
713 macro_rules! adjust_absolute_color {
714 ($color:expr) => {{
715 if matches!(
717 $color.color_space,
718 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
719 ) {
720 $color.components.0 = normalize($color.components.0);
721 }
722
723 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
725 $color.components = $color.components.map(normalize);
726 }
727
728 $color.alpha = normalize($color.alpha);
729 }};
730 }
731
732 Some(match *self {
733 Color::CurrentColor => ComputedColor::CurrentColor,
734 Color::Absolute(ref absolute) => {
735 let mut color = absolute.color;
736 adjust_absolute_color!(color);
737 ComputedColor::Absolute(color)
738 },
739 Color::ColorFunction(ref color_function) => {
740 debug_assert!(color_function.has_origin_color(),
741 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
742
743 if let Ok(absolute) = color_function.resolve_to_absolute() {
745 ComputedColor::Absolute(absolute)
746 } else {
747 let color_function = color_function
748 .map_origin_color(|origin_color| origin_color.to_computed_color(context));
749 ComputedColor::ColorFunction(Box::new(color_function))
750 }
751 },
752 Color::LightDark(ref ld) => ld.compute(context?),
753 Color::ColorMix(ref mix) => {
754 use crate::values::computed::percentage::Percentage;
755
756 let left = mix.left.to_computed_color(context)?;
757 let right = mix.right.to_computed_color(context)?;
758
759 ComputedColor::from_color_mix(GenericColorMix {
760 interpolation: mix.interpolation,
761 left,
762 left_percentage: Percentage(mix.left_percentage.get()),
763 right,
764 right_percentage: Percentage(mix.right_percentage.get()),
765 flags: mix.flags,
766 })
767 },
768 #[cfg(feature = "gecko")]
769 Color::System(system) => system.compute(context?),
770 #[cfg(feature = "gecko")]
771 Color::InheritFromBodyQuirk => {
772 ComputedColor::Absolute(context?.device().body_text_color())
773 },
774 })
775 }
776}
777
778impl ToComputedValue for Color {
779 type ComputedValue = ComputedColor;
780
781 fn to_computed_value(&self, context: &Context) -> ComputedColor {
782 self.to_computed_color(Some(context)).unwrap_or_else(|| {
783 debug_assert!(
784 false,
785 "Specified color could not be resolved to a computed color!"
786 );
787 ComputedColor::Absolute(AbsoluteColor::BLACK)
788 })
789 }
790
791 fn from_computed_value(computed: &ComputedColor) -> Self {
792 match *computed {
793 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
794 ComputedColor::ColorFunction(ref color_function) => {
795 let color_function =
796 color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
797 Self::ColorFunction(Box::new(color_function))
798 },
799 ComputedColor::CurrentColor => Color::CurrentColor,
800 ComputedColor::ColorMix(ref mix) => {
801 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
802 },
803 }
804 }
805}
806
807impl SpecifiedValueInfo for Color {
808 const SUPPORTED_TYPES: u8 = CssType::COLOR;
809
810 fn collect_completion_keywords(f: KeywordsCollectFn) {
811 f(&[
817 "currentColor",
818 "transparent",
819 "rgb",
820 "rgba",
821 "hsl",
822 "hsla",
823 "hwb",
824 "color",
825 "lab",
826 "lch",
827 "oklab",
828 "oklch",
829 "color-mix",
830 "light-dark",
831 ]);
832 }
833}
834
835#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
838#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
839pub struct ColorPropertyValue(pub Color);
840
841impl ToComputedValue for ColorPropertyValue {
842 type ComputedValue = AbsoluteColor;
843
844 #[inline]
845 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
846 let current_color = context.builder.get_parent_inherited_text().clone_color();
847 self.0
848 .to_computed_value(context)
849 .resolve_to_absolute(¤t_color)
850 }
851
852 #[inline]
853 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
854 ColorPropertyValue(Color::from_absolute_color(*computed).into())
855 }
856}
857
858impl Parse for ColorPropertyValue {
859 fn parse<'i, 't>(
860 context: &ParserContext,
861 input: &mut Parser<'i, 't>,
862 ) -> Result<Self, ParseError<'i>> {
863 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
864 }
865}
866
867pub type ColorOrAuto = GenericColorOrAuto<Color>;
869
870pub type CaretColor = GenericCaretColor<Color>;
872
873impl Parse for CaretColor {
874 fn parse<'i, 't>(
875 context: &ParserContext,
876 input: &mut Parser<'i, 't>,
877 ) -> Result<Self, ParseError<'i>> {
878 ColorOrAuto::parse(context, input).map(GenericCaretColor)
879 }
880}
881
882#[derive(
885 Clone,
886 Copy,
887 Debug,
888 Default,
889 Eq,
890 MallocSizeOf,
891 PartialEq,
892 SpecifiedValueInfo,
893 ToComputedValue,
894 ToResolvedValue,
895 ToShmem,
896)]
897#[repr(C)]
898#[value_info(other_values = "light,dark,only")]
899pub struct ColorSchemeFlags(u8);
900bitflags! {
901 impl ColorSchemeFlags: u8 {
902 const LIGHT = 1 << 0;
904 const DARK = 1 << 1;
906 const ONLY = 1 << 2;
908 }
909}
910
911#[derive(
913 Clone,
914 Debug,
915 Default,
916 MallocSizeOf,
917 PartialEq,
918 SpecifiedValueInfo,
919 ToComputedValue,
920 ToResolvedValue,
921 ToShmem,
922)]
923#[repr(C)]
924#[value_info(other_values = "normal")]
925pub struct ColorScheme {
926 #[ignore_malloc_size_of = "Arc"]
927 idents: crate::ArcSlice<CustomIdent>,
928 pub bits: ColorSchemeFlags,
930}
931
932impl ColorScheme {
933 pub fn normal() -> Self {
935 Self {
936 idents: Default::default(),
937 bits: ColorSchemeFlags::empty(),
938 }
939 }
940
941 pub fn raw_bits(&self) -> u8 {
943 self.bits.bits()
944 }
945}
946
947impl Parse for ColorScheme {
948 fn parse<'i, 't>(
949 _: &ParserContext,
950 input: &mut Parser<'i, 't>,
951 ) -> Result<Self, ParseError<'i>> {
952 let mut idents = vec![];
953 let mut bits = ColorSchemeFlags::empty();
954
955 let mut location = input.current_source_location();
956 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
957 let mut is_only = false;
958 match_ignore_ascii_case! { &ident,
959 "normal" => {
960 if idents.is_empty() && bits.is_empty() {
961 return Ok(Self::normal());
962 }
963 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
964 },
965 "light" => bits.insert(ColorSchemeFlags::LIGHT),
966 "dark" => bits.insert(ColorSchemeFlags::DARK),
967 "only" => {
968 if bits.intersects(ColorSchemeFlags::ONLY) {
969 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
970 }
971 bits.insert(ColorSchemeFlags::ONLY);
972 is_only = true;
973 },
974 _ => {},
975 };
976
977 if is_only {
978 if !idents.is_empty() {
979 break;
982 }
983 } else {
984 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
985 }
986 location = input.current_source_location();
987 }
988
989 if idents.is_empty() {
990 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
991 }
992
993 Ok(Self {
994 idents: crate::ArcSlice::from_iter(idents.into_iter()),
995 bits,
996 })
997 }
998}
999
1000impl ToCss for ColorScheme {
1001 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1002 where
1003 W: Write,
1004 {
1005 if self.idents.is_empty() {
1006 debug_assert!(self.bits.is_empty());
1007 return dest.write_str("normal");
1008 }
1009 let mut first = true;
1010 for ident in self.idents.iter() {
1011 if !first {
1012 dest.write_char(' ')?;
1013 }
1014 first = false;
1015 ident.to_css(dest)?;
1016 }
1017 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1018 dest.write_str(" only")?;
1019 }
1020 Ok(())
1021 }
1022}
1023
1024#[derive(
1026 Clone,
1027 Copy,
1028 Debug,
1029 MallocSizeOf,
1030 Parse,
1031 PartialEq,
1032 SpecifiedValueInfo,
1033 ToCss,
1034 ToComputedValue,
1035 ToResolvedValue,
1036 ToShmem,
1037)]
1038#[repr(u8)]
1039pub enum PrintColorAdjust {
1040 Economy,
1042 Exact,
1044}
1045
1046#[derive(
1048 Clone,
1049 Copy,
1050 Debug,
1051 MallocSizeOf,
1052 Parse,
1053 PartialEq,
1054 SpecifiedValueInfo,
1055 ToCss,
1056 ToComputedValue,
1057 ToResolvedValue,
1058 ToShmem,
1059)]
1060#[repr(u8)]
1061pub enum ForcedColorAdjust {
1062 Auto,
1064 None,
1066}
1067
1068#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1071#[repr(u8)]
1072pub enum ForcedColors {
1073 None,
1075 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1077 Requested,
1078 Active,
1080}
1081
1082impl ForcedColors {
1083 pub fn is_active(self) -> bool {
1085 matches!(self, Self::Active)
1086 }
1087}