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