Bug 1845679 - Add an internal light-dark() function to allow defining colors reacting to color-scheme. r=dshin

This implement something like what's proposed in
https://github.com/w3c/csswg-drafts/issues/7561, but the simplest
version possible, until issues are resolved.

This will allow the front-end to experiment with it and use it (and we
can update to the standard feature once there's a spec for it).

Differential Revision: https://phabricator.services.mozilla.com/D184680
This commit is contained in:
Emilio Cobos Álvarez 2023-07-27 18:04:23 +00:00
Родитель e928053513
Коммит e3aa78816f
7 изменённых файлов: 104 добавлений и 3 удалений

Просмотреть файл

@ -696,6 +696,12 @@ bool Gecko_IsDocumentBody(const Element* aElement) {
return doc && doc->GetBodyElement() == aElement;
}
bool Gecko_IsDarkColorScheme(const Document* aDoc,
const StyleColorScheme* aStyle) {
return LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits) ==
ColorScheme::Dark;
}
nscolor Gecko_ComputeSystemColor(StyleSystemColor aColor, const Document* aDoc,
const StyleColorScheme* aStyle) {
auto colorScheme = LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits);

Просмотреть файл

@ -527,6 +527,8 @@ void Gecko_StyleSheet_AddRef(const mozilla::StyleSheet* aSheet);
void Gecko_StyleSheet_Release(const mozilla::StyleSheet* aSheet);
bool Gecko_IsDocumentBody(const mozilla::dom::Element* element);
bool Gecko_IsDarkColorScheme(const mozilla::dom::Document*,
const mozilla::StyleColorScheme*);
nscolor Gecko_ComputeSystemColor(mozilla::StyleSystemColor,
const mozilla::dom::Document*,
const mozilla::StyleColorScheme*);

Просмотреть файл

@ -8375,6 +8375,13 @@
mirror: always
rust: true
# Is support for light-dark() on content enabled?
- name: layout.css.light-dark.enabled
type: RelaxedAtomicBool
value: false
mirror: always
rust: true
# Is support for color-mix with non-SRGB color spaces on content enabled?
- name: layout.css.color-mix.color-spaces.enabled
type: RelaxedAtomicBool

Просмотреть файл

@ -487,6 +487,11 @@ impl Device {
unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) }
}
/// Returns whether the used color-scheme for `color-scheme` should be dark.
pub(crate) fn is_dark_color_scheme(&self, color_scheme: &ColorScheme) -> bool {
unsafe { bindings::Gecko_IsDarkColorScheme(self.document(), color_scheme) }
}
/// Returns the default background color.
///
/// This is only for forced-colors/high-contrast, so looking at light colors

Просмотреть файл

@ -63,7 +63,7 @@ impl ColorMix {
let mut right_percentage = try_parse_percentage(input);
let right = Color::parse(context, input)?;
let right = Color::parse_internal(context, input, preserve_authored)?;
if right_percentage.is_none() {
right_percentage = try_parse_percentage(input);
@ -130,11 +130,53 @@ pub enum Color {
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)]
pub struct LightDark {
light: Color,
dark: Color,
}
impl LightDark {
fn compute(&self, cx: &Context) -> ComputedColor {
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
let dark = cx.device().is_dark_color_scheme(&style_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 {
@ -376,8 +418,7 @@ impl SystemColor {
use crate::gecko::values::convert_nscolor_to_absolute_color;
use crate::gecko_bindings::bindings;
// TODO: We should avoid cloning here most likely, though it's
// cheap-ish.
// TODO: We should avoid cloning here most likely, though it's cheap-ish.
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
let color = cx.device().system_nscolor(*self, &style_color_scheme);
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
@ -574,6 +615,7 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorParser<'i> for ColorParser<'a, 'b> {
/// 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,
@ -645,6 +687,11 @@ impl Color {
return Ok(Color::ColorMix(Box::new(mix)));
}
if let Ok(ld) = input.try_parse(|i| LightDark::parse(context, i, preserve_authored))
{
return Ok(Color::LightDark(Box::new(ld)));
}
match e.kind {
ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
@ -717,6 +764,7 @@ impl ToCss for Color {
Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
Color::Absolute(ref absolute) => absolute.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest),
Color::LightDark(ref ld) => ld.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest),
#[cfg(feature = "gecko")]
@ -732,6 +780,10 @@ impl Color {
Self::InheritFromBodyQuirk => false,
Self::CurrentColor | Color::System(..) => true,
Self::Absolute(ref absolute) => allow_transparent && absolute.color.alpha() == 0.0,
Self::LightDark(ref ld) => {
ld.light.honored_in_forced_colors_mode(allow_transparent) &&
ld.dark.honored_in_forced_colors_mode(allow_transparent)
},
Self::ColorMix(ref mix) => {
mix.left.honored_in_forced_colors_mode(allow_transparent) &&
mix.right.honored_in_forced_colors_mode(allow_transparent)
@ -854,6 +906,7 @@ impl Color {
Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Absolute(ref absolute) => ComputedColor::Absolute(absolute.color),
Color::LightDark(ref ld) => ld.compute(context?),
Color::ColorMix(ref mix) => {
use crate::values::computed::percentage::Percentage;

Просмотреть файл

@ -0,0 +1,2 @@
[light-dark.html]
prefs: [layout.css.light-dark.enabled:true]

Просмотреть файл

@ -0,0 +1,26 @@
<!doctype html>
<title>light-dark() color-scheme propagation</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<link rel="help" href="https://drafts.csswg.org/css-color-adjust/#color-scheme-effect">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7561">
<div id="system"></div>
<div id="light" style="color-scheme: light"></div>
<div id="dark" style="color-scheme: dark"></div>
<script>
const system_is_dark = matchMedia("(prefers-color-scheme: dark)").matches;
const elements = ["system", "light", "dark"].map(document.getElementById.bind(document));
function test_light_dark(color, expected_light, expected_dark) {
test(() => {
for (let element of elements) {
let should_be_dark = element.id == "dark" || (element.id == "system" && system_is_dark);
element.style.backgroundColor = color;
assert_not_equals(element.style.backgroundColor, "", "Should be valid");
assert_equals(getComputedStyle(element).backgroundColor, should_be_dark ? expected_dark : expected_light);
}
}, color);
}
test_light_dark("light-dark(white, black)", "rgb(255, 255, 255)", "rgb(0, 0, 0)");
test_light_dark("light-dark(light-dark(white, red), red)", "rgb(255, 255, 255)", "rgb(255, 0, 0)");
</script>