color.rs (38596B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified color values. 6 7 use super::AllowQuirks; 8 use crate::color::mix::ColorInterpolationMethod; 9 use crate::color::{parsing, AbsoluteColor, ColorFunction, ColorSpace}; 10 use crate::derives::*; 11 use crate::media_queries::Device; 12 use crate::parser::{Parse, ParserContext}; 13 use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; 14 use crate::values::generics::color::{ 15 ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto, GenericLightDark, 16 }; 17 use crate::values::specified::Percentage; 18 use crate::values::{normalize, CustomIdent}; 19 use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, ParseErrorKind, Parser, Token}; 20 use std::fmt::{self, Write}; 21 use std::io::Write as IoWrite; 22 use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; 23 use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; 24 25 /// A specified color-mix(). 26 pub type ColorMix = GenericColorMix<Color, Percentage>; 27 28 impl ColorMix { 29 fn parse<'i, 't>( 30 context: &ParserContext, 31 input: &mut Parser<'i, 't>, 32 preserve_authored: PreserveAuthored, 33 ) -> Result<Self, ParseError<'i>> { 34 input.expect_function_matching("color-mix")?; 35 36 input.parse_nested_block(|input| { 37 // If the color interpolation method is omitted, default to "in oklab". 38 // See: https://github.com/web-platform-tests/interop/issues/1166 39 let interpolation = input 40 .try_parse(|input| -> Result<_, ParseError<'i>> { 41 let interpolation = ColorInterpolationMethod::parse(context, input)?; 42 input.expect_comma()?; 43 Ok(interpolation) 44 }) 45 .unwrap_or_default(); 46 47 let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> { 48 input 49 .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)) 50 .ok() 51 }; 52 53 let mut left_percentage = try_parse_percentage(input); 54 55 let left = Color::parse_internal(context, input, preserve_authored)?; 56 if left_percentage.is_none() { 57 left_percentage = try_parse_percentage(input); 58 } 59 60 input.expect_comma()?; 61 62 let mut right_percentage = try_parse_percentage(input); 63 64 let right = Color::parse_internal(context, input, preserve_authored)?; 65 66 if right_percentage.is_none() { 67 right_percentage = try_parse_percentage(input); 68 } 69 70 let right_percentage = right_percentage 71 .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))); 72 73 let left_percentage = 74 left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); 75 76 if left_percentage.get() + right_percentage.get() <= 0.0 { 77 // If the percentages sum to zero, the function is invalid. 78 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 79 } 80 81 // Pass RESULT_IN_MODERN_SYNTAX here, because the result of the color-mix() function 82 // should always be in the modern color syntax to allow for out of gamut results and 83 // to preserve floating point precision. 84 Ok(ColorMix { 85 interpolation, 86 left, 87 left_percentage, 88 right, 89 right_percentage, 90 flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX, 91 }) 92 }) 93 } 94 } 95 96 /// Container holding an absolute color and the text specified by an author. 97 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 98 pub struct Absolute { 99 /// The specified color. 100 pub color: AbsoluteColor, 101 /// Authored representation. 102 pub authored: Option<Box<str>>, 103 } 104 105 impl ToCss for Absolute { 106 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 107 where 108 W: Write, 109 { 110 if let Some(ref authored) = self.authored { 111 dest.write_str(authored) 112 } else { 113 self.color.to_css(dest) 114 } 115 } 116 } 117 118 /// Specified color value 119 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)] 120 pub enum Color { 121 /// The 'currentColor' keyword 122 CurrentColor, 123 /// An absolute color. 124 /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function 125 Absolute(Box<Absolute>), 126 /// A color function that could not be resolved to a [Color::Absolute] color at parse time. 127 /// Right now this is only the case for relative colors with `currentColor` as the origin. 128 ColorFunction(Box<ColorFunction<Self>>), 129 /// A system color. 130 #[cfg(feature = "gecko")] 131 System(SystemColor), 132 /// A color mix. 133 ColorMix(Box<ColorMix>), 134 /// A light-dark() color. 135 LightDark(Box<GenericLightDark<Self>>), 136 /// The contrast-color function. 137 ContrastColor(Box<Color>), 138 /// Quirksmode-only rule for inheriting color from the body 139 #[cfg(feature = "gecko")] 140 InheritFromBodyQuirk, 141 } 142 143 impl From<AbsoluteColor> for Color { 144 #[inline] 145 fn from(value: AbsoluteColor) -> Self { 146 Self::from_absolute_color(value) 147 } 148 } 149 150 /// System colors. A bunch of these are ad-hoc, others come from Windows: 151 /// 152 /// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor 153 /// 154 /// Others are HTML/CSS specific. Spec is: 155 /// 156 /// https://drafts.csswg.org/css-color/#css-system-colors 157 /// https://drafts.csswg.org/css-color/#deprecated-system-colors 158 #[allow(missing_docs)] 159 #[cfg(feature = "gecko")] 160 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] 161 #[repr(u8)] 162 pub enum SystemColor { 163 Activeborder, 164 /// Background in the (active) titlebar. 165 Activecaption, 166 Appworkspace, 167 Background, 168 Buttonface, 169 Buttonhighlight, 170 Buttonshadow, 171 Buttontext, 172 Buttonborder, 173 /// Text color in the (active) titlebar. 174 Captiontext, 175 #[parse(aliases = "-moz-field")] 176 Field, 177 /// Used for disabled field backgrounds. 178 #[parse(condition = "ParserContext::chrome_rules_enabled")] 179 MozDisabledfield, 180 #[parse(aliases = "-moz-fieldtext")] 181 Fieldtext, 182 183 Mark, 184 Marktext, 185 186 /// Combobox widgets 187 MozComboboxtext, 188 MozCombobox, 189 190 Graytext, 191 Highlight, 192 Highlighttext, 193 Inactiveborder, 194 /// Background in the (inactive) titlebar. 195 Inactivecaption, 196 /// Text color in the (inactive) titlebar. 197 Inactivecaptiontext, 198 Infobackground, 199 Infotext, 200 Menu, 201 Menutext, 202 Scrollbar, 203 Threeddarkshadow, 204 Threedface, 205 Threedhighlight, 206 Threedlightshadow, 207 Threedshadow, 208 Window, 209 Windowframe, 210 Windowtext, 211 #[parse(aliases = "-moz-default-color")] 212 Canvastext, 213 #[parse(aliases = "-moz-default-background-color")] 214 Canvas, 215 MozDialog, 216 MozDialogtext, 217 /// Used for selected but not focused cell backgrounds. 218 #[parse(aliases = "-moz-html-cellhighlight")] 219 MozCellhighlight, 220 /// Used for selected but not focused cell text. 221 #[parse(aliases = "-moz-html-cellhighlighttext")] 222 MozCellhighlighttext, 223 /// Used for selected and focused html cell backgrounds. 224 Selecteditem, 225 /// Used for selected and focused html cell text. 226 Selecteditemtext, 227 /// Used for menu item backgrounds when hovered. 228 MozMenuhover, 229 /// Used for menu item backgrounds when hovered and disabled. 230 #[parse(condition = "ParserContext::chrome_rules_enabled")] 231 MozMenuhoverdisabled, 232 /// Used for menu item text when hovered. 233 MozMenuhovertext, 234 /// Used for menubar item text when hovered. 235 MozMenubarhovertext, 236 237 /// On platforms where this color is the same as field, or transparent, use fieldtext as 238 /// foreground color. 239 MozOddtreerow, 240 241 /// Used for button text background when hovered. 242 #[parse(condition = "ParserContext::chrome_rules_enabled")] 243 MozButtonhoverface, 244 /// Used for button text color when hovered. 245 #[parse(condition = "ParserContext::chrome_rules_enabled")] 246 MozButtonhovertext, 247 /// Used for button border color when hovered. 248 #[parse(condition = "ParserContext::chrome_rules_enabled")] 249 MozButtonhoverborder, 250 /// Used for button background when pressed. 251 #[parse(condition = "ParserContext::chrome_rules_enabled")] 252 MozButtonactiveface, 253 /// Used for button text when pressed. 254 #[parse(condition = "ParserContext::chrome_rules_enabled")] 255 MozButtonactivetext, 256 /// Used for button border when pressed. 257 #[parse(condition = "ParserContext::chrome_rules_enabled")] 258 MozButtonactiveborder, 259 260 /// Used for button background when disabled. 261 #[parse(condition = "ParserContext::chrome_rules_enabled")] 262 MozButtondisabledface, 263 /// Used for button border when disabled. 264 #[parse(condition = "ParserContext::chrome_rules_enabled")] 265 MozButtondisabledborder, 266 267 /// Colors used for the header bar (sorta like the tab bar / menubar). 268 #[parse(condition = "ParserContext::chrome_rules_enabled")] 269 MozHeaderbar, 270 #[parse(condition = "ParserContext::chrome_rules_enabled")] 271 MozHeaderbartext, 272 #[parse(condition = "ParserContext::chrome_rules_enabled")] 273 MozHeaderbarinactive, 274 #[parse(condition = "ParserContext::chrome_rules_enabled")] 275 MozHeaderbarinactivetext, 276 277 /// Foreground color of default buttons. 278 #[parse(condition = "ParserContext::chrome_rules_enabled")] 279 MozMacDefaultbuttontext, 280 /// Ring color around text fields and lists. 281 #[parse(condition = "ParserContext::chrome_rules_enabled")] 282 MozMacFocusring, 283 /// Text color of disabled text on toolbars. 284 #[parse(condition = "ParserContext::chrome_rules_enabled")] 285 MozMacDisabledtoolbartext, 286 /// The background of a sidebar. 287 #[parse(condition = "ParserContext::chrome_rules_enabled")] 288 MozSidebar, 289 /// The foreground color of a sidebar. 290 #[parse(condition = "ParserContext::chrome_rules_enabled")] 291 MozSidebartext, 292 /// The border color of a sidebar. 293 #[parse(condition = "ParserContext::chrome_rules_enabled")] 294 MozSidebarborder, 295 296 /// Theme accent color. 297 /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolor 298 Accentcolor, 299 300 /// Foreground for the accent color. 301 /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolortext 302 Accentcolortext, 303 304 /// The background-color for :autofill-ed inputs. 305 #[parse(condition = "ParserContext::chrome_rules_enabled")] 306 MozAutofillBackground, 307 308 #[parse(aliases = "-moz-hyperlinktext")] 309 Linktext, 310 #[parse(aliases = "-moz-activehyperlinktext")] 311 Activetext, 312 #[parse(aliases = "-moz-visitedhyperlinktext")] 313 Visitedtext, 314 315 /// Color of tree column headers 316 #[parse(condition = "ParserContext::chrome_rules_enabled")] 317 MozColheader, 318 #[parse(condition = "ParserContext::chrome_rules_enabled")] 319 MozColheadertext, 320 #[parse(condition = "ParserContext::chrome_rules_enabled")] 321 MozColheaderhover, 322 #[parse(condition = "ParserContext::chrome_rules_enabled")] 323 MozColheaderhovertext, 324 #[parse(condition = "ParserContext::chrome_rules_enabled")] 325 MozColheaderactive, 326 #[parse(condition = "ParserContext::chrome_rules_enabled")] 327 MozColheaderactivetext, 328 329 #[parse(condition = "ParserContext::chrome_rules_enabled")] 330 TextSelectDisabledBackground, 331 #[css(skip)] 332 TextSelectAttentionBackground, 333 #[css(skip)] 334 TextSelectAttentionForeground, 335 #[css(skip)] 336 TextHighlightBackground, 337 #[css(skip)] 338 TextHighlightForeground, 339 #[css(skip)] 340 TargetTextBackground, 341 #[css(skip)] 342 TargetTextForeground, 343 #[css(skip)] 344 IMERawInputBackground, 345 #[css(skip)] 346 IMERawInputForeground, 347 #[css(skip)] 348 IMERawInputUnderline, 349 #[css(skip)] 350 IMESelectedRawTextBackground, 351 #[css(skip)] 352 IMESelectedRawTextForeground, 353 #[css(skip)] 354 IMESelectedRawTextUnderline, 355 #[css(skip)] 356 IMEConvertedTextBackground, 357 #[css(skip)] 358 IMEConvertedTextForeground, 359 #[css(skip)] 360 IMEConvertedTextUnderline, 361 #[css(skip)] 362 IMESelectedConvertedTextBackground, 363 #[css(skip)] 364 IMESelectedConvertedTextForeground, 365 #[css(skip)] 366 IMESelectedConvertedTextUnderline, 367 #[css(skip)] 368 SpellCheckerUnderline, 369 #[css(skip)] 370 ThemedScrollbar, 371 #[css(skip)] 372 ThemedScrollbarThumb, 373 #[css(skip)] 374 ThemedScrollbarThumbHover, 375 #[css(skip)] 376 ThemedScrollbarThumbActive, 377 378 #[css(skip)] 379 End, // Just for array-indexing purposes. 380 } 381 382 #[cfg(feature = "gecko")] 383 impl SystemColor { 384 #[inline] 385 fn compute(&self, cx: &Context) -> ComputedColor { 386 use crate::gecko::values::convert_nscolor_to_absolute_color; 387 use crate::gecko_bindings::bindings; 388 389 let color = cx.device().system_nscolor(*self, cx.builder.color_scheme); 390 if cx.for_non_inherited_property { 391 cx.rule_cache_conditions 392 .borrow_mut() 393 .set_color_scheme_dependency(cx.builder.color_scheme); 394 } 395 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { 396 return ComputedColor::currentcolor(); 397 } 398 ComputedColor::Absolute(convert_nscolor_to_absolute_color(color)) 399 } 400 } 401 402 /// Whether to preserve authored colors during parsing. That's useful only if we 403 /// plan to serialize the color back. 404 #[derive(Copy, Clone)] 405 enum PreserveAuthored { 406 No, 407 Yes, 408 } 409 410 impl Parse for Color { 411 fn parse<'i, 't>( 412 context: &ParserContext, 413 input: &mut Parser<'i, 't>, 414 ) -> Result<Self, ParseError<'i>> { 415 Self::parse_internal(context, input, PreserveAuthored::Yes) 416 } 417 } 418 419 impl Color { 420 fn parse_internal<'i, 't>( 421 context: &ParserContext, 422 input: &mut Parser<'i, 't>, 423 preserve_authored: PreserveAuthored, 424 ) -> Result<Self, ParseError<'i>> { 425 let authored = match preserve_authored { 426 PreserveAuthored::No => None, 427 PreserveAuthored::Yes => { 428 // Currently we only store authored value for color keywords, 429 // because all browsers serialize those values as keywords for 430 // specified value. 431 let start = input.state(); 432 let authored = input.expect_ident_cloned().ok(); 433 input.reset(&start); 434 authored 435 }, 436 }; 437 438 match input.try_parse(|i| parsing::parse_color_with(context, i)) { 439 Ok(mut color) => { 440 if let Color::Absolute(ref mut absolute) = color { 441 // Because we can't set the `authored` value at construction time, we have to set it 442 // here. 443 absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str()); 444 } 445 Ok(color) 446 }, 447 Err(e) => { 448 #[cfg(feature = "gecko")] 449 { 450 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { 451 return Ok(Color::System(system)); 452 } 453 } 454 455 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored)) 456 { 457 return Ok(Color::ColorMix(Box::new(mix))); 458 } 459 460 if let Ok(ld) = input.try_parse(|i| { 461 GenericLightDark::parse_with(i, |i| { 462 Self::parse_internal(context, i, preserve_authored) 463 }) 464 }) { 465 return Ok(Color::LightDark(Box::new(ld))); 466 } 467 468 if static_prefs::pref!("layout.css.contrast-color.enabled") { 469 if let Ok(c) = input.try_parse(|i| { 470 i.expect_function_matching("contrast-color")?; 471 i.parse_nested_block(|i| { 472 Self::parse_internal(context, i, preserve_authored) 473 }) 474 }) { 475 return Ok(Color::ContrastColor(Box::new(c))); 476 } 477 } 478 479 match e.kind { 480 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { 481 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( 482 ValueParseErrorKind::InvalidColor(t), 483 ))) 484 }, 485 _ => Err(e), 486 } 487 }, 488 } 489 } 490 491 /// Returns whether a given color is valid for authors. 492 pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool { 493 input 494 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)) 495 .is_ok() 496 } 497 498 /// Tries to parse a color and compute it with a given device. 499 pub fn parse_and_compute( 500 context: &ParserContext, 501 input: &mut Parser, 502 device: Option<&Device>, 503 ) -> Option<ComputedColor> { 504 use crate::error_reporting::ContextualParseError; 505 let start = input.position(); 506 let result = input 507 .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)); 508 509 let specified = match result { 510 Ok(s) => s, 511 Err(e) => { 512 if !context.error_reporting_enabled() { 513 return None; 514 } 515 // Ignore other kinds of errors that might be reported, such as 516 // ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken), 517 // since Gecko didn't use to report those to the error console. 518 // 519 // TODO(emilio): Revise whether we want to keep this at all, we 520 // use this only for canvas, this warnings are disabled by 521 // default and not available on OffscreenCanvas anyways... 522 if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind { 523 let location = e.location.clone(); 524 let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e); 525 context.log_css_error(location, error); 526 } 527 return None; 528 }, 529 }; 530 531 match device { 532 Some(device) => { 533 Context::for_media_query_evaluation(device, device.quirks_mode(), |context| { 534 specified.to_computed_color(Some(&context)) 535 }) 536 }, 537 None => specified.to_computed_color(None), 538 } 539 } 540 } 541 542 impl ToCss for Color { 543 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 544 where 545 W: Write, 546 { 547 match *self { 548 Color::CurrentColor => dest.write_str("currentcolor"), 549 Color::Absolute(ref absolute) => absolute.to_css(dest), 550 Color::ColorFunction(ref color_function) => color_function.to_css(dest), 551 Color::ColorMix(ref mix) => mix.to_css(dest), 552 Color::LightDark(ref ld) => ld.to_css(dest), 553 Color::ContrastColor(ref c) => { 554 dest.write_str("contrast-color(")?; 555 c.to_css(dest)?; 556 dest.write_char(')') 557 }, 558 #[cfg(feature = "gecko")] 559 Color::System(system) => system.to_css(dest), 560 #[cfg(feature = "gecko")] 561 Color::InheritFromBodyQuirk => dest.write_str("-moz-inherit-from-body-quirk"), 562 } 563 } 564 } 565 566 impl Color { 567 /// Returns whether this color is allowed in forced-colors mode. 568 pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool { 569 match *self { 570 #[cfg(feature = "gecko")] 571 Self::InheritFromBodyQuirk => false, 572 Self::CurrentColor => true, 573 #[cfg(feature = "gecko")] 574 Self::System(..) => true, 575 Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(), 576 Self::ColorFunction(ref color_function) => { 577 // For now we allow transparent colors if we can resolve the color function. 578 // <https://bugzilla.mozilla.org/show_bug.cgi?id=1923053> 579 color_function 580 .resolve_to_absolute() 581 .map(|resolved| allow_transparent && resolved.is_transparent()) 582 .unwrap_or(false) 583 }, 584 Self::LightDark(ref ld) => { 585 ld.light.honored_in_forced_colors_mode(allow_transparent) 586 && ld.dark.honored_in_forced_colors_mode(allow_transparent) 587 }, 588 Self::ColorMix(ref mix) => { 589 mix.left.honored_in_forced_colors_mode(allow_transparent) 590 && mix.right.honored_in_forced_colors_mode(allow_transparent) 591 }, 592 Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent), 593 } 594 } 595 596 /// Returns currentcolor value. 597 #[inline] 598 pub fn currentcolor() -> Self { 599 Self::CurrentColor 600 } 601 602 /// Returns transparent value. 603 #[inline] 604 pub fn transparent() -> Self { 605 // We should probably set authored to "transparent", but maybe it doesn't matter. 606 Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK) 607 } 608 609 /// Create a color from an [`AbsoluteColor`]. 610 pub fn from_absolute_color(color: AbsoluteColor) -> Self { 611 Color::Absolute(Box::new(Absolute { 612 color, 613 authored: None, 614 })) 615 } 616 617 /// Resolve this Color into an AbsoluteColor if it does not use any of the 618 /// forms that are invalid in an absolute color. 619 /// https://drafts.csswg.org/css-color-5/#absolute-color 620 /// Returns None if the specified color is not valid as an absolute color. 621 pub fn resolve_to_absolute(&self) -> Option<AbsoluteColor> { 622 use crate::values::specified::percentage::ToPercentage; 623 624 match self { 625 Self::Absolute(c) => Some(c.color), 626 Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(), 627 Self::ColorMix(ref mix) => { 628 let left = mix.left.resolve_to_absolute()?; 629 let right = mix.right.resolve_to_absolute()?; 630 Some(crate::color::mix::mix( 631 mix.interpolation, 632 &left, 633 mix.left_percentage.to_percentage(), 634 &right, 635 mix.right_percentage.to_percentage(), 636 mix.flags, 637 )) 638 }, 639 _ => None, 640 } 641 } 642 643 /// Parse a color, with quirks. 644 /// 645 /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk> 646 pub fn parse_quirky<'i, 't>( 647 context: &ParserContext, 648 input: &mut Parser<'i, 't>, 649 allow_quirks: AllowQuirks, 650 ) -> Result<Self, ParseError<'i>> { 651 input.try_parse(|i| Self::parse(context, i)).or_else(|e| { 652 if !allow_quirks.allowed(context.quirks_mode) { 653 return Err(e); 654 } 655 Color::parse_quirky_color(input).map_err(|_| e) 656 }) 657 } 658 659 fn parse_hash<'i>( 660 bytes: &[u8], 661 loc: &cssparser::SourceLocation, 662 ) -> Result<Self, ParseError<'i>> { 663 match cssparser::color::parse_hash_color(bytes) { 664 Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy( 665 r, g, b, a, 666 ))), 667 Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)), 668 } 669 } 670 671 /// Parse a <quirky-color> value. 672 /// 673 /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk> 674 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { 675 let location = input.current_source_location(); 676 let (value, unit) = match *input.next()? { 677 Token::Number { 678 int_value: Some(integer), 679 .. 680 } => (integer, None), 681 Token::Dimension { 682 int_value: Some(integer), 683 ref unit, 684 .. 685 } => (integer, Some(unit)), 686 Token::Ident(ref ident) => { 687 if ident.len() != 3 && ident.len() != 6 { 688 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 689 } 690 return Self::parse_hash(ident.as_bytes(), &location); 691 }, 692 ref t => { 693 return Err(location.new_unexpected_token_error(t.clone())); 694 }, 695 }; 696 if value < 0 { 697 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 698 } 699 let length = if value <= 9 { 700 1 701 } else if value <= 99 { 702 2 703 } else if value <= 999 { 704 3 705 } else if value <= 9999 { 706 4 707 } else if value <= 99999 { 708 5 709 } else if value <= 999999 { 710 6 711 } else { 712 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 713 }; 714 let total = length + unit.as_ref().map_or(0, |d| d.len()); 715 if total > 6 { 716 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 717 } 718 let mut serialization = [b'0'; 6]; 719 let space_padding = 6 - total; 720 let mut written = space_padding; 721 let mut buf = itoa::Buffer::new(); 722 let s = buf.format(value); 723 (&mut serialization[written..]) 724 .write_all(s.as_bytes()) 725 .unwrap(); 726 written += s.len(); 727 if let Some(unit) = unit { 728 written += (&mut serialization[written..]) 729 .write(unit.as_bytes()) 730 .unwrap(); 731 } 732 debug_assert_eq!(written, 6); 733 Self::parse_hash(&serialization, &location) 734 } 735 } 736 737 impl Color { 738 /// Converts this Color into a ComputedColor. 739 /// 740 /// If `context` is `None`, and the specified color requires data from 741 /// the context to resolve, then `None` is returned. 742 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> { 743 macro_rules! adjust_absolute_color { 744 ($color:expr) => {{ 745 // Computed lightness values can not be NaN. 746 if matches!( 747 $color.color_space, 748 ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch 749 ) { 750 $color.components.0 = normalize($color.components.0); 751 } 752 753 // Computed RGB and XYZ components can not be NaN. 754 if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() { 755 $color.components = $color.components.map(normalize); 756 } 757 758 $color.alpha = normalize($color.alpha); 759 }}; 760 } 761 762 Some(match *self { 763 Color::CurrentColor => ComputedColor::CurrentColor, 764 Color::Absolute(ref absolute) => { 765 let mut color = absolute.color; 766 adjust_absolute_color!(color); 767 ComputedColor::Absolute(color) 768 }, 769 Color::ColorFunction(ref color_function) => { 770 debug_assert!(color_function.has_origin_color(), 771 "no need for a ColorFunction if it doesn't contain an unresolvable origin color"); 772 773 // Try to eagerly resolve the color function before making it a computed color. 774 if let Ok(absolute) = color_function.resolve_to_absolute() { 775 ComputedColor::Absolute(absolute) 776 } else { 777 let color_function = color_function 778 .map_origin_color(|origin_color| origin_color.to_computed_color(context)); 779 ComputedColor::ColorFunction(Box::new(color_function)) 780 } 781 }, 782 Color::LightDark(ref ld) => ld.compute(context?), 783 Color::ColorMix(ref mix) => { 784 use crate::values::computed::percentage::Percentage; 785 786 let left = mix.left.to_computed_color(context)?; 787 let right = mix.right.to_computed_color(context)?; 788 789 ComputedColor::from_color_mix(GenericColorMix { 790 interpolation: mix.interpolation, 791 left, 792 left_percentage: Percentage(mix.left_percentage.get()), 793 right, 794 right_percentage: Percentage(mix.right_percentage.get()), 795 flags: mix.flags, 796 }) 797 }, 798 Color::ContrastColor(ref c) => { 799 ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?)) 800 }, 801 #[cfg(feature = "gecko")] 802 Color::System(system) => system.compute(context?), 803 #[cfg(feature = "gecko")] 804 Color::InheritFromBodyQuirk => { 805 ComputedColor::Absolute(context?.device().body_text_color()) 806 }, 807 }) 808 } 809 } 810 811 impl ToComputedValue for Color { 812 type ComputedValue = ComputedColor; 813 814 fn to_computed_value(&self, context: &Context) -> ComputedColor { 815 self.to_computed_color(Some(context)).unwrap_or_else(|| { 816 debug_assert!( 817 false, 818 "Specified color could not be resolved to a computed color!" 819 ); 820 ComputedColor::Absolute(AbsoluteColor::BLACK) 821 }) 822 } 823 824 fn from_computed_value(computed: &ComputedColor) -> Self { 825 match *computed { 826 ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()), 827 ComputedColor::ColorFunction(ref color_function) => { 828 let color_function = 829 color_function.map_origin_color(|o| Some(Self::from_computed_value(o))); 830 Self::ColorFunction(Box::new(color_function)) 831 }, 832 ComputedColor::CurrentColor => Color::CurrentColor, 833 ComputedColor::ColorMix(ref mix) => { 834 Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix))) 835 }, 836 ComputedColor::ContrastColor(ref c) => { 837 Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c))) 838 }, 839 } 840 } 841 } 842 843 impl SpecifiedValueInfo for Color { 844 const SUPPORTED_TYPES: u8 = CssType::COLOR; 845 846 fn collect_completion_keywords(f: KeywordsCollectFn) { 847 // We are not going to insert all the color names here. Caller and 848 // devtools should take care of them. XXX Actually, transparent 849 // should probably be handled that way as well. 850 // XXX `currentColor` should really be `currentcolor`. But let's 851 // keep it consistent with the old system for now. 852 f(&[ 853 "currentColor", 854 "transparent", 855 "rgb", 856 "rgba", 857 "hsl", 858 "hsla", 859 "hwb", 860 "color", 861 "lab", 862 "lch", 863 "oklab", 864 "oklch", 865 "color-mix", 866 "contrast-color", 867 "light-dark", 868 ]); 869 } 870 } 871 872 /// Specified value for the "color" property, which resolves the `currentcolor` 873 /// keyword to the parent color instead of self's color. 874 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] 875 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)] 876 pub struct ColorPropertyValue(pub Color); 877 878 impl ToComputedValue for ColorPropertyValue { 879 type ComputedValue = AbsoluteColor; 880 881 #[inline] 882 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 883 let current_color = context.builder.get_parent_inherited_text().clone_color(); 884 self.0 885 .to_computed_value(context) 886 .resolve_to_absolute(¤t_color) 887 } 888 889 #[inline] 890 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 891 ColorPropertyValue(Color::from_absolute_color(*computed).into()) 892 } 893 } 894 895 impl Parse for ColorPropertyValue { 896 fn parse<'i, 't>( 897 context: &ParserContext, 898 input: &mut Parser<'i, 't>, 899 ) -> Result<Self, ParseError<'i>> { 900 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue) 901 } 902 } 903 904 /// auto | <color> 905 pub type ColorOrAuto = GenericColorOrAuto<Color>; 906 907 /// caret-color 908 pub type CaretColor = GenericCaretColor<Color>; 909 910 impl Parse for CaretColor { 911 fn parse<'i, 't>( 912 context: &ParserContext, 913 input: &mut Parser<'i, 't>, 914 ) -> Result<Self, ParseError<'i>> { 915 ColorOrAuto::parse(context, input).map(GenericCaretColor) 916 } 917 } 918 919 /// Various flags to represent the color-scheme property in an efficient 920 /// way. 921 #[derive( 922 Clone, 923 Copy, 924 Debug, 925 Default, 926 Eq, 927 MallocSizeOf, 928 PartialEq, 929 SpecifiedValueInfo, 930 ToComputedValue, 931 ToResolvedValue, 932 ToShmem, 933 )] 934 #[repr(C)] 935 #[value_info(other_values = "light,dark,only")] 936 pub struct ColorSchemeFlags(u8); 937 bitflags! { 938 impl ColorSchemeFlags: u8 { 939 /// Whether the author specified `light`. 940 const LIGHT = 1 << 0; 941 /// Whether the author specified `dark`. 942 const DARK = 1 << 1; 943 /// Whether the author specified `only`. 944 const ONLY = 1 << 2; 945 } 946 } 947 948 /// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop> 949 #[derive( 950 Clone, 951 Debug, 952 Default, 953 MallocSizeOf, 954 PartialEq, 955 SpecifiedValueInfo, 956 ToComputedValue, 957 ToResolvedValue, 958 ToShmem, 959 ToTyped, 960 )] 961 #[repr(C)] 962 #[value_info(other_values = "normal")] 963 pub struct ColorScheme { 964 #[ignore_malloc_size_of = "Arc"] 965 idents: crate::ArcSlice<CustomIdent>, 966 /// The computed bits for the known color schemes (plus the only keyword). 967 pub bits: ColorSchemeFlags, 968 } 969 970 impl ColorScheme { 971 /// Returns the `normal` value. 972 pub fn normal() -> Self { 973 Self { 974 idents: Default::default(), 975 bits: ColorSchemeFlags::empty(), 976 } 977 } 978 979 /// Returns the raw bitfield. 980 pub fn raw_bits(&self) -> u8 { 981 self.bits.bits() 982 } 983 } 984 985 impl Parse for ColorScheme { 986 fn parse<'i, 't>( 987 _: &ParserContext, 988 input: &mut Parser<'i, 't>, 989 ) -> Result<Self, ParseError<'i>> { 990 let mut idents = vec![]; 991 let mut bits = ColorSchemeFlags::empty(); 992 993 let mut location = input.current_source_location(); 994 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { 995 let mut is_only = false; 996 match_ignore_ascii_case! { &ident, 997 "normal" => { 998 if idents.is_empty() && bits.is_empty() { 999 return Ok(Self::normal()); 1000 } 1001 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1002 }, 1003 "light" => bits.insert(ColorSchemeFlags::LIGHT), 1004 "dark" => bits.insert(ColorSchemeFlags::DARK), 1005 "only" => { 1006 if bits.intersects(ColorSchemeFlags::ONLY) { 1007 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1008 } 1009 bits.insert(ColorSchemeFlags::ONLY); 1010 is_only = true; 1011 }, 1012 _ => {}, 1013 }; 1014 1015 if is_only { 1016 if !idents.is_empty() { 1017 // Only is allowed either at the beginning or at the end, 1018 // but not in the middle. 1019 break; 1020 } 1021 } else { 1022 idents.push(CustomIdent::from_ident(location, &ident, &[])?); 1023 } 1024 location = input.current_source_location(); 1025 } 1026 1027 if idents.is_empty() { 1028 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 1029 } 1030 1031 Ok(Self { 1032 idents: crate::ArcSlice::from_iter(idents.into_iter()), 1033 bits, 1034 }) 1035 } 1036 } 1037 1038 impl ToCss for ColorScheme { 1039 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1040 where 1041 W: Write, 1042 { 1043 if self.idents.is_empty() { 1044 debug_assert!(self.bits.is_empty()); 1045 return dest.write_str("normal"); 1046 } 1047 let mut first = true; 1048 for ident in self.idents.iter() { 1049 if !first { 1050 dest.write_char(' ')?; 1051 } 1052 first = false; 1053 ident.to_css(dest)?; 1054 } 1055 if self.bits.intersects(ColorSchemeFlags::ONLY) { 1056 dest.write_str(" only")?; 1057 } 1058 Ok(()) 1059 } 1060 } 1061 1062 /// https://drafts.csswg.org/css-color-adjust/#print-color-adjust 1063 #[derive( 1064 Clone, 1065 Copy, 1066 Debug, 1067 MallocSizeOf, 1068 Parse, 1069 PartialEq, 1070 SpecifiedValueInfo, 1071 ToCss, 1072 ToComputedValue, 1073 ToResolvedValue, 1074 ToShmem, 1075 ToTyped, 1076 )] 1077 #[repr(u8)] 1078 pub enum PrintColorAdjust { 1079 /// Ignore backgrounds and darken text. 1080 Economy, 1081 /// Respect specified colors. 1082 Exact, 1083 } 1084 1085 /// https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop 1086 #[derive( 1087 Clone, 1088 Copy, 1089 Debug, 1090 MallocSizeOf, 1091 Parse, 1092 PartialEq, 1093 SpecifiedValueInfo, 1094 ToCss, 1095 ToComputedValue, 1096 ToResolvedValue, 1097 ToShmem, 1098 ToTyped, 1099 )] 1100 #[repr(u8)] 1101 pub enum ForcedColorAdjust { 1102 /// Adjust colors if needed. 1103 Auto, 1104 /// Respect specified colors. 1105 None, 1106 } 1107 1108 /// Possible values for the forced-colors media query. 1109 /// <https://drafts.csswg.org/mediaqueries-5/#forced-colors> 1110 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] 1111 #[repr(u8)] 1112 pub enum ForcedColors { 1113 /// Page colors are not being forced. 1114 None, 1115 /// Page colors would be forced in content. 1116 #[parse(condition = "ParserContext::chrome_rules_enabled")] 1117 Requested, 1118 /// Page colors are being forced. 1119 Active, 1120 } 1121 1122 impl ForcedColors { 1123 /// Returns whether forced-colors is active for this page. 1124 pub fn is_active(self) -> bool { 1125 matches!(self, Self::Active) 1126 } 1127 }