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