Revision: 5416
Author: [email protected]
Date: Tue May 21 13:19:02 2013
Log: Inherited methods for node lists; more distinct toString()s.
https://codereview.appspot.com/9517046
* Give node list types toString methods.
* Give proxy handlers toString methods.
* When rebuilding the node-list prototype chain, copy old prototype
properties over so that node list types can have inherited methods;
used for toString. Not yet used for item() because those close over
their data source.
[email protected]
http://code.google.com/p/google-caja/source/detail?r=5416
Modified:
/trunk/src/com/google/caja/plugin/domado.js
/trunk/src/com/google/caja/ses/startSES.js
/trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html
=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Mon May 20 13:49:53 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Tue May 21 13:19:02 2013
@@ -132,6 +132,7 @@
domitaModules.proxiesInterceptNumeric = domitaModules.proxiesAvailable &&
(function() {
var handler = {
+ toString: function() { return 'proxiesInterceptNumeric test
handler'; },
getOwnPropertyDescriptor: function(name) {
return {value: name === '1' ? 'ok' : 'other'};
}
@@ -602,6 +603,9 @@
ProxyHandler.call(this, target);
}
inherit(CollectionProxyHandler, ProxyHandler);
+ CollectionProxyHandler.prototype.toString = function() {
+ return '[CollectionProxyHandler]';
+ };
CollectionProxyHandler.prototype.getOwnPropertyDescriptor =
function (name) {
var lookup;
@@ -2829,12 +2833,41 @@
* @param opt_superCtor If provided, must be itself registered.
*/
function registerArrayLikeClass(constructor, opt_superCtor) {
+ inertCtor(constructor, opt_superCtor || cajaVM.makeArrayLike(0),
+ undefined, true);
+ var definedPrototype = constructor.prototype;
+ // Caller will install properties on this prototype.
+
function updater(ArrayLike) {
+ // Replace prototype with one inheriting from new ArrayLike.
inertCtor(constructor, opt_superCtor || ArrayLike, undefined,
true);
- Object.freeze(constructor.prototype);
+ var newPrototype = constructor.prototype;
+ // Copy properties from old prototype.
+ assert(Object.isFrozen(definedPrototype));
+
Object.getOwnPropertyNames(definedPrototype).forEach(function(name) {
+ if (name === 'constructor') { return; }
+ Object.defineProperty(newPrototype, name,
+ Object.getOwnPropertyDescriptor(definedPrototype, name));
+ });
+ // and defend.
+ cajaVM.def(newPrototype);
}
arrayLikeCtorUpdaters.push(updater);
}
+ function finishArrayLikeClass(constructor) {
+ // Cannot def() the constructor because its .prototype is
reassigned
+ // (and browsers don't let us make it an accessor), so do only the
+ // prototype.
+ // Cannot def() the prototype because some ArrayLike impls can't be
+ // frozen, so do its pieces (except for it's [[Prototype]]).
+ var proto = constructor.prototype;
+ cajaVM.tamperProof(proto);
+ Object.getOwnPropertyNames(proto).forEach(function(prop) {
+ if (prop !== 'constructor') {
+ cajaVM.def(proto[prop]);
+ }
+ });
+ }
function constructArrayLike(ctor, getItem, getLength) {
var len = +getLength();
var ArrayLike = cajaVM.makeArrayLike(len);
@@ -2861,7 +2894,10 @@
return result;
}
registerArrayLikeClass(TameNodeList);
- // not def'd - prototype is replaced
+ setOwn(TameNodeList.prototype, 'toString', cajaVM.def(function() {
+ return '[domado object NodeList]';
+ }));
+ finishArrayLikeClass(TameNodeList);
// NamedNodeMap is a NodeList + live string-named properties;
therefore we
// can't just use ArrayLike.
@@ -2897,6 +2933,9 @@
};
// TODO(kpreid): Reorder code so exporting the name works
inertCtor(TameNamedNodeMap, Object /*, 'NamedNodeMap' */);
+ setOwn(TameNamedNodeMap.prototype, 'toString',
cajaVM.def(function() {
+ return '[domado object NamedNodeMap]';
+ }));
cajaVM.def(TameNamedNodeMap);
var NamedNodeMapProxyHandler = function NamedNodeMapProxyHandler_(
feral, mapping, visibleList, target) {
@@ -2906,6 +2945,9 @@
CollectionProxyHandler.call(this, target);
};
inherit(NamedNodeMapProxyHandler, CollectionProxyHandler);
+ NamedNodeMapProxyHandler.prototype.toString = function() {
+ return '[NamedNodeMapProxyHandler]';
+ };
NamedNodeMapProxyHandler.prototype.col_lookup = function(name) {
if (isNumericName(name)) {
return this.visibleList.item(+name);
@@ -2955,6 +2997,10 @@
return self;
};
registerArrayLikeClass(TameNamedNodeMap, TameNodeList);
+ setOwn(TameNamedNodeMap.prototype, 'toString',
cajaVM.def(function() {
+ return '[domado object NamedNodeMap]';
+ }));
+ finishArrayLikeClass(TameNamedNodeMap);
}
function TameOptionsList(nodeList, opt_tameNodeCtor) {
@@ -2972,15 +3018,21 @@
return result;
}
registerArrayLikeClass(TameOptionsList);
- // not def'd - prototype is replaced
+ setOwn(TameOptionsList.prototype, 'toString', cajaVM.def(function() {
+ return '[domado object HTMLOptionsCollection]';
+ }));
+ finishArrayLikeClass(TameOptionsList);
/**
* Return a fake node list containing tamed nodes.
* @param {Array.<TameNode>} array of tamed nodes.
+ * @param {String} typename either 'NodeList' or 'HTMLCollection'
* @return an array that duck types to a node list.
*/
- function fakeNodeList(array) {
+ function fakeNodeList(array, typename) {
array.item = cajaVM.constFunc(function(i) { return array[+i]; });
+ array.toString = cajaVM.constFunc(
+ function() { return '[domado object ' + typename + ']'; });
return Object.freeze(array);
}
@@ -3019,7 +3071,7 @@
for (var name in tameNodesByName) {
var tameNodes = tameNodesByName[name];
if (tameNodes.length > 1) {
- tamed[name] = fakeNodeList(tameNodes);
+ tamed[name] = fakeNodeList(tameNodes, 'NodeList');
} else {
tamed[name] = tameNodes[0];
}
@@ -3090,7 +3142,7 @@
//
htmlEl.ownerDocument.getElementsByClassName(htmlEl.className)
// will return an HtmlCollection containing htmlElement iff
// htmlEl.className contains a non-space character.
- return fakeNodeList([]);
+ return fakeNodeList([], 'NodeList');
}
// "unordered set of unique space-separated tokens representing
classes"
@@ -3144,7 +3196,7 @@
}
}
// "the method must return a live NodeList object"
- return fakeNodeList(matches);
+ return fakeNodeList(matches, 'NodeList');
}
}
@@ -3391,7 +3443,7 @@
if (privates.policy.childrenVisible) {
return new TameNodeList(f, defaultTameNode);
} else {
- return fakeNodeList([]);
+ return fakeNodeList([], 'NodeList');
}
})),
attributes: NP.TameMemoIf(namedNodeMapsAreLive, 'attributes',
@@ -3411,7 +3463,7 @@
});
} else {
// TODO(kpreid): no namedItem interface
- return fakeNodeList([]);
+ return fakeNodeList([], 'HTMLCollection');
}
}))
});
@@ -3566,12 +3618,12 @@
TameForeignNode.prototype.getElementsByTagName =
innocuous(function(tagName) {
// needed because TameForeignNode doesn't inherit TameElement
- return fakeNodeList([]);
+ return fakeNodeList([], 'NodeList');
});
TameForeignNode.prototype.getElementsByClassName =
innocuous(function(className) {
// needed because TameForeignNode doesn't inherit TameElement
- return fakeNodeList([]);
+ return fakeNodeList([], 'HTMLCollection');
});
cajaVM.def(TameForeignNode);
@@ -4894,6 +4946,9 @@
CollectionProxyHandler.call(this, target);
}
inherit(FormElementProxyHandler, CollectionProxyHandler);
+ FormElementProxyHandler.prototype.toString = function() {
+ return '[FormElementProxyHandler]';
+ };
FormElementProxyHandler.prototype.col_lookup = function(name) {
return nodeAmplify(this.target, function(privates) {
return makeDOMAccessible(
@@ -5321,7 +5376,7 @@
if (privates.policy.childrenVisible) {
return new TameNodeList(f, defaultTameNode);
} else {
- return fakeNodeList([]);
+ return fakeNodeList([], 'NodeList');
}
})),
tHead: NP_tameDescendant,
@@ -5688,7 +5743,7 @@
return privates.tameContainerNode.childNodes;
})},
attributes: { enumerable: true, get: innocuous(function() {
- return fakeNodeList([]);
+ return fakeNodeList([], 'HTMLCollection');
})},
parentNode: P_constant(null),
body: { enumerable: true, get: innocuous(function() {
@@ -5731,7 +5786,7 @@
// this node's virtual document.
if (tameForm !== null) { tameForms.push(tameForm); }
}
- return fakeNodeList(tameForms);
+ return fakeNodeList(tameForms, 'HTMLCollection');
})},
title: {
// TODO(kpreid): get the title element pointer in conformant way
=======================================
--- /trunk/src/com/google/caja/ses/startSES.js Mon May 20 10:01:51 2013
+++ /trunk/src/com/google/caja/ses/startSES.js Tue May 21 13:19:02 2013
@@ -1076,6 +1076,7 @@
}
ArrayLike.prototype = global.Proxy.create({
+ toString: function() { return '[SES ArrayLike proxy handler]';
},
getPropertyDescriptor: propDesc,
getOwnPropertyDescriptor: ownPropDesc,
get: get,
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Mon
May 20 13:49:53 2013
+++ /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Tue
May 21 13:19:02 2013
@@ -2892,12 +2892,15 @@
});
</script>
-<p class="testcontainer" id="testToString"><!--Test comment-->
- Non-toxic toString</p>
+<p class="testcontainer" id="testToString">testToString</p>
<script type="text/javascript">
jsunitRegister('testToString',
function testToString() {
- // These all used to throw a "toxic function" exception in ES5/3.
+ // Except as noted, all Domado objects we're checking here should
toString
+ // like the native DOM nodes with the extra marker "domado ".
+
+ // Nodes
+ // Printing the nodeName is non-standard
assertEquals('[domado object HTMLBodyElement BODY]',
document.body.toString());
assertEquals('[domado object Text #text]',
@@ -2908,6 +2911,21 @@
document.createElement('div').toString());
assertEquals('[domado object HTMLDocument #document]',
document.toString());
+
+ // Collections
+ assertEquals('getElementsByTagName', '[domado object NodeList]',
+ document.getElementsByTagName('form').toString());
+ assertEquals('getElementsByClassName', '[domado object NodeList]',
+ document.getElementsByClassName('testcontainer').toString());
+ assertEquals('getElementsByClassName stub', '[domado object NodeList]',
+ document.getElementsByClassName('').toString()); // mildly
special case
+ assertEquals('attributes', '[domado object NamedNodeMap]',
+ document.createElement('div').attributes.toString());
+ assertEquals('forms', '[domado object HTMLCollection]',
+ document.forms.toString());
+ assertEquals('options', '[domado object HTMLOptionsCollection]',
+ document.createElement('select').options.toString());
+
pass('testToString');
});
</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.