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.