From d27b43c1eeb006f52823d38883c3091bb868b85d Mon Sep 17 00:00:00 2001 From: Manuel Zahariev Date: Wed, 23 Apr 2025 07:06:11 -0700 Subject: [PATCH] LibWeb: Add specialized fast_is for lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before: `is` and other similar calls in this commit are resolved to `dynamic_cast`, which incurs runtime overhead for resolving the type. The Performance hit becomes apparent when rendering large lists. Callgrind analysis points to a significant performance hit from calls to `is<...>` in `Element::list_owner`. Reference: Michael Gibbs and Bjarne Stroustrup (2006) Fast dynamic casting. Softw. Pract. Exper. 2006; 36:139–156 After: Implement inline `fast_is` virtual method that immediately resolves the type. Results in noticeable performance improvement 2x-ish for lists with 20K entries. Bonus: Convert starting value for LI elements to signed integer The spec requires the start attribute and starting value to be "integer". Firefox and Chrome support a negative start attribute. FIXME: At the time of this PR, about 134 other objects resolve `is<...>` to `dynamic_cast`. It may be a good idea to coordinate similar changes to at least [some of] the ones that my have impact on performance (maybe open a new issue?). --- Libraries/LibWeb/DOM/Element.cpp | 2 +- Libraries/LibWeb/DOM/Node.h | 8 ++++++++ Libraries/LibWeb/HTML/HTMLLIElement.h | 9 +++++++++ Libraries/LibWeb/HTML/HTMLMenuElement.h | 9 +++++++++ Libraries/LibWeb/HTML/HTMLOListElement.cpp | 8 +++++--- Libraries/LibWeb/HTML/HTMLOListElement.h | 12 +++++++++++- Libraries/LibWeb/HTML/HTMLUListElement.h | 9 +++++++++ 7 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 50f0f89bbed..65496f8d492 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -3252,7 +3252,7 @@ size_t Element::ordinal_value() const auto reversed = false; if (is(owner)) { auto const* ol_element = static_cast(owner); - numbering = ol_element->starting_value(); + numbering = ol_element->starting_value().value(); reversed = ol_element->has_attribute(HTML::AttributeNames::reversed); } diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 3a76ac86ad7..4b7cbb206b9 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -214,6 +214,14 @@ public: virtual bool is_html_span_element() const { return false; } virtual bool is_html_frameset_element() const { return false; } virtual bool is_html_fieldset_element() const { return false; } + virtual bool is_html_li_element() const { return false; } + virtual bool is_html_menu_element() const { return false; } + virtual bool is_html_olist_element() const { return false; } + virtual bool is_html_ulist_element() const { return false; } + ALWAYS_INLINE bool is_html_ol_ul_menu_element() const + { + return is_html_olist_element() || is_html_ulist_element() || is_html_menu_element(); + } virtual bool is_navigable_container() const { return false; } virtual bool is_lazy_loading() const { return false; } diff --git a/Libraries/LibWeb/HTML/HTMLLIElement.h b/Libraries/LibWeb/HTML/HTMLLIElement.h index e07301fe409..f6bdf6a53df 100644 --- a/Libraries/LibWeb/HTML/HTMLLIElement.h +++ b/Libraries/LibWeb/HTML/HTMLLIElement.h @@ -41,6 +41,8 @@ public: MUST(set_attribute(AttributeNames::value, String::number(value))); } + virtual bool is_html_li_element() const override { return true; } + private: HTMLLIElement(DOM::Document&, DOM::QualifiedName); @@ -52,3 +54,10 @@ private: }; } + +namespace Web::DOM { + +template<> +inline bool Node::fast_is() const { return is_html_li_element(); } + +} diff --git a/Libraries/LibWeb/HTML/HTMLMenuElement.h b/Libraries/LibWeb/HTML/HTMLMenuElement.h index e9c21ab25f9..4d44bf669d3 100644 --- a/Libraries/LibWeb/HTML/HTMLMenuElement.h +++ b/Libraries/LibWeb/HTML/HTMLMenuElement.h @@ -21,6 +21,8 @@ public: // https://www.w3.org/TR/html-aria/#el-menu virtual Optional default_role() const override { return ARIA::Role::list; } + virtual bool is_html_menu_element() const override { return true; } + private: HTMLMenuElement(DOM::Document&, DOM::QualifiedName); @@ -28,3 +30,10 @@ private: }; } + +namespace Web::DOM { + +template<> +inline bool Node::fast_is() const { return is_html_menu_element(); } + +} diff --git a/Libraries/LibWeb/HTML/HTMLOListElement.cpp b/Libraries/LibWeb/HTML/HTMLOListElement.cpp index e0c51e4d98b..97adbffbfab 100644 --- a/Libraries/LibWeb/HTML/HTMLOListElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLOListElement.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace Web::HTML { @@ -49,7 +50,7 @@ WebIDL::Long HTMLOListElement::start() } // https://html.spec.whatwg.org/multipage/grouping-content.html#concept-ol-start -size_t HTMLOListElement::starting_value() const +AK::Checked HTMLOListElement::starting_value() const { // 1. If the ol element has a start attribute, then: auto start = get_attribute(AttributeNames::start); @@ -61,10 +62,11 @@ size_t HTMLOListElement::starting_value() const if (parsed.has_value()) return parsed.value(); } - // 2. If the ol element has a reversed attribute, then return the number of owned li elements. if (has_attribute(AttributeNames::reversed)) { - return number_of_owned_list_items(); + auto const reverse_list_starting_value = number_of_owned_list_items(); + VERIFY(reverse_list_starting_value <= NumericLimits::max()); + return reverse_list_starting_value; } // 3. Return 1. diff --git a/Libraries/LibWeb/HTML/HTMLOListElement.h b/Libraries/LibWeb/HTML/HTMLOListElement.h index 7687efeeefc..b7a002e8504 100644 --- a/Libraries/LibWeb/HTML/HTMLOListElement.h +++ b/Libraries/LibWeb/HTML/HTMLOListElement.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -28,7 +29,9 @@ public: MUST(set_attribute(AttributeNames::start, String::number(start))); } - size_t starting_value() const; + AK::Checked starting_value() const; + + virtual bool is_html_olist_element() const override { return true; } private: HTMLOListElement(DOM::Document&, DOM::QualifiedName); @@ -41,3 +44,10 @@ private: }; } + +namespace Web::DOM { + +template<> +inline bool Node::fast_is() const { return is_html_olist_element(); } + +} diff --git a/Libraries/LibWeb/HTML/HTMLUListElement.h b/Libraries/LibWeb/HTML/HTMLUListElement.h index d1b92a1c09a..b3703d2a5d0 100644 --- a/Libraries/LibWeb/HTML/HTMLUListElement.h +++ b/Libraries/LibWeb/HTML/HTMLUListElement.h @@ -21,6 +21,8 @@ public: // https://www.w3.org/TR/html-aria/#el-ul virtual Optional default_role() const override { return ARIA::Role::list; } + virtual bool is_html_ulist_element() const override { return true; } + private: HTMLUListElement(DOM::Document&, DOM::QualifiedName); @@ -31,3 +33,10 @@ private: }; } + +namespace Web::DOM { + +template<> +inline bool Node::fast_is() const { return is_html_ulist_element(); } + +}