<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
<script>
"use strict";
var wtu;
var canvas;
var gl;
var shouldGenerateGLError;
var extensionName;
var extension;

var buffer;
var framebuffer;
var program;
var renderbuffer;
var shader;
var texture;
var uniformLocation;
var arrayBuffer;
var arrayBufferView
var image;
var video;
var canvas2d;
var ctx2d;
var imageData;
var float32array;
var int32array;
var OES_vertex_array_object;
var vertexArrayObject;

var secondCanvas;
var secondGL;

function init()
{
    wtu = WebGLTestUtils;
    canvas = document.getElementById("canvas");
    gl = wtu.create3DContext(canvas);
    secondCanvas = document.getElementById("canvas2");
    secondGL = wtu.create3DContext(secondCanvas);
    shouldGenerateGLError = wtu.shouldGenerateGLError;

    description("Tests behavior under a lost context");

    // call testValidContext() before checking for the extension, because this is where we check
    // for the isContextLost() method, which we want to do regardless of the extension's presence.
    testValidContext();

    extensionName = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_lose_context");
    if (!extensionName) {
        debug("Could not find WEBGL_lose_context extension");
        finishTest();
        return false;
    }
    extension = gl.getExtension(extensionName);

    // need an extension that exposes new API methods.
    OES_vertex_array_object = wtu.getExtensionWithKnownPrefixes(gl, "OES_vertex_array_object");

    canvas.addEventListener("webglcontextlost", testLostContext, false);

    // We need to initialize |uniformLocation| before losing context.
    // Otherwise gl.getUniform() when context is lost will throw.
    uniformLocation = gl.getUniformLocation(program, "tex");
    loseContext();
}

function loseContext()
{
    debug("");
    debug("Lose context");

    // Note: this will cause the context to be lost, but the
    // webglcontextlost event listener to be queued.
    extension.loseContext();
    debug("");
}

function testValidContext()
{
    debug("Test valid context");

    shouldBeFalse("gl.isContextLost()");

    arrayBuffer = new ArrayBuffer(4);
    arrayBufferView = new Int8Array(arrayBuffer);

    // Generate resources for testing.
    buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    program = wtu.setupSimpleTextureProgram(gl);
    renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
    shader = gl.createShader(gl.VERTEX_SHADER);
    texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    shouldBe("gl.getError()", "gl.NO_ERROR");

    // Test is queries that will later be false
    shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enable(gl.BLEND)");
    shouldBeTrue("gl.isBuffer(buffer)");
    shouldBeTrue("gl.isEnabled(gl.BLEND)");
    shouldBeTrue("gl.isFramebuffer(framebuffer)");
    shouldBeTrue("gl.isProgram(program)");
    shouldBeTrue("gl.isRenderbuffer(renderbuffer)");
    shouldBeTrue("gl.isShader(shader)");
    shouldBeTrue("gl.isTexture(texture)");

    if (OES_vertex_array_object) {
      vertexArrayObject = OES_vertex_array_object.createVertexArrayOES();
      shouldBe("gl.getError()", "gl.NO_ERROR");
      shouldBeTrue("OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)");
    }
}

function testGLNOErrorFunctions(tests) {
  tests.forEach(function(test) {
      shouldGenerateGLError(gl, gl.NO_ERROR, test);
  });
}

function testFunctionsThatReturnNULL(tests) {
  tests.forEach(function(test) {
      shouldBeNull(test);
  });
}

function testUploadingLostContextToTexture() {
  debug("Testing uploading a canvas with a lost WebGL context to a texture");
  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors at beginning");
  let texture = secondGL.createTexture();
  secondGL.bindTexture(secondGL.TEXTURE_2D, texture);
  secondGL.texImage2D(secondGL.TEXTURE_2D, 0, secondGL.RGBA, secondGL.RGBA, secondGL.UNSIGNED_BYTE, canvas);
  wtu.glErrorShouldBe(secondGL, [secondGL.INVALID_OPERATION, secondGL.NO_ERROR], "Should not crash when uploading canvas with lost WebGL context to a texture");
}

function testLostContext()
{
    debug("Test lost context");

    // Functions with special return values.
    shouldBeTrue("gl.isContextLost()");
    shouldBe("gl.getError()", "gl.CONTEXT_LOST_WEBGL");
    shouldBe("gl.getError()", "gl.NO_ERROR");
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_UNSUPPORTED");
    shouldBe("gl.getAttribLocation(program, 'u_modelViewProjMatrix')", "-1");
    shouldBe("gl.getVertexAttribOffset(0, gl.VERTEX_ATTRIB_ARRAY_POINTER)", "0");

    // Attempt resize of lost context should succeed, still lost.
    gl.canvas.width = gl.canvas.width; // Try to hit any same-size resize path.
    shouldBeTrue("gl.isContextLost()");

    const newSize = gl.canvas.width + 1;
    gl.canvas.width = newSize;
    shouldBe("gl.canvas.width", newSize);
    shouldBeTrue("gl.isContextLost()");

    // Test the extension itself.
    shouldGenerateGLError(gl, gl.INVALID_OPERATION, "extension.loseContext()");

    image = document.createElement("img");
    video = document.createElement("video");
    canvas2d = document.createElement("canvas");
    ctx2d = canvas2d.getContext("2d");
    imageData = ctx2d.createImageData(1, 1);
    float32array = new Float32Array(1);
    int32array = new Int32Array(1);

    // Functions returning void should return immediately.
    // This is untestable, but we can at least be sure they cause no errors
    // and the codepaths are exercised.
    var voidTests = [
        "gl.activeTexture(gl.TEXTURE0)",
        "gl.attachShader(program, shader)",
        "gl.bindBuffer(gl.ARRAY_BUFFER, buffer)",
        "gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)",
        "gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer)",
        "gl.bindTexture(gl.TEXTURE_2D, texture)",
        "gl.blendColor(1.0, 1.0, 1.0, 1.0)",
        "gl.blendEquation(gl.FUNC_ADD)",
        "gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)",
        "gl.blendFunc(gl.ONE, gl.ONE)",
        "gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)",
        "gl.bufferData(gl.ARRAY_BUFFER, 0, gl.STATIC_DRAW)",
        "gl.bufferData(gl.ARRAY_BUFFER, arrayBufferView, gl.STATIC_DRAW)",
        "gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW)",
        "gl.bufferSubData(gl.ARRAY_BUFFRE, 0, arrayBufferView)",
        "gl.bufferSubData(gl.ARRAY_BUFFRE, 0, arrayBuffer)",
        "gl.clear(gl.COLOR_BUFFER_BIT)",
        "gl.clearColor(1, 1, 1, 1)",
        "gl.clearDepth(1)",
        "gl.clearStencil(0)",
        "gl.colorMask(1, 1, 1, 1)",
        "gl.compileShader(shader)",
        "gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 0, 0, 0)",
        "gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0)",
        "gl.cullFace(gl.FRONT)",
        "gl.deleteBuffer(buffer)",
        "gl.deleteFramebuffer(framebuffer)",
        "gl.deleteProgram(program)",
        "gl.deleteRenderbuffer(renderbuffer)",
        "gl.deleteShader(shader)",
        "gl.deleteTexture(texture)",
        "gl.depthFunc(gl.NEVER)",
        "gl.depthMask(0)",
        "gl.depthRange(0, 1)",
        "gl.detachShader(program, shader)",
        "gl.disable(gl.BLEND)",
        "gl.disableVertexAttribArray(0)",
        "gl.drawArrays(gl.POINTS, 0, 0)",
        "gl.drawElements(gl.POINTS, 0, gl.UNSIGNED_SHORT, 0)",
        "gl.enable(gl.BLEND)",
        "gl.enableVertexAttribArray(0)",
        "gl.finish()",
        "gl.flush()",
        "gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer)",
        "gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)",
        "gl.frontFace(gl.CW)",
        "gl.generateMipmap(gl.TEXTURE_2D)",
        "gl.hint(gl.GENERATE_MIPMAP_HINT, gl.FASTEST)",
        "gl.lineWidth(0)",
        "gl.linkProgram(program)",
        "gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0)",
        "gl.polygonOffset(0, 0)",
        "gl.readPixels(0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
        "gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 0, 0)",
        "gl.sampleCoverage(0, 0)",
        "gl.scissor(0, 0, 0, 0)",
        "gl.shaderSource(shader, '')",
        "gl.stencilFunc(gl.NEVER, 0, 0)",
        "gl.stencilFuncSeparate(gl.FRONT, gl.NEVER, 0, 0)",
        "gl.stencilMask(0)",
        "gl.stencilMaskSeparate(gl.FRONT, 0)",
        "gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)",
        "gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.KEEP)",
        "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
        "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageData)",
        "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)",
        "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas2d)",
        "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video)",
        "gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)",
        "gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)",
        "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
        "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, imageData)",
        "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image)",
        "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, canvas2d)",
        "gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video)",
        "gl.uniform1f(uniformLocation, 0)",
        "gl.uniform1fv(uniformLocation, float32array)",
        "gl.uniform1fv(uniformLocation, [0])",
        "gl.uniform1i(uniformLocation, 0)",
        "gl.uniform1iv(uniformLocation, int32array)",
        "gl.uniform1iv(uniformLocation, [0])",
        "gl.uniform2f(uniformLocation, 0, 0)",
        "gl.uniform2fv(uniformLocation, float32array)",
        "gl.uniform2fv(uniformLocation, [0, 0])",
        "gl.uniform2i(uniformLocation, 0, 0)",
        "gl.uniform2iv(uniformLocation, int32array)",
        "gl.uniform2iv(uniformLocation, [0, 0])",
        "gl.uniform3f(uniformLocation, 0, 0, 0)",
        "gl.uniform3fv(uniformLocation, float32array)",
        "gl.uniform3fv(uniformLocation, [0, 0, 0])",
        "gl.uniform3i(uniformLocation, 0, 0, 0)",
        "gl.uniform3iv(uniformLocation, int32array)",
        "gl.uniform3iv(uniformLocation, [0, 0, 0])",
        "gl.uniform4f(uniformLocation, 0, 0, 0, 0)",
        "gl.uniform4fv(uniformLocation, float32array)",
        "gl.uniform4fv(uniformLocation, [0, 0, 0, 0])",
        "gl.uniform4i(uniformLocation, 0, 0, 0, 0)",
        "gl.uniform4iv(uniformLocation, int32array)",
        "gl.uniform4iv(uniformLocation, [0, 0, 0, 0])",
        "gl.uniformMatrix2fv(uniformLocation, false, float32array)",
        "gl.uniformMatrix2fv(uniformLocation, false, [0, 0, 0, 0])",
        "gl.uniformMatrix3fv(uniformLocation, false, float32array)",
        "gl.uniformMatrix3fv(uniformLocation, false, [0, 0, 0, 0, 0, 0, 0, 0, 0])",
        "gl.uniformMatrix4fv(uniformLocation, false, float32array)",
        "gl.uniformMatrix4fv(uniformLocation, false, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])",
        "gl.useProgram(program)",
        "gl.validateProgram(program)",
        "gl.vertexAttrib1f(0, 0)",
        "gl.vertexAttrib1fv(0, float32array)",
        "gl.vertexAttrib1fv(0, [0])",
        "gl.vertexAttrib2f(0, 0, 0)",
        "gl.vertexAttrib2fv(0, float32array)",
        "gl.vertexAttrib2fv(0, [0, 0])",
        "gl.vertexAttrib3f(0, 0, 0, 0)",
        "gl.vertexAttrib3fv(0, float32array)",
        "gl.vertexAttrib3fv(0, [0, 0, 0])",
        "gl.vertexAttrib4f(0, 0, 0, 0, 0)",
        "gl.vertexAttrib4fv(0, float32array)",
        "gl.vertexAttrib4fv(0, [0, 0, 0, 0])",
        "gl.vertexAttribPointer(0, 0, gl.FLOAT, false, 0, 0)",
        "gl.viewport(0, 0, 0, 0)",
    ];
    testGLNOErrorFunctions(voidTests);

    // Functions return nullable values should all return null.
    var nullTests = [
        "gl.createBuffer()",
        "gl.createFramebuffer()",
        "gl.createProgram()",
        "gl.createRenderbuffer()",
        "gl.createShader(gl.GL_VERTEX_SHADER)",
        "gl.createTexture()",
        "gl.getActiveAttrib(program, 0)",
        "gl.getActiveUniform(program, 0)",
        "gl.getAttachedShaders(program)",
        "gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE)",
        "gl.getContextAttributes()",
        "gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)",
        "gl.getParameter(gl.CURRENT_PROGRAM)",
        "gl.getProgramInfoLog(program)",
        "gl.getProgramParameter(program, gl.LINK_STATUS)",
        "gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)",
        "gl.getShaderInfoLog(shader)",
        "gl.getShaderParameter(shader, gl.SHADER_TYPE)",
        "gl.getShaderSource(shader)",
        "gl.getTexParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S)",
        "gl.getUniform(program, uniformLocation)",
        "gl.getUniformLocation(program, 'vPosition')",
        "gl.getVertexAttrib(0, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)",
        "gl.getSupportedExtensions()",
        "gl.getExtension('" + extensionName + "')",
    ];
    testFunctionsThatReturnNULL(nullTests);

    // "Is" queries should all return false.
    shouldBeFalse("gl.isBuffer(buffer)");
    shouldBeFalse("gl.isEnabled(gl.BLEND)");
    shouldBeFalse("gl.isFramebuffer(framebuffer)");
    shouldBeFalse("gl.isProgram(program)");
    shouldBeFalse("gl.isRenderbuffer(renderbuffer)");
    shouldBeFalse("gl.isShader(shader)");
    shouldBeFalse("gl.isTexture(texture)");

    shouldBe("gl.getError()", "gl.NO_ERROR");

    // test extensions
    if (OES_vertex_array_object) {
      testGLNOErrorFunctions(
        [
          "OES_vertex_array_object.bindVertexArrayOES(vertexArrayObject)",
          "OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)",
          "OES_vertex_array_object.deleteVertexArrayOES(vertexArrayObject)",
        ]);
      testFunctionsThatReturnNULL(
        [
          "OES_vertex_array_object.createVertexArrayOES()",
        ]);
    }

    testUploadingLostContextToTexture();

    debug("");

    finishTest();
}

</script>
</head>
<body onload="init()">
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas">
<canvas id="canvas2">
</body>
</html>
