LibWeb: Cache name->element mappings in HTMLCollection

This makes https://wpt.fyi/ load today instead of tomorrow, although
there's a lot of room for improvement still.
This commit is contained in:
Andreas Kling 2024-07-25 07:49:41 +02:00 committed by Andreas Kling
parent 132ab775d8
commit 4d78c66b3d
Notes: github-actions[bot] 2024-07-25 11:12:56 +00:00
4 changed files with 48 additions and 31 deletions

View file

@ -47,9 +47,7 @@ JS::ThrowCompletionOr<bool> PlatformObject::is_named_property_exposed_on_object(
// 1. If P is not a supported property name of O, then return false.
// NOTE: This is in it's own variable to enforce the type.
auto supported_property_names = this->supported_property_names();
auto property_key_string = MUST(String::from_byte_string(property_key.to_string()));
if (!supported_property_names.contains_slow(property_key_string))
if (!is_supported_property_name(MUST(String::from_byte_string(property_key.to_string()))))
return false;
// 2. If O has an own property named P, then return false.
@ -500,6 +498,11 @@ Vector<FlyString> PlatformObject::supported_property_names() const
return {};
}
bool PlatformObject::is_supported_property_name(FlyString const& name) const
{
return supported_property_names().contains_slow(name);
}
bool PlatformObject::is_supported_property_index(u32) const
{
return false;

View file

@ -75,6 +75,7 @@ protected:
virtual WebIDL::ExceptionOr<JS::Value> item_value(size_t index) const;
virtual WebIDL::ExceptionOr<JS::Value> named_item_value(FlyString const& name) const;
virtual Vector<FlyString> supported_property_names() const;
virtual bool is_supported_property_name(FlyString const&) const;
virtual bool is_supported_property_index(u32) const;
// NOTE: These will crash if you make has_named_property_setter return true but do not override these methods.

View file

@ -48,6 +48,30 @@ void HTMLCollection::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor);
visitor.visit(m_root);
visitor.visit(m_cached_elements);
if (m_cached_name_to_element_mappings)
visitor.visit(*m_cached_name_to_element_mappings);
}
void HTMLCollection::update_name_to_element_mappings_if_needed() const
{
update_cache_if_needed();
if (m_cached_name_to_element_mappings)
return;
m_cached_name_to_element_mappings = make<HashMap<FlyString, JS::NonnullGCPtr<Element>>>();
for (auto const& element : m_cached_elements) {
// 1. If element has an ID which is not in result, append elements ID to result.
if (auto const& id = element->id(); id.has_value()) {
if (!id.value().is_empty() && !m_cached_name_to_element_mappings->contains(id.value()))
m_cached_name_to_element_mappings->set(id.value(), element);
}
// 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string nor is in result, append elements name attribute value to result.
if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
auto element_name = element->name().value();
if (!element_name.is_empty() && !m_cached_name_to_element_mappings->contains(element_name))
m_cached_name_to_element_mappings->set(move(element_name), element);
}
}
}
void HTMLCollection::update_cache_if_needed() const
@ -57,6 +81,7 @@ void HTMLCollection::update_cache_if_needed() const
return;
m_cached_elements.clear();
m_cached_name_to_element_mappings = nullptr;
if (m_scope == Scope::Descendants) {
m_root->for_each_in_subtree_of_type<Element>([&](auto& element) {
if (m_filter(element))
@ -107,23 +132,19 @@ Element* HTMLCollection::named_item(FlyString const& key) const
if (key.is_empty())
return nullptr;
update_cache_if_needed();
// 2. Return the first element in the collection for which at least one of the following is true:
for (auto const& element : m_cached_elements) {
// - it has an ID which is key;
if (element->id() == key)
return element;
// - it is in the HTML namespace and has a name attribute whose value is key;
if (element->namespace_uri() == Namespace::HTML && element->name() == key)
return element;
}
// or null if there is no such element.
update_name_to_element_mappings_if_needed();
if (auto it = m_cached_name_to_element_mappings->get(key); it.has_value())
return it.value();
return nullptr;
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
bool HTMLCollection::is_supported_property_name(FlyString const& name) const
{
update_name_to_element_mappings_if_needed();
return m_cached_name_to_element_mappings->contains(name);
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
Vector<FlyString> HTMLCollection::supported_property_names() const
{
@ -131,20 +152,9 @@ Vector<FlyString> HTMLCollection::supported_property_names() const
Vector<FlyString> result;
// 2. For each element represented by the collection, in tree order:
update_cache_if_needed();
for (auto const& element : m_cached_elements) {
// 1. If element has an ID which is not in result, append elements ID to result.
if (auto const& id = element->id(); id.has_value()) {
if (!id.value().is_empty() && !result.contains_slow(id.value()))
result.append(id.value());
}
// 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string nor is in result, append elements name attribute value to result.
if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
auto name = element->name().value();
if (!name.is_empty() && !result.contains_slow(name))
result.append(move(name));
}
update_name_to_element_mappings_if_needed();
for (auto const& it : *m_cached_name_to_element_mappings) {
result.append(it.key);
}
// 3. Return result.

View file

@ -43,6 +43,7 @@ public:
virtual WebIDL::ExceptionOr<JS::Value> item_value(size_t index) const override;
virtual WebIDL::ExceptionOr<JS::Value> named_item_value(FlyString const& name) const override;
virtual Vector<FlyString> supported_property_names() const override;
virtual bool is_supported_property_name(FlyString const&) const override;
virtual bool is_supported_property_index(u32) const override;
protected:
@ -57,9 +58,11 @@ private:
virtual void visit_edges(Cell::Visitor&) override;
void update_cache_if_needed() const;
void update_name_to_element_mappings_if_needed() const;
mutable u64 m_cached_dom_tree_version { 0 };
mutable Vector<JS::NonnullGCPtr<Element>> m_cached_elements;
mutable OwnPtr<HashMap<FlyString, JS::NonnullGCPtr<Element>>> m_cached_name_to_element_mappings;
JS::NonnullGCPtr<ParentNode> m_root;
Function<bool(Element const&)> m_filter;