# Copyright (c) 2010 Philip Taylor
# Released under the BSD license and W3C Test Suite License: see LICENSE.txt

- name: fallback.basic
  desc: Fallback content is inserted into the DOM
  testing:
    - fallback
  code: |
    @assert canvas.childNodes.length === 1;

- name: fallback.multiple
  desc: Fallback content with multiple elements
  testing:
    - fallback
  fallback: '<p class="fallback">FAIL</p><p class="fallback">FAIL</p>'
  code: |
    @assert canvas.childNodes.length === 2;

- name: fallback.nested
  desc: Fallback content containing another canvas (mostly testing parsers)
  testing:
    - fallback
  fallback: '<canvas><p class="fallback">FAIL (fallback content)</p></canvas><p class="fallback">FAIL (fallback content)</p>'
  code: |
    @assert canvas.childNodes.length === 2;

- name: type.name
  desc: HTMLCanvasElement type and toString
  testing:
    - canvas.type
  code: |
    @assert Object.prototype.toString.call(canvas) === '[object HTMLCanvasElement]';

- name: type.exists
  desc: HTMLCanvasElement is a property of window
  notes: &bindings Defined in "Web IDL" (draft)
  testing:
    - canvas.type
  code: |
    @assert window.HTMLCanvasElement;

- name: type.delete
  desc: window.HTMLCanvasElement interface object is [[Configurable]]
  notes: *bindings
  testing:
    - canvas.type
  code: |
    @assert delete window.HTMLCanvasElement === true;
    @assert window.HTMLCanvasElement === undefined;

- name: type.prototype
  desc: window.HTMLCanvasElement has prototype, which is { ReadOnly, DontDelete }. prototype has getContext, which is not
  notes: *bindings
  testing:
    - canvas.type
  code: |
    @assert window.HTMLCanvasElement.prototype;
    @assert window.HTMLCanvasElement.prototype.getContext;
    window.HTMLCanvasElement.prototype = null;
    @assert window.HTMLCanvasElement.prototype;
    delete window.HTMLCanvasElement.prototype;
    @assert window.HTMLCanvasElement.prototype;
    window.HTMLCanvasElement.prototype.getContext = 1;
    @assert window.HTMLCanvasElement.prototype.getContext === 1;
    delete window.HTMLCanvasElement.prototype.getContext;
    @assert window.HTMLCanvasElement.prototype.getContext === undefined;

- name: type.replace
  desc: HTMLCanvasElement methods can be replaced, and the replacement methods used by canvases
  notes: *bindings
  testing:
    - canvas.type
  code: |
    window.HTMLCanvasElement.prototype.getContext = function (name) { return 0; };
    @assert canvas.getContext('2d') === 0;

- name: type.extend
  desc: HTMLCanvasElement methods can be added, and the new methods used by canvases
  notes: *bindings
  testing:
    - canvas.type
  code: |
    window.HTMLCanvasElement.prototype.getZero = function () { return 0; };
    @assert canvas.getZero() === 0;


- name: size.attributes.idl.set.zero
  desc: Setting width/height IDL attributes to 0
  testing:
    - size.width
    - size.height
  code: |
    canvas.width = 0;
    canvas.height = 0;
    @assert canvas.width === 0;
    @assert canvas.height === 0;
#  expected: size 0 0 # can't generate zero-sized PNG

- name: size.attributes.idl
  desc: Getting/setting width/height IDL attributes
  testing:
    - size.width
    - size.height
  webidl:
    - es-unsigned-long
  code: |
    canvas.width = "100";
    canvas.height = "100";
    @assert canvas.width === 100;
    @assert canvas.height === 100;

    canvas.width = "+1.5e2";
    canvas.height = "0x96";
    @assert canvas.width === 150;
    @assert canvas.height === 150;

    canvas.width = 200 - Math.pow(2, 32);
    canvas.height = 200 - Math.pow(2, 32);
    @assert canvas.width === 200;
    @assert canvas.height === 200;

    canvas.width = 301.999;
    canvas.height = 301.001;
    @assert canvas.width === 301;
    @assert canvas.height === 301;

    canvas.width = "400x";
    canvas.height = "foo";
    @assert canvas.width === 0;
    @assert canvas.height === 0;

- name: size.attributes.default
  desc: Default width/height when attributes are missing
  testing:
    - size.default
    - size.missing
  canvas: ""
  code: |
    @assert canvas.width === 300;
    @assert canvas.height === 150;
    @assert !canvas.hasAttribute('width');
    @assert !canvas.hasAttribute('height');
  expected: size 300 150

- name: size.attributes.reflect.setidl
  desc: Setting IDL attributes updates IDL and content attributes
  testing:
    - size.reflect
  code: |
    canvas.width = 120;
    canvas.height = 60;
    @assert canvas.getAttribute('width') === '120';
    @assert canvas.getAttribute('height') === '60';
    @assert canvas.width === 120;
    @assert canvas.height === 60;
  expected: size 120 60

- name: size.attributes.reflect.setidlzero
  desc: Setting IDL attributes to 0 updates IDL and content attributes
  testing:
    - size.reflect
  code: |
    canvas.width = 0;
    canvas.height = 0;
    @assert canvas.getAttribute('width') === '0';
    @assert canvas.getAttribute('height') === '0';
    @assert canvas.width === 0;
    @assert canvas.height === 0;
#  expected: size 0 0 # can't generate zero-sized PNG

- name: size.attributes.reflect.setcontent
  desc: Setting content attributes updates IDL and content attributes
  testing:
    - size.reflect
  code: |
    canvas.setAttribute('width', '120');
    canvas.setAttribute('height', '60');
    @assert canvas.getAttribute('width') === '120';
    @assert canvas.getAttribute('height') === '60';
    @assert canvas.width === 120;
    @assert canvas.height === 60;
  expected: size 120 60

- name: size.attributes.removed
  desc: Removing content attributes reverts to default size
  testing:
    - size.missing
  canvas: width="120" height="60"
  code: |
    @assert canvas.width === 120;
    canvas.removeAttribute('width');
    @assert canvas.width === 300;
  expected: size 300 60

- meta: |
    cases = [
        ("zero", "0", 0),
        ("empty", "", None),
        ("onlyspace", "  ", None),
        ("space", "  100", 100),
        ("whitespace", "\n\t\f100", 100),
        ("plus", "+100", 100),
        ("minus", "-100", None),
        ("octal", "0100", 100),
        ("hex", "0x100", 0),
        ("exp", "100e1", 100),
        ("decimal", "100.999", 100),
        ("percent", "100%", 100),
        ("em", "100em", 100),
        ("junk", "#!?", None),
        ("trailingjunk", "100#!?", 100),
    ]
    def gen(name, string, exp, code):
        testing = ["size.nonnegativeinteger"]
        if exp is None:
            testing.append("size.error")
            code += "@assert canvas.width === 300;\n@assert canvas.height === 150;\n"
            expected = "size 300 150"
        else:
            code += "@assert canvas.width === %s;\n@assert canvas.height === %s;\n" % (exp, exp)
            expected = "size %s %s" % (exp, exp)

            # With "100%", Opera gets canvas.width = 100 but renders at 100% of the frame width,
            # so check the CSS display width
            code += '@assert window.getComputedStyle(canvas, null).getPropertyValue("width") === "%spx";\n' % (exp, )

        code += "@assert canvas.getAttribute('width') === %r;\n" % string
        code += "@assert canvas.getAttribute('height') === %r;\n" % string

        if exp == 0:
            expected = None # can't generate zero-sized PNGs for the expected image

        return code, testing, expected

    for name, string, exp in cases:
        code = ""
        code, testing, expected = gen(name, string, exp, code)
        tests.append( {
            "name": "size.attributes.parse.%s" % name,
            "desc": "Parsing of non-negative integers",
            "testing": testing,
            "canvas": 'width="%s" height="%s"' % (string, string),
            "code": code,
            "expected": expected
        } )

    for name, string, exp in cases:
        code = "canvas.setAttribute('width', %r);\ncanvas.setAttribute('height', %r);\n" % (string, string)
        code, testing, expected = gen(name, string, exp, code)
        tests.append( {
            "name": "size.attributes.setAttribute.%s" % name,
            "desc": "Parsing of non-negative integers in setAttribute",
            "testing": testing,
            "canvas": 'width="50" height="50"',
            "code": code,
            "expected": expected
        } )

- name: size.attributes.style
  desc: Canvas size is independent of CSS resizing
  testing:
    - size.css
  canvas: 'width="50" height="30" style="width: 100px; height: 50px"'
  code: |
    @assert canvas.width === 50;
    @assert canvas.height === 30;
  expected: size 100 50

- name: size.large
  DISABLED: |
    "User agents may impose implementation-specific limits on otherwise unconstrained
    inputs, e.g. to prevent denial of service attacks, to guard against running out of memory,
    or to work around platform-specific limitations."
  testing:
    - size.width
    - size.height
  notes: Not sure how reasonable this is, but the spec doesn't say there's an upper limit on the size.
  code: |
    var n = 2147483647; // 2^31 - 1, which should be supported by any sensible definition of "long"
    canvas.width = n;
    canvas.height = n;
    @assert canvas.width === n;
    @assert canvas.height === n;
#  expected: size 2147483647 2147483647 # not a good idea to generate the expected image in this case...

- name: initial.colour
  desc: Initial state is transparent black
  testing:
    - initial.colour
  notes: |
    Output should be transparent black (not transparent anything-else), but manual
    verification can only confirm that it's transparent - it's not possible to make
    the actual blackness visible.
  code: |
    @assert pixel 20,20 == 0,0,0,0;
  expected: size 100 50 # transparent

- name: initial.reset.different
  desc: Changing size resets canvas to transparent black
  testing:
    - initial.reset
  code: |
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    @assert pixel 20,20 == 255,0,0,255;
    canvas.width = 50;
    @assert pixel 20,20 == 0,0,0,0;
  expected: size 50 50 # transparent

- name: initial.reset.same
  desc: Setting size (not changing the value) resets canvas to transparent black
  testing:
    - initial.reset
  code: |
    canvas.width = 100;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 50, 50);
    @assert pixel 20,20 == 255,0,0,255;
    canvas.width = 100;
    @assert pixel 20,20 == 0,0,0,0;
  expected: size 100 50 # transparent

- name: initial.reset.path
  desc: Resetting the canvas state resets the current path
  testing:
    - initial.reset
  code: |
    canvas.width = 100;
    ctx.rect(0, 0, 100, 50);
    canvas.width = 100;
    ctx.fillStyle = '#f00';
    ctx.fill();
    @assert pixel 20,20 == 0,0,0,0;
  expected: size 100 50 # transparent

- name: initial.reset.clip
  desc: Resetting the canvas state resets the current clip region
  testing:
    - initial.reset
  code: |
    canvas.width = 100;
    ctx.rect(0, 0, 1, 1);
    ctx.clip();
    canvas.width = 100;
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 20,20 == 0,255,0,255;
  expected: green

- name: initial.reset.transform
  desc: Resetting the canvas state resets the current transformation matrix
  testing:
    - initial.reset
  code: |
    canvas.width = 100;
    ctx.scale(0.1, 0.1);
    canvas.width = 100;
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 20,20 == 0,255,0,255;
  expected: green

- name: initial.reset.gradient
  desc: Resetting the canvas state does not invalidate any existing gradients
  testing:
    - initial.reset
  code: |
    canvas.width = 50;
    var g = ctx.createLinearGradient(0, 0, 100, 0);
    g.addColorStop(0, '#0f0');
    g.addColorStop(1, '#0f0');
    canvas.width = 100;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

- name: initial.reset.pattern
  desc: Resetting the canvas state does not invalidate any existing patterns
  testing:
    - initial.reset
  code: |
    canvas.width = 30;
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 30, 50);
    var p = ctx.createPattern(canvas, 'repeat-x');
    canvas.width = 100;
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = p;
    ctx.fillRect(0, 0, 100, 50);
    @assert pixel 50,25 == 0,255,0,255;
  expected: green

# See tests2d.yaml for initial.reset.2dstate


- name: context.emptystring
  desc: getContext with empty string returns null
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext("") === null;

- name: context.unrecognised.badname
  desc: getContext with unrecognised context name returns null
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext('This is not an implemented context in any real browser') === null;

- name: context.unrecognised.badsuffix
  desc: Context name "2d" plus a suffix is unrecognised
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext("2d#") === null;

- name: context.unrecognised.nullsuffix
  desc: Context name "2d" plus a "\0" suffix is unrecognised
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext("2d\0") === null;

- name: context.unrecognised.unicode
  desc: Context name which kind of looks like "2d" is unrecognised
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext("2\uFF44") === null; // Fullwidth Latin Small Letter D

- name: context.casesensitive
  desc: Context name "2D" is unrecognised; matching is case sensitive
  testing:
    - context.unrecognised
  code: |
    @assert canvas.getContext('2D') === null;

- name: context.arguments.missing
  notes: *bindings
  testing:
    - canvas.getContext
  code: |
    @assert throws TypeError canvas.getContext(); @moz-todo



- name: toDataURL.default
  desc: toDataURL with no arguments returns a PNG
  testing:
    - toDataURL.noarguments
  code: |
    var data = canvas.toDataURL();
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.png
  desc: toDataURL with image/png returns a PNG
  testing:
    - toDataURL.png
    - toDataURL.witharguments
  code: |
    var data = canvas.toDataURL('image/png');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.jpg
  desc: toDataURL with image/jpg is invalid type hence returns a PNG
  testing:
    - toDataURL.jpg
    - toDataURL.witharguments
  code: |
    var data = canvas.toDataURL('image/jpg');
    @assert data =~ /^data:image\/png[;,]/;


- name: toDataURL.bogustype
  desc: toDataURL with a syntactically invalid type returns a PNG
  testing:
    - toDataURL.unrecognised
  code: |
    var data = canvas.toDataURL('bogus');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.unrecognised
  desc: toDataURL with an unhandled type returns a PNG
  testing:
    - toDataURL.unrecognised
  code: |
    var data = canvas.toDataURL('image/example');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.lowercase.ascii
  desc: toDataURL type is case-insensitive
  testing:
    - toDataURL.lowercase
  code: |
    var data = canvas.toDataURL('ImAgE/PnG');
    @assert data =~ /^data:image\/png[;,]/;

    // If JPEG is supported at all, it must be supported case-insensitively
    data = canvas.toDataURL('image/jpeg');
    if (data.match(/^data:image\/jpeg[;,]/)) {
        data = canvas.toDataURL('ImAgE/JpEg');
        @assert data =~ /^data:image\/jpeg[;,]/;
    }

- name: toDataURL.lowercase.unicode
  desc: toDataURL type is ASCII-case-insensitive
  testing:
    - toDataURL.lowercase
  code: |
    // Use LATIN CAPITAL LETTER I WITH DOT ABOVE (Unicode lowercase is "i")
    var data = canvas.toDataURL('\u0130mage/png');
    @assert data =~ /^data:image\/png[;,]/;

    var data = canvas.toDataURL('\u0130mage/jpeg');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.arguments.1
  desc: toDataURL ignores extra arguments
  testing:
    - toDataURL.arguments
  code: |
    var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.arguments.2
  desc: toDataURL ignores extra arguments
  testing:
    - toDataURL.arguments
  code: |
    var data = canvas.toDataURL('image/png', 'another argument that should not raise an exception', 'and another');
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.arguments.3
  desc: toDataURL ignores extra arguments
  testing:
    - toDataURL.arguments
  code: |
    // More arguments that should not raise exceptions
    var data = canvas.toDataURL('image/png', null, null, null);
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.nocontext
  desc: toDataURL works before any context has been got
  testing:
    - toDataURL.noarguments
  code: |
    var no_context_data = canvas.toDataURL();
    var ctx = canvas.getContext('2d');
    ctx.rect(0, 0, 100, 50);
    ctx.fillStyle = "rgba(0, 0, 0, 0)";
    ctx.fill();
    var data = canvas.toDataURL();
    assert_equals(no_context_data, data);

- name: toDataURL.zerosize
  desc: toDataURL on zero-size canvas returns 'data:,'
  testing:
    - toDataURL.zerosize
  canvas: width="0" height="0"
  code: |
    var data = canvas.toDataURL();
    @assert data === 'data:,';

- name: toDataURL.zerowidth
  desc: toDataURL on zero-size canvas returns 'data:,'
  testing:
    - toDataURL.zerosize
  canvas: width="0"
  code: |
    var data = canvas.toDataURL();
    @assert data === 'data:,';

- name: toDataURL.zeroheight
  desc: toDataURL on zero-size canvas returns 'data:,'
  testing:
    - toDataURL.zerosize
  canvas: height="0"
  code: |
    var data = canvas.toDataURL();
    @assert data === 'data:,';

- name: toDataURL.large1
  DISABLED: just testing implementation limits, and tends to crash
  canvas: width="30000" height="1"
  code: |
    var data = canvas.toDataURL();
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.large2
  DISABLED: just testing implementation limits, and tends to crash
  canvas: width="32767" height="1"
  code: |
    var data = canvas.toDataURL();
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.large3
  DISABLED: just testing implementation limits, and tends to crash
  canvas: width="32768" height="1"
  code: |
    var data = canvas.toDataURL();
    @assert data =~ /^data:image\/png[;,]/;

- name: toDataURL.png.primarycolours
  desc: toDataURL with PNG handles simple colours correctly
  testing:
    - toDataURL.png
  code: |
    ctx.fillStyle = '#ff0';
    ctx.fillRect(0, 0, 25, 40);
    ctx.fillStyle = '#0ff';
    ctx.fillRect(25, 0, 50, 40);
    ctx.fillStyle = '#00f';
    ctx.fillRect(75, 0, 25, 40);
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 40, 100, 10);
    var data = canvas.toDataURL();
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    var img = new Image();
    deferTest();
    img.onload = t.step_func_done(function ()
    {
        ctx.drawImage(img, 0, 0);
        @assert pixel 12,20 == 255,255,0,255;
        @assert pixel 50,20 == 0,255,255,255;
        @assert pixel 87,20 == 0,0,255,255;
        @assert pixel 50,45 == 255,255,255,255;
    });
    img.src = data;
  expected: |
    size 100 50
    cr.set_source_rgb(1, 1, 0)
    cr.rectangle(0, 0, 25, 40)
    cr.fill()
    cr.set_source_rgb(0, 1, 1)
    cr.rectangle(25, 0, 50, 40)
    cr.fill()
    cr.set_source_rgb(0, 0, 1)
    cr.rectangle(75, 0, 25, 40)
    cr.fill()
    cr.set_source_rgb(1, 1, 1)
    cr.rectangle(0, 40, 100, 10)
    cr.fill()

- name: toDataURL.png.complexcolours
  desc: toDataURL with PNG handles non-primary and non-solid colours correctly
  testing:
    - toDataURL.png
  code: |
    // (These values are chosen to survive relatively alright through being premultiplied)
    ctx.fillStyle = 'rgba(1, 3, 254, 1)';
    ctx.fillRect(0, 0, 25, 25);
    ctx.fillStyle = 'rgba(8, 252, 248, 0.75)';
    ctx.fillRect(25, 0, 25, 25);
    ctx.fillStyle = 'rgba(6, 10, 250, 0.502)';
    ctx.fillRect(50, 0, 25, 25);
    ctx.fillStyle = 'rgba(12, 16, 244, 0.25)';
    ctx.fillRect(75, 0, 25, 25);
    var img = new Image();
    deferTest();
    img.onload = t.step_func_done(function ()
    {
        ctx.drawImage(img, 0, 25);
        // (The alpha values do not really survive float->int conversion, so just
        // do approximate comparisons)
        @assert pixel 12,40 == 1,3,254,255;
        @assert pixel 37,40 ==~ 8,252,248,191 +/- 2;
        @assert pixel 62,40 ==~ 6,10,250,127 +/- 4;
        @assert pixel 87,40 ==~ 12,16,244,63 +/- 8;
    });
    img.src = canvas.toDataURL();
  expected: |
    size 100 50
    cr.set_source_rgba(1/255., 3/255., 254/255., 1)
    cr.rectangle(0, 0, 25, 50)
    cr.fill()
    cr.set_source_rgba(8/255., 252/255., 248/255., 191/255.)
    cr.rectangle(25, 0, 25, 50)
    cr.fill()
    cr.set_source_rgba(6/255., 10/255., 250/255., 127/255.)
    cr.rectangle(50, 0, 25, 50)
    cr.fill()
    cr.set_source_rgba(12/255., 16/255., 244/255., 63/255.)
    cr.rectangle(75, 0, 25, 50)
    cr.fill()

- name: toDataURL.jpeg.primarycolours
  desc: toDataURL with JPEG handles simple colours correctly
  testing:
    - toDataURL.jpeg
  code: |
    ctx.fillStyle = '#ff0';
    ctx.fillRect(0, 0, 25, 40);
    ctx.fillStyle = '#0ff';
    ctx.fillRect(25, 0, 50, 40);
    ctx.fillStyle = '#00f';
    ctx.fillRect(75, 0, 25, 40);
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 40, 100, 10);
    var data = canvas.toDataURL('image/jpeg'); // it is okay if this returns a PNG instead
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    var img = new Image();
    deferTest();
    img.onload = t.step_func_done(function ()
    {
        ctx.drawImage(img, 0, 0);
        @assert pixel 12,20 ==~ 255,255,0,255 +/- 8;
        @assert pixel 50,20 ==~ 0,255,255,255 +/- 8;
        @assert pixel 87,20 ==~ 0,0,255,255 +/- 8;
        @assert pixel 50,45 ==~ 255,255,255,255 +/- 8;
    });
    img.src = data;
  expected: |
    size 100 50
    cr.set_source_rgb(1, 1, 0)
    cr.rectangle(0, 0, 25, 40)
    cr.fill()
    cr.set_source_rgb(0, 1, 1)
    cr.rectangle(25, 0, 50, 40)
    cr.fill()
    cr.set_source_rgb(0, 0, 1)
    cr.rectangle(75, 0, 25, 40)
    cr.fill()
    cr.set_source_rgb(1, 1, 1)
    cr.rectangle(0, 40, 100, 10)
    cr.fill()

- name: toDataURL.jpeg.alpha
  desc: toDataURL with JPEG composites onto black
  testing:
    - toDataURL.jpeg
    - toDataURL.noalpha
  code: |
    ctx.fillStyle = 'rgba(128, 255, 128, 0.5)';
    ctx.fillRect(0, 0, 100, 50);
    ctx.globalCompositeOperation = 'destination-over'; // should be ignored by toDataURL
    var data = canvas.toDataURL('image/jpeg');
    ctx.globalCompositeOperation = 'source-over';
    if (!data.match(/^data:image\/jpeg[;,]/)) {
      @assert true;
    } else {
      ctx.fillStyle = '#f00';
      ctx.fillRect(0, 0, 100, 50);
      var img = new Image();
      deferTest();
      img.onload = t.step_func_done(function ()
      {
          ctx.drawImage(img, 0, 0);
          @assert pixel 50,25 ==~ 63,127,63,255 +/- 8;
      });
      img.src = data;
    }
  expected: |
    size 100 50
    cr.set_source_rgb(0.25, 0.5, 0.25)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()

- name: toDataURL.jpeg.quality.basic
  desc: toDataURL with JPEG uses the quality parameter
  testing:
    - toDataURL.jpeg.quality
  mozilla: { throws }
  code: |
    ctx.fillStyle = '#00f';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0ff';
    ctx.fillRect(0, 3, 100, 1);
    // Check for JPEG support first
    var data = canvas.toDataURL('image/jpeg');
    if (!data.match(/^data:image\/jpeg[;,]/)) {
      @assert true;
    } else {
      var data_hi = canvas.toDataURL('image/jpeg', 0.99);
      var data_lo = canvas.toDataURL('image/jpeg', 0.01);
      ctx.fillStyle = '#f00';
      ctx.fillRect(0, 0, 100, 50);
      deferTest();
      var img_hi = new Image();
      img_hi.onload = function ()
      {
          var img_lo = new Image();
          img_lo.onload = t.step_func_done(function ()
          {
              ctx.drawImage(img_hi, 0, 0, 50, 50, 0, 0, 50, 50);
              ctx.drawImage(img_lo, 0, 0, 50, 50, 50, 0, 50, 50);
              @assert data_hi.length > data_lo.length;
              @assert pixel 25,25 ==~ 0,0,255,255 +/- 8;
              @assert pixel 75,25 ==~ 0,0,255,255 +/- 32;
          });
          img_lo.src = data_lo;
      };
      img_hi.src = data_hi;
    }
  expected: |
    size 100 50
    cr.set_source_rgb(0, 0, 1)
    cr.rectangle(0, 0, 100, 50)
    cr.fill()
    cr.set_source_rgb(0, 1, 1)
    cr.rectangle(0, 3, 100, 1)
    cr.fill()

- name: toDataURL.jpeg.quality.notnumber
  desc: toDataURL with JPEG handles non-numeric quality parameters
  testing:
    - toDataURL.jpeg.nan
  code: |
    ctx.fillStyle = '#00f';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0ff';
    ctx.fillRect(0, 3, 100, 1);
    // Check for JPEG support first
    var data = canvas.toDataURL('image/jpeg');
    if (!data.match(/^data:image\/jpeg[;,]/)) {
      @assert true;
    } else {
        @assert canvas.toDataURL('image/jpeg', 'bogus') === data;
        @assert canvas.toDataURL('image/jpeg', {}) === data;
        @assert canvas.toDataURL('image/jpeg', null) === data;
        @assert canvas.toDataURL('image/jpeg', undefined) === data;
        @assert canvas.toDataURL('image/jpeg', true) === data;
        @assert canvas.toDataURL('image/jpeg', '0.01') === data;
    }

- name: toDataURL.jpeg.quality.outsiderange
  desc: toDataURL with JPEG handles out-of-range quality parameters
  testing:
    - toDataURL.jpeg.range
  code: |
    ctx.fillStyle = '#00f';
    ctx.fillRect(0, 0, 100, 50);
    ctx.fillStyle = '#0ff';
    ctx.fillRect(0, 3, 100, 1);
    // Check for JPEG support first
    var data = canvas.toDataURL('image/jpeg');
    if (!data.match(/^data:image\/jpeg[;,]/)) {
      @assert true;
    } else {
        @assert canvas.toDataURL('image/jpeg', 10) === data;
        @assert canvas.toDataURL('image/jpeg', -10) === data;
        @assert canvas.toDataURL('image/jpeg', 1.01) === data;
        @assert canvas.toDataURL('image/jpeg', -0.01) === data;

        @assert canvas.toDataURL('image/jpeg', 1).length >= canvas.toDataURL('image/jpeg', 0.9).length;
        @assert canvas.toDataURL('image/jpeg', 0).length <= canvas.toDataURL('image/jpeg', 0.1).length;
    }


# TODO: work out what security exception should be thrown
# TODO: test same-origin vs same-host

- name: security.drawImage.image
  desc: drawImage of different-origin image makes the canvas origin-unclean
  mozilla: { disabled } # relies on external resources
  testing:
    - security.drawImage.image
    - security.toDataURL
    - security.getImageData
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.drawImage.canvas
  desc: drawImage of unclean canvas makes the canvas origin-unclean
  mozilla: { disabled } # relies on external resources
  testing:
    - security.drawImage.canvas
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
    ctx.drawImage(canvas2, 0, 0);
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.pattern.create
  desc: Creating an unclean pattern does not make the canvas origin-unclean
  mozilla: { disabled } # relies on external resources
  testing:
    - security.start
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
    canvas.toDataURL();
    ctx.getImageData(0, 0, 1, 1);
    @assert true; // okay if there was no exception

- name: security.pattern.cross
  desc: Using an unclean pattern makes the target canvas origin-unclean, not the pattern canvas
  mozilla: { disabled } # relies on external resources
  testing:
    - security.start
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    var p = ctx2.createPattern(document.getElementById('yellow.png'), 'repeat');
    ctx.fillStyle = p;
    ctx.fillRect(0, 0, 100, 50);
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);
    canvas2.toDataURL();
    ctx2.getImageData(0, 0, 1, 1);

- name: security.pattern.canvas.timing
  desc: Pattern safety depends on whether the source was origin-clean, not on whether it still is clean
  notes: Disagrees with spec on "is" vs "was"
  mozilla: { disabled } # relies on external resources
  testing:
    - security.start
    - security.fillStyle.canvas
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.fillStyle = '#0f0';
    ctx2.fillRect(0, 0, 100, 50);
    var p = ctx.createPattern(canvas2, 'repeat');
    ctx2.drawImage(document.getElementById('yellow.png'), 0, 0); // make canvas2 origin-unclean
    ctx.fillStyle = p;
    ctx.fillRect(0, 0, 100, 50);
    canvas.toDataURL();
    ctx.getImageData(0, 0, 1, 1);
    @assert true; // okay if there was no exception

- name: security.pattern.image.fillStyle
  desc: Setting fillStyle to a pattern of a different-origin image makes the canvas origin-unclean
  mozilla: { disabled } # relies on external resources
  testing:
    - security.fillStyle.image
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
    ctx.fillStyle = p;
    ctx.fillStyle = 'red';
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.pattern.canvas.fillStyle
  desc: Setting fillStyle to a pattern of an unclean canvas makes the canvas origin-unclean
  mozilla: { bug: 354127, disabled } # relies on external resources
  testing:
    - security.fillStyle.canvas
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
    var p = ctx.createPattern(canvas2, 'repeat');
    ctx.fillStyle = p;
    ctx.fillStyle = 'red';
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.pattern.image.strokeStyle
  desc: Setting strokeStyle to a pattern of a different-origin image makes the canvas origin-unclean
  mozilla: { disabled } # relies on external resources
  testing:
    - security.strokeStyle.image
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var p = ctx.createPattern(document.getElementById('yellow.png'), 'repeat');
    ctx.strokeStyle = p;
    ctx.strokeStyle = 'red';
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.pattern.canvas.strokeStyle
  desc: Setting strokeStyle to a pattern of an unclean canvas makes the canvas origin-unclean
  mozilla: { bug: 354127, disabled } # relies on external resources
  testing:
    - security.strokeStyle.canvas
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    var canvas2 = document.createElement('canvas');
    canvas2.width = 100;
    canvas2.height = 50;
    var ctx2 = canvas2.getContext('2d');
    ctx2.drawImage(document.getElementById('yellow.png'), 0, 0);
    var p = ctx.createPattern(canvas2, 'repeat');
    ctx.strokeStyle = p;
    ctx.strokeStyle = 'red';
    @assert throws SECURITY_ERR canvas.toDataURL();
    @assert throws SECURITY_ERR ctx.getImageData(0, 0, 1, 1);

- name: security.dataURI
  desc: 'data: URIs do not count as different-origin, and do not taint the canvas'
  mozilla: { disabled, bug: 417836 } # can't do "todo" so just disable it
  code: |
    ctx.fillStyle = '#0f0';
    ctx.fillRect(0, 0, 100, 50);
    var data = canvas.toDataURL();
    ctx.fillStyle = '#f00';
    ctx.fillRect(0, 0, 100, 50);
    var img = new Image();
    deferTest();
    img.onload = t.step_func_done(function ()
    {
        ctx.drawImage(img, 0, 0);
        canvas.toDataURL(); // should be permitted
        @assert pixel 50,25 == 0,255,0,255;
    });
    img.src = data;
  expected: green

- name: security.reset
  desc: Resetting the canvas state resets the origin-clean flag
  mozilla: { disabled } # relies on external resources
  testing:
    - initial.reset
  scripts:
    - /common/get-host-info.sub.js
  script-variants:
    cross: data:text/javascript,addCrossOriginYellowImage()
    redirect: data:text/javascript,addCrossOriginRedirectYellowImage()
  code: |
    canvas.width = 50;
    ctx.drawImage(document.getElementById('yellow.png'), 0, 0);
    @assert throws SECURITY_ERR canvas.toDataURL();
    canvas.width = 100;
    canvas.toDataURL();
    ctx.getImageData(0, 0, 1, 1);
    @assert true; // okay if there was no exception
