5

I'm trying to draw a huge canvas rectangle on top of the page (some kind of lightbox background), the code is quite straightforward:

var el = document.createElement('canvas');
el.style.position = 'absolute';
el.style.top  = 0;
el.style.left = 0;
el.style.zIndex = 1000;
el.width  = window.innerWidth + window.scrollMaxX;
el.height = window.innerHeight + window.scrollMaxY;

...
document.body.appendChild(el);

// and later

var ctx = el.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fillRect(0, 0, el.width, el.height);

And sometimes (not always) the last line throws:

Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMCanvasRenderingContext2D.fillRect]

I've been guessing if that happens because of image size or because of the content type beneath the canvas (e.g. embeded video playing), but apparently not.

So I'm looking for any ideas on how to isolate and/or solve this issue.

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
nweb
  • 151
  • 1
  • 10

2 Answers2

11

Looking at the nsIDOMCanvasRenderingContext2D.fillRect() implementation (and going through the functions it calls) - there aren't all too many conditions that will return NS_ERROR_FAILURE. It can only happen if either EnsureSurface() or mThebes->CopyPath() fail. And the following two lines in EnsureSurface() are most likely the source of your issue:

// Check that the dimensions are sane
if (gfxASurface::CheckSurfaceSize(gfxIntSize(mWidth, mHeight), 0xffff)) {

What's being checked here:

  • Neither the width nor the height of the canvas can exceed 65535 pixels.
  • The height cannot exceed 32767 pixels on Mac OS X (platform limitation).
  • The size of canvas data (width * height * 4) cannot exceed 2 GB.

If any of these conditions is violated EnsureSurface() will return false and consequently produce the exception you've seen. Note that the above are implementation details that can change at any time, you shouldn't rely on them. But they might give you an idea which particular limit your code violates.

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
  • Unfortunately if breaks with the same error even on really small rectangles. Like ctx.fillRect(0, 0, 100, 100); – nweb Oct 01 '12 at 09:19
  • @nweb: This code doesn't check rectangle size, it checks the size of the canvas... In a canvas exceeding size limits any attempt to draw will fail with a similar exception. – Wladimir Palant Oct 01 '12 at 14:20
  • 1
    Thanks! Finally found the solution to my problem. Indeed it was because of a huge canvas. – nweb Oct 08 '12 at 09:51
  • unbelievable that this is not better documented :-/ – James Cat Jun 23 '16 at 20:23
  • 1
    @JamesCat: These are merely implementation details which are subject to change (and probably changed significantly already in the past four years). In the end, what you get can be summed up as: "size of canvas or individual operands can be subject to limitations, don't expect that large sizes will always work." – Wladimir Palant Jun 28 '16 at 20:54
  • @WladimirPalant yeah ok. I only ended up here as I got an error message (not a particularly helpful one) in firefox. Chrome just carried on as if nothing had happened except it gave me back an array populated with zeros, which made me go back and check everything in the source files pointlessly, I would say 1.5 days passed before I ended up here! – James Cat Jun 30 '16 at 10:06
  • @JamesCat: I'd consider Chrome's behavior a bug, maybe file it under https://bugs.chromium.org/p/chromium/issues/list? There should definitely be an error, ideally a `RangeError` with a helpful message like "Canvas size too large". Unfortunately, the spec doesn't really say what the correct behavior would be here. – Wladimir Palant Jun 30 '16 at 11:23
  • @JamesCat: Actually, Firefox 47 will also fail silently now, at least for the `fillRect()` call in the example here :-( – Wladimir Palant Jun 30 '16 at 12:50
2

You could apply a try-catch logic. Firefox seems to be the only browser which behaves this a bit odd way.

el.width  = window.innerWidth + window.scrollMaxX;
el.height = window.innerHeight + window.scrollMaxY;

// try first to draw something to check that the size is ok
try
{
  var ctx = el.getContext("2d");
  ctx.fillRect(0, 0, 1, 1);
}

// if it fails, decrease the canvas size
catch(err)
{
  el.width = el.width - 1000;
  el.height = el.height - 1000;
}

I haven't found any variable that tells what is the maximum canvas size. It varies from browser to browser and device to device.

The only cross browser method to detect the maximum canvas size seems to be a loop that decreases the canvas size eg. by 100px until it doesn't produce the error. I tested a loop, and it is rather fast way to detect the maximum canvas size. Because other browsers doesn't throw an error when trying to draw on an over sized canvas, it is better to try to draw eg. red rect and read a pixel and check if it is red.

To maximize detection performance:
- While looping, the canvas should be out of the DOM to maximize speed
- Set the starting size to a well known maximum which seems to be 32,767px (SO: Maximum size of a <canvas> element)
- You can make a more intelligent loop which forks the maximum size: something like using first bigger decrease step (eg.1000px) and when an accepted size is reached, tries to increase the size by 500px. If this is accepted size, then increase by 250px and so on. This way the maximum should be found in least amount of trials.

Community
  • 1
  • 1
Timo Kähkönen
  • 11,962
  • 9
  • 71
  • 112