mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 06:58:11 +00:00
LibWeb: Allow calc() values in image sizes
attribute
Note that we currently can't resolve calc() values without a layout node, so when normalizing an image's source set, we'll flush any pending layout updates and hope that gives us an up-to-date layout node. I've left a FIXME about implementing this in a more elegant and less layout-thrashy way, as that will require more architectural work.
This commit is contained in:
parent
9997f46593
commit
95097e47a7
7 changed files with 77 additions and 28 deletions
|
@ -23,6 +23,13 @@ Length LengthOrCalculated::resolve_calculated(NonnullRefPtr<CalculatedStyleValue
|
||||||
return calculated->resolve_length(layout_node).value();
|
return calculated->resolve_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<CalculatedStyleValue> const& calculated, Layout::Node const&) const
|
Percentage PercentageOrCalculated::resolve_calculated(NonnullRefPtr<CalculatedStyleValue> const& calculated, Layout::Node const&) const
|
||||||
{
|
{
|
||||||
return calculated->resolve_percentage().value();
|
return calculated->resolve_percentage().value();
|
||||||
|
|
|
@ -96,6 +96,7 @@ public:
|
||||||
using CalculatedOr<Length>::CalculatedOr;
|
using CalculatedOr<Length>::CalculatedOr;
|
||||||
|
|
||||||
Length resolve_calculated(NonnullRefPtr<CalculatedStyleValue> const&, Layout::Node const&) const override;
|
Length resolve_calculated(NonnullRefPtr<CalculatedStyleValue> const&, Layout::Node const&) const override;
|
||||||
|
[[nodiscard]] Length resolved(Length::ResolutionContext const&) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PercentageOrCalculated : public CalculatedOr<Percentage> {
|
class PercentageOrCalculated : public CalculatedOr<Percentage> {
|
||||||
|
|
|
@ -3460,6 +3460,23 @@ Optional<Parser::Dimension> Parser::parse_dimension(ComponentValue const& compon
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<LengthOrCalculated> 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<Length> Parser::parse_length(ComponentValue const& component_value)
|
Optional<Length> Parser::parse_length(ComponentValue const& component_value)
|
||||||
{
|
{
|
||||||
auto dimension = parse_dimension(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
|
// 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<Length> size;
|
// 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).
|
||||||
// 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.
|
|
||||||
auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);
|
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<LengthOrCalculated> size;
|
||||||
|
|
||||||
|
// 3. For each unparsed size in unparsed sizes list:
|
||||||
for (auto& unparsed_size : unparsed_sizes_list) {
|
for (auto& unparsed_size : unparsed_sizes_list) {
|
||||||
// 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
|
// 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.
|
// 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))
|
while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
|
||||||
unparsed_size.take_last();
|
unparsed_size.take_last();
|
||||||
if (unparsed_size.is_empty())
|
if (unparsed_size.is_empty()) {
|
||||||
|
log_parse_error();
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
|
// 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.
|
// let size be its value and remove the component value from unparsed size.
|
||||||
// FIXME: Any CSS function other than the math functions is invalid.
|
// FIXME: Any CSS function other than the math functions is invalid.
|
||||||
// Otherwise, there is a parse error; continue.
|
// Otherwise, there is a parse error; continue.
|
||||||
auto length = parse_length(unparsed_size.last());
|
if (auto source_size_value = parse_source_size_value(unparsed_size.last()); source_size_value.has_value()) {
|
||||||
if (length.has_value() && length.value().raw_value() >= 0) {
|
size = source_size_value.value();
|
||||||
size = length.value();
|
|
||||||
unparsed_size.take_last();
|
unparsed_size.take_last();
|
||||||
} else {
|
} else {
|
||||||
|
log_parse_error();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Remove all consecutive <whitespace-token>s from the end of unparsed size.
|
// 3. Remove all consecutive <whitespace-token>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))
|
while (!unparsed_size.is_empty() && unparsed_size.last().is_token() && unparsed_size.last().token().is(Token::Type::Whitespace))
|
||||||
unparsed_size.take_last();
|
unparsed_size.take_last();
|
||||||
|
|
||||||
|
// If unparsed size is now empty, then return size.
|
||||||
if (unparsed_size.is_empty())
|
if (unparsed_size.is_empty())
|
||||||
return size.value();
|
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 <media-condition>.
|
// 4. 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.
|
// 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 };
|
TokenStream<ComponentValue> token_stream { unparsed_size };
|
||||||
|
@ -8199,6 +8220,10 @@ Length Parser::Parser::parse_as_sizes_attribute()
|
||||||
} else {
|
} else {
|
||||||
continue;
|
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);
|
return Length(100, Length::Type::Vw);
|
||||||
|
|
|
@ -91,7 +91,7 @@ public:
|
||||||
static ErrorOr<RefPtr<StyleValue>> parse_css_value(Badge<StyleComputer>, ParsingContext const&, PropertyID, Vector<ComponentValue> const&);
|
static ErrorOr<RefPtr<StyleValue>> parse_css_value(Badge<StyleComputer>, ParsingContext const&, PropertyID, Vector<ComponentValue> const&);
|
||||||
static ErrorOr<RefPtr<CalculatedStyleValue>> parse_calculated_value(Badge<StyleComputer>, ParsingContext const&, Vector<ComponentValue> const&);
|
static ErrorOr<RefPtr<CalculatedStyleValue>> parse_calculated_value(Badge<StyleComputer>, ParsingContext const&, Vector<ComponentValue> const&);
|
||||||
|
|
||||||
CSS::Length parse_as_sizes_attribute();
|
[[nodiscard]] LengthOrCalculated parse_as_sizes_attribute();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Parser(ParsingContext const&, Vector<Token>);
|
Parser(ParsingContext const&, Vector<Token>);
|
||||||
|
@ -256,6 +256,7 @@ private:
|
||||||
Optional<Color> parse_rgb_or_hsl_color(StringView function_name, Vector<ComponentValue> const&);
|
Optional<Color> parse_rgb_or_hsl_color(StringView function_name, Vector<ComponentValue> const&);
|
||||||
Optional<Color> parse_color(ComponentValue const&);
|
Optional<Color> parse_color(ComponentValue const&);
|
||||||
Optional<Length> parse_length(ComponentValue const&);
|
Optional<Length> parse_length(ComponentValue const&);
|
||||||
|
[[nodiscard]] Optional<LengthOrCalculated> parse_source_size_value(ComponentValue const&);
|
||||||
Optional<Ratio> parse_ratio(TokenStream<ComponentValue>&);
|
Optional<Ratio> parse_ratio(TokenStream<ComponentValue>&);
|
||||||
Optional<UnicodeRange> parse_unicode_range(TokenStream<ComponentValue>&);
|
Optional<UnicodeRange> parse_unicode_range(TokenStream<ComponentValue>&);
|
||||||
Optional<UnicodeRange> parse_unicode_range(StringView);
|
Optional<UnicodeRange> parse_unicode_range(StringView);
|
||||||
|
|
|
@ -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.
|
// 10. Let el's source set be the result of creating a source set given default source, srcset, and sizes.
|
||||||
if (is<HTMLImageElement>(element))
|
if (is<HTMLImageElement>(element))
|
||||||
static_cast<HTMLImageElement&>(element).set_source_set(SourceSet::create(element.document(), default_source, srcset, sizes));
|
static_cast<HTMLImageElement&>(element).set_source_set(SourceSet::create(element, default_source, srcset, sizes));
|
||||||
else if (is<HTMLLinkElement>(element))
|
else if (is<HTMLLinkElement>(element))
|
||||||
TODO();
|
TODO();
|
||||||
return;
|
return;
|
||||||
|
@ -750,7 +750,7 @@ static void update_the_source_set(DOM::Element& element)
|
||||||
// Otherwise, set el's dimension attribute source to el.
|
// Otherwise, set el's dimension attribute source to el.
|
||||||
|
|
||||||
// 10. Normalize the source densities of source set.
|
// 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.
|
// 11. Let el's source set be source set.
|
||||||
if (is<HTMLImageElement>(element))
|
if (is<HTMLImageElement>(element))
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||||
#include <LibWeb/CSS/Parser/Parser.h>
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/HTML/SourceSet.h>
|
#include <LibWeb/HTML/SourceSet.h>
|
||||||
#include <LibWeb/Infra/CharacterTypes.h>
|
#include <LibWeb/Infra/CharacterTypes.h>
|
||||||
|
#include <LibWeb/Layout/Node.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
@ -337,14 +339,14 @@ descriptor_parser:
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/images.html#parse-a-sizes-attribute
|
// 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();
|
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();
|
return css_parser.parse_as_sizes_attribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/images.html#create-a-source-set
|
// 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.
|
// 1. Let source set be an empty source set.
|
||||||
SourceSet 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);
|
source_set = parse_a_srcset_attribute(srcset);
|
||||||
|
|
||||||
// 3. Let source size be the result of parsing sizes.
|
// 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
|
// 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,
|
// 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.
|
// 5. Normalize the source densities of source set.
|
||||||
source_set.normalize_source_densities();
|
source_set.normalize_source_densities(element);
|
||||||
|
|
||||||
// 6. Return source set.
|
// 6. Return source set.
|
||||||
return source_set;
|
return source_set;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/images.html#normalise-the-source-densities
|
// 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.
|
// 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<DOM::Document&>(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:
|
// 2. For each image source in source set:
|
||||||
for (auto& image_source : m_sources) {
|
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()
|
.value = (width_descriptor.value / source_size.absolute_length_to_px()).to_double()
|
||||||
};
|
};
|
||||||
} else {
|
} 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 {
|
image_source.descriptor = ImageSource::PixelDensityDescriptorValue {
|
||||||
.value = 1,
|
.value = 1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
#include <AK/URL.h>
|
#include <AK/URL.h>
|
||||||
#include <AK/Variant.h>
|
#include <AK/Variant.h>
|
||||||
#include <LibWeb/CSS/Length.h>
|
#include <LibWeb/CSS/CalculatedOr.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ struct ImageSourceAndPixelDensity {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/images.html#source-set
|
// https://html.spec.whatwg.org/multipage/images.html#source-set
|
||||||
struct SourceSet {
|
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;
|
[[nodiscard]] bool is_empty() const;
|
||||||
|
|
||||||
|
@ -41,15 +41,15 @@ struct SourceSet {
|
||||||
[[nodiscard]] ImageSourceAndPixelDensity select_an_image_source();
|
[[nodiscard]] ImageSourceAndPixelDensity select_an_image_source();
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/images.html#normalise-the-source-densities
|
// https://html.spec.whatwg.org/multipage/images.html#normalise-the-source-densities
|
||||||
void normalize_source_densities();
|
void normalize_source_densities(DOM::Element const&);
|
||||||
|
|
||||||
SourceSet();
|
SourceSet();
|
||||||
|
|
||||||
Vector<ImageSource> m_sources;
|
Vector<ImageSource> m_sources;
|
||||||
CSS::Length m_source_size;
|
CSS::LengthOrCalculated m_source_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
SourceSet parse_a_srcset_attribute(StringView);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue