mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 20:45:14 +00:00
LibWeb: Check for overflow when creating ImageData
We would overwise crash on overflow.
This commit is contained in:
parent
25c067872c
commit
3b04c983f1
Notes:
github-actions[bot]
2024-11-14 00:24:16 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/3b04c983f14 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2325
5 changed files with 487 additions and 1 deletions
|
@ -29,7 +29,15 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::create(JS::Realm& re
|
|||
|
||||
// 2. Initialize this given sw, sh, and settings set to settings.
|
||||
// 3. Initialize the image data of this to transparent black.
|
||||
auto data = TRY(JS::Uint8ClampedArray::create(realm, sw * sh * 4));
|
||||
//
|
||||
// If the Canvas Pixel ArrayBuffer cannot be allocated, then rethrow the RangeError thrown by JavaScript, and return.
|
||||
Checked<u32> size = sw;
|
||||
size *= sh;
|
||||
size *= sizeof(u32);
|
||||
if (size.has_overflow())
|
||||
return WebIDL::IndexSizeError::create(realm, "The specified image size could not created"_string);
|
||||
|
||||
auto data = TRY(JS::Uint8ClampedArray::create(realm, size.value()));
|
||||
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(sw, sh), sw * sizeof(u32), data->data().data()));
|
||||
|
||||
return realm.create<ImageData>(realm, bitmap, data);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
Summary
|
||||
|
||||
Harness status: OK
|
||||
|
||||
Rerun
|
||||
|
||||
Found 1 tests
|
||||
|
||||
1 Pass
|
||||
Details
|
||||
Result Test Name MessagePass Testing different type of ImageData constructor
|
|
@ -0,0 +1,112 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Canvas test: 2d.imageData.object.ctor.basics</title>
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../../html/canvas/resources/canvas-tests.js"></script>
|
||||
<link rel="stylesheet" href="../../../../html/canvas/resources/canvas-tests.css">
|
||||
<body class="show_output">
|
||||
|
||||
<h1>2d.imageData.object.ctor.basics</h1>
|
||||
<p class="desc">Testing different type of ImageData constructor</p>
|
||||
|
||||
|
||||
<p class="output">Actual output:</p>
|
||||
<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
|
||||
|
||||
<ul id="d"></ul>
|
||||
<script>
|
||||
var t = async_test("Testing different type of ImageData constructor");
|
||||
_addTest(function(canvas, ctx) {
|
||||
|
||||
function setRGBA(imageData, i, rgba)
|
||||
{
|
||||
var s = i * 4;
|
||||
imageData[s] = rgba[0];
|
||||
imageData[s + 1] = rgba[1];
|
||||
imageData[s + 2] = rgba[2];
|
||||
imageData[s + 3] = rgba[3];
|
||||
}
|
||||
|
||||
function getRGBA(imageData, i)
|
||||
{
|
||||
var result = [];
|
||||
var s = i * 4;
|
||||
for (var j = 0; j < 4; j++) {
|
||||
result[j] = imageData[s + j];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function assertArrayEquals(actual, expected)
|
||||
{
|
||||
_assertSame(typeof actual, "object", "typeof actual", "\"object\"");
|
||||
_assertDifferent(actual, null, "actual", "null");
|
||||
_assertSame("length" in actual, true, "\"length\" in actual", "true");
|
||||
_assertSame(actual.length, expected.length, "actual.length", "expected.length");
|
||||
for (var i = 0; i < actual.length; i++) {
|
||||
_assertSame(actual.hasOwnProperty(i), expected.hasOwnProperty(i), "actual.hasOwnProperty(i)", "expected.hasOwnProperty(i)");
|
||||
_assertSame(actual[i], expected[i], "actual[\""+(i)+"\"]", "expected[\""+(i)+"\"]");
|
||||
}
|
||||
}
|
||||
|
||||
_assertDifferent(ImageData, undefined, "ImageData", "undefined");
|
||||
imageData = new ImageData(100, 50);
|
||||
|
||||
_assertDifferent(imageData, null, "imageData", "null");
|
||||
_assertDifferent(imageData.data, null, "imageData.data", "null");
|
||||
_assertSame(imageData.width, 100, "imageData.width", "100");
|
||||
_assertSame(imageData.height, 50, "imageData.height", "50");
|
||||
assertArrayEquals(getRGBA(imageData.data, 4), [0, 0, 0, 0]);
|
||||
|
||||
var testColor = [0, 255, 255, 128];
|
||||
setRGBA(imageData.data, 4, testColor);
|
||||
assertArrayEquals(getRGBA(imageData.data, 4), testColor);
|
||||
|
||||
assert_throws_js(TypeError, function() { ImageData(1, 1); });
|
||||
assert_throws_js(TypeError, function() { new ImageData(10); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(0, 10); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(10, 0); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData('width', 'height'); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(1 << 31, 1 << 31); });
|
||||
assert_throws_js(TypeError, function() { new ImageData(new Uint8ClampedArray(0)); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(new Uint8Array(100), 25); });
|
||||
assert_throws_dom("INVALID_STATE_ERR", function() { new ImageData(new Uint8ClampedArray(27), 2); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(new Uint8ClampedArray(28), 7, 0); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(new Uint8ClampedArray(104), 14); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(new Uint8ClampedArray([12, 34, 168, 65328]), 1, 151); });
|
||||
assert_throws_js(TypeError, function() { new ImageData(self, 4, 4); });
|
||||
assert_throws_js(TypeError, function() { new ImageData(null, 4, 4); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(imageData.data, 0); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(imageData.data, 13); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(imageData.data, 1 << 31); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(imageData.data, 'biggish'); });
|
||||
assert_throws_dom("INDEX_SIZE_ERR", function() { new ImageData(imageData.data, 1 << 24, 1 << 31); });
|
||||
_assertSame(new ImageData(new Uint8ClampedArray(28), 7).height, 1, "new ImageData(new Uint8ClampedArray(28), 7).height", "1");
|
||||
|
||||
imageDataFromData = new ImageData(imageData.data, 100);
|
||||
_assertSame(imageDataFromData.width, 100, "imageDataFromData.width", "100");
|
||||
_assertSame(imageDataFromData.height, 50, "imageDataFromData.height", "50");
|
||||
_assertSame(imageDataFromData.data, imageData.data, "imageDataFromData.data", "imageData.data");
|
||||
assertArrayEquals(getRGBA(imageDataFromData.data, 10), getRGBA(imageData.data, 10));
|
||||
setRGBA(imageData.data, 10, testColor);
|
||||
assertArrayEquals(getRGBA(imageDataFromData.data, 10), getRGBA(imageData.data, 10));
|
||||
|
||||
var data = new Uint8ClampedArray(400);
|
||||
data[22] = 129;
|
||||
imageDataFromData = new ImageData(data, 20, 5);
|
||||
_assertSame(imageDataFromData.width, 20, "imageDataFromData.width", "20");
|
||||
_assertSame(imageDataFromData.height, 5, "imageDataFromData.height", "5");
|
||||
_assertSame(imageDataFromData.data, data, "imageDataFromData.data", "data");
|
||||
assertArrayEquals(getRGBA(imageDataFromData.data, 2), getRGBA(data, 2));
|
||||
setRGBA(imageDataFromData.data, 2, testColor);
|
||||
assertArrayEquals(getRGBA(imageDataFromData.data, 2), getRGBA(data, 2));
|
||||
|
||||
if (window.SharedArrayBuffer) {
|
||||
assert_throws_js(TypeError, function() { new ImageData(new Uint16Array(new SharedArrayBuffer(32)), 4, 2); });
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
html.fail {
|
||||
background: #f66;
|
||||
}
|
||||
html.pass {
|
||||
background: #6f6;
|
||||
}
|
||||
html.needs_check {
|
||||
background: #99f;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: small;
|
||||
font-family: sans-serif;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #00c;
|
||||
}
|
||||
a:visited {
|
||||
color: #808;
|
||||
}
|
||||
|
||||
body.framed {
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: larger;
|
||||
margin: 0;
|
||||
padding-left: 0.5em;
|
||||
text-indent: -0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p.notes {
|
||||
margin-bottom: 0.5em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
padding: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.refs {
|
||||
font-style: italic;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.refs ul {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.refs li {
|
||||
display: inline;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
border: 2px #f0f solid;
|
||||
background: url(../images/background.png);
|
||||
}
|
||||
|
||||
img.expected {
|
||||
display: none;
|
||||
border: 2px #f0f solid;
|
||||
background: url(../images/background.png);
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 2px #f0f solid;
|
||||
}
|
||||
|
||||
.output {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show_output .output, .needs_check .output {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.show_output #show_output {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.resource {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.fallback {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
|
||||
html.minimal body {
|
||||
color: white;
|
||||
}
|
||||
html.fail.minimal {
|
||||
background: #f00;
|
||||
}
|
||||
html.pass.minimal {
|
||||
background: #080;
|
||||
}
|
||||
html.needs_check.minimal {
|
||||
background: #008;
|
||||
}
|
||||
.minimal #d {
|
||||
display: none !important;
|
||||
}
|
||||
.minimal .expectedtext {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
#passtext, #failtext {
|
||||
display: none;
|
||||
}
|
||||
.minimal.pass #passtext, .minimal.fail #failtext {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
function _valToString(val)
|
||||
{
|
||||
if (val === undefined || val === null)
|
||||
return '[' + typeof(val) + ']';
|
||||
return val.toString() + '[' + typeof(val) + ']';
|
||||
}
|
||||
|
||||
function _assert(cond, text)
|
||||
{
|
||||
assert_true(!!cond, text);
|
||||
}
|
||||
|
||||
function _assertSame(a, b, text_a, text_b)
|
||||
{
|
||||
var msg = text_a + ' === ' + text_b + ' (got ' + _valToString(a) +
|
||||
', expected ' + _valToString(b) + ')';
|
||||
assert_equals(a, b, msg);
|
||||
}
|
||||
|
||||
function _assertDifferent(a, b, text_a, text_b)
|
||||
{
|
||||
var msg = text_a + ' !== ' + text_b + ' (got ' + _valToString(a) +
|
||||
', expected not ' + _valToString(b) + ')';
|
||||
assert_not_equals(a, b, msg);
|
||||
}
|
||||
|
||||
|
||||
function _getPixel(canvas, x,y)
|
||||
{
|
||||
var ctx = canvas.getContext('2d');
|
||||
var imgdata = ctx.getImageData(x, y, 1, 1);
|
||||
return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ];
|
||||
}
|
||||
|
||||
function _assertPixel(canvas, x, y, r, g, b, a)
|
||||
{
|
||||
var c = _getPixel(canvas, x,y);
|
||||
assert_equals(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_equals(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_equals(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_equals(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
|
||||
}
|
||||
|
||||
function _assertPixelApprox(canvas, x, y, r, g, b, a, tolerance)
|
||||
{
|
||||
var c = _getPixel(canvas, x,y);
|
||||
assert_approx_equals(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_approx_equals(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_approx_equals(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
|
||||
assert_approx_equals(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
|
||||
}
|
||||
|
||||
function _assertMatricesApproxEqual(matA, matB)
|
||||
{
|
||||
A = matA.toFloat32Array();
|
||||
B = matB.toFloat32Array();
|
||||
assert_equals(A.length, B.length);
|
||||
for (var i = 0; i < A.length; i++) {
|
||||
assert_approx_equals(A[i], B[i], 10e-6);
|
||||
}
|
||||
}
|
||||
|
||||
function rad2deg(angle_in_radians) {
|
||||
return angle_in_radians / Math.PI * 180;
|
||||
}
|
||||
|
||||
function deg2rad(angle_in_degrees) {
|
||||
return angle_in_degrees / 180 * Math.PI;
|
||||
}
|
||||
|
||||
let _deferred = false;
|
||||
|
||||
function deferTest() {
|
||||
_deferred = true;
|
||||
}
|
||||
|
||||
function _addTest(testFn, attributes={})
|
||||
{
|
||||
on_event(window, "load", function()
|
||||
{
|
||||
t.step(function() {
|
||||
var canvas = document.getElementById('c');
|
||||
var ctx = canvas.getContext('2d', attributes);
|
||||
t.step(testFn, window, canvas, ctx);
|
||||
});
|
||||
|
||||
if (!_deferred) {
|
||||
t.done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _assertGreen(ctx, canvasWidth, canvasHeight)
|
||||
{
|
||||
var testColor = function(d, idx, expected) {
|
||||
assert_equals(d[idx], expected, "d[" + idx + "]", String(expected));
|
||||
};
|
||||
var imagedata = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
|
||||
var w = imagedata.width, h = imagedata.height, d = imagedata.data;
|
||||
for (var i = 0; i < h; ++i) {
|
||||
for (var j = 0; j < w; ++j) {
|
||||
testColor(d, 4 * (w * i + j) + 0, 0);
|
||||
testColor(d, 4 * (w * i + j) + 1, 255);
|
||||
testColor(d, 4 * (w * i + j) + 2, 0);
|
||||
testColor(d, 4 * (w * i + j) + 3, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCrossOriginYellowImage()
|
||||
{
|
||||
var img = new Image();
|
||||
img.id = "yellow.png";
|
||||
img.className = "resource";
|
||||
img.src = get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png";
|
||||
document.body.appendChild(img);
|
||||
}
|
||||
|
||||
function addCrossOriginRedirectYellowImage()
|
||||
{
|
||||
var img = new Image();
|
||||
img.id = "yellow.png";
|
||||
img.className = "resource";
|
||||
img.src = get_host_info().HTTP_ORIGIN + "/common/redirect.py?location=" +
|
||||
get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png";
|
||||
document.body.appendChild(img);
|
||||
}
|
||||
|
||||
function forEachCanvasSource(crossOriginUrl, sameOriginUrl, callback) {
|
||||
function makeImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => resolve(image);
|
||||
image.onerror = reject;
|
||||
image.src = crossOriginUrl + "/images/red.png";
|
||||
});
|
||||
}
|
||||
|
||||
const arguments = [
|
||||
{
|
||||
name: "cross-origin HTMLImageElement",
|
||||
factory: makeImage,
|
||||
},
|
||||
|
||||
{
|
||||
name: "cross-origin SVGImageElement",
|
||||
factory: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = document.createElementNS("http://www.w3.org/2000/svg", "image");
|
||||
image.onload = () => resolve(image);
|
||||
image.onerror = reject;
|
||||
image.setAttribute("externalResourcesRequired", "true");
|
||||
image.setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', crossOriginUrl + "/images/red.png");
|
||||
document.body.appendChild(image);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "cross-origin HTMLVideoElement",
|
||||
factory: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
video.oncanplaythrough = () => resolve(video);
|
||||
video.preload = "auto";
|
||||
video.onerror = reject;
|
||||
video.src = getVideoURI(crossOriginUrl + "/media/movie_300");
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "redirected to cross-origin HTMLVideoElement",
|
||||
factory: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
video.oncanplaythrough = () => resolve(video);
|
||||
video.preload = "auto";
|
||||
video.onerror = reject;
|
||||
video.src = "/common/redirect.py?location=" + getVideoURI(crossOriginUrl + "/media/movie_300");
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "redirected to same-origin HTMLVideoElement",
|
||||
factory: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
video.oncanplaythrough = () => resolve(video);
|
||||
video.preload = "auto";
|
||||
video.onerror = reject;
|
||||
video.src = crossOriginUrl + "/common/redirect.py?location=" + getVideoURI(sameOriginUrl + "/media/movie_300");
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "unclean HTMLCanvasElement",
|
||||
factory: () => {
|
||||
return makeImage().then(image => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
context.drawImage(image, 0, 0);
|
||||
return canvas;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "unclean ImageBitmap",
|
||||
factory: () => {
|
||||
return makeImage().then(createImageBitmap);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (let { name, factory } of arguments) {
|
||||
callback(name, factory);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue