LibWeb/CSS: Parse <url> as a new CSS::URL type

Our previous approach to `<url>` had a couple of issues:
- We'd complete the URL during parsing, when we should actually keep it
  as the original string until it's used.
- There's nowhere for us to store `<url-modifier>`s on a `URL::URL`.

So, `CSS::URL` is a solution to this. It holds the original URL string,
and later will also hold any modifiers. This commit parses all `<url>`s
as `CSS::URL`, but then converts it into a `URL::URL`, so no user code
is changed. These will be modified in subsequent commits.

For `@namespace`, we were never supposed to complete the URL at all, so
this makes that more correct already. However, in practice all
`@namespace`s are absolute URLs already, so this should have no
observable effects.
This commit is contained in:
Sam Atkins 2025-04-08 14:33:01 +01:00 committed by Tim Ledbetter
parent c82f4b46a2
commit 7216c6b050
Notes: github-actions[bot] 2025-04-09 17:47:57 +00:00
7 changed files with 101 additions and 27 deletions

View file

@ -15,7 +15,6 @@
#include <AK/Debug.h>
#include <AK/GenericLexer.h>
#include <AK/TemporaryChange.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
@ -2014,9 +2013,13 @@ RefPtr<AbstractImageStyleValue> Parser::parse_image_value(TokenStream<ComponentV
if (url.has_value()) {
// If the value is a 'url(..)' parse as image, but if it is just a reference 'url(#xx)', leave it alone,
// so we can parse as URL further on. These URLs are used as references inside SVG documents for masks.
if (!url.value().equals(m_url, ::URL::ExcludeFragment::Yes)) {
tokens.discard_a_mark();
return ImageStyleValue::create(url.value());
if (!url->url().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<CSSStyleValue> Parser::parse_easing_value(TokenStream<ComponentValue>& to
return nullptr;
}
Optional<::URL::URL> Parser::parse_url_function(TokenStream<ComponentValue>& tokens)
Optional<URL> Parser::parse_url_function(TokenStream<ComponentValue>& 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<ComponentValue>& 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<CSSStyleValue> Parser::parse_url_value(TokenStream<ComponentValue>& 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<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// <url> [ format(<font-format>)]? [ tech( <font-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<FlyString> format;
@ -3719,7 +3722,7 @@ RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<Compone
// FIXME: [ tech( <font-tech>#)]?
transaction.commit();
return FontSourceStyleValue::create(url.release_value(), move(format));
return FontSourceStyleValue::create(completed_url.release_value(), move(format));
}
NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)