LibWeb: Cache state of the contenteditable attribute on HTMLElement

Instead of recomputing the state whenever someone asks for it, we now
cache it when the attribute is added/changed/removed.

Before this change, HTMLElement::is_editable() was 6.5% of CPU time
when furiously resizing Hacker News. After, it's less than 0.5%. :^)
This commit is contained in:
Andreas Kling 2023-05-15 10:08:13 +02:00
parent 990e7219d6
commit 20e2c9a7dd
Notes: sideshowbarker 2024-07-16 22:17:03 +09:00
2 changed files with 25 additions and 16 deletions

View File

@ -77,22 +77,9 @@ void HTMLElement::set_dir(DeprecatedString const& dir)
MUST(set_attribute(HTML::AttributeNames::dir, dir));
}
HTMLElement::ContentEditableState HTMLElement::content_editable_state() const
{
auto contenteditable = attribute(HTML::AttributeNames::contenteditable);
// "true", an empty string or a missing value map to the "true" state.
if ((!contenteditable.is_null() && contenteditable.is_empty()) || contenteditable.equals_ignoring_ascii_case("true"sv))
return ContentEditableState::True;
// "false" maps to the "false" state.
if (contenteditable.equals_ignoring_ascii_case("false"sv))
return ContentEditableState::False;
// Having no such attribute or an invalid value maps to the "inherit" state.
return ContentEditableState::Inherit;
}
bool HTMLElement::is_editable() const
{
switch (content_editable_state()) {
switch (m_content_editable_state) {
case ContentEditableState::True:
return true;
case ContentEditableState::False:
@ -106,7 +93,7 @@ bool HTMLElement::is_editable() const
DeprecatedString HTMLElement::content_editable() const
{
switch (content_editable_state()) {
switch (m_content_editable_state) {
case ContentEditableState::True:
return "true";
case ContentEditableState::False:
@ -242,6 +229,19 @@ void HTMLElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedStr
{
Element::parse_attribute(name, value);
if (name == HTML::AttributeNames::contenteditable) {
if ((!value.is_null() && value.is_empty()) || value.equals_ignoring_ascii_case("true"sv)) {
// "true", an empty string or a missing value map to the "true" state.
m_content_editable_state = ContentEditableState::True;
} else if (value.equals_ignoring_ascii_case("false"sv)) {
// "false" maps to the "false" state.
m_content_editable_state = ContentEditableState::False;
} else {
// Having no such attribute or an invalid value maps to the "inherit" state.
m_content_editable_state = ContentEditableState::Inherit;
}
}
// 1. If namespace is not null, or localName is not the name of an event handler content attribute on element, then return.
// FIXME: Add the namespace part once we support attribute namespaces.
#undef __ENUMERATE
@ -253,6 +253,14 @@ void HTMLElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedStr
#undef __ENUMERATE
}
void HTMLElement::did_remove_attribute(DeprecatedFlyString const& name)
{
Base::did_remove_attribute(name);
if (name == HTML::AttributeNames::contenteditable) {
m_content_editable_state = ContentEditableState::Inherit;
}
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-focus
void HTMLElement::focus()
{

View File

@ -67,6 +67,7 @@ protected:
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override;
virtual void did_remove_attribute(DeprecatedFlyString const& name) override;
virtual void visit_edges(Cell::Visitor&) override;
@ -81,7 +82,7 @@ private:
False,
Inherit,
};
ContentEditableState content_editable_state() const;
ContentEditableState m_content_editable_state { ContentEditableState::Inherit };
JS::GCPtr<DOMStringMap> m_dataset;