Bug 1307740 - Add more canvas filter tainting tests. r?tobytailor draft
authorMarkus Stange <mstange@themasta.com>
Wed, 19 Oct 2016 11:52:08 -0400
changeset 427024 29e873168a95c869d49430fbbefd6baf3ceaaf2f
parent 427023 24fab6a8fbe43341097eb2757676f76eab39e67c
child 427131 944c3d53126b3616fe53285e4e516fd2b744c97d
push id32898
push userbmo:mstange@themasta.com
push dateWed, 19 Oct 2016 15:52:56 +0000
reviewerstobytailor
bugs1307740
milestone52.0a1
Bug 1307740 - Add more canvas filter tainting tests. r?tobytailor MozReview-Commit-ID: JSISqvsTAmD
dom/canvas/test/test_filter_tainted.html
--- a/dom/canvas/test/test_filter_tainted.html
+++ b/dom/canvas/test/test_filter_tainted.html
@@ -5,50 +5,63 @@
 <svg style="display: block; width: 0; height: 0">
   <defs>
     <filter id="colormatrix-make-green">
       <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0
                                                               1 1 1 1 0
                                                               0 0 0 0 0
                                                               0 0 0 1 0"/>
     </filter>
+    <filter id="use-feFlood-as-map-on-SourceGraphic">
+      <feFlood flood-color="lime" result='green'/>
+      <!-- should shift everything up and to the left by 8 pixels (only respects A channel) -->
+      <feDisplacementMap in="SourceGraphic" in2="green" scale="16"/>
+    </filter>
     <filter id="render-cross-origin-red-feImage">
       <feImage xlink:href="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
     </filter>
+    <filter id="generate-green">
+      <feFlood flood-color="lime" result='green'/>
+    </filter>
     <filter id="use-SourceGraphic-as-map-on-red">
       <feImage xlink:href='image_red-16x16.png' result='img'/>
       <feDisplacementMap in="img" in2="SourceGraphic" scale="20"/>
     </filter>
     <filter id="use-cross-origin-green-feImage-as-map-on-same-origin-red">
       <feImage xlink:href='image_red-16x16.png' result='img'/>
       <feImage xlink:href='http://example.com/tests/dom/canvas/test/image_green-16x16.png' result='map'/>
       <feDisplacementMap in="img" in2="map" scale="20"/>
     </filter>
     <filter id="use-cross-origin-red-feImage-as-map-on-SourceGraphic">
       <feImage xlink:href='http://example.com/tests/dom/canvas/test/image_red-16x16.png' result='img'/>
       <feDisplacementMap in="SourceGraphic" in2="img" scale="20"/>
     </filter>
   </defs>
 </svg>
-<div id="canvasContainer"></div>
+<div id="canvasContainer" style="height:100px;"></div>
+<img id="same-origin-green" src="image_green-16x16.png"/>
+<img id="same-origin-red" src="image_red-16x16.png"/>
 <img id="cross-origin-green" src="http://example.com/tests/dom/canvas/test/image_green-16x16.png"/>
 <img id="cross-origin-red" src="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
 <script>
 
 function isPixel(ctx, x, y, r, g, b, a, d) {
   var pixel = ctx.getImageData(x, y, 1, 1);
   var pr = pixel.data[0],
       pg = pixel.data[1],
       pb = pixel.data[2],
       pa = pixel.data[3];
-  ok(r - d <= pr && pr <= r + d &&
-     g - d <= pg && pg <= g + d &&
-     b - d <= pb && pb <= b + d &&
-     a - d <= pa && pa <= a + d,
+  var checkSucceeded = r - d <= pr && pr <= r + d &&
+                       g - d <= pg && pg <= g + d &&
+                       b - d <= pb && pb <= b + d &&
+                       a - d <= pa && pa <= a + d;
+  ok(checkSucceeded,
      `pixel ${x},${y} is ${pr},${pg},${pb},${pa}; expected ${r},${g},${b},${a} +/- ${d}`);
+
+  return checkSucceeded;
 }
 
 function expectCanvasCtxToBeTainted(ctx) {
   try {
     ctx.getImageData(0, 0, 16, 16);
     ok(false, 'Canvas should have been tainted and should throw a SecurityError when calling getImageData.');
   } catch (e) {
     is(e.name, 'SecurityError', 'Canvas should have been tainted and should throw a SecurityError when calling getImageData.');
@@ -74,95 +87,251 @@ function newCanvas() {
 // This uses SpecialPowers + drawWindow and can check pixels even in tainted canvases.
 function checkCanvasPixel(canvas, x, y, r, g, b, a, fuzz) {
   var container = document.getElementById('canvasContainer');
   container.appendChild(canvas);
 
   var captureCanvas = newCanvas();
   var captureCtx = SpecialPowers.wrap(captureCanvas.getContext('2d'));
   captureCtx.drawWindow(window, 0, 0, 16, 16, 'rgb(255, 255, 255)', 0);
-  isPixel(captureCtx, x, y, r, g, b, a, fuzz);
+  if (!isPixel(captureCtx, x, y, r, g, b, a, fuzz)) {
+    info(captureCanvas.toDataURL('image/png'));
+  }
 
   container.removeChild(canvas);
 }
 
 function runTest() {
 
   SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
+    var sameOriginGreenImage = document.getElementById('same-origin-green');
+    var sameOriginRedImage = document.getElementById('same-origin-red');
     var crossOriginGreenImage = document.getElementById('cross-origin-green');
     var crossOriginRedImage = document.getElementById('cross-origin-red');
 
     var canvas = newCanvas();
     var ctx = canvas.getContext('2d');
   
+    // A CSS filter should not taint the canvas.
+    ctx.filter = 'grayscale(100%)';
+    ctx.fillStyle = 'green';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 8,8, 92,92,92,255, 5);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // An SVG filter should not taint the canvas.
+    ctx.filter = 'url(#colormatrix-make-green)';
+    ctx.fillStyle = 'red';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // A CSS -> SVG -> CSS filter chain should not taint the canvas.
+    ctx.filter = 'grayscale(100%) url(#colormatrix-make-green) grayscale(100%)';
+    is(ctx.filter, 'grayscale(100%) url(#colormatrix-make-green) grayscale(100%)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'red';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 8,8, 183,183,183,255, 5);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // An SVG -> CSS -> SVG filter chain should not taint the canvas.
+    ctx.filter = 'url(#colormatrix-make-green) grayscale(100%) url(#colormatrix-make-green)';
+    is(ctx.filter, 'url(#colormatrix-make-green) grayscale(100%) url(#colormatrix-make-green)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'red';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // feDisplacementMap with untainted map input from feFlood should work and not taint
+    ctx.filter = 'url(#use-feFlood-as-map-on-SourceGraphic)';
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 0,0,255,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // feDisplacementMap with untainted map input from different SVG filter should work and not taint
+    ctx.filter = 'url(#generate-green) url(#use-SourceGraphic-as-map-on-red)';
+    is(ctx.filter, 'url(#generate-green) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // feDisplacementMap with untainted map input from different CSS filter should work and not taint
+    ctx.filter = 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
+    is(ctx.filter, 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
+    expectCanvasCtxToBeUntainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // drawImage with cross-origin image and SVG filter should apply the filter and taint the canvas.
     ctx.filter = 'url(#colormatrix-make-green)';
     ctx.drawImage(crossOriginRedImage, 0, 0);
-
-    // The color matrix should turn the red green.
-    checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
-  
-    // But since the image was cross-origin, the canvas should now be tainted.
+    checkCanvasPixel(canvas, 8,8, 0,255,0,255, 1);
     expectCanvasCtxToBeTainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
 
     // Create new untainted canvas.
     canvas = newCanvas();
     ctx = canvas.getContext('2d');
 
+    // drawImage with cross-origin image and CSS filter should apply the filter and taint the canvas.
+    ctx.filter = 'grayscale(100%)';
+    ctx.drawImage(crossOriginRedImage, 0, 0);
+    checkCanvasPixel(canvas, 8,8, 53,53,53,255, 1);
+    expectCanvasCtxToBeTainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // feDisplacementMap with untainted map input from different CSS filter should work even on tainted canvas
+    ctx.filter = 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
+    is(ctx.filter, 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
+    expectCanvasCtxToBeTainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // feDisplacementMap with untainted map input from feFlood should work even on tainted canvas
+    ctx.filter = 'url(#use-feFlood-as-map-on-SourceGraphic)';
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 0,0,255,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
+    expectCanvasCtxToBeTainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
+
+    // Create new untainted canvas.
+    canvas = newCanvas();
+    ctx = canvas.getContext('2d');
+
+    // cross-origin feImage should render correctly and taint the canvas
+    ctx.filter = 'url(#render-cross-origin-red-feImage)';
+    ctx.rect(0, 0, 16, 16);
+    ctx.fill();
+    checkCanvasPixel(canvas, 8,8, 255,0,0,255, 5);
+    expectCanvasCtxToBeTainted(ctx);
+  
+    // Create new untainted canvas.
+    canvas = newCanvas();
+    ctx = canvas.getContext('2d');
+
+    // cross-origin feImage should be ignored as map input to a displacement map, and taint the canvas.
     ctx.filter = 'url(#use-cross-origin-green-feImage-as-map-on-same-origin-red)';
     ctx.rect(0, 0, 16, 16);
     ctx.fill();
-  
-    // The red feImage should be passed through the displacement map without being shifted
-    checkCanvasPixel(canvas, 8,8, 255,0,0,255, 5);
-
-    // but it should have tainted the canvas.
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
     expectCanvasCtxToBeTainted(ctx);
 
     // Create new untainted canvas.
     canvas = newCanvas();
     ctx = canvas.getContext('2d');
 
-    ctx.filter = 'url(#render-cross-origin-red-feImage)';
-    ctx.rect(0, 0, 16, 16);
-    ctx.fill();
+    expectCanvasCtxToBeUntainted(ctx);
 
-    // red feImage should have rendered
-    checkCanvasPixel(canvas, 8,8, 255,0,0,255, 5);
-  
-    // but it should have tainted the canvas.
+    // cross-origin SourceGraphic should be ignored as map input to a displacement map, and taint the canvas.
+    // SourceGraphic: cross-origin image_green-16x16.png,
+    // gets used as map to shift same-origin image_red-16x16.png,
+    // but should get ignored so that image_red-16x16.png gets drawn unshifted.
+    ctx.filter = 'url(#use-SourceGraphic-as-map-on-red)';
+    ctx.drawImage(crossOriginGreenImage, 0, 0);
+    // expect to see red because cross-origin image_green-16x16.png should not have displaced the red away
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
+    expectCanvasCtxToBeTainted(ctx);
+
+    // cross-origin feImage should be ignored as map to displacement map, and taint the canvas.
+    // SourceGraphic: cross-origin image_green-16x16.png,
+    // Cross-origin feImage image_red-16x16.png gets used as map to shift SourceGraphic,
+    // but should get ignored so that image_green-16x16.png gets drawn unshifted.
+    ctx.filter = 'url(#use-cross-origin-red-feImage-as-map-on-SourceGraphic)';
+    ctx.drawImage(crossOriginGreenImage, 0, 0);
+    // expect to see green because cross-origin image_red-16x16.png should not have displaced the green away
+    checkCanvasPixel(canvas, 4,4, 0,255,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 0,255,0,255, 1);
+    checkCanvasPixel(canvas, 4,12, 0,255,0,255, 1);
+    checkCanvasPixel(canvas, 12,12, 0,255,0,255, 1);
     expectCanvasCtxToBeTainted(ctx);
   
     // Create new untainted canvas.
     canvas = newCanvas();
     ctx = canvas.getContext('2d');
 
-    expectCanvasCtxToBeUntainted(ctx);
+    // feDisplacementMap with tainted map input from different CSS filter should be ignored
+    ctx.filter = 'grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
+    is(ctx.filter, 'grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
+    ctx.drawImage(crossOriginGreenImage, 0, 0);
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
+    expectCanvasCtxToBeTainted(ctx);
+    ctx.clearRect(0, 0, 16, 16);
 
-    // SourceGraphic: cross-origin image_green-16x16.png,
-    // gets used as map to shift same-origin image_red-16x16.png,
-    // but should get ignored so that image_red-16x16.png gets drawn unshifted.
-    ctx.filter = 'url(#use-SourceGraphic-as-map-on-red)';
-    ctx.drawImage(crossOriginGreenImage, 0, 0);
-  
-    // expect to see red because cross-origin image_green-16x16.png should not have displaced the red away
-    checkCanvasPixel(canvas, 8,8, 255,0,0,255, 5);
+    // Create new untainted canvas.
+    canvas = newCanvas();
+    ctx = canvas.getContext('2d');
 
+    // feDisplacementMap with tainted feImage map input from different SVG filter should be ignored
+    ctx.filter = 'url(#render-cross-origin-red-feImage) url(#use-SourceGraphic-as-map-on-red)';
+    is(ctx.filter, 'url(#render-cross-origin-red-feImage) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, 16, 16);
+    checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
+    checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
     expectCanvasCtxToBeTainted(ctx);
 
-    // SourceGraphic: cross-origin image_green-16x16.png,
-    // Cross-origin feImage image_red-16x16.png gets used as map to shift SourceGraphic,
-    // but should get ignored so that image_green-16x16.png gets drawn unshifted.
-    ctx.filter = 'url(#use-cross-origin-red-feImage-as-map-on-SourceGraphic)';
-    ctx.drawImage(crossOriginGreenImage, 0, 0);
+    // Paint 'canvas' (which is red and tainted) into a different canvas and makes sure it taints that other canvas.
+    var secondCanvas = newCanvas();
+    var secondCtx = secondCanvas.getContext('2d');
+    secondCtx.filter = 'grayscale(100%)';
+    secondCtx.drawImage(canvas, 0, 0);
+    checkCanvasPixel(secondCanvas, 4,4, 53,53,53,255, 1);
+    checkCanvasPixel(secondCanvas, 12,4, 53,53,53,255, 1);
+    checkCanvasPixel(secondCanvas, 4,12, 53,53,53,255, 1);
+    checkCanvasPixel(secondCanvas, 12,12, 53,53,53,255, 1);
+    expectCanvasCtxToBeTainted(secondCtx);
 
-    // expect to see green because cross-origin image_red-16x16.png should not have displaced the green away
-    checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
+    // Fill the left half with blue (i.e. an untainted SourceGraphic) and make sure the canvas is still tainted.
+    secondCtx.fillStyle = "blue";
+    secondCtx.fillRect(0, 0, 8, 16);
+    checkCanvasPixel(secondCanvas, 4,4, 17,17,17,255, 1);
+    checkCanvasPixel(secondCanvas, 12,4, 53,53,53,255, 1);
+    checkCanvasPixel(secondCanvas, 4,12, 17,17,17,255, 1);
+    checkCanvasPixel(secondCanvas, 12,12, 53,53,53,255, 1);
+    expectCanvasCtxToBeTainted(secondCtx);
 
-    expectCanvasCtxToBeTainted(ctx);
-  
     SimpleTest.finish();
     
   });
 
 }
 
 SimpleTest.waitForExplicitFinish();