Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Canvas context allocation fails because "Total canvas memory use exce…
…eds the maximum limit"

https://bugs.webkit.org/show_bug.cgi?id=195325
rdar://48609162

Reviewed by Geoff Garen.

Some pages that use a lot of transient canvas memory can hit our
artificial limits even when they are no longer referencing canvases.
This happens because we don't instantly reclaim memory when we
drop the reference, and instead have to wait for garbage collection.
This problem can also continue between page refreshes.

The limit was introduced many many releases ago when devices had
less memory, and it was more common to accidentally jetsam a page
if you used too much canvas memory. After some internal and external
discussion we've decided to remove the canvas limit and just let the page
follow the same memory restrictions as all other Web features.

This might mean more pages crash (jetsam) than break.

* LayoutTests/fast/canvas/canvas-too-large-to-draw-expected.txt: Removed.
* LayoutTests/fast/canvas/canvas-too-large-to-draw.html: Removed.
* Source/WebCore/html/CanvasBase.cpp:
(WebCore::CanvasBase::allocateImageBuffer const):
(WebCore::CanvasBase::maxActivePixelMemory): Deleted.
(WebCore::CanvasBase::setMaxPixelMemoryForTesting): Deleted.
* Source/WebCore/html/CanvasBase.h:
* Source/WebCore/html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::createContext2d):
* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::resetToConsistentState):
(WebCore::Internals::avoidIOSurfaceSizeCheckInWebProcess):
(WebCore::Internals::setMaxCanvasPixelMemory): Deleted.
* Source/WebCore/testing/Internals.idl:
* Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp:
(WebKit::RemoteRenderingBackend::getPixelBufferForImageBufferWithNewMemory):

Canonical link: https://commits.webkit.org/265628@main
  • Loading branch information
grorg committed Jun 29, 2023
1 parent 3f9ec43 commit 6bd11f3
Show file tree
Hide file tree
Showing 14 changed files with 3 additions and 162 deletions.
4 changes: 1 addition & 3 deletions LayoutTests/fast/canvas/canvas-crash.html
Expand Up @@ -11,10 +11,8 @@

function canvastest()
{
if (window.internals) {
window.internals.setMaxCanvasPixelMemory(16384 * 16384 * 4);
if (window.internals)
window.internals.setMaxCanvasArea(13951 * 11138);
}
var ctx = document.getCSSCanvasContext("2d", "canvastest", 13951, 11138);
ctx.putImageData(ctx.getImageData(1431655766, document.getElementById("a").appendChild(document.createElement("media")).clientWidth, 4096, -1024), 128, -65535, 127, -2147483648, 2147483647, -2147483648);

Expand Down
4 changes: 1 addition & 3 deletions LayoutTests/fast/canvas/canvas-skia-excessive-size.html
Expand Up @@ -11,10 +11,8 @@
if (window.testRunner)
testRunner.dumpAsText();

if (window.internals) {
window.internals.setMaxCanvasPixelMemory(16384 * 16384 * 4);
if (window.internals)
window.internals.setMaxCanvasArea(134217728);
}

var canvas = document.getElementById("bigCanvas");
var width = canvas.width;
Expand Down
4 changes: 0 additions & 4 deletions LayoutTests/fast/canvas/canvas-too-large-to-draw-expected.txt

This file was deleted.

50 changes: 0 additions & 50 deletions LayoutTests/fast/canvas/canvas-too-large-to-draw.html

This file was deleted.

4 changes: 1 addition & 3 deletions LayoutTests/fast/canvas/large-getImageData.html
Expand Up @@ -8,10 +8,8 @@
var width = 4096;
var height = 4097;

if (window.internals) {
window.internals.setMaxCanvasPixelMemory(width * height * 4);
if (window.internals)
window.internals.setMaxCanvasArea(width * height);
}

var canvas = document.createElement('canvas');
canvas.width = width;
Expand Down
1 change: 0 additions & 1 deletion LayoutTests/platform/wincairo/TestExpectations
Expand Up @@ -2257,7 +2257,6 @@ webgl/2.0.0/conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html
webgl/2.0.0/conformance2/state/gl-object-get-calls.html [ Skip ] # Timeout Failure Pass
webgl/2.0.0/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html [ Skip ] # Timeout
webgl/2.0.0/conformance2/transform_feedback/transform_feedback.html [ Skip ] # Timeout and flaky
webgl/webgl-oom-paint-document-no-crash.html [ Skip ]
workers/bomb.html [ Skip ] # Timeout

# This test is crashing on Buildbot. Skipping this test makes the subsequent test crash. Not sure which preceding test is the root cause.
Expand Down
16 changes: 0 additions & 16 deletions LayoutTests/webgl/webgl-oom-paint-document-no-crash-expected.html

This file was deleted.

31 changes: 0 additions & 31 deletions LayoutTests/webgl/webgl-oom-paint-document-no-crash.html

This file was deleted.

32 changes: 0 additions & 32 deletions Source/WebCore/html/CanvasBase.cpp
Expand Up @@ -61,7 +61,6 @@ const InterpolationQuality defaultInterpolationQuality = InterpolationQuality::D
#endif

static std::optional<size_t> maxCanvasAreaForTesting;
static std::optional<size_t> maxActivePixelMemoryForTesting;

CanvasBase::CanvasBase(IntSize size, const std::optional<NoiseInjectionHashSalt>& noiseHashSalt)
: m_size(size)
Expand Down Expand Up @@ -138,29 +137,6 @@ size_t CanvasBase::externalMemoryCost() const
return m_imageBuffer->externalMemoryCost();
}

size_t CanvasBase::maxActivePixelMemory()
{
if (maxActivePixelMemoryForTesting)
return *maxActivePixelMemoryForTesting;

static size_t maxPixelMemory;
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
#if PLATFORM(IOS_FAMILY)
maxPixelMemory = ramSize() / 4;
#else
maxPixelMemory = std::max(ramSize() / 4, 2151 * MB);
#endif
});

return maxPixelMemory;
}

void CanvasBase::setMaxPixelMemoryForTesting(std::optional<size_t> size)
{
maxActivePixelMemoryForTesting = size;
}

static inline size_t maxCanvasArea()
{
if (maxCanvasAreaForTesting)
Expand Down Expand Up @@ -349,14 +325,6 @@ RefPtr<ImageBuffer> CanvasBase::allocateImageBuffer(bool usesDisplayListDrawing,
return nullptr;
}

// Make sure we don't use more pixel memory than the system can support.
auto checkedRequestedPixelMemory = (4 * checkedArea) + activePixelMemory();
if (checkedRequestedPixelMemory.hasOverflowed() || checkedRequestedPixelMemory > maxActivePixelMemory()) {
auto message = makeString("Total canvas memory use exceeds the maximum limit (", maxActivePixelMemory() / 1024 / 1024, " MB).");
scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, message);
return nullptr;
}

unsigned area = checkedArea.value();
if (!area)
return nullptr;
Expand Down
2 changes: 0 additions & 2 deletions Source/WebCore/html/CanvasBase.h
Expand Up @@ -125,8 +125,6 @@ class CanvasBase {
bool shouldAccelerate(const IntSize&) const;
bool shouldAccelerate(unsigned area) const;

WEBCORE_EXPORT static size_t maxActivePixelMemory();
WEBCORE_EXPORT static void setMaxPixelMemoryForTesting(std::optional<size_t>);
WEBCORE_EXPORT static void setMaxCanvasAreaForTesting(std::optional<size_t>);

virtual void queueTaskKeepingObjectAlive(TaskSource, Function<void()>&&) = 0;
Expand Down
8 changes: 0 additions & 8 deletions Source/WebCore/html/HTMLCanvasElement.cpp
Expand Up @@ -347,14 +347,6 @@ CanvasRenderingContext2D* HTMLCanvasElement::createContext2d(const String& type,
ASSERT_UNUSED(HTMLCanvasElement::is2dType(type), type);
ASSERT(!m_context);

// Make sure we don't use more pixel memory than the system can support.
size_t requestedPixelMemory = 4 * width() * height();
if (activePixelMemory() + requestedPixelMemory > maxActivePixelMemory()) {
auto message = makeString("Total canvas memory use exceeds the maximum limit (", maxActivePixelMemory() / 1024 / 1024, " MB).");
document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, message);
return nullptr;
}

m_context = CanvasRenderingContext2D::create(*this, WTFMove(settings), document().inQuirksMode());

#if USE(IOSURFACE_CANVAS_BACKING_STORE)
Expand Down
7 changes: 0 additions & 7 deletions Source/WebCore/testing/Internals.cpp
Expand Up @@ -651,7 +651,6 @@ void Internals::resetToConsistentState(Page& page)
WebCore::MediaRecorder::setCustomPrivateRecorderCreator(nullptr);
#endif

CanvasBase::setMaxPixelMemoryForTesting(std::nullopt);
CanvasBase::setMaxCanvasAreaForTesting(std::nullopt);
LocalDOMWindow::overrideTransientActivationDurationForTesting(std::nullopt);

Expand Down Expand Up @@ -6507,11 +6506,6 @@ void Internals::setMockWebAuthenticationConfiguration(const MockWebAuthenticatio
}
#endif

void Internals::setMaxCanvasPixelMemory(unsigned size)
{
CanvasBase::setMaxPixelMemoryForTesting(size);
}

void Internals::setMaxCanvasArea(unsigned size)
{
CanvasBase::setMaxCanvasAreaForTesting(size);
Expand Down Expand Up @@ -7077,7 +7071,6 @@ void Internals::avoidIOSurfaceSizeCheckInWebProcess(HTMLCanvasElement& element)
return;
page->settings().setMaximumAccelerated2dCanvasSize(UINT_MAX);
CanvasBase::setMaxCanvasAreaForTesting(UINT_MAX);
CanvasBase::setMaxPixelMemoryForTesting(UINT_MAX);
element.setAvoidIOSurfaceSizeCheckInWebProcessForTesting();
}

Expand Down
1 change: 0 additions & 1 deletion Source/WebCore/testing/Internals.idl
Expand Up @@ -1068,7 +1068,6 @@ typedef (FetchRequest or FetchResponse) FetchObject;

undefined markContextAsInsecure();

undefined setMaxCanvasPixelMemory(unsigned long size);
undefined setMaxCanvasArea(unsigned long size);

[Conditional=VIDEO] readonly attribute NowPlayingState nowPlayingState;
Expand Down
Expand Up @@ -279,7 +279,6 @@ void RemoteRenderingBackend::getPixelBufferForImageBufferWithNewMemory(Rendering
m_getPixelBufferSharedMemory = nullptr;
auto sharedMemory = WebKit::SharedMemory::map(WTFMove(handle), WebKit::SharedMemory::Protection::ReadWrite);
MESSAGE_CHECK(sharedMemory, "Shared memory could not be mapped.");
MESSAGE_CHECK(sharedMemory->size() <= HTMLCanvasElement::maxActivePixelMemory(), "Shared memory too big.");
m_getPixelBufferSharedMemory = WTFMove(sharedMemory);
getPixelBufferForImageBuffer(imageBuffer, WTFMove(destinationFormat), WTFMove(srcRect), WTFMove(completionHandler));
}
Expand Down

0 comments on commit 6bd11f3

Please sign in to comment.