Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support the usage of CORS for WebGL and the 2D canvas #3

Open
wants to merge 1 commit into
base: webgl
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
This test ensures WebGL implementations follow proper same-origin restrictions.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".

PASS Playable video format found
PASS img was loaded

check that an attempt to upload an image from another origin throws an exception.
PASS texImage2D with cross-origin image should throw exception.
PASS texSubImage2D with cross-origin image should throw exception.
check that readPixels and toDataURL continue to work against this canvas.
PASS readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.
PASS should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.
check that an attempt to upload a tainted canvas throws an exception.
PASS should throw exception by toDataURL for NON origin clean canvas.
PASS texImage2D with NON origin clean canvas should throw exception.
PASS texSubImage2D with NON origin clean canvas should throw exception.
check that readPixels and toDataURL continue to work against this canvas.
PASS readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.
PASS should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.
check that an attempt to upload a video from another origin throws an exception.
PASS texImage2D with cross-origin video should throw exception.
PASS texSubImage2D with cross-origin video should throw exception.
check that readPixels and toDataURL continue to work against this canvas.
PASS readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.
PASS should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.

TEST COMPLETE

218 changes: 218 additions & 0 deletions LayoutTests/http/tests/canvas/webgl/origin-clean-conformance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebGL Origin Restrictions Conformance Tests</title>
<script>
function create3DContext(canvas, attributes)
{
if (!canvas)
canvas = document.createElement("canvas");
var context = null;
try {
context = canvas.getContext("experimental-webgl", attributes);
} catch(e) {}
if (!context) {
try {
context = canvas.getContext("webkit-3d", attributes);
} catch(e) {}
}
if (!context) {
try {
context = canvas.getContext("moz-webgl", attributes);
} catch(e) {}
}
if (!context) {
throw "Unable to fetch WebGL rendering context for Canvas";
}
return context;
}

function description(msg)
{
// For MSIE 6 compatibility
var span = document.createElement("span");
span.innerHTML = '<p>' + msg + '</p><p>On success, you will see a series of "<span class="pass">PASS</span>" messages, followed by "<span class="pass">TEST COMPLETE</span>".</p>';
var description = document.getElementById("description");
if (description.firstChild)
description.replaceChild(span, description.firstChild);
else
description.appendChild(span);
}

function debug(msg)
{
var span = document.createElement("span");
document.getElementById("console").appendChild(span); // insert it first so XHTML knows the namespace
span.innerHTML = msg + '<br />';
}

function escapeHTML(text)
{
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/\0/g, "\\0");
}

function testPassed(msg)
{
debug('<span><span class="pass">PASS</span> ' + escapeHTML(msg) + '</span>');
}

function testFailed(msg)
{
debug('<span><span class="fail">FAIL</span> ' + escapeHTML(msg) + '</span>');
}

function assertMsg(assertion, msg) {
if (assertion) {
testPassed(msg);
} else {
testFailed(msg);
}
}

// Checks if function throws an exception.
function causedException(func) {
var hadException = false;
try {
func();
} catch(e) {
hadException = true;
}
return hadException;
}

var testVideo = false;

function init() {
var video = document.getElementById("video");

var base = "http://localhost:8000/resources/";
var videos = [
["video/mp4", base + "test.mp4"],
["video/ogg", base + "test.ogv"],
];
var videoFile = null;
for (var i = 0; i < videos.length; ++i) {
if (video.canPlayType(videos[i][0])) {
videoFile = videos[i][1];
break;
}
}
assertMsg(videoFile, "Playable video format found");

if (videoFile) {
if (window.layoutTestController) {
layoutTestController.overridePreference("WebKitWebGLEnabled", "1");
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}
video.src = videoFile;
video.addEventListener("playing", runTests);
video.play();
testVideo = true;
} else {
// Still run the other tests, even if the video failed.
runTests();
}
}

function runTests() {
description("This test ensures WebGL implementations follow proper same-origin restrictions.");
var img = document.getElementById("img");
assertMsg(img.width > 0 && img.height > 0, "img was loaded");

function makeTexImage2D(gl, src) {
return function() {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
};
}

function makeTexSubImage2D(gl, src) {
return function() {
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, src);
};
}

function makeReadPixels(gl) {
return function() {
var buf = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf);
};
}

function makeToDataURL(canvas) {
return function() {
var data = canvas.toDataURL();
}
}

var canvas1 = document.getElementById("canvas1");
var gl = create3DContext(canvas1);

debug("");
debug("check that an attempt to upload an image from another origin throws an exception.");
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
assertMsg(causedException(makeTexImage2D(gl, img)),
"texImage2D with cross-origin image should throw exception.");
assertMsg(causedException(makeTexSubImage2D(gl, img)),
"texSubImage2D with cross-origin image should throw exception.");

debug("check that readPixels and toDataURL continue to work against this canvas.");
assertMsg(!causedException(makeReadPixels(gl)),
"readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.");
assertMsg(!causedException(makeToDataURL(canvas1)),
"should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.");

debug("check that an attempt to upload a tainted canvas throws an exception.");
var canvas2 = document.getElementById("canvas2");
var ctx2d = canvas2.getContext("2d");
ctx2d.drawImage(img, 0, 0);
assertMsg(causedException(makeToDataURL(canvas2)),
"should throw exception by toDataURL for NON origin clean canvas.");
assertMsg(causedException(makeTexImage2D(gl, canvas2)),
"texImage2D with NON origin clean canvas should throw exception.");
assertMsg(causedException(makeTexSubImage2D(gl, canvas2)),
"texSubImage2D with NON origin clean canvas should throw exception.");

debug("check that readPixels and toDataURL continue to work against this canvas.");
assertMsg(!causedException(makeReadPixels(gl)),
"readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.");
assertMsg(!causedException(makeToDataURL(canvas1)),
"should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.");

if (testVideo) {
debug("check that an attempt to upload a video from another origin throws an exception.");
var video = document.getElementById("video");
assertMsg(causedException(makeTexImage2D(gl, video)),
"texImage2D with cross-origin video should throw exception.");
assertMsg(causedException(makeTexSubImage2D(gl, video)),
"texSubImage2D with cross-origin video should throw exception.");

debug("check that readPixels and toDataURL continue to work against this canvas.");
assertMsg(!causedException(makeReadPixels(gl)),
"readPixels should never throw exception -- not possible to dirty origin of WebGL canvas.");
assertMsg(!causedException(makeToDataURL(canvas1)),
"should not throw exception by toDataURL for WebGL canvas, which should stay origin clean.");
}

debug('<br /><span class="pass">TEST COMPLETE</span>');
if (window.layoutTestController)
layoutTestController.waitUntilDone();
if (window.layoutTestController) {
layoutTestController.notifyDone();
}
}
</script>
</head>
<body onload="init()">
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
<img id="img" src="http://localhost:8000/local/resources/abe.png" style="display:none;">
<video id="video" style="display:none;"/>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Untainted canvas:
PASS: Calling getImageData() from an untainted canvas was allowed.
PASS: Calling toDataURL() on an untainted canvas was allowed.


Tainted canvas:
PASS: Calling getImageData() from a canvas tainted by a remote image was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a remote image was allowed.
PASS: Calling getImageData() from a canvas tainted by a CORS-untained canvas was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a CORS-untained canvas was allowed.
PASS: Calling getImageData() from a canvas tainted by a remote image tainted pattern was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a remote image tainted pattern was allowed.
PASS: Calling getImageData() from a canvas tainted by a CORS-untainted canvas pattern was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a CORS-untainted canvas pattern was allowed.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Untainted canvas:
PASS: Calling getImageData() from an untainted canvas was allowed.
PASS: Calling toDataURL() on an untainted canvas was allowed.


Tainted canvas:
PASS: Calling getImageData() from a canvas tainted by a remote image was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a remote image was allowed.
PASS: Calling getImageData() from a canvas tainted by a CORS-untained canvas was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a CORS-untained canvas was allowed.
PASS: Calling getImageData() from a canvas tainted by a remote image tainted pattern was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a remote image tainted pattern was allowed.
PASS: Calling getImageData() from a canvas tainted by a CORS-untainted canvas pattern was allowed.
PASS: Calling toDataURL() on a canvas CORS-untainted by a CORS-untainted canvas pattern was allowed.

Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<pre id="console"></pre>
<script>
if (window.layoutTestController) {
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}

log = function(msg)
{
document.getElementById('console').appendChild(document.createTextNode(msg + "\n"));
}

testGetImageData = function(context, description)
{
description = "Calling getImageData() from a canvas tainted by a " + description;
try {
var imageData = context.getImageData(0,0,100,100);
log("PASS: " + description + " was allowed.");
} catch (e) {
log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
}
}

testToDataURL = function(canvas, description)
{
description = "Calling toDataURL() on a canvas CORS-untainted by a " + description;
try {
var dataURL = canvas.toDataURL();
log("PASS: " + description + " was allowed.");
} catch (e) {
log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
}
}

test = function(canvas, description)
{
testGetImageData(canvas.getContext("2d"), description);
testToDataURL(canvas, description);
}

var image = new Image();
image.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext("2d");

// Control tests
log("Untainted canvas:");
try {
var imageData = context.getImageData(0, 0, 100, 100);
log("PASS: Calling getImageData() from an untainted canvas was allowed.");
} catch (e) {
log("FAIL: Calling getImageData() from an untainted canvas was not allowed: Threw error: " + e + ".");
}
try {
var dataURL = canvas.toDataURL();
log("PASS: Calling toDataURL() on an untainted canvas was allowed.");
} catch (e) {
log("FAIL: Calling toDataURL() on an untainted canvas was not allowed: Threw error: " + e + ".");
}

log("\n");
log("Tainted canvas:");
// Test reading from a canvas after drawing a remote image onto it
context.drawImage(image, 0, 0, 100, 100);

test(canvas, "remote image");

var dirtyCanvas = canvas;

// Now test reading from a canvas after drawing a tainted canvas onto it
canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext("2d");
context.drawImage(dirtyCanvas, 0, 0, 100, 100);

test(canvas, "CORS-untained canvas");

// Test reading after using a tainted pattern
canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext("2d");
var remoteImagePattern = context.createPattern(image, "repeat");
context.fillStyle = remoteImagePattern;
context.fillRect(0, 0, 100, 100);

test(canvas, "remote image tainted pattern");

// Test reading after using a tainted pattern
canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext("2d");
var taintedCanvasPattern = context.createPattern(dirtyCanvas, "repeat");
context.fillStyle = taintedCanvasPattern;
context.fillRect(0, 0, 100, 100);

test(canvas, "CORS-untainted canvas pattern");

if (window.layoutTestController)
layoutTestController.notifyDone();
}
image.crossOrigin = "use-credentials";
image.src = "http://localhost:8000/security/resources/abe-allow-credentials.php";
</script>
Loading