Explorar o código

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.
Andreas Kling hai 7 meses
pai
achega
b981e6f7bc
Modificáronse 56 ficheiros con 377 adicións e 37 borrados
  1. 8 2
      Libraries/LibWeb/CSS/StyleComputer.cpp
  2. 1 1
      Libraries/LibWeb/CSS/StyleComputer.h
  3. 38 10
      Libraries/LibWeb/DOM/Element.cpp
  4. 1 0
      Libraries/LibWeb/DOM/Element.h
  5. 1 0
      Libraries/LibWeb/DOM/Node.h
  6. 11 0
      Libraries/LibWeb/HTML/HTMLBodyElement.cpp
  7. 1 0
      Libraries/LibWeb/HTML/HTMLBodyElement.h
  8. 10 0
      Libraries/LibWeb/HTML/HTMLCanvasElement.cpp
  9. 1 0
      Libraries/LibWeb/HTML/HTMLCanvasElement.h
  10. 8 0
      Libraries/LibWeb/HTML/HTMLDivElement.cpp
  11. 1 0
      Libraries/LibWeb/HTML/HTMLDivElement.h
  12. 13 0
      Libraries/LibWeb/HTML/HTMLEmbedElement.cpp
  13. 1 0
      Libraries/LibWeb/HTML/HTMLEmbedElement.h
  14. 11 0
      Libraries/LibWeb/HTML/HTMLFontElement.cpp
  15. 1 0
      Libraries/LibWeb/HTML/HTMLFontElement.h
  16. 8 0
      Libraries/LibWeb/HTML/HTMLHRElement.cpp
  17. 1 0
      Libraries/LibWeb/HTML/HTMLHRElement.h
  18. 8 0
      Libraries/LibWeb/HTML/HTMLHeadingElement.cpp
  19. 1 0
      Libraries/LibWeb/HTML/HTMLHeadingElement.h
  20. 5 0
      Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
  21. 11 0
      Libraries/LibWeb/HTML/HTMLImageElement.cpp
  22. 1 0
      Libraries/LibWeb/HTML/HTMLImageElement.h
  23. 17 0
      Libraries/LibWeb/HTML/HTMLInputElement.cpp
  24. 1 0
      Libraries/LibWeb/HTML/HTMLInputElement.h
  25. 13 0
      Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp
  26. 1 0
      Libraries/LibWeb/HTML/HTMLMarqueeElement.h
  27. 14 0
      Libraries/LibWeb/HTML/HTMLObjectElement.cpp
  28. 1 0
      Libraries/LibWeb/HTML/HTMLObjectElement.h
  29. 8 0
      Libraries/LibWeb/HTML/HTMLParagraphElement.cpp
  30. 1 0
      Libraries/LibWeb/HTML/HTMLParagraphElement.h
  31. 8 0
      Libraries/LibWeb/HTML/HTMLPreElement.cpp
  32. 1 0
      Libraries/LibWeb/HTML/HTMLPreElement.h
  33. 8 0
      Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp
  34. 1 0
      Libraries/LibWeb/HTML/HTMLTableCaptionElement.h
  35. 14 0
      Libraries/LibWeb/HTML/HTMLTableCellElement.cpp
  36. 1 0
      Libraries/LibWeb/HTML/HTMLTableCellElement.h
  37. 8 0
      Libraries/LibWeb/HTML/HTMLTableColElement.cpp
  38. 1 0
      Libraries/LibWeb/HTML/HTMLTableColElement.h
  39. 16 0
      Libraries/LibWeb/HTML/HTMLTableElement.cpp
  40. 1 0
      Libraries/LibWeb/HTML/HTMLTableElement.h
  41. 12 0
      Libraries/LibWeb/HTML/HTMLTableRowElement.cpp
  42. 1 0
      Libraries/LibWeb/HTML/HTMLTableRowElement.h
  43. 10 0
      Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp
  44. 1 0
      Libraries/LibWeb/HTML/HTMLTableSectionElement.h
  45. 11 0
      Libraries/LibWeb/SVG/SVGCircleElement.cpp
  46. 1 0
      Libraries/LibWeb/SVG/SVGCircleElement.h
  47. 10 0
      Libraries/LibWeb/SVG/SVGForeignObjectElement.cpp
  48. 1 0
      Libraries/LibWeb/SVG/SVGForeignObjectElement.h
  49. 32 24
      Libraries/LibWeb/SVG/SVGGraphicsElement.cpp
  50. 1 0
      Libraries/LibWeb/SVG/SVGGraphicsElement.h
  51. 14 0
      Libraries/LibWeb/SVG/SVGSVGElement.cpp
  52. 1 0
      Libraries/LibWeb/SVG/SVGSVGElement.h
  53. 10 0
      Libraries/LibWeb/SVG/SVGStopElement.cpp
  54. 1 0
      Libraries/LibWeb/SVG/SVGStopElement.h
  55. 13 0
      Libraries/LibWeb/SVG/SVGSymbolElement.cpp
  56. 1 0
      Libraries/LibWeb/SVG/SVGSymbolElement.h

+ 8 - 2
Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -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);
 

+ 1 - 1
Libraries/LibWeb/CSS/StyleComputer.h

@@ -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;

+ 38 - 10
Libraries/LibWeb/DOM/Element.cpp

@@ -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 user’s 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

+ 1 - 0
Libraries/LibWeb/DOM/Element.h

@@ -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_);

+ 1 - 0
Libraries/LibWeb/DOM/Node.h

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

+ 11 - 0
Libraries/LibWeb/HTML/HTMLBodyElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLBodyElement.h

@@ -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

+ 10 - 0
Libraries/LibWeb/HTML/HTMLCanvasElement.cpp

@@ -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

+ 1 - 0
Libraries/LibWeb/HTML/HTMLCanvasElement.h

@@ -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;

+ 8 - 0
Libraries/LibWeb/HTML/HTMLDivElement.cpp

@@ -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
 {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLDivElement.h

@@ -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;
 };
 

+ 13 - 0
Libraries/LibWeb/HTML/HTMLEmbedElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLEmbedElement.h

@@ -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;
 };

+ 11 - 0
Libraries/LibWeb/HTML/HTMLFontElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLFontElement.h

@@ -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:

+ 8 - 0
Libraries/LibWeb/HTML/HTMLHRElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLHRElement.h

@@ -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;
 };
 

+ 8 - 0
Libraries/LibWeb/HTML/HTMLHeadingElement.cpp

@@ -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
 {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLHeadingElement.h

@@ -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

+ 5 - 0
Libraries/LibWeb/HTML/HTMLIFrameElement.cpp

@@ -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

+ 11 - 0
Libraries/LibWeb/HTML/HTMLImageElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLImageElement.h

@@ -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

+ 17 - 0
Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -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)

+ 1 - 0
Libraries/LibWeb/HTML/HTMLInputElement.h

@@ -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

+ 13 - 0
Libraries/LibWeb/HTML/HTMLMarqueeElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/HTML/HTMLMarqueeElement.h

@@ -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;
 };
 

+ 14 - 0
Libraries/LibWeb/HTML/HTMLObjectElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLObjectElement.h

@@ -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;

+ 8 - 0
Libraries/LibWeb/HTML/HTMLParagraphElement.cpp

@@ -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
 {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLParagraphElement.h

@@ -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

+ 8 - 0
Libraries/LibWeb/HTML/HTMLPreElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/HTML/HTMLPreElement.h

@@ -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;
 };
 

+ 8 - 0
Libraries/LibWeb/HTML/HTMLTableCaptionElement.cpp

@@ -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
 {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableCaptionElement.h

@@ -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

+ 14 - 0
Libraries/LibWeb/HTML/HTMLTableCellElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableCellElement.h

@@ -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;
 };
 

+ 8 - 0
Libraries/LibWeb/HTML/HTMLTableColElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableColElement.h

@@ -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;
 };
 

+ 16 - 0
Libraries/LibWeb/HTML/HTMLTableElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableElement.h

@@ -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;
 

+ 12 - 0
Libraries/LibWeb/HTML/HTMLTableRowElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableRowElement.h

@@ -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;

+ 10 - 0
Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp

@@ -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) {

+ 1 - 0
Libraries/LibWeb/HTML/HTMLTableSectionElement.h

@@ -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;

+ 11 - 0
Libraries/LibWeb/SVG/SVGCircleElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/SVG/SVGCircleElement.h

@@ -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;

+ 10 - 0
Libraries/LibWeb/SVG/SVGForeignObjectElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/SVG/SVGForeignObjectElement.h

@@ -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;

+ 32 - 24
Libraries/LibWeb/SVG/SVGGraphicsElement.cpp

@@ -143,32 +143,40 @@ struct NamedPropertyID {
     StringView 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),
+};
+
+bool SVGGraphicsElement::is_presentational_hint(FlyString const& name) 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),
-    };
+    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
+{
     CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
     for_each_attribute([&](auto& name, auto& value) {
         for (auto property : attribute_style_properties) {

+ 1 - 0
Libraries/LibWeb/SVG/SVGGraphicsElement.h

@@ -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;

+ 14 - 0
Libraries/LibWeb/SVG/SVGSVGElement.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/SVG/SVGSVGElement.h

@@ -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; }

+ 10 - 0
Libraries/LibWeb/SVG/SVGStopElement.cpp

@@ -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() };

+ 1 - 0
Libraries/LibWeb/SVG/SVGStopElement.h

@@ -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)); }

+ 13 - 0
Libraries/LibWeb/SVG/SVGSymbolElement.cpp

@@ -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)));

+ 1 - 0
Libraries/LibWeb/SVG/SVGSymbolElement.h

@@ -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; }