Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: third_party/WebKit/LayoutTests/fast/canvas/canvas-createImageBitmap-drawImage-with-options.html

Issue 1809603004: Use ImageDecoder for createImageBitmap(HTMLImageElement, premultiplyAlpha=false) (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: halfRed-->darkRed Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2 <html>
3 <head>
4 <script src="../../resources/js-test.js"></script>
5 </head>
6 <body>
7 <script>
8
9 description("Ensure correct behavior of drawImage with ImageBitmaps along with f lipY option.");
10 window.jsTestIsAsync = true;
11
12 function jsWrapperClass(node)
13 {
14 // returns the ClassName of node
15 if (!node)
16 return "[null]";
17 var string = Object.prototype.toString.apply(node);
18
19 // string will be of the form [object ClassName]
20 return string.substr(8, string.length - 9);
21 }
22
23 function shouldBeType(expression, className)
24 {
25 shouldBe("jsWrapperClass(" + expression + ")", "'" + className + "'");
26 }
27
28 function shouldNotBeCalled() {
29 testFailed("createImageBitmap promise rejected.");
30 finishJSTest();
31 }
32
33 function shouldBeRed(x, y) {
34 d = ctx.getImageData(x, y, 1, 1).data;
35 if (d[0] == 255 && d[1] == 0 && d[2] == 0 && d[3] == 255) {
36 testPassed("This pixel is red.");
37 return;
38 }
39 testFailed("This pixel is expected to be red, but it is actually " + d);
40 }
41
42 function shouldBeGreen(x, y) {
43 d = ctx.getImageData(x, y, 1, 1).data;
44 if (d[0] == 0 && d[1] == 255 && d[2] == 0 && d[3] == 255) {
45 testPassed("This pixel is green.");
46 return;
47 }
48 testFailed("This pixel is expected to be green, but it is actually " + d);
49 }
50
51 function shouldBeBlue(x, y) {
52 d = ctx.getImageData(x, y, 1, 1).data;
53 if (d[0] == 0 && d[1] == 0 && d[2] == 255 && d[3] == 255) {
54 testPassed("This pixel is blue.");
55 return;
56 }
57 testFailed("This pixel is expected to be blue, but it is actually " + d);
58 }
59
60 function shouldBeBlack(x, y) {
61 d = ctx.getImageData(x, y, 1, 1).data;
62 if (d[0] == 0 && d[1] == 0 && d[2] == 0 && d[3] == 255) {
63 testPassed("This pixel is black.");
64 return;
65 }
66 testFailed("This pixel is expected to be black, but it is actually " + d);
67 }
68
69 function shouldBeClear(x, y) {
70 // should be transparent black pixels
71 d = ctx.getImageData(x, y, 1, 1).data;
72 if (d[0] == 0 && d[1] == 0 && d[2] == 0 && d[3] == 0) {
73 testPassed("This pixel is transparent black.");
74 return;
75 }
76 testFailed("This pixel is expected to be transparent black, but it is actual ly " + d);
77 }
78
79 function drawPattern(ctx) {
80 // Draw a four-color pattern
81 ctx.beginPath();
82 ctx.fillStyle = "rgb(255, 0, 0)";
83 ctx.fillRect(0, 0, 10, 10);
84 ctx.fillStyle = "rgb(0, 255, 0)";
85 ctx.fillRect(10, 0, 10, 10);
86 ctx.fillStyle = "rgb(0, 0, 255)";
87 ctx.fillRect(0, 10, 10, 10);
88 ctx.fillStyle = "rgb(0, 0, 0)";
89 ctx.fillRect(10, 10, 10, 10);
90 }
91
92 function clearContext(context) {
93 context.clearRect(0, 0, 50, 50);
94 }
95
96 var bitmap;
97 var image;
98 var testBitmap; // this is an ImageBitmap that is uncropped. We use this to test createImageBitmap(testBitmap)
99 var d; // image.imageData
100 var elements;
101 var imageOrientationOptions;
102 var premultiplyAlphaOptions;
103 var optionIndexArray;
104
105 var imageWidth = 20;
106 var imageHeight = 20;
107
108 // Draw to an auxillary canvas.
109 var aCanvas = document.createElement("canvas");
110 aCanvas.width = imageWidth;
111 aCanvas.height = imageHeight;
112 var aCtx = aCanvas.getContext("2d");
113 drawPattern(aCtx);
114
115 var canvas = document.createElement("canvas");
116 canvas.setAttribute("width", "50");
117 canvas.setAttribute("height", "50");
118 var ctx = canvas.getContext("2d");
119
120 image = new Image();
121 image.onload = imageLoaded;
122 image.src = aCanvas.toDataURL();
123
124 var imageLoaded = false;
125 var imageBitmapLoaded = false;
126 var blobLoaded = false;
127
128 function imageLoaded() {
129 createImageBitmap(image).then(imageBitmapLoadedCallback, shouldNotBeCalled);
130 d = aCtx.getImageData(0, 0, 20, 20);
131 imageLoaded = true;
132 loaded();
133 }
134
135 function imageBitmapLoadedCallback(imageBitmap) {
136 testBitmap = imageBitmap;
137
138 shouldBe("testBitmap.width", "imageWidth");
139 shouldBe("testBitmap.height", "imageHeight");
140
141 // width and height are readonly
142 testBitmap.width = 42;
143 testBitmap.height = 42;
144 shouldBe("testBitmap.width", "imageWidth");
145 shouldBe("testBitmap.height", "imageHeight");
146
147 imageBitmapLoaded = true;
148 loaded();
149 }
150
151 var xhr = new XMLHttpRequest();
152 xhr.open("GET", 'resources/pattern.png');
153 xhr.responseType = 'blob';
154 xhr.send();
155 xhr.onload = function() {
156 blob = xhr.response;
157 blobLoaded = true;
158 loaded();
159 }
160
161 function loaded() {
162 if (imageLoaded && imageBitmapLoaded && blobLoaded) {
163 // check all elements
164 elements = [image, aCanvas, d, testBitmap, blob];
165 imageOrientationOptions = ["none", "flipY", "invalid"];
166 premultiplyAlphaOptions = ["default", "none", "premultiply", "invalid"];
167 optionIndexArray = [[0, 0], [0, 1], [0, 2], [0, 3],
168 [1, 0], [1, 1], [1, 2], [1, 3],
169 [2, 0], [2, 1], [2, 2], [2, 3]];
170 // wait for callback to finish before each check to ensure synchronous b ehavior
171 nextCheck(0, 0);
172 }
173 }
174
175 function nextCheck(elementIndex, optionIndex) {
176 if (elementIndex == elements.length) {
177 finishJSTest();
178 return;
179 }
180 var element = elements[elementIndex];
181 var optionIndex1 = optionIndexArray[optionIndex][0];
182 var optionIndex2 = optionIndexArray[optionIndex][1];
183 imageBitmaps = {};
184 debug("Checking " + jsWrapperClass(element) + " with imageOrientation: " + i mageOrientationOptions[optionIndex1] + " with premultiplyAlphaOption: " + premul tiplyAlphaOptions[optionIndex2] + ".");
185 var p1 = createImageBitmap(element, {imageOrientation: imageOrientationOptio ns[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[optionIndex2]}).then (function (image) { imageBitmaps.noCrop = image });
186 var p2 = createImageBitmap(element, 0, 0, 10, 10, {imageOrientation: imageOr ientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[option Index2]}).then(function (image) { imageBitmaps.crop = image });
187 var p3 = createImageBitmap(element, 5, 5, 10, 10, {imageOrientation: imageOr ientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[option Index2]}).then(function (image) { imageBitmaps.cropCenter = image });
188 var p4 = createImageBitmap(element, 10, 10, 10, 10, {imageOrientation: image OrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[opti onIndex2]}).then(function (image) { imageBitmaps.cropRight = image });
189 var p5 = createImageBitmap(element, -10, -10, 60, 60, {imageOrientation: ima geOrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[op tionIndex2]}).then(function (image) { imageBitmaps.overCrop = image });
190 var p6 = createImageBitmap(element, 10, 10, 50, 50, {imageOrientation: image OrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[opti onIndex2]}).then(function (image) { imageBitmaps.overCropRight = image });
191 var p7 = createImageBitmap(element, 10, 10, -10, -10, {imageOrientation: ima geOrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[op tionIndex2]}).then(function (image) { imageBitmaps.negativeCrop = image });
192 var p8 = createImageBitmap(element, -30, -30, 30, 30, {imageOrientation: ima geOrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[op tionIndex2]}).then(function (image) { imageBitmaps.empty = image });
193 var p9 = createImageBitmap(element, 40, 30, 30, 30, {imageOrientation: image OrientationOptions[optionIndex1], premultiplyAlpha: premultiplyAlphaOptions[opti onIndex2]}).then(function (image) { imageBitmaps.emptyTwo = image });
194 Promise.all([p1, p2, p3, p4, p5, p6, p7, p8, p9]).then(function() {
195 checkNoCrop(imageBitmaps.noCrop, imageOrientationOptions[optionIndex1]);
196 checkCrop(imageBitmaps.crop, imageOrientationOptions[optionIndex1]);
197 checkCropCenter(imageBitmaps.cropCenter, imageOrientationOptions[optionI ndex1]);
198 checkCropRight(imageBitmaps.cropRight, imageOrientationOptions[optionInd ex1]);
199 checkOverCrop(imageBitmaps.overCrop, imageOrientationOptions[optionIndex 1]);
200 checkOverCropRight(imageBitmaps.overCropRight, imageOrientationOptions[o ptionIndex1]);
201 checkCrop(imageBitmaps.negativeCrop, imageOrientationOptions[optionIndex 1]);
202 checkEmpty(imageBitmaps.empty, imageOrientationOptions[optionIndex1]);
203 checkEmpty(imageBitmaps.emptyTwo, imageOrientationOptions[optionIndex1]) ;
204 if (optionIndex == optionIndexArray.length - 1)
205 nextCheck(elementIndex + 1, 0);
206 else
207 nextCheck(elementIndex, optionIndex + 1);
208 }, function() {
209 // when the options are invalid, we expect the promise to be rejected.
210 if (imageOrientationOptions[optionIndex1] == "invalid" || premultiplyAlp haOptions[optionIndex2] == "invalid") {
211 testPassed("createImageBitmap with invalid options are rejected");
212 if (optionIndex == optionIndexArray.length - 1)
213 nextCheck(elementIndex + 1, 0);
214 else
215 nextCheck(elementIndex, optionIndex + 1);
216 } else {
217 shouldNotBeCalled();
218 }
219 });
220 }
221
222 function checkNoCrop(imageBitmap, option) {
223 debug("Check no crop.");
224 bitmap = imageBitmap;
225 shouldBeType("bitmap", "ImageBitmap");
226 shouldBe("bitmap.width", "imageWidth");
227 shouldBe("bitmap.height", "imageHeight");
228
229 clearContext(ctx);
230 ctx.drawImage(imageBitmap, 0, 0);
231 if (option == "flipY" || option == "bottomLeft") {
232 shouldBeBlue(9, 9);
233 shouldBeBlack(11, 9);
234 shouldBeRed(9, 11);
235 shouldBeGreen(11, 11);
236 shouldBeGreen(19, 19);
237 shouldBeClear(1, 21);
238 shouldBeClear(21, 1);
239 shouldBeClear(21, 21);
240 } else {
241 shouldBeRed(9, 9);
242 shouldBeGreen(11, 9);
243 shouldBeBlue(9, 11);
244 shouldBeBlack(11, 11);
245 shouldBeBlack(19, 19);
246 shouldBeClear(1, 21);
247 shouldBeClear(21, 1);
248 shouldBeClear(21, 21);
249 }
250
251 /*commenting out these cases because of crbug.com/578889
252 clearContext(ctx);
253 ctx.drawImage(imageBitmap, 0, 0, 10, 10);
254 if (option == "flipY" || option == "bottomLeft") {
255 shouldBeBlue(4, 4);
256 shouldBeBlack(6, 4);
257 shouldBeRed(4, 6);
258 shouldBeGreen(6, 6);
259 shouldBeGreen(9, 9);
260 shouldBeClear(1, 11);
261 shouldBeClear(11, 1);
262 shouldBeClear(11, 11);
263 } else {
264 shouldBeRed(4, 4);
265 shouldBeGreen(6, 4);
266 shouldBeBlue(4, 6);
267 shouldBeBlack(6, 6);
268 shouldBeBlack(9, 9);
269 shouldBeClear(1, 11);
270 shouldBeClear(11, 1);
271 shouldBeClear(11, 11);
272 }
273
274 clearContext(ctx);
275 ctx.drawImage(imageBitmap, 10, 10, 10, 10);
276 if (option == "flipY" || option == "bottomLeft") {
277 shouldBeBlue(14, 14);
278 shouldBeBlack(16, 14);
279 shouldBeRed(14, 16);
280 shouldBeGreen(16, 16);
281 shouldBeGreen(19, 19);
282 shouldBeClear(11, 21);
283 shouldBeClear(21, 11);
284 shouldBeClear(21, 21);
285 } else {
286 shouldBeRed(14, 14);
287 shouldBeGreen(16, 14);
288 shouldBeBlue(14, 16);
289 shouldBeBlack(16, 16);
290 shouldBeBlack(19, 19);
291 shouldBeClear(11, 21);
292 shouldBeClear(21, 11);
293 shouldBeClear(21, 21);
294 }
295
296 clearContext(ctx);
297 ctx.drawImage(imageBitmap, 10, 10, 10, 10, 10, 10, 10, 10);
298 if (option == "flipY" || option == "bottomLeft") {
299 shouldBeClear(9, 9);
300 shouldBeGreen(11, 11);
301 shouldBeGreen(19, 19);
302 shouldBeClear(1, 21);
303 shouldBeClear(21, 1);
304 shouldBeClear(21, 21);
305 } else {
306 shouldBeClear(9, 9);
307 shouldBeBlack(11, 11);
308 shouldBeBlack(19, 19);
309 shouldBeClear(1, 21);
310 shouldBeClear(21, 1);
311 shouldBeClear(21, 21);
312 }*/
313 }
314
315 function checkCrop(imageBitmap, option) {
316 debug("Check crop.");
317 bitmap = imageBitmap;
318 shouldBeType("bitmap", "ImageBitmap");
319 shouldBe("bitmap.width", "10");
320 shouldBe("bitmap.height", "10");
321
322 clearContext(ctx);
323 ctx.drawImage(imageBitmap, 0, 0);
324 if (option == "flipY" || option == "bottomLeft") {
325 shouldBeRed(1, 1);
326 shouldBeRed(9, 9);
327 shouldBeClear(12, 12);
328 shouldBeClear(1, 12);
329 shouldBeClear(12, 1);
330 } else {
331 shouldBeRed(1, 1);
332 shouldBeRed(9, 9);
333 shouldBeClear(12, 12);
334 shouldBeClear(1, 12);
335 shouldBeClear(12, 1);
336 }
337
338 /*commenting out these cases because of crbug.com/578889
339 clearContext(ctx);
340 ctx.drawImage(imageBitmap, 0, 0, 20, 20);
341 if (option == "flipY" || option == "bottomLeft") {
342 shouldBeRed(1, 1);
343 shouldBeRed(18, 18);
344 shouldBeClear(22, 22);
345 shouldBeClear(1, 22);
346 shouldBeClear(22, 1);
347 } else {
348 shouldBeRed(1, 1);
349 shouldBeRed(18, 18);
350 shouldBeClear(22, 22);
351 shouldBeClear(1, 22);
352 shouldBeClear(22, 1);
353 }*/
354 }
355
356 function checkCropCenter(imageBitmap, option) {
357 debug("Check crop center.");
358 bitmap = imageBitmap;
359 shouldBeType("bitmap", "ImageBitmap");
360 shouldBe("bitmap.width", "10");
361 shouldBe("bitmap.height", "10");
362
363 clearContext(ctx);
364 ctx.drawImage(imageBitmap, 0, 0);
365 if (option == "flipY" || option == "bottomLeft") {
366 shouldBeBlue(4, 4);
367 shouldBeBlack(6, 4);
368 shouldBeRed(4, 6);
369 shouldBeGreen(6, 6);
370 shouldBeGreen(9, 9);
371 shouldBeClear(11, 11);
372 shouldBeClear(1, 11);
373 shouldBeClear(11, 1);
374 } else {
375 shouldBeRed(4, 4);
376 shouldBeGreen(6, 4);
377 shouldBeBlue(4, 6);
378 shouldBeBlack(6, 6);
379 shouldBeBlack(9, 9);
380 shouldBeClear(11, 11);
381 shouldBeClear(1, 11);
382 shouldBeClear(11, 1);
383 }
384
385 /*commenting out these cases because of crbug.com/578889
386 clearContext(ctx);
387 ctx.drawImage(imageBitmap, 0, 0, 20, 20);
388 if (option == "flipY" || option == "bottomLeft") {
389 shouldBeBlue(8, 8);
390 shouldBeBlack(11, 8);
391 shouldBeRed(8, 11);
392 shouldBeGreen(11, 11);
393 shouldBeGreen(18, 18);
394 shouldBeClear(22, 22);
395 shouldBeClear(1, 21);
396 shouldBeClear(21, 1);
397 } else {
398 shouldBeRed(8, 8);
399 shouldBeGreen(11, 8);
400 shouldBeBlue(8, 11);
401 shouldBeBlack(11, 11);
402 shouldBeBlack(18, 18);
403 shouldBeClear(22, 22);
404 shouldBeClear(1, 21);
405 shouldBeClear(21, 1);
406 }*/
407 }
408
409 function checkCropRight(imageBitmap, option) {
410 debug("Check crop right.");
411 bitmap = imageBitmap;
412 shouldBeType("bitmap", "ImageBitmap");
413 shouldBe("bitmap.width", "10");
414 shouldBe("bitmap.height", "10");
415
416 clearContext(ctx);
417 ctx.drawImage(imageBitmap, 0, 0);
418 if (option == "flipY" || option == "bottomLeft") {
419 shouldBeBlack(1, 1);
420 shouldBeBlack(9, 9);
421 shouldBeClear(11, 11);
422 shouldBeClear(1, 11);
423 shouldBeClear(11, 1);
424 } else {
425 shouldBeBlack(1, 1);
426 shouldBeBlack(9, 9);
427 shouldBeClear(11, 11);
428 shouldBeClear(1, 11);
429 shouldBeClear(11, 1);
430 }
431 }
432
433 function checkOverCrop(imageBitmap, option) {
434 debug("Check over crop.");
435 bitmap = imageBitmap;
436 shouldBeType("bitmap", "ImageBitmap");
437 shouldBe("bitmap.width", "60");
438 shouldBe("bitmap.height", "60");
439
440 clearContext(ctx);
441 ctx.drawImage(imageBitmap, 0, 0);
442 if (option == "flipY" || option == "bottomLeft") {
443 shouldBeClear(1, 59);
444 shouldBeClear(9, 51);
445 shouldBeBlue(11, 31);
446 shouldBeBlue(19, 39);
447 shouldBeBlack(21, 31);
448 shouldBeBlack(29, 39);
449 shouldBeRed(11, 41);
450 shouldBeRed(19, 49);
451 shouldBeGreen(21, 41);
452 shouldBeGreen(29, 49);
453 shouldBeClear(31, 59);
454 shouldBeClear(1, 29);
455 shouldBeClear(31, 31);
456 } else {
457 shouldBeClear(1, 1);
458 shouldBeClear(9, 9);
459 shouldBeRed(11, 11);
460 shouldBeRed(19, 19);
461 shouldBeGreen(21, 19);
462 shouldBeBlue(19, 21);
463 shouldBeBlack(21, 21);
464 shouldBeBlack(29, 29);
465 shouldBeClear(32, 1);
466 shouldBeClear(1, 32);
467 shouldBeClear(32, 32);
468 }
469
470 // comment out this part for now due to crbug.com/578889
471 /*clearContext(ctx);
472 ctx.drawImage(imageBitmap, 0, 0, 30, 30);
473 if (option == "flipY" || option == "bottomLeft") {
474 shouldBeClear(1, 29);
475 shouldBeClear(4, 25);
476 shouldBeBlue(5, 15);
477 shouldBeBlue(9, 19);
478 shouldBeBlack(10, 15);
479 shouldBeBlack(14, 19);
480 shouldBeRed(5, 21);
481 shouldBeRed(9, 24);
482 shouldBeGreen(11, 21);
483 shouldBeGreen(14, 24);
484 shouldBeClear(16, 29);
485 shouldBeClear(1, 14);
486 shouldBeClear(15, 15);
487 } else {
488 shouldBeClear(1, 1);
489 shouldBeClear(4, 4);
490 shouldBeRed(6, 6);
491 shouldBeRed(9, 9);
492 shouldBeGreen(11, 9);
493 shouldBeBlue(9, 11);
494 shouldBeBlack(11, 11);
495 shouldBeBlack(14, 14);
496 shouldBeClear(16, 1);
497 shouldBeClear(1, 16);
498 shouldBeClear(16, 16);
499 }*/
500 }
501
502 function checkOverCropRight(imageBitmap, option) {
503 debug("Check over crop right.");
504 bitmap = imageBitmap;
505 shouldBe("bitmap.width", "50");
506 shouldBe("bitmap.height", "50");
507
508 clearContext(ctx);
509 ctx.drawImage(imageBitmap, 0, 0);
510 if (option == "flipY" || option == "bottomLeft") {
511 shouldBeBlack(1, 40);
512 shouldBeBlack(9, 49);
513 shouldBeClear(10, 49);
514 shouldBeClear(1, 39);
515 shouldBeClear(10, 40);
516 } else {
517 shouldBeBlack(1, 1);
518 shouldBeBlack(9, 9);
519 shouldBeClear(11, 11);
520 shouldBeClear(1, 11);
521 shouldBeClear(11, 1);
522 }
523
524 // comment out this part for now due to crbug.com/578889
525 /*clearContext(ctx);
526 ctx.drawImage(imageBitmap, 0, 0, 20, 20);
527 if (option == "flipY" || option == "bottomLeft") {
528 shouldBeBlack(0, 16);
529 shouldBeBlack(3, 19);
530 shouldBeClear(4, 19);
531 shouldBeClear(0, 15);
532 shouldBeClear(4, 15);
533 } else {
534 shouldBeBlack(1, 1);
535 shouldBeBlack(3, 3);
536 shouldBeClear(5, 5);
537 shouldBeClear(1, 5);
538 shouldBeClear(5, 1);
539 }
540
541 clearContext(ctx);
542 ctx.drawImage(imageBitmap, 10, 10, 20, 20, 0, 0, 20, 20);
543 if (option == "flipY" || option == "bottomLeft") {
544 shouldBeClear(1, 1);
545 shouldBeClear(3, 3);
546 shouldBeClear(5, 5);
547 shouldBeClear(1, 5);
548 shouldBeClear(5, 1);
549 } else {
550 shouldBeClear(1, 1);
551 shouldBeClear(3, 3);
552 shouldBeClear(5, 5);
553 shouldBeClear(1, 5);
554 shouldBeClear(5, 1);
555 }*/
556 }
557
558 // For an empty image, the orientation doesn't matter
559 function checkEmpty(imageBitmap, option) {
560 debug("Check empty.");
561 bitmap = imageBitmap;
562 shouldBeType("bitmap", "ImageBitmap");
563 shouldBe("bitmap.width", "30");
564 shouldBe("bitmap.height", "30");
565
566 // nothing should be drawn
567 clearContext(ctx);
568 ctx.drawImage(imageBitmap, 0, 0);
569 shouldBeClear(1, 1);
570 shouldBeClear(9, 9);
571 shouldBeClear(11, 11);
572 shouldBeClear(22, 22);
573 }
574 </script>
575 </body>
576 </html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698