Revision: 5422
Author:   [email protected]
Date:     Thu May 23 14:04:29 2013
Log:      Refactor Canvas 2D context taming to prototype style.
https://codereview.appspot.com/9698043

* CanvasRenderingContext2D, CanvasGradient, and ImageData have regular
  taming ctors and prototypes rather than closure-based wrappers.
* Method taming tools used by canvas taming are now implemented as
  property specs as used in the rest of Domado.
* Removed drawFocusRing taming as drawFocusRing no longer exists in
  spec or implementations.
* 2D canvas is no longer fetched overeagerly and <canvas>.getContext is
  capable of supporting other context types (if we had tamings for
  them).

Supporting changes in es53-test-scan-guest:
* Refer to canvas methods via prototype rather than string matching.
* Fix bugs in "Show only errors" checkbox.

[email protected]

http://code.google.com/p/google-caja/source/detail?r=5422

Modified:
 /trunk/src/com/google/caja/plugin/domado.js
 /trunk/tests/com/google/caja/plugin/es53-test-domado-canvas-guest.html
 /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js

=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Thu May 23 12:24:52 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Thu May 23 14:04:29 2013
@@ -4450,15 +4450,14 @@

       // http://dev.w3.org/html5/spec/Overview.html#the-canvas-element
       (function() {
-        // If the host browser does not have getContext, then it must not
-        // usefully
-        // support canvas, so we don't either; skip registering the canvas
-        // element
-        // class.
         // TODO(felix8a): need to call bridal.initCanvasElement
var canvasTest = makeDOMAccessible(document.createElement('canvas'));
-        if (typeof canvasTest.getContext !== 'function')
+        if (typeof canvasTest.getContext !== 'function') {
+          // If the host browser does not have getContext, then it must not
+ // usefully support canvas, so we don't either; skip registering the
+          // canvas element class.
           return;
+        }

// TODO(kpreid): snitched from Caja runtime; review whether we actually // need this (the Canvas spec says that invalid values should be ignored
@@ -4474,13 +4473,11 @@
          */
         function enforceType(specimen, typename, opt_name) {
           if (typeof specimen !== typename) {
-            throw new Error('expected ', typename, ' instead of ',
-                typeof specimen, ': ', (opt_name || specimen));
+            throw new Error('expected ' + typename + ' instead of ' +
+                typeof specimen + ': ' + (opt_name || specimen));
           }
           return specimen;
         }
-
-        var TameContext2DConf = new Confidence('TameContext2D');

         function matchesStyleFully(cssPropertyName, value) {
           if (typeof value !== "string") { return false; }
@@ -4627,30 +4624,32 @@
           return Object.freeze(tameImageData);
         }
         function TameGradient(gradient) {
-          gradient = makeDOMAccessible(gradient);
-          var tameGradient = {
-            toString: innocuous(function() {
-              return '[domado object CanvasGradient]';
-            }),
-            addColorStop: innocuous(function(offset, color) {
-              try {
-                enforceType(offset, 'number', 'color stop offset');
-                if (!(0 <= offset && offset <= 1)) {
-                  throw new Error(INDEX_SIZE_ERROR);
-                  // TODO(kpreid): should be a DOMException per spec
-                }
-                if (!isColor(color)) {
-                  throw new Error("SYNTAX_ERR");
-                  // TODO(kpreid): should be a DOMException per spec
-                }
-                gradient.addColorStop(offset, color);
-              } catch (e) { throw tameException(e); }
-            })
-          };
-          TameGradientConf.confide(tameGradient, taming);
-          taming.tamesTo(gradient, tameGradient);
-          return Object.freeze(tameGradient);
+          TameGradientConf.confide(this, taming);
+          TameGradientConf.amplify(this, function(privates) {
+            privates.feral = makeDOMAccessible(gradient);
+          });
+          taming.tamesTo(gradient, this);
+          Object.freeze(this);
         }
+        inertCtor(TameGradient, Object, 'CanvasGradient');
+        Props.define(TameGradient.prototype, TameGradientConf, {
+          toString: Props.plainMethod(function() {
+             return '[domado object CanvasGradient]';
+          }),
+ addColorStop: tameMethodCustom(function(privates, offset, color) {
+            enforceType(offset, 'number', 'color stop offset');
+            if (!(0 <= offset && offset <= 1)) {
+              throw new Error(INDEX_SIZE_ERROR);
+              // TODO(kpreid): should be a DOMException per spec
+            }
+            if (!isColor(color)) {
+              throw new Error('SYNTAX_ERR');
+              // TODO(kpreid): should be a DOMException per spec
+            }
+            privates.feral.addColorStop(offset, color);
+          })
+        });
+
         function enforceFinite(value, name) {
           enforceType(value, 'number', name);
           if (!isFinite(value)) {
@@ -4658,9 +4657,124 @@
             // TODO(kpreid): should be a DOMException per spec
           }
         }
+
+        // Design note: We generally reject the wrong number of arguments,
+ // unlike default JS behavior. This is because we are just passing data + // through to the underlying implementation, but we don't want to pass + // on anything which might be an extension we don't know about, and it + // is better to fail explicitly than to leave the client wondering about
+        // why their extension usage isn't working.
+
+ // TODO(kpreid): Consolidate this with tameNoArgEditMethod and friends.
+        var tameSimpleOp = Props.markPropMaker(function (env) {
+          var prop = env.prop;
+          return {
+            enumerable: true,
+            value: env.amplifying(function(privates) {
+              if (arguments.length !== 1) {
+                throw new Error(prop + ' takes no args, not ' +
+                    (arguments.length - 1));
+              }
+              privates.feral[prop]();
+            })
+          };
+        });
+
+        function tameFloatsOp(count) {
+          return Props.markPropMaker(function(env) {
+            var prop = env.prop;
+            return {
+              enumerable: true,
+              value: env.amplifying(function(privates) {
+                if (arguments.length - 1 !== count) {
+                  throw new Error(prop + ' takes ' + count +
+                      ' args, not ' + (arguments.length - 1));
+                }
+                var args = new Array(count);
+                for (var i = 0; i < count; i++) {
+                  args[+i] = enforceType(arguments[+i + 1], 'number',
+                      prop + ' argument ' + i);
+                }
+ // The copy-into-array is necessary in ES5/3 because host DOM
+                // won't take an arguments object from inside of ES53.
+                var feral = privates.feral;
+ // TODO(kpreid): Needing to do this not good. A normal mDA isn't
+                // sufficient because we're using .apply for varargs
+                makeDOMAccessible(feral[prop]).apply(feral, args);
+              })
+            };
+          });
+        }
+
+        function tameRectMethod(resultFn) {
+          return Props.markPropMaker(function(env) {
+            var prop = env.prop;
+            return {
+              enumerable: true,
+              value: env.amplifying(function(privates, x, y, w, h) {
+                if (arguments.length !== 5) {
+                  throw new Error(prop + ' takes 4 args, not ' +
+                                  (arguments.length - 1));
+                }
+                enforceType(x, 'number', 'x');
+                enforceType(y, 'number', 'y');
+                enforceType(w, 'number', 'width');
+                enforceType(h, 'number', 'height');
+                return resultFn(privates.feral[prop](x, y, w, h));
+              })
+            };
+          });
+        }
+
+        var tameDrawText = Props.markPropMaker(function(env) {
+          var prop = env.prop;
+          return {
+            enumerable: true,
+            value: env.amplifying(function(
+                privates, text, x, y, maxWidth) {
+              enforceType(text, 'string', 'text');
+              enforceType(x, 'number', 'x');
+              enforceType(y, 'number', 'y');
+              switch (arguments.length - 1) {
+              case 3:
+                privates.feral[prop](text, x, y);
+                return;
+              case 4:
+                enforceType(maxWidth, 'number', 'maxWidth');
+                privates.feral[prop](text, x, y, maxWidth);
+                return;
+              default:
+                throw new Error(prop + ' cannot accept ' +
+                    (arguments.length - 1) + ' arguments');
+              }
+            })
+          };
+        });
+
+ // TODO(kpreid): Consolidate this with tameNoArgEditMethod and friends.
+        function tameMethodCustom(baseFunc, dontCheckLength) {
+ var expectedLength = baseFunc.length - 1; // remove 'privates' arg
+          return Props.markPropMaker(function(env) {
+            var prop = env.prop;
+            var ampFn = env.amplifying(baseFunc);
+            function argCheckingWrapper() {
+              if (arguments.length !== expectedLength) {
+                throw new Error(env + ' takes ' + expectedLength +
+                    ' args, not ' + arguments.length);
+              }
+              return ampFn.apply(this, arguments);
+            }
+            return {
+              enumerable: true,
+              value: dontCheckLength ? ampFn : argCheckingWrapper
+            };
+          });
+        }

         function TameTextMetrics(feralMetrics) {
           feralMetrics = makeDOMAccessible(feralMetrics);
+ // TextMetrics just acts as a record, so we don't need any forwarding
+          // wrapper; copying the data is sufficient.
           [
             'actualBoundingBoxAscent',
             'actualBoundingBoxDescent',
@@ -4681,456 +4795,296 @@
         }
         inertCtor(TameTextMetrics, Object, 'TextMetrics');

-        function TameCanvasElement(node) {
-          // TODO(kpreid): review whether this can use defineElement
-          TameElement.call(this, node);
-
-          // helpers for tame context
-          var context = makeDOMAccessible(node.getContext('2d'));
-          function tameFloatsOp(count, verb) {
-            var m = makeFunctionAccessible(context[verb]);
-            return cajaVM.constFunc(function() {
-              if (arguments.length !== count) {
-                throw new Error(verb + ' takes ' + count + ' args, not ' +
-                                arguments.length);
-              }
-              for (var i = 0; i < count; i++) {
- enforceType(arguments[+i], 'number', verb + ' argument ' + i);
-              }
-              try {
- // The copy-into-array is necessary in ES5/3 because host DOM
-                // won't take an arguments object from inside of ES53.
-                m.apply(context, Array.prototype.slice.call(arguments));
-              } catch (e) { throw tameException(e); }
-            });
-          }
-          function tameRectMethod(m, hasResult) {
-            makeFunctionAccessible(m);
-            return cajaVM.constFunc(function(x, y, w, h) {
-              if (arguments.length !== 4) {
-                throw new Error(m + ' takes 4 args, not ' +
-                                arguments.length);
-              }
-              enforceType(x, 'number', 'x');
-              enforceType(y, 'number', 'y');
-              enforceType(w, 'number', 'width');
-              enforceType(h, 'number', 'height');
-              try {
-                if (hasResult) {
-                  return m.call(context, x, y, w, h);
-                } else {
-                  m.call(context, x, y, w, h);
-                }
-              } catch (e) { throw tameException(e); }
-            });
-          }
-          function tameDrawText(m) {
-            makeFunctionAccessible(m);
-            return cajaVM.constFunc(function(text, x, y, maxWidth) {
-              enforceType(text, 'string', 'text');
-              enforceType(x, 'number', 'x');
-              enforceType(y, 'number', 'y');
-              try {
-                switch (arguments.length) {
-                case 3:
-                  m.apply(context, Array.prototype.slice.call(arguments));
-                  return;
-                case 4:
-                  enforceType(maxWidth, 'number', 'maxWidth');
-                  m.apply(context, Array.prototype.slice.call(arguments));
-                  return;
-                default:
- throw new Error(m + ' cannot accept ' + arguments.length +
-                                      ' arguments');
-                }
-              } catch (e) { throw tameException(e); }
-            });
-          }
-          function tameGetMethod(prop) {
-            return cajaVM.constFunc(function() {
-              try {
-                return context[prop];
-              } catch (e) { throw tameException(e); }
-            });
-          }
-          function tameSetMethod(prop, validator) {
-            return cajaVM.constFunc(function(newValue) {
-              try {
-                if (validator(newValue)) {
-                  context[prop] = newValue;
-                }
-              } catch (e) { throw tameException(e); }
-              return newValue;
-            });
-          }
-          var CP_STYLE = Props.markPropMaker(function(env) {
-            var prop = env.prop;
-            return {
-              enumerable: true,
-              get: cajaVM.constFunc(function() {
-                try {
-                  var value = context[prop];
-                  if (typeof(value) === 'string') {
-                    return canonColor(value);
-                  } else if (cajaVM.passesGuard(TameGradientT,
-                                                taming.tame(value))) {
-                    return taming.tame(value);
-                  } else {
- throw new Error('Internal: Can\'t tame value ' + value +
-                        ' of ' + prop);
-                  }
-                } catch (e) { throw tameException(e); }
-              }),
-              set: cajaVM.constFunc(function(newValue) {
-                try {
-                  if (isColor(newValue)) {
-                    context[prop] = newValue;
-                  } else if (typeof(newValue) === "object" &&
-                             cajaVM.passesGuard(TameGradientT, newValue)) {
-                    context[prop] = taming.untame(newValue);
-                  } // else do nothing
-                  return newValue;
-                } catch (e) { throw tameException(e); }
-              })
-            };
+        // http://dev.w3.org/html5/2dcontext/
+        var TameContext2DConf = new Confidence('TameContext2D');
+        function TameContext2D(feralContext, policy) {
+          // policy is needed for the PropertyTaming accessors
+          feralContext = makeDOMAccessible(feralContext);
+          TameContext2DConf.confide(this, taming);
+          TameContext2DConf.amplify(this, function(privates) {
+            privates.feral = feralContext;
+            privates.policy = policy;
+            Object.preventExtensions(privates);
           });
-          function tameSimpleOp(m) {  // no return value
-            makeFunctionAccessible(m);
-            return cajaVM.constFunc(function() {
-              if (arguments.length !== 0) {
- throw new Error(m + ' takes no args, not ' + arguments.length);
-              }
-              try {
-                m.call(context);
-              } catch (e) { throw tameException(e); }
-            });
-          }
+        }
+        inertCtor(TameContext2D, Object, 'CanvasRenderingContext2D');
+ // TODO(kpreid): have inertCtor automatically install an appropriate
+        // toString method.
+        TameContext2D.prototype.toString = cajaVM.constFunc(function() {
+          return '[Domado CanvasRenderingContext2D]';
+        });
+        Props.define(TameContext2D.prototype, TameContext2DConf, {
+          save: tameSimpleOp,
+          restore: tameSimpleOp,

- // Design note: We generally reject the wrong number of arguments, - // unlike default JS behavior. This is because we are just passing
-          // data
- // through to the underlying implementation, but we don't want to pass - // on anything which might be an extension we don't know about, and it - // is better to fail explicitly than to leave the client wondering
-          // about
-          // why their extension usage isn't working.
+          scale: tameFloatsOp(2),
+          rotate: tameFloatsOp(1),
+          translate: tameFloatsOp(2),
+          transform: tameFloatsOp(6),
+          setTransform: tameFloatsOp(6),

-          // http://dev.w3.org/html5/2dcontext/
- // TODO(kpreid): Review this for converting to prototypical objects
-          var tameContext2d = {
-            toString: innocuous(function() {
-              return '[domado object CanvasRenderingContext2D]';
-            }),
+          createLinearGradient: tameMethodCustom(
+              function(privates, x0, y0, x1, y1) {
+            enforceType(x0, 'number', 'x0');
+            enforceType(y0, 'number', 'y0');
+            enforceType(x1, 'number', 'x1');
+            enforceType(y1, 'number', 'y1');
+            return new TameGradient(
+                privates.feral.createLinearGradient(x0, y0, x1, y1));
+          }),

-            save: tameSimpleOp(context.save),
-            restore: tameSimpleOp(context.restore),
+          createRadialGradient: tameMethodCustom(
+              function(privates, x0, y0, r0, x1, y1, r1) {
+            enforceType(x0, 'number', 'x0');
+            enforceType(y0, 'number', 'y0');
+            enforceType(r0, 'number', 'r0');
+            enforceType(x1, 'number', 'x1');
+            enforceType(y1, 'number', 'y1');
+            enforceType(r1, 'number', 'r1');
+            return new TameGradient(privates.feral.createRadialGradient(
+              x0, y0, r0, x1, y1, r1));
+          }),

-            scale: tameFloatsOp(2, 'scale'),
-            rotate: tameFloatsOp(1, 'rotate'),
-            translate: tameFloatsOp(2, 'translate'),
-            transform: tameFloatsOp(6, 'transform'),
-            setTransform: tameFloatsOp(6, 'setTransform'),
+          createPattern: tameMethodCustom(
+              function(privates, imageElement, repetition) {
+ // Consider what policy to have wrt reading the pixels from image
+            // elements before implementing this.
+            throw new Error(
+                'Domado: canvas createPattern not yet implemented');
+          }),

-            createLinearGradient: function (x0, y0, x1, y1) {
-              if (arguments.length !== 4) {
-                throw new Error('createLinearGradient takes 4 args, not ' +
-                                arguments.length);
-              }
-              enforceType(x0, 'number', 'x0');
-              enforceType(y0, 'number', 'y0');
-              enforceType(x1, 'number', 'x1');
-              enforceType(y1, 'number', 'y1');
-              try {
-                return new TameGradient(
-                  context.createLinearGradient(x0, y0, x1, y1));
-              } catch (e) { throw tameException(e); }
-            },
-            createRadialGradient: function (x0, y0, r0, x1, y1, r1) {
-              if (arguments.length !== 6) {
-                throw new Error('createRadialGradient takes 6 args, not ' +
-                                arguments.length);
-              }
-              enforceType(x0, 'number', 'x0');
-              enforceType(y0, 'number', 'y0');
-              enforceType(r0, 'number', 'r0');
-              enforceType(x1, 'number', 'x1');
-              enforceType(y1, 'number', 'y1');
-              enforceType(r1, 'number', 'r1');
-              try {
-                return new TameGradient(context.createRadialGradient(
-                  x0, y0, r0, x1, y1, r1));
-              } catch (e) { throw tameException(e); }
-            },
+          clearRect:  tameRectMethod(function() {}),
+          fillRect:   tameRectMethod(function() {}),
+          strokeRect: tameRectMethod(function() {}),

-            createPattern: function (imageElement, repetition) {
- // Consider what policy to have wrt reading the pixels from image
-              // elements before implementing this.
-              throw new Error(
-                  'Domita: canvas createPattern not yet implemented');
-            },
+          beginPath: tameSimpleOp,
+          closePath: tameSimpleOp,
+          moveTo: tameFloatsOp(2),
+          lineTo: tameFloatsOp(2),
+          quadraticCurveTo: tameFloatsOp(4),
+          bezierCurveTo: tameFloatsOp(6),
+          arcTo: tameFloatsOp(5),
+          rect: tameFloatsOp(4),
+          arc: tameMethodCustom(function(
+ privates, x, y, radius, startAngle, endAngle, anticlockwise) {
+            enforceType(x, 'number', 'x');
+            enforceType(y, 'number', 'y');
+            enforceType(radius, 'number', 'radius');
+            enforceType(startAngle, 'number', 'startAngle');
+            enforceType(endAngle, 'number', 'endAngle');
+            enforceType(anticlockwise, 'boolean', 'anticlockwise');
+            if (radius < 0) {
+              throw new Error(INDEX_SIZE_ERROR);
+              // TODO(kpreid): should be a DOMException per spec
+            }
+            privates.feral.arc(
+                x, y, radius, startAngle, endAngle, anticlockwise);
+          }),

-            clearRect:  tameRectMethod(context.clearRect,  false),
-            fillRect:   tameRectMethod(context.fillRect,   false),
-            strokeRect: tameRectMethod(context.strokeRect, false),
+          fill: tameSimpleOp,
+          stroke: tameSimpleOp,
+          clip: tameSimpleOp,

-            beginPath: tameSimpleOp(context.beginPath),
-            closePath: tameSimpleOp(context.closePath),
-            moveTo: tameFloatsOp(2, 'moveTo'),
-            lineTo: tameFloatsOp(2, 'lineTo'),
-            quadraticCurveTo: tameFloatsOp(4, 'quadraticCurveTo'),
-            bezierCurveTo: tameFloatsOp(6, 'bezierCurveTo'),
-            arcTo: tameFloatsOp(5, 'arcTo'),
-            rect: tameFloatsOp(4, 'rect'),
- arc: function (x, y, radius, startAngle, endAngle, anticlockwise) {
-              if (arguments.length !== 6) {
- throw new Error('arc takes 6 args, not ' + arguments.length);
-              }
-              enforceType(x, 'number', 'x');
-              enforceType(y, 'number', 'y');
-              enforceType(radius, 'number', 'radius');
-              enforceType(startAngle, 'number', 'startAngle');
-              enforceType(endAngle, 'number', 'endAngle');
-              enforceType(anticlockwise, 'boolean', 'anticlockwise');
-              if (radius < 0) {
-                throw new Error(INDEX_SIZE_ERROR);
-                // TODO(kpreid): should be a DOMException per spec
-              }
-              try {
- context.arc(x, y, radius, startAngle, endAngle, anticlockwise);
-              } catch (e) { throw tameException(e); }
-            },
-            fill: tameSimpleOp(context.fill),
-            stroke: tameSimpleOp(context.stroke),
-            clip: tameSimpleOp(context.clip),
+ // TODO(kpreid): Generic type-checking wrapper to eliminate the need
+          // for this code
+          isPointInPath: tameMethodCustom(function(privates, x, y) {
+            enforceType(x, 'number', 'x');
+            enforceType(y, 'number', 'y');
+ return enforceType(privates.feral.isPointInPath(x, y), 'boolean');
+          }),

-            isPointInPath: function (x, y) {
-              enforceType(x, 'number', 'x');
-              enforceType(y, 'number', 'y');
-              try {
-                return enforceType(context.isPointInPath(x, y), 'boolean');
-              } catch (e) { throw tameException(e); }
-            },
+          fillText: tameDrawText,
+          strokeText: tameDrawText,

-            fillText: tameDrawText(context.fillText),
-            strokeText: tameDrawText(context.strokeText),
-            measureText: function (string) {
-              if (arguments.length !== 1) {
-                throw new Error('measureText takes 1 arg, not ' +
-                    arguments.length);
-              }
-              enforceType(string, 'string', 'measureText argument');
-              try {
-                return new TameTextMetrics(context.measureText(string));
-              } catch (e) { throw tameException(e); }
-            },
+          measureText: tameMethodCustom(function(privates, string) {
+            enforceType(string, 'string', 'measureText argument');
+            return new TameTextMetrics(privates.feral.measureText(string));
+          }),

-            drawImage: function (imageElement) {
- // Consider what policy to have wrt reading the pixels from image
-              // elements before implementing this.
- throw new Error('Domita: canvas drawImage not yet implemented');
-            },
+          drawImage: tameMethodCustom(function(privates, imageElement) {
+ // Consider what policy to have wrt reading the pixels from image
+            // elements before implementing this.
+ throw new Error('Domita: canvas drawImage not yet implemented');
+          }),

-            createImageData: function (sw, sh) {
-              if (arguments.length !== 2) {
-                throw new Error('createImageData takes 2 args, not ' +
-                                arguments.length);
+          createImageData: tameMethodCustom(function(privates, sw, sh) {
+            enforceType(sw, 'number', 'sw');
+            enforceType(sh, 'number', 'sh');
+ // TODO(kpreid): taming membrane? or is this best considered a copy? + return new TameImageData(privates.feral.createImageData(sw, sh));
+          }),
+          getImageData: tameRectMethod(TameImageData),
+          putImageData: tameMethodCustom(function(privates,
+ tameImageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
+            tameImageData = TameImageDataT.coerce(tameImageData);
+            enforceFinite(dx, 'dx');
+            enforceFinite(dy, 'dy');
+            switch (arguments.length - 1) {
+            case 3:
+              dirtyX = 0;
+              dirtyY = 0;
+              dirtyWidth = tameImageData.width;
+              dirtyHeight = tameImageData.height;
+              break;
+            case 7:
+              enforceFinite(dirtyX, 'dirtyX');
+              enforceFinite(dirtyY, 'dirtyY');
+              enforceFinite(dirtyWidth, 'dirtyWidth');
+              enforceFinite(dirtyHeight, 'dirtyHeight');
+              break;
+            default:
+ throw 'putImageData cannot accept ' + (arguments.length - 1) +
+                  ' arguments';
+            }
+ TameImageDataConf.amplify(tameImageData, function(imageDataPriv) {
+              var tamePixelArray = imageDataPriv.tamePixelArray;
+              if (tamePixelArray) {
+                tamePixelArray._d_canvas_writeback();
               }
-              enforceType(sw, 'number', 'sw');
-              enforceType(sh, 'number', 'sh');
-              try {
-                return new TameImageData(context.createImageData(sw, sh));
-              } catch (e) { throw tameException(e); }
-            },
-            getImageData: tameRectMethod(function (sx, sy, sw, sh) {
-              return TameImageData(context.getImageData(sx, sy, sw, sh));
-            }, true),
-            putImageData: function
-                (tameImageData, dx, dy, dirtyX, dirtyY,
-                    dirtyWidth, dirtyHeight) {
-              tameImageData = TameImageDataT.coerce(tameImageData);
-              enforceFinite(dx, 'dx');
-              enforceFinite(dy, 'dy');
-              switch (arguments.length) {
-              case 3:
-                dirtyX = 0;
-                dirtyY = 0;
-                dirtyWidth = tameImageData.width;
-                dirtyHeight = tameImageData.height;
-                break;
-              case 7:
-                enforceFinite(dirtyX, 'dirtyX');
-                enforceFinite(dirtyY, 'dirtyY');
-                enforceFinite(dirtyWidth, 'dirtyWidth');
-                enforceFinite(dirtyHeight, 'dirtyHeight');
-                break;
-              default:
-                throw 'putImageData cannot accept ' + arguments.length +
-                    ' arguments';
+              privates.feral.putImageData(imageDataPriv.feral,
+                  dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
+            });
+          }, true)
+        });
+        var CP_STYLE = Props.markPropMaker(function(env) {
+          var prop = env.prop;
+          return {
+            enumerable: true,
+            get: env.amplifying(function(privates) {
+              var value = privates.feral[prop];
+              if (typeof(value) === 'string') {
+                return canonColor(value);
+              } else if (cajaVM.passesGuard(TameGradientT,
+                                            taming.tame(value))) {
+                return taming.tame(value);
+              } else {
+                throw new Error('Internal: Can\'t tame value ' + value +
+                    ' of ' + prop);
               }
- TameImageDataConf.amplify(tameImageData, function(imageDataPriv) {
-                var tamePixelArray = imageDataPriv.tamePixelArray;
-                if (tamePixelArray) {
-                  tamePixelArray._d_canvas_writeback();
-                }
-                context.putImageData(imageDataPriv.feral,
-                                     dx, dy, dirtyX, dirtyY,
-                                     dirtyWidth, dirtyHeight);
-              });
-            }
+            }),
+            set: env.amplifying(function(privates, newValue) {
+              if (isColor(newValue)) {
+                privates.feral[prop] = newValue;
+              } else if (typeof(newValue) === 'object' &&
+                         cajaVM.passesGuard(TameGradientT, newValue)) {
+                privates.feral[prop] = taming.untame(newValue);
+              } // else do nothing
+              return newValue;
+            })
           };
-
-          if ("drawFocusRing" in context) {
- // TODO(kpreid): drawFocusRing is not provided in current browsers
-            // and is renamed/revised in current spec.
-            tameContext2d.drawFocusRing = function
-                (tameElement, x, y, canDrawCustom) {
-              switch (arguments.length) {
-              case 3:
-                canDrawCustom = false;
-                break;
-              case 4:
-                break;
-              default:
-                throw 'drawFocusRing cannot accept ' + arguments.length +
-                    ' arguments';
-              }
-              enforceType(x, 'number', 'x');
-              enforceType(y, 'number', 'y');
-              enforceType(canDrawCustom, 'boolean', 'canDrawCustom');
-              return nodeAmplify(tameElement, function(elemPriv) {
-                // On safety of using the untamed node here: The only
- // information drawFocusRing takes from the node is whether it - // is focused. Note that the nodeAmp also provides exception
-                // taming.
-                return enforceType(
-                    context.drawFocusRing(elemPriv.feral, x, y,
-                                          canDrawCustom),
-                    'boolean');
-              });
+        });
+        Props.define(TameContext2D.prototype, TameContext2DConf, {
+          // We filter the values supplied to setters in case some browser
+ // extension makes them more powerful, e.g. containing scripting or
+          // a URL.
+          // TODO(kpreid): Do we want to filter the *getters* as well?
+          // Scenarios: (a) canvas shared with innocent code, (b) browser
+          // quirks?? If we do, then what should be done with a bad value?
+          globalAlpha: PT.RWCond(
+              function (v) { return typeof v === 'number' &&
+                                    0.0 <= v && v <= 1.0;     }),
+          globalCompositeOperation: PT.RWCond(
+              StringTest([
+                'source-atop',
+                'source-in',
+                'source-out',
+                'source-over',
+                'destination-atop',
+                'destination-in',
+                'destination-out',
+                'destination-over',
+                'lighter',
+                'copy',
+                'xor'
+              ])),
+          strokeStyle: CP_STYLE,
+          fillStyle: CP_STYLE,
+          lineWidth: PT.RWCond(
+              function (v) { return typeof v === 'number' &&
+                                    0.0 < v && v !== Infinity; }),
+          lineCap: PT.RWCond(
+              StringTest([
+                'butt',
+                'round',
+                'square'
+              ])),
+          lineJoin: PT.RWCond(
+              StringTest([
+                'bevel',
+                'round',
+                'miter'
+              ])),
+          miterLimit: PT.RWCond(
+              function (v) { return typeof v === 'number' &&
+                                    0 < v && v !== Infinity; }),
+          shadowOffsetX: PT.RWCond(
+              function (v) {
+                return typeof v === 'number' && isFinite(v); }),
+          shadowOffsetY: PT.RWCond(
+              function (v) {
+                return typeof v === 'number' && isFinite(v); }),
+          shadowBlur: PT.RWCond(
+              function (v) { return typeof v === 'number' &&
+                                    0.0 <= v && v !== Infinity; }),
+          shadowColor: Props.markPropMaker(function(env) {
+            return {
+              enumerable: true,
+              // TODO(kpreid): Better tools for deriving descriptors
+              get: CP_STYLE(env).get,
+              set: PT.RWCond(isColor)(env).set
             };
-          }
+          }),

-          Props.define(tameContext2d, TameContext2DConf, {
- // We filter the values supplied to setters in case some browser - // extension makes them more powerful, e.g. containing scripting or
-            // a URL.
-            // TODO(kpreid): Do we want to filter the *getters* as well?
-            // Scenarios: (a) canvas shared with innocent code, (b) browser
- // quirks?? If we do, then what should be done with a bad value?
-            globalAlpha: PT.RWCond(
-                function (v) { return typeof v === "number" &&
-                                      0.0 <= v && v <= 1.0;     }),
-            globalCompositeOperation: PT.RWCond(
-                StringTest([
-                  "source-atop",
-                  "source-in",
-                  "source-out",
-                  "source-over",
-                  "destination-atop",
-                  "destination-in",
-                  "destination-out",
-                  "destination-over",
-                  "lighter",
-                  "copy",
-                  "xor"
-                ])),
-            strokeStyle: CP_STYLE,
-            fillStyle: CP_STYLE,
-            lineWidth: PT.RWCond(
-                function (v) { return typeof v === "number" &&
-                                      0.0 < v && v !== Infinity; }),
-            lineCap: PT.RWCond(
-                StringTest([
-                  "butt",
-                  "round",
-                  "square"
-                ])),
-            lineJoin: PT.RWCond(
-                StringTest([
-                  "bevel",
-                  "round",
-                  "miter"
-                ])),
-            miterLimit: PT.RWCond(
-                  function (v) { return typeof v === "number" &&
-                                        0 < v && v !== Infinity; }),
-            shadowOffsetX: PT.RWCond(
-                  function (v) {
-                    return typeof v === "number" && isFinite(v); }),
-            shadowOffsetY: PT.RWCond(
-                  function (v) {
-                    return typeof v === "number" && isFinite(v); }),
-            shadowBlur: PT.RWCond(
-                  function (v) { return typeof v === "number" &&
-                                        0.0 <= v && v !== Infinity; }),
-            shadowColor: Props.markPropMaker(function(env) {
-              return {
-                enumerable: true,
-                // TODO(kpreid): Better tools for deriving descriptors
-                get: CP_STYLE(env).get,
-                set: PT.RWCond(isColor)(env).set
-              };
-            }),
+          font: PT.RWCond(isFont),
+          textAlign: PT.RWCond(
+              StringTest([
+                'start',
+                'end',
+                'left',
+                'right',
+                'center'
+              ])),
+          textBaseline: PT.RWCond(
+              StringTest([
+                'top',
+                'hanging',
+                'middle',
+                'alphabetic',
+                'ideographic',
+                'bottom'
+              ]))
+        });
+        cajaVM.def(TameContext2D);

-            font: PT.RWCond(isFont),
-            textAlign: PT.RWCond(
-                StringTest([
-                  "start",
-                  "end",
-                  "left",
-                  "right",
-                  "center"
-                ])),
-            textBaseline: PT.RWCond(
-                StringTest([
-                  "top",
-                  "hanging",
-                  "middle",
-                  "alphabetic",
-                  "ideographic",
-                  "bottom"
-                ]))
-          });
+        defineElement({
+          domClass: 'HTMLCanvasElement',
+          properties: {
+            height: PT.filter(false, identity, false, Number),
+            width: PT.filter(false, identity, false, Number),
+            getContext: Props.ampMethod(function(privates, contextId) {
+ // TODO(kpreid): We can refine this by adding policy checks to the + // canvas taming, which allow getImageData and so on but not any + // drawing. Not bothering to do that for now; if you have a use
+              // for it let us know.
+              privates.policy.requireEditable();

-          var policy;
-          nodeAmplify(this, function(privates) {
-            privates.tameContext2d = tameContext2d;
-            policy = privates.policy;
-          });
-
-          TameContext2DConf.confide(tameContext2d, taming);
-          TameContext2DConf.amplify(tameContext2d, function(privates) {
-            privates.policy = policy;
-            privates.feral = context;
-            Object.preventExtensions(privates);
-          });
-          cajaVM.def(tameContext2d);
-          taming.tamesTo(context, tameContext2d);
-        }  // end of TameCanvasElement
-        inertCtor(TameCanvasElement, TameElement, 'HTMLCanvasElement');
-        Props.define(TameCanvasElement.prototype, TameNodeConf, {
-          height: PT.filter(false, identity, false, Number),
-          width: PT.filter(false, identity, false, Number),
-          getContext: Props.ampMethod(function(privates, contextId) {
- // TODO(kpreid): We can refine this by inventing a ReadOnlyCanvas - // object to return in this situation, which allows getImageData and - // so on but not any drawing. Not bothering to do that for now; if
-            // you have a use for it let us know.
-            privates.policy.requireEditable();
-
-            enforceType(contextId, 'string', 'contextId');
-            switch (contextId) {
-              case '2d':
- // TODO(kpreid): Need to be lazy once we support other context
-                // types.
-                return privates.tameContext2d;
-              default:
- // http://dev.w3.org/html5/spec/the-canvas-element.html#the-canvas-element - // "If contextId is not the name of a context supported by the
-                // user agent, return null and abort these steps."
-                return null;
-            }
-          })
+              enforceType(contextId, 'string', 'contextId');
+              switch (contextId) {
+                case '2d':
+                  var feralContext = privates.feral.getContext('2d');
+                  if (!taming.hasTameTwin(feralContext)) {
+ taming.tamesTo(feralContext, cajaVM.def(new TameContext2D(
+                        feralContext, privates.policy)));
+                  }
+                  return taming.tame(feralContext);
+                default:
+ // http://dev.w3.org/html5/spec/the-canvas-element.html#the-canvas-element + // "If contextId is not the name of a context supported by the
+                  // user agent, return null and abort these steps."
+                  return null;
+              }
+            })
+          }
         });
       })();

=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-canvas-guest.html Tue Jan 22 16:30:41 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-domado-canvas-guest.html Thu May 23 14:04:29 2013
@@ -527,56 +527,6 @@
   });
 </script>

-<div id="testCanvasContextFocus" class="testcontainer">
-  Canvas drawFocusRing
-  <canvas id="testCanvasContextFocus-canvas" height="20" width="50">
-    <form>
-      <input type="text" id="testCanvasContextFocus-field-1">
-      <input type="text" id="testCanvasContextFocus-field-2">
-    </form>
-  </canvas>
-</div>
-<script type="text/javascript">
-  jsunitRegister('testCanvasContextFocus',
-                 function testCanvasContextFocus() {
-    var context = document.getElementById('testCanvasContextFocus-canvas')
-                          .getContext('2d');
-
-    if ("drawFocusRing" in context) {
-      var focusElem1 =
-          document.getElementById('testCanvasContextFocus-field-1');
-      var focusElem2 =
-          document.getElementById('testCanvasContextFocus-field-2');
-
-      // Establish a path
-      context.beginPath();
-      context.rect(0, 0, 10, 10);
-
-      focusElem1.focus();
-      focusElem1.blur();
-      // nothing of ours is focused
-
-      var res;
-
-      res = context.drawFocusRing(focusElem1, 5, 5);
- assertFalse('drawFocusRing without canDrawCustom without focus', res);
-
-      res = context.drawFocusRing(focusElem1, 5, 5, true);
-      assertFalse('drawFocusRing with canDrawCustom without focus', res);
-
-      focusElem1.focus();
-      res = context.drawFocusRing(focusElem1, 5, 5, true);
-      assertTrue('drawFocusRing with canDrawCustom with focus', res);
- // Might be false, per the spec, but it's more relevant to test this case
-      // than the chance the 'user requires focus rings' case holds
-    } else {
-      console.log("no drawFocusRing, skipping test.");
-    }
-
-    pass('testCanvasContextFocus');
-  });
-</script>
-
 <p id="testCanvasNotEditable" class="testcontainer">
   Canvas context without editability
   <canvas id="testCanvasNotEditable-canvas"
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Thu May 23 10:22:00 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Thu May 23 14:04:29 2013
@@ -24,7 +24,7 @@
  *     assertTrue, assertEquals, pass, jsunitFail,
* Event, HTMLInputElement, HTMLTableRowElement, HTMLTableSectionElement, * HTMLTableElement, Image, Option, XMLHttpRequest, Window, Document, Node,
- *     Attr, Text,
+ *     Attr, Text, CanvasRenderingContext2D, CanvasGradient
  * @overrides window
  */

@@ -1071,7 +1071,10 @@
         return null;
       }
     });
-    var argsByIdentity = functionArgs.setByIdentity;
+    function argsByIdentity(obj, g) {
+      functionArgs.setByIdentity(obj, g);
+      return g;
+    }
     function argsByProp(p, g) {
       functionArgs.setByPropertyName(p, g);
       functionArgs.setByPropertyName('get ' + p + '<THIS>()', g);
@@ -1431,54 +1434,65 @@
genMethod(genEventName, G.value(function stubL() {}), genBoolean)));

     // 2D context (and friends) methods
- // TODO(kpreid): Better matching strategy so these are specific to context,
-    // or class-ify canvas taming so we can take the .prototype methods.
+    var canvas2DProto = CanvasRenderingContext2D.prototype;
     function imageDataResult(calls) {
       return annotate(calls, function(context, thrown) {
         expectedUnfrozen.setByIdentity(context.get().data, true);
       });
     }
-    argsByProp('arc', genMethodAlt(genNumbers(5)));
-    argsByProp('arcTo', genMethodAlt(genNumbers(5)));
-    argsByProp('beginPath', genNoArgMethod);
-    argsByProp('bezierCurveTo', genMethodAlt(genNumbers(6)));
-    argsByProp('clearRect', argsByProp('fillRect', argsByProp('strokeRect',
-        genMethodAlt(genNumbers(4)))));
-    argsByProp('clip', genNoArgMethod); // TODO(kpreid): Path obj
-    argsByProp('closePath', genNoArgMethod);
-    argsByProp('createImageData', imageDataResult(genMethodAlt(G.value(
-        // restricting size in order to avoid large pixel arrays
-        [2, 2], [-1, 2], [1, NaN]))));
- argsByProp('createLinearGradient', genMethodAlt(G.value([0, 1, 2, 3])));
-    argsByProp('createRadialGradient', genMethodAlt(
-        G.value([0, 1, 2, 3, 4, 5])));
-    argsByProp('createPattern', genMethodAlt(G.value([null, null])));
+    argsByIdentity(canvas2DProto.arc, genMethodAlt(genNumbers(5)));
+    argsByIdentity(canvas2DProto.arcTo, genMethodAlt(genNumbers(5)));
+    argsByIdentity(canvas2DProto.beginPath, genNoArgMethod);
+ argsByIdentity(canvas2DProto.bezierCurveTo, genMethodAlt(genNumbers(6)));
+    argsByIdentity(canvas2DProto.clearRect,
+        argsByIdentity(canvas2DProto.fillRect,
+            argsByIdentity(canvas2DProto.strokeRect,
+                genMethodAlt(genNumbers(4)))));
+    argsByIdentity(canvas2DProto.clip, genNoArgMethod);
+        // TODO(kpreid): Path obj
+    argsByIdentity(canvas2DProto.closePath, genNoArgMethod);
+    argsByIdentity(canvas2DProto.createImageData,
+        imageDataResult(genMethodAlt(G.value(
+            // restricting size in order to avoid large pixel arrays
+            [2, 2], [-1, 2], [1, NaN]))));
+    argsByIdentity(canvas2DProto.createLinearGradient,
+        genMethodAlt(G.value([0, 1, 2, 3])));
+    argsByIdentity(canvas2DProto.createRadialGradient,
+        genMethodAlt(G.value([0, 1, 2, 3, 4, 5])));
+    argsByIdentity(canvas2DProto.createPattern,
+        genMethodAlt(G.value([null, null])));
// TODO(kpreid): args for createPattern (not implemented yet though)
-    argsByProp('drawImage', genMethodAlt(G.none)); // TODO(kpreid): unstub
-    argsByProp('ellipse', genMethodAlt(genNumbers(7)));
-    argsByProp('fill', genNoArgMethod); // TODO(kpreid): Path obj
-    argsByProp('fillText', argsByProp('strokeText',
+    argsByIdentity(canvas2DProto.drawImage, genMethodAlt(G.none));
+        // TODO(kpreid): unstub
+    argsByIdentity(canvas2DProto.ellipse, genMethodAlt(genNumbers(7)));
+    argsByIdentity(canvas2DProto.fill, genNoArgMethod);
+        // TODO(kpreid): Path obj
+    argsByIdentity(canvas2DProto.fillText, argsByProp('strokeText',
         genMethodAlt(genConcat(G.tuple(genString), genNumbers(3)))));
-    argsByProp('getImageData', imageDataResult(genMethodAlt(genConcat(
-        // restricting size in order to avoid large pixel arrays
-        genNumbers(2), G.value([2, 2])))));
-    argsByProp('lineTo', genMethodAlt(genNumbers(2)));
-    argsByProp('measureText', genMethod(genString));
-    argsByProp('moveTo', genMethodAlt(genNumbers(2)));
-    argsByProp('quadraticCurveTo', genMethodAlt(genNumbers(4)));
-    argsByProp('rect', genMethodAlt(genNumbers(4)));
-    argsByProp('save', genNoArgMethod);
-    argsByProp('scale', genMethodAlt(genNumbers(2)));
-    argsByProp('stroke', genNoArgMethod); // TODO(kpreid): Path obj
-    argsByProp('restore', genNoArgMethod);
-    argsByProp('rotate', genMethodAlt(genNumbers(2)));
-    argsByProp('isPointInPath', genMethodAlt(genNumbers(2)));
+    argsByIdentity(canvas2DProto.getImageData,
+        imageDataResult(genMethodAlt(genConcat(
+            // restricting size in order to avoid large pixel arrays
+              genNumbers(2), G.value([2, 2])))));
+    argsByIdentity(canvas2DProto.lineTo, genMethodAlt(genNumbers(2)));
+    argsByIdentity(canvas2DProto.measureText, genMethod(genString));
+    argsByIdentity(canvas2DProto.moveTo, genMethodAlt(genNumbers(2)));
+ argsByIdentity(canvas2DProto.quadraticCurveTo, genMethodAlt(genNumbers(4)));
+    argsByIdentity(canvas2DProto.rect, genMethodAlt(genNumbers(4)));
+    argsByIdentity(canvas2DProto.save, genNoArgMethod);
+    argsByIdentity(canvas2DProto.scale, genMethodAlt(genNumbers(2)));
+    argsByIdentity(canvas2DProto.stroke, genNoArgMethod);
         // TODO(kpreid): Path obj
- argsByProp('putImageData', genMethodAlt(G.none)); // TODO(kpreid): unstub
-    argsByProp('setTransform', genMethodAlt(genNumbers(6)));
-    argsByProp('translate', genMethodAlt(genNumbers(2)));
-    argsByProp('transform', genMethodAlt(genNumbers(6)));
-    argsByProp('addColorStop', genMethod(genSmallInteger, genCSSColor));
+    argsByIdentity(canvas2DProto.restore, genNoArgMethod);
+    argsByIdentity(canvas2DProto.rotate, genMethodAlt(genNumbers(2)));
+ argsByIdentity(canvas2DProto.isPointInPath, genMethodAlt(genNumbers(2)));
+        // TODO(kpreid): Path obj
+    argsByIdentity(canvas2DProto.putImageData, genMethodAlt(G.none));
+        // TODO(kpreid): unstub
+ argsByIdentity(canvas2DProto.setTransform, genMethodAlt(genNumbers(6)));
+    argsByIdentity(canvas2DProto.translate, genMethodAlt(genNumbers(2)));
+    argsByIdentity(canvas2DProto.transform, genMethodAlt(genNumbers(6)));
+    argsByIdentity(CanvasGradient.prototype.addColorStop,
+          genMethod(genSmallInteger, genCSSColor));
     argsByProp('_d_canvas_writeback', G.none);  // TODO(kpreid): hide

     // Event methods
@@ -1583,6 +1597,11 @@
     obtainInstance.define(ArrayLike, ArrayLike(
       Object.create(ArrayLike.prototype),
       function() { return 100; }, function(i) { return i; }));
+    obtainInstance.define(CanvasRenderingContext2D,
+        document.createElement('canvas').getContext('2d'));
+    obtainInstance.define(CanvasGradient,
+ document.createElement('canvas').getContext('2d').createLinearGradient(
+            0, 1, 2, 3));

     var output = document.getElementById('testUniverse-report');
     function write(var_args) {
@@ -1654,10 +1673,10 @@
       if (problemCount === 0 && gapCount === 0) {
         pass('testUniverse');
       } else {
+        toggleNonErrors(true);
         jsunitFail('testUniverse',
             'Failed: ' + problemCount + ' problems and ' + gapCount +
             ' coverage gaps.');
-        toggleNonErrors(true);
       }
     }
   };
@@ -1688,7 +1707,7 @@
   var hideCheckbox;
   function toggleNonErrors(opt_state) {
     var el = document.getElementById('testUniverse-report');
-    var hide = opt_state !== undefined ? opt_state : !hideCheckbox.checked;
+    var hide = opt_state !== undefined ? opt_state : hideCheckbox.checked;
     if (hide) {
       el.className = el.className + ' scan-hide-non-errors';
     } else {
@@ -1696,7 +1715,7 @@
     }
     hideCheckbox.checked = hide;
   }
-  document.addEventListener('load', function() {
+  document.addEventListener('DOMContentLoaded', function() {
     hideCheckbox = document.getElementById('hide-non-errors-checkbox');
     hideCheckbox.onclick = function() { toggleNonErrors(); };
   }, false);

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to