LibWeb: Avoid many style invalidations on DOM attribute mutation

Many times, attribute mutation doesn't necessitate a full style
invalidation on the element. However, the conditions are pretty
elaborate, so this first version has a lot of false positives.

We only need to invalidate style when any of these things apply:

1. The change may affect the match state of a selector somewhere.
2. The change may affect presentational hints applied to the element.

For (1) in this first version, we have a fixed list of attribute names
that may affect selectors. We also collect all names referenced by
attribute selectors anywhere in the document.

For (2), we add a new Element::is_presentational_hint() virtual that
tells us whether a given attribute name is a presentational hint.

This drastically reduces style work on many websites. As an example,
https://cnn.com/ is once again browseable.
This commit is contained in:
Andreas Kling 2024-12-23 17:51:10 +01:00 committed by Andreas Kling
commit b981e6f7bc
Notes: github-actions[bot] 2024-12-24 16:18:00 +00:00
56 changed files with 377 additions and 37 deletions

View file

@ -2047,8 +2047,14 @@ void StyleComputer::compute_font(ComputedProperties& style, DOM::Element const*
RefPtr<Gfx::Font const> const found_font = font_list->first(); RefPtr<Gfx::Font const> const found_font = font_list->first();
style.set_property(CSS::PropertyID::FontSize, LengthStyleValue::create(CSS::Length::make_px(CSSPixels::nearest_value_for(found_font->pixel_size())))); style.set_property(
style.set_property(CSS::PropertyID::FontWeight, NumberStyleValue::create(font_weight.to_font_weight())); CSS::PropertyID::FontSize,
LengthStyleValue::create(CSS::Length::make_px(CSSPixels::nearest_value_for(found_font->pixel_size()))),
style.is_property_inherited(CSS::PropertyID::FontSize) ? ComputedProperties::Inherited::Yes : ComputedProperties::Inherited::No);
style.set_property(
CSS::PropertyID::FontWeight,
NumberStyleValue::create(font_weight.to_font_weight()),
style.is_property_inherited(CSS::PropertyID::FontWeight) ? ComputedProperties::Inherited::Yes : ComputedProperties::Inherited::No);
style.set_computed_font_list(*font_list); style.set_computed_font_list(*font_list);

View file

@ -177,6 +177,7 @@ public:
[[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::Element&, Optional<Selector::PseudoElement::Type>, CascadedProperties&) const; [[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::Element&, Optional<Selector::PseudoElement::Type>, CascadedProperties&) const;
void absolutize_values(ComputedProperties&) const; void absolutize_values(ComputedProperties&) const;
void compute_font(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
private: private:
enum class ComputeStyleMode { enum class ComputeStyleMode {
@ -193,7 +194,6 @@ private:
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive); static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
void compute_font(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
void compute_math_depth(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const; void compute_math_depth(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
void compute_defaulted_values(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const; void compute_defaulted_values(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
void start_needed_transitions(ComputedProperties const& old_style, ComputedProperties& new_style, DOM::Element&, Optional<Selector::PseudoElement::Type>) const; void start_needed_transitions(ComputedProperties const& old_style, ComputedProperties& new_style, DOM::Element&, Optional<Selector::PseudoElement::Type>) const;

View file

@ -573,6 +573,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style()
invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value); invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value);
} }
document().style_computer().compute_font(*computed_properties, this, {});
document().style_computer().absolutize_values(*computed_properties); document().style_computer().absolutize_values(*computed_properties);
layout_node()->apply_style(*computed_properties); layout_node()->apply_style(*computed_properties);
@ -1899,26 +1900,53 @@ ErrorOr<void> Element::scroll_into_view(Optional<Variant<bool, ScrollIntoViewOpt
// FIXME: 8. Optionally perform some other action that brings the element to the users attention. // FIXME: 8. Optionally perform some other action that brings the element to the users attention.
} }
static bool attribute_name_may_affect_selectors(Element const& element, FlyString const& attribute_name)
{
// FIXME: We could make these cases more narrow by making the conditions more elaborate.
if (attribute_name == HTML::AttributeNames::id
|| attribute_name == HTML::AttributeNames::class_
|| attribute_name == HTML::AttributeNames::dir
|| attribute_name == HTML::AttributeNames::lang
|| attribute_name == HTML::AttributeNames::checked
|| attribute_name == HTML::AttributeNames::disabled
|| attribute_name == HTML::AttributeNames::readonly
|| attribute_name == HTML::AttributeNames::switch_
|| attribute_name == HTML::AttributeNames::href
|| attribute_name == HTML::AttributeNames::open
|| attribute_name == HTML::AttributeNames::placeholder) {
return true;
}
return element.document().style_computer().has_attribute_selector(attribute_name);
}
void Element::invalidate_style_after_attribute_change(FlyString const& attribute_name) void Element::invalidate_style_after_attribute_change(FlyString const& attribute_name)
{ {
// FIXME: Only invalidate if the attribute can actually affect style. // FIXME: Only invalidate if the attribute can actually affect style.
// OPTIMIZATION: For the `style` attribute, unless it's referenced by an attribute selector, // OPTIMIZATION: For the `style` attribute, unless it's referenced by an attribute selector,
// only invalidate the element itself, then let inheritance propagate to descendants. // only invalidate the element itself, then let inheritance propagate to descendants.
if (attribute_name == HTML::AttributeNames::style if (attribute_name == HTML::AttributeNames::style) {
&& !document().style_computer().has_attribute_selector(HTML::AttributeNames::style)) { if (!document().style_computer().has_attribute_selector(HTML::AttributeNames::style)) {
set_needs_style_update(true); set_needs_style_update(true);
for_each_shadow_including_descendant([](Node& node) { for_each_shadow_including_descendant([](Node& node) {
if (!node.is_element()) if (!node.is_element())
return TraversalDecision::Continue;
auto& element = static_cast<Element&>(node);
element.set_needs_inherited_style_update(true);
return TraversalDecision::Continue; return TraversalDecision::Continue;
auto& element = static_cast<Element&>(node); });
element.set_needs_inherited_style_update(true); } else {
return TraversalDecision::Continue; invalidate_style(StyleInvalidationReason::ElementAttributeChange);
}); }
return; return;
} }
invalidate_style(StyleInvalidationReason::ElementAttributeChange); if (is_presentational_hint(attribute_name)
|| attribute_name_may_affect_selectors(*this, attribute_name)) {
invalidate_style(StyleInvalidationReason::ElementAttributeChange);
return;
}
} }
bool Element::is_hidden() const bool Element::is_hidden() const

View file

@ -173,6 +173,7 @@ public:
// https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes // https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
virtual bool supports_dimension_attributes() const { return false; } virtual bool supports_dimension_attributes() const { return false; }
virtual bool is_presentational_hint(FlyString const&) const { return false; }
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const { } virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const { }
void run_attribute_change_steps(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_); void run_attribute_change_steps(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_);

View file

@ -71,6 +71,7 @@ enum class IsDescendant {
X(ElementSetShadowRoot) \ X(ElementSetShadowRoot) \
X(FocusedElementChange) \ X(FocusedElementChange) \
X(HTMLHyperlinkElementHrefChange) \ X(HTMLHyperlinkElementHrefChange) \
X(HTMLIFrameElementGeometryChange) \
X(HTMLInputElementSetChecked) \ X(HTMLInputElementSetChecked) \
X(HTMLObjectElementUpdateLayoutAndChildObjects) \ X(HTMLObjectElementUpdateLayoutAndChildObjects) \
X(HTMLSelectElementSetIsOpen) \ X(HTMLSelectElementSetIsOpen) \

View file

@ -41,6 +41,17 @@ void HTMLBodyElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLBodyElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLBodyElement);
} }
bool HTMLBodyElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::bgcolor,
HTML::AttributeNames::text,
HTML::AttributeNames::background);
}
void HTMLBodyElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLBodyElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -22,6 +22,7 @@ public:
virtual ~HTMLBodyElement() override; virtual ~HTMLBodyElement() override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override; virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// https://www.w3.org/TR/html-aria/#el-body // https://www.w3.org/TR/html-aria/#el-body

View file

@ -65,6 +65,16 @@ void HTMLCanvasElement::visit_edges(Cell::Visitor& visitor)
}); });
} }
bool HTMLCanvasElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::width,
HTML::AttributeNames::height);
}
void HTMLCanvasElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLCanvasElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
// https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images // https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images

View file

@ -52,6 +52,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override; virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override;

View file

@ -21,6 +21,14 @@ HTMLDivElement::HTMLDivElement(DOM::Document& document, DOM::QualifiedName quali
HTMLDivElement::~HTMLDivElement() = default; HTMLDivElement::~HTMLDivElement() = default;
bool HTMLDivElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::align;
}
// https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3 // https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3
void HTMLDivElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLDivElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {

View file

@ -26,6 +26,7 @@ protected:
private: private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -29,6 +29,19 @@ void HTMLEmbedElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLEmbedElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLEmbedElement);
} }
bool HTMLEmbedElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::align,
HTML::AttributeNames::height,
HTML::AttributeNames::hspace,
HTML::AttributeNames::vspace,
HTML::AttributeNames::width);
}
void HTMLEmbedElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLEmbedElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -22,6 +22,7 @@ private:
virtual bool is_html_embed_element() const override { return true; } virtual bool is_html_embed_element() const override { return true; }
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual void adjust_computed_style(CSS::ComputedProperties&) override; virtual void adjust_computed_style(CSS::ComputedProperties&) override;
}; };

View file

@ -112,6 +112,17 @@ void HTMLFontElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLFontElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLFontElement);
} }
bool HTMLFontElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::color,
HTML::AttributeNames::face,
HTML::AttributeNames::size);
}
void HTMLFontElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLFontElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -17,6 +17,7 @@ class HTMLFontElement final : public HTMLElement {
public: public:
virtual ~HTMLFontElement() override; virtual ~HTMLFontElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
private: private:

View file

@ -28,6 +28,14 @@ void HTMLHRElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLHRElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLHRElement);
} }
bool HTMLHRElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name, HTML::AttributeNames::width);
}
void HTMLHRElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLHRElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -26,6 +26,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -27,6 +27,14 @@ void HTMLHeadingElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLHeadingElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLHeadingElement);
} }
bool HTMLHeadingElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::align;
}
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2 // https://html.spec.whatwg.org/multipage/rendering.html#tables-2
void HTMLHeadingElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLHeadingElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {

View file

@ -18,6 +18,7 @@ class HTMLHeadingElement final : public HTMLElement {
public: public:
virtual ~HTMLHeadingElement() override; virtual ~HTMLHeadingElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// https://www.w3.org/TR/html-aria/#el-h1-h6 // https://www.w3.org/TR/html-aria/#el-h1-h6

View file

@ -60,6 +60,11 @@ void HTMLIFrameElement::attribute_changed(FlyString const& name, Optional<String
if (name == AttributeNames::srcdoc || (name == AttributeNames::src && !has_attribute(AttributeNames::srcdoc))) if (name == AttributeNames::srcdoc || (name == AttributeNames::src && !has_attribute(AttributeNames::srcdoc)))
process_the_iframe_attributes(); process_the_iframe_attributes();
} }
if (name == HTML::AttributeNames::width || name == HTML::AttributeNames::height) {
// FIXME: This should only invalidate the layout, not the style.
invalidate_style(DOM::StyleInvalidationReason::ElementAttributeChange);
}
} }
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:html-element-post-connection-steps // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:html-element-post-connection-steps

View file

@ -82,6 +82,17 @@ void HTMLImageElement::visit_edges(Cell::Visitor& visitor)
visit_lazy_loading_element(visitor); visit_lazy_loading_element(visitor);
} }
bool HTMLImageElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::hspace,
HTML::AttributeNames::vspace,
HTML::AttributeNames::border);
}
void HTMLImageElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLImageElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -121,6 +121,7 @@ private:
virtual void adopted_from(DOM::Document&) override; virtual void adopted_from(DOM::Document&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element:dimension-attributes // https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element:dimension-attributes

View file

@ -1593,6 +1593,23 @@ void HTMLInputElement::form_associated_element_was_removed(DOM::Node*)
set_shadow_root(nullptr); set_shadow_root(nullptr);
} }
bool HTMLInputElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
if (type_state() != TypeAttributeState::ImageButton)
return false;
return first_is_one_of(name,
HTML::AttributeNames::align,
HTML::AttributeNames::border,
HTML::AttributeNames::height,
HTML::AttributeNames::hspace,
HTML::AttributeNames::vspace,
HTML::AttributeNames::width);
}
void HTMLInputElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLInputElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
if (type_state() != TypeAttributeState::ImageButton) if (type_state() != TypeAttributeState::ImageButton)

View file

@ -227,6 +227,7 @@ private:
void type_attribute_changed(TypeAttributeState old_state, TypeAttributeState new_state); void type_attribute_changed(TypeAttributeState old_state, TypeAttributeState new_state);
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// ^DOM::Node // ^DOM::Node

View file

@ -30,6 +30,19 @@ void HTMLMarqueeElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLMarqueeElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLMarqueeElement);
} }
bool HTMLMarqueeElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::bgcolor,
HTML::AttributeNames::height,
HTML::AttributeNames::hspace,
HTML::AttributeNames::vspace,
HTML::AttributeNames::width);
}
void HTMLMarqueeElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLMarqueeElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
HTMLElement::apply_presentational_hints(cascaded_properties); HTMLElement::apply_presentational_hints(cascaded_properties);

View file

@ -30,6 +30,7 @@ private:
HTMLMarqueeElement(DOM::Document&, DOM::QualifiedName); HTMLMarqueeElement(DOM::Document&, DOM::QualifiedName);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -100,6 +100,20 @@ void HTMLObjectElement::form_associated_element_was_removed(DOM::Node*)
destroy_the_child_navigable(); destroy_the_child_navigable();
} }
bool HTMLObjectElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::align,
HTML::AttributeNames::border,
HTML::AttributeNames::height,
HTML::AttributeNames::hspace,
HTML::AttributeNames::vspace,
HTML::AttributeNames::width);
}
void HTMLObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -54,6 +54,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override; virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override;

View file

@ -27,6 +27,14 @@ void HTMLParagraphElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLParagraphElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLParagraphElement);
} }
bool HTMLParagraphElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::align;
}
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2 // https://html.spec.whatwg.org/multipage/rendering.html#tables-2
void HTMLParagraphElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLParagraphElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {

View file

@ -18,6 +18,7 @@ class HTMLParagraphElement final : public HTMLElement {
public: public:
virtual ~HTMLParagraphElement() override; virtual ~HTMLParagraphElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// https://www.w3.org/TR/html-aria/#el-p // https://www.w3.org/TR/html-aria/#el-p

View file

@ -28,6 +28,14 @@ void HTMLPreElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLPreElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLPreElement);
} }
bool HTMLPreElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::wrap;
}
void HTMLPreElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLPreElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
HTMLElement::apply_presentational_hints(cascaded_properties); HTMLElement::apply_presentational_hints(cascaded_properties);

View file

@ -26,6 +26,7 @@ private:
HTMLPreElement(DOM::Document&, DOM::QualifiedName); HTMLPreElement(DOM::Document&, DOM::QualifiedName);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -27,6 +27,14 @@ void HTMLTableCaptionElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableCaptionElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableCaptionElement);
} }
bool HTMLTableCaptionElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::align;
}
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2 // https://html.spec.whatwg.org/multipage/rendering.html#tables-2
void HTMLTableCaptionElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableCaptionElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {

View file

@ -18,6 +18,7 @@ class HTMLTableCaptionElement final : public HTMLElement {
public: public:
virtual ~HTMLTableCaptionElement() override; virtual ~HTMLTableCaptionElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// https://www.w3.org/TR/html-aria/#el-caption // https://www.w3.org/TR/html-aria/#el-caption

View file

@ -37,6 +37,20 @@ void HTMLTableCellElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableCellElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableCellElement);
} }
bool HTMLTableCellElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::align,
HTML::AttributeNames::background,
HTML::AttributeNames::bgcolor,
HTML::AttributeNames::height,
HTML::AttributeNames::valign,
HTML::AttributeNames::width);
}
void HTMLTableCellElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableCellElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -34,6 +34,7 @@ private:
virtual bool is_html_table_cell_element() const override { return true; } virtual bool is_html_table_cell_element() const override { return true; }
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -51,6 +51,14 @@ WebIDL::ExceptionOr<void> HTMLTableColElement::set_span(unsigned int value)
return set_attribute(HTML::AttributeNames::span, String::number(value)); return set_attribute(HTML::AttributeNames::span, String::number(value));
} }
bool HTMLTableColElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return name == HTML::AttributeNames::width;
}
void HTMLTableColElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableColElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -26,6 +26,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
}; };

View file

@ -51,6 +51,22 @@ static unsigned parse_border(StringView value)
return value.to_number<unsigned>().value_or(0); return value.to_number<unsigned>().value_or(0);
} }
bool HTMLTableElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::align,
HTML::AttributeNames::background,
HTML::AttributeNames::bgcolor,
HTML::AttributeNames::border,
HTML::AttributeNames::cellpadding,
HTML::AttributeNames::cellspacing,
HTML::AttributeNames::height,
HTML::AttributeNames::width);
}
void HTMLTableElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -58,6 +58,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override; virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;

View file

@ -39,6 +39,18 @@ void HTMLTableRowElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableRowElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLTableRowElement);
} }
bool HTMLTableRowElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::bgcolor,
HTML::AttributeNames::background,
HTML::AttributeNames::height,
HTML::AttributeNames::valign);
}
void HTMLTableRowElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableRowElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
Base::apply_presentational_hints(cascaded_properties); Base::apply_presentational_hints(cascaded_properties);

View file

@ -35,6 +35,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
GC::Ptr<DOM::HTMLCollection> mutable m_cells; GC::Ptr<DOM::HTMLCollection> mutable m_cells;

View file

@ -100,6 +100,16 @@ WebIDL::ExceptionOr<void> HTMLTableSectionElement::delete_row(WebIDL::Long index
return {}; return {};
} }
bool HTMLTableSectionElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
HTML::AttributeNames::background,
HTML::AttributeNames::bgcolor);
}
void HTMLTableSectionElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void HTMLTableSectionElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {

View file

@ -37,6 +37,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
GC::Ptr<DOM::HTMLCollection> mutable m_rows; GC::Ptr<DOM::HTMLCollection> mutable m_rows;

View file

@ -29,6 +29,17 @@ void SVGCircleElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGCircleElement); WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGCircleElement);
} }
bool SVGCircleElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
SVG::AttributeNames::cx,
SVG::AttributeNames::cy,
SVG::AttributeNames::r);
}
void SVGCircleElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGCircleElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
Base::apply_presentational_hints(cascaded_properties); Base::apply_presentational_hints(cascaded_properties);

View file

@ -18,6 +18,7 @@ class SVGCircleElement final : public SVGGeometryElement {
public: public:
virtual ~SVGCircleElement() override = default; virtual ~SVGCircleElement() override = default;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Gfx::Path get_path(CSSPixelSize viewport_size) override; virtual Gfx::Path get_path(CSSPixelSize viewport_size) override;

View file

@ -52,6 +52,16 @@ GC::Ptr<Layout::Node> SVGForeignObjectElement::create_layout_node(GC::Ref<CSS::C
return heap().allocate<Layout::SVGForeignObjectBox>(document(), *this, move(style)); return heap().allocate<Layout::SVGForeignObjectBox>(document(), *this, move(style));
} }
bool SVGForeignObjectElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
SVG::AttributeNames::width,
SVG::AttributeNames::height);
}
void SVGForeignObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGForeignObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
Base::apply_presentational_hints(cascaded_properties); Base::apply_presentational_hints(cascaded_properties);

View file

@ -31,6 +31,7 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
GC::Ptr<SVG::SVGAnimatedLength> m_x; GC::Ptr<SVG::SVGAnimatedLength> m_x;

View file

@ -143,32 +143,40 @@ struct NamedPropertyID {
StringView name; StringView name;
}; };
static Array const attribute_style_properties {
// FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now.
NamedPropertyID(CSS::PropertyID::Fill),
// FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now.
NamedPropertyID(CSS::PropertyID::Stroke),
NamedPropertyID(CSS::PropertyID::StrokeDasharray),
NamedPropertyID(CSS::PropertyID::StrokeDashoffset),
NamedPropertyID(CSS::PropertyID::StrokeLinecap),
NamedPropertyID(CSS::PropertyID::StrokeLinejoin),
NamedPropertyID(CSS::PropertyID::StrokeMiterlimit),
NamedPropertyID(CSS::PropertyID::StrokeWidth),
NamedPropertyID(CSS::PropertyID::FillRule),
NamedPropertyID(CSS::PropertyID::FillOpacity),
NamedPropertyID(CSS::PropertyID::StrokeOpacity),
NamedPropertyID(CSS::PropertyID::Opacity),
NamedPropertyID(CSS::PropertyID::TextAnchor),
NamedPropertyID(CSS::PropertyID::FontSize),
NamedPropertyID(CSS::PropertyID::Mask),
NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::ClipPath),
NamedPropertyID(CSS::PropertyID::ClipRule),
NamedPropertyID(CSS::PropertyID::Display),
};
bool SVGGraphicsElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return any_of(attribute_style_properties, [&](auto& property) { return name.equals_ignoring_ascii_case(property.name); });
}
void SVGGraphicsElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGGraphicsElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
static Array const attribute_style_properties {
// FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now.
NamedPropertyID(CSS::PropertyID::Fill),
// FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now.
NamedPropertyID(CSS::PropertyID::Stroke),
NamedPropertyID(CSS::PropertyID::StrokeDasharray),
NamedPropertyID(CSS::PropertyID::StrokeDashoffset),
NamedPropertyID(CSS::PropertyID::StrokeLinecap),
NamedPropertyID(CSS::PropertyID::StrokeLinejoin),
NamedPropertyID(CSS::PropertyID::StrokeMiterlimit),
NamedPropertyID(CSS::PropertyID::StrokeWidth),
NamedPropertyID(CSS::PropertyID::FillRule),
NamedPropertyID(CSS::PropertyID::FillOpacity),
NamedPropertyID(CSS::PropertyID::StrokeOpacity),
NamedPropertyID(CSS::PropertyID::Opacity),
NamedPropertyID(CSS::PropertyID::TextAnchor),
NamedPropertyID(CSS::PropertyID::FontSize),
NamedPropertyID(CSS::PropertyID::Mask),
NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::ClipPath),
NamedPropertyID(CSS::PropertyID::ClipRule),
NamedPropertyID(CSS::PropertyID::Display),
};
CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute }; CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
for_each_attribute([&](auto& name, auto& value) { for_each_attribute([&](auto& name, auto& value) {
for (auto property : attribute_style_properties) { for (auto property : attribute_style_properties) {

View file

@ -29,6 +29,7 @@ class SVGGraphicsElement : public SVGElement {
WEB_PLATFORM_OBJECT(SVGGraphicsElement, SVGElement); WEB_PLATFORM_OBJECT(SVGGraphicsElement, SVGElement);
public: public:
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override; virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;

View file

@ -79,6 +79,20 @@ RefPtr<CSS::CSSStyleValue> SVGSVGElement::height_style_value_from_attribute() co
return nullptr; return nullptr;
} }
bool SVGSVGElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
SVG::AttributeNames::x,
SVG::AttributeNames::y,
SVG::AttributeNames::width,
SVG::AttributeNames::height,
SVG::AttributeNames::viewBox,
SVG::AttributeNames::preserveAspectRatio);
}
void SVGSVGElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGSVGElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
Base::apply_presentational_hints(cascaded_properties); Base::apply_presentational_hints(cascaded_properties);

View file

@ -31,6 +31,7 @@ class SVGSVGElement final : public SVGGraphicsElement
public: public:
virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override; virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual bool requires_svg_container() const override { return false; } virtual bool requires_svg_container() const override { return false; }

View file

@ -30,6 +30,16 @@ void SVGStopElement::attribute_changed(FlyString const& name, Optional<String> c
} }
} }
bool SVGStopElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
return first_is_one_of(name,
"stop-color"sv,
"stop-opacity"sv);
}
void SVGStopElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGStopElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
CSS::Parser::ParsingContext parsing_context { document() }; CSS::Parser::ParsingContext parsing_context { document() };

View file

@ -25,6 +25,7 @@ public:
GC::Ref<SVGAnimatedNumber> offset() const; GC::Ref<SVGAnimatedNumber> offset() const;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
NumberPercentage stop_offset() const { return m_offset.value_or(NumberPercentage::create_number(0)); } NumberPercentage stop_offset() const { return m_offset.value_or(NumberPercentage::create_number(0)); }

View file

@ -39,11 +39,24 @@ void SVGSymbolElement::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_view_box_for_bindings); visitor.visit(m_view_box_for_bindings);
} }
bool SVGSymbolElement::is_presentational_hint(FlyString const& name) const
{
if (Base::is_presentational_hint(name))
return true;
// FIXME: This is not a correct use of the presentational hint mechanism.
if (is_direct_child_of_use_shadow_tree())
return true;
return false;
}
// https://svgwg.org/svg2-draft/struct.html#SymbolNotes // https://svgwg.org/svg2-draft/struct.html#SymbolNotes
void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{ {
Base::apply_presentational_hints(cascaded_properties); Base::apply_presentational_hints(cascaded_properties);
// FIXME: This is not a correct use of the presentational hint mechanism.
if (is_direct_child_of_use_shadow_tree()) { if (is_direct_child_of_use_shadow_tree()) {
// The generated instance of a symbol that is the direct referenced element of a use element must always have a computed value of inline for the display property. // The generated instance of a symbol that is the direct referenced element of a use element must always have a computed value of inline for the display property.
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Inline))); cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Inline)));

View file

@ -19,6 +19,7 @@ class SVGSymbolElement final : public SVGGraphicsElement
public: public:
virtual ~SVGSymbolElement() override = default; virtual ~SVGSymbolElement() override = default;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override; virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Optional<ViewBox> view_box() const override { return m_view_box; } virtual Optional<ViewBox> view_box() const override { return m_view_box; }