diff --git a/Userland/Libraries/LibWeb/CSS/CalculatedOr.cpp b/Userland/Libraries/LibWeb/CSS/CalculatedOr.cpp index ed01f47a681..0b73f108883 100644 --- a/Userland/Libraries/LibWeb/CSS/CalculatedOr.cpp +++ b/Userland/Libraries/LibWeb/CSS/CalculatedOr.cpp @@ -23,6 +23,13 @@ Length LengthOrCalculated::resolve_calculated(NonnullRefPtrresolve_length(layout_node).value(); } +Length LengthOrCalculated::resolved(Length::ResolutionContext const& context) const +{ + if (is_calculated()) + return calculated()->resolve_length(context).value(); + return value(); +} + Percentage PercentageOrCalculated::resolve_calculated(NonnullRefPtr const& calculated, Layout::Node const&) const { return calculated->resolve_percentage().value(); diff --git a/Userland/Libraries/LibWeb/CSS/CalculatedOr.h b/Userland/Libraries/LibWeb/CSS/CalculatedOr.h index 28d6aeb834c..75d8cfe8e05 100644 --- a/Userland/Libraries/LibWeb/CSS/CalculatedOr.h +++ b/Userland/Libraries/LibWeb/CSS/CalculatedOr.h @@ -96,6 +96,7 @@ public: using CalculatedOr::CalculatedOr; Length resolve_calculated(NonnullRefPtr const&, Layout::Node const&) const override; + [[nodiscard]] Length resolved(Length::ResolutionContext const&) const; }; class PercentageOrCalculated : public CalculatedOr { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index e141162aa81..1707b3a986f 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3460,6 +3460,23 @@ Optional Parser::parse_dimension(ComponentValue const& compon return {}; } +Optional Parser::parse_source_size_value(ComponentValue const& component_value) +{ + if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_ascii_case("auto"sv)) { + return LengthOrCalculated { Length::make_auto() }; + } + + if (auto dynamic_value = parse_dynamic_value(component_value); !dynamic_value.is_error() && dynamic_value.value()) { + return LengthOrCalculated { dynamic_value.value()->as_calculated().as_calculated() }; + } + + if (auto length = parse_length(component_value); length.has_value()) { + return LengthOrCalculated { length.release_value() }; + } + + return {}; +} + Optional Parser::parse_length(ComponentValue const& component_value) { auto dimension = parse_dimension(component_value); @@ -8152,44 +8169,48 @@ private: }; // https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute -Length Parser::Parser::parse_as_sizes_attribute() +LengthOrCalculated Parser::Parser::parse_as_sizes_attribute() { - Optional size; - - // When asked to parse a sizes attribute from an element, - // parse a comma-separated list of component values from the value of the element's sizes attribute - // (or the empty string, if the attribute is absent), and let unparsed sizes list be the result. + // 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). auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream); - // For each unparsed size in unparsed sizes list: + // 2. Let size be null. + Optional size; + + // 3. For each unparsed size in unparsed sizes list: for (auto& unparsed_size : unparsed_sizes_list) { // 1. Remove all consecutive 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(); - if (unparsed_size.is_empty()) + if (unparsed_size.is_empty()) { + log_parse_error(); continue; + } // 2. If the last component value in unparsed size is a valid non-negative , // let size be its value and remove the component value from unparsed size. // FIXME: Any CSS function other than the math functions is invalid. // Otherwise, there is a parse error; continue. - auto length = parse_length(unparsed_size.last()); - if (length.has_value() && length.value().raw_value() >= 0) { - size = length.value(); + if (auto source_size_value = parse_source_size_value(unparsed_size.last()); source_size_value.has_value()) { + size = source_size_value.value(); unparsed_size.take_last(); } else { + log_parse_error(); continue; } // 3. Remove all consecutive s from the end of unparsed size. - // If unparsed size is now empty, return size and exit this algorithm. - // If this was not the last item in unparsed sizes list, that is a parse error. while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace)) unparsed_size.take_last(); + + // If unparsed size is now empty, then return size. if (unparsed_size.is_empty()) return size.value(); + // FIXME: If this was not the keyword auto and it was not the last item in unparsed sizes list, that is a parse error. + // 4. Parse the remaining component values in unparsed size as a . // If it does not parse correctly, or it does parse correctly but the evaluates to false, continue. TokenStream token_stream { unparsed_size }; @@ -8199,6 +8220,10 @@ Length Parser::Parser::parse_as_sizes_attribute() } else { continue; } + + // 5. If size is not auto, then return size. + if (size.value().is_calculated() || !size.value().value().is_auto()) + return size.value(); } return Length(100, Length::Type::Vw); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 75c17a6749b..ce3b74c8d0a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -91,7 +91,7 @@ public: static ErrorOr> parse_css_value(Badge, ParsingContext const&, PropertyID, Vector const&); static ErrorOr> parse_calculated_value(Badge, ParsingContext const&, Vector const&); - CSS::Length parse_as_sizes_attribute(); + [[nodiscard]] LengthOrCalculated parse_as_sizes_attribute(); private: Parser(ParsingContext const&, Vector); @@ -256,6 +256,7 @@ private: Optional parse_rgb_or_hsl_color(StringView function_name, Vector const&); Optional parse_color(ComponentValue const&); Optional parse_length(ComponentValue const&); + [[nodiscard]] Optional parse_source_size_value(ComponentValue const&); Optional parse_ratio(TokenStream&); Optional parse_unicode_range(TokenStream&); Optional parse_unicode_range(StringView); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp index b6f70a1c007..30c07eb4cc2 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -710,7 +710,7 @@ static void update_the_source_set(DOM::Element& element) // 10. Let el's source set be the result of creating a source set given default source, srcset, and sizes. if (is(element)) - static_cast(element).set_source_set(SourceSet::create(element.document(), default_source, srcset, sizes)); + static_cast(element).set_source_set(SourceSet::create(element, default_source, srcset, sizes)); else if (is(element)) TODO(); return; @@ -750,7 +750,7 @@ static void update_the_source_set(DOM::Element& element) // Otherwise, set el's dimension attribute source to el. // 10. Normalize the source densities of source set. - source_set.normalize_source_densities(); + source_set.normalize_source_densities(element); // 11. Let el's source set be source set. if (is(element)) diff --git a/Userland/Libraries/LibWeb/HTML/SourceSet.cpp b/Userland/Libraries/LibWeb/HTML/SourceSet.cpp index 0be8b55f1e3..5ccefead56b 100644 --- a/Userland/Libraries/LibWeb/HTML/SourceSet.cpp +++ b/Userland/Libraries/LibWeb/HTML/SourceSet.cpp @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include namespace Web::HTML { @@ -337,14 +339,14 @@ descriptor_parser: } // https://html.spec.whatwg.org/multipage/images.html#parse-a-sizes-attribute -CSS::Length parse_a_sizes_attribute(DOM::Document const& document, StringView sizes) +CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Document const& document, StringView sizes) { auto css_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext { document }, sizes).release_value_but_fixme_should_propagate_errors(); return css_parser.parse_as_sizes_attribute(); } // https://html.spec.whatwg.org/multipage/images.html#create-a-source-set -SourceSet SourceSet::create(DOM::Document const& document, String default_source, String srcset, String sizes) +SourceSet SourceSet::create(DOM::Element const& element, String default_source, String srcset, String sizes) { // 1. Let source set be an empty source set. SourceSet source_set; @@ -354,7 +356,7 @@ SourceSet SourceSet::create(DOM::Document const& document, String default_source 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(document, sizes); + source_set.m_source_size = parse_a_sizes_attribute(element.document(), sizes); // 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, @@ -375,17 +377,30 @@ SourceSet SourceSet::create(DOM::Document const& document, String default_source } // 5. Normalize the source densities of source set. - source_set.normalize_source_densities(); + source_set.normalize_source_densities(element); // 6. Return source set. return source_set; } // https://html.spec.whatwg.org/multipage/images.html#normalise-the-source-densities -void SourceSet::normalize_source_densities() +void SourceSet::normalize_source_densities(DOM::Element const& element) { // 1. Let source size be source set's source size. - auto source_size = m_source_size; + auto source_size = [&] { + if (!m_source_size.is_calculated()) + return m_source_size.value(); + + // HACK: Flush any pending layouts here so we get an up-to-date length resolution context. + // FIXME: We should have a way to build a LengthResolutionContext for any DOM node without going through the layout tree. + const_cast(element.document()).update_layout(); + if (element.layout_node()) { + auto context = CSS::Length::ResolutionContext::for_layout_node(*element.layout_node()); + return m_source_size.resolved(context); + } + // FIXME: This is wrong, but we don't have a better way to resolve lengths without a layout node yet. + return CSS::Length::make_auto(); + }(); // 2. For each image source in source set: for (auto& image_source : m_sources) { @@ -403,7 +418,7 @@ void SourceSet::normalize_source_densities() .value = (width_descriptor.value / source_size.absolute_length_to_px()).to_double() }; } else { - dbgln("FIXME: Handle relative sizes: {}", source_size); + dbgln("FIXME: Image element has unresolved relative length '{}' in sizes attribute", source_size); image_source.descriptor = ImageSource::PixelDensityDescriptorValue { .value = 1, }; diff --git a/Userland/Libraries/LibWeb/HTML/SourceSet.h b/Userland/Libraries/LibWeb/HTML/SourceSet.h index 663b451d9f6..16a75f6a322 100644 --- a/Userland/Libraries/LibWeb/HTML/SourceSet.h +++ b/Userland/Libraries/LibWeb/HTML/SourceSet.h @@ -8,7 +8,7 @@ #include #include -#include +#include namespace Web::HTML { @@ -33,7 +33,7 @@ struct ImageSourceAndPixelDensity { // https://html.spec.whatwg.org/multipage/images.html#source-set struct SourceSet { - static SourceSet create(DOM::Document const&, String default_source, String srcset, String sizes); + static SourceSet create(DOM::Element const&, String default_source, String srcset, String sizes); [[nodiscard]] bool is_empty() const; @@ -41,15 +41,15 @@ struct SourceSet { [[nodiscard]] ImageSourceAndPixelDensity select_an_image_source(); // https://html.spec.whatwg.org/multipage/images.html#normalise-the-source-densities - void normalize_source_densities(); + void normalize_source_densities(DOM::Element const&); SourceSet(); Vector m_sources; - CSS::Length m_source_size; + CSS::LengthOrCalculated m_source_size; }; SourceSet parse_a_srcset_attribute(StringView); -CSS::Length parse_a_sizes_attribute(DOM::Document const&, StringView); +[[nodiscard]] CSS::LengthOrCalculated parse_a_sizes_attribute(DOM::Document const&, StringView); }