Skip to content

Commit 154e9db

Browse files
tcl3gmta
authored andcommitted
LibWeb: Cache the value of Element::lang()
This reduces the time spent in `SelectorEngine::matches_lang_pseudo_class()` from 1.9% to 0.41% on https://cloudflare.com
1 parent 0ec97ea commit 154e9db

File tree

5 files changed

+92
-8
lines changed

5 files changed

+92
-8
lines changed

Libraries/LibWeb/DOM/Element.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3907,6 +3907,11 @@ void Element::attribute_changed(FlyString const& local_name, Optional<String> co
39073907
m_dir = Dir::Auto;
39083908
else
39093909
m_dir = {};
3910+
} else if (local_name == HTML::AttributeNames::lang) {
3911+
for_each_in_inclusive_subtree_of_type<Element>([](auto& element) {
3912+
element.invalidate_lang_value();
3913+
return TraversalDecision::Continue;
3914+
});
39103915
}
39113916

39123917
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:concept-element-attributes-change-ext
@@ -4020,7 +4025,7 @@ void Element::set_counters_set(OwnPtr<CSS::CountersSet>&& counters_set)
40204025
// https://html.spec.whatwg.org/multipage/dom.html#the-lang-and-xml:lang-attributes
40214026
Optional<String> Element::lang() const
40224027
{
4023-
auto attempt_to_determine_lang_attribute = [&]() -> Optional<String> {
4028+
auto determine_lang_attribute = [&]() -> String {
40244029
// 1. If the node is an element that has a lang attribute in the XML namespace set
40254030
// Use the value of that attribute.
40264031
auto maybe_xml_lang = get_attribute_ns(Namespace::XML, HTML::AttributeNames::lang);
@@ -4039,23 +4044,23 @@ Optional<String> Element::lang() const
40394044
// Use the language of that shadow root's host.
40404045
if (auto parent = parent_element()) {
40414046
if (parent->is_shadow_root())
4042-
return parent->shadow_root()->host()->lang();
4047+
return parent->shadow_root()->host()->lang().value_or({});
40434048
}
40444049

40454050
// 4. If the node's parent element is not null
40464051
// Use the language of that parent element.
40474052
if (auto parent = parent_element())
4048-
return parent->lang();
4053+
return parent->lang().value_or({});
40494054

40504055
// 5. Otherwise
40514056
// - If there is a pragma-set default language set, then that is the language of the node.
40524057
if (document().pragma_set_default_language().has_value()) {
4053-
return document().pragma_set_default_language();
4058+
return document().pragma_set_default_language().value_or({});
40544059
}
40554060

40564061
// - If there is no pragma-set default language set, then language information from a higher-level protocol (such as HTTP),
40574062
if (document().http_content_language().has_value()) {
4058-
return document().http_content_language();
4063+
return document().http_content_language().value_or({});
40594064
}
40604065

40614066
// if any, must be used as the final fallback language instead.
@@ -4065,11 +4070,22 @@ Optional<String> Element::lang() const
40654070
return {};
40664071
};
40674072

4073+
if (!m_lang_value.has_value())
4074+
m_lang_value = determine_lang_attribute();
4075+
40684076
// If the resulting value is the empty string, then it must be interpreted as meaning that the language of the node is explicitly unknown.
4069-
auto maybe_lang = attempt_to_determine_lang_attribute();
4070-
if (!maybe_lang.has_value() || maybe_lang->is_empty())
4077+
if (m_lang_value->is_empty())
40714078
return {};
4072-
return maybe_lang.release_value();
4079+
4080+
return m_lang_value;
4081+
}
4082+
4083+
void Element::invalidate_lang_value()
4084+
{
4085+
if (m_lang_value.has_value()) {
4086+
m_lang_value.clear();
4087+
set_needs_style_update(true);
4088+
}
40734089
}
40744090

40754091
template<typename Callback>

Libraries/LibWeb/DOM/Element.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class WEB_API Element
148148
String get_attribute_value(FlyString const& local_name, Optional<FlyString> const& namespace_ = {}) const;
149149

150150
Optional<String> lang() const;
151+
void invalidate_lang_value();
151152

152153
WebIDL::ExceptionOr<void> set_attribute_for_bindings(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, Utf16String> const& value);
153154
WebIDL::ExceptionOr<void> set_attribute_for_bindings(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, String> const& value);
@@ -626,6 +627,8 @@ class WEB_API Element
626627
// https://html.spec.whatwg.org/multipage/grouping-content.html#ordinal-value
627628
Optional<i32> m_ordinal_value;
628629

630+
mutable Optional<String> m_lang_value;
631+
629632
// https://w3c.github.io/webappsec-csp/#is-element-nonceable
630633
// AD-HOC: We need to know the element had a duplicate attribute when it was created from the HTML parser.
631634
// However, there currently isn't any specified way to do this, so we store a flag on the token, which is

Libraries/LibWeb/HTML/HTMLMetaElement.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ void HTMLMetaElement::inserted()
144144
// 9. Set the pragma-set default language to candidate.
145145
auto language = String::from_utf8_without_validation(candidate.bytes());
146146
document().set_pragma_set_default_language(language);
147+
document().document_element()->invalidate_lang_value();
147148
break;
148149
}
149150
case HttpEquivAttributeState::ContentSecurityPolicy: {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<style>
3+
div {
4+
color: green;
5+
}
6+
</style>
7+
<div>This text should be green</div>
8+
<div>This text should also be green</div>
9+
<div>This text should also be green</div>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE html>
2+
<html class="reftest-wait">
3+
<head></head>
4+
<link rel="match" href="../../expected/css/lang-selector-invalidation-ref.html">
5+
<style>
6+
div:lang(en) {
7+
color: red;
8+
}
9+
div:lang(es) {
10+
color: blue;
11+
}
12+
div:lang(eu) {
13+
color: green;
14+
}
15+
html:lang(eu) {
16+
color: green;
17+
}
18+
</style>
19+
<div class="parent" lang="es">
20+
<div class="child">
21+
This text should be green
22+
<div class="grandchild" lang="en">This text should also be green</div>
23+
</div>
24+
</div>
25+
This text should also be green
26+
<script>
27+
28+
window.onload = async () => {
29+
function waitForStyleUpdate() {
30+
return new Promise((resolve) => {
31+
requestAnimationFrame(() => {
32+
requestAnimationFrame(() => {
33+
resolve();
34+
});
35+
});
36+
});
37+
}
38+
const parent = document.querySelector('.parent');
39+
const grandchild = document.querySelector('.grandchild');
40+
41+
await waitForStyleUpdate();
42+
parent.setAttribute("lang", "eu");
43+
await waitForStyleUpdate();
44+
grandchild.removeAttribute("lang");
45+
46+
const metaElement = document.createElement("meta");
47+
metaElement.httpEquiv = "content-language";
48+
metaElement.content = "eu";
49+
document.head.appendChild(metaElement)
50+
await waitForStyleUpdate();
51+
document.documentElement.classList.remove("reftest-wait");
52+
}
53+
54+
</script>
55+
</html>

0 commit comments

Comments
 (0)