diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 9f8b303f16c..112150edd8e 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -195,6 +195,7 @@ set(SOURCES CSS/Time.cpp CSS/Transformation.cpp CSS/TransitionEvent.cpp + CSS/URL.cpp CSS/VisualViewport.cpp Cookie/Cookie.cpp Cookie/ParsedCookie.cpp diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index ad96c2afed9..0931aa232f9 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace Web::CSS::Parser { @@ -276,7 +277,7 @@ private: Optional parse_repeat(Vector const&); Optional parse_track_sizing_function(ComponentValue const&); - Optional<::URL::URL> parse_url_function(TokenStream&); + Optional parse_url_function(TokenStream&); RefPtr parse_url_value(TokenStream&); Optional parse_shape_radius(TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index 6896937d359..4f511e1ade5 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -153,15 +153,22 @@ GC::Ptr Parser::convert_to_import_rule(AtRule const& rule) TokenStream tokens { rule.prelude }; tokens.discard_whitespace(); - Optional<::URL::URL> url = parse_url_function(tokens); + Optional url = parse_url_function(tokens); if (!url.has_value() && tokens.next_token().is(Token::Type::String)) - url = complete_url(tokens.consume_a_token().token().string()); + url = URL { tokens.consume_a_token().token().string().to_string() }; if (!url.has_value()) { dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string()); return {}; } + // FIXME: Stop completing the URL here + auto resolved_url = complete_url(url->url()); + if (!resolved_url.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to complete `{}` as URL.", url->url()); + return {}; + } + tokens.discard_whitespace(); // FIXME: Implement layer support. RefPtr supports {}; @@ -191,7 +198,7 @@ GC::Ptr Parser::convert_to_import_rule(AtRule const& rule) return {}; } - return CSSImportRule::create(url.value(), const_cast(*document()), supports, move(media_query_list)); + return CSSImportRule::create(resolved_url.release_value(), const_cast(*document()), supports, move(media_query_list)); } Optional Parser::parse_layer_name(TokenStream& tokens, AllowBlankLayerName allow_blank_layer_name) @@ -435,7 +442,10 @@ GC::Ptr Parser::convert_to_namespace_rule(AtRule const& rule) FlyString namespace_uri; if (auto url = parse_url_function(tokens); url.has_value()) { - namespace_uri = url.value().to_string(); + // "A URI string parsed from the URI syntax must be treated as a literal string: as with the STRING syntax, no + // URI-specific normalization is applied." + // https://drafts.csswg.org/css-namespaces/#syntax + namespace_uri = url->url(); } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) { namespace_uri = url_token.token().string(); } else { diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 80a1f19d86c..aaebc8b7a9c 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -2014,9 +2013,13 @@ RefPtr Parser::parse_image_value(TokenStreamurl().starts_with('#')) { + // FIXME: Stop completing the URL here + auto completed_url = complete_url(url->url()); + if (completed_url.has_value()) { + tokens.discard_a_mark(); + return ImageStyleValue::create(completed_url.release_value()); + } } tokens.restore_a_mark(); return nullptr; @@ -2562,24 +2565,16 @@ RefPtr Parser::parse_easing_value(TokenStream& to return nullptr; } -Optional<::URL::URL> Parser::parse_url_function(TokenStream& tokens) +Optional Parser::parse_url_function(TokenStream& tokens) { auto transaction = tokens.begin_transaction(); - auto& component_value = tokens.consume_a_token(); - - auto convert_string_to_url = [&](StringView url_string) -> Optional<::URL::URL> { - auto url = complete_url(url_string); - if (url.has_value()) { - transaction.commit(); - return url; - } - return {}; - }; + auto const& component_value = tokens.consume_a_token(); if (component_value.is(Token::Type::Url)) { - auto url_string = component_value.token().url(); - return convert_string_to_url(url_string); + transaction.commit(); + return URL { component_value.token().url().to_string() }; } + if (component_value.is_function("url"sv)) { auto const& function_values = component_value.function().value; // FIXME: Handle url-modifiers. https://www.w3.org/TR/css-values-4/#url-modifiers @@ -2588,8 +2583,8 @@ Optional<::URL::URL> Parser::parse_url_function(TokenStream& tok if (value.is(Token::Type::Whitespace)) continue; if (value.is(Token::Type::String)) { - auto url_string = value.token().string(); - return convert_string_to_url(url_string); + transaction.commit(); + return URL { value.token().string().to_string() }; } break; } @@ -2603,7 +2598,11 @@ RefPtr Parser::parse_url_value(TokenStream& token auto url = parse_url_function(tokens); if (!url.has_value()) return nullptr; - return URLStyleValue::create(*url); + // FIXME: Stop completing the URL here + auto completed_url = complete_url(url->url()); + if (!completed_url.has_value()) + return nullptr; + return URLStyleValue::create(completed_url.release_value()); } // https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius @@ -3681,7 +3680,11 @@ RefPtr Parser::parse_font_source_value(TokenStream [ format()]? [ tech( #)]? auto url = parse_url_function(tokens); - if (!url.has_value() || !url->is_valid()) + if (!url.has_value()) + return nullptr; + // FIXME: Stop completing the URL here + auto completed_url = complete_url(url->url()); + if (!completed_url.has_value()) return nullptr; Optional format; @@ -3719,7 +3722,7 @@ RefPtr Parser::parse_font_source_value(TokenStream#)]? transaction.commit(); - return FontSourceStyleValue::create(url.release_value(), move(format)); + return FontSourceStyleValue::create(completed_url.release_value(), move(format)); } NonnullRefPtr Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved) diff --git a/Libraries/LibWeb/CSS/URL.cpp b/Libraries/LibWeb/CSS/URL.cpp new file mode 100644 index 00000000000..a634b6128bc --- /dev/null +++ b/Libraries/LibWeb/CSS/URL.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::CSS { + +URL::URL(String url) + : m_url(move(url)) +{ +} + +// https://drafts.csswg.org/cssom-1/#serialize-a-url +String URL::to_string() const +{ + // To serialize a URL means to create a string represented by "url(", followed by the serialization of the URL as a string, followed by ")". + StringBuilder builder; + builder.append("url("sv); + serialize_a_string(builder, m_url); + builder.append(')'); + + return builder.to_string_without_validation(); +} + +bool URL::operator==(URL const&) const = default; + +} diff --git a/Libraries/LibWeb/CSS/URL.h b/Libraries/LibWeb/CSS/URL.h new file mode 100644 index 00000000000..9380ad2e226 --- /dev/null +++ b/Libraries/LibWeb/CSS/URL.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/css-values-4/#urls +class URL { +public: + URL(String url); + + String const& url() const { return m_url; } + + String to_string() const; + bool operator==(URL const&) const; + +private: + String m_url; +}; + +} diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 557f007dafc..8a9eca06696 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -269,6 +269,7 @@ class TransformationStyleValue; class TransitionStyleValue; class UnicodeRangeStyleValue; class UnresolvedStyleValue; +class URL; class URLStyleValue; class VisualViewport;