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
parent b11bdd4022
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();
style.set_property(CSS::PropertyID::FontSize, LengthStyleValue::create(CSS::Length::make_px(CSSPixels::nearest_value_for(found_font->pixel_size()))));
style.set_property(CSS::PropertyID::FontWeight, NumberStyleValue::create(font_weight.to_font_weight()));
style.set_property(
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);

View file

@ -177,6 +177,7 @@ public:
[[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::Element&, Optional<Selector::PseudoElement::Type>, CascadedProperties&) const;
void absolutize_values(ComputedProperties&) const;
void compute_font(ComputedProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
private:
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_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;
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_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;

View file

@ -573,6 +573,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style()
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);
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.
}
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)
{
// FIXME: Only invalidate if the attribute can actually affect style.
// OPTIMIZATION: For the `style` attribute, unless it's referenced by an attribute selector,
// only invalidate the element itself, then let inheritance propagate to descendants.
if (attribute_name == HTML::AttributeNames::style
&& !document().style_computer().has_attribute_selector(HTML::AttributeNames::style)) {
set_needs_style_update(true);
for_each_shadow_including_descendant([](Node& node) {
if (!node.is_element())
if (attribute_name == HTML::AttributeNames::style) {
if (!document().style_computer().has_attribute_selector(HTML::AttributeNames::style)) {
set_needs_style_update(true);
for_each_shadow_including_descendant([](Node& node) {
if (!node.is_element())
return TraversalDecision::Continue;
auto& element = static_cast<Element&>(node);
element.set_needs_inherited_style_update(true);
return TraversalDecision::Continue;
auto& element = static_cast<Element&>(node);
element.set_needs_inherited_style_update(true);
return TraversalDecision::Continue;
});
});
} else {
invalidate_style(StyleInvalidationReason::ElementAttributeChange);
}
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

View file

@ -173,6 +173,7 @@ public:
// https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
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 { }
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(FocusedElementChange) \
X(HTMLHyperlinkElementHrefChange) \
X(HTMLIFrameElementGeometryChange) \
X(HTMLInputElementSetChecked) \
X(HTMLObjectElementUpdateLayoutAndChildObjects) \
X(HTMLSelectElementSetIsOpen) \

View file

@ -41,6 +41,17 @@ void HTMLBodyElement::initialize(JS::Realm& realm)
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -22,6 +22,7 @@ public:
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 bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// 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
{
// 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 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 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;
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
void HTMLDivElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{

View file

@ -26,6 +26,7 @@ protected:
private:
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;
};

View file

@ -29,6 +29,19 @@ void HTMLEmbedElement::initialize(JS::Realm& realm)
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
{
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 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 adjust_computed_style(CSS::ComputedProperties&) override;
};

View file

@ -112,6 +112,17 @@ void HTMLFontElement::initialize(JS::Realm& realm)
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

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

View file

@ -28,6 +28,14 @@ void HTMLHRElement::initialize(JS::Realm& realm)
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -26,6 +26,7 @@ private:
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;
};

View file

@ -27,6 +27,14 @@ void HTMLHeadingElement::initialize(JS::Realm& realm)
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
void HTMLHeadingElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{

View file

@ -18,6 +18,7 @@ class HTMLHeadingElement final : public HTMLElement {
public:
virtual ~HTMLHeadingElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// 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)))
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

View file

@ -82,6 +82,17 @@ void HTMLImageElement::visit_edges(Cell::Visitor& 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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -121,6 +121,7 @@ private:
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;
// 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);
}
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
{
if (type_state() != TypeAttributeState::ImageButton)

View file

@ -227,6 +227,7 @@ private:
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;
// ^DOM::Node

View file

@ -30,6 +30,19 @@ void HTMLMarqueeElement::initialize(JS::Realm& realm)
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
{
HTMLElement::apply_presentational_hints(cascaded_properties);

View file

@ -30,6 +30,7 @@ private:
HTMLMarqueeElement(DOM::Document&, DOM::QualifiedName);
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;
};

View file

@ -100,6 +100,20 @@ void HTMLObjectElement::form_associated_element_was_removed(DOM::Node*)
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -54,6 +54,7 @@ private:
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 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);
}
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
void HTMLParagraphElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{

View file

@ -18,6 +18,7 @@ class HTMLParagraphElement final : public HTMLElement {
public:
virtual ~HTMLParagraphElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// 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);
}
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
{
HTMLElement::apply_presentational_hints(cascaded_properties);

View file

@ -26,6 +26,7 @@ private:
HTMLPreElement(DOM::Document&, DOM::QualifiedName);
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;
};

View file

@ -27,6 +27,14 @@ void HTMLTableCaptionElement::initialize(JS::Realm& realm)
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
void HTMLTableCaptionElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{

View file

@ -18,6 +18,7 @@ class HTMLTableCaptionElement final : public HTMLElement {
public:
virtual ~HTMLTableCaptionElement() override;
virtual bool is_presentational_hint(FlyString const&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
// 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);
}
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
{
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 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;
};

View file

@ -51,6 +51,14 @@ WebIDL::ExceptionOr<void> HTMLTableColElement::set_span(unsigned int 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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -26,6 +26,7 @@ private:
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;
};

View file

@ -51,6 +51,22 @@ static unsigned parse_border(StringView value)
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -58,6 +58,7 @@ private:
virtual void initialize(JS::Realm&) 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 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);
}
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
{
Base::apply_presentational_hints(cascaded_properties);

View file

@ -35,6 +35,7 @@ private:
virtual void initialize(JS::Realm&) 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;
GC::Ptr<DOM::HTMLCollection> mutable m_cells;

View file

@ -100,6 +100,16 @@ WebIDL::ExceptionOr<void> HTMLTableSectionElement::delete_row(WebIDL::Long index
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
{
for_each_attribute([&](auto& name, auto& value) {

View file

@ -37,6 +37,7 @@ private:
virtual void initialize(JS::Realm&) 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;
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);
}
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
{
Base::apply_presentational_hints(cascaded_properties);

View file

@ -18,6 +18,7 @@ class SVGCircleElement final : public SVGGeometryElement {
public:
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 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));
}
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
{
Base::apply_presentational_hints(cascaded_properties);

View file

@ -31,6 +31,7 @@ private:
virtual void initialize(JS::Realm&) 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;
GC::Ptr<SVG::SVGAnimatedLength> m_x;

View file

@ -143,32 +143,40 @@ struct NamedPropertyID {
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
{
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 };
for_each_attribute([&](auto& name, auto& value) {
for (auto property : attribute_style_properties) {

View file

@ -29,6 +29,7 @@ class SVGGraphicsElement : public SVGElement {
WEB_PLATFORM_OBJECT(SVGGraphicsElement, SVGElement);
public:
virtual bool is_presentational_hint(FlyString const&) 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;

View file

@ -79,6 +79,20 @@ RefPtr<CSS::CSSStyleValue> SVGSVGElement::height_style_value_from_attribute() co
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
{
Base::apply_presentational_hints(cascaded_properties);

View file

@ -31,6 +31,7 @@ class SVGSVGElement final : public SVGGraphicsElement
public:
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 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
{
CSS::Parser::ParsingContext parsing_context { document() };

View file

@ -25,6 +25,7 @@ public:
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;
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);
}
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
void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
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()) {
// 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)));

View file

@ -19,6 +19,7 @@ class SVGSymbolElement final : public SVGGraphicsElement
public:
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 Optional<ViewBox> view_box() const override { return m_view_box; }