mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb/CSS: Implement the light-dark color function
This commit is contained in:
parent
4a0ac312cc
commit
8e56109515
Notes:
github-actions[bot]
2025-01-08 11:19:19 +00:00
Author: https://github.com/Gingeh Commit: https://github.com/LadybirdBrowser/ladybird/commit/8e56109515a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3146 Reviewed-by: https://github.com/AtkinsSJ ✅
14 changed files with 275 additions and 0 deletions
|
@ -138,6 +138,7 @@ set(SOURCES
|
|||
CSS/StyleValues/CSSKeywordValue.cpp
|
||||
CSS/StyleValues/CSSLabLike.cpp
|
||||
CSS/StyleValues/CSSLCHLike.cpp
|
||||
CSS/StyleValues/CSSLightDark.cpp
|
||||
CSS/StyleValues/CSSRGB.cpp
|
||||
CSS/StyleValues/DisplayStyleValue.cpp
|
||||
CSS/StyleValues/EasingStyleValue.cpp
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSLCHLike.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSLabLike.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSLightDark.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSRGB.h>
|
||||
#include <LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
|
||||
|
@ -3491,6 +3492,40 @@ RefPtr<CSSStyleValue> Parser::parse_color_function(TokenStream<ComponentValue>&
|
|||
alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-color-5/#funcdef-light-dark
|
||||
RefPtr<CSSStyleValue> Parser::parse_light_dark_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
|
||||
outer_tokens.discard_whitespace();
|
||||
auto const& function_token = outer_tokens.consume_a_token();
|
||||
if (!function_token.is_function("light-dark"sv))
|
||||
return {};
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().value };
|
||||
|
||||
inner_tokens.discard_whitespace();
|
||||
auto light = parse_color_value(inner_tokens);
|
||||
if (!light)
|
||||
return {};
|
||||
|
||||
inner_tokens.discard_whitespace();
|
||||
if (!inner_tokens.consume_a_token().is(Token::Type::Comma))
|
||||
return {};
|
||||
|
||||
inner_tokens.discard_whitespace();
|
||||
auto dark = parse_color_value(inner_tokens);
|
||||
if (!dark)
|
||||
return {};
|
||||
|
||||
inner_tokens.discard_whitespace();
|
||||
if (inner_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
transaction.commit();
|
||||
return CSSLightDark::create(light.release_nonnull(), dark.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#color-syntax
|
||||
RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
@ -3522,6 +3557,8 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
|
|||
return oklab;
|
||||
if (auto oklch = parse_oklch_color_value(tokens))
|
||||
return oklch;
|
||||
if (auto light_dark = parse_light_dark_color_value(tokens))
|
||||
return light_dark;
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
|
|
|
@ -291,6 +291,7 @@ private:
|
|||
RefPtr<CSSStyleValue> parse_lch_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_oklch_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_color_function(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_light_dark_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_color_scheme_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_counter_value(TokenStream<ComponentValue>&);
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
Rec2020,
|
||||
XYZD50,
|
||||
XYZD65,
|
||||
LightDark, // This is used by CSSLightDark for light-dark(..., ...).
|
||||
};
|
||||
ColorType color_type() const { return m_color_type; }
|
||||
|
||||
|
|
37
Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp
Normal file
37
Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSLightDark.h"
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSLightDark::to_color(Optional<Layout::NodeWithStyle const&> node) const
|
||||
{
|
||||
if (node.has_value() && node.value().computed_values().color_scheme() == PreferredColorScheme::Dark)
|
||||
return m_properties.dark->to_color(node);
|
||||
|
||||
return m_properties.light->to_color(node);
|
||||
}
|
||||
|
||||
bool CSSLightDark::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_light_dark = verify_cast<CSSLightDark>(other_color);
|
||||
return m_properties == other_light_dark.m_properties;
|
||||
}
|
||||
|
||||
String CSSLightDark::to_string(SerializationMode mode) const
|
||||
{
|
||||
// FIXME: We don't have enough information to determine the computed value here.
|
||||
return MUST(String::formatted("light-dark({}, {})", m_properties.light->to_string(mode), m_properties.dark->to_string(mode)));
|
||||
}
|
||||
|
||||
}
|
43
Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h
Normal file
43
Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.csswg.org/css-color-5/#funcdef-light-dark
|
||||
class CSSLightDark final : public CSSColorValue {
|
||||
public:
|
||||
virtual ~CSSLightDark() override = default;
|
||||
|
||||
static ValueComparingNonnullRefPtr<CSSLightDark> create(ValueComparingNonnullRefPtr<CSSStyleValue> light, ValueComparingNonnullRefPtr<CSSStyleValue> dark)
|
||||
{
|
||||
return AK::adopt_ref(*new (nothrow) CSSLightDark(move(light), move(dark)));
|
||||
}
|
||||
|
||||
virtual bool equals(CSSStyleValue const&) const override;
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
virtual String to_string(SerializationMode) const override;
|
||||
|
||||
private:
|
||||
CSSLightDark(ValueComparingNonnullRefPtr<CSSStyleValue> light, ValueComparingNonnullRefPtr<CSSStyleValue> dark)
|
||||
: CSSColorValue(CSSColorValue::ColorType::LightDark)
|
||||
, m_properties { .light = move(light), .dark = move(dark) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> light;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> dark;
|
||||
bool operator==(Properties const&) const = default;
|
||||
};
|
||||
|
||||
Properties m_properties;
|
||||
};
|
||||
|
||||
} // Web::CSS
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CurrentColor can be used inside light-dark</title>
|
||||
<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">
|
||||
<link rel="match" href="../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
|
||||
<style>
|
||||
.square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: green;
|
||||
background-color: light-dark(currentColor, currentColor);
|
||||
}
|
||||
</style>
|
||||
<p>Test passes if there is a filled green square.</p>
|
||||
<div class="square"></div>
|
10
Tests/LibWeb/Screenshot/expected/color-scheme-ref.html
Normal file
10
Tests/LibWeb/Screenshot/expected/color-scheme-ref.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
<img src="../images/color-scheme-ref.png">
|
BIN
Tests/LibWeb/Screenshot/images/color-scheme-ref.png
Normal file
BIN
Tests/LibWeb/Screenshot/images/color-scheme-ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
69
Tests/LibWeb/Screenshot/input/color-scheme.html
Normal file
69
Tests/LibWeb/Screenshot/input/color-scheme.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<link rel="match" href="../expected/color-scheme-ref.html" />
|
||||
<style>
|
||||
body>div>div {
|
||||
padding: 1em;
|
||||
background-color: Canvas;
|
||||
}
|
||||
</style>
|
||||
<div style="display: flex">
|
||||
<div style="color-scheme: light">
|
||||
<div style="background: AccentColor; width: 10em; color: transparent">a</div>
|
||||
<div style="background: AccentColorText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ActiveText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonBorder; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonFace; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Canvas; width: 10em; color: transparent">a</div>
|
||||
<div style="background: CanvasText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Field; width: 10em; color: transparent">a</div>
|
||||
<div style="background: FieldText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: GrayText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Highlight; width: 10em; color: transparent">a</div>
|
||||
<div style="background: HighlightText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: LinkText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Mark; width: 10em; color: transparent">a</div>
|
||||
<div style="background: MarkText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: SelectedItem; width: 10em; color: transparent">a</div>
|
||||
<div style="background: SelectedItemText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: VisitedText; width: 10em; color: transparent">a</div>
|
||||
<div><button style="width: 10rem">Button</button></div>
|
||||
<div><button style="width: 10rem" disabled>Disabled</button></div>
|
||||
<div><input style="width: 10rem" value="Input"></input></div>
|
||||
<div><input type="checkbox"></input><input type="checkbox" checked></input></div>
|
||||
<div><input type="checkbox" switch></input><input type="checkbox" switch checked></input></div>
|
||||
<div><input type="radio" name="lightradio"></input><input type="radio" name="lightradio" checked></input></div>
|
||||
<div><progress style="width: 10rem" value="70" max="100"></progress></div>
|
||||
<div><input type="range" style="width: 10rem" value="70" max="100"></input></div>
|
||||
<div style="color: light-dark(red,green)">light-dark(red,green)</div>
|
||||
</div>
|
||||
<div style="color-scheme: dark">
|
||||
<div style="background: AccentColor; width: 10em; color: transparent">a</div>
|
||||
<div style="background: AccentColorText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ActiveText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonBorder; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonFace; width: 10em; color: transparent">a</div>
|
||||
<div style="background: ButtonText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Canvas; width: 10em; color: transparent">a</div>
|
||||
<div style="background: CanvasText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Field; width: 10em; color: transparent">a</div>
|
||||
<div style="background: FieldText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: GrayText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Highlight; width: 10em; color: transparent">a</div>
|
||||
<div style="background: HighlightText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: LinkText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: Mark; width: 10em; color: transparent">a</div>
|
||||
<div style="background: MarkText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: SelectedItem; width: 10em; color: transparent">a</div>
|
||||
<div style="background: SelectedItemText; width: 10em; color: transparent">a</div>
|
||||
<div style="background: VisitedText; width: 10em; color: transparent">a</div>
|
||||
<div><button style="width: 10rem">Button</button></div>
|
||||
<div><button style="width: 10rem" disabled>Disabled</button></div>
|
||||
<div><input style="width: 10rem" value="Input"></input></div>
|
||||
<div><input type="checkbox"></input><input type="checkbox" checked></input></div>
|
||||
<div><input type="checkbox" switch></input><input type="checkbox" switch checked></input></div>
|
||||
<div><input type="radio" name="darkradio"></input><input type="radio" name="darkradio" checked></input></div>
|
||||
<div><progress style="width: 10rem" value="70" max="100"></progress></div>
|
||||
<div><input type="range" style="width: 10rem" value="70" max="100"></input></div>
|
||||
<div style="color: light-dark(red,green)">light-dark(red,green)</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 2 tests
|
||||
|
||||
2 Pass
|
||||
Pass light-dark(white, black)
|
||||
Pass light-dark(light-dark(white, red), red)
|
|
@ -0,0 +1,6 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 1 tests
|
||||
|
||||
1 Fail
|
||||
Fail curentColor in light-dark() refers to parent color
|
|
@ -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>
|
|
@ -0,0 +1,21 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CurrentColor can be used inside light-dark for the color property</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-color-5/#light-dark">
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
#parent { color: green; }
|
||||
#child {
|
||||
color-scheme: dark;
|
||||
color: light-dark(red, currentColor);
|
||||
}
|
||||
</style>
|
||||
<div id="parent">
|
||||
<div id="child">Text should be green</div>
|
||||
</div>
|
||||
<script>
|
||||
test(() => {
|
||||
assert_equals(getComputedStyle(child).color, "rgb(0, 128, 0)");
|
||||
}, "curentColor in light-dark() refers to parent color");
|
||||
</script>
|
Loading…
Add table
Reference in a new issue