ladybird/Libraries/LibWeb/CSS/StyleValues/KeywordStyleValue.cpp
Callum Law 4730493667 LibWeb: Apply body attribute link colors as presentational hints
Previously we overrode the resolved values of the `activetext`,
`linktext`, and `visitedtext` system colors based on the values of the
body's `alink`, `link`, and `vlink` attributes respectively.

This was incorrect and the spec instead expects us to apply these to the
relevant link elements as presentational hints while leaving those
colors untouched for other users.
2026-06-04 16:14:05 +02:00

388 lines
16 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2021-2026, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "KeywordStyleValue.h"
#include <LibGfx/Palette.h>
#include <LibWeb/CSS/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/SystemColor.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Page/Page.h>
namespace Web::CSS {
void KeywordStyleValue::serialize(StringBuilder& builder, SerializationMode) const
{
builder.append(string_from_keyword(keyword()));
}
bool KeywordStyleValue::is_color(Keyword keyword)
{
switch (keyword) {
case Keyword::Accentcolor:
case Keyword::Accentcolortext:
case Keyword::Activeborder:
case Keyword::Activecaption:
case Keyword::Activetext:
case Keyword::Appworkspace:
case Keyword::Background:
case Keyword::Buttonborder:
case Keyword::Buttonface:
case Keyword::Buttonhighlight:
case Keyword::Buttonshadow:
case Keyword::Buttontext:
case Keyword::Canvas:
case Keyword::Canvastext:
case Keyword::Captiontext:
case Keyword::Currentcolor:
case Keyword::Field:
case Keyword::Fieldtext:
case Keyword::Graytext:
case Keyword::Highlight:
case Keyword::Highlighttext:
case Keyword::Inactiveborder:
case Keyword::Inactivecaption:
case Keyword::Inactivecaptiontext:
case Keyword::Infobackground:
case Keyword::Infotext:
case Keyword::LibwebButtonfacedisabled:
case Keyword::LibwebButtonfacehover:
case Keyword::LibwebLink:
case Keyword::LibwebPaletteActiveLink:
case Keyword::LibwebPaletteActiveWindowBorder1:
case Keyword::LibwebPaletteActiveWindowBorder2:
case Keyword::LibwebPaletteActiveWindowTitle:
case Keyword::LibwebPaletteBase:
case Keyword::LibwebPaletteBaseText:
case Keyword::LibwebPaletteButton:
case Keyword::LibwebPaletteButtonText:
case Keyword::LibwebPaletteDesktopBackground:
case Keyword::LibwebPaletteFocusOutline:
case Keyword::LibwebPaletteHighlightWindowBorder1:
case Keyword::LibwebPaletteHighlightWindowBorder2:
case Keyword::LibwebPaletteHighlightWindowTitle:
case Keyword::LibwebPaletteHoverHighlight:
case Keyword::LibwebPaletteInactiveSelection:
case Keyword::LibwebPaletteInactiveSelectionText:
case Keyword::LibwebPaletteInactiveWindowBorder1:
case Keyword::LibwebPaletteInactiveWindowBorder2:
case Keyword::LibwebPaletteInactiveWindowTitle:
case Keyword::LibwebPaletteLink:
case Keyword::LibwebPaletteMenuBase:
case Keyword::LibwebPaletteMenuBaseText:
case Keyword::LibwebPaletteMenuSelection:
case Keyword::LibwebPaletteMenuSelectionText:
case Keyword::LibwebPaletteMenuStripe:
case Keyword::LibwebPaletteMovingWindowBorder1:
case Keyword::LibwebPaletteMovingWindowBorder2:
case Keyword::LibwebPaletteMovingWindowTitle:
case Keyword::LibwebPaletteRubberBandBorder:
case Keyword::LibwebPaletteRubberBandFill:
case Keyword::LibwebPaletteRuler:
case Keyword::LibwebPaletteRulerActiveText:
case Keyword::LibwebPaletteRulerBorder:
case Keyword::LibwebPaletteRulerInactiveText:
case Keyword::LibwebPaletteSelection:
case Keyword::LibwebPaletteSelectionText:
case Keyword::LibwebPaletteSyntaxComment:
case Keyword::LibwebPaletteSyntaxControlKeyword:
case Keyword::LibwebPaletteSyntaxIdentifier:
case Keyword::LibwebPaletteSyntaxKeyword:
case Keyword::LibwebPaletteSyntaxNumber:
case Keyword::LibwebPaletteSyntaxOperator:
case Keyword::LibwebPaletteSyntaxPreprocessorStatement:
case Keyword::LibwebPaletteSyntaxPreprocessorValue:
case Keyword::LibwebPaletteSyntaxPunctuation:
case Keyword::LibwebPaletteSyntaxString:
case Keyword::LibwebPaletteSyntaxType:
case Keyword::LibwebPaletteTextCursor:
case Keyword::LibwebPaletteThreedHighlight:
case Keyword::LibwebPaletteThreedShadow1:
case Keyword::LibwebPaletteThreedShadow2:
case Keyword::LibwebPaletteVisitedLink:
case Keyword::LibwebPaletteWindow:
case Keyword::LibwebPaletteWindowText:
case Keyword::Linktext:
case Keyword::Mark:
case Keyword::Marktext:
case Keyword::Menu:
case Keyword::Menutext:
case Keyword::Scrollbar:
case Keyword::Selecteditem:
case Keyword::Selecteditemtext:
case Keyword::Threeddarkshadow:
case Keyword::Threedface:
case Keyword::Threedhighlight:
case Keyword::Threedlightshadow:
case Keyword::Threedshadow:
case Keyword::Visitedtext:
case Keyword::Window:
case Keyword::Windowframe:
case Keyword::Windowtext:
return true;
default:
return false;
}
}
bool KeywordStyleValue::has_color() const
{
return is_color(keyword());
}
Optional<Color> KeywordStyleValue::to_color(ColorResolutionContext color_resolution_context) const
{
if (keyword() == Keyword::Currentcolor) {
return color_resolution_context.current_color.value_or(Color::Black);
}
PreferredColorScheme scheme = color_resolution_context.color_scheme.value_or(PreferredColorScheme::Light);
// First, handle <system-color>s, since they don't strictly require a node.
// https://www.w3.org/TR/css-color-4/#css-system-colors
// https://www.w3.org/TR/css-color-4/#deprecated-system-colors
switch (keyword()) {
// AD-HOC: The spec says that 'accentcolor' and 'accentcolortext' should be resolved based on the value of the
// 'accent-color' property, but this isn't implemented by any other browsers and isn't fully specified
// (https://github.com/w3c/csswg-drafts/issues/10971).
case Keyword::Accentcolor:
return SystemColor::accent_color(scheme);
case Keyword::Accentcolortext:
return SystemColor::accent_color_text(scheme);
case Keyword::Activetext:
return SystemColor::active_text(scheme);
case Keyword::Buttonborder:
case Keyword::Activeborder:
case Keyword::Inactiveborder:
case Keyword::Threeddarkshadow:
case Keyword::Threedhighlight:
case Keyword::Threedlightshadow:
case Keyword::Threedshadow:
case Keyword::Windowframe:
return SystemColor::button_border(scheme);
case Keyword::Buttonface:
case Keyword::Buttonhighlight:
case Keyword::Buttonshadow:
case Keyword::Threedface:
return SystemColor::button_face(scheme);
case Keyword::Buttontext:
return SystemColor::button_text(scheme);
case Keyword::Canvas:
case Keyword::Appworkspace:
case Keyword::Background:
case Keyword::Inactivecaption:
case Keyword::Infobackground:
case Keyword::Menu:
case Keyword::Scrollbar:
case Keyword::Window:
return SystemColor::canvas(scheme);
case Keyword::Canvastext:
case Keyword::Activecaption:
case Keyword::Captiontext:
case Keyword::Infotext:
case Keyword::Menutext:
case Keyword::Windowtext:
return SystemColor::canvas_text(scheme);
case Keyword::Field:
return SystemColor::field(scheme);
case Keyword::Fieldtext:
return SystemColor::field_text(scheme);
case Keyword::Graytext:
case Keyword::Inactivecaptiontext:
return SystemColor::gray_text(scheme);
case Keyword::Highlight:
return SystemColor::highlight(scheme);
case Keyword::Highlighttext:
return SystemColor::highlight_text(scheme);
case Keyword::Mark:
return SystemColor::mark(scheme);
case Keyword::Marktext:
return SystemColor::mark_text(scheme);
case Keyword::Selecteditem:
return SystemColor::selected_item(scheme);
case Keyword::Selecteditemtext:
return SystemColor::selected_item_text(scheme);
case Keyword::LibwebButtonfacedisabled:
return SystemColor::button_face(scheme).with_alpha(128);
case Keyword::LibwebButtonfacehover:
return SystemColor::button_face(scheme).darkened(0.8f);
case Keyword::LibwebLink:
case Keyword::Linktext:
return SystemColor::link_text(scheme);
case Keyword::Visitedtext:
return SystemColor::visited_text(scheme);
default:
break;
}
if (!color_resolution_context.document) {
// FIXME: Can't resolve palette colors without a document.
return Color::Black;
}
auto palette = color_resolution_context.document->page().palette();
switch (keyword()) {
case Keyword::LibwebPaletteDesktopBackground:
return palette.color(ColorRole::DesktopBackground);
case Keyword::LibwebPaletteActiveWindowBorder1:
return palette.color(ColorRole::ActiveWindowBorder1);
case Keyword::LibwebPaletteActiveWindowBorder2:
return palette.color(ColorRole::ActiveWindowBorder2);
case Keyword::LibwebPaletteActiveWindowTitle:
return palette.color(ColorRole::ActiveWindowTitle);
case Keyword::LibwebPaletteInactiveWindowBorder1:
return palette.color(ColorRole::InactiveWindowBorder1);
case Keyword::LibwebPaletteInactiveWindowBorder2:
return palette.color(ColorRole::InactiveWindowBorder2);
case Keyword::LibwebPaletteInactiveWindowTitle:
return palette.color(ColorRole::InactiveWindowTitle);
case Keyword::LibwebPaletteMovingWindowBorder1:
return palette.color(ColorRole::MovingWindowBorder1);
case Keyword::LibwebPaletteMovingWindowBorder2:
return palette.color(ColorRole::MovingWindowBorder2);
case Keyword::LibwebPaletteMovingWindowTitle:
return palette.color(ColorRole::MovingWindowTitle);
case Keyword::LibwebPaletteHighlightWindowBorder1:
return palette.color(ColorRole::HighlightWindowBorder1);
case Keyword::LibwebPaletteHighlightWindowBorder2:
return palette.color(ColorRole::HighlightWindowBorder2);
case Keyword::LibwebPaletteHighlightWindowTitle:
return palette.color(ColorRole::HighlightWindowTitle);
case Keyword::LibwebPaletteMenuStripe:
return palette.color(ColorRole::MenuStripe);
case Keyword::LibwebPaletteMenuBase:
return palette.color(ColorRole::MenuBase);
case Keyword::LibwebPaletteMenuBaseText:
return palette.color(ColorRole::MenuBaseText);
case Keyword::LibwebPaletteMenuSelection:
return palette.color(ColorRole::MenuSelection);
case Keyword::LibwebPaletteMenuSelectionText:
return palette.color(ColorRole::MenuSelectionText);
case Keyword::LibwebPaletteWindow:
return palette.color(ColorRole::Window);
case Keyword::LibwebPaletteWindowText:
return palette.color(ColorRole::WindowText);
case Keyword::LibwebPaletteButton:
return palette.color(ColorRole::Button);
case Keyword::LibwebPaletteButtonText:
return palette.color(ColorRole::ButtonText);
case Keyword::LibwebPaletteBase:
return palette.color(ColorRole::Base);
case Keyword::LibwebPaletteBaseText:
return palette.color(ColorRole::BaseText);
case Keyword::LibwebPaletteThreedHighlight:
return palette.color(ColorRole::ThreedHighlight);
case Keyword::LibwebPaletteThreedShadow1:
return palette.color(ColorRole::ThreedShadow1);
case Keyword::LibwebPaletteThreedShadow2:
return palette.color(ColorRole::ThreedShadow2);
case Keyword::LibwebPaletteHoverHighlight:
return palette.color(ColorRole::HoverHighlight);
case Keyword::LibwebPaletteSelection:
return palette.color(ColorRole::Selection);
case Keyword::LibwebPaletteSelectionText:
return palette.color(ColorRole::SelectionText);
case Keyword::LibwebPaletteInactiveSelection:
return palette.color(ColorRole::InactiveSelection);
case Keyword::LibwebPaletteInactiveSelectionText:
return palette.color(ColorRole::InactiveSelectionText);
case Keyword::LibwebPaletteRubberBandFill:
return palette.color(ColorRole::RubberBandFill);
case Keyword::LibwebPaletteRubberBandBorder:
return palette.color(ColorRole::RubberBandBorder);
case Keyword::LibwebPaletteLink:
return palette.color(ColorRole::Link);
case Keyword::LibwebPaletteActiveLink:
return palette.color(ColorRole::ActiveLink);
case Keyword::LibwebPaletteVisitedLink:
return palette.color(ColorRole::VisitedLink);
case Keyword::LibwebPaletteRuler:
return palette.color(ColorRole::Ruler);
case Keyword::LibwebPaletteRulerBorder:
return palette.color(ColorRole::RulerBorder);
case Keyword::LibwebPaletteRulerActiveText:
return palette.color(ColorRole::RulerActiveText);
case Keyword::LibwebPaletteRulerInactiveText:
return palette.color(ColorRole::RulerInactiveText);
case Keyword::LibwebPaletteTextCursor:
return palette.color(ColorRole::TextCursor);
case Keyword::LibwebPaletteFocusOutline:
return palette.color(ColorRole::FocusOutline);
case Keyword::LibwebPaletteSyntaxComment:
return palette.color(ColorRole::SyntaxComment);
case Keyword::LibwebPaletteSyntaxNumber:
return palette.color(ColorRole::SyntaxNumber);
case Keyword::LibwebPaletteSyntaxString:
return palette.color(ColorRole::SyntaxString);
case Keyword::LibwebPaletteSyntaxType:
return palette.color(ColorRole::SyntaxType);
case Keyword::LibwebPaletteSyntaxPunctuation:
return palette.color(ColorRole::SyntaxPunctuation);
case Keyword::LibwebPaletteSyntaxOperator:
return palette.color(ColorRole::SyntaxOperator);
case Keyword::LibwebPaletteSyntaxKeyword:
return palette.color(ColorRole::SyntaxKeyword);
case Keyword::LibwebPaletteSyntaxControlKeyword:
return palette.color(ColorRole::SyntaxControlKeyword);
case Keyword::LibwebPaletteSyntaxIdentifier:
return palette.color(ColorRole::SyntaxIdentifier);
case Keyword::LibwebPaletteSyntaxPreprocessorStatement:
return palette.color(ColorRole::SyntaxPreprocessorStatement);
case Keyword::LibwebPaletteSyntaxPreprocessorValue:
return palette.color(ColorRole::SyntaxPreprocessorValue);
default:
return {};
}
}
ValueComparingNonnullRefPtr<StyleValue const> KeywordStyleValue::absolutized(ComputationContext const& context) const
{
if (!has_color())
return *this;
// The currentcolor keyword computes to itself.
// https://drafts.csswg.org/css-color-4/#resolving-other-colors
if (keyword() == Keyword::Currentcolor)
return *this;
ColorResolutionContext color_resolution_context;
if (context.abstract_element.has_value()) {
color_resolution_context.document = context.abstract_element->document();
color_resolution_context.calculation_resolution_context = CalculationResolutionContext::from_computation_context(context);
color_resolution_context.color_scheme = context.color_scheme;
}
auto resolved_color = to_color(color_resolution_context);
if (!resolved_color.has_value())
return *this;
return ColorFunctionStyleValue::create(
ColorStyleValue::ColorType::RGB,
NumberStyleValue::create(resolved_color->red()),
NumberStyleValue::create(resolved_color->green()),
NumberStyleValue::create(resolved_color->blue()),
NumberStyleValue::create(resolved_color->alpha() / 255.0f),
ColorSyntax::Legacy);
}
Vector<Parser::ComponentValue> KeywordStyleValue::tokenize() const
{
return { Parser::Token::create_ident(FlyString::from_utf8_without_validation(string_from_keyword(m_keyword).bytes())) };
}
// https://drafts.css-houdini.org/css-typed-om-1/#reify-ident
GC::Ref<CSSStyleValue> KeywordStyleValue::reify(JS::Realm& realm, FlyString const&) const
{
// 1. Return a new CSSKeywordValue with its value internal slot set to the serialization of ident.
return CSSKeywordValue::create(realm, FlyString::from_utf8_without_validation(string_from_keyword(m_keyword).bytes()));
}
}