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