LibWeb: Add specialized fast_is for lists

Before:
  `is<HTMLOLListElement>` 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?).
This commit is contained in:
Manuel Zahariev 2025-04-23 07:06:11 -07:00 committed by Sam Atkins
commit d27b43c1ee
Notes: github-actions[bot] 2025-06-16 11:46:35 +00:00
7 changed files with 52 additions and 5 deletions

View file

@ -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<Web::HTML::HTMLLIElement>() const { return is_html_li_element(); }
}

View file

@ -21,6 +21,8 @@ public:
// https://www.w3.org/TR/html-aria/#el-menu
virtual Optional<ARIA::Role> 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<Web::HTML::HTMLMenuElement>() const { return is_html_menu_element(); }
}

View file

@ -11,6 +11,7 @@
#include <LibWeb/HTML/AttributeNames.h>
#include <LibWeb/HTML/HTMLOListElement.h>
#include <LibWeb/HTML/Numbers.h>
#include <LibWeb/WebIDL/Types.h>
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<i32> 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<WebIDL::Long>::max());
return reverse_list_starting_value;
}
// 3. Return 1.

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Checked.h>
#include <LibWeb/ARIA/Roles.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/WebIDL/Types.h>
@ -28,7 +29,9 @@ public:
MUST(set_attribute(AttributeNames::start, String::number(start)));
}
size_t starting_value() const;
AK::Checked<i32> 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<Web::HTML::HTMLOListElement>() const { return is_html_olist_element(); }
}

View file

@ -21,6 +21,8 @@ public:
// https://www.w3.org/TR/html-aria/#el-ul
virtual Optional<ARIA::Role> 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<Web::HTML::HTMLUListElement>() const { return is_html_ulist_element(); }
}