mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-22 09:12:13 -05:00
LibWeb: Expand invalidation sets usage to any attribute change
Before this change invalidation sets were only used for "class" and "id" attribute changes.
This commit is contained in:
parent
039e0d478a
commit
34bf833a0a
Notes:
github-actions[bot]
2025-01-19 18:55:38 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/34bf833a0a4 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3292 Reviewed-by: https://github.com/awesomekling
6 changed files with 91 additions and 43 deletions
|
@ -37,7 +37,11 @@ namespace AK {
|
|||
|
||||
unsigned Traits<Web::CSS::InvalidationSet::Property>::hash(Web::CSS::InvalidationSet::Property const& invalidation_set_property)
|
||||
{
|
||||
return pair_int_hash(to_underlying(invalidation_set_property.type), invalidation_set_property.name.hash());
|
||||
auto value_hash = invalidation_set_property.value.visit(
|
||||
[](FlyString const& value) -> int { return value.hash(); },
|
||||
[](Web::CSS::PseudoClass const& value) -> int { return to_underlying(value); },
|
||||
[](Empty) -> int { return 0; });
|
||||
return pair_int_hash(to_underlying(invalidation_set_property.type), value_hash);
|
||||
}
|
||||
|
||||
ErrorOr<void> Formatter<Web::CSS::InvalidationSet::Property>::format(FormatBuilder& builder, Web::CSS::InvalidationSet::Property const& invalidation_set_property)
|
||||
|
@ -49,24 +53,29 @@ ErrorOr<void> Formatter<Web::CSS::InvalidationSet::Property>::format(FormatBuild
|
|||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::Class: {
|
||||
TRY(builder.put_string("."sv));
|
||||
TRY(builder.put_string(invalidation_set_property.name));
|
||||
TRY(builder.put_string(invalidation_set_property.name()));
|
||||
return {};
|
||||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::Id: {
|
||||
TRY(builder.put_string("#"sv));
|
||||
TRY(builder.put_string(invalidation_set_property.name));
|
||||
TRY(builder.put_string(invalidation_set_property.name()));
|
||||
return {};
|
||||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::TagName: {
|
||||
TRY(builder.put_string(invalidation_set_property.name));
|
||||
TRY(builder.put_string(invalidation_set_property.name()));
|
||||
return {};
|
||||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::Attribute: {
|
||||
TRY(builder.put_string("["sv));
|
||||
TRY(builder.put_string(invalidation_set_property.name));
|
||||
TRY(builder.put_string(invalidation_set_property.name()));
|
||||
TRY(builder.put_string("]"sv));
|
||||
return {};
|
||||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::PseudoClass: {
|
||||
TRY(builder.put_string(":"sv));
|
||||
TRY(builder.put_string(pseudo_class_name(invalidation_set_property.value.get<Web::CSS::PseudoClass>())));
|
||||
return {};
|
||||
}
|
||||
case Web::CSS::InvalidationSet::Property::Type::InvalidateWholeSubtree: {
|
||||
TRY(builder.put_string("*"sv));
|
||||
return {};
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/Forward.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Traits.h>
|
||||
#include <LibWeb/CSS/PseudoClass.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
|
@ -24,10 +25,13 @@ public:
|
|||
Id,
|
||||
TagName,
|
||||
Attribute,
|
||||
PseudoClass,
|
||||
};
|
||||
|
||||
Type type;
|
||||
FlyString name {};
|
||||
Variant<FlyString, PseudoClass, Empty> value { Empty {} };
|
||||
|
||||
FlyString const& name() const { return value.get<FlyString>(); }
|
||||
|
||||
bool operator==(Property const& other) const = default;
|
||||
};
|
||||
|
@ -44,6 +48,7 @@ public:
|
|||
void set_needs_invalidate_id(FlyString const& name) { m_properties.set({ Property::Type::Id, name }); }
|
||||
void set_needs_invalidate_tag_name(FlyString const& name) { m_properties.set({ Property::Type::TagName, name }); }
|
||||
void set_needs_invalidate_attribute(FlyString const& name) { m_properties.set({ Property::Type::Attribute, name }); }
|
||||
void set_needs_invalidate_pseudo_class(PseudoClass pseudo_class) { m_properties.set({ Property::Type::PseudoClass, pseudo_class }); }
|
||||
|
||||
bool is_empty() const;
|
||||
void for_each_property(Function<void(Property const&)> const& callback) const;
|
||||
|
|
|
@ -447,19 +447,23 @@ bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet::
|
|||
return true;
|
||||
switch (property.type) {
|
||||
case InvalidationSet::Property::Type::Id:
|
||||
if (m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name))
|
||||
if (m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::Class:
|
||||
if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name))
|
||||
if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::Attribute:
|
||||
if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name))
|
||||
if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::TagName:
|
||||
if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name))
|
||||
if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name()))
|
||||
return true;
|
||||
break;
|
||||
case InvalidationSet::Property::Type::PseudoClass:
|
||||
if (m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get<PseudoClass>()))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -66,6 +66,16 @@ static void collect_properties_used_in_has(Selector::SimpleSelector const& selec
|
|||
}
|
||||
case Selector::SimpleSelector::Type::PseudoClass: {
|
||||
auto const& pseudo_class = selector.pseudo_class();
|
||||
switch (pseudo_class.type) {
|
||||
case PseudoClass::Enabled:
|
||||
case PseudoClass::Disabled:
|
||||
case PseudoClass::PlaceholderShown:
|
||||
case PseudoClass::Checked:
|
||||
style_invalidation_data.pseudo_classes_used_in_has_selectors.set(pseudo_class.type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto const& child_selector : pseudo_class.argument_selector_list) {
|
||||
for (auto const& compound_selector : child_selector->compound_selectors()) {
|
||||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||||
|
@ -102,6 +112,16 @@ static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector
|
|||
break;
|
||||
case Selector::SimpleSelector::Type::PseudoClass: {
|
||||
auto const& pseudo_class = selector.pseudo_class();
|
||||
switch (pseudo_class.type) {
|
||||
case PseudoClass::Enabled:
|
||||
case PseudoClass::Disabled:
|
||||
case PseudoClass::PlaceholderShown:
|
||||
case PseudoClass::Checked:
|
||||
invalidation_set.set_needs_invalidate_pseudo_class(pseudo_class.type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (pseudo_class.type == PseudoClass::Has)
|
||||
break;
|
||||
if (exclude_properties_nested_in_not_pseudo_class == ExcludePropertiesNestedInNotPseudoClass::Yes && pseudo_class.type == PseudoClass::Not)
|
||||
|
|
|
@ -18,6 +18,7 @@ struct StyleInvalidationData {
|
|||
HashTable<FlyString> class_names_used_in_has_selectors;
|
||||
HashTable<FlyString> attribute_names_used_in_has_selectors;
|
||||
HashTable<FlyString> tag_names_used_in_has_selectors;
|
||||
HashTable<PseudoClass> pseudo_classes_used_in_has_selectors;
|
||||
|
||||
InvalidationSet build_invalidation_sets_for_selector(Selector const& selector);
|
||||
};
|
||||
|
|
|
@ -1146,15 +1146,32 @@ bool Element::affected_by_invalidation_property(CSS::InvalidationSet::Property c
|
|||
{
|
||||
switch (property.type) {
|
||||
case CSS::InvalidationSet::Property::Type::Class:
|
||||
return m_classes.contains_slow(property.name);
|
||||
return m_classes.contains_slow(property.name());
|
||||
case CSS::InvalidationSet::Property::Type::Id:
|
||||
return m_id == property.name;
|
||||
return m_id == property.name();
|
||||
case CSS::InvalidationSet::Property::Type::TagName:
|
||||
return local_name() == property.name;
|
||||
return local_name() == property.name();
|
||||
case CSS::InvalidationSet::Property::Type::Attribute: {
|
||||
if (property.name == HTML::AttributeNames::id || property.name == HTML::AttributeNames::class_)
|
||||
if (property.name() == HTML::AttributeNames::id || property.name() == HTML::AttributeNames::class_)
|
||||
return true;
|
||||
return has_attribute(property.name);
|
||||
return has_attribute(property.name());
|
||||
}
|
||||
case CSS::InvalidationSet::Property::Type::PseudoClass: {
|
||||
switch (property.value.get<CSS::PseudoClass>()) {
|
||||
case CSS::PseudoClass::Enabled: {
|
||||
return (is<HTML::HTMLButtonElement>(*this) || is<HTML::HTMLInputElement>(*this) || is<HTML::HTMLSelectElement>(*this) || is<HTML::HTMLTextAreaElement>(*this) || is<HTML::HTMLOptGroupElement>(*this) || is<HTML::HTMLOptionElement>(*this) || is<HTML::HTMLFieldSetElement>(*this))
|
||||
&& !is_actually_disabled();
|
||||
}
|
||||
case CSS::PseudoClass::Disabled: {
|
||||
return is_actually_disabled();
|
||||
}
|
||||
case CSS::PseudoClass::Checked: {
|
||||
// FIXME: This could be narrowed down to return true only if element is actually checked.
|
||||
return is<HTML::HTMLInputElement>(*this) || is<HTML::HTMLOptionElement>(*this);
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
case CSS::InvalidationSet::Property::Type::InvalidateSelf:
|
||||
return false;
|
||||
|
@ -1951,26 +1968,6 @@ ErrorOr<void> Element::scroll_into_view(Optional<Variant<bool, ScrollIntoViewOpt
|
|||
// FIXME: 8. Optionally perform some other action that brings the element to the user’s attention.
|
||||
}
|
||||
|
||||
static bool attribute_name_may_affect_selectors(Element const& element, FlyString const& attribute_name)
|
||||
{
|
||||
// FIXME: We could make these cases more narrow by making the conditions more elaborate.
|
||||
if (attribute_name == HTML::AttributeNames::id
|
||||
|| attribute_name == HTML::AttributeNames::class_
|
||||
|| attribute_name == HTML::AttributeNames::dir
|
||||
|| attribute_name == HTML::AttributeNames::lang
|
||||
|| attribute_name == HTML::AttributeNames::checked
|
||||
|| attribute_name == HTML::AttributeNames::disabled
|
||||
|| attribute_name == HTML::AttributeNames::readonly
|
||||
|| attribute_name == HTML::AttributeNames::switch_
|
||||
|| attribute_name == HTML::AttributeNames::href
|
||||
|| attribute_name == HTML::AttributeNames::open
|
||||
|| attribute_name == HTML::AttributeNames::placeholder) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return element.document().style_computer().has_attribute_selector(attribute_name);
|
||||
}
|
||||
|
||||
void Element::invalidate_style_after_attribute_change(FlyString const& attribute_name, Optional<String> const& old_value, Optional<String> const& new_value)
|
||||
{
|
||||
// FIXME: Only invalidate if the attribute can actually affect style.
|
||||
|
@ -2003,15 +2000,15 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute
|
|||
Vector<CSS::InvalidationSet::Property> changed_properties;
|
||||
for (auto& old_class : old_classes) {
|
||||
if (!new_classes.contains_slow(old_class)) {
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .name = FlyString::from_utf8_without_validation(old_class.bytes()) });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .value = FlyString::from_utf8_without_validation(old_class.bytes()) });
|
||||
}
|
||||
}
|
||||
for (auto& new_class : new_classes) {
|
||||
if (!old_classes.contains_slow(new_class)) {
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .name = FlyString::from_utf8_without_validation(new_class.bytes()) });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .value = FlyString::from_utf8_without_validation(new_class.bytes()) });
|
||||
}
|
||||
}
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .name = HTML::AttributeNames::class_ });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = HTML::AttributeNames::class_ });
|
||||
invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties);
|
||||
return;
|
||||
}
|
||||
|
@ -2019,19 +2016,31 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute
|
|||
if (attribute_name == HTML::AttributeNames::id) {
|
||||
Vector<CSS::InvalidationSet::Property> changed_properties;
|
||||
if (old_value.has_value())
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .name = old_value.value() });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .value = FlyString(old_value.value()) });
|
||||
if (new_value.has_value())
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .name = new_value.value() });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .name = HTML::AttributeNames::id });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .value = FlyString(new_value.value()) });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = HTML::AttributeNames::id });
|
||||
invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_presentational_hint(attribute_name)
|
||||
|| attribute_name_may_affect_selectors(*this, attribute_name)) {
|
||||
Vector<CSS::InvalidationSet::Property> changed_properties;
|
||||
if (attribute_name == HTML::AttributeNames::disabled) {
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Disabled });
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Enabled });
|
||||
} else if (attribute_name == HTML::AttributeNames::placeholder) {
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::PlaceholderShown });
|
||||
} else if (attribute_name == HTML::AttributeNames::value) {
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Checked });
|
||||
}
|
||||
|
||||
if (is_presentational_hint(attribute_name)) {
|
||||
invalidate_style(StyleInvalidationReason::ElementAttributeChange);
|
||||
return;
|
||||
}
|
||||
|
||||
changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = attribute_name });
|
||||
invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties);
|
||||
}
|
||||
|
||||
bool Element::is_hidden() const
|
||||
|
|
Loading…
Reference in a new issue