Created attachment 366070 [details] how the shadow looks on Safari 12.1 when values changed Use case: - Add an Element (not square preferably) - Apply a drop-shadow() filter to it - Change the drop shadow values with Javascript Actual: The drop shadow refreshes only in the boundaries of the Element but not outside of it Expected: Should repaint the entire shadow Link: https://codepen.io/tombigel/pen/QoXjEN I created this pen to play with drop shadow so it's a bit of an overkill but if you change any param you will see the issue (see screenshot) Tested on: Does not recreate on Chrome, Firefox, Safari 12.0.3 Recreates on Safari 12.1, Safari Technology Preview 78 Notes: I also encountered a similar bug only on Safari 12.1+ with animating -webkit-clip-path, hadn't had time to create a simple test case for it
<rdar://problem/49387957>
Huh, I could have sworn this worked in the past.
I was thinking of box-shadow repainting. We don't have a code path that ensures correct repainting of layers with out-setting filters, for changes on the renderer with the filter, or descendants. We just get a RepaintLayer RenderStyle diff type, and call renderer.repaint(). Also we just do this in willChangeStyle, so we only ever repaint the "before" state of the filter when the style changes. What we'll need to do before and after the style change is to walk the ancestor RenderLayer chain and ensure that repaint rects get inflated for filters that move pixels.
Update 2021: Still recreates in 14.x Found a viable workaround - using "will-change: filter" On Chrome it breaks the shadow in a weird way so it's a partial workaround because it requires browser detection.
Created attachment 442108 [details] Small testcase
Need something like this: diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp index d7990e430f10a0c962fe11483f9a55d67349431b..bf5946d3de53e610bc24ec3c9b6c848ef3f8b419 100644 --- a/Source/WebCore/rendering/RenderBox.cpp +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -4860,7 +4860,8 @@ void RenderBox::addVisualEffectOverflow() bool hasBoxShadow = style().boxShadow(); bool hasBorderImageOutsets = style().hasBorderImageOutsets(); bool hasOutline = outlineStyleForRepaint().hasOutlineInVisualOverflow(); - if (!hasBoxShadow && !hasBorderImageOutsets && !hasOutline) + bool hasFilter = style().hasFilter(); + if (!hasBoxShadow && !hasBorderImageOutsets && !hasOutline && !hasFilter) return; addVisualOverflow(applyVisualEffectOverflow(borderBoxRect())); @@ -4901,6 +4902,16 @@ LayoutRect RenderBox::applyVisualEffectOverflow(const LayoutRect& borderBox) con overflowMinY = std::min(overflowMinY, borderBox.y() - ((!isFlipped || !isHorizontal) ? borderOutsets.top() : borderOutsets.bottom())); overflowMaxY = std::max(overflowMaxY, borderBox.maxY() + ((!isFlipped || !isHorizontal) ? borderOutsets.bottom() : borderOutsets.top())); } + + if (style().hasFilter()) { + auto filterOutsets = style().filterOutsets(); + + // FIXME: For writing modes. + overflowMinX = std::min(overflowMinX, borderBox.x() - filterOutsets.left()); + overflowMaxX = std::max(overflowMaxX, borderBox.maxX() + filterOutsets.right()); + overflowMinY = std::min(overflowMinY, borderBox.y() - filterOutsets.top()); + overflowMaxY = std::max(overflowMaxY, borderBox.maxY() + filterOutsets.bottom()); + } if (outlineStyleForRepaint().hasOutlineInVisualOverflow()) { LayoutUnit outlineSize { outlineStyleForRepaint().outlineSize() }; diff --git a/Source/WebCore/rendering/style/RenderStyle.cpp b/Source/WebCore/rendering/style/RenderStyle.cpp index 606c99b83dfb71b2682e91412745a6d0c3f6b124..f559b34a8bf0daf73f5ab2a42ff259f3bb55ab42 100644 --- a/Source/WebCore/rendering/style/RenderStyle.cpp +++ b/Source/WebCore/rendering/style/RenderStyle.cpp @@ -643,6 +643,10 @@ inline bool RenderStyle::changeAffectsVisualOverflow(const RenderStyle& other) c return visualOverflowForDecorations(*this, { }) != visualOverflowForDecorations(other, { }); } + // FIXME: Examine filters to determine if outsets changed. + if (filter() != other.filter()) + return true; + if (hasOutlineInVisualOverflow() != other.hasOutlineInVisualOverflow()) return true; return false;