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| {
451 GenericLightDark::parse_with(i, |i| {
452 Self::parse_internal(context, i, preserve_authored)
453 })
454 }) {
455 return Ok(Color::LightDark(Box::new(ld)));
456 }
457
458 match e.kind {
459 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
460 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
461 ValueParseErrorKind::InvalidColor(t),
462 )))
463 },
464 _ => Err(e),
465 }
466 },
467 }
468 }
469
470 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
472 input
473 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
474 .is_ok()
475 }
476
477 pub fn parse_and_compute(
479 context: &ParserContext,
480 input: &mut Parser,
481 device: Option<&Device>,
482 ) -> Option<ComputedColor> {
483 use crate::error_reporting::ContextualParseError;
484 let start = input.position();
485 let result = input
486 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
487
488 let specified = match result {
489 Ok(s) => s,
490 Err(e) => {
491 if !context.error_reporting_enabled() {
492 return None;
493 }
494 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
502 let location = e.location.clone();
503 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
504 context.log_css_error(location, error);
505 }
506 return None;
507 },
508 };
509
510 match device {
511 Some(device) => {
512 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
513 specified.to_computed_color(Some(&context))
514 })
515 },
516 None => specified.to_computed_color(None),
517 }
518 }
519}
520
521impl ToCss for Color {
522 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
523 where
524 W: Write,
525 {
526 match *self {
527 Color::CurrentColor => dest.write_str("currentcolor"),
528 Color::Absolute(ref absolute) => absolute.to_css(dest),
529 Color::ColorFunction(ref color_function) => color_function.to_css(dest),
530 Color::ColorMix(ref mix) => mix.to_css(dest),
531 Color::LightDark(ref ld) => ld.to_css(dest),
532 #[cfg(feature = "gecko")]
533 Color::System(system) => system.to_css(dest),
534 #[cfg(feature = "gecko")]
535 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"),
536 }
537 }
538}
539
540impl Color {
541 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
543 match *self {
544 #[cfg(feature = "gecko")]
545 Self::InheritFromBodyQuirk => false,
546 Self::CurrentColor => true,
547 #[cfg(feature = "gecko")]
548 Self::System(..) => true,
549 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
550 Self::ColorFunction(ref color_function) => {
551 color_function
554 .resolve_to_absolute()
555 .map(|resolved| allow_transparent && resolved.is_transparent())
556 .unwrap_or(false)
557 },
558 Self::LightDark(ref ld) => {
559 ld.light.honored_in_forced_colors_mode(allow_transparent)
560 && ld.dark.honored_in_forced_colors_mode(allow_transparent)
561 },
562 Self::ColorMix(ref mix) => {
563 mix.left.honored_in_forced_colors_mode(allow_transparent)
564 && mix.right.honored_in_forced_colors_mode(allow_transparent)
565 },
566 }
567 }
568
569 #[inline]
571 pub fn currentcolor() -> Self {
572 Self::CurrentColor
573 }
574
575 #[inline]
577 pub fn transparent() -> Self {
578 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
580 }
581
582 pub fn from_absolute_color(color: AbsoluteColor) -> Self {
584 Color::Absolute(Box::new(Absolute {
585 color,
586 authored: None,
587 }))
588 }
589
590 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> {
595 use crate::values::specified::percentage::ToPercentage;
596
597 match self {
598 Self::Absolute(c) => Some(c.color),
599 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
600 Self::ColorMix(ref mix) => {
601 let left = mix.left.resolve_to_absolute()?;
602 let right = mix.right.resolve_to_absolute()?;
603 Some(crate::color::mix::mix(
604 mix.interpolation,
605 &left,
606 mix.left_percentage.to_percentage(),
607 &right,
608 mix.right_percentage.to_percentage(),
609 mix.flags,
610 ))
611 },
612 _ => None,
613 }
614 }
615
616 pub fn parse_quirky<'i, 't>(
620 context: &ParserContext,
621 input: &mut Parser<'i, 't>,
622 allow_quirks: AllowQuirks,
623 ) -> Result<Self, ParseError<'i>> {
624 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
625 if !allow_quirks.allowed(context.quirks_mode) {
626 return Err(e);
627 }
628 Color::parse_quirky_color(input).map_err(|_| e)
629 })
630 }
631
632 fn parse_hash<'i>(
633 bytes: &[u8],
634 loc: &cssparser::SourceLocation,
635 ) -> Result<Self, ParseError<'i>> {
636 match cssparser::color::parse_hash_color(bytes) {
637 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
638 r, g, b, a,
639 ))),
640 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
641 }
642 }
643
644 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
648 let location = input.current_source_location();
649 let (value, unit) = match *input.next()? {
650 Token::Number {
651 int_value: Some(integer),
652 ..
653 } => (integer, None),
654 Token::Dimension {
655 int_value: Some(integer),
656 ref unit,
657 ..
658 } => (integer, Some(unit)),
659 Token::Ident(ref ident) => {
660 if ident.len() != 3 && ident.len() != 6 {
661 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
662 }
663 return Self::parse_hash(ident.as_bytes(), &location);
664 },
665 ref t => {
666 return Err(location.new_unexpected_token_error(t.clone()));
667 },
668 };
669 if value < 0 {
670 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
671 }
672 let length = if value <= 9 {
673 1
674 } else if value <= 99 {
675 2
676 } else if value <= 999 {
677 3
678 } else if value <= 9999 {
679 4
680 } else if value <= 99999 {
681 5
682 } else if value <= 999999 {
683 6
684 } else {
685 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
686 };
687 let total = length + unit.as_ref().map_or(0, |d| d.len());
688 if total > 6 {
689 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
690 }
691 let mut serialization = [b'0'; 6];
692 let space_padding = 6 - total;
693 let mut written = space_padding;
694 let mut buf = itoa::Buffer::new();
695 let s = buf.format(value);
696 (&mut serialization[written..])
697 .write_all(s.as_bytes())
698 .unwrap();
699 written += s.len();
700 if let Some(unit) = unit {
701 written += (&mut serialization[written..])
702 .write(unit.as_bytes())
703 .unwrap();
704 }
705 debug_assert_eq!(written, 6);
706 Self::parse_hash(&serialization, &location)
707 }
708}
709
710impl Color {
711 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
716 macro_rules! adjust_absolute_color {
717 ($color:expr) => {{
718 if matches!(
720 $color.color_space,
721 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
722 ) {
723 $color.components.0 = normalize($color.components.0);
724 }
725
726 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
728 $color.components = $color.components.map(normalize);
729 }
730
731 $color.alpha = normalize($color.alpha);
732 }};
733 }
734
735 Some(match *self {
736 Color::CurrentColor => ComputedColor::CurrentColor,
737 Color::Absolute(ref absolute) => {
738 let mut color = absolute.color;
739 adjust_absolute_color!(color);
740 ComputedColor::Absolute(color)
741 },
742 Color::ColorFunction(ref color_function) => {
743 debug_assert!(color_function.has_origin_color(),
744 "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
745
746 if let Ok(absolute) = color_function.resolve_to_absolute() {
748 ComputedColor::Absolute(absolute)
749 } else {
750 let color_function = color_function
751 .map_origin_color(|origin_color| origin_color.to_computed_color(context));
752 ComputedColor::ColorFunction(Box::new(color_function))
753 }
754 },
755 Color::LightDark(ref ld) => ld.compute(context?),
756 Color::ColorMix(ref mix) => {
757 use crate::values::computed::percentage::Percentage;
758
759 let left = mix.left.to_computed_color(context)?;
760 let right = mix.right.to_computed_color(context)?;
761
762 ComputedColor::from_color_mix(GenericColorMix {
763 interpolation: mix.interpolation,
764 left,
765 left_percentage: Percentage(mix.left_percentage.get()),
766 right,
767 right_percentage: Percentage(mix.right_percentage.get()),
768 flags: mix.flags,
769 })
770 },
771 #[cfg(feature = "gecko")]
772 Color::System(system) => system.compute(context?),
773 #[cfg(feature = "gecko")]
774 Color::InheritFromBodyQuirk => {
775 ComputedColor::Absolute(context?.device().body_text_color())
776 },
777 })
778 }
779}
780
781impl ToComputedValue for Color {
782 type ComputedValue = ComputedColor;
783
784 fn to_computed_value(&self, context: &Context) -> ComputedColor {
785 self.to_computed_color(Some(context)).unwrap_or_else(|| {
786 debug_assert!(
787 false,
788 "Specified color could not be resolved to a computed color!"
789 );
790 ComputedColor::Absolute(AbsoluteColor::BLACK)
791 })
792 }
793
794 fn from_computed_value(computed: &ComputedColor) -> Self {
795 match *computed {
796 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
797 ComputedColor::ColorFunction(ref color_function) => {
798 let color_function =
799 color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
800 Self::ColorFunction(Box::new(color_function))
801 },
802 ComputedColor::CurrentColor => Color::CurrentColor,
803 ComputedColor::ColorMix(ref mix) => {
804 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
805 },
806 }
807 }
808}
809
810impl SpecifiedValueInfo for Color {
811 const SUPPORTED_TYPES: u8 = CssType::COLOR;
812
813 fn collect_completion_keywords(f: KeywordsCollectFn) {
814 f(&[
820 "currentColor",
821 "transparent",
822 "rgb",
823 "rgba",
824 "hsl",
825 "hsla",
826 "hwb",
827 "color",
828 "lab",
829 "lch",
830 "oklab",
831 "oklch",
832 "color-mix",
833 "light-dark",
834 ]);
835 }
836}
837
838#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
841#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
842pub struct ColorPropertyValue(pub Color);
843
844impl ToComputedValue for ColorPropertyValue {
845 type ComputedValue = AbsoluteColor;
846
847 #[inline]
848 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
849 let current_color = context.builder.get_parent_inherited_text().clone_color();
850 self.0
851 .to_computed_value(context)
852 .resolve_to_absolute(¤t_color)
853 }
854
855 #[inline]
856 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
857 ColorPropertyValue(Color::from_absolute_color(*computed).into())
858 }
859}
860
861impl Parse for ColorPropertyValue {
862 fn parse<'i, 't>(
863 context: &ParserContext,
864 input: &mut Parser<'i, 't>,
865 ) -> Result<Self, ParseError<'i>> {
866 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
867 }
868}
869
870pub type ColorOrAuto = GenericColorOrAuto<Color>;
872
873pub type CaretColor = GenericCaretColor<Color>;
875
876impl Parse for CaretColor {
877 fn parse<'i, 't>(
878 context: &ParserContext,
879 input: &mut Parser<'i, 't>,
880 ) -> Result<Self, ParseError<'i>> {
881 ColorOrAuto::parse(context, input).map(GenericCaretColor)
882 }
883}
884
885#[derive(
888 Clone,
889 Copy,
890 Debug,
891 Default,
892 Eq,
893 MallocSizeOf,
894 PartialEq,
895 SpecifiedValueInfo,
896 ToComputedValue,
897 ToResolvedValue,
898 ToShmem,
899)]
900#[repr(C)]
901#[value_info(other_values = "light,dark,only")]
902pub struct ColorSchemeFlags(u8);
903bitflags! {
904 impl ColorSchemeFlags: u8 {
905 const LIGHT = 1 << 0;
907 const DARK = 1 << 1;
909 const ONLY = 1 << 2;
911 }
912}
913
914#[derive(
916 Clone,
917 Debug,
918 Default,
919 MallocSizeOf,
920 PartialEq,
921 SpecifiedValueInfo,
922 ToComputedValue,
923 ToResolvedValue,
924 ToShmem,
925)]
926#[repr(C)]
927#[value_info(other_values = "normal")]
928pub struct ColorScheme {
929 #[ignore_malloc_size_of = "Arc"]
930 idents: crate::ArcSlice<CustomIdent>,
931 pub bits: ColorSchemeFlags,
933}
934
935impl ColorScheme {
936 pub fn normal() -> Self {
938 Self {
939 idents: Default::default(),
940 bits: ColorSchemeFlags::empty(),
941 }
942 }
943
944 pub fn raw_bits(&self) -> u8 {
946 self.bits.bits()
947 }
948}
949
950impl Parse for ColorScheme {
951 fn parse<'i, 't>(
952 _: &ParserContext,
953 input: &mut Parser<'i, 't>,
954 ) -> Result<Self, ParseError<'i>> {
955 let mut idents = vec![];
956 let mut bits = ColorSchemeFlags::empty();
957
958 let mut location = input.current_source_location();
959 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
960 let mut is_only = false;
961 match_ignore_ascii_case! { &ident,
962 "normal" => {
963 if idents.is_empty() && bits.is_empty() {
964 return Ok(Self::normal());
965 }
966 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
967 },
968 "light" => bits.insert(ColorSchemeFlags::LIGHT),
969 "dark" => bits.insert(ColorSchemeFlags::DARK),
970 "only" => {
971 if bits.intersects(ColorSchemeFlags::ONLY) {
972 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
973 }
974 bits.insert(ColorSchemeFlags::ONLY);
975 is_only = true;
976 },
977 _ => {},
978 };
979
980 if is_only {
981 if !idents.is_empty() {
982 break;
985 }
986 } else {
987 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
988 }
989 location = input.current_source_location();
990 }
991
992 if idents.is_empty() {
993 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
994 }
995
996 Ok(Self {
997 idents: crate::ArcSlice::from_iter(idents.into_iter()),
998 bits,
999 })
1000 }
1001}
1002
1003impl ToCss for ColorScheme {
1004 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1005 where
1006 W: Write,
1007 {
1008 if self.idents.is_empty() {
1009 debug_assert!(self.bits.is_empty());
1010 return dest.write_str("normal");
1011 }
1012 let mut first = true;
1013 for ident in self.idents.iter() {
1014 if !first {
1015 dest.write_char(' ')?;
1016 }
1017 first = false;
1018 ident.to_css(dest)?;
1019 }
1020 if self.bits.intersects(ColorSchemeFlags::ONLY) {
1021 dest.write_str(" only")?;
1022 }
1023 Ok(())
1024 }
1025}
1026
1027#[derive(
1029 Clone,
1030 Copy,
1031 Debug,
1032 MallocSizeOf,
1033 Parse,
1034 PartialEq,
1035 SpecifiedValueInfo,
1036 ToCss,
1037 ToComputedValue,
1038 ToResolvedValue,
1039 ToShmem,
1040)]
1041#[repr(u8)]
1042pub enum PrintColorAdjust {
1043 Economy,
1045 Exact,
1047}
1048
1049#[derive(
1051 Clone,
1052 Copy,
1053 Debug,
1054 MallocSizeOf,
1055 Parse,
1056 PartialEq,
1057 SpecifiedValueInfo,
1058 ToCss,
1059 ToComputedValue,
1060 ToResolvedValue,
1061 ToShmem,
1062)]
1063#[repr(u8)]
1064pub enum ForcedColorAdjust {
1065 Auto,
1067 None,
1069}
1070
1071#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
1074#[repr(u8)]
1075pub enum ForcedColors {
1076 None,
1078 #[parse(condition = "ParserContext::chrome_rules_enabled")]
1080 Requested,
1081 Active,
1083}
1084
1085impl ForcedColors {
1086 pub fn is_active(self) -> bool {
1088 matches!(self, Self::Active)
1089 }
1090}