/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Specified color values.
usesuper::AllowQuirks; usecrate::color::mix::ColorInterpolationMethod; usecrate::color::{parsing, AbsoluteColor, ColorFunction, ColorSpace}; usecrate::media_queries::Device; usecrate::parser::{Parse, ParserContext}; usecrate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; usecrate::values::generics::color::{
ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
}; usecrate::values::specified::Percentage; usecrate::values::{normalize, CustomIdent}; use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token}; use std::fmt::{self, Write}; use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A specified color-mix(). pubtype ColorMix = GenericColorMix<Color, Percentage>;
let right = Color::parse_internal(context, input, preserve_authored)?;
if right_percentage.is_none() {
right_percentage = try_parse_percentage(input);
}
let right_percentage = right_percentage
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
if left_percentage.get() + right_percentage.get() <= 0.0 { // If the percentages sum to zero, the function is invalid. return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
// Pass RESULT_IN_MODERN_SYNTAX here, because the result of the color-mix() function // should always be in the modern color syntax to allow for out of gamut results and // to preserve floating point precision.
Ok(ColorMix {
interpolation,
left,
left_percentage,
right,
right_percentage,
flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
})
})
}
}
/// Container holding an absolute color and the text specified by an author. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pubstruct Absolute { /// The specified color. pub color: AbsoluteColor, /// Authored representation. pub authored: Option<Box<str>>,
}
/// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pubenum Color { /// The 'currentColor' keyword
CurrentColor, /// An absolute color. /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function
Absolute(Box<Absolute>), /// A color function that could not be resolved to a [Color::Absolute] color at parse time. /// Right now this is only the case for relative colors with `currentColor` as the origin.
ColorFunction(Box<ColorFunction<Self>>), /// A system color. #[cfg(feature = "gecko")]
System(SystemColor), /// A color mix.
ColorMix(Box<ColorMix>), /// A light-dark() color.
LightDark(Box<LightDark>), /// Quirksmode-only rule for inheriting color from the body #[cfg(feature = "gecko")]
InheritFromBodyQuirk,
}
/// A light-dark(<light-color>, <dark-color>) function. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToCss)] #[css(function, comma)] pubstruct LightDark { /// The <color> that is returned when using a light theme. pub light: Color, /// The <color> that is returned when using a dark theme. pub dark: Color,
}
impl LightDark { fn compute(&self, cx: &Context) -> ComputedColor { let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme); if cx.for_non_inherited_property {
cx.rule_cache_conditions
.borrow_mut()
.set_color_scheme_dependency(cx.builder.color_scheme);
} let used = if dark { &self.dark } else { &self.light };
used.to_computed_value(cx)
}
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
preserve_authored: PreserveAuthored,
) -> Result<Self, ParseError<'i>> { let enabled =
context.chrome_rules_enabled() || static_prefs::pref!("layout.css.light-dark.enabled"); if !enabled { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
input.expect_function_matching("light-dark")?;
input.parse_nested_block(|input| { let light = Color::parse_internal(context, input, preserve_authored)?;
input.expect_comma()?; let dark = Color::parse_internal(context, input, preserve_authored)?;
Ok(LightDark { light, dark })
})
}
}
impl From<AbsoluteColor> for Color { #[inline] fn from(value: AbsoluteColor) -> Self { Self::from_absolute_color(value)
}
}
/// System colors. A bunch of these are ad-hoc, others come from Windows: /// /// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor /// /// Others are HTML/CSS specific. Spec is: /// /// https://drafts.csswg.org/css-color/#css-system-colors /// https://drafts.csswg.org/css-color/#deprecated-system-colors #[allow(missing_docs)] #[cfg(feature = "gecko")] #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] #[repr(u8)] pubenum SystemColor {
Activeborder, /// Background in the (active) titlebar.
Activecaption,
Appworkspace,
Background,
Buttonface,
Buttonhighlight,
Buttonshadow,
Buttontext,
Buttonborder, /// Text color in the (active) titlebar.
Captiontext, #[parse(aliases = "-moz-field")]
Field, /// Used for disabled field backgrounds. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozDisabledfield, #[parse(aliases = "-moz-fieldtext")]
Fieldtext,
Graytext,
Highlight,
Highlighttext,
Inactiveborder, /// Background in the (inactive) titlebar.
Inactivecaption, /// Text color in the (inactive) titlebar.
Inactivecaptiontext,
Infobackground,
Infotext,
Menu,
Menutext,
Scrollbar,
Threeddarkshadow,
Threedface,
Threedhighlight,
Threedlightshadow,
Threedshadow,
Window,
Windowframe,
Windowtext, #[parse(aliases = "-moz-default-color")]
Canvastext, #[parse(aliases = "-moz-default-background-color")]
Canvas,
MozDialog,
MozDialogtext, /// Used for selected but not focused cell backgrounds. #[parse(aliases = "-moz-html-cellhighlight")]
MozCellhighlight, /// Used for selected but not focused cell text. #[parse(aliases = "-moz-html-cellhighlighttext")]
MozCellhighlighttext, /// Used for selected and focused html cell backgrounds.
Selecteditem, /// Used for selected and focused html cell text.
Selecteditemtext, /// Used to button text background when hovered.
MozButtonhoverface, /// Used to button text color when hovered.
MozButtonhovertext, /// Used for menu item backgrounds when hovered.
MozMenuhover, /// Used for menu item backgrounds when hovered and disabled. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMenuhoverdisabled, /// Used for menu item text when hovered.
MozMenuhovertext, /// Used for menubar item text when hovered.
MozMenubarhovertext,
/// On platforms where these colors are the same as -moz-field, use /// -moz-fieldtext as foreground color
MozEventreerow,
MozOddtreerow,
/// Used for button text when pressed. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtonactivetext,
/// Used for button background when pressed. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtonactiveface,
/// Used for button background when disabled. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtondisabledface,
/// Colors used for the header bar (sorta like the tab bar / menubar). #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbar, #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbartext, #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbarinactive, #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbarinactivetext,
/// Foreground color of default buttons. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacDefaultbuttontext, /// Ring color around text fields and lists. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacFocusring, /// Text color of disabled text on toolbars. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacDisabledtoolbartext, /// The background of a sidebar. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebar, /// The foreground color of a sidebar. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebartext, /// The border color of a sidebar. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebarborder,
/// The background-color for :autofill-ed inputs. #[parse(condition = "ParserContext::chrome_rules_enabled")]
MozAutofillBackground,
/// Hyperlink color extracted from the system, not affected by the browser.anchor_color user /// pref. /// /// There is no OS-specified safe background color for this text, but it is used regularly /// within Windows and the Gnome DE on Dialog and Window colors. #[css(skip)]
MozNativehyperlinktext,
/// As above, but visited link color. #[css(skip)]
MozNativevisitedhyperlinktext,
let color = cx.device().system_nscolor(*self, cx.builder.color_scheme); if cx.for_non_inherited_property {
cx.rule_cache_conditions
.borrow_mut()
.set_color_scheme_dependency(cx.builder.color_scheme);
} if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor();
}
ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
}
}
/// Whether to preserve authored colors during parsing. That's useful only if we /// plan to serialize the color back. #[derive(Copy, Clone)] enum PreserveAuthored {
No,
Yes,
}
impl Color { fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
preserve_authored: PreserveAuthored,
) -> Result<Self, ParseError<'i>> { let authored = match preserve_authored {
PreserveAuthored::No => None,
PreserveAuthored::Yes => { // Currently we only store authored value for color keywords, // because all browsers serialize those values as keywords for // specified value. let start = input.state(); let authored = input.expect_ident_cloned().ok();
input.reset(&start);
authored
},
};
match input.try_parse(|i| parsing::parse_color_with(context, i)) {
Ok(mut color) => { iflet Color::Absolute(refmut absolute) = color { // Because we can't set the `authored` value at construction time, we have to set it // here.
absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
}
Ok(color)
},
Err(e) => { #[cfg(feature = "gecko")]
{ iflet Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { return Ok(Color::System(system));
}
}
/// Returns whether a given color is valid for authors. pubfn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
input
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
.is_ok()
}
/// Tries to parse a color and compute it with a given device. pubfn parse_and_compute(
context: &ParserContext,
input: &mut Parser,
device: Option<&Device>,
) -> Option<ComputedColor> { usecrate::error_reporting::ContextualParseError; let start = input.position(); let result = input
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
let specified = match result {
Ok(s) => s,
Err(e) => { if !context.error_reporting_enabled() { return None;
} // Ignore other kinds of errors that might be reported, such as // ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken), // since Gecko didn't use to report those to the error console. // // TODO(emilio): Revise whether we want to keep this at all, we // use this only for canvas, this warnings are disabled by // default and not available on OffscreenCanvas anyways... iflet ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind { let location = e.location.clone(); let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
context.log_css_error(location, error);
} return None;
},
};
/// Returns transparent value. #[inline] pubfn transparent() -> Self { // We should probably set authored to "transparent", but maybe it doesn't matter. Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
}
/// Create a color from an [`AbsoluteColor`]. pubfn from_absolute_color(color: AbsoluteColor) -> Self {
Color::Absolute(Box::new(Absolute {
color,
authored: None,
}))
}
/// Resolve this Color into an AbsoluteColor if it does not use any of the /// forms that are invalid in an absolute color. /// https://drafts.csswg.org/css-color-5/#absolute-color /// Returns None if the specified color is not valid as an absolute color. pubfn resolve_to_absolute(&self) -> Option<AbsoluteColor> { usecrate::values::specified::percentage::ToPercentage;
matchself { Self::Absolute(c) => Some(c.color), Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(), Self::ColorMix(ref mix) => { let left = mix.left.resolve_to_absolute()?; let right = mix.right.resolve_to_absolute()?;
Some(crate::color::mix::mix(
mix.interpolation,
&left,
mix.left_percentage.to_percentage(),
&right,
mix.right_percentage.to_percentage(),
mix.flags,
))
},
_ => None,
}
}
/// Parse a <quirky-color> value. /// /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk> fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); let (value, unit) = match *input.next()? {
Token::Number {
int_value: Some(integer),
..
} => (integer, None),
Token::Dimension {
int_value: Some(integer), ref unit,
..
} => (integer, Some(unit)),
Token::Ident(ref ident) => { if ident.len() != 3 && ident.len() != 6 { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} returnSelf::parse_hash(ident.as_bytes(), &location);
}, ref t => { return Err(location.new_unexpected_token_error(t.clone()));
},
}; if value < 0 { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} let length = if value <= 9 { 1
} elseif value <= 99 { 2
} elseif value <= 999 { 3
} elseif value <= 9999 { 4
} elseif value <= 99999 { 5
} elseif value <= 999999 { 6
} else { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}; let total = length + unit.as_ref().map_or(0, |d| d.len()); if total > 6 { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} letmut serialization = [b'0'; 6]; let space_padding = 6 - total; letmut written = space_padding; letmut buf = itoa::Buffer::new(); let s = buf.format(value);
(&mut serialization[written..])
.write_all(s.as_bytes())
.unwrap();
written += s.len(); iflet Some(unit) = unit {
written += (&mut serialization[written..])
.write(unit.as_bytes())
.unwrap();
}
debug_assert_eq!(written, 6); Self::parse_hash(&serialization, &location)
}
}
impl Color { /// Converts this Color into a ComputedColor. /// /// If `context` is `None`, and the specified color requires data from /// the context to resolve, then `None` is returned. pubfn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
macro_rules! adjust_absolute_color {
($color:expr) => {{ // Computed lightness values can not be NaN. if matches!(
$color.color_space,
ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
) {
$color.components.0 = normalize($color.components.0);
}
// Computed RGB and XYZ components can not be NaN. if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
$color.components = $color.components.map(normalize);
}
$color.alpha = normalize($color.alpha);
}};
}
Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Absolute(ref absolute) => { letmut color = absolute.color;
adjust_absolute_color!(color);
ComputedColor::Absolute(color)
},
Color::ColorFunction(ref color_function) => {
debug_assert!(color_function.has_origin_color(), "no need for a ColorFunction if it doesn't contain an unresolvable origin color");
// Try to eagerly resolve the color function before making it a computed color. iflet Ok(absolute) = color_function.resolve_to_absolute() {
ComputedColor::Absolute(absolute)
} else { let color_function = color_function
.map_origin_color(|origin_color| origin_color.to_computed_color(context));
ComputedColor::ColorFunction(Box::new(color_function))
}
},
Color::LightDark(ref ld) => ld.compute(context?),
Color::ColorMix(ref mix) => { usecrate::values::computed::percentage::Percentage;
let left = mix.left.to_computed_color(context)?; let right = mix.right.to_computed_color(context)?;
impl ToComputedValue for Color { type ComputedValue = ComputedColor;
fn to_computed_value(&self, context: &Context) -> ComputedColor { self.to_computed_color(Some(context)).unwrap_or_else(|| {
debug_assert!( false, "Specified color could not be resolved to a computed color!"
);
ComputedColor::Absolute(AbsoluteColor::BLACK)
})
}
impl SpecifiedValueInfo for Color { const SUPPORTED_TYPES: u8 = CssType::COLOR;
fn collect_completion_keywords(f: KeywordsCollectFn) { // We are not going to insert all the color names here. Caller and // devtools should take care of them. XXX Actually, transparent // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now.
f(&[ "currentColor", "transparent", "rgb", "rgba", "hsl", "hsla", "hwb", "color", "lab", "lch", "oklab", "oklch", "color-mix", "light-dark",
]);
}
}
/// Specified value for the "color" property, which resolves the `currentcolor` /// keyword to the parent color instead of self's color. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pubstruct ColorPropertyValue(pub Color);
impl ToComputedValue for ColorPropertyValue { type ComputedValue = AbsoluteColor;
if is_only { if !idents.is_empty() { // Only is allowed either at the beginning or at the end, // but not in the middle. break;
}
} else {
idents.push(CustomIdent::from_ident(location, &ident, &[])?);
}
location = input.current_source_location();
}
if idents.is_empty() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
/// Possible values for the forced-colors media query. /// <https://drafts.csswg.org/mediaqueries-5/#forced-colors> #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] #[repr(u8)] pubenum ForcedColors { /// Page colors are not being forced.
None, /// Page colors would be forced in content. #[parse(condition = "ParserContext::chrome_rules_enabled")]
Requested, /// Page colors are being forced.
Active,
}
impl ForcedColors { /// Returns whether forced-colors is active for this page. pubfn is_active(self) -> bool {
matches!(self, Self::Active)
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.