<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>WebGL in OffscreenCanvas</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<canvas id="c1" width="64" height="64"></canvas>
<canvas id="c2" width="64" height="64"></canvas>
<canvas id="c_ref" width="64" height="64"></canvas>
<script>

function testWorker(onFinished) {
  var worker = new Worker("offscreencanvas.js");

  ok(worker, "Web worker successfully created");

  worker.onmessage = function(evt) {
    var msg = evt.data || {};
    //console.log('onmessage', {evt}, msg.type, msg.result, msg.name);
    if (msg.type == "test") {
      ok(msg.result, msg.name);
    }
    if (msg.type == "imagebitmap") {
      // testing toBlob
      // Fill c_ref with green color.
      var ctx = c_ref.getContext("2d");
      ctx.rect(0, 0, 64, 64);
      ctx.fillStyle = "#00FF00";
      ctx.fill();

      var bitmapRenderer = c1.getContext("bitmaprenderer");
      bitmapRenderer.transferFromImageBitmap(msg.bitmap);

      ok(c1.toDataURL() == c_ref.toDataURL(),
         "c1.toDataURL MUST return a 64x64 green square");

      // The ownership of msg.bitmap should be transferred to canvas c1 when
      // we called transferFromImageBitmap, marking msg.bitmap as "detached".
      // Ensure that transferFromImageBitmap again should throw.
      var bitmapRenderer = c2.getContext("bitmaprenderer");

      let didThrow = false;
      try {
        bitmapRenderer.transferFromImageBitmap(msg.bitmap)
      } catch (e) {
        didThrow = true;
      }
      ok(didThrow, 'transferFromImageBitmap(detached) must throw');
      ok(c1.toDataURL() == c_ref.toDataURL(),
         "c2.toDataURL MUST NOT return a 64x64 green square");

      worker.terminate();
      onFinished();
    }
  }

  worker.postMessage({test: 'webgl_imagebitmap'});
}

function expectEq(name, expected, was, when) {
  let msg = `${name} was ${was}`;
  if (when) {
    msg = `[${when}] ` + msg;
  }
  let eq = (was == expected);
  if (!eq) {
    if (typeof(expected) == 'number') {
      eq = (Math.abs(was - expected) < 0.000001);
    }
  }
  if (!eq) {
    msg = msg + `, expected ${expected}`;
  }
  ok(eq, msg);
}
function expectMemberEq(obj, key, expected, when) {
  const was = obj[key];
  expectEq(`${obj}.${key}`, expected, was, when);
}
function expectEachMemberEq(obj, expectedByKeyMap, when) {
  for (const [key,expected] of Object.entries(expectedByKeyMap)) {
    expectMemberEq(obj, key, expected, when);
  }
}

function note(text) {
  console.log(text);
  ok(true, text);
}

function invoke(fn) { return fn(); }

invoke(async () => {
  SimpleTest.waitForExplicitFinish();

  await new Promise(go =>
    SpecialPowers.pushPrefEnv({'set': [
      ['gfx.offscreencanvas.enabled', true],
      ['webgl.force-enabled', true],
    ]}, go));

  console.log('await testWorker...');
  await new Promise(go => testWorker(go));

  // -

  const [W, H] = [5, 7];

  note('Begin canvas2d transferToImageBitmap tests...');
  {
    const oc = new OffscreenCanvas(W, H);
    const c2d = oc.getContext('2d');

    c2d.fillStyle = '#00ff00';
    c2d.fillRect(0, 0, W, H);
    {
      const idata = c2d.getImageData(0, 0, W, H);
      expectEq('getImageData.data.slice(0, 4)', '[0,255,0,255]',
               `[${idata.data.slice(0,4).join(',')}]`, 'after fillRect');
    }

    // -

    const NON_DEFAULT_STATE_2D = {
      direction: 'rtl',
      fillStyle: '#aaaaaa',
      font: '13px serif',
      fontKerning: 'none',
      globalAlpha: 0.42,
      globalCompositeOperation: 'xor',
      imageSmoothingEnabled: false,
      imageSmoothingQuality: 'high',
      lineCap: 'round',
      lineDashOffset: 4.2,
      lineJoin: 'round',
      lineWidth: 3.14,
      miterLimit: 1.0,
      shadowBlur: 1,
      shadowColor: '#bbbbbb',
      shadowOffsetX: 2,
      shadowOffsetY: 3,
      strokeStyle: '#cccccc',
      textAlign: 'right',
      textBaseline: 'middle',
    };
    Object.assign(c2d, NON_DEFAULT_STATE_2D);
    expectEachMemberEq(c2d, NON_DEFAULT_STATE_2D, 'before transferToImageBitmap');

    const beforeTtibData = c2d.getImageData(0, 0, W, H);
    const ib = oc.transferToImageBitmap();
    const afterTtibData = c2d.getImageData(0, 0, W, H);

    // Same state afterwards
    expectEachMemberEq(oc, {width: W, height: H}, 'after transferToImageBitmap');
    expectEachMemberEq(c2d, NON_DEFAULT_STATE_2D, 'after transferToImageBitmap');
    // But bitmap cleared afterwards
    let was = `[${afterTtibData.data.slice(0, 4).join(',')}]`;
    expectEq('getImageData.data.slice(0, 4)', '[0,0,0,0]', was, 'after transferToImageBitmap');
  }

  note('Begin webgl transferToImageBitmap tests...');
  {
    const oc = new OffscreenCanvas(W, H);
    const gl = oc.getContext('webgl', {preserveDrawingBuffer:true});

    gl.clearColor(0, 1, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    const p = new Uint8Array(4);
    gl.readPixels(0,0,1,1, gl.RGBA, gl.UNSIGNED_BYTE, p);
    expectEq('gl.readPixels(0,0,1,1)', '[0,255,0,255]',
             `[${p.slice(0,4).join(',')}]`, 'after gl.clear');

    // -

    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    expectEq(`ARRAY_BUFFER_BINDING`, buf,
      gl.getParameter(gl.ARRAY_BUFFER_BINDING), 'before transferToImageBitmap');

    const ib = oc.transferToImageBitmap();

    // Same state afterwards
    expectEachMemberEq(oc, {width: W, height: H}, 'after transferToImageBitmap');
    expectEq(`ARRAY_BUFFER_BINDING`, buf,
      gl.getParameter(gl.ARRAY_BUFFER_BINDING), 'after transferToImageBitmap');
    // But bitmap cleared afterwards
    gl.readPixels(0,0,1,1, gl.RGBA, gl.UNSIGNED_BYTE, p);
    expectEq('gl.readPixels(0,0,1,1)', '[0,0,0,0]',
             `[${p.slice(0,4).join(',')}]`, 'after transferToImageBitmap');
  }

  note('Tests complete.');
  SimpleTest.finish();
});

</script>
</body>
</html>
