LibWeb: Bring parse_as_sizes_attribute() up to date with the spec

The following spec algorithms had changed since we implemented them:
- "parse a sizes attribute"
- "update the source set"
- "create a source set"

This commit brings them up to date, as well as adding some additional
logging when parsing the sizes attribute fails in some way.
This commit is contained in:
Sam Atkins 2024-09-11 16:41:18 +01:00 committed by Andreas Kling
parent 99718653e2
commit 2a0f6fd23e
Notes: github-actions[bot] 2024-09-12 05:40:06 +00:00
5 changed files with 80 additions and 33 deletions

View file

@ -39,6 +39,7 @@
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/Parser/Rule.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/Sizing.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
@ -86,6 +87,7 @@
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Infra/Strings.h>
@ -7930,29 +7932,43 @@ private:
};
// https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
LengthOrCalculated Parser::Parser::parse_as_sizes_attribute()
LengthOrCalculated Parser::Parser::parse_as_sizes_attribute([[maybe_unused]] DOM::Element const& element, HTML::HTMLImageElement const* img)
{
// When asked to parse a sizes attribute from an element element, with an img element or null img:
// 1. Let unparsed sizes list be the result of parsing a comma-separated list of component values
// from the value of element's sizes attribute (or the empty string, if the attribute is absent).
// NOTE: The sizes attribute has already been tokenized into m_token_stream by this point.
auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);
// 2. Let size be null.
Optional<LengthOrCalculated> size;
auto size_is_auto = [&size]() {
return !size->is_calculated() && size->value().is_auto();
};
auto remove_all_consecutive_whitespace_tokens_from_the_end_of = [](auto& tokens) {
while (!tokens.is_empty() && tokens.last().is_token() && tokens.last().token().is(Token::Type::Whitespace))
tokens.take_last();
};
// 3. For each unparsed size in unparsed sizes list:
for (auto& unparsed_size : unparsed_sizes_list) {
for (auto i = 0u; i < unparsed_sizes_list.size(); i++) {
auto& unparsed_size = unparsed_sizes_list[i];
// 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
// If unparsed size is now empty, that is a parse error; continue.
while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
unparsed_size.take_last();
remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
if (unparsed_size.is_empty()) {
log_parse_error();
dbgln_if(CSS_PARSER_DEBUG, "-> Failed in step 3.1; all whitespace");
continue;
}
// 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
// let size be its value and remove the component value from unparsed size.
// FIXME: Any CSS function other than the math functions is invalid.
// then set size to its value and remove the component value from unparsed size.
// Any CSS function other than the math functions is invalid.
// Otherwise, there is a parse error; continue.
auto last_value_stream = TokenStream<ComponentValue>::of_single_token(unparsed_size.last());
if (auto source_size_value = parse_source_size_value(last_value_stream); source_size_value.has_value()) {
@ -7960,33 +7976,55 @@ LengthOrCalculated Parser::Parser::parse_as_sizes_attribute()
unparsed_size.take_last();
} else {
log_parse_error();
dbgln_if(CSS_PARSER_DEBUG, "-> Failed in step 3.2; couldn't parse {} as a <source-size-value>", unparsed_size.last().to_debug_string());
continue;
}
// 3. Remove all consecutive <whitespace-token>s from the end of unparsed size.
while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
unparsed_size.take_last();
// 3. If size is auto, and img is not null, and img is being rendered, and img allows auto-sizes,
// then set size to the concrete object size width of img, in CSS pixels.
// FIXME: "img is being rendered" - we just see if it has a bitmap for now
if (size_is_auto() && img && img->immutable_bitmap() && img->allows_auto_sizes()) {
// FIXME: The spec doesn't seem to tell us how to determine the concrete size of an <img>, so use the default sizing algorithm.
// Should this use some of the methods from FormattingContext?
auto concrete_size = run_default_sizing_algorithm(
img->width(), img->height(),
img->natural_width(), img->natural_height(), img->intrinsic_aspect_ratio(),
// NOTE: https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
CSSPixelSize { 300, 150 });
size = Length::make_px(concrete_size.width());
}
// If unparsed size is now empty, then return size.
if (unparsed_size.is_empty())
return size.value();
// 4. Remove all consecutive <whitespace-token>s from the end of unparsed size.
// If unparsed size is now empty:
remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
if (unparsed_size.is_empty()) {
// 1. If this was not the last item in unparsed sizes list, that is a parse error.
if (i != unparsed_sizes_list.size() - 1) {
log_parse_error();
dbgln_if(CSS_PARSER_DEBUG, "-> Failed in step 3.4.1; is unparsed size #{}, count {}", i, unparsed_sizes_list.size());
}
// FIXME: If this was not the keyword auto and it was not the last item in unparsed sizes list, that is a parse error.
// 2. If size is not auto, then return size. Otherwise, continue.
if (!size_is_auto())
return size.release_value();
continue;
}
// 4. Parse the remaining component values in unparsed size as a <media-condition>.
// 5. Parse the remaining component values in unparsed size as a <media-condition>.
// If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
TokenStream<ComponentValue> token_stream { unparsed_size };
auto media_condition = parse_media_condition(token_stream, MediaCondition::AllowOr::Yes);
auto context_window = m_context.window();
if (context_window && media_condition && media_condition->evaluate(*context_window) == MatchResult::True) {
return size.value();
}
// 5. If size is not auto, then return size.
if (size.value().is_calculated() || !size.value().value().is_auto())
auto const* context_window = m_context.window();
if (!media_condition || (context_window && media_condition->evaluate(*context_window) == MatchResult::False)) {
continue;
}
// 5. If size is not auto, then return size. Otherwise, continue.
if (!size_is_auto())
return size.value();
}
// 4. Return 100vw.
return Length(100, Length::Type::Vw);
}

View file

@ -75,7 +75,7 @@ public:
static NonnullRefPtr<CSSStyleValue> resolve_unresolved_style_value(ParsingContext const&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, PropertyID, UnresolvedStyleValue const&);
[[nodiscard]] LengthOrCalculated parse_as_sizes_attribute();
[[nodiscard]] LengthOrCalculated parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img = nullptr);
private:
Parser(ParsingContext const&, Vector<Token>);

View file

@ -990,7 +990,12 @@ static void update_the_source_set(DOM::Element& element)
});
}
// 4. For each child in elements:
// 4. Let img be el if el is an img element, otherwise null.
HTMLImageElement* img = nullptr;
if (is<HTMLImageElement>(element))
img = static_cast<HTMLImageElement*>(&element);
// 5. For each child in elements:
for (auto child : elements) {
// 1. If child is el:
if (child == &element) {
@ -1039,11 +1044,13 @@ static void update_the_source_set(DOM::Element& element)
default_source = href_value.release_value();
}
// 10. Let el's source set be the result of creating a source set given default source, srcset, and sizes.
// 10. Let el's source set be the result of creating a source set given default source, srcset, sizes, and img.
if (is<HTMLImageElement>(element))
static_cast<HTMLImageElement&>(element).set_source_set(SourceSet::create(element, default_source, srcset, sizes));
static_cast<HTMLImageElement&>(element).set_source_set(SourceSet::create(element, default_source, srcset, sizes, img));
else if (is<HTMLLinkElement>(element))
TODO();
// 11. Return.
return;
}
// 2. If child is not a source element, then continue.
@ -1070,8 +1077,8 @@ static void update_the_source_set(DOM::Element& element)
}
}
// 7. Parse child's sizes attribute, and let source set's source size be the returned value.
source_set.m_source_size = parse_a_sizes_attribute(element.document(), child->get_attribute_value(HTML::AttributeNames::sizes));
// 7. Parse child's sizes attribute with img, and let source set's source size be the returned value.
source_set.m_source_size = parse_a_sizes_attribute(element, child->get_attribute_value(HTML::AttributeNames::sizes), img);
// 8. If child has a type attribute, and its value is an unknown or unsupported MIME type, continue to the next child.
if (child->has_attribute(HTML::AttributeNames::type)) {

View file

@ -339,15 +339,17 @@ descriptor_parser:
}
// https://html.spec.whatwg.org/multipage/images.html#parse-a-sizes-attribute
CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Document const& document, StringView sizes)
CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Element const& element, StringView sizes, HTML::HTMLImageElement const* img)
{
auto css_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext { document }, sizes);
return css_parser.parse_as_sizes_attribute();
auto css_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext { element.document() }, sizes);
return css_parser.parse_as_sizes_attribute(element, img);
}
// https://html.spec.whatwg.org/multipage/images.html#create-a-source-set
SourceSet SourceSet::create(DOM::Element const& element, String default_source, String srcset, String sizes)
SourceSet SourceSet::create(DOM::Element const& element, String const& default_source, String const& srcset, String const& sizes, HTML::HTMLImageElement const* img)
{
// When asked to create a source set given a string default source, a string srcset, a string sizes, and an element or null img:
// 1. Let source set be an empty source set.
SourceSet source_set;
@ -355,8 +357,8 @@ SourceSet SourceSet::create(DOM::Element const& element, String default_source,
if (!srcset.is_empty())
source_set = parse_a_srcset_attribute(srcset);
// 3. Let source size be the result of parsing sizes.
source_set.m_source_size = parse_a_sizes_attribute(element.document(), sizes);
// 3. Let source size be the result of parsing sizes with img.
source_set.m_source_size = parse_a_sizes_attribute(element, sizes, img);
// 4. If default source is not the empty string and source set does not contain an image source
// with a pixel density descriptor value of 1, and no image source with a width descriptor,

View file

@ -33,7 +33,7 @@ struct ImageSourceAndPixelDensity {
// https://html.spec.whatwg.org/multipage/images.html#source-set
struct SourceSet {
static SourceSet create(DOM::Element const&, String default_source, String srcset, String sizes);
static SourceSet create(DOM::Element const& element, String const& default_source, String const& srcset, String const& sizes, HTML::HTMLImageElement const* img = nullptr);
[[nodiscard]] bool is_empty() const;
@ -50,6 +50,6 @@ struct SourceSet {
};
SourceSet parse_a_srcset_attribute(StringView);
[[nodiscard]] CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Document const&, StringView);
[[nodiscard]] CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Element const& element, StringView sizes, HTML::HTMLImageElement const* img = nullptr);
}