diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 5996c6032db..ec17aadcb82 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -264,6 +264,7 @@ set(SOURCES CSS/StyleValues/GridTemplateAreaStyleValue.cpp CSS/StyleValues/GridTrackPlacementStyleValue.cpp CSS/StyleValues/GridTrackSizeListStyleValue.cpp + CSS/StyleValues/ImageSetStyleValue.cpp CSS/StyleValues/ImageStyleValue.cpp CSS/StyleValues/IntegerStyleValue.cpp CSS/StyleValues/KeywordStyleValue.cpp diff --git a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp index 53f07329aef..7bec61ce527 100644 --- a/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp +++ b/Libraries/LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.cpp @@ -85,6 +85,22 @@ bool contains_guaranteed_invalid_value(Vector const& values) return false; } +static bool contains_attr_tainted_value(Vector const& values) +{ + for (auto const& value : values) { + if (value.contains_attr_tainted_value()) + return true; + } + return false; +} + +static Vector mark_as_attr_tainted(Vector values) +{ + for (auto& value : values) + value.set_attr_tainted(); + return values; +} + // https://drafts.csswg.org/css-values-5/#replace-an-attr-function static Vector replace_an_attr_function(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, ArbitrarySubstitutionFunctionArguments const& arguments) { @@ -218,7 +234,7 @@ static Vector replace_an_attr_function(DOM::AbstractElement& ele }); if (return_from_step_4) { if (step_4_result.has_value()) - return step_4_result.release_value(); + return mark_as_attr_tainted(step_4_result.release_value()); return failure(); } @@ -228,7 +244,7 @@ static Vector replace_an_attr_function(DOM::AbstractElement& ele [](Empty) { return true; }, [](RawStringKeyword) { return true; }, [](auto&) { return false; })) { - return { Token::create_string(*attribute_value) }; + return mark_as_attr_tainted({ Token::create_string(*attribute_value) }); } // 6. Substitute arbitrary substitution functions in attr value, with «"attribute", attr name» as the substitution @@ -242,7 +258,7 @@ static Vector replace_an_attr_function(DOM::AbstractElement& ele auto parsed_value = parse_with_a_syntax(ParsingParams { element.document() }, substituted_values, *syntax.get>()); if (parsed_value->is_guaranteed_invalid()) return failure(); - return parsed_value->tokenize(); + return mark_as_attr_tainted(parsed_value->tokenize()); // 7. FAILURE: // NB: Step 7 is a lambda defined at the top of the function. @@ -317,6 +333,7 @@ static Vector replace_an_if_function(DOM::AbstractElement& eleme // 1. Substitute arbitrary substitution functions in the first of branch, then parse the // result as an . If parsing returns failure, continue; otherwise, let the result be condition. auto substituted_condition = substitute_arbitrary_substitution_functions(element, guarded_contexts, branch.condition); + auto condition_is_attr_tainted = contains_attr_tainted_value(substituted_condition); TokenStream tokens { substituted_condition }; auto maybe_parsed_if_condition = parser.parse_if_condition(tokens); @@ -339,7 +356,10 @@ static Vector replace_an_if_function(DOM::AbstractElement& eleme if (!branch.value.has_value()) return {}; - return substitute_arbitrary_substitution_functions(element, guarded_contexts, branch.value.value()); + auto result = substitute_arbitrary_substitution_functions(element, guarded_contexts, branch.value.value()); + if (condition_is_attr_tainted) + return mark_as_attr_tainted(move(result)); + return result; } // 2. Return nothing (an empty sequence of component values). diff --git a/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp b/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp index 5d8dfd5ce97..1a6866c485a 100644 --- a/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp +++ b/Libraries/LibWeb/CSS/Parser/ComponentValue.cpp @@ -106,4 +106,28 @@ bool ComponentValue::contains_guaranteed_invalid_value() const }); } +bool ComponentValue::contains_attr_tainted_value() const +{ + if (m_attr_tainted) + return true; + + return m_value.visit( + [](Token const&) { + return false; + }, + [](SimpleBlock const& block) { + return block.value + .first_matching([](auto const& it) { return it.contains_attr_tainted_value(); }) + .has_value(); + }, + [](Function const& function) { + return function.value + .first_matching([](auto const& it) { return it.contains_attr_tainted_value(); }) + .has_value(); + }, + [](GuaranteedInvalidValue const&) { + return false; + }); +} + } diff --git a/Libraries/LibWeb/CSS/Parser/ComponentValue.h b/Libraries/LibWeb/CSS/Parser/ComponentValue.h index 9a4454a3427..f2e70ab0833 100644 --- a/Libraries/LibWeb/CSS/Parser/ComponentValue.h +++ b/Libraries/LibWeb/CSS/Parser/ComponentValue.h @@ -44,6 +44,8 @@ public: bool is_guaranteed_invalid() const { return m_value.has(); } bool contains_guaranteed_invalid_value() const; + bool contains_attr_tainted_value() const; + void set_attr_tainted() { m_attr_tainted = true; } String to_string() const; String to_debug_string() const; @@ -53,6 +55,7 @@ public: private: Variant m_value; + bool m_attr_tainted { false }; }; } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index af6c056c2d6..e4ae5884e1f 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -427,7 +427,13 @@ private: RefPtr parse_rect_value(TokenStream&); RefPtr parse_ratio_value(TokenStream&); RefPtr parse_string_value(TokenStream&); + enum class AllowImageSet { + No, + Yes, + }; RefPtr parse_image_value(TokenStream&); + RefPtr parse_image_value(TokenStream&, AllowImageSet); + RefPtr parse_image_set_function(TokenStream&); RefPtr parse_paint_value(TokenStream&); enum class PositionParsingMode { Normal, diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index f67affe2676..afa1fc386b6 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -2674,6 +2675,92 @@ RefPtr Parser::parse_string_value(TokenStream Parser::parse_image_value(TokenStream& tokens) +{ + return parse_image_value(tokens, AllowImageSet::Yes); +} + +RefPtr Parser::parse_image_set_function(TokenStream& tokens) +{ + tokens.discard_whitespace(); + auto const& function_token = tokens.next_token(); + if (!function_token.is_function("image-set"sv) && !function_token.is_function("-webkit-image-set"sv)) + return nullptr; + + auto transaction = tokens.begin_transaction(); + auto const& function = tokens.consume_a_token().function(); + TokenStream function_tokens { function.value }; + auto image_set_options_tokens = parse_a_comma_separated_list_of_component_values(function_tokens); + function_tokens.discard_whitespace(); + if (!function_tokens.is_empty()) + return nullptr; + + Vector options; + options.ensure_capacity(image_set_options_tokens.size()); + for (auto const& option_tokens_list : image_set_options_tokens) { + if (option_tokens_list.first_matching([](auto const& component_value) { return component_value.contains_attr_tainted_value(); }).has_value()) + return nullptr; + + TokenStream option_tokens { option_tokens_list }; + option_tokens.discard_whitespace(); + + RefPtr image; + if (option_tokens.next_token().is(Token::Type::String)) { + auto url = URL { option_tokens.consume_a_token().token().string().to_string() }; + image = ImageStyleValue::create(url); + } else { + image = parse_image_value(option_tokens, AllowImageSet::No); + } + if (!image) + return nullptr; + + RefPtr resolution; + Optional type; + while (true) { + option_tokens.discard_whitespace(); + if (option_tokens.is_empty()) + break; + + if (!resolution) { + if (auto parsed_resolution = parse_resolution_value(option_tokens, infinite_range)) { + resolution = parsed_resolution; + continue; + } + } + + if (!type.has_value() && option_tokens.next_token().is_function("type"sv)) { + auto const& type_function = option_tokens.consume_a_token().function(); + TokenStream type_tokens { type_function.value }; + type_tokens.discard_whitespace(); + if (!type_tokens.next_token().is(Token::Type::String)) + return nullptr; + type = type_tokens.consume_a_token().token().string().to_string(); + type_tokens.discard_whitespace(); + if (!type_tokens.is_empty()) + return nullptr; + continue; + } + + return nullptr; + } + + if (!resolution) + resolution = ResolutionStyleValue::create(Resolution { 1, ResolutionUnit::X }); + + options.unchecked_append({ + .image = image.release_nonnull(), + .resolution = resolution.release_nonnull(), + .type = move(type), + }); + } + + if (options.is_empty()) + return nullptr; + + transaction.commit(); + return ImageSetStyleValue::create(move(options)); +} + +RefPtr Parser::parse_image_value(TokenStream& tokens, AllowImageSet allow_image_set) { tokens.mark(); auto url = parse_url_function(tokens); @@ -2690,6 +2777,11 @@ RefPtr Parser::parse_image_value(TokenStream +#include namespace Web::CSS { @@ -16,6 +17,11 @@ GC::Ref AbstractImageStyleValue::reify(JS::Realm& realm, FlyStrin return CSSImageValue::create(realm, *this); } +void AbstractImageStyleValue::load_any_resources(Layout::NodeWithStyle const& layout_node) +{ + load_any_resources(const_cast(layout_node.document())); +} + ColorStopListElement ColorStopListElement::absolutized(ComputationContext const& context) const { auto absolutize_if_nonnull = [&context](RefPtr const& input) -> RefPtr { diff --git a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h index 99b714cf40f..0771ac4bca4 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.h @@ -32,6 +32,7 @@ public: } virtual void load_any_resources(DOM::Document&) { } + virtual void load_any_resources(Layout::NodeWithStyle const&); virtual void resolve_for_size(Layout::NodeWithStyle const&, CSSPixelSize) const { } virtual bool is_paintable() const = 0; diff --git a/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp new file mode 100644 index 00000000000..edc2ba790cd --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +ValueComparingNonnullRefPtr ImageSetStyleValue::create(Vector