mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
Effects (opacity, blend mode, filters) must be applied in the parent's coordinate space, before the element's transform. Previously this was handled by manually switching to the parent's visual context when applying effects at paint time. By adding EffectsData to AccumulatedVisualContext and positioning it before TransformData in the chain, effects are now naturally applied in the correct order during display list replay, eliminating the special case in StackingContext::paint(). For SVG filters that can generate content from empty elements (feFlood, feImage, feTurbulence), a transparent FillRect command is emitted to trigger the filter through the same AVC pipeline.
97 lines
4.6 KiB
C++
97 lines
4.6 KiB
C++
/*
|
|
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
* Copyright (c) 2025, Manuel Zahariev <manuel@duck.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibWeb/CSS/PropertyID.h>
|
|
#include <LibWeb/CSS/StyleInvalidation.h>
|
|
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
|
|
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
|
|
|
namespace Web::CSS {
|
|
|
|
RequiredInvalidationAfterStyleChange compute_property_invalidation(CSS::PropertyID property_id, RefPtr<StyleValue const> const& old_value, RefPtr<StyleValue const> const& new_value)
|
|
{
|
|
RequiredInvalidationAfterStyleChange invalidation;
|
|
|
|
bool const property_value_changed = (old_value || new_value) && ((!old_value || !new_value) || *old_value != *new_value);
|
|
if (!property_value_changed)
|
|
return invalidation;
|
|
|
|
// NOTE: If the computed CSS display, position, content, or content-visibility property changes, we have to rebuild the entire layout tree.
|
|
// In the future, we should figure out ways to rebuild a smaller part of the tree.
|
|
if (AK::first_is_one_of(property_id, CSS::PropertyID::Display, CSS::PropertyID::Position, CSS::PropertyID::Content, CSS::PropertyID::ContentVisibility)) {
|
|
return RequiredInvalidationAfterStyleChange::full();
|
|
}
|
|
|
|
// NOTE: If the text-transform property changes, it may affect layout. Furthermore, since the
|
|
// Layout::TextNode caches the post-transform text, we have to update the layout tree.
|
|
if (property_id == CSS::PropertyID::TextTransform) {
|
|
invalidation.rebuild_layout_tree = true;
|
|
invalidation.relayout = true;
|
|
invalidation.repaint = true;
|
|
return invalidation;
|
|
}
|
|
|
|
// NOTE: If one of the overflow properties change, we rebuild the entire layout tree.
|
|
// This ensures that overflow propagation from root/body to viewport happens correctly.
|
|
// In the future, we can make this invalidation narrower.
|
|
if (property_id == CSS::PropertyID::OverflowX || property_id == CSS::PropertyID::OverflowY) {
|
|
return RequiredInvalidationAfterStyleChange::full();
|
|
}
|
|
|
|
if (AK::first_is_one_of(property_id, CSS::PropertyID::CounterReset, CSS::PropertyID::CounterSet, CSS::PropertyID::CounterIncrement)) {
|
|
invalidation.rebuild_layout_tree = property_value_changed;
|
|
return invalidation;
|
|
}
|
|
|
|
// OPTIMIZATION: Special handling for CSS `visibility`:
|
|
if (property_id == CSS::PropertyID::Visibility) {
|
|
// We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
|
|
if ((old_value && old_value->to_keyword() == CSS::Keyword::Collapse) != (new_value && new_value->to_keyword() == CSS::Keyword::Collapse))
|
|
invalidation.relayout = true;
|
|
// Of course, we still have to repaint on any visibility change.
|
|
invalidation.repaint = true;
|
|
} else if (CSS::property_affects_layout(property_id)) {
|
|
invalidation.relayout = true;
|
|
}
|
|
|
|
if (property_id == CSS::PropertyID::Opacity && old_value && new_value) {
|
|
// OPTIMIZATION: An element creates a stacking context when its opacity changes from 1 to less than 1
|
|
// and stops to create one when opacity returns to 1. So stacking context tree rebuild is
|
|
// not required for opacity changes within the range below 1.
|
|
auto old_value_opacity = old_value->as_number().number();
|
|
auto new_value_opacity = new_value->as_number().number();
|
|
if (old_value_opacity != new_value_opacity && (old_value_opacity == 1 || new_value_opacity == 1)) {
|
|
invalidation.rebuild_stacking_context_tree = true;
|
|
}
|
|
} else if (CSS::property_affects_stacking_context(property_id)) {
|
|
invalidation.rebuild_stacking_context_tree = true;
|
|
}
|
|
invalidation.repaint = true;
|
|
|
|
// Transform, perspective, clip, clip-path, and effects properties require rebuilding AccumulatedVisualContext tree.
|
|
if (AK::first_is_one_of(property_id,
|
|
CSS::PropertyID::Transform,
|
|
CSS::PropertyID::Rotate,
|
|
CSS::PropertyID::Scale,
|
|
CSS::PropertyID::Translate,
|
|
CSS::PropertyID::Perspective,
|
|
CSS::PropertyID::TransformOrigin,
|
|
CSS::PropertyID::PerspectiveOrigin,
|
|
CSS::PropertyID::Clip,
|
|
CSS::PropertyID::ClipPath,
|
|
CSS::PropertyID::Opacity,
|
|
CSS::PropertyID::MixBlendMode,
|
|
CSS::PropertyID::Filter,
|
|
CSS::PropertyID::Isolation)) {
|
|
invalidation.rebuild_accumulated_visual_contexts = true;
|
|
}
|
|
|
|
return invalidation;
|
|
}
|
|
|
|
}
|