Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: c5d6db573430acbf012085a24fb2368845e8158b
https://github.com/WebKit/WebKit/commit/c5d6db573430acbf012085a24fb2368845e8158b
Author: Kiet Ho <[email protected]>
Date: 2026-07-20 (Mon, 20 Jul 2026)
Changed paths:
A
LayoutTests/fast/canvas/canvas-filtered-drawing-after-stack-exhaustion-expected.html
A
LayoutTests/fast/canvas/canvas-filtered-drawing-after-stack-exhaustion.html
M Source/WebCore/html/canvas/CanvasFilterContextSwitcher.cpp
M Source/WebCore/html/canvas/CanvasFilterContextSwitcher.h
Log Message:
-----------
CanvasRenderingContext2D: filtered drawing should not need to save states
rdar://181410087
https://bugs.webkit.org/show_bug.cgi?id=319036
Reviewed by Simon Fraser.
CanvasRenderingContext2D limits the amount of state on the stack to MaxStackSize
(currently 16384). If the limit is reached, save() is a no-op and doesn't push
new
states onto the stack. This is fine for most code, however it'll cause trouble
for
CanvasFilterContextSwitcher as it requires a state save to change
targetSwitcher:
std::unique_ptr<CanvasFilterContextSwitcher>
CanvasFilterContextSwitcher::create(CanvasRenderingContext2DBase& context,
const FloatRect& bounds)
{
auto filter = [...];
auto filterSwitcher = makeUnique<CanvasFilterContextSwitcher>(context);
<-- (a)
auto targetSwitcher = CanvasLayerContextSwitcher::create(context, bounds,
WTF::move(filter)); <-- (d)
if (!targetSwitcher)
return nullptr;
context.modifiableState().targetSwitcher = WTF::move(targetSwitcher);
<-- (c)
return filterSwitcher;
}
CanvasFilterContextSwitcher::CanvasFilterContextSwitcher(CanvasRenderingContext2DBase&
context)
: m_context(context)
{
// (b)
context.save();
context.realizeSaves();
}
RefPtr<CanvasLayerContextSwitcher>
CanvasLayerContextSwitcher::create(CanvasRenderingContext2DBase& context, const
FloatRect& bounds, RefPtr<Filter>&& filter)
{
ASSERT(!bounds.isEmpty());
auto* effectiveDrawingContext = context.effectiveDrawingContext(); <-- (e)
if (!effectiveDrawingContext)
return nullptr;
auto targetSwitcher =
GraphicsContextSwitcher::create(*effectiveDrawingContext, bounds,
context.colorSpace(), WTF::move(filter));
if (!targetSwitcher)
return nullptr;
return adoptRef(*new CanvasLayerContextSwitcher(context, bounds,
WTF::move(targetSwitcher)));
}
When the CanvasFilterContextSwitcher is created (a), it saves the current state
(b),
so it can change targetSwitcher later (c). But if the state stack is exhausted,
then
no states are pushed on the stack. In that case, the CanvasLayerContextSwitcher
created in (d) holds the drawing context (e) from the current
effectiveDrawingContext,
aka the drawing context from the targetSwitcher at the top of the stack. Then
(c)
overwrites the top targetSwitcher with the newly created one in (d). If the old
targetSwitcher is only kept alive by the top state, then overwriting it would
free it.
But the new targetSwitcher holds a reference to the drawingContext of the
targetSwitcher
that we just freed.
This patch fixes this by eliminating the state save. Instead,
CanvasFilterContextSwitcher
holds onto the old targetSwitcher in top of stack when it's created, then
replace it with
the new one. On destruction, it'll put the old one back. This ensures filtered
drawing
would work regardless of stack exhaustion.
Test: fast/canvas/canvas-filtered-drawing-after-stack-exhaustion.html
*
LayoutTests/fast/canvas/canvas-filtered-drawing-after-stack-exhaustion-expected.html:
Added.
* LayoutTests/fast/canvas/canvas-filtered-drawing-after-stack-exhaustion.html:
Added.
* Source/WebCore/html/canvas/CanvasFilterContextSwitcher.cpp:
(WebCore::CanvasFilterContextSwitcher::create):
(WebCore::CanvasFilterContextSwitcher::CanvasFilterContextSwitcher):
(WebCore::CanvasFilterContextSwitcher::~CanvasFilterContextSwitcher):
* Source/WebCore/html/canvas/CanvasFilterContextSwitcher.h:
Canonical link: https://commits.webkit.org/317546@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications