Add ToT WebGL conformance tests : part 12 (last one)

Add conformance/extensions

BUG=
TEST=
TBR=kbr@chromium.org

Review URL: https://codereview.chromium.org/41893003

git-svn-id: http://src.chromium.org/svn/trunk/deps/third_party/webgl/sdk/tests@230853 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
diff --git a/conformance/extensions/00_test_list.txt b/conformance/extensions/00_test_list.txt
new file mode 100644
index 0000000..c6d7ab3
--- /dev/null
+++ b/conformance/extensions/00_test_list.txt
@@ -0,0 +1,29 @@
+--min-version 1.0.3 angle-instanced-arrays.html
+--min-version 1.0.3 ext-frag-depth.html
+--min-version 1.0.2 ext-texture-filter-anisotropic.html
+--min-version 1.0.2 get-extension.html
+oes-standard-derivatives.html
+oes-texture-float-with-canvas.html
+oes-texture-float-with-image-data.html
+oes-texture-float-with-image.html
+oes-texture-float-with-video.html
+oes-texture-float.html
+oes-vertex-array-object.html
+--min-version 1.0.3 oes-texture-half-float.html
+--min-version 1.0.3 oes-texture-float-linear.html
+--min-version 1.0.3 oes-texture-half-float-linear.html
+--min-version 1.0.3 oes-texture-half-float-with-canvas.html
+--min-version 1.0.3 oes-texture-half-float-with-image-data.html
+--min-version 1.0.3 oes-texture-half-float-with-image.html
+--min-version 1.0.3 oes-texture-half-float-with-video.html
+--min-version 1.0.2 oes-element-index-uint.html
+webgl-debug-renderer-info.html
+webgl-debug-shaders.html
+--min-version 1.0.3 webgl-compressed-texture-atc.html
+--min-version 1.0.3 webgl-compressed-texture-pvrtc.html
+--min-version 1.0.2 webgl-compressed-texture-s3tc.html
+--min-version 1.0.3 webgl-compressed-texture-size-limit.html
+--min-version 1.0.2 webgl-depth-texture.html
+--min-version 1.0.3 webgl-draw-buffers.html
+--min-version 1.0.3 webgl-shared-resources.html
+
diff --git a/conformance/extensions/angle-instanced-arrays.html b/conformance/extensions/angle-instanced-arrays.html
new file mode 100644
index 0000000..1a907d0
--- /dev/null
+++ b/conformance/extensions/angle-instanced-arrays.html
@@ -0,0 +1,398 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL ANGLE_instanced_arrays Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing instanced draws -->
+<script id="outputVertexShader" type="x-shader/x-vertex">
+attribute vec4 aPosition;
+attribute vec2 aOffset;
+attribute vec4 aColor;
+varying vec4 vColor;
+void main() {
+    vColor = aColor;
+    gl_Position = aPosition + vec4(aOffset, 0.0, 0.0);
+}
+</script>
+
+<script id="outputFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 vColor;
+void main() {
+    gl_FragColor = vColor;
+}
+</script>
+
+<script>
+"use strict";
+description("This test verifies the functionality of the ANGLE_instanced_arrays extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+
+var positionLoc = 0;
+var offsetLoc = 2;
+var colorLoc = 3;
+var program;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    runDivisorTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "ANGLE_instanced_arrays");
+    if (!ext) {
+        testPassed("No ANGLE_instanced_arrays support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled ANGLE_instanced_arrays extension");
+
+        runSupportedTest(true);
+
+        runDivisorTestEnabled();
+        runUniqueObjectTest();
+
+        setupCanvas();
+        runOutputTests();
+        runANGLECorruptionTest();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("ANGLE_instanced_arrays") >= 0) {
+        if (extensionEnabled) {
+            testPassed("ANGLE_instanced_arrays listed as supported and getExtension succeeded");
+        } else {
+            testFailed("ANGLE_instanced_arrays listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("ANGLE_instanced_arrays not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("ANGLE_instanced_arrays not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runDivisorTestDisabled() {
+    debug("Testing VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE with extension disabled");
+    
+    var VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE = 0x88FE;
+
+    gl.getVertexAttrib(0, VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE should not be queryable if extension is disabled");
+}
+
+function runDivisorTestEnabled() {
+    debug("Testing VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE with extension enabled");
+
+    shouldBe("ext.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE", "0x88FE");
+
+    var max_vertex_attribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+
+    for (var i = 0; i < max_vertex_attribs; ++i) {
+        var queried_value = gl.getVertexAttrib(i, ext.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
+        if(queried_value == 0){
+            testPassed("Vertex attribute " + i + " must has a default divisor of 0");
+        }
+        else{
+            testFailed("Default divisor of vertex attribute " + i + " should be: 0, returned value was: " + queried_value);
+        }
+    }
+
+    ext.vertexAttribDivisorANGLE(max_vertex_attribs, 2);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "vertexAttribDivisorANGLE index set greater than or equal to MAX_VERTEX_ATTRIBS should be an invalid value");
+
+    ext.vertexAttribDivisorANGLE(max_vertex_attribs-1, 2);
+    glErrorShouldBe(gl, gl.NO_ERROR, "vertexAttribDivisorANGLE index set less than MAX_VERTEX_ATTRIBS should succeed");
+
+    var queried_value = gl.getVertexAttrib(max_vertex_attribs-1, ext.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE);
+    if(queried_value == 2){
+        testPassed("Set value of VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE matches expecation");
+    }
+    else{
+        testFailed("Set value of VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE should be: 2, returned value was: " + queried_value);
+    }
+}
+
+function setupCanvas() {
+    canvas.width = 50; canvas.height = 50;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    gl.clearColor(0, 0, 0, 0);
+
+    program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"], ['aPosition', 'aOffset', 'aColor'], [positionLoc, offsetLoc, colorLoc]);
+}
+
+function runOutputTests() {
+    var e = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher
+    var instanceCount = 4;
+
+    debug("Testing various draws for valid built-in function behavior");
+
+    var offsets = new Float32Array([
+        -1.0,  1.0,
+         1.0,  1.0,
+        -1.0, -1.0,
+         1.0, -1.0,
+    ]);
+    var offsetBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(offsetLoc);
+    gl.vertexAttribPointer(offsetLoc, 2, gl.FLOAT, false, 0, 0);
+    ext.vertexAttribDivisorANGLE(offsetLoc, 1);
+
+    var colors = new Float32Array([
+        1.0, 0.0, 0.0, 1.0, // Red
+        0.0, 1.0, 0.0, 1.0, // Green
+        0.0, 0.0, 1.0, 1.0, // Blue
+        1.0, 1.0, 0.0, 1.0, // Yellow
+    ]);
+    var colorBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(colorLoc);
+    gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
+    ext.vertexAttribDivisorANGLE(colorLoc, 1);
+
+    // Draw 1: Draw Non-indexed instances
+    debug("Testing drawArraysInstancedANGLE");
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    wtu.setupUnitQuad(gl, 0);
+
+    // Test drawArraysInstancedANGLE error conditions
+    ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, instanceCount);
+    wtu.checkCanvasRect(gl, 0, canvas.height/2, canvas.width/2, canvas.height/2, [255, 0, 0, 255]);
+    wtu.checkCanvasRect(gl, canvas.width/2, canvas.height/2, canvas.width/2, canvas.height/2, [0, 255, 0, 255]);
+    wtu.checkCanvasRect(gl, 0, 0, canvas.width/2, canvas.height/2, [0, 0, 255, 255]);
+    wtu.checkCanvasRect(gl, canvas.width/2, 0, canvas.width/2, canvas.height/2, [255, 255, 0, 255]);
+
+    ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, -1);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "drawArraysInstancedANGLE cannot have a primcount less than 0");
+
+    ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, -1, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "drawArraysInstancedANGLE cannot have a count less than 0");
+
+    ext.vertexAttribDivisorANGLE(positionLoc, 1);
+    ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "There must be at least one vertex attribute with a divisor of zero when calling drawArraysInstancedANGLE");
+    ext.vertexAttribDivisorANGLE(positionLoc, 0);
+
+    ext.drawArraysInstancedANGLE(gl.POINTS, 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstancedANGLE with POINTS should succeed");
+    ext.drawArraysInstancedANGLE(gl.LINES, 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstancedANGLE with LINES should succeed");
+    ext.drawArraysInstancedANGLE(gl.LINE_LIST, 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstancedANGLE with LINE_LIST should return succeed");
+    ext.drawArraysInstancedANGLE(gl.TRIANGLE_LIST, 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstancedANGLE with TRIANGLE_LIST should succeed");
+
+    ext.drawArraysInstancedANGLE(desktopGL['QUAD_STRIP'], 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstancedANGLE with QUAD_STRIP should return INVALID_ENUM");
+    ext.drawArraysInstancedANGLE(desktopGL['QUADS'], 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstancedANGLE with QUADS should return INVALID_ENUM");
+    ext.drawArraysInstancedANGLE(desktopGL['POLYGON'], 0, 6, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstancedANGLE with POLYGON should return INVALID_ENUM");
+
+    // Draw 2: Draw indexed instances
+    debug("Testing drawElementsInstancedANGLE");
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    wtu.setupIndexedQuad(gl, 1, 0);
+    ext.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    wtu.checkCanvasRect(gl, 0, canvas.height/2, canvas.width/2, canvas.height/2, [255, 0, 0, 255]);
+    wtu.checkCanvasRect(gl, canvas.width/2, canvas.height/2, canvas.width/2, canvas.height/2, [0, 255, 0, 255]);
+    wtu.checkCanvasRect(gl, 0, 0, canvas.width/2, canvas.height/2, [0, 0, 255, 255]);
+    wtu.checkCanvasRect(gl, canvas.width/2, 0, canvas.width/2, canvas.height/2, [255, 255, 0, 255]);
+
+    // Test drawElementsInstancedANGLE error conditions
+    ext.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, -1);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "drawElementsInstancedANGLE cannot have a primcount less than 0");
+
+    ext.drawElementsInstancedANGLE(gl.TRIANGLES, -1, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "drawElementsInstancedANGLE cannot have a count less than 0");
+
+    ext.vertexAttribDivisorANGLE(positionLoc, 1);
+    ext.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "There must be at least one vertex attribute with a divisor of zero when calling drawElementsInstancedANGLE");
+    ext.vertexAttribDivisorANGLE(positionLoc, 0);
+
+    ext.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstancedANGLE with UNSIGNED_BYTE should succeed");
+
+    ext.drawElementsInstancedANGLE(gl.POINTS, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstancedANGLE with POINTS should succeed");
+    ext.drawElementsInstancedANGLE(gl.LINES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstancedANGLE with LINES should succeed");
+    ext.drawElementsInstancedANGLE(gl.LINE_LIST, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstancedANGLE with LINE_LIST should return succeed");
+    ext.drawElementsInstancedANGLE(gl.TRIANGLE_LIST, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstancedANGLE with TRIANGLE_LIST should succeed");
+
+    ext.drawElementsInstancedANGLE(desktopGL['QUAD_STRIP'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstancedANGLE with QUAD_STRIP should return INVALID_ENUM");
+    ext.drawElementsInstancedANGLE(desktopGL['QUADS'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstancedANGLE with QUADS should return INVALID_ENUM");
+    ext.drawElementsInstancedANGLE(desktopGL['POLYGON'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstancedANGLE with POLYGON should return INVALID_ENUM");
+}
+
+function runUniqueObjectTest()
+{
+    debug("Testing that getExtension() returns the same object each time");
+    gl.getExtension("ANGLE_instanced_arrays").myProperty = 2;
+    gc();
+    shouldBe('gl.getExtension("ANGLE_instanced_arrays").myProperty', '2');
+}
+
+function runANGLECorruptionTest()
+{
+    debug("")
+    debug("Testing to ensure that rendering isn't corrupt due to an ANGLE bug");
+    // See: https://code.google.com/p/angleproject/issues/detail?id=467
+
+    var tolerance = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher
+    var instanceCount = 10; // Must be higher than 6
+    var iteration = 0;
+    var totalIterations = 10;
+
+    var offsets = new Float32Array([
+        0.0, 0.0,
+        0.2, 0.0,
+        0.4, 0.0,
+        0.6, 0.0,
+        0.8, 0.0,
+        1.0, 0.0,
+        1.2, 0.0,
+        1.4, 0.0,
+        1.6, 0.0,
+        1.8, 0.0,
+    ]);
+    var offsetBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, offsets.byteLength * 2, gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, offsets);
+    gl.enableVertexAttribArray(offsetLoc);
+    gl.vertexAttribPointer(offsetLoc, 2, gl.FLOAT, false, 0, 0);
+    ext.vertexAttribDivisorANGLE(offsetLoc, 1);
+
+    var colors = new Float32Array([
+        1.0, 0.0, 0.0, 1.0,
+        1.0, 1.0, 0.0, 1.0,
+        0.0, 1.0, 0.0, 1.0,
+        0.0, 1.0, 1.0, 1.0,
+        0.0, 0.0, 1.0, 1.0,
+        1.0, 0.0, 1.0, 1.0,
+        1.0, 0.0, 0.0, 1.0,
+        1.0, 1.0, 0.0, 1.0,
+        0.0, 1.0, 0.0, 1.0,
+        0.0, 1.0, 1.0, 1.0,
+    ]);
+    var colorBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, colors.byteLength * 2, gl.STATIC_DRAW);
+    gl.bufferSubData(gl.ARRAY_BUFFER, 0, colors);
+    gl.enableVertexAttribArray(colorLoc);
+    gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
+    ext.vertexAttribDivisorANGLE(colorLoc, 1);
+
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    wtu.setupUnitQuad(gl, 0);
+
+    function cycleAndTest() {
+        // Update the instanced data buffers outside the accessed range.
+        // This, plus rendering more instances than vertices, triggers the bug.
+        var nullData = new Float32Array(offsets.length);
+        gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+        gl.bufferSubData(gl.ARRAY_BUFFER, offsets.byteLength, nullData);
+
+        nullData = new Float32Array(colors.length);
+        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+        gl.bufferSubData(gl.ARRAY_BUFFER, colors.byteLength, nullData);
+
+        ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, instanceCount);
+
+        // Make sure each color was drawn correctly 
+        var i;
+        var passed = true;
+        for (i = 0; i < instanceCount; ++i) {
+            var w = canvas.width / instanceCount;
+            var x = w * i;
+            var color =  [colors[(i*4)] * 255, colors[(i*4)+1] * 255, colors[(i*4)+2] * 255, 255]
+
+            wtu.checkCanvasRectColor(
+                gl, x, 0, w, canvas.height, color, tolerance,
+                function() {},
+                function() {
+                    passed = false;
+                }, debug);
+        }
+
+        if (passed) {
+            testPassed("Passed test " + iteration + " of " + totalIterations);
+            if (iteration < totalIterations) {
+                ++iteration;
+                setTimeout(cycleAndTest, 100);
+            } else {
+                finishTest();
+            }
+        } else {
+            testFailed("Failed test " + iteration + " of " + totalIterations);
+            finishTest();
+        }
+    }
+
+    cycleAndTest();
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/ext-frag-depth.html b/conformance/extensions/ext-frag-depth.html
new file mode 100644
index 0000000..04417f4
--- /dev/null
+++ b/conformance/extensions/ext-frag-depth.html
@@ -0,0 +1,243 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL EXT_frag_depth Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing fragment depth writing -->
+
+<!-- Shader omitting the required #extension pragma -->
+<script id="missingPragmaFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    gl_FragDepthEXT = 1.0;
+}
+</script>
+
+<!-- Shader to test macro definition -->
+<script id="macroFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+#ifdef GL_EXT_frag_depth
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+#else
+    // Error expected
+    #error no GL_EXT_frag_depth;
+#endif
+}
+</script>
+
+<!-- Shader with required #extension pragma -->
+<script id="testFragmentShader" type="x-shader/x-fragment">
+#extension GL_EXT_frag_depth : enable
+precision mediump float;
+void main() {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    gl_FragDepthEXT = 1.0;
+}
+</script>
+<!-- Shaders to link with test fragment shaders -->
+<script id="goodVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main() {
+    gl_Position = vPosition;
+}
+</script>
+<!-- Shaders to test output -->
+<script id="outputVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main() {
+    gl_Position = vPosition;
+}
+</script>
+<script id="outputFragmentShader" type="x-shader/x-fragment">
+#extension GL_EXT_frag_depth : enable
+precision mediump float;
+uniform float uDepth;
+void main() {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    gl_FragDepthEXT = uDepth;
+}
+</script>
+
+<script>
+"use strict";
+description("This test verifies the functionality of the EXT_frag_depth extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    runShaderTests(false);
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "EXT_frag_depth");
+    if (!ext) {
+        testPassed("No EXT_frag_depth support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled EXT_frag_depth extension");
+
+        runSupportedTest(true);
+
+        runShaderTests(true);
+        runOutputTests();
+        runUniqueObjectTest();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("EXT_frag_depth") >= 0) {
+        if (extensionEnabled) {
+            testPassed("EXT_frag_depth listed as supported and getExtension succeeded");
+        } else {
+            testFailed("EXT_frag_depth listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("EXT_frag_depth not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("EXT_frag_depth not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runShaderTests(extensionEnabled) {
+    debug("");
+    debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+    // Expect the macro shader to succeed ONLY if enabled
+    var macroFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "macroFragmentShader");
+    if (extensionEnabled) {
+        if (macroFragmentProgram) {
+            // Expected result
+            testPassed("GL_EXT_frag_depth defined in shaders when extension is enabled");
+        } else {
+            testFailed("GL_EXT_frag_depth not defined in shaders when extension is enabled");
+        }
+    } else {
+        if (macroFragmentProgram) {
+            testFailed("GL_EXT_frag_depth defined in shaders when extension is disabled");
+        } else {
+            testPassed("GL_EXT_frag_depth not defined in shaders when extension disabled");
+        }
+    }
+
+    // Always expect the shader missing the #pragma to fail (whether enabled or not)
+    var missingPragmaFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "missingPragmaFragmentShader");
+    if (missingPragmaFragmentProgram) {
+        testFailed("Shader built-ins allowed without #extension pragma");
+    } else {
+        testPassed("Shader built-ins disallowed without #extension pragma");
+    }
+
+    // Try to compile a shader using the built-ins that should only succeed if enabled
+    var testFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "testFragmentShader");
+    if (extensionEnabled) {
+        if (testFragmentProgram) {
+            testPassed("Shader built-ins compiled successfully when extension enabled");
+        } else {
+            testFailed("Shader built-ins failed to compile when extension enabled");
+        }
+    } else {
+        if (testFragmentProgram) {
+            testFailed("Shader built-ins compiled successfully when extension disabled");
+        } else {
+            testPassed("Shader built-ins failed to compile when extension disabled");
+        }
+    }
+}
+
+function runOutputTests() {
+    var e = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher
+
+    debug("Testing various draws for valid built-in function behavior");
+
+    canvas.width = 50; canvas.height = 50;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    
+    // Enable depth testing with a clearDepth of 0.5
+    // This makes it so that fragments are only rendered when
+    // gl_fragDepthEXT is < 0.5
+    gl.clearDepth(0.5);
+    gl.enable(gl.DEPTH_TEST);
+
+    var positionLoc = 0;
+    var texcoordLoc = 1;
+    var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"], ['vPosition'], [0]);
+    var quadParameters = wtu.setupUnitQuad(gl, 0, 1);
+    var depthUniform = gl.getUniformLocation(program, "uDepth");
+
+    // Draw 1: Greater than clear depth
+    gl.uniform1f(depthUniform, 1.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 255, 255, 255]);
+
+    // Draw 2: Less than clear depth
+    gl.uniform1f(depthUniform, 0.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+}
+
+function runUniqueObjectTest()
+{
+    debug("Testing that getExtension() returns the same object each time");
+    gl.getExtension("EXT_frag_depth").myProperty = 2;
+    gc();
+    shouldBe('gl.getExtension("EXT_frag_depth").myProperty', '2');
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/ext-texture-filter-anisotropic.html b/conformance/extensions/ext-texture-filter-anisotropic.html
new file mode 100644
index 0000000..a8c9fcb
--- /dev/null
+++ b/conformance/extensions/ext-texture-filter-anisotropic.html
@@ -0,0 +1,180 @@
+<!--
+
+/*
+** Copyright (c) 2012 Florian Boesch <pyalot@gmail.com>.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL EXT_texture_filter_anisotropic Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+
+<script>
+"use strict";
+description("This test verifies the functionality of the EXT_texture_filter_anisotropic extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runHintTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "EXT_texture_filter_anisotropic");
+
+    if (!ext) {
+        testPassed("No EXT_texture_filter_anisotropic support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled EXT_texture_filter_anisotropic extension");
+
+        runSupportedTest(true);
+        runHintTestEnabled();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "EXT_texture_filter_anisotropic") !== undefined) {
+        if (extensionEnabled) {
+            testPassed("EXT_texture_filter_anisotropic listed as supported and getExtension succeeded");
+        } else {
+            testFailed("EXT_texture_filter_anisotropic listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("EXT_texture_filter_anisotropic not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("EXT_texture_filter_anisotropic not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runHintTestDisabled() {
+    debug("Testing MAX_TEXTURE_MAX_ANISOTROPY_EXT with extension disabled");
+    
+    var MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF;
+    gl.getParameter(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "MAX_TEXTURE_MAX_ANISOTROPY_EXT should not be queryable if extension is disabled");
+    
+    debug("Testing TEXTURE_MAX_ANISOTROPY_EXT with extension disabled");
+    var TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE;
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+   
+    gl.getTexParameter(gl.TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "TEXTURE_MAX_ANISOTROPY_EXT should not be queryable if extension is disabled");
+
+    gl.texParameterf(gl.TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, 1);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "TEXTURE_MAX_ANISOTROPY_EXT should not be settable if extension is disabled");
+    
+    gl.texParameteri(gl.TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, 1);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "TEXTURE_MAX_ANISOTROPY_EXT should not be settable if extension is disabled");
+
+    gl.deleteTexture(texture);
+}
+
+function runHintTestEnabled() {
+    debug("Testing MAX_TEXTURE_MAX_ANISOTROPY_EXT with extension enabled");
+
+    shouldBe("ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT", "0x84FF");
+
+    var max_anisotropy = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
+    glErrorShouldBe(gl, gl.NO_ERROR, "MAX_TEXTURE_MAX_ANISOTROPY_EXT query should succeed if extension is enabled");
+
+    if(max_anisotropy >= 2){
+        testPassed("Minimum value of MAX_TEXTURE_MAX_ANISOTROPY_EXT is 2.0");
+    }
+    else{
+        testFailed("Minimum value of MAX_TEXTURE_MAX_ANISOTROPY_EXT is 2.0, returned values was: " + max_anisotropy);
+    }
+    
+    // TODO make a texture and verify initial value == 1 and setting to less than 1 is invalid value
+
+    debug("Testing TEXTURE_MAX_ANISOTROPY_EXT with extension disabled");
+    shouldBe("ext.TEXTURE_MAX_ANISOTROPY_EXT", "0x84FE");
+
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+   
+    var queried_value = gl.getTexParameter(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT);
+    glErrorShouldBe(gl, gl.NO_ERROR, "TEXTURE_MAX_ANISOTROPY_EXT query should succeed if extension is enabled");
+
+    if(queried_value == 1){
+        testPassed("Initial value of TEXTURE_MAX_ANISOTROPY_EXT is 1.0");
+    }
+    else{
+        testFailed("Initial value of TEXTURE_MAX_ANISOTROPY_EXT should be 1.0, returned value was: " + queried_value);
+    }
+
+    gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, 0);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "texParameterf TEXTURE_MAX_ANISOTROPY_EXT set to < 1 should be an invalid value");
+    
+    gl.texParameteri(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, 0);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "texParameteri TEXTURE_MAX_ANISOTROPY_EXT set to < 1 should be an invalid value");
+    
+    gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy);
+    glErrorShouldBe(gl, gl.NO_ERROR, "texParameterf TEXTURE_MAX_ANISOTROPY_EXT set to >= 2 should should succeed");
+    
+    gl.texParameteri(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy);
+    glErrorShouldBe(gl, gl.NO_ERROR, "texParameteri TEXTURE_MAX_ANISOTROPY_EXT set to >= 2 should should succeed");
+
+    var queried_value = gl.getTexParameter(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT);
+    if(queried_value == max_anisotropy){
+        testPassed("Set value of TEXTURE_MAX_ANISOTROPY_EXT matches expecation");
+    }
+    else{
+        testFailed("Set value of TEXTURE_MAX_ANISOTROPY_EXT should be: " + max_anisotropy + " , returned value was: " + queried_value);
+    }
+
+    gl.deleteTexture(texture);
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/get-extension.html b/conformance/extensions/get-extension.html
new file mode 100644
index 0000000..3fd39e9
--- /dev/null
+++ b/conformance/extensions/get-extension.html
@@ -0,0 +1,99 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Extension Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<script>
+"use strict";
+var randomizeCase = function(str) {
+    var newChars = [];
+    for (var ii = 0; ii < str.length; ++ii) {
+        var c = str.substr(ii, 1);
+        var m = (Math.random() > 0.5) ? c.toLowerCase() : c.toUpperCase();
+        newChars.push(m);
+    }
+    return newChars.join("");
+}
+
+description();
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+
+debug("check every extension advertised can be enabled");
+var extensions = [];
+var extensionNames = gl.getSupportedExtensions();
+for (var ii = 0; ii < extensionNames.length; ++ii) {
+    var originalName = extensionNames[ii];
+    var mixedName = randomizeCase(originalName);
+    var extension = gl.getExtension(mixedName);
+    assertMsg(extension, "able to get " + originalName + " as " + mixedName);
+    if (extension) {
+        var kTestString = "this is a test";
+        var kTestNumber = 123;
+        var kTestFunction = function() { };
+        var kTestObject = { };
+        extension.testStringProperty = kTestString;
+        extension.testNumberProperty = kTestNumber;
+        extension.testFunctionProperty = kTestFunction;
+        extension.testObjectProperty = kTestObject;
+        gc();
+        var extension2 = gl.getExtension(originalName);
+        assertMsg(
+            extension === extension2,
+            "calling getExtension twice for the same extension returns the same object");
+        assertMsg(
+            extension2.testStringProperty === kTestString &&
+            extension2.testFunctionProperty === kTestFunction &&
+            extension2.testObjectProperty === kTestObject &&
+            extension2.testNumberProperty === kTestNumber,
+            "object returned by 2nd call to getExtension has same properties");
+    }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/oes-element-index-uint.html b/conformance/extensions/oes-element-index-uint.html
new file mode 100644
index 0000000..45f0206
--- /dev/null
+++ b/conformance/extensions/oes-element-index-uint.html
@@ -0,0 +1,459 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_element_index_uint Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+
+<script id="vs" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec4 vColor;
+varying vec4 color;
+void main() {
+    gl_Position = vPosition;
+    color = vColor;
+}
+</script>
+<script id="fs" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 color;
+void main() {
+  gl_FragColor = color;
+}
+</script>
+
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_element_index_uint extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = null;
+var ext = null;
+var canvas = null;
+
+// Test both STATIC_DRAW and DYNAMIC_DRAW as a regression test
+// for a bug in ANGLE which has since been fixed.
+for (var ii = 0; ii < 2; ++ii) {
+    canvas = document.createElement("canvas");
+    canvas.width = 50;
+    canvas.height = 50;
+
+    gl = wtu.create3DContext(canvas);
+
+    if (!gl) {
+        testFailed("WebGL context does not exist");
+    } else {
+        testPassed("WebGL context exists");
+    
+        var drawType = (ii == 0) ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW;
+        debug("Testing " + ((ii == 0) ? "STATIC_DRAW" : "DYNAMIC_DRAW"));
+
+
+        // Query the extension and store globally so shouldBe can access it
+        ext = gl.getExtension("OES_element_index_uint");
+        if (!ext) {
+            testPassed("No OES_element_index_uint support -- this is legal");
+
+            runSupportedTest(false);
+        } else {
+            testPassed("Successfully enabled OES_element_index_uint extension");
+
+            runSupportedTest(true);
+
+            runDrawTests(drawType);
+
+            // These tests are tweaked duplicates of the buffers/index-validation* tests
+            // using unsigned int indices to ensure that behavior remains consistent
+            runIndexValidationTests(drawType);
+            runCopiesIndicesTests(drawType);
+            runResizedBufferTests(drawType);
+            runVerifiesTooManyIndicesTests(drawType);
+            runCrashWithBufferSubDataTests(drawType);
+
+            glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+        }
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("OES_element_index_uint") >= 0) {
+        if (extensionEnabled) {
+            testPassed("OES_element_index_uint listed as supported and getExtension succeeded");
+        } else {
+            testFailed("OES_element_index_uint listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("OES_element_index_uint not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("OES_element_index_uint not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runDrawTests(drawType) {
+    debug("Test that draws with unsigned integer indices produce the expected results");
+    
+    canvas.width = 50; canvas.height = 50;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    
+    var program = wtu.setupNoTexCoordTextureProgram(gl);
+
+    function setupDraw(s) {
+        // Create a vertex buffer that cannot be fully indexed via shorts
+        var quadArrayLen = 65537 * 3;
+        var quadArray = new Float32Array(quadArrayLen);
+        
+        // Leave all but the last 4 values zero-ed out
+        var idx = quadArrayLen - 12;
+
+        // Initialized the last 4 values to a quad
+        quadArray[idx++] = 1.0 * s;
+        quadArray[idx++] = 1.0 * s;
+        quadArray[idx++] = 0.0;
+
+        quadArray[idx++] = -1.0 * s;
+        quadArray[idx++] = 1.0 * s;
+        quadArray[idx++] = 0.0;
+
+        quadArray[idx++] = -1.0 * s;
+        quadArray[idx++] = -1.0 * s;
+        quadArray[idx++] = 0.0;
+
+        quadArray[idx++] = 1.0 * s;
+        quadArray[idx++] = -1.0 * s;
+        quadArray[idx++] = 0.0;
+
+        var vertexObject = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+        gl.bufferData(gl.ARRAY_BUFFER, quadArray, drawType);
+
+        // Create an unsigned int index buffer that indexes the last 4 vertices
+        var baseIndex = (quadArrayLen / 3) - 4;
+
+        var indexObject = gl.createBuffer();
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array([
+            baseIndex + 0,
+            baseIndex + 1,
+            baseIndex + 2,
+            baseIndex + 2,
+            baseIndex + 3,
+            baseIndex + 0]), drawType);
+
+        var opt_positionLocation = 0;
+        gl.enableVertexAttribArray(opt_positionLocation);
+        gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0);
+    };
+    function readLocation(x, y) {
+        var pixels = new Uint8Array(1 * 1 * 4);
+        gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+        return pixels;
+    };
+    function testPixel(blackList, whiteList) {
+        function testList(list, expected) {
+            for (var n = 0; n < list.length; n++) {
+                var l = list[n];
+                var x = -Math.floor(l * canvas.width / 2) + canvas.width / 2;
+                var y = -Math.floor(l * canvas.height / 2) + canvas.height / 2;
+                var source = readLocation(x, y);
+                if (Math.abs(source[0] - expected) > 2) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return testList(blackList, 0) && testList(whiteList, 255);
+    };
+    function verifyDraw(drawNumber, s) {
+        gl.clearColor(1.0, 1.0, 1.0, 1.0);
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
+
+        var blackList = [];
+        var whiteList = [];
+        var points = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
+        for (var n = 0; n < points.length; n++) {
+            if (points[n] <= s) {
+                blackList.push(points[n]);
+            } else {
+                whiteList.push(points[n]);
+            }
+        }
+        if (testPixel(blackList, whiteList)) {
+            testPassed("Draw " + drawNumber + " passed pixel test");
+        } else {
+            testFailed("Draw " + drawNumber + " failed pixel test");
+        }
+    };
+
+    setupDraw(0.5);
+    verifyDraw(0, 0.5);
+}
+
+function runIndexValidationTests(drawType) {
+    description("Tests that index validation verifies the correct number of indices");
+
+    function sizeInBytes(type) {
+      switch (type) {
+      case gl.BYTE:
+      case gl.UNSIGNED_BYTE:
+        return 1;
+      case gl.SHORT:
+      case gl.UNSIGNED_SHORT:
+        return 2;
+      case gl.INT:
+      case gl.UNSIGNED_INT:
+      case gl.FLOAT:
+        return 4;
+      default:
+        throw "unknown type";
+      }
+    }
+
+    var program = wtu.loadStandardProgram(gl);
+
+    // 3 vertices => 1 triangle, interleaved data
+    var dataComplete = new Float32Array([0, 0, 0, 1,
+                                         0, 0, 1,
+                                         1, 0, 0, 1,
+                                         0, 0, 1,
+                                         1, 1, 1, 1,
+                                         0, 0, 1]);
+    var dataIncomplete = new Float32Array([0, 0, 0, 1,
+                                           0, 0, 1,
+                                           1, 0, 0, 1,
+                                           0, 0, 1,
+                                           1, 1, 1, 1]);
+    var indices = new Uint32Array([0, 1, 2]);
+
+    debug("Testing with valid indices");
+
+    var bufferComplete = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, bufferComplete);
+    gl.bufferData(gl.ARRAY_BUFFER, dataComplete, drawType);
+    var elements = gl.createBuffer();
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+    gl.useProgram(program);
+    var vertexLoc = gl.getAttribLocation(program, "a_vertex");
+    var normalLoc = gl.getAttribLocation(program, "a_normal");
+    gl.vertexAttribPointer(vertexLoc, 4, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 0);
+    gl.enableVertexAttribArray(vertexLoc);
+    gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 4 * sizeInBytes(gl.FLOAT));
+    gl.enableVertexAttribArray(normalLoc);
+    shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+    glErrorShouldBe(gl, gl.NO_ERROR);
+
+    debug("Testing with out-of-range indices");
+
+    var bufferIncomplete = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, bufferIncomplete);
+    gl.bufferData(gl.ARRAY_BUFFER, dataIncomplete, drawType);
+    gl.vertexAttribPointer(vertexLoc, 4, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 0);
+    gl.enableVertexAttribArray(vertexLoc);
+    gl.disableVertexAttribArray(normalLoc);
+    debug("Enable vertices, valid");
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    debug("Enable normals, out-of-range");
+    gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 4 * sizeInBytes(gl.FLOAT));
+    gl.enableVertexAttribArray(normalLoc);
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+    glErrorShouldBe(gl, gl.INVALID_OPERATION);
+
+    debug("Test with enabled attribute that does not belong to current program");
+
+    gl.disableVertexAttribArray(normalLoc);
+    var extraLoc = Math.max(vertexLoc, normalLoc) + 1;
+    gl.enableVertexAttribArray(extraLoc);
+    debug("Enable an extra attribute with null");
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+    glErrorShouldBe(gl, gl.INVALID_OPERATION);
+    debug("Enable an extra attribute with insufficient data buffer");
+    gl.vertexAttribPointer(extraLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 4 * sizeInBytes(gl.FLOAT));
+    glErrorShouldBe(gl, gl.NO_ERROR);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+    debug("Pass large negative index to vertexAttribPointer");
+    gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), -2000000000 * sizeInBytes(gl.FLOAT));
+    glErrorShouldBe(gl, gl.INVALID_VALUE);
+    shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+}
+
+function runCopiesIndicesTests(drawType) {
+    debug("Test that client data is always copied during bufferData and bufferSubData calls");
+
+    var program = wtu.loadStandardProgram(gl);
+
+    gl.useProgram(program);
+    var vertexObject = gl.createBuffer();
+    gl.enableVertexAttribArray(0);
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    // 4 vertices -> 2 triangles
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0,0, 0,1,0, 1,0,0, 1,1,0 ]), drawType);
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+    var indexObject = gl.createBuffer();
+
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+    var indices = new Uint32Array([ 10000, 0, 1, 2, 3, 10000 ]);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+    shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 4)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 0)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 8)");
+    indices[0] = 2;
+    indices[5] = 1;
+    shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 4)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 0)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 8)");
+}
+
+function runResizedBufferTests(drawType) {
+    debug("Test that updating the size of a vertex buffer is properly noticed by the WebGL implementation.");
+
+    var program = wtu.setupProgram(gl, ["vs", "fs"], ["vPosition", "vColor"]);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after initialization");
+
+    var vertexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
+        [-1,1,0, 1,1,0, -1,-1,0,
+         -1,-1,0, 1,1,0, 1,-1,0]), drawType);
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after vertex setup");
+
+    var texCoordObject = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordObject);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
+        [0,0, 1,0, 0,1,
+         0,1, 1,0, 1,1]), drawType);
+    gl.enableVertexAttribArray(1);
+    gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after texture coord setup");
+
+    // Now resize these buffers because we want to change what we're drawing.
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+        -1,1,0, 1,1,0, -1,-1,0, 1,-1,0,
+        -1,1,0, 1,1,0, -1,-1,0, 1,-1,0]), drawType);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after vertex redefinition");
+    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordObject);
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([
+        255, 0, 0, 255,
+        255, 0, 0, 255,
+        255, 0, 0, 255,
+        255, 0, 0, 255,
+        0, 255, 0, 255,
+        0, 255, 0, 255,
+        0, 255, 0, 255,
+        0, 255, 0, 255]), drawType);
+    gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, false, 0, 0);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after texture coordinate / color redefinition");
+
+    var numQuads = 2;
+    var indices = new Uint32Array(numQuads * 6);
+    for (var ii = 0; ii < numQuads; ++ii) {
+        var offset = ii * 6;
+        var quad = (ii == (numQuads - 1)) ? 4 : 0;
+        indices[offset + 0] = quad + 0;
+        indices[offset + 1] = quad + 1;
+        indices[offset + 2] = quad + 2;
+        indices[offset + 3] = quad + 2;
+        indices[offset + 4] = quad + 1;
+        indices[offset + 5] = quad + 3;
+    }
+    var indexObject = gl.createBuffer();
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after setting up indices");
+    gl.drawElements(gl.TRIANGLES, numQuads * 6, gl.UNSIGNED_INT, 0);
+    glErrorShouldBe(gl, gl.NO_ERROR, "after drawing");
+}
+
+function runVerifiesTooManyIndicesTests(drawType) {
+    description("Tests that index validation for drawElements does not examine too many indices");
+
+    var program = wtu.loadStandardProgram(gl);
+
+    gl.useProgram(program);
+    var vertexObject = gl.createBuffer();
+    gl.enableVertexAttribArray(0);
+    gl.disableVertexAttribArray(1);
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+    // 4 vertices -> 2 triangles
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0,0, 0,1,0, 1,0,0, 1,1,0 ]), drawType);
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+    var indexObject = gl.createBuffer();
+
+    debug("Test out of range indices")
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array([ 10000, 0, 1, 2, 3, 10000 ]), drawType);
+    shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 4)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 0)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_INT, 8)");
+}
+
+function runCrashWithBufferSubDataTests(drawType) {
+    debug('Verifies that the index validation code which is within bufferSubData does not crash.')
+
+    var elementBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 256, drawType);
+    var data = new Uint32Array(127);
+    gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 64, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "after attempting to update a buffer outside of the allocated bounds");
+    testPassed("bufferSubData, when buffer object was initialized with null, did not crash");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/oes-standard-derivatives.html b/conformance/extensions/oes-standard-derivatives.html
new file mode 100644
index 0000000..7ee2255
--- /dev/null
+++ b/conformance/extensions/oes-standard-derivatives.html
@@ -0,0 +1,384 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_standard_derivatives Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing standard derivatives -->
+
+<!-- Shader omitting the required #extension pragma -->
+<script id="missingPragmaFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec2 texCoord;
+void main() {
+    float dx = dFdx(texCoord.x);
+    float dy = dFdy(texCoord.y);
+    float w = fwidth(texCoord.x);
+    gl_FragColor = vec4(dx, dy, w, 1.0);
+}
+</script>
+
+<!-- Shader to test macro definition -->
+<script id="macroFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+#ifdef GL_OES_standard_derivatives
+    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
+#else
+    // Error expected
+    #error no GL_OES_standard_derivatives;
+#endif
+}
+</script>
+
+<!-- Shader with required #extension pragma -->
+<script id="testFragmentShader" type="x-shader/x-fragment">
+#extension GL_OES_standard_derivatives : enable
+precision mediump float;
+varying vec2 texCoord;
+void main() {
+    float dx = dFdx(texCoord.x);
+    float dy = dFdy(texCoord.y);
+    float w = fwidth(texCoord.x);
+    gl_FragColor = vec4(dx, dy, w, 1.0);
+}
+</script>
+<!-- Shaders to link with test fragment shaders -->
+<script id="goodVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+varying vec2 texCoord;
+void main() {
+    texCoord = vPosition.xy;
+    gl_Position = vPosition;
+}
+</script>
+<!-- Shaders to test output -->
+<script id="outputVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+varying vec4 position;
+void main() {
+    position = vPosition;
+    gl_Position = vPosition;
+}
+</script>
+<script id="outputFragmentShader" type="x-shader/x-fragment">
+#extension GL_OES_standard_derivatives : enable
+precision mediump float;
+varying vec4 position;
+void main() {
+    float dzdx = dFdx(position.z);
+    float dzdy = dFdy(position.z);
+    float fw = fwidth(position.z);
+    gl_FragColor = vec4(abs(dzdx) * 40.0, abs(dzdy) * 40.0, fw * 40.0, 1.0);
+}
+</script>
+
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_standard_derivatives extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runHintTestDisabled();
+    runShaderTests(false);
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = gl.getExtension("OES_standard_derivatives");
+    if (!ext) {
+        testPassed("No OES_standard_derivatives support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled OES_standard_derivatives extension");
+
+        runSupportedTest(true);
+
+        runHintTestEnabled();
+        runShaderTests(true);
+        runOutputTests();
+        runUniqueObjectTest();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("OES_standard_derivatives") >= 0) {
+        if (extensionEnabled) {
+            testPassed("OES_standard_derivatives listed as supported and getExtension succeeded");
+        } else {
+            testFailed("OES_standard_derivatives listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("OES_standard_derivatives not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("OES_standard_derivatives not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runHintTestDisabled() {
+    debug("Testing FRAGMENT_SHADER_DERIVATIVE_HINT_OES with extension disabled");
+
+    // Use the constant directly as we don't have the extension
+    var FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B;
+
+    gl.getParameter(FRAGMENT_SHADER_DERIVATIVE_HINT_OES);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "FRAGMENT_SHADER_DERIVATIVE_HINT_OES should not be queryable if extension is disabled");
+
+    gl.hint(FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl.DONT_CARE);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "hint should not accept FRAGMENT_SHADER_DERIVATIVE_HINT_OES if extension is disabled");
+}
+
+function runHintTestEnabled() {
+    debug("Testing FRAGMENT_SHADER_DERIVATIVE_HINT_OES with extension enabled");
+
+    shouldBe("ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES", "0x8B8B");
+
+    gl.getParameter(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES);
+    glErrorShouldBe(gl, gl.NO_ERROR, "FRAGMENT_SHADER_DERIVATIVE_HINT_OES query should succeed if extension is enabled");
+
+    // Default value is DONT_CARE
+    if (gl.getParameter(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES) == gl.DONT_CARE) {
+        testPassed("Default value of FRAGMENT_SHADER_DERIVATIVE_HINT_OES is DONT_CARE");
+    } else {
+        testFailed("Default value of FRAGMENT_SHADER_DERIVATIVE_HINT_OES is not DONT_CARE");
+    }
+
+    // Ensure that we can set the target
+    gl.hint(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl.DONT_CARE);
+    glErrorShouldBe(gl, gl.NO_ERROR, "hint should accept FRAGMENT_SHADER_DERIVATIVE_HINT_OES");
+
+    // Test all the hint modes
+    var validModes = ["FASTEST", "NICEST", "DONT_CARE"];
+    var anyFailed = false;
+    for (var n = 0; n < validModes.length; n++) {
+        var mode = validModes[n];
+        gl.hint(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl[mode]);
+        if (gl.getParameter(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES) != gl[mode]) {
+            testFailed("Round-trip of hint()/getParameter() failed on mode " + mode);
+            anyFailed = true;
+        }
+    }
+    if (!anyFailed) {
+        testPassed("Round-trip of hint()/getParameter() with all supported modes");
+    }
+}
+
+function runShaderTests(extensionEnabled) {
+    debug("");
+    debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+    // Expect the macro shader to succeed ONLY if enabled
+    var macroFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "macroFragmentShader");
+    if (extensionEnabled) {
+        if (macroFragmentProgram) {
+            // Expected result
+            testPassed("GL_OES_standard_derivatives defined in shaders when extension is enabled");
+        } else {
+            testFailed("GL_OES_standard_derivatives not defined in shaders when extension is enabled");
+        }
+    } else {
+        if (macroFragmentProgram) {
+            testFailed("GL_OES_standard_derivatives defined in shaders when extension is disabled");
+        } else {
+            testPassed("GL_OES_standard_derivatives not defined in shaders when extension disabled");
+        }
+    }
+
+    // Always expect the shader missing the #pragma to fail (whether enabled or not)
+    var missingPragmaFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "missingPragmaFragmentShader");
+    if (missingPragmaFragmentProgram) {
+        testFailed("Shader built-ins allowed without #extension pragma");
+    } else {
+        testPassed("Shader built-ins disallowed without #extension pragma");
+    }
+
+    // Try to compile a shader using the built-ins that should only succeed if enabled
+    var testFragmentProgram = wtu.loadProgramFromScriptExpectError(gl, "goodVertexShader", "testFragmentShader");
+    if (extensionEnabled) {
+        if (testFragmentProgram) {
+            testPassed("Shader built-ins compiled successfully when extension enabled");
+        } else {
+            testFailed("Shader built-ins failed to compile when extension enabled");
+        }
+    } else {
+        if (testFragmentProgram) {
+            testFailed("Shader built-ins compiled successfully when extension disabled");
+        } else {
+            testPassed("Shader built-ins failed to compile when extension disabled");
+        }
+    }
+}
+
+function runOutputTests() {
+    // This tests does several draws with various values of z.
+    // The output of the fragment shader is:
+    // [dFdx(z), dFdy(z), fwidth(z), 1.0]
+    // The expected math: (note the conversion to uint8)
+    //    canvas.width = canvas.height = 50
+    //    dFdx = totalChange.x / canvas.width  = 0.5 / 50.0 = 0.01
+    //    dFdy = totalChange.y / canvas.height = 0.5 / 50.0 = 0.01
+    //    fw = abs(dFdx + dFdy) = 0.01 + 0.01 = 0.02
+    //    r = floor(dFdx * 40.0 * 255) = 102
+    //    g = floor(dFdy * 40.0 * 255) = 102
+    //    b = floor(fw * 40.0 * 255) = 204
+
+    var e = 5; // Amount of variance to allow in result pixels - may need to be tweaked higher
+
+    debug("Testing various draws for valid built-in function behavior");
+
+    canvas.width = 50; canvas.height = 50;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    gl.hint(ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES, gl.NICEST);
+
+    var positionLoc = 0;
+    var texcoordLoc = 1;
+    var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"], ['vPosition', 'texCoord0'], [0, 1]);
+    var quadParameters = wtu.setupUnitQuad(gl, positionLoc, texcoordLoc);
+
+    function readLocation(x, y) {
+        var pixels = new Uint8Array(1 * 1 * 4);
+        var px = Math.floor(x * canvas.width);
+        var py = Math.floor(y * canvas.height);
+        gl.readPixels(px, py, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+        return pixels;
+    };
+    function toString(arr) {
+        var s = "[";
+        for (var n = 0; n < arr.length; n++) {
+            s += arr[n];
+            if (n < arr.length - 1) {
+                s += ", ";
+            }
+        }
+        return s + "]";
+    };
+    function expectResult(target, successMessage, failureMessage) {
+        var locations = [
+            readLocation(0.1, 0.1),
+            readLocation(0.9, 0.1),
+            readLocation(0.1, 0.9),
+            readLocation(0.9, 0.9),
+            readLocation(0.5, 0.5)
+        ];
+        var anyDiffer = false;
+        for (var n = 0; n < locations.length; n++) {
+            var source = locations[n];
+            for (var m = 0; m < 4; m++) {
+                if (Math.abs(source[m] - target[m]) > e) {
+                    anyDiffer = true;
+                    testFailed(failureMessage + "; should be " + toString(target) + ", was " + toString(source));
+                    break;
+                }
+            }
+        }
+        if (!anyDiffer) {
+            testPassed(successMessage);
+        }
+    };
+
+    function setupBuffers(tl, tr, bl, br) {
+        gl.bindBuffer(gl.ARRAY_BUFFER, quadParameters[0]);
+        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+           1.0,  1.0, tr,
+          -1.0,  1.0, tl,
+          -1.0, -1.0, bl,
+           1.0,  1.0, tr,
+          -1.0, -1.0, bl,
+           1.0, -1.0, br]), gl.STATIC_DRAW);
+        gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
+    };
+
+    // Draw 1: (no variation)
+    setupBuffers(0.0, 0.0, 0.0, 0.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    expectResult([0, 0, 0, 255],
+                 "Draw 1 (no variation) returned the correct data",
+                 "Draw 1 (no variation) returned incorrect data");
+
+    // Draw 2: (variation in x)
+    setupBuffers(1.0, 0.0, 1.0, 0.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    expectResult([204, 0, 204, 255],
+                 "Draw 2 (variation in x) returned the correct data",
+                 "Draw 2 (variation in x) returned incorrect data");
+
+    // Draw 3: (variation in y)
+    setupBuffers(1.0, 1.0, 0.0, 0.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    expectResult([0, 204, 204, 255],
+                 "Draw 3 (variation in y) returned the correct data",
+                 "Draw 3 (variation in y) returned incorrect data");
+
+    // Draw 4: (variation in x & y)
+    setupBuffers(1.0, 0.5, 0.5, 0.0);
+    wtu.clearAndDrawUnitQuad(gl);
+    expectResult([102, 102, 204, 255],
+                 "Draw 4 (variation in x & y) returned the correct data",
+                 "Draw 4 (variation in x & y) returned incorrect data");
+
+}
+
+function runUniqueObjectTest()
+{
+    debug("Testing that getExtension() returns the same object each time");
+    gl.getExtension("OES_standard_derivatives").myProperty = 2;
+    gc();
+    shouldBe('gl.getExtension("OES_standard_derivatives").myProperty', '2');
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float-linear.html b/conformance/extensions/oes-texture-float-linear.html
new file mode 100644
index 0000000..3008091
--- /dev/null
+++ b/conformance/extensions/oes-texture-float-linear.html
@@ -0,0 +1,54 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/oes-texture-float-and-half-float-linear.js"></script>
+<script>
+"use strict";
+function testPrologue(gl, extensionTypeName) {
+    if (!gl.getExtension(extensionTypeName)) {
+        testPassed("No " + extensionTypeName + " support -- this is legal");
+        return false;
+    }
+    testPassed("Successfully enabled " + extensionTypeName + " extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("OES_texture_float", "OES_texture_float_linear", "FLOAT", testPrologue)()'>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float-with-canvas.html b/conformance/extensions/oes-texture-float-with-canvas.html
new file mode 100644
index 0000000..d343e86
--- /dev/null
+++ b/conformance/extensions/oes-texture-float-with-canvas.html
@@ -0,0 +1,55 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-canvas.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    if (!gl.getExtension("OES_texture_float")) {
+        testPassed("No OES_texture_float support -- this is legal");
+        return false;
+    }
+
+    testPassed("Successfully enabled OES_texture_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "FLOAT", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float-with-image-data.html b/conformance/extensions/oes-texture-float-with-image-data.html
new file mode 100644
index 0000000..e81cfb3
--- /dev/null
+++ b/conformance/extensions/oes-texture-float-with-image-data.html
@@ -0,0 +1,56 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-image-data.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    if (!gl.getExtension("OES_texture_float")) {
+        testPassed("No OES_texture_float support -- this is legal");
+        return false;
+    }
+
+    testPassed("Successfully enabled OES_texture_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "FLOAT", testPrologue)()'>
+<canvas id="texcanvas" width="1" height="2"></canvas>
+<canvas id="example" width="1" height="2"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float-with-image.html b/conformance/extensions/oes-texture-float-with-image.html
new file mode 100644
index 0000000..bd4bdc9
--- /dev/null
+++ b/conformance/extensions/oes-texture-float-with-image.html
@@ -0,0 +1,55 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-image.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    if (!gl.getExtension("OES_texture_float")) {
+        testPassed("No OES_texture_float support -- this is legal");
+        return false;
+    }
+
+    testPassed("Successfully enabled OES_texture_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "FLOAT", "..", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float-with-video.html b/conformance/extensions/oes-texture-float-with-video.html
new file mode 100644
index 0000000..0deb7ae
--- /dev/null
+++ b/conformance/extensions/oes-texture-float-with-video.html
@@ -0,0 +1,60 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-video.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    if (!gl.getExtension("OES_texture_float")) {
+        testPassed("No OES_texture_float support -- this is legal");
+        return false;
+    }
+
+    testPassed("Successfully enabled OES_texture_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "FLOAT", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<video width="640" height="228" id="vid" controls>
+  <source src="../resources/red-green.mp4"  type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
+  <source src="../resources/red-green.webmvp8.webm" type='video/webm; codecs="vp8, vorbis"' />
+  <source src="../resources/red-green.theora.ogv"  type='video/ogg; codecs="theora, vorbis"' />
+</video>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-float.html b/conformance/extensions/oes-texture-float.html
new file mode 100644
index 0000000..a6af325
--- /dev/null
+++ b/conformance/extensions/oes-texture-float.html
@@ -0,0 +1,231 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_texture_float Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing floating-point textures -->
+<script id="testFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D tex;
+uniform vec4 subtractor;
+varying vec2 texCoord;
+void main()
+{
+    vec4 color = texture2D(tex, texCoord);
+    if (abs(color.r - subtractor.r) +
+        abs(color.g - subtractor.g) +
+        abs(color.b - subtractor.b) +
+        abs(color.a - subtractor.a) < 8.0) {
+        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+    } else {
+        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    }
+}
+</script>
+<!-- Shaders for testing floating-point render targets -->
+<script id="positionVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main()
+{
+    gl_Position = vPosition;
+}
+</script>
+<script id="floatingPointFragmentShader" type="x-shader/x-fragment">
+void main()
+{
+    gl_FragColor = vec4(10000.0, 10000.0, 10000.0, 10000.0);
+}
+</script>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_texture_float extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+
+if (!gl) {
+  testFailed("WebGL context does not exist");
+} else {
+  testPassed("WebGL context exists");
+
+  var texturedShaders = [
+      wtu.setupSimpleTextureVertexShader(gl),
+      "testFragmentShader"
+  ];
+  var testProgram =
+      wtu.setupProgram(gl,
+                       texturedShaders,
+                       ['vPosition', 'texCoord0'],
+                       [0, 1]);
+  var quadParameters = wtu.setupUnitQuad(gl, 0, 1);
+
+  // First verify that allocation of floating-point textures fails if
+  // the extension has not been enabled yet.
+  runTextureCreationTest(testProgram, false);
+
+  if (!gl.getExtension("OES_texture_float")) {
+      testPassed("No OES_texture_float support -- this is legal");
+  } else {
+      testPassed("Successfully enabled OES_texture_float extension");
+      runTextureCreationTest(testProgram, true, gl.RGBA, 4, [10000, 10000, 10000, 10000]);
+      runTextureCreationTest(testProgram, true, gl.RGB, 3, [10000, 10000, 10000, 0]);
+      runTextureCreationTest(testProgram, true, gl.LUMINANCE, 1, [10000, 10000, 10000, 0]);
+      runTextureCreationTest(testProgram, true, gl.ALPHA, 1, [0, 0, 0, 10000]);
+      runTextureCreationTest(testProgram, true, gl.LUMINANCE_ALPHA, 2, [10000, 10000, 10000, 10000]);
+      runRenderTargetTest(testProgram);
+      runUniqueObjectTest();
+  }
+}
+
+function allocateTexture()
+{
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    glErrorShouldBe(gl, gl.NO_ERROR, "texture parameter setup should succeed");
+    return texture;
+}
+
+function checkRenderingResults()
+{
+    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+function runTextureCreationTest(testProgram, extensionEnabled, opt_format, opt_numChannels, opt_subtractor)
+{
+    var format = opt_format || gl.RGBA;
+    var numberOfChannels = opt_numChannels || 4;
+    var expectFailure = !extensionEnabled;
+    var subtractor = opt_subtractor || [10000, 10000, 10000, 10000];
+
+    debug("");
+    debug("testing format: " + wtu.glEnumToString(gl, format) +
+          " expect:" + (extensionEnabled ? "success" : "failure"));
+
+    var texture = allocateTexture();
+    // Generate data.
+    var width = 2;
+    var height = 2;
+    var data = new Float32Array(width * height * numberOfChannels);
+    for (var ii = 0; ii < data.length; ++ii) {
+        data[ii] = 10000;
+    }
+    gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.FLOAT, data);
+    if (expectFailure) {
+        glErrorShouldBe(gl, gl.INVALID_ENUM, "floating-point texture allocation must be disallowed if OES_texture_float isn't enabled");
+        return;
+    } else {
+        glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed if OES_texture_float is enabled");
+    }
+    // Verify that the texture actually works for sampling and contains the expected data.
+    gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), subtractor);
+    wtu.clearAndDrawUnitQuad(gl);
+    checkRenderingResults();
+
+    // Check that linear fails.
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    wtu.clearAndDrawUnitQuad(gl);
+    wtu.checkCanvas(gl, [255, 0, 0, 255], "should be red");
+}
+
+function runRenderTargetTest(testProgram)
+{
+    debug("");
+    debug("testing floating-point render targets");
+
+    var texture = allocateTexture();
+    var width = 2;
+    var height = 2;
+    var numberOfChannels = 4;
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
+    glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed if OES_texture_float is enabled");
+
+    // Try to use this texture as a render target.
+    var fbo = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    // It is legal for a WebGL implementation exposing the OES_texture_float extension to
+    // support floating-point textures but not as attachments to framebuffer objects.
+    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+        debug("floating-point render targets not supported -- this is legal");
+        return;
+    }
+
+    var renderProgram =
+        wtu.setupProgram(gl,
+                         ["positionVertexShader", "floatingPointFragmentShader"],
+                         ['vPosition'],
+                         [0]);
+    wtu.clearAndDrawUnitQuad(gl);
+    glErrorShouldBe(gl, gl.NO_ERROR, "rendering to floating-point texture should succeed");
+
+    // Now sample from the floating-point texture and verify we got the correct values.
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.useProgram(testProgram);
+    gl.uniform1i(gl.getUniformLocation(testProgram, "tex"), 0);
+    wtu.clearAndDrawUnitQuad(gl);
+    glErrorShouldBe(gl, gl.NO_ERROR, "rendering from floating-point texture should succeed");
+    checkRenderingResults();
+}
+
+function runUniqueObjectTest()
+{
+    debug("Testing that getExtension() returns the same object each time");
+    gl.getExtension("OES_texture_float").myProperty = 2;
+    gc();
+    shouldBe('gl.getExtension("OES_texture_float").myProperty', '2');
+}
+
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float-linear.html b/conformance/extensions/oes-texture-half-float-linear.html
new file mode 100644
index 0000000..a24a918
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float-linear.html
@@ -0,0 +1,57 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/oes-texture-float-and-half-float-linear.js"></script>
+<script>
+"use strict";
+function testPrologue(gl, extensionTypeName) {
+    var ext = null;
+    if (!(ext = gl.getExtension(extensionTypeName))) {
+        testPassed("No " + extensionTypeName + " support -- this is legal");
+        return false;
+    }
+    // Required by the test harness.
+    gl.HALF_FLOAT_OES = ext.HALF_FLOAT_OES;
+    testPassed("Successfully enabled " + extensionTypeName + " extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("OES_texture_half_float", "OES_texture_half_float_linear", "HALF_FLOAT_OES", testPrologue)()'>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float-with-canvas.html b/conformance/extensions/oes-texture-half-float-with-canvas.html
new file mode 100644
index 0000000..fe7a7bf
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float-with-canvas.html
@@ -0,0 +1,60 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-canvas.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    var ext = null;
+
+    if (!(ext = gl.getExtension("OES_texture_half_float"))) {
+        testPassed("No OES_texture_half_float support -- this is legal");
+        return false;
+    }
+
+    // Required by the test harness.
+    gl.HALF_FLOAT_OES = ext.HALF_FLOAT_OES;
+
+    testPassed("Successfully enabled OES_texture_half_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "HALF_FLOAT_OES", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float-with-image-data.html b/conformance/extensions/oes-texture-half-float-with-image-data.html
new file mode 100644
index 0000000..8659bd2
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float-with-image-data.html
@@ -0,0 +1,61 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-image-data.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    var ext = null;
+
+    if (!(ext = gl.getExtension("OES_texture_half_float"))) {
+        testPassed("No OES_texture_half_float support -- this is legal");
+        return false;
+    }
+
+    // Required by the test harness.
+    gl.HALF_FLOAT_OES = ext.HALF_FLOAT_OES;
+
+    testPassed("Successfully enabled OES_texture_half_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "HALF_FLOAT_OES", testPrologue)()'>
+<canvas id="texcanvas" width="1" height="2"></canvas>
+<canvas id="example" width="1" height="2"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float-with-image.html b/conformance/extensions/oes-texture-half-float-with-image.html
new file mode 100644
index 0000000..2f2ebf1
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float-with-image.html
@@ -0,0 +1,60 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-image.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    var ext = null;
+
+    if (!(ext = gl.getExtension("OES_texture_half_float"))) {
+        testPassed("No OES_texture_half_float support -- this is legal");
+        return false;
+    }
+
+    // Required by the test harness.
+    gl.HALF_FLOAT_OES = ext.HALF_FLOAT_OES;
+
+    testPassed("Successfully enabled OES_texture_half_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "HALF_FLOAT_OES", "..", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float-with-video.html b/conformance/extensions/oes-texture-half-float-with-video.html
new file mode 100644
index 0000000..17ba65d
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float-with-video.html
@@ -0,0 +1,65 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<script src="../resources/tex-image-and-sub-image-2d-with-video.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+    var ext = null;
+
+    if (!(ext = gl.getExtension("OES_texture_half_float"))) {
+        testPassed("No OES_texture_half_float support -- this is legal");
+        return false;
+    }
+
+    // Required by the test harness.
+    gl.HALF_FLOAT_OES = ext.HALF_FLOAT_OES;
+
+    testPassed("Successfully enabled OES_texture_half_float extension");
+    return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "HALF_FLOAT_OES", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<video width="640" height="228" id="vid" controls>
+  <source src="../resources/red-green.mp4"  type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
+  <source src="../resources/red-green.webmvp8.webm" type='video/webm; codecs="vp8, vorbis"' />
+  <source src="../resources/red-green.theora.ogv"  type='video/ogg; codecs="theora, vorbis"' />
+</video>
+</body>
+</html>
diff --git a/conformance/extensions/oes-texture-half-float.html b/conformance/extensions/oes-texture-half-float.html
new file mode 100644
index 0000000..f74d4ad
--- /dev/null
+++ b/conformance/extensions/oes-texture-half-float.html
@@ -0,0 +1,302 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_texture_half_float Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<canvas id="canvas2d" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<script id="testFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D tex;
+uniform vec4 subtractor;
+varying vec2 texCoord;
+void main()
+{
+    vec4 color = texture2D(tex, texCoord);
+    if (abs(color.r - subtractor.r) +
+        abs(color.g - subtractor.g) +
+        abs(color.b - subtractor.b) +
+        abs(color.a - subtractor.a) < 8.0) {
+        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+    } else {
+        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    }
+}
+</script>
+<!-- Shaders for testing half-floating-point render targets -->
+<script id="positionVertexShader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+void main()
+{
+    gl_Position = vPosition;
+}
+</script>
+<script id="floatingPointFragmentShader" type="x-shader/x-fragment">
+void main()
+{
+    gl_FragColor = vec4(10000.0, 10000.0, 10000.0, 10000.0);
+}
+</script>
+<script>
+"use strict"
+description("This test verifies the functionality of OES_texture_half_float with null/non-null ArrayBufferView");
+
+debug("");
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var colorCanvas = document.getElementById("canvas2d");
+colorCanvas.width = 2;
+colorCanvas.height = 2;
+var ctx = colorCanvas.getContext("2d");
+ctx.fillStyle = "rgb(255,0,0)";
+ctx.fillRect(0, 0, 2, 2);
+var gl = wtu.create3DContext(canvas);
+// This constant must be defined in order to run the texture creation test without the extension enabled.
+var halfFloatOESEnum = 0x8D61;
+var ext = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exists");
+} else {
+    testPassed("WebGL context exists");
+
+    // Verify that allocation of texture fails if extension is not enabled
+    runTextureCreationTest(false);
+    ext = gl.getExtension("OES_texture_half_float")
+    if (!ext) {
+        testPassed("No OES_texture_half_float support. This is legal");
+    } else {
+        testPassed("Successfully enabled OES_texture_half_float extension");
+
+        var program = wtu.setupTexturedQuad(gl);
+
+        // Check if creation of texture succeed's with various formats and null ArrayBufferView
+        var formats = [
+          { format: gl.RGBA,            expected: [255,   0,   0, 255], },
+          { format: gl.RGB,             expected: [255,   0,   0, 255], },
+          { format: gl.LUMINANCE,       expected: [255, 255, 255, 255], },
+          { format: gl.ALPHA,           expected: [  0,   0,   0, 255], },
+          { format: gl.LUMINANCE_ALPHA, expected: [255, 255, 255, 255], },
+        ];
+        formats.forEach(function(f) {
+            runTextureCreationTest(true, f.format, null, f.expected);
+        });
+        
+        // Texture creation should fail when passed with non-null ArrayBufferView
+        formats.forEach(function(f) {
+            var width = 2;
+            var height = 2;
+            var format = f.format;
+            
+            // Float32Array
+            var float32Data = new Float32Array(width * height * getNumberOfChannels(format));
+            for (var ii = 0; ii < float32Data.length; ii++) {
+                float32Data[ii] = 1000;
+            }
+            runTextureCreationTest(true, format, float32Data);
+
+            // Int16Array
+            var int16Data = new Int16Array(width * height * getNumberOfChannels(format));
+            for (var ii = 0; ii <  int16Data.length; ii++) {
+                int16Data[ii] = 1000;
+            }
+            runTextureCreationTest(true, format, int16Data);
+
+            // Uint16Array
+            var uint16Data = new Uint16Array(width * height * getNumberOfChannels(format));
+            for (var ii = 0; ii <  uint16Data.length; ii++) {
+                uint16Data[ii] = 1000;
+            }
+            runTextureCreationTest(true, format, uint16Data);
+        });
+
+        // Check if attaching texture as FBO target succeeds (Not mandatory)
+        runRenderTargetTest();
+        // Check of getExtension() returns same object
+        runUniqueObjectTest();
+    }
+}
+
+function getNumberOfChannels(format)
+{
+    if (format == gl.RGBA)
+        return 4;
+    else if (format == gl.RGB)
+        return 3;
+    else if (format == gl.LUMINANCE || format == gl.ALPHA)
+        return 1;
+    else if (format == gl.LUMINANCE_ALPHA)
+        return 2;
+}
+
+function getFormatName(format)
+{
+    if (format == gl.RGBA)
+        return "RGBA";
+    else if (format == gl.RGB)
+        return "RGB";
+    else if (format == gl.LUMINANCE)
+        return "LUMINANCE";
+    else if (format == gl.ALPHA)
+        return "ALPHA";
+    else if (format == gl.LUMINANCE_ALPHA)
+        return "LUMINANCE_ALPHA";
+}
+
+function allocateTexture()
+{
+    var texture = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    glErrorShouldBe(gl, gl.NO_ERROR, "texture parameter setup should succeed");
+    return texture;
+}
+
+function runTextureCreationTest(extensionEnabled, opt_format, opt_data, opt_expected)
+{
+    var format = opt_format || gl.RGBA;
+    var data = opt_data || null;
+    var expectSuccess = true;
+    
+    if (!extensionEnabled || data)
+        expectSuccess = false;
+    debug("Testing texture creation with extension " + (extensionEnabled ? "enabled" : "disabled") +
+          ", format " + getFormatName(format) + ", and data " + (data ? "non-null" : "null") +
+          ". Expect " + (expectSuccess ? "Success" : "Failure"));   
+
+    var texture = allocateTexture();
+    var width = 2;
+    var height = 2;
+    gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, halfFloatOESEnum, data);
+    if(!extensionEnabled) {
+        glErrorShouldBe(gl, gl.INVALID_ENUM, "Half floating point texture must be diallowed if OES_texture_half_float isn't enabled");
+        return;
+    } else if (data) {
+        glErrorShouldBe(gl, gl.INVALID_OPERATION, "Half floating point texture allocation must be diallowed when ArrayBufferView is not-null");
+        return;
+    } else {
+        glErrorShouldBe(gl, gl.NO_ERROR, "Half floating point texture allocation should succeed if OES_texture_half_float is enabled");
+
+        gl.texImage2D(gl.TEXTURE_2D, 0, format, format, halfFloatOESEnum, colorCanvas);
+        wtu.clearAndDrawUnitQuad(gl);
+        wtu.checkCanvas(gl, opt_expected);
+        // Check that linear fails.
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+        wtu.clearAndDrawUnitQuad(gl);
+        wtu.checkCanvas(gl, [0, 0, 0, 255], "should be black");
+    }
+
+}
+
+function checkRenderingResults()
+{
+    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+function runRenderTargetTest(testProgram)
+{
+    debug("");
+    debug("Testing half floating point render target");
+
+    var texture = allocateTexture();
+    var width = 2;
+    var height = 2;
+    
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, ext.HALF_FLOAT_OES, null);
+    glErrorShouldBe(gl, gl.NO_ERROR, "Half floating point texture allocation should succeed if OES_texture_half_float is enabled");
+
+    // Try to use this texture as render target
+    var fbo = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+    gl.bindTexture(gl.TEXTURE_2D, null);
+
+    // It is legal for a WebGL implementation exposing the OES_texture_half_float extension to
+    // support half floating point textures but not as attachments to framebuffer objects.
+    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+        debug("Half floating point render targets not supported -- this is legal");
+        return;
+    }
+
+    var renderProgram =
+        wtu.setupProgram(gl,
+                         ["positionVertexShader", "floatingPointFragmentShader"],
+                         ['vPosition'],
+                         [0]);
+    wtu.drawUnitQuad(gl);
+    glErrorShouldBe(gl, gl.NO_ERROR, "Rendering to half floating point texture should succeed");
+
+    // Now sample from the half floating-point texture and verify we got the correct values.
+    var texturedShaders = [
+      wtu.setupSimpleTextureVertexShader(gl),
+          "testFragmentShader"
+      ];
+    var testProgram =
+        wtu.setupProgram(gl,
+                        [wtu.setupSimpleTextureVertexShader(gl), "testFragmentShader"],
+                        ['vPosition', 'texCoord0'],
+                        [0, 1]);
+    var quadParameters = wtu.setupUnitQuad(gl, 0, 1);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.useProgram(testProgram);
+    gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), [10000, 10000, 10000, 10000]);
+    wtu.drawUnitQuad(gl);
+    glErrorShouldBe(gl, gl.NO_ERROR, "rendering from half floating point texture should succeed");
+    checkRenderingResults();
+}
+
+function runUniqueObjectTest()
+{
+    debug("Testing that getExtension() returns the same object each time");
+    gl.getExtension("OES_texture_half_float").myProperty = 2;
+    gc();
+    shouldBe('gl.getExtension("OES_texture_half_float").myProperty', '2');
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/oes-vertex-array-object.html b/conformance/extensions/oes-vertex-array-object.html
new file mode 100644
index 0000000..15d6728
--- /dev/null
+++ b/conformance/extensions/oes-vertex-array-object.html
@@ -0,0 +1,654 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_vertex_array_object Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<!-- comment in the script tag below to test through JS emualation of the extension. -->
+<!--
+<script src="../../../demos/google/resources/OESVertexArrayObject.js"></script>
+-->
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 a_position;
+attribute vec4 a_color;
+varying vec4 v_color;
+void main(void) {
+    gl_Position = a_position;
+    v_color = a_color;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 v_color;
+void main(void) {
+    gl_FragColor = v_color;
+}
+</script>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_vertex_array_object extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+var vao = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Setup emulated OESVertexArrayObject if it has been included.
+    if (window.setupVertexArrayObject) {
+        debug("using emuated OES_vertex_array_object");
+        setupVertexArrayObject(gl);
+    }
+
+    // Run tests with extension disabled
+    runBindingTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = gl.getExtension("OES_vertex_array_object");
+    if (!ext) {
+        testPassed("No OES_vertex_array_object support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled OES_vertex_array_object extension");
+
+        runSupportedTest(true);
+        runBindingTestEnabled();
+        runObjectTest();
+        runAttributeTests();
+        runAttributeValueTests();
+        runDrawTests();
+        runDeleteTests();
+        runArrayBufferBindTests();
+        glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("OES_vertex_array_object") >= 0) {
+        if (extensionEnabled) {
+            testPassed("OES_vertex_array_object listed as supported and getExtension succeeded");
+        } else {
+            testFailed("OES_vertex_array_object listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("OES_vertex_array_object not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("OES_vertex_array_object not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runBindingTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+    
+    // Use the constant directly as we don't have the extension
+    var VERTEX_ARRAY_BINDING_OES = 0x85B5;
+    
+    gl.getParameter(VERTEX_ARRAY_BINDING_OES);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "VERTEX_ARRAY_BINDING_OES should not be queryable if extension is disabled");
+}
+
+function runBindingTestEnabled() {
+    debug("Testing binding enum with extension enabled");
+    
+    shouldBe("ext.VERTEX_ARRAY_BINDING_OES", "0x85B5");
+    
+    gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES);
+    glErrorShouldBe(gl, gl.NO_ERROR, "VERTEX_ARRAY_BINDING_OES query should succeed if extension is enable");
+    
+    // Default value is null
+    if (gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) === null) {
+        testPassed("Default value of VERTEX_ARRAY_BINDING_OES is null");
+    } else {
+        testFailed("Default value of VERTEX_ARRAY_BINDING_OES is not null");
+    }
+    
+    debug("Testing binding a VAO");
+    var vao0 = ext.createVertexArrayOES();
+    var vao1 = ext.createVertexArrayOES();
+    shouldBeNull("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES)");
+    ext.bindVertexArrayOES(vao0);
+    if (gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) == vao0) {
+        testPassed("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) is expected VAO");
+    } else {
+        testFailed("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) is not expected VAO")
+    }
+    ext.bindVertexArrayOES(vao1);
+    if (gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) == vao1) {
+        testPassed("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) is expected VAO");
+    } else {
+        testFailed("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES) is not expected VAO")
+    }
+    ext.deleteVertexArrayOES(vao1);
+    shouldBeNull("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES)");
+    ext.bindVertexArrayOES(vao1);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "binding a deleted vertex array object");
+    ext.bindVertexArrayOES(null);
+    shouldBeNull("gl.getParameter(ext.VERTEX_ARRAY_BINDING_OES)");
+    ext.deleteVertexArrayOES(vao1);
+}
+
+function runObjectTest() {
+    debug("Testing object creation");
+    
+    vao = ext.createVertexArrayOES();
+    glErrorShouldBe(gl, gl.NO_ERROR, "createVertexArrayOES should not set an error");
+    shouldBeNonNull("vao");
+    
+    // Expect false if never bound
+    shouldBeFalse("ext.isVertexArrayOES(vao)");
+    ext.bindVertexArrayOES(vao);
+    shouldBeTrue("ext.isVertexArrayOES(vao)");
+    ext.bindVertexArrayOES(null);
+    shouldBeTrue("ext.isVertexArrayOES(vao)");
+    
+    shouldBeFalse("ext.isVertexArrayOES(null)");
+    
+    ext.deleteVertexArrayOES(vao);
+    vao = null;
+}
+
+function runAttributeTests() {
+    debug("Testing attributes work across bindings");
+    
+    var states = [];
+    
+    var attrCount = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+    for (var n = 0; n < attrCount; n++) {
+        gl.bindBuffer(gl.ARRAY_BUFFER, null);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+        
+        var state = {};
+        states.push(state);
+        
+        var vao = state.vao = ext.createVertexArrayOES();
+        ext.bindVertexArrayOES(vao);
+        
+        var enableArray = (n % 2 == 0);
+        if (enableArray) {
+            gl.enableVertexAttribArray(n);
+        } else {
+            gl.disableVertexAttribArray(n);
+        }
+        
+        if (enableArray) {
+            var buffer = state.buffer = gl.createBuffer();
+            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+            gl.bufferData(gl.ARRAY_BUFFER, 1024, gl.STATIC_DRAW);
+            
+            gl.vertexAttribPointer(n, 1 + n % 4, gl.FLOAT, true, n * 4, n * 4);
+        }
+        
+        if (enableArray) {
+            var elbuffer = state.elbuffer = gl.createBuffer();
+            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elbuffer);
+            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 1024, gl.STATIC_DRAW);
+        }
+        
+        ext.bindVertexArrayOES(null);
+    }
+    
+    var anyMismatch = false;
+    for (var n = 0; n < attrCount; n++) {
+        var state = states[n];
+        
+        ext.bindVertexArrayOES(state.vao);
+        
+        var shouldBeEnabled = (n % 2 == 0);
+        var isEnabled = gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_ENABLED);
+        if (shouldBeEnabled != isEnabled) {
+            testFailed("VERTEX_ATTRIB_ARRAY_ENABLED not preserved");
+            anyMismatch = true;
+        }
+        
+        var buffer = gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING);
+        if (shouldBeEnabled) {
+            if (buffer == state.buffer) {
+                // Matched
+                if ((gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_SIZE) == 1 + n % 4) &&
+                    (gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_TYPE) == gl.FLOAT) &&
+                    (gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_NORMALIZED) == true) &&
+                    (gl.getVertexAttrib(n, gl.VERTEX_ATTRIB_ARRAY_STRIDE) == n * 4) &&
+                    (gl.getVertexAttribOffset(n, gl.VERTEX_ATTRIB_ARRAY_POINTER) == n * 4)) {
+                    // Matched
+                } else {
+                    testFailed("VERTEX_ATTRIB_ARRAY_* not preserved");
+                    anyMismatch = true;
+                }
+            } else {
+                testFailed("VERTEX_ATTRIB_ARRAY_BUFFER_BINDING not preserved");
+                anyMismatch = true;
+            }
+        } else {
+            // GL_CURRENT_VERTEX_ATTRIB is not preserved
+            if (buffer) {
+                testFailed("VERTEX_ATTRIB_ARRAY_BUFFER_BINDING not preserved");
+                anyMismatch = true;
+            }
+        }
+        
+        var elbuffer = gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING);
+        if (shouldBeEnabled) {
+            if (elbuffer == state.elbuffer) {
+                // Matched
+            } else {
+                testFailed("ELEMENT_ARRAY_BUFFER_BINDING not preserved");
+                anyMismatch = true;
+            }
+        } else {
+            if (elbuffer == null) {
+                // Matched
+            } else {
+                testFailed("ELEMENT_ARRAY_BUFFER_BINDING not preserved");
+                anyMismatch = true;
+            }
+        }
+    }
+    ext.bindVertexArrayOES(null);
+    if (!anyMismatch) {
+        testPassed("All attributes preserved across bindings");
+    }
+    
+    for (var n = 0; n < attrCount; n++) {
+        var state = states[n];
+        ext.deleteVertexArrayOES(state.vao);
+    }
+}
+
+function runAttributeValueTests() {
+    debug("Testing that attribute values are not attached to bindings");
+    
+    var v;
+    var vao0 = ext.createVertexArrayOES();
+    var anyFailed = false;
+    
+    ext.bindVertexArrayOES(null);
+    gl.vertexAttrib4f(0, 0, 1, 2, 3);
+    
+    v = gl.getVertexAttrib(0, gl.CURRENT_VERTEX_ATTRIB);
+    if (!(v[0] == 0 && v[1] == 1 && v[2] == 2 && v[3] == 3)) {
+        testFailed("Vertex attrib value not round-tripped?");
+        anyFailed = true;
+    }
+    
+    ext.bindVertexArrayOES(vao0);
+    
+    v = gl.getVertexAttrib(0, gl.CURRENT_VERTEX_ATTRIB);
+    if (!(v[0] == 0 && v[1] == 1 && v[2] == 2 && v[3] == 3)) {
+        testFailed("Vertex attrib value reset across bindings");
+        anyFailed = true;
+    }
+    
+    gl.vertexAttrib4f(0, 4, 5, 6, 7);
+    ext.bindVertexArrayOES(null);
+    
+    v = gl.getVertexAttrib(0, gl.CURRENT_VERTEX_ATTRIB);
+    if (!(v[0] == 4 && v[1] == 5 && v[2] == 6 && v[3] == 7)) {
+        testFailed("Vertex attrib value bound to buffer");
+        anyFailed = true;
+    }
+    
+    if (!anyFailed) {
+        testPassed("Vertex attribute values are not attached to bindings")
+    }
+    
+    ext.bindVertexArrayOES(null);
+    ext.deleteVertexArrayOES(vao0);
+}
+
+function runDrawTests() {
+    debug("Testing draws with various VAO bindings");
+    
+    canvas.width = 50; canvas.height = 50;
+    gl.viewport(0, 0, canvas.width, canvas.height);
+    
+    var vao0 = ext.createVertexArrayOES();
+    var vao1 = ext.createVertexArrayOES();
+
+    var opt_positionLocation = 0;
+    var opt_texcoordLocation = 1;
+    
+    var program = wtu.setupSimpleTextureProgram(gl, opt_positionLocation, opt_texcoordLocation);
+    
+    function setupQuad(s) {
+        var vertexObject = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+             1.0 * s,  1.0 * s, 0.0,
+            -1.0 * s,  1.0 * s, 0.0,
+            -1.0 * s, -1.0 * s, 0.0,
+             1.0 * s,  1.0 * s, 0.0,
+            -1.0 * s, -1.0 * s, 0.0,
+             1.0 * s, -1.0 * s, 0.0]), gl.STATIC_DRAW);
+        gl.enableVertexAttribArray(opt_positionLocation);
+        gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0);
+
+        var vertexObject = gl.createBuffer();
+        gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+            1.0 * s, 1.0 * s,
+            0.0 * s, 1.0 * s,
+            0.0 * s, 0.0 * s,
+            1.0 * s, 1.0 * s,
+            0.0 * s, 0.0 * s,
+            1.0 * s, 0.0 * s]), gl.STATIC_DRAW);
+        gl.enableVertexAttribArray(opt_texcoordLocation);
+        gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0);
+    };
+    
+    function readLocation(x, y) {
+        var pixels = new Uint8Array(1 * 1 * 4);
+        gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+        return pixels;
+    };
+    function testPixel(blackList, whiteList) {
+        function testList(list, expected) {
+            for (var n = 0; n < list.length; n++) {
+                var l = list[n];
+                var x = -Math.floor(l * canvas.width / 2) + canvas.width / 2;
+                var y = -Math.floor(l * canvas.height / 2) + canvas.height / 2;
+                var source = readLocation(x, y);
+                if (Math.abs(source[0] - expected) > 2) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return testList(blackList, 0) && testList(whiteList, 255);
+    };
+    function verifyDraw(drawNumber, s) {
+        wtu.clearAndDrawUnitQuad(gl);
+        var blackList = [];
+        var whiteList = [];
+        var points = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
+        for (var n = 0; n < points.length; n++) {
+            if (points[n] <= s) {
+                blackList.push(points[n]);
+            } else {
+                whiteList.push(points[n]);
+            }
+        }
+        if (testPixel(blackList, whiteList)) {
+            testPassed("Draw " + drawNumber + " passed pixel test");
+        } else {
+            testFailed("Draw " + drawNumber + " failed pixel test");
+        }
+    };
+    
+    // Setup all bindings
+    setupQuad(1);
+    ext.bindVertexArrayOES(vao0);
+    setupQuad(0.5);
+    ext.bindVertexArrayOES(vao1);
+    setupQuad(0.25);
+    
+    // Verify drawing
+    ext.bindVertexArrayOES(null);
+    verifyDraw(0, 1);
+    ext.bindVertexArrayOES(vao0);
+    verifyDraw(1, 0.5);
+    ext.bindVertexArrayOES(vao1);
+    verifyDraw(2, 0.25);
+    
+    ext.bindVertexArrayOES(null);
+    ext.deleteVertexArrayOES(vao0);
+    ext.deleteVertexArrayOES(vao1);
+
+    // Disable global vertex attrib array
+    gl.disableVertexAttribArray(opt_positionLocation);
+    gl.disableVertexAttribArray(opt_texcoordLocation);
+
+    // Draw with values.
+    var positionLoc = 0;
+    var colorLoc = 1;
+    var gridRes = 1;
+    wtu.setupIndexedQuad(gl, gridRes, positionLoc);
+    // Set the vertex color to red.
+    gl.vertexAttrib4f(colorLoc, 1, 0, 0, 1);
+
+    var vao0 = ext.createVertexArrayOES();
+    ext.bindVertexArrayOES(vao0);
+    var program = wtu.setupSimpleVertexColorProgram(gl, positionLoc, colorLoc);
+    wtu.setupIndexedQuad(gl, gridRes, positionLoc);
+    // Set the vertex color to green.
+    gl.vertexAttrib4f(colorLoc, 0, 1, 0, 1);
+    wtu.clearAndDrawIndexedQuad(gl, gridRes);
+    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green")
+    ext.deleteVertexArrayOES(vao0);
+    wtu.clearAndDrawIndexedQuad(gl, gridRes);
+    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green")
+}
+
+function runDeleteTests() {
+    debug("Testing using deleted buffers referenced by VAOs");
+
+    var program = wtu.setupProgram(gl, ["vshader", "fshader"], ["a_position", "a_color"]);
+    gl.useProgram(program);
+
+    var positionBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+    gl.bufferData(
+        gl.ARRAY_BUFFER,
+        new Float32Array([
+           1.0,  1.0,
+          -1.0,  1.0,
+          -1.0, -1.0,
+           1.0, -1.0]),
+        gl.STATIC_DRAW);
+
+    var colors = [
+      [255,   0,   0, 255],
+      [  0, 255,   0, 255],
+      [  0,   0, 255, 255],
+      [  0, 255, 255, 255]
+    ];
+    var colorBuffers = [];
+    var elementBuffers = [];
+    var vaos = [];
+    for (var ii = 0; ii < colors.length; ++ii) {
+      var vao = ext.createVertexArrayOES();
+      vaos.push(vao);
+      ext.bindVertexArrayOES(vao);
+      // Set the position buffer
+      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+      gl.enableVertexAttribArray(0);
+      gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+      var elementBuffer = gl.createBuffer();
+      elementBuffers.push(elementBuffer);
+      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
+      gl.bufferData(
+          gl.ELEMENT_ARRAY_BUFFER,
+          new Uint8Array([0, 1, 2, 0, 2, 3]),
+          gl.STATIC_DRAW);
+
+      // Setup the color attrib
+      var color = colors[ii];
+      if (ii < 3) {
+        var colorBuffer = gl.createBuffer();
+        colorBuffers.push(colorBuffer);
+        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+        gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(
+          [ color[0], color[1], color[2], color[3],
+            color[0], color[1], color[2], color[3],
+            color[0], color[1], color[2], color[3],
+            color[0], color[1], color[2], color[3]
+          ]), gl.STATIC_DRAW);
+        gl.enableVertexAttribArray(1);
+        gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, true, 0, 0);
+      } else {
+        gl.vertexAttrib4f(1, color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255);
+      }
+    }
+
+    // delete the color buffers AND the position buffer.
+    ext.bindVertexArrayOES(null);
+    for (var ii = 0; ii < colorBuffers.length; ++ii) {
+      gl.deleteBuffer(colorBuffers[ii]);
+      gl.deleteBuffer(elementBuffers[ii]);
+      // The buffers should still be valid at this point, since it was attached to the VAO
+      if(!gl.isBuffer(colorBuffers[ii])) {
+        testFailed("buffer removed too early");
+      }
+    }
+    gl.deleteBuffer(positionBuffer);
+
+    // Render with the deleted buffers. As they are referenced by VAOs they
+    // must still be around.
+    for (var ii = 0; ii < colors.length; ++ii) {
+      var color = colors[ii];
+      ext.bindVertexArrayOES(vaos[ii]);
+      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
+      wtu.checkCanvas(gl, color, "should be " + color);
+    }
+
+    // Clean up.
+    for (var ii = 0; ii < colorBuffers.length; ++ii) {
+      ext.deleteVertexArrayOES(vaos[ii]);
+    }
+
+    for (var ii = 0; ii < colorBuffers.length; ++ii) {
+      // The buffers should no longer be valid now that the VAOs are deleted
+      if(gl.isBuffer(colorBuffers[ii])) {
+        testFailed("buffer not properly cleaned up after VAO deletion");
+      }
+    }
+}
+
+function runArrayBufferBindTests() {
+    debug("Testing that VAOs don't effect ARRAY_BUFFER binding.");
+
+    ext.bindVertexArrayOES(null);
+
+    var program = wtu.setupProgram(gl, ["vshader", "fshader"], ["a_color", "a_position"]);
+    gl.useProgram(program);
+
+    // create shared element buuffer
+    var elementBuffer = gl.createBuffer();
+    // bind to default
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
+    gl.bufferData(
+        gl.ELEMENT_ARRAY_BUFFER,
+        new Uint8Array([0, 1, 2, 0, 2, 3]),
+        gl.STATIC_DRAW);
+
+    // first create the buffers for no vao draw.
+    var nonVAOColorBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, nonVAOColorBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(
+      [ 0, 255, 0, 255,
+        0, 255, 0, 255,
+        0, 255, 0, 255,
+        0, 255, 0, 255,
+      ]), gl.STATIC_DRAW);
+
+    // shared position buffer.
+    var positionBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+    gl.bufferData(
+        gl.ARRAY_BUFFER,
+        new Float32Array([
+           1.0,  1.0,
+          -1.0,  1.0,
+          -1.0, -1.0,
+           1.0, -1.0]),
+        gl.STATIC_DRAW);
+
+    // attach position buffer to default
+    gl.enableVertexAttribArray(1);
+    gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+
+    // now create vao
+    var vao = ext.createVertexArrayOES();
+    ext.bindVertexArrayOES(vao);
+
+    // attach the position buffer vao
+    gl.enableVertexAttribArray(1);
+    gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+
+    var vaoColorBuffer = gl.createBuffer();
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 4, gl.UNSIGNED_BYTE, true, 0, 0);
+    gl.bindBuffer(gl.ARRAY_BUFFER, vaoColorBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(
+      [ 255, 0, 0, 255,
+        255, 0, 0, 255,
+        255, 0, 0, 255,
+        255, 0, 0, 255,
+      ]), gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 4, gl.UNSIGNED_BYTE, true, 0, 0);
+
+    // now set the buffer back to the nonVAOColorBuffer
+    gl.bindBuffer(gl.ARRAY_BUFFER, nonVAOColorBuffer);
+
+    // bind to vao
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
+    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
+    wtu.checkCanvas(gl, [255, 0, 0, 255], "should be red");
+
+    // unbind vao
+    ext.bindVertexArrayOES(null);
+
+    // At this point the nonVAOColorBuffer should be still be bound.
+    // If the WebGL impl is emulating VAOs it must make sure
+    // it correctly restores this binding.
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 4, gl.UNSIGNED_BYTE, true, 0, 0);
+    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
+    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-compressed-texture-atc.html b/conformance/extensions/webgl-compressed-texture-atc.html
new file mode 100644
index 0000000..1a4c70b
--- /dev/null
+++ b/conformance/extensions/webgl-compressed-texture-atc.html
@@ -0,0 +1,423 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<title>WebGL WEBGL_compressed_texture_atc Conformance Tests</title>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+.testimages {
+}
+
+.testimages br {
+  clear: both;
+}
+
+.testimages > div {
+  float: left;
+  margin: 1em;
+}
+</style>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_compressed_texture_atc extension, if it is available.");
+
+debug("");
+
+// Compressed textures generated with AMD's Compressonator tool
+// http://developer.amd.com/resources/archive/archived-tools/gpu-tools-archive/the-compressonator/
+var img_4x4_rgba_raw = new Uint8Array([
+    0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
+]);
+var img_4x4_rgb_atc = new Uint8Array([
+    0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+var img_4x4_rgba_atc_explicit = new Uint8Array([
+    0xf6,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+var img_4x4_rgba_atc_implicit = new Uint8Array([
+    0xff,0x6a,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+var img_8x8_rgba_raw = new Uint8Array([
+    0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,
+    0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,
+    0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,
+    0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,
+    0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
+    0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,
+    0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,
+    0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,
+    0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,
+    0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,
+    0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,
+    0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,
+    0xff,0x00,0xff,0x69,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,
+    0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,
+]);
+var img_8x8_rgb_atc = new Uint8Array([
+    0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,0x1f,0x00,0xe0,0xff,0x33,0x30,0x3f,0x00,
+    0x1f,0x7c,0xe0,0x07,0x33,0x30,0x3f,0x00,0x1f,0x00,0xff,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+var img_8x8_rgba_atc_explicit = new Uint8Array([
+    0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0xe0,0xff,0x33,0x30,0x3f,0x00,
+    0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x1f,0x7c,0xe0,0x07,0x33,0x30,0x3f,0x00,
+    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x1f,0x00,0xff,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+var img_8x8_rgba_atc_implicit = new Uint8Array([
+    0xff,0x6a,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x7c,0xe0,0x07,0xcc,0xcf,0xc0,0xff,
+    0x00,0xff,0x49,0x92,0x24,0x49,0x92,0x24,0x1f,0x00,0xe0,0xff,0x33,0x30,0x3f,0x00,
+    0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x1f,0x7c,0xe0,0x07,0x33,0x30,0x3f,0x00,
+    0x00,0xff,0x49,0x92,0x24,0x49,0x92,0x24,0x1f,0x00,0xff,0x07,0xcc,0xcf,0xc0,0xff,
+]);
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+    COMPRESSED_RGB_ATC_WEBGL                        : 0x8C92,
+    COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL        : 0x8C93,
+    COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL    : 0x87EE,
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_atc");
+    if (!ext) {
+        testPassed("No WEBGL_compressed_texture_atc support -- this is legal");
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_compressed_texture_atc extension");
+
+        runSupportedTest(true);
+        runTestExtension();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_atc");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_compressed_texture_atc listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_compressed_texture_atc listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_compressed_texture_atc not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_compressed_texture_atc not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+
+function runTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+
+    shouldBe('gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)', '[]');
+}
+
+function formatExists(format, supportedFormats) {
+    for (var ii = 0; ii < supportedFormats.length; ++ii) {
+        if (format == supportedFormats[ii]) {
+            testPassed("supported format " + formatToString(format) + " is exists");
+            return;
+        }
+    }
+    testFailed("supported format " + formatToString(format) + " does not exist");
+}
+
+function formatToString(format) {
+    for (var p in ext) {
+        if (ext[p] == format) {
+            return p;
+        }
+    }
+    return "0x" + format.toString(16);
+}
+
+function runTestExtension() {
+    debug("Testing WEBGL_compressed_texture_atc");
+
+    // check that all format enums exist.
+    for (name in validFormats) {
+        var expected = "0x" + validFormats[name].toString(16);
+        var actual = "ext['" + name + "']";
+        shouldBe(actual, expected);
+    }
+
+    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
+    // There should be exactly 3 formats
+    shouldBe("supportedFormats.length", "3");
+
+    // check that all 3 formats exist
+    for (var name in validFormats.length) {
+        formatExists(validFormats[name], supportedFormats);
+    }
+
+    // Test each format
+    testATC_RGB();
+    testATC_RGBA_Explicit();
+    testATC_RGBA_Interpolated();
+}
+
+function testATC_RGB() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 3,
+            data: img_4x4_rgb_atc,
+            raw: img_4x4_rgba_raw,
+            format: ext.COMPRESSED_RGB_ATC_WEBGL
+        },
+        {   width: 8,
+            height: 8,
+            channels: 3,
+            data: img_8x8_rgb_atc,
+            raw: img_8x8_rgba_raw,
+            format: ext.COMPRESSED_RGB_ATC_WEBGL
+        }
+    ];
+    testACTTextures(tests);
+}
+
+function testATC_RGBA_Explicit() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: img_4x4_rgba_atc_explicit,
+            raw: img_4x4_rgba_raw,
+            format: ext.COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL
+        },
+        {   width: 8,
+            height: 8,
+            channels: 4,
+            data: img_8x8_rgba_atc_explicit,
+            raw: img_8x8_rgba_raw,
+            format: ext.COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL
+        }
+    ];
+    testACTTextures(tests);
+}
+
+function testATC_RGBA_Interpolated() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: img_4x4_rgba_atc_interpolated,
+            raw: img_4x4_rgba_raw,
+            format: ext.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL
+        },
+        {   width: 8,
+            height: 8,
+            channels: 4,
+            data: img_8x8_rgba_atc_interpolated,
+            raw: img_8x8_rgba_raw,
+            format: ext.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL
+        }
+    ];
+    testACTTextures(tests);
+}
+
+function testACTTextures(tests) {
+    debug("<hr/>");
+    for (var ii = 0; ii < tests.length; ++ii) {
+        testACTTexture(tests[ii]);
+    }
+}
+
+function testACTTexture(test) {
+    var data = new Uint8Array(test.data);
+    var width = test.width;
+    var height = test.height;
+    var format = test.format;
+    var uncompressedData = test.raw;
+
+    canvas.width = width;
+    canvas.height = height;
+    gl.viewport(0, 0, width, height);
+    debug("testing " + formatToString(format) + " " + width + "x" + height);
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+    gl.generateMipmap(gl.TEXTURE_2D);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
+    wtu.clearAndDrawUnitQuad(gl);
+    compareRect(width, height, test.channels, width, height, uncompressedData, data, format);
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, -1, format, 1, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "cannot specify negative mip level");
+
+    // ATC Does not allow use of CompressedTexSubImage
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed");
+}
+
+function insertImg(element, caption, img) {
+    var div = document.createElement("div");
+    div.appendChild(img);
+    var label = document.createElement("div");
+    label.appendChild(document.createTextNode(caption));
+    div.appendChild(label);
+    element.appendChild(div);
+}
+
+function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
+    var scale = 8;
+    var c = document.createElement("canvas");
+    c.width = imageWidth * scale;
+    c.height = imageHeight * scale;
+    var ctx = c.getContext("2d");
+    for (var yy = 0; yy < imageHeight; ++yy) {
+        for (var xx = 0; xx < imageWidth; ++xx) {
+            var offset = (yy * dataWidth + xx) * 4;
+            ctx.fillStyle = "rgba(" +
+                    data[offset + 0] + "," +
+                    data[offset + 1] + "," +
+                    data[offset + 2] + "," +
+                    (alpha ? data[offset + 3] / 255 : 1) + ")";
+            ctx.fillRect(xx * scale, yy * scale, scale, scale);
+        }
+    }
+    var img = document.createElement("img");
+    img.src = c.toDataURL();
+    return img;
+}
+function compareRect(
+        actualWidth, actualHeight, actualChannels,
+        dataWidth, dataHeight, expectedData,
+        testData, testFormat, tolerance) {
+    if(typeof(tolerance) == 'undefined') { tolerance = 5; }
+    var actual = new Uint8Array(actualWidth * actualHeight * 4);
+    gl.readPixels(
+            0, 0, actualWidth, actualHeight, gl.RGBA, gl.UNSIGNED_BYTE, actual);
+
+    var div = document.createElement("div");
+    div.className = "testimages";
+    insertImg(div, "expected", makeImage(
+            actualWidth, actualHeight, dataWidth, expectedData,
+            actualChannels == 4));
+    insertImg(div, "actual", makeImage(
+            actualWidth, actualHeight, actualWidth, actual,
+            actualChannels == 4));
+    div.appendChild(document.createElement('br'));
+    document.getElementById("console").appendChild(div);
+
+    var failed = false;
+    for (var yy = 0; yy < actualHeight; ++yy) {
+        for (var xx = 0; xx < actualWidth; ++xx) {
+            var actualOffset = (yy * actualWidth + xx) * 4;
+            var expectedOffset = (yy * dataWidth + xx) * 4;
+            var expected = [
+                    expectedData[expectedOffset + 0],
+                    expectedData[expectedOffset + 1],
+                    expectedData[expectedOffset + 2],
+                    (actualChannels == 3 ? 255 : expectedData[expectedOffset + 3])
+            ];
+            for (var jj = 0; jj < 4; ++jj) {
+                if (Math.abs(actual[actualOffset + jj] - expected[jj]) > tolerance) {
+                    failed = true;
+                    var was = actual[actualOffset + 0].toString();
+                    for (j = 1; j < 4; ++j) {
+                        was += "," + actual[actualOffset + j];
+                    }
+                    testFailed('at (' + xx + ', ' + yy +
+                                         ') expected: ' + expected + ' was ' + was);
+                }
+            }
+        }
+    }
+    if (!failed) {
+        testPassed("texture rendered correctly");
+    }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-compressed-texture-pvrtc.html b/conformance/extensions/webgl-compressed-texture-pvrtc.html
new file mode 100644
index 0000000..131d3ce
--- /dev/null
+++ b/conformance/extensions/webgl-compressed-texture-pvrtc.html
@@ -0,0 +1,365 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<title>WebGL WEBGL_compressed_texture_pvrtc Conformance Tests</title>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+.testimages {
+}
+
+.testimages br {
+  clear: both;
+}
+
+.testimages > div {
+  float: left;
+  margin: 1em;
+}
+</style>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_compressed_texture_pvrtc extension, if it is available.");
+
+debug("");
+
+// Compressed textures generated with thw PowerVR SDK
+// http://www.imgtec.com/powervr/insider/sdkdownloads/index.asp
+var pvrtc_4x4_rgba_2bpp = new Uint8Array([
+  0x77, 0x22, 0x77, 0x22, 0xbb, 0x2b, 0x00, 0x80, 0x77, 0x22, 0x77, 0x22, 0xbb, 0x2b, 0x00, 0x80,
+  0x77, 0x22, 0x77, 0x22, 0xbb, 0x2b, 0x00, 0x80, 0x77, 0x22, 0x77, 0x22, 0xbb, 0x2b, 0x00, 0x80,
+]);
+
+var pvrtc_4x4_rgba_2bpp_decoded = new Uint8Array([
+  0x00, 0x00, 0x00, 0xff, 0x35, 0x35, 0x32, 0xca, 0x76, 0x76, 0x71, 0x8a, 0x70, 0x70, 0x6b, 0x8f,
+  0x35, 0x35, 0x32, 0xca, 0x46, 0x46, 0x43, 0xb8, 0x70, 0x70, 0x6b, 0x8f, 0xbd, 0xbd, 0xb5, 0x44,
+  0x00, 0x00, 0x00, 0xff, 0x35, 0x35, 0x32, 0xca, 0x76, 0x76, 0x71, 0x8a, 0x70, 0x70, 0x6b, 0x8f,
+  0x35, 0x35, 0x32, 0xca, 0x46, 0x46, 0x43, 0xb8, 0x70, 0x70, 0x6b, 0x8f, 0xbd, 0xbd, 0xb5, 0x44,
+]);
+
+var pvrtc_4x4_rgba_4bpp = new Uint8Array([
+  0x1b, 0x1b, 0x1b, 0x1b, 0xba, 0x2b, 0x00, 0x80, 0x1b, 0x1b, 0x1b, 0x1b, 0xba, 0x2b, 0x00, 0x80,
+  0x1b, 0x1b, 0x1b, 0x1b, 0xba, 0x2b, 0x00, 0x80, 0x1b, 0x1b, 0x1b, 0x1b, 0xbc, 0x2b, 0x00, 0x80,
+]);
+
+var pvrtc_4x4_rgba_4bpp_decoded = new Uint8Array([
+  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x49, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xba, 0x44,
+  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x4c, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xbc, 0x44,
+  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x43, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xb5, 0x44,
+  0x00, 0x00, 0x00, 0xff, 0x46, 0x46, 0x46, 0xb8, 0x76, 0x76, 0x71, 0x8a, 0xbd, 0xbd, 0xb7, 0x44,
+]);
+
+// FIXME: The PowerVR SDK tools do not generate RGB only textures, so we currently do not have
+// sample data for COMPRESSED_RGB_PVRTC_4BPPV1_IMG and COMPRESSED_RGB_PVRTC_2BPPV1_IMG
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+    COMPRESSED_RGB_PVRTC_4BPPV1_IMG      : 0x8C00,
+    COMPRESSED_RGB_PVRTC_2BPPV1_IMG      : 0x8C01,
+    COMPRESSED_RGBA_PVRTC_4BPPV1_IMG     : 0x8C02,
+    COMPRESSED_RGBA_PVRTC_2BPPV1_IMG     : 0x8C03,
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_pvrtc");
+    if (!ext) {
+        testPassed("No WEBGL_compressed_texture_pvrtc support -- this is legal");
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_compressed_texture_pvrtc extension");
+
+        runSupportedTest(true);
+        runTestExtension();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_pvrtc");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_compressed_texture_pvrtc listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_compressed_texture_pvrtc listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_compressed_texture_pvrtc not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_compressed_texture_pvrtc not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+
+function runTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+
+    shouldBe('gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)', '[]');
+}
+
+function formatExists(format, supportedFormats) {
+    for (var ii = 0; ii < supportedFormats.length; ++ii) {
+        if (format == supportedFormats[ii]) {
+            testPassed("supported format " + formatToString(format) + " is exists");
+            return;
+        }
+    }
+    testFailed("supported format " + formatToString(format) + " does not exist");
+}
+
+function formatToString(format) {
+    for (var p in ext) {
+        if (ext[p] == format) {
+            return p;
+        }
+    }
+    return "0x" + format.toString(16);
+}
+
+function runTestExtension() {
+    debug("Testing WEBGL_compressed_texture_pvrtc");
+
+    // check that all format enums exist.
+    for (name in validFormats) {
+        var expected = "0x" + validFormats[name].toString(16);
+        var actual = "ext['" + name + "']";
+        shouldBe(actual, expected);
+    }
+
+    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
+    // There should be exactly 3 formats
+    shouldBe("supportedFormats.length", "3");
+
+    // check that all 3 formats exist
+    for (var name in validFormats.length) {
+        formatExists(validFormats[name], supportedFormats);
+    }
+
+    // Test each format
+    testPVRTC_RGBA_2BPP();
+    testPVRTC_RGBA_4BPP();
+}
+
+function testPVRTC_RGBA_2BPP() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: pvrtc_4x4_rgba_2bpp,
+            raw: pvrtc_4x4_rgba_2bpp_decoded,
+            format: ext.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+        }
+    ];
+    testPVRTCTextures(tests);
+}
+
+function testPVRTC_RGBA_4BPP() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: pvrtc_4x4_rgba_4bpp,
+            raw: pvrtc_4x4_rgba_4bpp_decoded,
+            format: ext.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+        }
+    ];
+    testPVRTCTextures(tests);
+}
+
+function testPVRTCTextures(tests) {
+    debug("<hr/>");
+    for (var ii = 0; ii < tests.length; ++ii) {
+        testPVRTCTexture(tests[ii]);
+    }
+}
+
+function testPVRTCTexture(test) {
+    var data = new Uint8Array(test.data);
+    var width = test.width;
+    var height = test.height;
+    var format = test.format;
+    var uncompressedData = test.raw;
+
+    canvas.width = width;
+    canvas.height = height;
+    gl.viewport(0, 0, width, height);
+    debug("testing " + formatToString(format) + " " + width + "x" + height);
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+    gl.generateMipmap(gl.TEXTURE_2D);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
+    wtu.clearAndDrawUnitQuad(gl);
+    compareRect(width, height, test.channels, width, height, uncompressedData, data, format);
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "compressedTexSubImage2D allowed for reloading of complete textures");
+
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 2, 0, width - 2, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 2, width, height - 2, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "compressedTexSubImage2D not allowed for partial texture updates");
+}
+
+function insertImg(element, caption, img) {
+    var div = document.createElement("div");
+    div.appendChild(img);
+    var label = document.createElement("div");
+    label.appendChild(document.createTextNode(caption));
+    div.appendChild(label);
+    element.appendChild(div);
+}
+
+function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
+    var scale = 8;
+    var c = document.createElement("canvas");
+    c.width = imageWidth * scale;
+    c.height = imageHeight * scale;
+    var ctx = c.getContext("2d");
+    for (var yy = 0; yy < imageHeight; ++yy) {
+        for (var xx = 0; xx < imageWidth; ++xx) {
+            var offset = (yy * dataWidth + xx) * 4;
+            ctx.fillStyle = "rgba(" +
+                    data[offset + 0] + "," +
+                    data[offset + 1] + "," +
+                    data[offset + 2] + "," +
+                    (alpha ? data[offset + 3] / 255 : 1) + ")";
+            ctx.fillRect(xx * scale, yy * scale, scale, scale);
+        }
+    }
+    var img = document.createElement("img");
+    img.src = c.toDataURL();
+    return img;
+}
+function compareRect(
+        actualWidth, actualHeight, actualChannels,
+        dataWidth, dataHeight, expectedData,
+        testData, testFormat, tolerance) {
+    if(typeof(tolerance) == 'undefined') { tolerance = 5; }
+    var actual = new Uint8Array(actualWidth * actualHeight * 4);
+    gl.readPixels(
+            0, 0, actualWidth, actualHeight, gl.RGBA, gl.UNSIGNED_BYTE, actual);
+
+    var div = document.createElement("div");
+    div.className = "testimages";
+    insertImg(div, "expected", makeImage(
+            actualWidth, actualHeight, dataWidth, expectedData,
+            actualChannels == 4));
+    insertImg(div, "actual", makeImage(
+            actualWidth, actualHeight, actualWidth, actual,
+            actualChannels == 4));
+    div.appendChild(document.createElement('br'));
+    document.getElementById("console").appendChild(div);
+
+    var failed = false;
+    for (var yy = 0; yy < actualHeight; ++yy) {
+        for (var xx = 0; xx < actualWidth; ++xx) {
+            var actualOffset = (yy * actualWidth + xx) * 4;
+            var expectedOffset = (yy * dataWidth + xx) * 4;
+            var expected = [
+                    expectedData[expectedOffset + 0],
+                    expectedData[expectedOffset + 1],
+                    expectedData[expectedOffset + 2],
+                    (actualChannels == 3 ? 255 : expectedData[expectedOffset + 3])
+            ];
+            for (var jj = 0; jj < 4; ++jj) {
+                if (Math.abs(actual[actualOffset + jj] - expected[jj]) > tolerance) {
+                    failed = true;
+                    var was = actual[actualOffset + 0].toString();
+                    for (j = 1; j < 4; ++j) {
+                        was += "," + actual[actualOffset + j];
+                    }
+                    testFailed('at (' + xx + ', ' + yy +
+                                         ') expected: ' + expected + ' was ' + was);
+                }
+            }
+        }
+    }
+    if (!failed) {
+        testPassed("texture rendered correctly");
+    }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-compressed-texture-s3tc.html b/conformance/extensions/webgl-compressed-texture-s3tc.html
new file mode 100644
index 0000000..beda535
--- /dev/null
+++ b/conformance/extensions/webgl-compressed-texture-s3tc.html
@@ -0,0 +1,636 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<title>WebGL WEBGL_compressed_texture_s3tc Conformance Tests</title>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+.testimages {
+}
+
+.testimages br {
+  clear: both;
+}
+
+.testimages > div {
+  float: left;
+  margin: 1em;
+}
+</style>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_compressed_texture_s3tc extension, if it is available.");
+
+debug("");
+
+var img_4x4_rgba_raw = new Uint8Array([
+    0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,
+]);
+var img_4x4_rgb_dxt1 = new Uint8Array([
+    0xe0,0x07,0x00,0xf8,0x11,0x10,0x15,0x00,
+]);
+var img_4x4_rgba_dxt1 = new Uint8Array([
+    0xe0,0x07,0x00,0xf8,0x13,0x10,0x15,0x00,
+]);
+var img_4x4_rgba_dxt3 = new Uint8Array([
+    0xf6,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,
+]);
+var img_4x4_rgba_dxt5 = new Uint8Array([
+    0xf6,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,
+]);
+var img_8x8_rgba_raw = new Uint8Array([
+    0xff,0x00,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0x00,0x69,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0x69,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,
+]);
+var img_8x8_rgb_dxt1 = new Uint8Array([
+    0xe0,0x07,0x00,0xf8,0x11,0x10,0x15,0x00,0x1f,0x00,0xe0,0xff,0x11,0x10,0x15,0x00,0xe0,0x07,0x1f,0xf8,0x44,0x45,0x40,0x55,0x1f,0x00,0xff,0x07,0x44,0x45,0x40,0x55,
+]);
+var img_8x8_rgba_dxt1 = new Uint8Array([
+    0xe0,0x07,0x00,0xf8,0x13,0x13,0x15,0x00,0x1f,0x00,0xe0,0xff,0x11,0x10,0x15,0x00,0xe0,0x07,0x1f,0xf8,0x44,0x45,0x43,0x57,0x1f,0x00,0xff,0x07,0x44,0x45,0x40,0x55,
+]);
+var img_8x8_rgba_dxt3 = new Uint8Array([
+    0xf6,0xff,0xf6,0xff,0xff,0xff,0xff,0xff,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0xff,0x1f,0x00,0x44,0x45,0x40,0x55,0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xff,0x1f,0xf8,0xe0,0x07,0x11,0x10,0x15,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07,0x1f,0x00,0x11,0x10,0x15,0x00,
+]);
+var img_8x8_rgba_dxt5 = new Uint8Array([
+    0xff,0x69,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0xf8,0xe0,0x07,0x44,0x45,0x40,0x55,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xff,0x1f,0x00,0x44,0x45,0x40,0x55,0xff,0x69,0x00,0x00,0x00,0x01,0x10,0x00,0x1f,0xf8,0xe0,0x07,0x11,0x10,0x15,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x07,0x1f,0x00,0x11,0x10,0x15,0x00,
+]);
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+    COMPRESSED_RGB_S3TC_DXT1_EXT        : 0x83F0,
+    COMPRESSED_RGBA_S3TC_DXT1_EXT       : 0x83F1,
+    COMPRESSED_RGBA_S3TC_DXT3_EXT       : 0x83F2,
+    COMPRESSED_RGBA_S3TC_DXT5_EXT       : 0x83F3,
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_s3tc");
+    if (!ext) {
+        testPassed("No WEBGL_compressed_texture_s3tc support -- this is legal");
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_compressed_texture_s3tc extension");
+
+        runSupportedTest(true);
+        runTestExtension();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_s3tc");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_compressed_texture_s3tc listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_compressed_texture_s3tc listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_compressed_texture_s3tc not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_compressed_texture_s3tc not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+
+function runTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+
+    shouldBe('gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)', '[]');
+}
+
+function formatExists(format, supportedFormats) {
+    for (var ii = 0; ii < supportedFormats.length; ++ii) {
+        if (format == supportedFormats[ii]) {
+            testPassed("supported format " + formatToString(format) + " is exists");
+            return;
+        }
+    }
+    testFailed("supported format " + formatToString(format) + " does not exist");
+}
+
+function formatToString(format) {
+    for (var p in ext) {
+        if (ext[p] == format) {
+            return p;
+        }
+    }
+    return "0x" + format.toString(16);
+}
+
+function runTestExtension() {
+    debug("Testing WEBGL_compressed_texture_s3tc");
+
+    // check that all format enums exist.
+    for (name in validFormats) {
+        var expected = "0x" + validFormats[name].toString(16);
+        var actual = "ext['" + name + "']";
+        shouldBe(actual, expected);
+    }
+
+    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
+    // There should be exactly 4 formats
+    shouldBe("supportedFormats.length", "4");
+
+    // check that all 4 formats exist
+    for (var name in validFormats.length) {
+        formatExists(validFormats[name], supportedFormats);
+    }
+
+    // Test each format
+    testDXT1_RGB();
+    testDXT1_RGBA();
+    testDXT3_RGBA();
+    testDXT5_RGBA();
+}
+
+function testDXT1_RGB() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 3,
+            data: img_4x4_rgb_dxt1,
+            format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT
+        },
+        {   width: 8,
+            height: 8,
+            channels: 3,
+            data: img_8x8_rgb_dxt1,
+            format: ext.COMPRESSED_RGB_S3TC_DXT1_EXT
+        }
+    ];
+    testDXTTextures(tests);
+}
+
+function testDXT1_RGBA() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: img_4x4_rgba_dxt1,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT
+        },
+        {   width: 8,
+            height: 8,
+            channels: 4,
+            data: img_8x8_rgba_dxt1,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT1_EXT
+        }
+    ];
+    testDXTTextures(tests);
+}
+
+function testDXT3_RGBA() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: img_4x4_rgba_dxt3,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT3_EXT
+        },
+        {   width: 8,
+            height: 8,
+            channels: 4,
+            data: img_8x8_rgba_dxt3,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT3_EXT
+        }
+    ];
+    testDXTTextures(tests);
+}
+
+function testDXT5_RGBA() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 4,
+            data: img_4x4_rgba_dxt5,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT
+        },
+        {   width: 8,
+            height: 8,
+            channels: 4,
+            data: img_8x8_rgba_dxt5,
+            format: ext.COMPRESSED_RGBA_S3TC_DXT5_EXT
+        }
+    ];
+    testDXTTextures(tests);
+}
+
+function testDXTTextures(tests) {
+    debug("<hr/>");
+    for (var ii = 0; ii < tests.length; ++ii) {
+        testDXTTexture(tests[ii]);
+    }
+}
+
+function uncompressDXTBlock(
+    destBuffer, destX, destY, destWidth, src, srcOffset, format) {
+    function make565(src, offset) {
+        return src[offset + 0] + src[offset + 1] * 256;
+    }
+    function make8888From565(c) {
+        return [
+                Math.floor(((c >> 11) & 0x1F) * 255 / 31),
+                Math.floor(((c >>    5) & 0x3F) * 255 / 63),
+                Math.floor(((c >>    0) & 0x1F) * 255 / 31),
+                255
+            ];
+    }
+    function mix(mult, c0, c1, div) {
+        var r = [];
+        for (var ii = 0; ii < c0.length; ++ii) {
+            r[ii] = Math.floor((c0[ii] * mult + c1[ii]) / div);
+        }
+        return r;
+    }
+    var isDXT1 = format == ext.COMPRESSED_RGB_S3TC_DXT1_EXT ||
+                 format == ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+    var colorOffset = srcOffset + (isDXT1 ? 0 : 8);
+    var color0 = make565(src, colorOffset + 0);
+    var color1 = make565(src, colorOffset + 2);
+    var c0gtc1 = color0 > color1 || !isDXT1;
+    var rgba0 = make8888From565(color0);
+    var rgba1 = make8888From565(color1);
+    var colors = [
+            rgba0,
+            rgba1,
+            c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
+            c0gtc1 ? mix(2, rgba1, rgba0, 3) : [0, 0, 0, 255]
+        ];
+
+    // yea I know there is a lot of math in this inner loop.
+    // so sue me.
+    for (var yy = 0; yy < 4; ++yy) {
+        var pixels = src[colorOffset + 4 + yy];
+        for (var xx = 0; xx < 4; ++xx) {
+            var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
+            var code = (pixels >> (xx * 2)) & 0x3;
+            var srcColor = colors[code];
+            var alpha;
+            switch (format) {
+            case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
+                alpha = 255;
+                break;
+            case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
+                alpha = (code == 3 && !c0gtc1) ? 0 : 255;
+                break;
+            case ext.COMPRESSED_RGBA_S3TC_DXT3_EXT:
+                {
+                    var alpha0 = src[srcOffset + yy * 2 + Math.floor(xx / 2)];
+                    var alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
+                    alpha = alpha1 | (alpha1 << 4);
+                }
+                break;
+            case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
+                {
+                    var alpha0 = src[srcOffset + 0];
+                    var alpha1 = src[srcOffset + 1];
+                    var alphaOff = Math.floor(yy / 2) * 3 + 2;
+                    var alphaBits =
+                         src[srcOffset + alphaOff + 0] +
+                         src[srcOffset + alphaOff + 1] * 256 +
+                         src[srcOffset + alphaOff + 2] * 65536;
+                    var alphaShift = (yy % 2) * 12 + xx * 3;
+                    var alphaCode = (alphaBits >> alphaShift) & 0x7;
+                    if (alpha0 > alpha1) {
+                        switch (alphaCode) {
+                        case 0:
+                            alpha = alpha0;
+                            break;
+                        case 1:
+                            alpha = alpha1;
+                            break;
+                        default:
+                            alpha = ((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7;
+                            break;
+                        }
+                    } else {
+                        switch (alphaCode) {
+                        case 0:
+                            alpha = alpha0;
+                            break;
+                        case 1:
+                            alpha = alpha1;
+                            break;
+                        case 6:
+                            alpha = 0;
+                            break;
+                        case 7:
+                            alpha = 255;
+                            break;
+                        default:
+                            alpha = ((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5;
+                            break;
+                        }
+                    }
+                }
+                break;
+            default:
+                throw "bad format";
+            }
+            destBuffer[dstOff + 0] = srcColor[0];
+            destBuffer[dstOff + 1] = srcColor[1];
+            destBuffer[dstOff + 2] = srcColor[2];
+            destBuffer[dstOff + 3] = alpha;
+        }
+    }
+}
+
+function getBlockSize(format) {
+  var isDXT1 = format == ext.COMPRESSED_RGB_S3TC_DXT1_EXT ||
+               format == ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+  return isDXT1 ? 8 : 16;
+}
+
+function uncompressDXT(width, height, data, format) {
+    if (width % 4 || height % 4) throw "bad width or height";
+
+    var dest = new Uint8Array(width * height * 4);
+    var blocksAcross = width / 4;
+    var blocksDown = height / 4;
+    var blockSize = getBlockSize(format);
+    for (var yy = 0; yy < blocksDown; ++yy) {
+        for (var xx = 0; xx < blocksAcross; ++xx) {
+            uncompressDXTBlock(
+                dest, xx * 4, yy * 4, width, data,
+                (yy * blocksAcross + xx) * blockSize, format);
+        }
+    }
+    return dest;
+}
+
+function copyRect(data, srcX, srcY, dstX, dstY, width, height, stride) {
+  var bytesPerLine = width * 4;
+  var srcOffset = srcX * 4 + srcY * stride;
+  var dstOffset = dstX * 4 + dstY * stride;
+  for (; height > 0; --height) {
+    for (var ii = 0; ii < bytesPerLine; ++ii) {
+      data[dstOffset + ii] = data[srcOffset + ii];
+    }
+    srcOffset += stride;
+    dstOffset += stride;
+  }
+}
+
+function testDXTTexture(test) {
+    var data = new Uint8Array(test.data);
+    var width = test.width;
+    var height = test.height;
+    var format = test.format;
+
+    var uncompressedData = uncompressDXT(width, height, data, format);
+
+    canvas.width = width;
+    canvas.height = height;
+    gl.viewport(0, 0, width, height);
+    debug("testing " + formatToString(format) + " " + width + "x" + height);
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+    gl.generateMipmap(gl.TEXTURE_2D);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
+    wtu.clearAndDrawUnitQuad(gl);
+    compareRect(width, height, test.channels, width, height, uncompressedData, data, format);
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+    if (width == 4) {
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 1, height, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 2, height, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+    }
+    if (height == 4) {
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 1, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 2, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+    }
+
+    // pick a wrong format that uses the same amount of data.
+    var wrongFormat;
+    switch (format) {
+    case ext.COMPRESSED_RGB_S3TC_DXT1_EXT:
+      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+      break;
+    case ext.COMPRESSED_RGBA_S3TC_DXT1_EXT:
+      wrongFormat = ext.COMPRESSED_RGB_S3TC_DXT1_EXT;
+      break;
+    case ext.COMPRESSED_RGBA_S3TC_DXT3_EXT:
+      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+      break;
+    case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
+      wrongFormat = ext.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+      break;
+    }
+
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, wrongFormat, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "format does not match");
+
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width + 4, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height + 4, format, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 4, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 4, format, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 1, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width - 2, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 1, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height - 2, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid dimensions");
+
+    var subData = new Uint8Array(data.buffer, 0, getBlockSize(format));
+
+    if (width == 8 && height == 8) {
+        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 1, 0, 4, 4, format, subData);
+        glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
+        gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 1, 4, 4, format, subData);
+        glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid offset");
+    }
+
+    var stride = width * 4;
+    for (var yoff = 0; yoff < height; yoff += 4) {
+        for (var xoff = 0; xoff < width; xoff += 4) {
+            copyRect(uncompressedData, 0, 0, xoff, yoff, 4, 4, stride);
+            gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, xoff, yoff, 4, 4, format, subData);
+            glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+            wtu.clearAndDrawUnitQuad(gl);
+            compareRect(width, height, test.channels, width, height, uncompressedData, data, format);
+        }
+    }
+}
+
+function insertImg(element, caption, img) {
+    var div = document.createElement("div");
+    div.appendChild(img);
+    var label = document.createElement("div");
+    label.appendChild(document.createTextNode(caption));
+    div.appendChild(label);
+    element.appendChild(div);
+}
+
+function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
+    var scale = 8;
+    var c = document.createElement("canvas");
+    c.width = imageWidth * scale;
+    c.height = imageHeight * scale;
+    var ctx = c.getContext("2d");
+    for (var yy = 0; yy < imageHeight; ++yy) {
+        for (var xx = 0; xx < imageWidth; ++xx) {
+            var offset = (yy * dataWidth + xx) * 4;
+            ctx.fillStyle = "rgba(" +
+                    data[offset + 0] + "," +
+                    data[offset + 1] + "," +
+                    data[offset + 2] + "," +
+                    (alpha ? data[offset + 3] / 255 : 1) + ")";
+            ctx.fillRect(xx * scale, yy * scale, scale, scale);
+        }
+    }
+    var img = document.createElement("img");
+    img.src = c.toDataURL();
+    return img;
+}
+function compareRect(
+        actualWidth, actualHeight, actualChannels,
+        dataWidth, dataHeight, expectedData,
+        testData, testFormat) {
+    var actual = new Uint8Array(actualWidth * actualHeight * 4);
+    gl.readPixels(
+            0, 0, actualWidth, actualHeight, gl.RGBA, gl.UNSIGNED_BYTE, actual);
+
+    var div = document.createElement("div");
+    div.className = "testimages";
+    insertImg(div, "expected", makeImage(
+            actualWidth, actualHeight, dataWidth, expectedData,
+            actualChannels == 4));
+    insertImg(div, "actual", makeImage(
+            actualWidth, actualHeight, actualWidth, actual,
+            actualChannels == 4));
+    div.appendChild(document.createElement('br'));
+    document.getElementById("console").appendChild(div);
+
+    var failed = false;
+    for (var yy = 0; yy < actualHeight; ++yy) {
+        for (var xx = 0; xx < actualWidth; ++xx) {
+            var actualOffset = (yy * actualWidth + xx) * 4;
+            var expectedOffset = (yy * dataWidth + xx) * 4;
+            var expected = [
+                    expectedData[expectedOffset + 0],
+                    expectedData[expectedOffset + 1],
+                    expectedData[expectedOffset + 2],
+                    (actualChannels == 3 ? 255 : expectedData[expectedOffset + 3])
+            ];
+            for (var jj = 0; jj < 4; ++jj) {
+                if (actual[actualOffset + jj] != expected[jj]) {
+                    failed = true;
+                    var was = actual[actualOffset + 0].toString();
+                    for (j = 1; j < 4; ++j) {
+                        was += "," + actual[actualOffset + j];
+                    }
+                    testFailed('at (' + xx + ', ' + yy +
+                                         ') expected: ' + expected + ' was ' + was);
+                }
+            }
+        }
+    }
+    if (!failed) {
+        testPassed("texture rendered correctly");
+    }
+}
+
+function testPVRTCTextures() {
+    testFailed("PVRTC test not yet implemented");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-compressed-texture-size-limit.html b/conformance/extensions/webgl-compressed-texture-size-limit.html
new file mode 100644
index 0000000..40acdca
--- /dev/null
+++ b/conformance/extensions/webgl-compressed-texture-size-limit.html
@@ -0,0 +1,190 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL compressed texture size limit conformance test</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"> </script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="32" height="32" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Checks size limit of the webgl compressed textures")
+var canvas;
+
+function numLevelsFromSize(size) {
+  var levels = 0;
+  while ((size >> levels) > 0) {
+    ++levels;
+  }
+  return levels;
+}
+
+// More formats can be added here when more texture compression extensions is enabled in WebGL.
+var validFormats = {
+    COMPRESSED_RGB_S3TC_DXT1_EXT        : 0x83F0,
+    COMPRESSED_RGBA_S3TC_DXT1_EXT       : 0x83F1,
+    COMPRESSED_RGBA_S3TC_DXT3_EXT       : 0x83F2,
+    COMPRESSED_RGBA_S3TC_DXT5_EXT       : 0x83F3,
+};
+
+// format specific restrictions for COMPRESSED_RGB_S3TC_DXT1_EXT and COMPRESSED_RGBA_S3TC_DXT1_EXT
+// on the byteLength of the ArrayBufferView, pixels
+function func1 (width, height)
+{
+    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
+}
+
+// format specific restrictions for COMPRESSED_RGBA_S3TC_DXT3_EXT and COMPRESSED_RGBA_S3TC_DXT5_EXT
+// on the byteLength of the ArrayBufferView, pixels
+function func2 (width, height)
+{
+    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
+}
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example");
+var tests = [
+  // More tests can be added here when more texture compression extensions is enabled in WebGL.
+  { extension: "WEBGL_compressed_texture_s3tc", format: validFormats.COMPRESSED_RGB_S3TC_DXT1_EXT, dataType: Uint8Array, func: func1},
+  { extension: "WEBGL_compressed_texture_s3tc", format: validFormats.COMPRESSED_RGBA_S3TC_DXT1_EXT, dataType: Uint8Array, func: func1},
+  { extension: "WEBGL_compressed_texture_s3tc", format: validFormats.COMPRESSED_RGBA_S3TC_DXT3_EXT, dataType: Uint8Array, func: func2},
+  { extension: "WEBGL_compressed_texture_s3tc", format: validFormats.COMPRESSED_RGBA_S3TC_DXT5_EXT, dataType: Uint8Array, func: func2},
+];
+
+// Note: We expressly only use 2 textures because first a texture will be defined
+// as using all mips of 1 format, then for a moment it will have mixed formats which
+// may uncover bugs.
+var targets = [
+  { target: gl.TEXTURE_2D,
+    maxSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
+    tex: gl.createTexture(),
+    targets: [gl.TEXTURE_2D]
+  },
+  { target: gl.TEXTURE_CUBE_MAP,
+    maxSize: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),
+    tex: gl.createTexture(),
+    targets: [
+      gl.TEXTURE_CUBE_MAP_POSITIVE_X,
+      gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
+      gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
+      gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
+      gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
+      gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
+    ]
+  }
+];
+
+gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
+
+var trg = 0;
+var tt = 0;
+runNextTest();
+
+function runNextTest() {
+  var t = targets[trg];
+
+  if (tt == 0) {
+    var tex = t.tex;
+    gl.bindTexture(t.target, tex);
+
+    debug("");
+    debug("max size for " + wtu.glEnumToString(gl, t.target) + ": " + t.maxSize);
+    var numLevels = numLevelsFromSize(t.maxSize);
+    debug("num levels " + numLevels);
+  }
+
+  var test = tests[tt];
+  testFormatType(t, test);
+  ++tt;
+  if (tt == tests.length) {
+    tt = 0;
+    ++trg;
+    if (trg == targets.length) {
+      finishTest();
+      return;
+    }
+  }
+  wtu.waitForComposite(gl, runNextTest)
+}
+
+function testFormatType(t, test) {
+  debug("");
+
+  // Query the extension and store globally so shouldBe can access it
+  var ext = wtu.getExtensionWithKnownPrefixes(gl, test.extension);
+  if (ext) {
+    testPassed("Successfully enabled " + test.extension + " extension");
+
+    for (var j = 0; j < t.targets.length; ++j) {
+      var target = t.targets[j];
+      debug("");
+      debug(wtu.glEnumToString(gl, target));
+      var numLevels = numLevelsFromSize(t.maxSize);
+
+      // positive test
+      for (var i = 0, size = t.maxSize; i < numLevels; i++, size /= 2) {
+        var pixels = new test.dataType(test.func(size, size));
+        gl.compressedTexImage2D(target, i, test.format, size, size, 0, pixels);
+        glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture should generate NO_ERROR."
+            + "level is " + i + ", size is " + size + "x" + size);
+      }
+
+      // out of bound test
+      // width or height out of bound
+      var pixelsNegativeTest1 = new test.dataType(test.func(t.maxSize * 2, t.maxSize * 2));
+      gl.compressedTexImage2D(target, 0, test.format, t.maxSize * 2, t.maxSize * 2, 0, pixelsNegativeTest1);
+      glErrorShouldBe(gl, gl.INVALID_VALUE, "width or height out of bound: should generate INVALID_VALUE."
+          + " level is 0, size is " + (t.maxSize * 2) + "x" + (t.maxSize * 2));
+      // level out of bound
+      var pixelsNegativeTest2 = new test.dataType(test.func(256, 256));
+      gl.compressedTexImage2D(target, numLevels, test.format, 256, 256, 0, pixelsNegativeTest2);
+      glErrorShouldBe(gl, gl.INVALID_VALUE, "level out of bound: should generate INVALID_VALUE."
+          + " level is " + numLevels + ", size is 256x256");
+      //width or height out of bound for specified level
+      gl.compressedTexImage2D(target, numLevels - 1, test.format, 256, 256, 0, pixelsNegativeTest2);
+      glErrorShouldBe(gl, gl.INVALID_VALUE, "width or height out of bound for specified level: should generate INVALID_VALUE."
+          + " level is " + (numLevels - 1) + ", size is 256x256");
+    }
+  }
+  else
+    testPassed("No " + test.extension + " extension support -- this is legal");
+}
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
+
diff --git a/conformance/extensions/webgl-debug-renderer-info.html b/conformance/extensions/webgl-debug-renderer-info.html
new file mode 100644
index 0000000..d65e6f2
--- /dev/null
+++ b/conformance/extensions/webgl-debug-renderer-info.html
@@ -0,0 +1,127 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WebGL_debug_renderer_info Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 1px; height: 1px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing standard derivatives -->
+
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_debug_renderer_info extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+var ext = null;
+var vao = null;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = gl.getExtension("WEBGL_debug_renderer_info");
+    if (!ext) {
+        testPassed("No WEBGL_debug_renderer_info support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_debug_renderer_info extension");
+
+        runSupportedTest(true);
+        runTestEnabled();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("WEBGL_debug_renderer_info") >= 0) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_debug_renderer_info listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_debug_renderer_info listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_debug_renderer_info not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_debug_renderer_info not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runTestDisabled() {
+    debug("Testing enums with extension disabled");
+
+    // Use the constants directly as we don't have the extension
+
+    var UNMASKED_VENDOR_WEBGL = 0x9245;
+    gl.getParameter(UNMASKED_VENDOR_WEBGL);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "UNMASKED_VENDOR_WEBGL should not be queryable if extension is disabled");
+
+    var UNMASKED_RENDERER_WEBGL = 0x9246;
+    gl.getParameter(UNMASKED_RENDERER_WEBGL);
+    glErrorShouldBe(gl, gl.INVALID_ENUM, "UNMASKED_RENDERER_WEBGL should not be queryable if extension is disabled");
+}
+
+function runTestEnabled() {
+    debug("Testing enums with extension enabled");
+
+    shouldBe("ext.UNMASKED_VENDOR_WEBGL", "0x9245");
+    gl.getParameter(ext.UNMASKED_VENDOR_WEBGL);
+    glErrorShouldBe(gl, gl.NO_ERROR, "UNMASKED_VENDOR_WEBGL query should succeed if extension is enable");
+
+    shouldBe("ext.UNMASKED_RENDERER_WEBGL", "0x9246");
+    gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
+    glErrorShouldBe(gl, gl.NO_ERROR, "UNMASKED_RENDERER_WEBGL query should succeed if extension is enable");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-debug-shaders.html b/conformance/extensions/webgl-debug-shaders.html
new file mode 100644
index 0000000..fd592f7
--- /dev/null
+++ b/conformance/extensions/webgl-debug-shaders.html
@@ -0,0 +1,165 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WebGL_debug_shaders Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 1px; height: 1px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing standard derivatives -->
+
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_debug_shaders extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+var ext = null;
+var shader = null;
+var program = null;
+var info = null;
+var translatedSource;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = gl.getExtension("WEBGL_debug_shaders");
+    if (!ext) {
+        testPassed("No WEBGL_debug_shaders support -- this is legal");
+
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_debug_shaders extension");
+
+        runSupportedTest(true);
+        runTestEnabled();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var supported = gl.getSupportedExtensions();
+    if (supported.indexOf("WEBGL_debug_shaders") >= 0) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_debug_shaders listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_debug_shaders listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_debug_shaders not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_debug_shaders not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runTestEnabled() {
+    debug("Testing function with extension enabled");
+
+    var shaderInfos = [
+      {
+        source: "void main() { gl_Position = vec4(1.0, 0.0, 0.0, 1.0); }",
+        type: gl.VERTEX_SHADER
+      },
+      {
+        source: "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }",
+        type: gl.FRAGMENT_SHADER
+      }
+    ];
+
+    // Do this twice to test for caching issues.
+    for (var jj = 0; jj < 2; ++jj) {
+        debug("pass:" + (jj + 1));
+        program = gl.createProgram();
+        for (var ii = 0; ii < shaderInfos.length; ++ii) {
+            info = shaderInfos[ii];
+
+            shader = gl.createShader(info.type);
+
+            // if no source has been defined or compileShader() has not been called,
+            // getTranslatedShaderSource() should return an empty string.
+            shouldBeNull("ext.getTranslatedShaderSource(shader)");
+            gl.shaderSource(shader, info.source);
+            shouldBeNull("ext.getTranslatedShaderSource(shader)");
+            gl.compileShader(shader);
+            shouldBeTrue("gl.getShaderParameter(shader, gl.COMPILE_STATUS)");
+            translatedSource = ext.getTranslatedShaderSource(shader);
+            glErrorShouldBe(gl, gl.NO_ERROR, "No gl error should occur");
+            if (translatedSource && translatedSource.length > 0) {
+                testPassed("Successfully called getTranslatedShaderSource()");
+            } else {
+                testFailed("Calling getTranslatedShaderSource() failed");
+            }
+            gl.attachShader(program, shader);
+        }
+        gl.linkProgram(program);
+        shouldBeTrue("gl.getProgramParameter(program, gl.LINK_STATUS)");
+    }
+
+    // Test changing the source. Make sure we get the correct source each time.
+    debug("test changing source");
+    shader = gl.createShader(gl.FRAGMENT_SHADER);
+    gl.shaderSource(shader, "void main() { gl_FragColor = vec4(gl_FragCoord.x, 0.0, 0.0, 1.0); }");
+    gl.compileShader(shader);
+    shouldBeTrue("gl.getShaderParameter(shader, gl.COMPILE_STATUS)");
+    translatedSource = ext.getTranslatedShaderSource(shader);
+    shouldBeTrue('translatedSource && translatedSource.indexOf("gl_FragCoord") >= 0');
+    // change the source but don't compile.
+    gl.shaderSource(shader, "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }");
+    // the source should NOT change. It should be the same as the old source.
+    newTranslatedSource = ext.getTranslatedShaderSource(shader);
+    shouldBe('newTranslatedSource', 'translatedSource');
+    // now compile.
+    gl.compileShader(shader);
+    shouldBeTrue("gl.getShaderParameter(shader, gl.COMPILE_STATUS)");
+    // the source should have change.
+    newTranslatedSource = ext.getTranslatedShaderSource(shader);
+    shouldNotBe('newTranslatedSource', 'translatedSource');
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-depth-texture.html b/conformance/extensions/webgl-depth-texture.html
new file mode 100644
index 0000000..ff00918
--- /dev/null
+++ b/conformance/extensions/webgl-depth-texture.html
@@ -0,0 +1,327 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<title>WebGL WEBGL_depth_texture Conformance Tests</title>
+</head>
+<body>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 a_position;
+void main()
+{
+    gl_Position = a_position;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D u_texture;
+uniform vec2 u_resolution;
+void main()
+{
+    vec2 texcoord = gl_FragCoord.xy / u_resolution;
+    gl_FragColor = texture2D(u_texture, texcoord);
+}
+</script>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_depth_texture extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var tex;
+var name;
+var supportedFormats;
+var canvas2;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_depth_texture");
+    if (!ext) {
+        testPassed("No WEBGL_depth_texture support -- this is legal");
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_depth_texture extension");
+
+        runSupportedTest(true);
+        runTestExtension();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_depth_texture");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_depth_texture listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_depth_texture listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_depth_texture not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_depth_texture not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+
+function runTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    shouldGenerateGLError(gl, gl.INVALID_ENUM, 'gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, 1, 1, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null)');
+    shouldGenerateGLError(gl, gl.INVALID_ENUM, 'gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, 1, 1, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null)');
+}
+
+
+function dumpIt(gl, res, msg) {
+  return;  // comment out to debug
+  debug(msg);
+  var actualPixels = new Uint8Array(res * res * 4);
+  gl.readPixels(0, 0, res, res, gl.RGBA, gl.UNSIGNED_BYTE, actualPixels);
+
+  for (var yy = 0; yy < res; ++yy) {
+    var strs = [];
+    for (var xx = 0; xx < res; ++xx) {
+      var actual = (yy * res + xx) * 4;
+      strs.push("(" + actualPixels[actual] + "," + actualPixels[actual+1] + "," + actualPixels[actual + 2] + "," + actualPixels[actual + 3] + ")");
+    }
+    debug(strs.join(" "));
+  }
+}
+function runTestExtension() {
+    debug("Testing WEBGL_depth_texture");
+
+    var res = 8;
+
+    // make canvas for testing.
+    canvas2 = document.createElement("canvas");
+    canvas2.width = res;
+    canvas2.height = res;
+    var ctx = canvas2.getContext("2d");
+    ctx.fillStyle = "blue";
+    ctx.fillRect(0, 0, canvas2.width, canvas2.height);
+
+    var program = wtu.setupProgram(gl, ['vshader', 'fshader'], ['a_position']);
+    gl.useProgram(program);
+    gl.uniform2f(gl.getUniformLocation(program, "u_resolution"), res, res);
+
+    var buffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+    gl.bufferData(
+        gl.ARRAY_BUFFER,
+        new Float32Array(
+            [   1,  1,  1,
+               -1,  1,  0,
+               -1, -1, -1,
+                1,  1,  1,
+               -1, -1, -1,
+                1, -1,  0,
+            ]),
+        gl.STATIC_DRAW);
+    gl.enableVertexAttribArray(0);
+    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+    var types = [
+        {obj: 'gl',  attachment: 'DEPTH_ATTACHMENT',         format: 'DEPTH_COMPONENT', type: 'UNSIGNED_SHORT',          data: 'new Uint16Array(1)' },
+        {obj: 'gl',  attachment: 'DEPTH_ATTACHMENT',         format: 'DEPTH_COMPONENT', type: 'UNSIGNED_INT',            data: 'new Uint32Array(1)' },
+        {obj: 'ext', attachment: 'DEPTH_STENCIL_ATTACHMENT', format: 'DEPTH_STENCIL',   type: 'UNSIGNED_INT_24_8_WEBGL', data: 'new Uint32Array(1)' }
+    ];
+
+    for (var ii = 0; ii < types.length; ++ii) {
+        var typeInfo = types[ii];
+        var type = typeInfo.type;
+        var typeStr = typeInfo.obj + '.' + type;
+
+        debug("");
+        debug("testing: " + type);
+
+        // check that cubemaps are not allowed.
+        var cubeTex = gl.createTexture();
+        gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTex);
+        var targets = [
+          'TEXTURE_CUBE_MAP_POSITIVE_X',
+          'TEXTURE_CUBE_MAP_NEGATIVE_X',
+          'TEXTURE_CUBE_MAP_POSITIVE_Y',
+          'TEXTURE_CUBE_MAP_NEGATIVE_Y',
+          'TEXTURE_CUBE_MAP_POSITIVE_Z',
+          'TEXTURE_CUBE_MAP_NEGATIVE_Z'
+        ];
+        for (var tt = 0; tt < targets.length; ++tt) {
+            shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.texImage2D(gl.' + targets[ii] + ', 1, gl.' + typeInfo.format + ', 1, 1, 0, gl.' + typeInfo.format + ', ' + typeStr + ', null)');
+        }
+
+        // check 2d textures.
+        tex = gl.createTexture();
+        gl.bindTexture(gl.TEXTURE_2D, tex);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+
+        // test level > 0
+        shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.texImage2D(gl.TEXTURE_2D, 1, gl.' + typeInfo.format + ', 1, 1, 0, gl.' + typeInfo.format + ', ' + typeStr + ', null)');
+
+        // test with data
+        shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.texImage2D(gl.TEXTURE_2D, 0, gl.' + typeInfo.format + ', 1, 1, 0, gl.' + typeInfo.format + ', ' + typeStr + ', ' + typeInfo.data + ')');
+
+        // test with canvas
+        shouldGenerateGLError(gl, [gl.INVALID_VALUE, gl.INVALID_ENUM, gl.INVALID_OPERATION], 'gl.texImage2D(gl.TEXTURE_2D, 0, gl.' + typeInfo.format + ', gl.' + typeInfo.format + ', ' + typeStr  + ', canvas2)');
+
+        // test copyTexImage2D
+        shouldGenerateGLError(gl, [gl.INVALID_ENUM, gl.INVALID_OPERATION], 'gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.' + typeInfo.format + ', 0, 0, 1, 1, 0)');
+
+        // test real thing
+        shouldGenerateGLError(gl, gl.NO_ERROR, 'gl.texImage2D(gl.TEXTURE_2D, 0, gl.' + typeInfo.format + ', ' + res + ', ' + res + ', 0, gl.' + typeInfo.format + ', ' + typeStr + ', null)');
+
+        // test texSubImage2D
+        shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 1, 1, gl.' + typeInfo.format + ', ' + typeStr  + ', ' + typeInfo.data + ')');
+
+        // test copyTexSubImage2D
+        shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1)');
+
+        // test generateMipmap
+        shouldGenerateGLError(gl, gl.INVALID_OPERATION, 'gl.generateMipmap(gl.TEXTURE_2D)');
+
+        var fbo = gl.createFramebuffer();
+        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl[typeInfo.attachment], gl.TEXTURE_2D, tex, 0);
+        // TODO: remove this check if the spec is updated to require these combinations to work.
+        if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
+        {
+            // try adding a color buffer.
+            var colorTex = gl.createTexture();
+            gl.bindTexture(gl.TEXTURE_2D, colorTex);
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, res, res, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0);
+        }
+
+        shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+        // use the default texture to render with while we return to the depth texture.
+        gl.bindTexture(gl.TEXTURE_2D, null);
+
+        // render the z-quad
+        gl.enable(gl.DEPTH_TEST);
+        gl.clearColor(1, 0, 0, 1);
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        dumpIt(gl, res, "--first--");
+
+        // render the depth texture.
+        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+        gl.bindTexture(gl.TEXTURE_2D, tex);
+        gl.clearColor(0, 0, 1, 1);
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        var actualPixels = new Uint8Array(res * res * 4);
+        gl.readPixels(0, 0, res, res, gl.RGBA, gl.UNSIGNED_BYTE, actualPixels);
+
+        dumpIt(gl, res, "--depth--");
+
+        // Check that each pixel's R value is less than that of the previous pixel
+        // in either direction. Basically verify we have a gradient.
+        var success = true;
+        for (var yy = 0; yy < res; ++yy) {
+          for (var xx = 0; xx < res; ++xx) {
+            var actual = (yy * res + xx) * 4;
+            var left = actual - 4;
+            var down = actual - res * 4;
+
+            if (xx > 0) {
+              if (actualPixels[actual] <= actualPixels[left]) {
+                  testFailed("actual(" + actualPixels[actual] + ") < left(" + actualPixels[left] + ")");
+                  success = false;
+              }
+            }
+            if (yy > 0) {
+                if (actualPixels[actual] <= actualPixels[down]) {
+                    testFailed("actual(" + actualPixels[actual] + ") < down(" + actualPixels[down] + ")");
+                    success = false;
+                }
+            }
+          }
+        }
+
+        // Check that bottom left corner is vastly different thatn top right.
+        if (actualPixels[(res * res - 1) * 4] - actualPixels[0] < 0xC0) {
+            testFailed("corners are not different enough");
+            success = false;
+        }
+
+        if (success) {
+            testPassed("depth texture rendered correctly.");
+        }
+
+        // check limitations
+        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl[typeInfo.attachment], gl.TEXTURE_2D, null, 0);
+        var badAttachment = typeInfo.attachment == 'DEPTH_ATTACHMENT' ? 'DEPTH_STENCIL_ATTACHMENT' : 'DEPTH_ATTACHMENT';
+        shouldGenerateGLError(gl, gl.NO_ERROR, 'gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.' + badAttachment + ', gl.TEXTURE_2D, tex, 0)');
+        shouldNotBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, 'gl.clear(gl.DEPTH_BUFFER_BIT)');
+        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+        shouldBe('gl.getError()', 'gl.NO_ERROR');
+    }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/conformance/extensions/webgl-draw-buffers.html b/conformance/extensions/webgl-draw-buffers.html
new file mode 100644
index 0000000..5bf20c7
--- /dev/null
+++ b/conformance/extensions/webgl-draw-buffers.html
@@ -0,0 +1,703 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WEBGL_draw_buffers Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="64" height="64"> </canvas>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 a_position;
+void main() {
+    gl_Position = a_position;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+#extension GL_EXT_draw_buffers : require
+precision mediump float;
+uniform vec4 u_colors[$(numDrawingBuffers)];
+void main() {
+    for (int i = 0; i < $(numDrawingBuffers); ++i) {
+        gl_FragData[i] = u_colors[i];
+    }
+}
+</script>
+<script id="fshaderRed" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+    gl_FragColor = vec4(1,0,0,1);
+}
+</script>
+<script id="fshaderMacroDisabled" type="x-shader/x-fragment">
+#ifdef GL_EXT_draw_buffers
+  bad code here
+#endif
+precision mediump float;
+void main() {
+    gl_FragColor = vec4(0,0,0,0);
+}
+</script>
+<script id="fshaderMacroEnabled" type="x-shader/x-fragment">
+#ifdef GL_EXT_draw_buffers
+  #if GL_EXT_draw_buffers == 1
+    #define CODE
+  #else
+    #define CODE this_code_is_bad_it_should_have_compiled
+  #endif
+#else
+   #define CODE this_code_is_bad_it_should_have_compiled
+#endif
+CODE
+precision mediump float;
+void main() {
+    gl_FragColor = vec4(0,0,0,0);
+}
+</script>
+<script id="fshaderBuiltInConstEnabled" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+    gl_FragColor = (gl_MaxDrawBuffers == $(numDrawingBuffers)) ? vec4(0,1,0,1) : vec4(1,0,0,1);
+}
+</script>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_draw_buffers extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var output = document.getElementById("console");
+var gl = wtu.create3DContext(canvas);
+var ext = null;
+var vao = null;
+
+var extensionConstants = [
+  { name: "MAX_COLOR_ATTACHMENTS_WEBGL", enum: 0x8CDF, expectedFn: function(v) { return v > 0; }, passMsg: " should be > 0"},
+  { name: "MAX_DRAW_BUFFERS_WEBGL",      enum: 0x8824, expectedFn: function(v) { return v > 0; }, passMsg: " should be > 0"},
+
+  { name: "COLOR_ATTACHMENT0_WEBGL",     enum: 0x8CE0, },
+  { name: "COLOR_ATTACHMENT1_WEBGL",     enum: 0x8CE1, },
+  { name: "COLOR_ATTACHMENT2_WEBGL",     enum: 0x8CE2, },
+  { name: "COLOR_ATTACHMENT3_WEBGL",     enum: 0x8CE3, },
+  { name: "COLOR_ATTACHMENT4_WEBGL",     enum: 0x8CE4, },
+  { name: "COLOR_ATTACHMENT5_WEBGL",     enum: 0x8CE5, },
+  { name: "COLOR_ATTACHMENT6_WEBGL",     enum: 0x8CE6, },
+  { name: "COLOR_ATTACHMENT7_WEBGL",     enum: 0x8CE7, },
+  { name: "COLOR_ATTACHMENT8_WEBGL",     enum: 0x8CE8, },
+  { name: "COLOR_ATTACHMENT9_WEBGL",     enum: 0x8CE9, },
+  { name: "COLOR_ATTACHMENT10_WEBGL",    enum: 0x8CEA, },
+  { name: "COLOR_ATTACHMENT11_WEBGL",    enum: 0x8CEB, },
+  { name: "COLOR_ATTACHMENT12_WEBGL",    enum: 0x8CEC, },
+  { name: "COLOR_ATTACHMENT13_WEBGL",    enum: 0x8CED, },
+  { name: "COLOR_ATTACHMENT14_WEBGL",    enum: 0x8CEE, },
+  { name: "COLOR_ATTACHMENT15_WEBGL",    enum: 0x8CEF, },
+
+  { name: "DRAW_BUFFER0_WEBGL",          enum: 0x8825, },
+  { name: "DRAW_BUFFER1_WEBGL",          enum: 0x8826, },
+  { name: "DRAW_BUFFER2_WEBGL",          enum: 0x8827, },
+  { name: "DRAW_BUFFER3_WEBGL",          enum: 0x8828, },
+  { name: "DRAW_BUFFER4_WEBGL",          enum: 0x8829, },
+  { name: "DRAW_BUFFER5_WEBGL",          enum: 0x882A, },
+  { name: "DRAW_BUFFER6_WEBGL",          enum: 0x882B, },
+  { name: "DRAW_BUFFER7_WEBGL",          enum: 0x882C, },
+  { name: "DRAW_BUFFER8_WEBGL",          enum: 0x882D, },
+  { name: "DRAW_BUFFER9_WEBGL",          enum: 0x882E, },
+  { name: "DRAW_BUFFER10_WEBGL",         enum: 0x882F, },
+  { name: "DRAW_BUFFER11_WEBGL",         enum: 0x8830, },
+  { name: "DRAW_BUFFER12_WEBGL",         enum: 0x8831, },
+  { name: "DRAW_BUFFER13_WEBGL",         enum: 0x8832, },
+  { name: "DRAW_BUFFER14_WEBGL",         enum: 0x8833, },
+  { name: "DRAW_BUFFER15_WEBGL",         enum: 0x8834, },
+];
+
+if (!gl) {
+  testFailed("WebGL context does not exist");
+} else {
+  testPassed("WebGL context exists");
+
+  // Run tests with extension disabled
+  runEnumTestDisabled();
+  runShadersTestDisabled();
+  runAttachmentTestDisabled();
+
+  debug("");
+
+  // Query the extension and store globally so shouldBe can access it
+  ext = gl.getExtension("WEBGL_draw_buffers");
+  if (!ext) {
+    testPassed("No WEBGL_draw_buffers support -- this is legal");
+
+    runSupportedTest(false);
+  } else {
+    testPassed("Successfully enabled WEBGL_draw_buffers extension");
+
+    runSupportedTest(true);
+    runEnumTestEnabled();
+    runShadersTestEnabled();
+    runAttachmentTestEnabled();
+    runDrawTests();
+
+    glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+  }
+}
+
+function createExtDrawBuffersProgram(scriptId, sub) {
+  var fsource = wtu.getScript(scriptId);
+  fsource = wtu.replaceParams(fsource, sub);
+  wtu.addShaderSource(output, "fragement shader", fsource);
+  return wtu.setupProgram(gl, ["vshader", fsource], ["a_position"]);
+}
+
+function runSupportedTest(extensionEnabled) {
+  var supported = gl.getSupportedExtensions();
+  if (supported.indexOf("WEBGL_draw_buffers") >= 0) {
+    if (extensionEnabled) {
+      testPassed("WEBGL_draw_buffers listed as supported and getExtension succeeded");
+    } else {
+      testFailed("WEBGL_draw_buffers listed as supported but getExtension failed");
+    }
+  } else {
+    if (extensionEnabled) {
+      testFailed("WEBGL_draw_buffers not listed as supported but getExtension succeeded");
+    } else {
+      testPassed("WEBGL_draw_buffers not listed as supported and getExtension failed -- this is legal");
+    }
+  }
+}
+
+function runEnumTestDisabled() {
+  debug("");
+  debug("Testing binding enum with extension disabled");
+
+  // Use the constant directly as we don't have the extension
+  extensionConstants.forEach(function(c) {
+    if (c.expectedFn) {
+      gl.getParameter(c.enum);
+      glErrorShouldBe(gl, gl.INVALID_ENUM, c.name + " should not be queryable if extension is disabled");
+    }
+  });
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function runEnumTestEnabled() {
+  debug("");
+  debug("Testing enums with extension enabled");
+
+  extensionConstants.forEach(function(c) {
+    shouldBe("ext." + c.name, "0x" + c.enum.toString(16));
+    if (c.expectedFn) {
+      glErrorShouldBe(gl, gl.NO_ERROR, "foo");
+      debug(c.name + ":" + ext[c.name].toString(16));
+      expectTrue(c.expectedFn(gl.getParameter(ext[c.name])), "gl.getParemter(ext." + c.name + ")" + c.passMsg);
+      glErrorShouldBe(gl, gl.NO_ERROR, c.name + " query should succeed if extension is enabled");
+    }
+  });
+
+  debug("Testing drawBuffersWEBGL with default drawing buffer");
+  shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK");
+  shouldGenerateGLError(gl, gl.INVALID_VALUE, "ext.drawBuffersWEBGL([])");
+  shouldGenerateGLError(gl, gl.INVALID_VALUE, "ext.drawBuffersWEBGL([gl.NONE, gl.NONE])");
+  shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL])");
+  shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "ext.drawBuffersWEBGL([gl.NONE])");
+  shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.NONE");
+  shouldGenerateGLError(gl, gl.NO_ERROR, "ext.drawBuffersWEBGL([gl.BACK])");
+  shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL)", "gl.BACK");
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function testShaders(tests, sub) {
+  tests.forEach(function(test) {
+    var shaders = [wtu.getScript(test.shaders[0]), wtu.replaceParams(wtu.getScript(test.shaders[1]), sub)];
+    wtu.addShaderSource(output, "vertex shader", shaders[0]);
+    wtu.addShaderSource(output, "fragement shader", shaders[1]);
+    var program = wtu.setupProgram(gl, shaders, ["a_position"]);
+    var programLinkedSuccessfully = (program != null);
+    var expectedProgramToLinkSuccessfully = (test.expectFailure == true);
+    expectTrue(programLinkedSuccessfully != expectedProgramToLinkSuccessfully, test.msg);
+    gl.deleteProgram(program);
+  });
+}
+
+function runShadersTestDisabled() {
+  debug("");
+  debug("test shaders disabled");
+
+  testShaders([
+    { shaders: ["vshader", "fshaderMacroDisabled"],
+      msg: "GL_EXT_draw_buffers should not be defined in GLSL",
+    },
+    { shaders: ["vshader", "fshader"],
+      msg: "#extension GL_EXT_draw_buffers should not be allowed in GLSL",
+      expectFailure: true,
+    },
+  ], {numDrawingBuffers: 1});
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function runShadersTestEnabled() {
+  debug("");
+  debug("test shaders enabled");
+
+  var sub = {numDrawingBuffers: gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL)};
+  testShaders([
+    { shaders: ["vshader", "fshaderMacroEnabled"],
+    msg: "GL_EXT_draw_buffers should be defined as 1 in GLSL",
+    },
+  ], sub);
+
+  var program = createExtDrawBuffersProgram("fshaderBuiltInConstEnabled", sub);
+  wtu.setupUnitQuad(gl);
+  wtu.clearAndDrawUnitQuad(gl);
+  wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+  gl.deleteProgram(program);
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function runAttachmentTestDisabled() {
+  debug("");
+  debug("test attachment disabled");
+  var tex = gl.createTexture();
+  var fb = gl.createFramebuffer();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + 1, gl.TEXTURE_2D, tex, 0);
+  glErrorShouldBe(gl, gl.INVALID_ENUM, "should not be able to attach to gl.COLOR_ATTACHMENT1");
+  gl.deleteFramebuffer(fb);
+  gl.deleteTexture(tex);
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function makeArray(size, value) {
+  var array = []
+  for (var ii = 0; ii < size; ++ii) {
+    array.push(value);
+  }
+  return array;
+}
+
+function makeColorAttachmentArray(size) {
+  var array = []
+  for (var ii = 0; ii < size; ++ii) {
+    array.push(gl.COLOR_ATTACHMENT0 + ii);
+  }
+  return array;
+}
+
+function runAttachmentTestEnabled() {
+  debug("");
+  debug("test attachment enabled");
+
+  var maxDrawingBuffers = gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL);
+  var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
+
+  var tex = gl.createTexture();
+  var fb = gl.createFramebuffer();
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments, gl.TEXTURE_2D, tex, 0);
+  glErrorShouldBe(gl, gl.INVALID_ENUM, "should not be able to attach pass the max attachment point: gl.COLOR_ATTACHMENT0 + " + maxColorAttachments);
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments - 1, gl.TEXTURE_2D, tex, 0);
+  glErrorShouldBe(gl, gl.NO_ERROR, "should be able to attach to the max attachment point: gl.COLOR_ATTACHMENT0 + " + (maxColorAttachments - 1));
+  ext.drawBuffersWEBGL(makeArray(maxDrawingBuffers, gl.NONE));
+  glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with array NONE of size " + maxColorAttachments);
+  var bufs = makeColorAttachmentArray(maxDrawingBuffers);
+  ext.drawBuffersWEBGL(bufs);
+  glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with array attachments of size " + maxColorAttachments);
+  bufs[0] = gl.NONE;
+  ext.drawBuffersWEBGL(bufs);
+  glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffersWEBGL with mixed array attachments of size " + maxColorAttachments);
+  if (maxDrawingBuffers > 1) {
+    bufs[0] = ext.COLOR_ATTACHMENT1_WEBGL;
+    bufs[1] = ext.COLOR_ATTACHMENT0_WEBGL;
+    ext.drawBuffersWEBGL(bufs);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be able to call drawBuffersWEBGL with out of order attachments of size " + maxColorAttachments);
+    var bufs = makeColorAttachmentArray(Math.floor(maxDrawingBuffers / 2));
+    glErrorShouldBe(gl, gl.NO_ERROR, "should not be able to call drawBuffersWEBGL with short array of attachments of size " + maxColorAttachments);
+  }
+
+  gl.deleteFramebuffer(fb);
+  gl.deleteTexture(tex);
+  glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function makeColorByIndex(index) {
+  var low = (index - 1) % 15 + 1;
+  var high = (index - 1) / 15;
+
+  var zeroOrOne = function(v) {
+    return v ? 1 : 0;
+  };
+
+  var oneOrTwo = function(v) {
+    return v ? 2 : 1;
+  }
+
+  var makeComponent = function(b0, b1, b2) {
+    return Math.floor(255 * zeroOrOne(b0) / oneOrTwo(b1) / oneOrTwo(b2));
+  };
+  return [
+    makeComponent(low & (1 << 0), high & (1 << 0), high & (1 << 4)),
+    makeComponent(low & (1 << 1), high & (1 << 1), high & (1 << 5)),
+    makeComponent(low & (1 << 2), high & (1 << 2), high & (1 << 6)),
+    makeComponent(low & (1 << 3), high & (1 << 3), high & (1 << 7)),
+  ];
+}
+
+function runDrawTests() {
+  debug("");
+  debug("--------- draw tests -----------");
+  var fb = gl.createFramebuffer();
+  var fb2 = gl.createFramebuffer();
+  var halfFB1 = gl.createFramebuffer();
+  var halfFB2 = gl.createFramebuffer();
+  var endsFB = gl.createFramebuffer();
+  var middleFB = gl.createFramebuffer();
+
+  var maxDrawingBuffers = gl.getParameter(ext.MAX_DRAW_BUFFERS_WEBGL);
+  var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
+  var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
+  var maxUsable = Math.min(maxDrawingBuffers, maxColorAttachments, maxUniformVectors);
+  var half = Math.floor(maxUsable / 2);
+  var bufs = makeColorAttachmentArray(maxUsable);
+  var nones = makeArray(maxUsable, gl.NONE);
+
+  [fb, fb2, halfFB1, halfFB2, endsFB, middleFB].forEach(function(fbo) {
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    ext.drawBuffersWEBGL(bufs);
+  });
+
+  var checkProgram = wtu.setupTexturedQuad(gl);
+  var redProgram = wtu.setupProgram(gl, ["vshader", "fshaderRed"], ["a_position"]);
+  var drawProgram = createExtDrawBuffersProgram("fshader", {numDrawingBuffers: maxDrawingBuffers});
+  var width = 64;
+  var height = 64;
+  var attachments = [];
+  // Makes 6 framebuffers.
+  // fb and fb2 have all the attachments.
+  // halfFB1 has the first half of the attachments
+  // halfFB2 has the second half of the attachments
+  // endsFB has the first and last attachments
+  // middleFB has all but the first and last attachments
+  for (var ii = 0; ii < maxUsable; ++ii) {
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, ii < half ? halfFB1 : halfFB2);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, (ii == 0 || ii == (maxUsable - 1)) ? endsFB : middleFB);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+    var location = gl.getUniformLocation(drawProgram, "u_colors[" + ii + "]");
+    var color = makeColorByIndex(ii + 1);
+    var floatColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255];
+    gl.uniform4fv(location, floatColor);
+    attachments.push({
+      texture: tex,
+      location: location,
+      color: color
+    });
+  }
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+
+  var checkAttachmentsForColorFn = function(attachments, colorFn) {
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+    gl.useProgram(checkProgram);
+    attachments.forEach(function(attachment, index) {
+      gl.bindTexture(gl.TEXTURE_2D, attachment.texture);
+      wtu.clearAndDrawUnitQuad(gl);
+      var expectedColor = colorFn(attachment, index);
+      var tolerance = 0;
+      expectedColor.forEach(function(v) {
+        if (v != 0 && v != 255) {
+          tolerance = 8;
+        }
+      });
+      wtu.checkCanvas(gl, expectedColor, "attachment " + index + " should be " + expectedColor.toString(), tolerance);
+    });
+    debug("");
+  };
+
+  var checkAttachmentsForColor = function(attachments, color) {
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return color || attachment.color;
+    });
+  };
+
+  var drawAndCheckAttachments = function(testFB, msg, testFn) {
+    debug("test clearing " + msg);
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+
+    attachments.forEach(function(attachment, index) {
+      debug("attachment: " + index + " = " + wtu.glEnumToString(gl, gl.getParameter(ext.DRAW_BUFFER0_WEBGL + index)) +
+            ", " + wtu.glEnumToString(gl, gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + index, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)));
+    });
+
+    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+      debug("framebuffer not complete");
+      debug("");
+      return;
+    }
+
+    // Clear all the attachments
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+    gl.clearColor(0, 0, 0, 0);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    //checkAttachmentsForColorFn(attachments, function(attachment, index) {
+    //  return [0, 0, 0, 0];
+    //});
+    //debug("--");
+
+    // Clear some attachments using testFB
+    gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+
+    gl.clearColor(0, 1, 0, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return testFn(attachment, index) ? [0, 255, 0, 255] : [0, 0, 0, 0];
+    });
+
+    debug("test drawing to " + msg);
+
+    // Draw to some attachments using testFB
+    gl.useProgram(drawProgram);
+    gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+    wtu.drawUnitQuad(gl);
+
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return testFn(attachment, index) ? attachment.color : [0, 0, 0, 0];
+    });
+  };
+
+  gl.useProgram(drawProgram);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  ext.drawBuffersWEBGL(bufs);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  ext.drawBuffersWEBGL(bufs);
+
+  wtu.drawUnitQuad(gl);
+
+  debug("test that each texture got the correct color.");
+
+  checkAttachmentsForColor(attachments);
+
+  debug("test clearing clears all the textures");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  gl.clearColor(0, 1, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  checkAttachmentsForColor(attachments, [0, 255, 0, 255]);
+
+  debug("test that NONE draws nothing");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  ext.drawBuffersWEBGL(nones);
+  gl.useProgram(redProgram);
+  wtu.clearAndDrawUnitQuad(gl);
+
+  checkAttachmentsForColor(attachments, [0, 255, 0, 255]);
+
+  debug("test that gl_FragColor broadcasts");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  ext.drawBuffersWEBGL(bufs);
+  gl.useProgram(redProgram);
+  wtu.drawUnitQuad(gl);
+
+  checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+  if (maxUsable > 1) {
+    var bufs1 = makeColorAttachmentArray(maxUsable);
+    var bufs2 = makeColorAttachmentArray(maxUsable);
+    for (var ii = 0; ii < maxUsable; ++ii) {
+      if (ii < half) {
+        bufs1[ii] = gl.NONE;
+      } else {
+        bufs2[ii] = gl.NONE;
+      }
+    }
+
+    debug("test setting first half to NONE and clearing");
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    ext.drawBuffersWEBGL(bufs1);
+    gl.clearColor(0, 1, 0, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return index < half ? [255, 0, 0, 255] : [0, 255, 0, 255];
+    });
+
+    debug("test setting first half to NONE and drawing");
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.useProgram(drawProgram);
+    wtu.drawUnitQuad(gl);
+
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return index < half ? [255, 0, 0, 255] : attachment.color;
+    });
+
+    debug("test setting second half to NONE and clearing");
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    ext.drawBuffersWEBGL(bufs);
+    gl.clearColor(1, 0, 0, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    ext.drawBuffersWEBGL(bufs2);
+    gl.clearColor(0, 0, 1, 1);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return index < half ? [0, 0, 255, 255] : [255, 0, 0, 255];
+    });
+
+    debug("test setting second half to NONE and drawing");
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.useProgram(drawProgram);
+    wtu.drawUnitQuad(gl);
+
+    checkAttachmentsForColorFn(attachments, function(attachment, index) {
+      return index < half ? attachment.color : [255, 0, 0, 255];
+    });
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB1);
+    ext.drawBuffersWEBGL(bufs);
+    drawAndCheckAttachments(
+      halfFB1, "framebuffer that only has first half of attachments",
+      function(attachment, index) {
+        return index < half;
+      });
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB2);
+    ext.drawBuffersWEBGL(bufs);
+    drawAndCheckAttachments(
+      halfFB2, "framebuffer that only has second half of attachments",
+      function(attachment, index) {
+        return index >= half;
+      });
+
+    if (maxUsable > 2) {
+      gl.bindFramebuffer(gl.FRAMEBUFFER, endsFB);
+      ext.drawBuffersWEBGL(bufs);
+      drawAndCheckAttachments(
+        endsFB, "framebuffer that only has first and last attachments",
+        function(attachment, index) {
+          return index == 0 || index == (maxUsable - 1);
+        });
+
+      gl.bindFramebuffer(gl.FRAMEBUFFER, middleFB);
+      ext.drawBuffersWEBGL(bufs);
+      drawAndCheckAttachments(
+        middleFB,
+        "framebuffer that has all but the first and last attachments",
+        function(attachment, index) {
+          return index != 0 && index != (maxUsable - 1);
+        });
+    }
+  }
+
+  debug("test switching between fbos keeps drawbuffer state");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  ext.drawBuffersWEBGL(nones);
+
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  ext.drawBuffersWEBGL(bufs);
+  gl.clearColor(1, 0, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  gl.useProgram(drawProgram);
+  wtu.drawUnitQuad(gl);
+  checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  gl.useProgram(drawProgram);
+  wtu.drawUnitQuad(gl);
+  checkAttachmentsForColor(attachments);
+
+  debug("test queries");
+  debug("check framebuffer with all attachments on");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  for (var ii = 0; ii < maxUsable; ++ii) {
+    shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL + " + ii + ")", "gl.COLOR_ATTACHMENT0 + " + ii);
+  }
+
+  debug("check framebuffer with all attachments off");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  for (var ii = 0; ii < maxUsable; ++ii) {
+    shouldBe("gl.getParameter(ext.DRAW_BUFFER0_WEBGL + " + ii + ")", "gl.NONE");
+  }
+
+  debug("test attachment size mis-match");
+  gl.bindTexture(gl.TEXTURE_2D, attachments[0].texture);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width * 2, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+  shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+
+  gl.deleteFramebuffer(fb);
+  gl.deleteFramebuffer(fb2);
+  gl.deleteFramebuffer(halfFB1);
+  gl.deleteFramebuffer(halfFB2);
+  attachments.forEach(function(attachment) {
+    gl.deleteTexture(attachment.texture);
+  });
+  gl.deleteProgram(checkProgram);
+  gl.deleteProgram(redProgram);
+  gl.deleteProgram(drawProgram);
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/conformance/extensions/webgl-shared-resources.html b/conformance/extensions/webgl-shared-resources.html
new file mode 100644
index 0000000..e28a562
--- /dev/null
+++ b/conformance/extensions/webgl-shared-resources.html
@@ -0,0 +1,862 @@
+<!--
+
+/*
+** Copyright (c) 2013 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WEBGL_Shared_Resources Conformance Test</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/desktop-gl-constants.js" type="text/javascript"></script>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+</head>
+<body>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 a_position;
+void main() {
+  gl_Position = a_position;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform vec4 u_color;
+void main() {
+  gl_FragColor = u_color;
+}
+</script>
+<style>
+canvas {
+    border: 1px solid black;
+}
+</style>
+<canvas id="canvas1" width="64" height="64"> </canvas>
+<canvas id="canvas2" width="64" height="64"> </canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description();
+
+var wtu = WebGLTestUtils;
+var canvas1 = document.getElementById("canvas1");
+var gl = wtu.create3DContext(canvas1);
+var gl2;
+var ext = null;
+var ext2;
+var ext3;
+var buf;
+var elementBuf;
+var tex;
+var tex3;
+var rb;
+var fb;
+var id;
+var resource;
+var shader;
+var program;
+var uniformLocation;
+var acquiredFlag;
+var shaderProgram;  // acquired progam (never released) used for shader testing
+var programShader;  // acquired shader (never released) used for program testing
+
+if (!gl) {
+  testFailed("context does not exist");
+} else {
+  testPassed("context exists");
+
+  // Run tests with extension disabled
+  runTestDisabled();
+
+  // Query the extension and store globally so shouldBe can access it
+  ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_shared_resources");
+  if (!ext) {
+      testPassed("No WEBGL_shared_resources support -- this is legal");
+      runSupportedTest(false);
+      finishTest();
+  } else {
+      testPassed("Successfully enabled WEBGL_shared_resources extension");
+
+      runSupportedTest(true);
+      runTestExtension();
+  }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_shared_resources");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_shared_resources listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_shared_resources listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_shared_resources not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_shared_resources not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+function runTestDisabled() {
+    // There is no functionality accessable with this extension disabled.
+}
+
+function makeFailCallback(msg) {
+  return function() {
+    testFailed(msg);
+  }
+};
+
+
+function runTestExtension() {
+  var canvas2 = document.getElementById("canvas2");
+  gl2 = wtu.create3DContext(canvas2, { group: ext.group });
+  ext2 = wtu.getExtensionWithKnownPrefixes(gl2, "WEBGL_shared_resources");
+
+  // Runs an array of functions. Expects each function takes a callback
+  // it will call when finished.
+  var runSequence = function(steps) {
+    var stepNdx = 0;
+    var runNextStep = function() {
+      if (stepNdx < steps.length) {
+        steps[stepNdx++](runNextStep);
+      }
+    };
+    runNextStep();
+  };
+
+  var bufferTests = {
+    resourceType: "buffer",
+
+    setupFunction: function() {
+      buf = gl.createBuffer();
+      gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+      gl.bufferData(gl.ARRAY_BUFFER, 16, gl.STATIC_DRAW);
+      return buf;
+    },
+
+    bindFunction: function(buf) {
+      gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    },
+
+    implicitBindFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0)");
+    },
+
+    modificationFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.bufferData(gl.ARRAY_BUFFER, 16, gl.STATIC_DRAW)");
+      shouldGenerateGLError(gl, expectedError, "gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Uint8Array(4))");
+    },
+
+    queryFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE)");
+    },
+  };
+
+  var programTests = {
+    resourceType: "program",
+
+    setupFunction: function() {
+      // We need a valid a program with valid shaders to share because the only way to 'bind'
+      // a program is to call gl.useProgram and you can't call gl.useProgram on an invalid program.
+      program = wtu.setupProgram(gl, ["vshader", "fshader"]);
+      programShader = gl.getAttachedShaders(program)[0];
+      uniformLocation = gl.getUniformLocation(program, "u_color");
+      return program;
+    },
+
+    bindFunction: function(program) {
+      gl.useProgram(program);
+    },
+
+    implicitBindFunctions: function(expectedError) {
+    },
+
+    modificationFunctions: function(expectedError) {
+      if (expectedError == gl.NO_ERROR) {
+        // Need to get a new location because we may have re-linked.
+        uniformLocation = gl.getUniformLocation(program, 'u_color');
+      }
+      shouldGenerateGLError(gl, expectedError, "gl.uniform4f(uniformLocation, 0, 1, 2, 3)");
+      shouldGenerateGLError(gl, expectedError, "gl.detachShader(program, programShader)");
+      shouldGenerateGLError(gl, expectedError, "gl.attachShader(program, programShader)");
+      shouldGenerateGLError(gl, expectedError, "gl.bindAttribLocation(program, 0, 'foo')");
+      shouldGenerateGLError(gl, expectedError, "gl.linkProgram(program)");
+    },
+
+    queryFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.getProgramParameter(program, gl.LINK_STATUS)");
+      shouldGenerateGLError(gl, expectedError, "gl.getProgramInfoLog(program)");
+      shouldGenerateGLError(gl, expectedError, "gl.getAttachedShaders(program)");
+      shouldGenerateGLError(gl, expectedError, "gl.getAttribLocation(program, 'foo')");
+      shouldGenerateGLError(gl, expectedError, "gl.getUniformLocation(program, 'foo')");
+      shouldGenerateGLError(gl, expectedError, "gl.getActiveAttrib(program, 0)");
+      shouldGenerateGLError(gl, expectedError, "gl.getActiveUniform(program, 0)");
+      shouldGenerateGLError(gl, expectedError, "gl.getUniform(program, uniformLocation)");
+    },
+  };
+
+  var renderbufferTests = {
+    resourceType: "renderbuffer",
+
+    setupFunction: function() {
+      rb = gl.createRenderbuffer();
+      gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+      gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16);
+      fb = gl.createFramebuffer();
+      return rb;
+    },
+
+    bindFunction: function(rb) {
+      gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+    },
+
+    implicitBindFunctions: function(expectedError) {
+       gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+       shouldGenerateGLError(gl, expectedError, "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb)");
+       gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+    },
+
+    modificationFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16)");
+    },
+
+    queryFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)");
+    },
+  };
+
+
+  var shaderTests = {
+    resourceType: "shader",
+
+    setupFunction: function() {
+      var shaderSource = "Hello World";
+      shader = gl.createShader(gl.VERTEX_SHADER);
+      gl.shaderSource(shader, shaderSource);
+      shaderProgram = gl.createProgram();
+      gl.attachShader(shaderProgram, shader);
+      return shader;
+    },
+
+    bindFunction: function(shader) {
+      gl.detachShader(shaderProgram, shader);  // you can't attach if a shader is already attached.
+      gl.attachShader(shaderProgram, shader);
+    },
+
+    implicitBindFunctions: function(expectedError) {
+    },
+
+    modificationFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.shaderSource(shader, 'foo')");
+      shouldGenerateGLError(gl, expectedError, "gl.compileShader(shader)");
+    },
+
+    queryFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.getShaderParameter(shader, gl.COMPILE_STATUS)");
+      shouldGenerateGLError(gl, expectedError, "gl.getShaderInfoLog(shader)");
+    },
+  };
+
+  var textureTests = {
+    resourceType: "texture",
+
+    setupFunction: function() {
+      tex = gl.createTexture();
+      gl.bindTexture(gl.TEXTURE_2D, tex);
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 16, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+      return tex;
+    },
+
+    bindFunction: function(tex) {
+      gl.bindTexture(gl.TEXTURE_2D, tex);
+    },
+
+    implicitBindFunctions: function(expectedError) {
+    },
+
+    modificationFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)");
+      shouldGenerateGLError(gl, expectedError, "gl.generateMipmap(gl.TEXTURE_2D)");
+      shouldGenerateGLError(gl, expectedError, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 16, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)");
+      shouldGenerateGLError(gl, expectedError, "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      shouldGenerateGLError(gl, expectedError, "gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 16, 16, 0)");
+      shouldGenerateGLError(gl, expectedError, "gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 16, 16)");
+      // TODO: Add compressed texture test if extension exists?
+    },
+
+    queryFunctions: function(expectedError) {
+      shouldGenerateGLError(gl, expectedError, "gl.getTexParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S)");
+    },
+  };
+
+  var testResourceWithSingleContext = function(info, callback) {
+    var resourceType = info.resourceType;
+
+    debug("")
+    debug("test " + resourceType);
+    var resource = info.setupFunction();
+    ext.releaseSharedResource(resource);
+
+    debug("");
+    debug("test " + resourceType + " functions can not be called on released " + resourceType);
+    info.modificationFunctions(gl.INVALID_OPERATION);
+    info.implicitBindFunctions(gl.INVALID_OPERATION);
+    info.queryFunctions(gl.INVALID_OPERATION);
+
+    debug("");
+    debug("test acquring " + resourceType);
+    ext.acquireSharedResource(resource, ext.READ_ONLY, function() {
+      debug("");
+      debug("test " + resourceType + " functions can not be called on READ_ONLY acquired " + resourceType + " that has not been bound");
+      info.queryFunctions(gl.INVALID_OPERATION);
+      info.modificationFunctions(gl.INVALID_OPERATION);
+
+      debug("");
+      debug("test query " + resourceType + " functions can be called on READ_ONLY acquired " + resourceType + " that has been bound but not " + resourceType + " modification functions");
+      info.bindFunction(resource);
+      info.queryFunctions(gl.NO_ERROR);
+      info.modificationFunctions(gl.INVALID_OPERATION);
+
+      ext.releaseSharedResource(resource);
+      ext.acquireSharedResource(resource, ext.EXCLUSIVE, function() {
+        debug("");
+        debug("test " + resourceType + " functions can not be called on EXCLUSIVE acquired " + resourceType + " that has not been bound");
+        info.queryFunctions(gl.INVALID_OPERATION);
+        info.modificationFunctions(gl.INVALID_OPERATION);
+
+        debug("");
+        debug("test all " + resourceType + " functions can be called on EXCLUSIVE acquired " + resourceType + " that has been bound.");
+        info.bindFunction(resource)
+        info.queryFunctions(gl.NO_ERROR);
+        info.modificationFunctions(gl.NO_ERROR);
+        callback();
+      });
+    });
+  };
+
+  var makeSingleContextResourceTest = function(info) {
+    return function(callback) {
+      testResourceWithSingleContext(info, callback);
+    };
+  };
+
+  var testCommonResourceFeatures = function(info, callback) {
+    var type = info.resourceType.charAt(0).toUpperCase() + info.resourceType.slice(1);
+    acquiredFlag = false;
+    debug("");
+    debug("test common features of " + type);
+
+    resource = info.setupFunction();
+    info.bindFunction(resource);
+
+    debug("Test a deleted resource can still be acquired.");
+    var checkAcquireAfterDelete = function() {
+      debug("check Acquire After Delete");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.delete" + type + "(resource)");
+// TODO: fix spec then comment this in      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.bind" + type + "(gl." + target + ", resource)");  // You can't bind a deleted resource
+      shouldGenerateGLError(gl, gl.NO_ERROR, "ext.releaseSharedResource(resource)");
+      callback();
+    };
+
+    var checkDeleteExclusive = function() {
+      debug("check delete EXLUSIVE");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.delete" + type + "(resource)");
+      info.bindFunction(resource);
+      glErrorShouldBe(gl, gl.NO_ERROR, "no error deleting exclusively acquired resource");
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.delete" + type + "(resource)");
+      ext.releaseSharedResource(resource);
+      ext.acquireSharedResource(resource, ext.EXCLUSIVE, checkAcquireAfterDelete);
+    };
+
+    var checkDeleteReadOnly = function() {
+      acquiredFlag = true;
+      debug("check delete READ_ONLY");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.delete" + type + "(resource)");
+      info.bindFunction(resource);
+      glErrorShouldBe(gl, gl.NO_ERROR, "no error bind read only acquired resource");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.delete" + type + "(resource)");  // We're READ_ONLY so this should fail
+      ext.releaseSharedResource(resource);
+      ext.acquireSharedResource(resource, ext.EXCLUSIVE, checkDeleteExclusive);
+    };
+
+    debug("Test you can't have 2 outstanding requests for the same resource.");
+    ext.releaseSharedResource(resource);
+    ext.acquireSharedResource(resource, ext.READ_ONLY, checkDeleteReadOnly);
+    glErrorShouldBe(gl, gl.NO_ERROR, "should be no error from 1st acquire request");
+    ext.acquireSharedResource(resource, ext.READ_ONLY, checkDeleteReadOnly);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "should be INVALID_OPERATION from 2nd acquire request");
+
+    debug("Test acquire does not happen immediately on release (must exit current event)");
+    shouldBeTrue("acquiredFlag === false");
+  };
+
+  var makeCommonResourceFeatureTest = function(info) {
+    return function(callback) {
+      testCommonResourceFeatures(info, callback);
+    };
+  };
+
+  // acquire multiple resources in multiple contexts.
+  var acquireMultipleResources = function(extensions, resources, mode, callback) {
+    var numNeeded = resources.length * extensions.length;
+
+    var checkAcquire = function() {
+      --numNeeded;
+      if (numNeeded == 0) {
+        callback();
+      }
+    };
+
+    resources.forEach(function(resource) {
+      extensions.forEach(function(ext) {
+        ext.acquireSharedResource(resource, mode, checkAcquire);
+      });
+    });
+  };
+
+  // release multiple resources in multiple contexts.
+  var releaseMultipleResources = function(extensions, resources) {
+    resources.forEach(function(resource) {
+      extensions.forEach(function(ext) {
+        ext.releaseSharedResource(resource);
+      });
+    });
+  };
+
+  var testRendering = function(callback) {
+    debug("");
+    debug("test rendering");
+    var positionLocation = 0;
+    var texcoordLocation = 1;
+    var program = wtu.setupSimpleTextureProgram(gl, positionLocation, texcoordLocation);
+    var buffers = wtu.setupUnitQuad(gl, positionLocation, texcoordLocation);
+    var rb = gl.createRenderbuffer();
+    var fb = gl.createFramebuffer();
+
+    var elementBuf = gl.createBuffer();
+    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuf);
+    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 3, 4, 5]), gl.STATIC_DRAW);
+
+    var width = 16;
+    var height = 16;
+
+    gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+    gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, width, height);
+
+    var destTex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, destTex);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+    // It's not clear if gl.RGBA4 must be framebuffer complete.
+    var canCheckRenderbuffer = (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE);
+
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, destTex, 0);
+    glErrorShouldBe(gl, gl.NO_ERROR, "setup");
+    shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE");
+
+    var tex = gl.createTexture();
+    wtu.fillTexture(gl, tex, 1, 1, [0, 255, 0, 255]);
+
+    if (!program) {
+      testFailed("could not link program");
+      callback();
+      return;
+    }
+
+    var releaseAndAcquireResources = function(callback) {
+      var resources = [buffers[0], buffers[1], tex, program, elementBuf];
+      releaseMultipleResources([ext], resources);
+      acquireMultipleResources([ext, ext2], resources, ext.READ_ONLY, callback);
+    };
+
+    var doRenderTest = function(callback) {
+      debug("Test 2 contexts can render with read only resources.");
+
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      gl.bindBuffer(gl.ARRAY_BUFFER, buffers[0]);
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      gl.bindBuffer(gl.ARRAY_BUFFER, buffers[1]);
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      gl.bindTexture(gl.TEXTURE_2D, tex);
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      gl.useProgram(program);
+
+      // Render to canvas1;
+      debug("render with context 1");
+      wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 0, 0, 0]);
+      wtu.drawUnitQuad(gl);
+      wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 0, 255]);
+
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)");
+      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuf);
+      gl.clear(gl.COLOR_BUFFER_BIT);
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)");
+      wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 0, 255]);
+
+      // Render to canvas2;
+      debug("render with context 2");
+      gl2.useProgram(program);
+
+      gl2.bindBuffer(gl.ARRAY_BUFFER, buffers[0]);
+      gl2.enableVertexAttribArray(positionLocation);
+      gl2.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
+      gl2.bindBuffer(gl.ARRAY_BUFFER, buffers[1]);
+      gl2.enableVertexAttribArray(texcoordLocation);
+      gl2.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);
+
+      gl2.bindTexture(gl.TEXTURE_2D, tex);
+
+      wtu.checkCanvas(gl2, [0, 0, 0, 0]);
+      wtu.drawUnitQuad(gl2);
+      wtu.checkCanvas(gl2, [0, 255, 0, 255]);
+
+      shouldGenerateGLError(gl2, gl2.INVALID_OPERATION, "gl2.drawElements(gl2.TRIANGLES, 6, gl2.UNSIGNED_SHORT, 0)");
+      gl2.bindBuffer(gl2.ELEMENT_ARRAY_BUFFER, elementBuf);
+      gl2.clear(gl2.COLOR_BUFFER_BIT);
+      shouldGenerateGLError(gl2, gl2.NO_ERROR, "gl2.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)");
+      wtu.checkCanvas(gl2, [0, 255, 0, 255]);
+
+      debug("Test you can't render to a framebuffer with a released texture");
+      ext.releaseSharedResource(destTex);
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.clear(gl.COLOR_BUFFER_BIT)");
+
+      debug("Test you can't read from a framebuffer with a released texture");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+
+      ext.acquireSharedResource(destTex, ext.READ_ONLY, callback);
+    };
+
+    var checkReadOnlyTextureOnFramebuffer = function(callback) {
+      debug("");
+      debug("test READ_ONLY texture attachment");
+      debug("Test we fail of we haven't bound again");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+
+      gl.activeTexture(gl.TEXTURE1);
+      gl.bindTexture(gl.TEXTURE_2D, destTex);
+      gl.activeTexture(gl.TEXTURE0);
+      debug("Test we fail to draw because we're read only.");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.clear(gl.COLOR_BUFFER_BIT)");
+
+      debug("Test we can read a READ_ONLY texture.");
+      shouldBeTrue("gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE");
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+
+      checkRenderbuffer(callback);
+    };
+
+    var checkRenderbuffer = function(callback) {
+      if (canCheckRenderbuffer) {
+        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+        wtu.drawUnitQuad(gl);
+        wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 0, 255]);
+
+        debug("Test you can't render to a framebuffer with a released renderbuffer");
+        ext.releaseSharedResource(rb);
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.clear(gl.COLOR_BUFFER_BIT)");
+
+        debug("Test you can't read from a framebuffer with a released renderbuffer");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      }
+
+      ext.acquireSharedResource(rb, ext.READ_ONLY, callback);
+    };
+
+    var checkReadOnlyRenderbufferOnFramebuffer = function(callback) {
+      if (canCheckRenderbuffer) {
+        debug("");
+        debug("test READ_ONLY renderbuffer attachment");
+        debug("Test we fail of we haven't bound again");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+
+        gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+        debug("Test we fail to draw because we're read only.");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.clear(gl.COLOR_BUFFER_BIT)");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
+
+        debug("Test we can read a READ_ONLY renderbuffer.");
+        shouldBeTrue("gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE");
+        shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      }
+
+      ext.releaseSharedResource(rb);
+      ext.acquireSharedResource(rb, ext.READ_ONLY, callback);
+    };
+
+    var checkRenderbufferBindsOnAttach = function(callback) {
+      if (canCheckRenderbuffer) {
+        debug("");
+        debug("Test we fail of we haven't bound again");
+        shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+
+        debug("Test attaching a renderbuffer marks it as bound");
+        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+
+        debug("Test we can read a READ_ONLY renderbuffer.");
+        shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      }
+
+      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, destTex, 0);
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+      ext.releaseSharedResource(destTex);
+      ext.acquireSharedResource(destTex, ext.READ_ONLY, callback);
+    };
+
+    var checkTextureBindsOnAttach = function(callback) {
+      debug("");
+      debug("Test we fail of we haven't bound again");
+      shouldGenerateGLError(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+
+      debug("Test attaching a texture marks it as bound");
+      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, destTex, 0);
+
+      debug("Test we can read a READ_ONLY texture.");
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4))");
+
+      callback();
+    };
+
+    var checkCanNotRenderWithReleasedProgram = function(callback) {
+      debug("");
+      gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.releaseSharedResource(program);
+
+      debug("Test we can't draw with a released program.");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.acquireSharedResource(program, ext.EXCLUSIVE, callback);
+      ext2.releaseSharedResource(program);
+    };
+
+    var checkCanNotRenderWithReleasedBuffer = function(callback) {
+      debug("");
+      gl.useProgram(program);
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.releaseSharedResource(buffers[0]);
+
+      debug("Test we can't draw with a released buffer.");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.acquireSharedResource(buffers[0], ext.READ_ONLY, callback);
+    };
+
+    var checkCanNotRenderWithReleasedTexture = function(callback) {
+      debug("");
+      gl.bindBuffer(gl.ARRAY_BUFFER, buffers[0]);
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.releaseSharedResource(tex);
+
+      debug("Test we can't draw with a released texture.");
+      shouldGenerateGLError(gl, gl.INVALID_OPERATION, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+
+      ext.acquireSharedResource(tex, ext.READ_ONLY, callback);
+    };
+
+    var checkCanRenderWithReleasedShader = function(callback) {
+      gl.bindTexture(gl.TEXTURE_2D, tex);
+      var shaders = gl.getAttachedShaders(program);
+      ext.releaseSharedResource(shaders[0]);
+
+      debug("");
+      debug("Test we can draw with a released shader.");
+      shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 6)");
+      callback();
+    };
+
+    runSequence(
+      [
+        releaseAndAcquireResources,
+        doRenderTest,
+        checkReadOnlyTextureOnFramebuffer,
+        checkReadOnlyRenderbufferOnFramebuffer,
+        checkRenderbufferBindsOnAttach,
+        checkTextureBindsOnAttach,
+        checkCanNotRenderWithReleasedProgram,
+        checkCanNotRenderWithReleasedBuffer,
+        checkCanNotRenderWithReleasedTexture,
+        checkCanRenderWithReleasedShader,
+        callback,
+      ]);
+  };
+
+  var testMisc = function(callback) {
+    debug("");
+    debug("Test you can't release a framebuffer");
+    // TODO: It's not clear what should happen here to me.
+    //shouldThrow("ext.releaseSharedResource(fb)", "TypeError");
+
+    debug("")
+    debug("Test you can compare sharegroups");
+    var gl3 = wtu.create3DContext();
+    ext3 = wtu.getExtensionWithKnownPrefixes(gl3, "WEBGL_shared_resources");
+    // TODO: comment in once this comparison works.
+    //shouldBeTrue("ext.group == ext2.group");
+    shouldBeTrue("ext.group != ext3.group");
+
+    debug("Test you can't use resources from another different group.");
+    tex3 = gl3.createTexture();
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.releaseSharedResource(tex3)");
+    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "ext.acquireSharedResource(tex3, ext.READ_ONLY, makeFailCallback('should not be able to acquire resource from other context'))");
+
+    var failTest = function() {
+      testFailed("cancelled callback was called");
+    };
+
+    var tex = gl.createTexture();
+    debug("test releasing from the wrong context. Should be a no-op");
+    shouldGenerateGLError(gl, gl.NO_ERROR, "ext2.releaseSharedResource(tex)");
+
+    id = ext2.acquireSharedResource(tex, ext.READ_ONLY, failTest);
+    debug("test cancelling a request for which an event has not been posted");
+    ext2.cancelAcquireSharedResource(id);
+
+    debug("test cancelling a request for which an event has already been posted");
+    ext.releaseSharedResource(tex);
+    id = ext.acquireSharedResource(tex, ext.READ_ONLY, failTest);
+    ext.cancelAcquireSharedResource(id);
+
+    debug("test cancelling on the wrong context's extension is ignored");
+    id = ext2.acquireSharedResource(tex, ext.READ_ONLY, callback);
+    shouldGenerateGLError(gl, gl.NO_ERROR, 'ext.cancelAcquireSharedResource(id)');
+  };
+
+  var testLostContext = function(callback) {
+    var WEBGL_lose_context = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_lose_context");
+    if (!WEBGL_lose_context) {
+      callback();
+      return;
+    }
+
+    var tex = gl.createTexture();
+    var tex2 = gl.createTexture();
+
+    var setupAcquire = function(callback) {
+      var callbacksNeeded = 3;
+      var waitForContextLostAndAcquire = function(e) {
+        if (e && e.preventDefault) {
+          e.preventDefault();  // allow context restore.
+        }
+        --callbacksNeeded;
+        if (callbacksNeeded == 0) {
+          callback();
+        }
+        return false;
+      };
+
+      debug("");
+      debug("Test extension still functions during context lost.");
+      acquireMultipleResources([ext2], [tex, tex2], ext2.READ_ONLY, waitForContextLostAndAcquire);
+      canvas1.addEventListener("webglcontextlost", waitForContextLostAndAcquire, false);
+      canvas2.addEventListener("webglcontextlost", waitForContextLostAndAcquire, false);
+      // Release one before context lost
+      ext.releaseSharedResource(tex);
+      WEBGL_lose_context.loseContext();
+      // Release one after context lost
+      ext.releaseSharedResource(tex2);
+
+      shouldBeTrue('gl.isContextLost()');
+      shouldBeTrue('gl2.isContextLost()');
+    };
+
+    var checkAcquireExt2 = function(callback) {
+      testPassed("was able to acquire resources during context lost");
+      acquireMultipleResources([ext], [tex, tex2], ext.READ_ONLY, callback);
+    };
+
+    var checkAcquireExt = function(callback) {
+      testPassed("was able to request acquire resources during context lost");
+      canvas1.addEventListener("webglcontextrestored", callback, false);
+      WEBGL_lose_context.restoreContext();
+    };
+
+    var passTest = function(callback) {
+      testPassed("extension works during lost context");
+      callback();
+    };
+
+    runSequence(
+      [
+        setupAcquire,
+        checkAcquireExt2,
+        checkAcquireExt,
+        passTest,
+        callback,
+      ]);
+  };
+
+  runSequence(
+    [
+      makeCommonResourceFeatureTest(bufferTests),
+      makeCommonResourceFeatureTest(programTests),
+      makeCommonResourceFeatureTest(shaderTests),
+      makeCommonResourceFeatureTest(renderbufferTests),
+      makeCommonResourceFeatureTest(textureTests),
+      makeSingleContextResourceTest(bufferTests),
+      makeSingleContextResourceTest(programTests),
+      makeSingleContextResourceTest(renderbufferTests),
+      makeSingleContextResourceTest(shaderTests),
+      makeSingleContextResourceTest(textureTests),
+      testRendering,
+      testMisc,
+      testLostContext,
+      finishTest,
+    ]);
+
+}
+var successfullyParsed = true;
+</script>
+</body>
+</html>
+