the-canvas.yaml (18144B)
1 - name: 2d.canvas.host.reference 2 desc: canvas refers back to its canvas 3 code: | 4 @assert ctx.canvas === canvas; 5 6 - name: 2d.canvas.host.readonly 7 desc: Canvas objects are readonly 8 code: | 9 var canvas2 = {{ new_canvas }}; 10 var d = ctx.canvas; 11 @assert canvas2 !== d; 12 ctx.canvas = canvas2; 13 @assert ctx.canvas === d; 14 variants: &new-canvas-definition 15 - HtmlCanvas: 16 append_variants_to_name: false 17 canvas_types: ['HtmlCanvas'] 18 new_canvas: |- 19 document.createElement('canvas') 20 OffscreenCanvas: 21 append_variants_to_name: false 22 canvas_types: ['OffscreenCanvas', 'Worker'] 23 new_canvas: |- 24 new OffscreenCanvas(100, 50) 25 26 - name: 2d.canvas.host.size.attributes 27 canvas_types: ['HtmlCanvas'] 28 code: | 29 {{ set_attributes -}} 30 @assert canvas.width === {{ result_size[0] }}; 31 @assert canvas.height === {{ result_size[1] }}; 32 {% if result_size[0] != 300 %} 33 {# With "100%", Opera gets canvas.width = 100 but renders at 100% of the 34 frame width, so check the CSS display width -#} 35 @assert window.getComputedStyle(canvas, null)\- 36 .getPropertyValue("width") === "{{ result_size[0] }}px"; 37 {% endif -%} 38 39 {% set attrib_size = escaped_size or input_size %} 40 @assert canvas.getAttribute('width') === '{{ attrib_size }}'; 41 @assert canvas.getAttribute('height') === '{{ attrib_size }}'; 42 expected: | 43 {% if result_size[0] != 0 -%} 44 size {{ result_size[0] }} {{ result_size[1] }} 45 {% endif %} 46 variants: 47 - parse: 48 desc: Parsing of non-negative integers 49 size: ['{{ input_size }}', '{{ input_size }}'] 50 setAttribute: 51 desc: Parsing of non-negative integers in setAttribute 52 size: [ 50, 50 ] 53 set_attributes: | 54 canvas.setAttribute('width', '{{ escaped_size or input_size }}'); 55 canvas.setAttribute('height', '{{ escaped_size or input_size }}'); 56 - zero: 57 input_size: '0' 58 result_size: [0, 0] 59 empty: 60 input_size: '' 61 result_size: [300, 150] 62 onlyspace: 63 input_size: ' ' 64 result_size: [300, 150] 65 space: 66 input_size: ' 100' 67 result_size: [100, 100] 68 whitespace: 69 input_size: "
\n\t\x0c100" 70 escaped_size: "\\r\\n\\t\\x0c100" 71 result_size: [100, 100] 72 plus: 73 input_size: '+100' 74 result_size: [100, 100] 75 minus: 76 input_size: '-100' 77 result_size: [300, 150] 78 octal: 79 input_size: '0100' 80 result_size: [100, 100] 81 hex: 82 input_size: '0x100' 83 result_size: [0, 0] 84 exp: 85 input_size: '100e1' 86 result_size: [100, 100] 87 decimal: 88 input_size: '100.999' 89 result_size: [100, 100] 90 percent: 91 input_size: '100%' 92 result_size: [100, 100] 93 em: 94 input_size: '100em' 95 result_size: [100, 100] 96 junk: 97 input_size: '#!?' 98 result_size: [300, 150] 99 trailingjunk: 100 input_size: '100#!?' 101 result_size: [100, 100] 102 103 - name: 2d.canvas.host.size.attributes.parse 104 desc: Parsing of non-negative integers 105 canvas_types: ['OffscreenCanvas', 'Worker'] 106 code: | 107 {% if result_size == 'exception' %} 108 @assert throws TypeError canvas.width = '{{ input_size }}'; 109 {% else %} 110 canvas.width = '{{ input_size }}'; 111 canvas.height = '{{ input_size }}'; 112 @assert canvas.width === {{ result_size }}; 113 @assert canvas.height === {{ result_size }}; 114 {% endif %} 115 variants: 116 - zero: 117 input_size: '0' 118 result_size: 0 119 empty: 120 input_size: '' 121 result_size: 0 122 onlyspace: 123 input_size: ' ' 124 result_size: 0 125 space: 126 input_size: ' 100' 127 result_size: 100 128 whitespace: 129 input_size: "\t\f100" 130 result_size: 100 131 plus: 132 input_size: '+100' 133 result_size: 100 134 minus: 135 input_size: '-100' 136 result_size: 'exception' 137 octal: 138 input_size: '0100' 139 result_size: 100 140 hex: 141 input_size: '0x100' 142 result_size: 0x100 143 exp: 144 input_size: '100e1' 145 result_size: 1000.0 146 decimal: 147 input_size: '100.999' 148 result_size: 100 149 percent: 150 input_size: '100%' 151 result_size: 'exception' 152 em: 153 input_size: '100em' 154 result_size: 'exception' 155 junk: 156 input_size: '#!?' 157 result_size: 'exception' 158 trailingjunk: 159 input_size: '100#!?' 160 result_size: 'exception' 161 162 - name: 2d.canvas.host.type.delete 163 canvas_types: ['HtmlCanvas'] 164 desc: window.HTMLCanvasElement interface object is [[Configurable]] 165 code: | 166 @assert delete window.HTMLCanvasElement === true; 167 @assert window.HTMLCanvasElement === undefined; 168 169 - name: 2d.canvas.host.type.delete 170 canvas_types: ['OffscreenCanvas', 'Worker'] 171 desc: OffscreenCanvas interface object is [[Configurable]] 172 code: | 173 {% set root = 'self' if canvas_type == 'Worker' else 'window' %} 174 @assert delete {{ root }}.OffscreenCanvas === true; 175 @assert {{ root }}.OffscreenCanvas === undefined; 176 177 - name: 2d.canvas.host.type.name 178 canvas_types: ['HtmlCanvas'] 179 desc: HTMLCanvasElement type and toString 180 code: | 181 @assert Object.prototype.toString.call(canvas) === '[object HTMLCanvasElement]'; 182 183 - name: 2d.canvas.host.type.name 184 canvas_types: ['OffscreenCanvas', 'Worker'] 185 desc: OffscreenCanvas type and toString 186 code: | 187 @assert Object.prototype.toString.call(canvas) === '[object OffscreenCanvas]'; 188 189 - name: 2d.canvas.context.exists 190 desc: The 2D context is implemented 191 code: | 192 @assert canvas.getContext('2d') !== null; 193 194 - name: 2d.canvas.context.extraargs.create 195 desc: The 2D context doesn't throw with extra getContext arguments (new context) 196 code: | 197 @assert {{ new_canvas }}.getContext('2d', false, {}, [], 1, "2") !== null; 198 @assert {{ new_canvas }}.getContext('2d', 123) !== null; 199 @assert {{ new_canvas }}.getContext('2d', "test") !== null; 200 @assert {{ new_canvas }}.getContext('2d', undefined) !== null; 201 @assert {{ new_canvas }}.getContext('2d', null) !== null; 202 @assert {{ new_canvas }}.getContext('2d', Symbol.hasInstance) !== null; 203 variants: *new-canvas-definition 204 205 - name: 2d.canvas.context.invalid.args 206 desc: Calling getContext with invalid arguments. 207 canvas_types: ['HtmlCanvas'] 208 code: | 209 @assert canvas.getContext('') === null; 210 @assert canvas.getContext('2d#') === null; 211 @assert canvas.getContext('This is clearly not a valid context name.') === null; 212 @assert canvas.getContext('2d\0') === null; 213 @assert canvas.getContext('2\uFF44') === null; 214 @assert canvas.getContext('2D') === null; 215 @assert throws TypeError canvas.getContext(); 216 @assert canvas.getContext('null') === null; 217 @assert canvas.getContext('undefined') === null; 218 219 - name: 2d.canvas.context.invalid.args 220 desc: Calling getContext with invalid arguments. 221 canvas_types: ['OffscreenCanvas', 'Worker'] 222 code: | 223 @assert throws TypeError canvas.getContext(''); 224 @assert throws TypeError canvas.getContext('This is not an implemented context in any real browser'); 225 @assert throws TypeError canvas.getContext('2d#'); 226 @assert throws TypeError canvas.getContext('2d\0'); 227 @assert throws TypeError canvas.getContext('2\uFF44'); 228 @assert throws TypeError canvas.getContext('2D'); 229 @assert throws TypeError canvas.getContext(); 230 @assert throws TypeError canvas.getContext('null'); 231 @assert throws TypeError canvas.getContext('undefined'); 232 233 234 - name: 2d.canvas.context.extraargs.cache 235 desc: The 2D context doesn't throw with extra getContext arguments (cached) 236 code: | 237 @assert canvas.getContext('2d', false, {}, [], 1, '2') !== null; 238 @assert canvas.getContext('2d', 123) !== null; 239 @assert canvas.getContext('2d', 'test') !== null; 240 @assert canvas.getContext('2d', undefined) !== null; 241 @assert canvas.getContext('2d', null) !== null; 242 @assert canvas.getContext('2d', Symbol.hasInstance) !== null; 243 244 - name: 2d.canvas.context.unique 245 desc: getContext('2d') returns the same object 246 code: | 247 @assert canvas.getContext('2d') === canvas.getContext('2d'); 248 249 - name: 2d.canvas.context.shared 250 desc: getContext('2d') returns objects which share canvas state 251 code: | 252 var ctx2 = canvas.getContext('2d'); 253 ctx.fillStyle = '#f00'; 254 ctx2.fillStyle = '#0f0'; 255 ctx.fillRect(0, 0, 100, 50); 256 @assert pixel 50,25 == 0,255,0,255; 257 expected: green 258 259 - name: 2d.canvas.host.initial.color 260 desc: Initial state is transparent black 261 code: | 262 @assert pixel 20,20 == 0,0,0,0; 263 264 - name: 2d.canvas.host.initial.reset.different 265 desc: Changing size resets canvas to transparent black 266 code: | 267 ctx.fillStyle = '#f00'; 268 ctx.fillRect(0, 0, 50, 50); 269 @assert pixel 20,20 == 255,0,0,255; 270 canvas.width = 50; 271 @assert pixel 20,20 == 0,0,0,0; 272 273 - name: 2d.canvas.host.initial.reset.same 274 desc: Setting size (not changing the value) resets canvas to transparent black 275 code: | 276 canvas.width = 100; 277 ctx.fillStyle = '#f00'; 278 ctx.fillRect(0, 0, 50, 50); 279 @assert pixel 20,20 == 255,0,0,255; 280 canvas.width = 100; 281 @assert pixel 20,20 == 0,0,0,0; 282 283 284 - name: 2d.canvas.host.initial.reset.path 285 desc: Resetting the canvas state resets the current path 286 code: | 287 canvas.width = 100; 288 ctx.rect(0, 0, 100, 50); 289 canvas.width = 100; 290 ctx.fillStyle = '#f00'; 291 ctx.fill(); 292 @assert pixel 20,20 == 0,0,0,0; 293 294 - name: 2d.canvas.host.initial.reset.clip 295 desc: Resetting the canvas state resets the current clip region 296 code: | 297 canvas.width = 100; 298 ctx.rect(0, 0, 1, 1); 299 ctx.clip(); 300 canvas.width = 100; 301 ctx.fillStyle = '#0f0'; 302 ctx.fillRect(0, 0, 100, 50); 303 @assert pixel 20,20 == 0,255,0,255; 304 305 - name: 2d.canvas.host.initial.reset.transform 306 desc: Resetting the canvas state resets the current transformation matrix 307 code: | 308 canvas.width = 100; 309 ctx.scale(0.1, 0.1); 310 canvas.width = 100; 311 ctx.fillStyle = '#0f0'; 312 ctx.fillRect(0, 0, 100, 50); 313 @assert pixel 20,20 == 0,255,0,255; 314 315 - name: 2d.canvas.host.initial.reset.gradient 316 desc: Resetting the canvas state does not invalidate any existing gradients 317 code: | 318 canvas.width = 50; 319 var g = ctx.createLinearGradient(0, 0, 100, 0); 320 g.addColorStop(0, '#0f0'); 321 g.addColorStop(1, '#0f0'); 322 canvas.width = 100; 323 ctx.fillStyle = '#f00'; 324 ctx.fillRect(0, 0, 100, 50); 325 ctx.fillStyle = g; 326 ctx.fillRect(0, 0, 100, 50); 327 @assert pixel 50,25 == 0,255,0,255; 328 329 - name: 2d.canvas.host.initial.reset.pattern 330 desc: Resetting the canvas state does not invalidate any existing patterns 331 code: | 332 canvas.width = 30; 333 ctx.fillStyle = '#0f0'; 334 ctx.fillRect(0, 0, 30, 50); 335 var p = ctx.createPattern(canvas, 'repeat-x'); 336 canvas.width = 100; 337 ctx.fillStyle = '#f00'; 338 ctx.fillRect(0, 0, 100, 50); 339 ctx.fillStyle = p; 340 ctx.fillRect(0, 0, 100, 50); 341 @assert pixel 50,25 == 0,255,0,255; 342 343 - name: 2d.canvas.host.size.attributes.idl.set.zero 344 desc: Setting width/height IDL attributes to 0 345 code: | 346 canvas.width = 0; 347 canvas.height = 0; 348 @assert canvas.width === 0; 349 @assert canvas.height === 0; 350 351 - name: 2d.canvas.host.size.attributes.idl 352 desc: Getting/setting width/height IDL attributes 353 code: | 354 canvas.width = '100'; 355 canvas.height = '100'; 356 @assert canvas.width === 100; 357 @assert canvas.height === 100; 358 canvas.width = 301.999; 359 canvas.height = 301.001; 360 @assert canvas.width === 301; 361 @assert canvas.height === 301; 362 canvas.width = "+1.5e2"; 363 canvas.height = "0x96"; 364 @assert canvas.width === 150; 365 @assert canvas.height === 150; 366 367 - name: 2d.canvas.host.size.invalid.attributes.idl 368 canvas_types: ['OffscreenCanvas', 'Worker'] 369 desc: Getting/setting width/height IDL attributes 370 code: | 371 @assert throws TypeError canvas.width = 200 - Math.pow(2, 32); 372 @assert throws TypeError canvas.height = 200 - Math.pow(2, 32); 373 @assert throws TypeError canvas.width = '400x'; 374 @assert throws TypeError canvas.height = 'foo'; 375 376 - name: 2d.canvas.host.size.invalid.attributes.idl 377 canvas_types: ['HtmlCanvas'] 378 desc: Getting/setting width/height IDL attributes 379 code: | 380 canvas.width = 200 - Math.pow(2, 32); 381 canvas.height = 200 - Math.pow(2, 32); 382 @assert canvas.width === 200; 383 @assert canvas.height === 200; 384 canvas.width = '400x'; 385 canvas.height = 'foo'; 386 @assert canvas.width === 0; 387 @assert canvas.height === 0; 388 389 - name: 2d.canvas.host.size.attributes.default 390 desc: Default width/height when attributes are missing 391 code: | 392 @assert canvas.width === 100; 393 @assert canvas.height === 50; 394 395 - name: 2d.canvas.host.size.attributes.removed 396 size: [120, 50] 397 canvas_types: ['HtmlCanvas'] 398 desc: Removing content attributes reverts to default size 399 code: | 400 @assert canvas.width === 120; 401 canvas.removeAttribute('width'); 402 @assert canvas.width === 300; 403 404 405 - name: 2d.canvas.host.size.attributes.reflect.setidl 406 desc: Setting IDL attributes updates IDL and content attributes 407 code: | 408 canvas.width = 120; 409 canvas.height = 60; 410 @assert canvas.width === 120; 411 @assert canvas.height === 60; 412 {% if canvas_type == 'HtmlCanvas' %} 413 @assert canvas.getAttribute('width') === '120'; 414 @assert canvas.getAttribute('height') === '60'; 415 {% endif %} 416 417 - name: 2d.canvas.host.size.attributes.reflect.setidlzero 418 desc: Setting IDL attributes to 0 updates IDL and content attributes 419 code: | 420 canvas.width = 0; 421 canvas.height = 0; 422 {% if canvas_type == 'HtmlCanvas' %} 423 _assertSame(canvas.getAttribute('width'), '0', "canvas.getAttribute('width')", "'0'"); 424 _assertSame(canvas.getAttribute('height'), '0', "canvas.getAttribute('height')", "'0'"); 425 {% endif %} 426 @assert canvas.width === 0; 427 @assert canvas.height === 0; 428 429 - name: 2d.canvas.host.size.attributes.reflect.setcontent 430 canvas_types: ['HtmlCanvas'] 431 desc: Setting content attributes updates IDL and content attributes 432 code: | 433 canvas.setAttribute('width', '120'); 434 canvas.setAttribute('height', '60'); 435 @assert canvas.getAttribute('width') === '120'; 436 @assert canvas.getAttribute('height') === '60'; 437 @assert canvas.width === 120; 438 @assert canvas.height === 60; 439 440 - name: 2d.canvas.host.size.large 441 notes: Not sure how reasonable this is, but the spec doesn't say there's an upper 442 limit on the size. 443 code: | 444 var n = 2147483647; // 2^31 - 1, which should be supported by any sensible definition of "long" 445 canvas.width = n; 446 canvas.height = n; 447 @assert canvas.width === n; 448 @assert canvas.height === n; 449 450 - name: 2d.canvas.context.prototype 451 desc: checks CanvasRenderingContext2D prototype 452 canvas_types: ['HtmlCanvas'] 453 code: | 454 @assert Object.getPrototypeOf(CanvasRenderingContext2D.prototype) === Object.prototype; 455 @assert Object.getPrototypeOf(ctx) === CanvasRenderingContext2D.prototype; 456 457 - name: 2d.canvas.context.prototype 458 desc: checks OffscreenCanvasRenderingContext2D prototype 459 canvas_types: ['OffscreenCanvas', 'Worker'] 460 code: | 461 @assert Object.getPrototypeOf(OffscreenCanvasRenderingContext2D.prototype) === Object.prototype; 462 @assert Object.getPrototypeOf(ctx) === OffscreenCanvasRenderingContext2D.prototype; 463 464 - name: 2d.canvas.host.scaled 465 desc: CSS-scaled canvases get drawn correctly 466 canvas_types: ['HtmlCanvas'] 467 size: [50, 25] 468 canvas: 'style="width: 100px; height: 50px"' 469 manual: 470 code: | 471 ctx.fillStyle = '#00f'; 472 ctx.fillRect(0, 0, 50, 25); 473 ctx.fillStyle = '#0ff'; 474 ctx.fillRect(0, 0, 25, 10); 475 expected: | 476 size 100 50 477 cr.set_source_rgb(0, 0, 1) 478 cr.rectangle(0, 0, 100, 50) 479 cr.fill() 480 cr.set_source_rgb(0, 1, 1) 481 cr.rectangle(0, 0, 50, 20) 482 cr.fill() 483 484 - name: 2d.canvas.context.type.exists 485 desc: The 2D context interface is a property of '{{ root }}' 486 notes: &bindings Defined in "Web IDL" (draft) 487 code: | 488 @assert {{ root }}.{{ context_object }}; 489 variants: &get-context-definition 490 - HtmlCanvas: 491 append_variants_to_name: false 492 canvas_types: ['HtmlCanvas'] 493 root: window 494 context_object: CanvasRenderingContext2D 495 OffscreenCanvas: 496 append_variants_to_name: false 497 canvas_types: ['OffscreenCanvas'] 498 root: window 499 context_object: OffscreenCanvasRenderingContext2D 500 Worker: 501 append_variants_to_name: false 502 canvas_types: ['Worker'] 503 root: self 504 context_object: OffscreenCanvasRenderingContext2D 505 506 - name: 2d.canvas.context.type.extend 507 desc: Interface methods can be added 508 notes: *bindings 509 code: | 510 {{ root }}.{{ context_object }}.prototype.fillRectGreen = function (x, y, w, h) 511 { 512 this.fillStyle = '#0f0'; 513 this.fillRect(x, y, w, h); 514 }; 515 ctx.fillStyle = '#f00'; 516 ctx.fillRectGreen(0, 0, 100, 50); 517 @assert pixel 50,25 == 0,255,0,255; 518 expected: green 519 variants: *get-context-definition 520 521 - name: 2d.canvas.context.type.replace 522 desc: Interface methods can be overridden 523 notes: *bindings 524 code: | 525 var fillRect = {{ root }}.{{ context_object }}.prototype.fillRect; 526 {{ root }}.{{ context_object }}.prototype.fillRect = function (x, y, w, h) 527 { 528 this.fillStyle = '#0f0'; 529 fillRect.call(this, x, y, w, h); 530 }; 531 ctx.fillStyle = '#f00'; 532 ctx.fillRect(0, 0, 100, 50); 533 @assert pixel 50,25 == 0,255,0,255; 534 expected: green 535 variants: *get-context-definition 536 537 - name: 2d.canvas.context.type.prototype 538 desc: window.CanvasRenderingContext2D.prototype are not [[Writable]] and not [[Configurable]], 539 and its methods are [[Configurable]]. 540 notes: *bindings 541 code: | 542 @assert {{ root }}.{{ context_object }}.prototype; 543 @assert {{ root }}.{{ context_object }}.prototype.fill; 544 {{ root }}.{{ context_object }}.prototype = null; 545 @assert {{ root }}.{{ context_object }}.prototype; 546 delete {{ root }}.{{ context_object }}.prototype; 547 @assert {{ root }}.{{ context_object }}.prototype; 548 {{ root }}.{{ context_object }}.prototype.fill = 1; 549 @assert {{ root }}.{{ context_object }}.prototype.fill === 1; 550 delete {{ root }}.{{ context_object }}.prototype.fill; 551 @assert {{ root }}.{{ context_object }}.prototype.fill === undefined; 552 variants: *get-context-definition