Revision: 5450
Author: [email protected]
Date: Mon Jun 17 12:21:19 2013
Log: Lazily construct taming classes in Domado.
https://codereview.appspot.com/10240043
Defer the construction of taming ctors and their prototypes until an
instance is going to be created, or the inert ctor global (e.g.
window.Element) is accessed.
This reduces the startup time of Domado, and eliminates nearly all of
the time cost of supporting features which are not used by the guest
content (e.g. XMLHttpRequest or <canvas>).
This is also part of the refactoring-Domado project: the lazy loading
enforces a lack of imperative dependencies between individual taming
classes.
Certain fundamental classes are not made lazy, as they would always
be loaded and disentangling them would be additional work, such as
Node, Element, Window, and Document. I expect to move these to the
same lazy infrastructure as part of future refactoring.
Supporting changes:
* Events created by document.createEvent() are no longer a distinct
type; the notYetDispatched flag turns out to be exactly sufficient
(and secure) for the distinctions we need to make.
* Newly visible-to-the-guest types:
CSSStyleDeclaration (old laziness kludge didn't allow exporting)
ImageData (now uses prototype inheritance)
CajaComputedCSSStyleDeclaration (implementation detail)
CajaFormField (implementation detail)
* Added a REPL which evaluates inside the sandbox, for debugging
(linked from test-index.html).
* TameImageData now uses prototype inheritance.
* Fixed scanner's check for taming ctors leaking having been broken
by the move to a single SES frame.
* Scanner is aware of newly exported CSSStyleDeclaration
Performance notes:
Measuring using console.time() in Chrome, approximately 130 ms is
removed from the run time of attachDocument; this figure is adjusted
for the cost of lazy loading that always happens, namely Event for
onload events.
Test run time changes (Firefox; one run, so very noisy). domado-global
particularly benefits because all it does is start many trivial guests.
guest-domado-global-es53-min 15.956/26.862 = 0.59
guest-domado-global-es5-min 3.519 /4.684 = 0.75
cajajs-invocation-nomin-es53 28.556/24.015 = 1.18
cajajs-invocation-nomin-es5 6.252 /6.298 = 0.99
cajajs-invocation-min-es53 24.473/22.089 = 1.10
cajajs-invocation-min-es5 6.656 /5.245 = 1.26
guest-domado-dom-es53-min 12.551/13.500 = 0.92
guest-domado-dom-es5-min 5.339 /5.117 = 1.04
[email protected]
http://code.google.com/p/google-caja/source/detail?r=5450
Added:
/trunk/tests/com/google/caja/plugin/repl.html
Modified:
/trunk/src/com/google/caja/plugin/domado.js
/trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js
/trunk/tests/com/google/caja/plugin/test-index.html
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/plugin/repl.html Mon Jun 17 12:21:19 2013
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>Caja in-the-sandbox</title>
+<div id="dummy" style="display: none;"></div>
+<script>
+ jsunitRegister('dummy', function() { pass(); });
+</script><pre id="out">
+
+</pre>
+<form onsubmit="
+ (function() {
+ var i = document.getElementById('in');
+ var text = i.value;
+ var o = document.getElementById('out');
+ var r;
+ try {
+ r = String(cajaVM.compileExpr(text)(window));
+ } catch (e) {
+ r = String(e);
+ }
+ i.select();
+ o.appendChild(document.createTextNode('\n> ' + text + '\n' + r
+ '\n'));
+ }());
+ return false;
+">
+ <code>
+ > <input
+ type="text" id="in" style="width: 90%; font-family: monospace;">
+ </code>
+</form>
=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Thu Jun 13 15:57:55 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Mon Jun 17 12:21:19 2013
@@ -1195,14 +1195,48 @@
}
function TamingClassTable() {
+ var self = this;
+
var hop = Object.prototype.hasOwnProperty;
- // would be Object.create(null) but not supported by ES5/3
- var tamingCtors = {};
- var safeCtors = {};
+ // TODO(kpreid): When ES5/3 and the relevant Chrome bug are dead, make
this
+ // Object.create(null).
+ var thunks = {};
var prototypeNames = new WeakMap();
+ /**
+ * Register a lazy-initialized taming ctor. The builder function should
+ * return the taming ctor.
+ */
+ this.registerLazy = function registerLazy(name, builder) {
+ if (Object.prototype.hasOwnProperty.call(thunks, name)) {
+ throw new Error('TamingClassTable: duplicate registration of ' +
name);
+ }
+ var result = undefined;
+ thunks[name] = function tamingClassMemoThunk() {
+ if (!result) {
+ var tamingCtor = builder();
+ var inert = tamingCtor.prototype.constructor;
+ // TODO(kpreid): Validate that the inert ctor is in fact inert
+ result = {
+ tamingCtor: tamingCtor, // special applications need lack of
def
+ // here
+ guestCtor: cajaVM.def(inert)
+ };
+
+ // Registering multiple names is allowed. However, if we ever
stop
+ // having multiple names (HTMLElement vs. Element, HTMLDocument
vs.
+ // Document), which would be technically correct, this should
become a
+ // failure case.
+ if (!prototypeNames.has(inert.prototype)) {
+ prototypeNames.set(inert.prototype, name);
+ }
+ }
+ return result;
+ };
+ };
+
/**
* This does three things:
*
@@ -1213,6 +1247,7 @@
* checks).
*
* Register the inert ctor under the given name if not undefined.
+ * TODO(kpreid): Review deprecating that entirely in favor of laziness.
*/
this.inertCtor = function inertCtor(
tamingCtor, someSuper, opt_name, opt_writableProto) {
@@ -1233,54 +1268,60 @@
});
if (opt_name !== undefined) {
- setFinal(tamingCtors, opt_name, tamingCtor);
- setFinal(safeCtors, opt_name, inert);
- prototypeNames.set(inert.prototype, opt_name);
+ self.registerLazy(opt_name, function() { return tamingCtor; });
}
return inert;
};
+ // TODO(kpreid): Remove uses of this -- all for the wrong multiple
names.
this.registerSafeCtor = function registerSafeCtor(name, safeCtor) {
- setFinal(tamingCtors, name, undefined); // prohibit inconsistency
- setFinal(safeCtors, name, safeCtor);
- // Registering multiple names is allowed. However, if we ever stop
having
- // multiple names (HTMLElement vs. Element, HTMLDocument vs.
Document),
- // which would be technically correct, this should become a failure
case.
- if (!prototypeNames.has(safeCtor.prototype)) {
- prototypeNames.set(safeCtor.prototype, name);
- }
+ self.registerLazy(name, function() {
+ function stubTamingCtor() {
+ throw new Error('Should not have been called');
+ }
+ stubTamingCtor.prototype = { constructor: safeCtor };
+ return stubTamingCtor;
+ });
};
- this.defAllAndFinish = function defAllAndFinish() {
- for (var name in safeCtors) {
- var ctor = safeCtors[name];
- cajaVM.def(ctor);
- }
-
- // prohibit late additions
- Object.freeze(tamingCtors);
- Object.freeze(safeCtors);
+ this.finish = function finish() {
+ Object.freeze(thunks);
};
this.exportTo = function exportTo(imports) {
- if (Object.isExtensible(safeCtors)) {
+ if (Object.isExtensible(thunks)) {
throw new Error(
'TamingClassTable: exportTo called before defAllAndFinish');
}
- for (var name in safeCtors) {
- var ctor = safeCtors[name];
+ Object.getOwnPropertyNames(thunks).forEach(function(name) {
+ var thunk = thunks[name];
Object.defineProperty(imports, name, {
enumerable: true,
configurable: true,
- writable: true,
- value: ctor
+ get: cajaVM.constFunc(function tamingClassGetter() {
+ return thunk().guestCtor;
+ }),
+ set: cajaVM.constFunc(function tamingClassSetter(newValue) {
+ // This differs from the makeOverrideSetter setter in that it
allows
+ // override on the object itself, not just objects inheriting
this
+ // descriptor.
+ Object.defineProperty(this, name, {
+ value: newValue,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+ })
});
- }
+ });
};
this.getTamingCtor = function(name) {
- return hop.call(tamingCtors, name) ? tamingCtors[name] : undefined;
+ // TODO(kpreid): When we no longer need to support platforms without
+ // Object.create(null) (ES5/3, some Chrome), we should throw out this
+ // hasOwnProperty.
+ return hop.call(thunks, name) ? thunks[name]().tamingCtor :
undefined;
};
this.getNameOfPrototype = function(prototype) {
@@ -2705,9 +2746,9 @@
throw 'Internal: Attr nodes cannot be generically wrapped';
case 3: // Text
case 4: // CDATA Section Node
- return new TameTextNode(node);
+ return new (tamingClassTable.getTamingCtor('Text'))(node);
case 8: // Comment
- return new TameCommentNode(node);
+ return new (tamingClassTable.getTamingCtor('Comment'))(node);
case 9: // Document (not well supported)
return new TameBackedNode(node);
case 11: // Document Fragment
@@ -3869,27 +3910,31 @@
// Non-element node types:
- function TameTextNode(node) {
- assert(node.nodeType === 3);
- TameBackedNode.call(this, node);
- }
- inertCtor(TameTextNode, TameBackedNode, 'Text');
- var textAccessor = Props.actAs('nodeValue', PT.filterProp(identity,
- function(value) { return String(value || ''); }));
- Props.define(TameTextNode.prototype, TameNodeConf, {
- nodeValue: textAccessor,
- textContent: textAccessor,
- innerText: textAccessor,
- data: textAccessor
+ tamingClassTable.registerLazy('Text', function() {
+ function TameTextNode(node) {
+ assert(node.nodeType === 3);
+ TameBackedNode.call(this, node);
+ }
+ inertCtor(TameTextNode, TameBackedNode);
+ var textAccessor = Props.actAs('nodeValue', PT.filterProp(identity,
+ function(value) { return String(value || ''); }));
+ Props.define(TameTextNode.prototype, TameNodeConf, {
+ nodeValue: textAccessor,
+ textContent: textAccessor,
+ innerText: textAccessor,
+ data: textAccessor
+ });
+ return cajaVM.def(TameTextNode); // and defend its prototype
});
- cajaVM.def(TameTextNode); // and its prototype
- function TameCommentNode(node) {
- assert(node.nodeType === 8);
- TameBackedNode.call(this, node);
- }
- inertCtor(TameCommentNode, TameBackedNode, 'Comment');
- cajaVM.def(TameCommentNode); // and its prototype
+ tamingClassTable.registerLazy('Comment', function() {
+ function TameCommentNode(node) {
+ assert(node.nodeType === 8);
+ TameBackedNode.call(this, node);
+ }
+ inertCtor(TameCommentNode, TameBackedNode);
+ return cajaVM.def(TameCommentNode); // and defend its prototype
+ });
// Note that our tame attribute nodes bake in the notion of what
element
// they belong to (in order to implement virtualization policy).
@@ -3907,90 +3952,91 @@
} else if (taming.hasTameTwin(node)) {
return taming.tame(node);
} else {
- var self = new TameBackedAttributeNode(node, ownerElement);
+ var self = new (tamingClassTable.getTamingCtor('Attr'))(
+ node, ownerElement);
taming.tamesTo(node, self);
return self;
}
}
- /**
- * Plays the role of an Attr node for TameElement objects.
- */
- function TameBackedAttributeNode(node, ownerElement) {
- node = makeDOMAccessible(node);
- if ('ownerElement' in node && node.ownerElement !== ownerElement) {
- throw new Error('Inconsistent ownerElement');
- }
+ tamingClassTable.registerLazy('Attr', function() {
+ function TameBackedAttributeNode(node, ownerElement) {
+ node = makeDOMAccessible(node);
+ if ('ownerElement' in node && node.ownerElement !==
ownerElement) {
+ throw new Error('Inconsistent ownerElement');
+ }
- var ownerPolicy = nodeAmplify(defaultTameNode(ownerElement),
- function(ownerPriv) {
- return ownerPriv.policy;
- });
+ var ownerPolicy = nodeAmplify(defaultTameNode(ownerElement),
+ function(ownerPriv) {
+ return ownerPriv.policy;
+ });
- TameBackedNode.call(this, node, ownerPolicy);
+ TameBackedNode.call(this, node, ownerPolicy);
- nodeAmplify(this, function(privates) {
- privates.ownerElement = ownerElement;
- });
- }
- inertCtor(TameBackedAttributeNode, TameBackedNode, 'Attr');
- var nameAccessor = PT.ROView(function(name) {
- if (cajaPrefRe.test(name)) {
- name = name.substring(cajaPrefix.length);
+ nodeAmplify(this, function(privates) {
+ privates.ownerElement = ownerElement;
+ });
}
- return name;
- });
- var valueAccessor = {
- enumerable: true,
- get: innocuous(function() {
- return this.ownerElement.getAttribute(this.name);
- }),
- set: innocuous(function(value) {
- return this.ownerElement.setAttribute(this.name, value);
- })
- };
- var notImplementedNodeMethod = {
- enumerable: true,
- value: innocuous(function() {
- throw new Error('Not implemented.');
- })
- };
- Props.define(TameBackedAttributeNode.prototype, TameNodeConf, {
- nodeName: nameAccessor,
- name: nameAccessor,
- specified: {
+ inertCtor(TameBackedAttributeNode, TameBackedNode);
+ var nameAccessor = PT.ROView(function(name) {
+ if (cajaPrefRe.test(name)) {
+ name = name.substring(cajaPrefix.length);
+ }
+ return name;
+ });
+ var valueAccessor = {
enumerable: true,
get: innocuous(function() {
- return this.ownerElement.hasAttribute(this.name);
+ return this.ownerElement.getAttribute(this.name);
+ }),
+ set: innocuous(function(value) {
+ return this.ownerElement.setAttribute(this.name, value);
})
- },
- nodeValue: valueAccessor,
- value: valueAccessor,
- ownerElement: {
+ };
+ var notImplementedNodeMethod = {
enumerable: true,
- get: nodeAmp(function(privates) {
- return defaultTameNode(privates.ownerElement);
+ value: innocuous(function() {
+ throw new Error('Not implemented.');
})
- },
- nodeType: P_constant(2),
- firstChild: P_UNIMPLEMENTED,
- lastChild: P_UNIMPLEMENTED,
- nextSibling: P_UNIMPLEMENTED,
- previousSibling: P_UNIMPLEMENTED,
- parentNode: P_UNIMPLEMENTED,
- childNodes: P_UNIMPLEMENTED,
- attributes: P_UNIMPLEMENTED,
- cloneNode: Props.ampMethod(function(privates, deep) {
- var clone = bridal.cloneNode(privates.feral, Boolean(deep));
- // From
http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4
- // "Note that cloning an immutable subtree results in a
mutable copy"
- return tameAttributeNode(clone, privates.ownerElement);
- }),
- appendChild: notImplementedNodeMethod,
- insertBefore: notImplementedNodeMethod,
- removeChild: notImplementedNodeMethod,
- replaceChild: notImplementedNodeMethod
+ };
+ Props.define(TameBackedAttributeNode.prototype, TameNodeConf, {
+ nodeName: nameAccessor,
+ name: nameAccessor,
+ specified: {
+ enumerable: true,
+ get: innocuous(function() {
+ return this.ownerElement.hasAttribute(this.name);
+ })
+ },
+ nodeValue: valueAccessor,
+ value: valueAccessor,
+ ownerElement: {
+ enumerable: true,
+ get: nodeAmp(function(privates) {
+ return defaultTameNode(privates.ownerElement);
+ })
+ },
+ nodeType: P_constant(2),
+ firstChild: P_UNIMPLEMENTED,
+ lastChild: P_UNIMPLEMENTED,
+ nextSibling: P_UNIMPLEMENTED,
+ previousSibling: P_UNIMPLEMENTED,
+ parentNode: P_UNIMPLEMENTED,
+ childNodes: P_UNIMPLEMENTED,
+ attributes: P_UNIMPLEMENTED,
+ cloneNode: Props.ampMethod(function(privates, deep) {
+ var clone = bridal.cloneNode(privates.feral, Boolean(deep));
+ // From
http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4
+ // "Note that cloning an immutable subtree results in a
mutable
+ // copy"
+ return tameAttributeNode(clone, privates.ownerElement);
+ }),
+ appendChild: notImplementedNodeMethod,
+ insertBefore: notImplementedNodeMethod,
+ removeChild: notImplementedNodeMethod,
+ replaceChild: notImplementedNodeMethod
+ });
+ return cajaVM.def(TameBackedAttributeNode); // and defend its
prototype
});
- cajaVM.def(TameBackedAttributeNode); // and its prototype
// Register set handlers for onclick, onmouseover, etc.
function
registerElementScriptAttributeHandlers(tameElementPrototype) {
@@ -4030,6 +4076,9 @@
// Elements in general and specific elements:
+ // TODO(kpreid): See if it's feasible to make TameElement lazy
constructed
+ // for guests that don't do any DOM manipulation until after load.
Will
+ // require deferring the tameContainerNode.
/**
* @constructor
*/
@@ -4133,9 +4182,8 @@
style: {
enumerable: true,
get: nodeAmp(function(privates) {
- TameStyle || buildTameStyle();
- return new TameStyle(privates.feral.style,
privates.policy.editable,
- this);
+ return new
(tamingClassTable.getTamingCtor('CSSStyleDeclaration'))(
+ privates.feral.style, privates.policy.editable, this);
}),
set: innocuous(function(value) {
this.setAttribute('style', value);
@@ -4349,14 +4397,16 @@
/**
* Define a taming class for a subclass of HTMLElement.
*
- * @param {Array} record.superclass The tame superclass constructor
- * (defaults to TameElement) with parameters (this, node, policy,
- * opt_proxyType).
+ * @param {function|string} record.superclass The tame superclass
+ * constructor (defaults to TameElement) with parameters (this,
node,
+ * policy, opt_proxyType). May be specified as a string which
will be
+ * looked up in the tamingClassTable.
* @param {Array} record.names The element names which should be
tamed
* using this class.
- * @param {String} record.domClass The DOM-specified class name.
- * @param {Object} record.properties The custom properties this class
- * should have (in the format accepted by Props.define).
+ * @param {string} record.domClass The DOM-specified class name.
+ * @param {function} record.properties A function returning the
custom
+ * properties this class should have (in the format accepted by
+ * Props.define). (Is a function for laziness.)
* @param {function} record.construct Code to invoke at the end of
* construction; takes and returns self.
* @param {?boolean} record.virtualized Whether it should be
expected that
@@ -4364,35 +4414,46 @@
* If null, no restriction.
* @param {boolean} record.forceChildrenNotEditable Whether to force
the
* child node list and child nodes to not be mutable.
- * @return {function} The constructor.
*/
function defineElement(record) {
- var superclass = record.superclass || TameElement;
+ var domClass = record.domClass;
+ if (!domClass) {
+ throw new Error('Anonymous element classes are useless');
+ }
+ var superclassRef = record.superclass || 'HTMLElement';
var proxyType = record.proxyType;
var construct = record.construct || identity;
var shouldBeVirtualized = "virtualized" in record
? record.virtualized : false;
var opt_policy = record.forceChildrenNotEditable
? nodePolicyReadOnlyChildren : null;
- function TameSpecificElement(node) {
- if (shouldBeVirtualized !== null) {
- var isVirtualized =
- htmlSchema.isVirtualizedElementName(node.tagName);
- if (!isVirtualized !== !shouldBeVirtualized) {
- throw new Error('Domado internal inconsistency: ' +
node.tagName +
- ' has inconsistent virtualization state with class ' +
- record.domClass);
+ function defineElementThunk() {
+ var superclass = typeof superclassRef === 'string'
+ ? tamingClassTable.getTamingCtor(superclassRef)
+ : superclassRef;
+ function TameSpecificElement(node) {
+ if (shouldBeVirtualized !== null) {
+ var isVirtualized =
+ htmlSchema.isVirtualizedElementName(node.tagName);
+ if (!isVirtualized !== !shouldBeVirtualized) {
+ throw new Error('Domado internal inconsistency: ' +
node.tagName
+ + ' has inconsistent virtualization state with class '
+
+ record.domClass);
+ }
}
+ superclass.call(this, node, opt_policy, proxyType);
+ construct.call(this);
}
- superclass.call(this, node, opt_policy, proxyType);
- construct.call(this);
+ inertCtor(TameSpecificElement, superclass);
+ if (record.properties) {
+ Props.define(TameSpecificElement.prototype, TameNodeConf,
+ (0,record.properties)());
+ }
+ // Note: cajaVM.def will be applied to all registered node
classes
+ // later, so users of defineElement don't need to.
+ return cajaVM.def(TameSpecificElement);
}
- inertCtor(TameSpecificElement, superclass, record.domClass);
- Props.define(TameSpecificElement.prototype, TameNodeConf,
- record.properties || {});
- // Note: cajaVM.def will be applied to all registered node classes
- // later, so users of defineElement don't need to.
- return TameSpecificElement;
+ tamingClassTable.registerLazy(domClass, defineElementThunk);
}
cajaVM.def(defineElement);
@@ -4402,12 +4463,12 @@
* warning).
*/
function defineTrivialElement(domClass) {
- return defineElement({domClass: domClass});
+ defineElement({domClass: domClass});
}
defineElement({
domClass: 'HTMLAnchorElement',
- properties: {
+ properties: function() { return {
hash: PT.filter(
false,
function (value) { return unsuffix(value, idSuffix, value); },
@@ -4416,15 +4477,15 @@
identity),
// TODO(felix8a): fragment rewriting?
href: PT.filter(false, identity, true, identity)
- }
+ }; }
});
defineTrivialElement('HTMLBRElement');
- var TameBodyElement = defineElement({
+ defineElement({
virtualized: true,
domClass: 'HTMLBodyElement',
- properties: {
+ properties: function() { return {
setAttribute: Props.overridable(true, innocuous(
function(attrib, value) {
TameElement.prototype.setAttribute.call(this, attrib, value);
@@ -4466,7 +4527,7 @@
}
}
}))
- }
+ }; }
});
// http://dev.w3.org/html5/spec/Overview.html#the-canvas-element
@@ -4565,110 +4626,113 @@
// only as #hhhhhh, not as names.
return colorNameTable[" " + colorString] || colorString;
}
- function TameImageData(imageData) {
- imageData = makeDOMAccessible(imageData);
- var p = function() { throw 'obsolete'; };
+
+ tamingClassTable.registerLazy('ImageData', function() {
+ function TameImageData(imageData) {
+ imageData = makeDOMAccessible(imageData);
- // Since we can't interpose indexing, we can't wrap the
- // CanvasPixelArray
- // so we have to copy the pixel data. This is horrible, bad, and
- // awful.
- // TODO(kpreid): No longer true in ES5-land; we can interpose
but not
- // under ES5/3. Use proxies conditional on the same switch that
- // controls
- // liveness of node lists.
- var tameImageData = {
- toString: innocuous(function() {
- return '[domado object ImageData]';
- }),
- width: Number(imageData.width),
- height: Number(imageData.height)
- };
- TameImageDataConf.confide(tameImageData, taming);
- taming.permitUntaming(tameImageData);
+ // Since we can't interpose indexing, we can't wrap the
+ // CanvasPixelArray
+ // so we have to copy the pixel data. This is horrible, bad,
and
+ // awful.
+ // TODO(kpreid): No longer true in ES5-land; we can interpose
but
+ // not under ES5/3. Use proxies conditional on the same switch
that
+ // controls liveness of node lists.
- TameImageDataConf.amplify(tameImageData, function(privates) {
- // used to unwrap for passing to putImageData
- privates.feral = imageData;
+ TameImageDataConf.confide(this, taming);
+ taming.permitUntaming(this);
- // lazily constructed tame copy, backs .data accessor; also
used to
- // test whether we need to write-back the copy before a
putImageData
- privates.tamePixelArray = undefined;
+ this.width = Number(imageData.width);
+ this.height = Number(imageData.height);
- Props.define(tameImageData, TameNodeConf, {
- data: {
- enumerable: true,
- // Accessor used so we don't need to copy if the client is
just
- // blitting (getImageData -> putImageData) rather than
- // inspecting the pixels.
- get: cajaVM.constFunc(function() {
- if (!privates.tamePixelArray) {
+ TameImageDataConf.amplify(this, function(privates) {
+ // used to unwrap for passing to putImageData
+ privates.feral = imageData;
- var bareArray = imageData.data;
- // Note: On Firefox 4.0.1, at least, pixel arrays
cannot
- // have added properties (such as our w___).
Therefore, for
- // writing,
- // we use a special routine, and we don't do
- // makeDOMAccessible
- // because it would have no effect. An alternative
approach
- // would be to muck with the "Uint8ClampedArray"
prototype.
+ // lazily constructed tame copy, backs .data accessor; also
used
+ // to test whether we need to write-back the copy before a
+ // putImageData
+ privates.tamePixelArray = undefined;
- var length = bareArray.length;
- var tamePixelArray = { // not frozen, user-modifiable
- // TODO: Investigate whether it would be an
optimization
- // to make this an array with properties added.
- toString: innocuous(function() {
- return '[domado object CanvasPixelArray]';
- }),
- _d_canvas_writeback: innocuous(function() {
- // This is invoked just before each putImageData
+ Object.preventExtensions(privates);
+ });
+ Object.freeze(this);
+ }
+ inertCtor(TameImageData, Object);
+ Props.define(TameImageData.prototype, TameImageDataConf, {
+ toString: Props.overridable(false, innocuous(function() {
+ return '[domado object ImageData]';
+ })),
+ // Accessor used so we don't need to copy if the client is
+ // just blitting (getImageData -> putImageData) rather than
+ // inspecting the pixels.
+ data: Props.ampGetter(function(privates) {
+ if (!privates.tamePixelArray) {
- // TODO(kpreid): shouldn't be a public method (but
is
- // harmless).
+ var bareArray = privates.feral.data;
+ // Note: On Firefox 4.0.1, at least, pixel arrays cannot
+ // have added properties (such as our w___). Therefore,
+ // for writing, we use a special routine, and we don't do
+ // makeDOMAccessible because it would have no effect. An
+ // alternative approach would be to muck with the
+ // "Uint8ClampedArray" prototype.
- rulebreaker.writeToPixelArray(
- tamePixelArray, bareArray, length);
- })
- };
- for (var i = length-1; i >= 0; i--) {
- tamePixelArray[+i] = bareArray[+i];
- }
- privates.tamePixelArray = tamePixelArray;
- }
- return privates.tamePixelArray;
- })
+ var length = bareArray.length;
+ var tamePixelArray = { // not frozen, user-modifiable
+ // TODO: Investigate whether it would be an optimization
+ // to make this an array with properties added.
+ toString: innocuous(function() {
+ return '[domado object CanvasPixelArray]';
+ }),
+ _d_canvas_writeback: innocuous(function() {
+ // This is invoked just before each putImageData
+
+ // TODO(kpreid): shouldn't be a public method (but is
+ // harmless).
+
+ rulebreaker.writeToPixelArray(
+ tamePixelArray, bareArray, length);
+ })
+ };
+ for (var i = length-1; i >= 0; i--) {
+ tamePixelArray[+i] = bareArray[+i];
+ }
+ privates.tamePixelArray = tamePixelArray;
}
- });
+ return privates.tamePixelArray;
+ })
+ });
+ return cajaVM.def(TameImageData);
+ });
- Object.preventExtensions(privates);
+ tamingClassTable.registerLazy('CanvasGradient', function() {
+ function TameGradient(gradient) {
+ TameGradientConf.confide(this, taming);
+ TameGradientConf.amplify(this, function(privates) {
+ privates.feral = makeDOMAccessible(gradient);
+ });
+ taming.tamesTo(gradient, this);
+ Object.freeze(this);
+ }
+ inertCtor(TameGradient, Object);
+ 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);
+ })
});
- return Object.freeze(tameImageData);
- }
- function TameGradient(gradient) {
- 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);
- })
+ return cajaVM.def(TameGradient);
});
function enforceFinite(value, name) {
@@ -4799,297 +4863,308 @@
});
}
- 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',
- 'actualBoundingBoxLeft',
- 'actualBoundingBoxRight',
- 'alphabeticBaseline',
- 'emHeightAscent',
- 'emHeightDescent',
- 'fontBoundingBoxAscent',
- 'fontBoundingBoxDescent',
- 'hangingBaseline',
- 'ideographicBaseline',
- 'width'
- ].forEach(function(prop) {
- this[prop] = +feralMetrics[prop];
- }, this);
- Object.freeze(this);
- }
- inertCtor(TameTextMetrics, Object, 'TextMetrics');
+ tamingClassTable.registerLazy('TextMetrics', function() {
+ 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',
+ 'actualBoundingBoxLeft',
+ 'actualBoundingBoxRight',
+ 'alphabeticBaseline',
+ 'emHeightAscent',
+ 'emHeightDescent',
+ 'fontBoundingBoxAscent',
+ 'fontBoundingBoxDescent',
+ 'hangingBaseline',
+ 'ideographicBaseline',
+ 'width'
+ ].forEach(function(prop) {
+ this[prop] = +feralMetrics[prop];
+ }, this);
+ Object.freeze(this);
+ }
+ inertCtor(TameTextMetrics, Object);
+ return cajaVM.def(TameTextMetrics);
+ });
- // http://dev.w3.org/html5/2dcontext/
- //
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#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);
+ tamingClassTable.registerLazy('CanvasRenderingContext2D',
function() {
+ // http://dev.w3.org/html5/2dcontext/
+ //
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#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);
+ });
+ }
+ inertCtor(TameContext2D, Object);
+ // TODO(kpreid): have inertCtor automatically install an
appropriate
+ // toString method.
+ TameContext2D.prototype.toString = cajaVM.constFunc(function() {
+ return '[domado object CanvasRenderingContext2D]';
});
- }
- inertCtor(TameContext2D, Object, 'CanvasRenderingContext2D');
- // TODO(kpreid): have inertCtor automatically install an
appropriate
- // toString method.
- TameContext2D.prototype.toString = cajaVM.constFunc(function() {
- return '[domado object CanvasRenderingContext2D]';
- });
- Props.define(TameContext2D.prototype, TameContext2DConf, {
- save: tameNoArgOp,
- restore: tameNoArgOp,
+ Props.define(TameContext2D.prototype, TameContext2DConf, {
+ save: tameNoArgOp,
+ restore: tameNoArgOp,
- scale: tameFloatsOp(2),
- rotate: tameFloatsOp(1),
- translate: tameFloatsOp(2),
- transform: tameFloatsOp(6),
- setTransform: tameFloatsOp(6),
- // TODO(kpreid): whatwg has resetTransform
+ scale: tameFloatsOp(2),
+ rotate: tameFloatsOp(1),
+ translate: tameFloatsOp(2),
+ transform: tameFloatsOp(6),
+ setTransform: tameFloatsOp(6),
+ // TODO(kpreid): whatwg has resetTransform
- 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));
- }),
+ 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
(tamingClassTable.getTamingCtor('CanvasGradient'))(
+ privates.feral.createLinearGradient(x0, y0, x1, y1));
+ }),
- 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));
- }),
+ 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
(tamingClassTable.getTamingCtor('CanvasGradient'))(
+ privates.feral.createRadialGradient(x0, y0, r0, x1, y1,
r1));
+ }),
- 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');
- }),
+ 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');
+ }),
- clearRect: tameRectMethod(function() {}),
- fillRect: tameRectMethod(function() {}),
- strokeRect: tameRectMethod(function() {}),
+ clearRect: tameRectMethod(function() {}),
+ fillRect: tameRectMethod(function() {}),
+ strokeRect: tameRectMethod(function() {}),
- beginPath: tameNoArgOp,
- closePath: tameNoArgOp,
- moveTo: tameFloatsOp(2),
- lineTo: tameFloatsOp(2),
- quadraticCurveTo: tameFloatsOp(4),
- bezierCurveTo: tameFloatsOp(6),
- arcTo: tameFloatsOp(5),
- // TODO(kpreid): whatwg adds 2 optional args to arcTo
- 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');
- anticlockwise = anticlockwise || false;
- enforceType(anticlockwise, 'boolean', 'anticlockwise');
- privates.feral.arc(
- x, y, radius, startAngle, endAngle, anticlockwise);
- }),
+ beginPath: tameNoArgOp,
+ closePath: tameNoArgOp,
+ moveTo: tameFloatsOp(2),
+ lineTo: tameFloatsOp(2),
+ quadraticCurveTo: tameFloatsOp(4),
+ bezierCurveTo: tameFloatsOp(6),
+ arcTo: tameFloatsOp(5),
+ // TODO(kpreid): whatwg adds 2 optional args to arcTo
+ 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');
+ anticlockwise = anticlockwise || false;
+ enforceType(anticlockwise, 'boolean', 'anticlockwise');
+ privates.feral.arc(
+ x, y, radius, startAngle, endAngle, anticlockwise);
+ }),
- // TODO(kpreid): Path objects for filling/stroking
- fill: tameNoArgOp,
- stroke: tameNoArgOp,
- clip: tameNoArgOp,
+ // TODO(kpreid): Path objects for filling/stroking
+ fill: tameNoArgOp,
+ stroke: tameNoArgOp,
+ clip: tameNoArgOp,
- // TODO(kpreid): Generic type-checking wrapper to eliminate the
need
- // for this code
- // TODO(kpreid): implement spec'd optional args
- isPointInPath: tameMethodCustom(function(privates, x, y) {
- enforceType(x, 'number', 'x');
- enforceType(y, 'number', 'y');
- return enforceType(privates.feral.isPointInPath(x,
y), 'boolean');
- }),
+ // TODO(kpreid): Generic type-checking wrapper to eliminate
the need
+ // for this code
+ // TODO(kpreid): implement spec'd optional args
+ isPointInPath: tameMethodCustom(function(privates, x, y) {
+ enforceType(x, 'number', 'x');
+ enforceType(y, 'number', 'y');
+ return enforceType(privates.feral.isPointInPath(x,
y), 'boolean');
+ }),
- fillText: tameDrawText,
- strokeText: tameDrawText,
+ fillText: tameDrawText,
+ strokeText: tameDrawText,
- measureText: tameMethodCustom(function(privates, string) {
- enforceType(string, 'string', 'measureText argument');
- return new TameTextMetrics(privates.feral.measureText(string));
- }),
+ measureText: tameMethodCustom(function(privates, string) {
+ enforceType(string, 'string', 'measureText argument');
+ return new (tamingClassTable.getTamingCtor('TextMetrics'))(
+ privates.feral.measureText(string));
+ }),
- drawImage: tameMethodCustom(function(privates, imageElement) {
- // TODO(kpreid): Implement. Original concern was reading image
data,
- // but Caja's general policy is NOT to reimplement same-origin
- // restrictions.
- throw new Error('Domado: canvas drawImage not yet
implemented');
- }),
***The diff for this file has been truncated for email.***
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Tue May 28
14:58:38 2013
+++ /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Mon Jun 17
12:21:19 2013
@@ -24,7 +24,8 @@
* assertTrue, assertEquals, pass, jsunitFail,
* Event, HTMLInputElement, HTMLTableRowElement,
HTMLTableSectionElement,
* HTMLTableElement, Image, Option, XMLHttpRequest, Window, Document,
Node,
- * Attr, Text, CanvasRenderingContext2D, CanvasGradient
+ * Attr, Text, CSSStyleDeclaration, CanvasRenderingContext2D,
+ * CanvasGradient, ImageData, Location
* @overrides window
*/
@@ -660,7 +661,7 @@
}
}
// TODO(kpreid): factor out recognition
- if (typeof object === 'function' && frameOfObject === 'taming' &&
+ if (typeof object === 'function' &&
/function Tame(?!XMLHttpRequest|OptionFun|ImageFun)/
.test(object.toString())) {
noteProblem('Object is a taming ctor', context);
@@ -1047,13 +1048,17 @@
if (/^\[domado inert constructor(?:.*)\]$/.test(str)) {
// inert ctor -- should throw
return G.tuple(G.value(CONSTRUCT), G.tuple());
- } else if (/\.get \w+$/.test(path)) {
+ } else if (/(\.|^)get \w+$/.test(path)) {
// TODO(kpreid): Test invocation with an alternate this?
return G.value([THIS, []]);
- } else if (/\.set on\w+$/.test(path)) {
+ } else if (/(\.|^)set on\w+$/.test(path)) {
// Event handler accessor
return genEventHandlerSet;
- } else if (/\.set (\w+)$/.test(path)) {
+ } else if (name === 'tamingClassSetter') {
+ // Don't invoke these setters because they notably mangle the
global
+ // environment.
+ return G.none;
+ } else if (/(\.|^)set (\w+)$/.test(path)) {
var isLive = false;
var thisArg = context.getThisArg();
try { isLive = isLive || thisArg.parentNode; } catch (e) {}
@@ -1440,11 +1445,6 @@
// 2D context (and friends) methods
var canvas2DProto = CanvasRenderingContext2D.prototype;
- function imageDataResult(calls) {
- return annotate(calls, function(context, thrown) {
- expectedUnfrozen.setByIdentity(context.get().data, true);
- });
- }
argsByIdentity(canvas2DProto.arc, genMethodAlt(genNumbers(5)));
argsByIdentity(canvas2DProto.arcTo, genMethodAlt(genNumbers(5)));
argsByIdentity(canvas2DProto.beginPath, genNoArgMethod);
@@ -1456,10 +1456,9 @@
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.createImageData, 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,
@@ -1474,10 +1473,9 @@
// TODO(kpreid): Path obj
argsByIdentity(canvas2DProto.fillText, argsByProp('strokeText',
genMethodAlt(genConcat(G.tuple(genString), genNumbers(3)))));
- argsByIdentity(canvas2DProto.getImageData,
- imageDataResult(genMethodAlt(genConcat(
- // restricting size in order to avoid large pixel arrays
- genNumbers(2), G.value([2, 2])))));
+ argsByIdentity(canvas2DProto.getImageData, 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)));
@@ -1497,7 +1495,13 @@
argsByIdentity(canvas2DProto.translate, genMethodAlt(genNumbers(2)));
argsByIdentity(canvas2DProto.transform, genMethodAlt(genNumbers(6)));
argsByIdentity(CanvasGradient.prototype.addColorStop,
- genMethod(genSmallInteger, genCSSColor));
+ genMethod(genSmallInteger, genCSSColor));
+ argsByIdentity(
+ Object.getOwnPropertyDescriptor(ImageData.prototype, 'data').get,
+ annotate(G.value([THIS, []]), function(context, thrown) {
+ // Pixel arrays are not frozen
+ expectedUnfrozen.setByIdentity(context.get(), true);
+ }));
argsByProp('_d_canvas_writeback', G.none); // TODO(kpreid): hide
// Event methods
@@ -1593,12 +1597,15 @@
obtainInstance.define(Text, document.createTextNode('foo'));
obtainInstance.define(Document, document); // TODO(kpreid):
createDocument
obtainInstance.define(Window, window);
+ obtainInstance.define(Location, window.location);
obtainInstance.define(Event, document.createEvent('HTMLEvents'));
obtainInstance.define(Attr, (function() {
var el = document.createElement('span');
el.className = 'foo';
return el.attributes[0];
}()));
+ obtainInstance.define(CSSStyleDeclaration,
+ document.createElement('div').style);
obtainInstance.define(ArrayLike, ArrayLike(
Object.create(ArrayLike.prototype),
function() { return 100; }, function(i) { return i; }));
@@ -1607,6 +1614,9 @@
obtainInstance.define(CanvasGradient,
document.createElement('canvas').getContext('2d').createLinearGradient(
0, 1, 2, 3));
+ obtainInstance.define(ImageData,
+ document.createElement('canvas').getContext('2d').createImageData(
+ 2, 2));
var output = document.getElementById('testUniverse-report');
function write(var_args) {
=======================================
--- /trunk/tests/com/google/caja/plugin/test-index.html Wed Jun 12 12:11:32
2013
+++ /trunk/tests/com/google/caja/plugin/test-index.html Mon Jun 17 12:21:19
2013
@@ -64,6 +64,8 @@
Detailed SES initialization report
</a></li>
<li><a href="apidiff/analyzer.html">Browser API report/diff</a></li>
+ <li><a
href="browser-test-case.html?test-case=repl.html&es5=true">
+ REPL inside the sandbox</a></li>
</ul>
<script src="catalog-parser.js"></script>
<script src="test-index.js"></script>
--
---
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.