mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-22 02:09:24 +00:00
LibWeb: Avoid many style invalidations on DOM attribute mutation
Many times, attribute mutation doesn't necessitate a full style invalidation on the element. However, the conditions are pretty elaborate, so this first version has a lot of false positives. We only need to invalidate style when any of these things apply: 1. The change may affect the match state of a selector somewhere. 2. The change may affect presentational hints applied to the element. For (1) in this first version, we have a fixed list of attribute names that may affect selectors. We also collect all names referenced by attribute selectors anywhere in the document. For (2), we add a new Element::is_presentational_hint() virtual that tells us whether a given attribute name is a presentational hint. This drastically reduces style work on many websites. As an example, https://cnn.com/ is once again browseable.
This commit is contained in:
parent
b11bdd4022
commit
b981e6f7bc
Notes:
github-actions[bot]
2024-12-24 16:18:00 +00:00
Author: https://github.com/awesomekling
Commit: b981e6f7bc
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3034
56 changed files with 377 additions and 37 deletions
|
@ -29,6 +29,17 @@ void SVGCircleElement::initialize(JS::Realm& realm)
|
|||
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGCircleElement);
|
||||
}
|
||||
|
||||
bool SVGCircleElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
return first_is_one_of(name,
|
||||
SVG::AttributeNames::cx,
|
||||
SVG::AttributeNames::cy,
|
||||
SVG::AttributeNames::r);
|
||||
}
|
||||
|
||||
void SVGCircleElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
Base::apply_presentational_hints(cascaded_properties);
|
||||
|
|
|
@ -18,6 +18,7 @@ class SVGCircleElement final : public SVGGeometryElement {
|
|||
public:
|
||||
virtual ~SVGCircleElement() override = default;
|
||||
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
virtual Gfx::Path get_path(CSSPixelSize viewport_size) override;
|
||||
|
|
|
@ -52,6 +52,16 @@ GC::Ptr<Layout::Node> SVGForeignObjectElement::create_layout_node(GC::Ref<CSS::C
|
|||
return heap().allocate<Layout::SVGForeignObjectBox>(document(), *this, move(style));
|
||||
}
|
||||
|
||||
bool SVGForeignObjectElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
return first_is_one_of(name,
|
||||
SVG::AttributeNames::width,
|
||||
SVG::AttributeNames::height);
|
||||
}
|
||||
|
||||
void SVGForeignObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
Base::apply_presentational_hints(cascaded_properties);
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
GC::Ptr<SVG::SVGAnimatedLength> m_x;
|
||||
|
|
|
@ -143,32 +143,40 @@ struct NamedPropertyID {
|
|||
StringView name;
|
||||
};
|
||||
|
||||
static Array const attribute_style_properties {
|
||||
// FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now.
|
||||
NamedPropertyID(CSS::PropertyID::Fill),
|
||||
// FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now.
|
||||
NamedPropertyID(CSS::PropertyID::Stroke),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeDasharray),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeDashoffset),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeLinecap),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeLinejoin),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeMiterlimit),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeWidth),
|
||||
NamedPropertyID(CSS::PropertyID::FillRule),
|
||||
NamedPropertyID(CSS::PropertyID::FillOpacity),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeOpacity),
|
||||
NamedPropertyID(CSS::PropertyID::Opacity),
|
||||
NamedPropertyID(CSS::PropertyID::TextAnchor),
|
||||
NamedPropertyID(CSS::PropertyID::FontSize),
|
||||
NamedPropertyID(CSS::PropertyID::Mask),
|
||||
NamedPropertyID(CSS::PropertyID::MaskType),
|
||||
NamedPropertyID(CSS::PropertyID::ClipPath),
|
||||
NamedPropertyID(CSS::PropertyID::ClipRule),
|
||||
NamedPropertyID(CSS::PropertyID::Display),
|
||||
};
|
||||
|
||||
bool SVGGraphicsElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
return any_of(attribute_style_properties, [&](auto& property) { return name.equals_ignoring_ascii_case(property.name); });
|
||||
}
|
||||
|
||||
void SVGGraphicsElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
static Array const attribute_style_properties {
|
||||
// FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now.
|
||||
NamedPropertyID(CSS::PropertyID::Fill),
|
||||
// FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now.
|
||||
NamedPropertyID(CSS::PropertyID::Stroke),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeDasharray),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeDashoffset),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeLinecap),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeLinejoin),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeMiterlimit),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeWidth),
|
||||
NamedPropertyID(CSS::PropertyID::FillRule),
|
||||
NamedPropertyID(CSS::PropertyID::FillOpacity),
|
||||
NamedPropertyID(CSS::PropertyID::StrokeOpacity),
|
||||
NamedPropertyID(CSS::PropertyID::Opacity),
|
||||
NamedPropertyID(CSS::PropertyID::TextAnchor),
|
||||
NamedPropertyID(CSS::PropertyID::FontSize),
|
||||
NamedPropertyID(CSS::PropertyID::Mask),
|
||||
NamedPropertyID(CSS::PropertyID::MaskType),
|
||||
NamedPropertyID(CSS::PropertyID::ClipPath),
|
||||
NamedPropertyID(CSS::PropertyID::ClipRule),
|
||||
NamedPropertyID(CSS::PropertyID::Display),
|
||||
};
|
||||
|
||||
CSS::Parser::ParsingContext parsing_context { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
|
||||
for_each_attribute([&](auto& name, auto& value) {
|
||||
for (auto property : attribute_style_properties) {
|
||||
|
|
|
@ -29,6 +29,7 @@ class SVGGraphicsElement : public SVGElement {
|
|||
WEB_PLATFORM_OBJECT(SVGGraphicsElement, SVGElement);
|
||||
|
||||
public:
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
||||
|
|
|
@ -79,6 +79,20 @@ RefPtr<CSS::CSSStyleValue> SVGSVGElement::height_style_value_from_attribute() co
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool SVGSVGElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
return first_is_one_of(name,
|
||||
SVG::AttributeNames::x,
|
||||
SVG::AttributeNames::y,
|
||||
SVG::AttributeNames::width,
|
||||
SVG::AttributeNames::height,
|
||||
SVG::AttributeNames::viewBox,
|
||||
SVG::AttributeNames::preserveAspectRatio);
|
||||
}
|
||||
|
||||
void SVGSVGElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
Base::apply_presentational_hints(cascaded_properties);
|
||||
|
|
|
@ -31,6 +31,7 @@ class SVGSVGElement final : public SVGGraphicsElement
|
|||
public:
|
||||
virtual GC::Ptr<Layout::Node> create_layout_node(GC::Ref<CSS::ComputedProperties>) override;
|
||||
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
virtual bool requires_svg_container() const override { return false; }
|
||||
|
|
|
@ -30,6 +30,16 @@ void SVGStopElement::attribute_changed(FlyString const& name, Optional<String> c
|
|||
}
|
||||
}
|
||||
|
||||
bool SVGStopElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
return first_is_one_of(name,
|
||||
"stop-color"sv,
|
||||
"stop-opacity"sv);
|
||||
}
|
||||
|
||||
void SVGStopElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
CSS::Parser::ParsingContext parsing_context { document() };
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
|
||||
GC::Ref<SVGAnimatedNumber> offset() const;
|
||||
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
NumberPercentage stop_offset() const { return m_offset.value_or(NumberPercentage::create_number(0)); }
|
||||
|
|
|
@ -39,11 +39,24 @@ void SVGSymbolElement::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(m_view_box_for_bindings);
|
||||
}
|
||||
|
||||
bool SVGSymbolElement::is_presentational_hint(FlyString const& name) const
|
||||
{
|
||||
if (Base::is_presentational_hint(name))
|
||||
return true;
|
||||
|
||||
// FIXME: This is not a correct use of the presentational hint mechanism.
|
||||
if (is_direct_child_of_use_shadow_tree())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://svgwg.org/svg2-draft/struct.html#SymbolNotes
|
||||
void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
|
||||
{
|
||||
Base::apply_presentational_hints(cascaded_properties);
|
||||
|
||||
// FIXME: This is not a correct use of the presentational hint mechanism.
|
||||
if (is_direct_child_of_use_shadow_tree()) {
|
||||
// The generated instance of a ‘symbol’ that is the direct referenced element of a ‘use’ element must always have a computed value of inline for the display property.
|
||||
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Inline)));
|
||||
|
|
|
@ -19,6 +19,7 @@ class SVGSymbolElement final : public SVGGraphicsElement
|
|||
public:
|
||||
virtual ~SVGSymbolElement() override = default;
|
||||
|
||||
virtual bool is_presentational_hint(FlyString const&) const override;
|
||||
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
|
||||
|
||||
virtual Optional<ViewBox> view_box() const override { return m_view_box; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue