Revision: 5151
Author: [email protected]
Date: Tue Nov 13 10:11:08 2012
Log: Support blank iframe contentDocument/contentWindow.
http://codereview.appspot.com/6744068
Guest code can now access the contentDocument of an iframe. Loading a
document into an iframe (with iframe src= or contentWindow.location)
is still not supported.
Includes support for Domado virtual documents rooted at real document
nodes; however, since the HTML schema is written for the usual case, we
still have to virtualize the HTML global structure (<caja-v-html> etc);
this has not yet had any detectable negative effects.
Improves jQuery test suite compatibility.
[email protected]
http://code.google.com/p/google-caja/source/detail?r=5151
Modified:
/trunk/src/com/google/caja/plugin/bridal.js
/trunk/src/com/google/caja/plugin/domado.js
/trunk/tests/com/google/caja/plugin/JQueryTest.java
/trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html
=======================================
--- /trunk/src/com/google/caja/plugin/bridal.js Fri Jun 22 11:39:33 2012
+++ /trunk/src/com/google/caja/plugin/bridal.js Tue Nov 13 10:11:08 2012
@@ -770,8 +770,10 @@
* Returns the window containing this element.
*/
// mda = makeDOMAccessible
-bridalMaker.getWindow = function(element, mda) {
- var doc = mda(mda(element).ownerDocument);
+bridalMaker.getWindow = function(node, mda) {
+ var doc = mda(node).nodeType === 9 // Document node
+ ? node
+ : mda(node.ownerDocument);
// IE
if (doc.parentWindow) { return doc.parentWindow; }
// Everything else
=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Thu Nov 8 15:14:44 2012
+++ /trunk/src/com/google/caja/plugin/domado.js Tue Nov 13 10:11:08 2012
@@ -1547,7 +1547,9 @@
var vdocContainsForeignNodes = false;
containerNode = makeDOMAccessible(containerNode);
- var document = containerNode.ownerDocument;
+ var document = containerNode.nodeType === 9 // Document node
+ ? containerNode
+ : containerNode.ownerDocument;
document = makeDOMAccessible(document);
var docEl = makeDOMAccessible(document.documentElement);
var bridal = bridalMaker(makeDOMAccessible, document);
@@ -2415,16 +2417,21 @@
// Catch errors because node might be from a different domain.
try {
- var docElem = node.ownerDocument.documentElement;
+ var doc = node.ownerDocument;
for (var ancestor = node;
ancestor;
ancestor = makeDOMAccessible(ancestor.parentNode)) {
- if (idClassPattern.test(ancestor.className)) {
+ if (ancestor === containerNode ||
+ (ancestor.nodeType === 1 &&
+ idClassPattern.test(ancestor.className))) {
+ // is within the virtual document
return tameNodeCtor(node);
- } else if (ancestor === docElem) {
+ } else if (ancestor === doc) {
+ // didn't find evidence of being within the virtual document
return null;
}
}
+ // permit orphaned nodes
return tameNodeCtor(node);
} catch (e) {}
return null;
@@ -3743,7 +3750,10 @@
np(this).policy.requireChildrenEditable();
var node = np(this).feral;
var schemaElem = htmlSchema.element(node.tagName);
- if (!schemaElem.allowed) { throw new Error(); }
+ if (!schemaElem.allowed) {
+ throw new Error("Can't set .innerHTML of non-whitelisted <" +
+ node.tagName + ">");
+ }
var isRCDATA = schemaElem.contentIsRCDATA;
var htmlFragmentString;
if (!isRCDATA && htmlFragment instanceof Html) {
@@ -4642,7 +4652,19 @@
height: NP.filterProp(identity, Number),
width: NP.filterProp(identity, Number),
src: P_blacklist,
- name: P_blacklist
+ name: P_blacklist,
+ contentDocument: {
+ enumerable: true,
+ get: cajaVM.def(function () {
+ return contentDomicile(this).document;
+ })
+ },
+ contentWindow: {
+ enumerable: true,
+ get: cajaVM.def(function () {
+ return contentDomicile(this).window;
+ })
+ }
}
});
// TODO(kpreid): Check these two (straight from Domita) for
correctness
@@ -4671,6 +4693,40 @@
'] attribute of an iframe.');
return value;
}));
+ function contentDomicile(tameIFrame) {
+ // TODO(kpreid): Once we support loading content via src=, we will
need
+ // to consider whether this should always allow access to said
content,
+ // and probably other issues.
+ var privates = np(tameIFrame);
+ var frameFeralDoc =
makeDOMAccessible(privates.feral.contentDocument);
+ if (!privates.contentDomicile ||
+ frameFeralDoc !== privates.seenContentDocument) {
+ if (!frameFeralDoc) {
+ return {document: null, window: null};
+ }
+
+ var domicile = privates.contentDomicile = attachDocument(
+ '-caja-iframe___', naiveUriPolicy, frameFeralDoc,
+ optTargetAttributePresets, taming);
+ privates.seenContentDocument = frameFeralDoc;
+
+ // Replace document structure with virtualized forms
+ // TODO(kpreid): Use an alternate HTML schema (requires
refactoring)
+ // which makes <html> <head> <body> permitted (in particular,
+ // non-opaque) so that this is unnecessary.
+ var child;
+ while ((child = makeDOMAccessible(frameFeralDoc.lastChild))) {
+ frameFeralDoc.removeChild(child);
+ }
+ var tdoc = domicile.document;
+ var thtml = tdoc.createElement('html');
+ thtml.appendChild(tdoc.createElement('head'));
+ thtml.appendChild(tdoc.createElement('body'));
+ frameFeralDoc.appendChild(domicile.feralNode(thtml));
+ // cannot do this via pseudo-node
+ }
+ return privates.contentDomicile;
+ }
var TameImageElement = defineElement({
names: ['img'],
@@ -5700,10 +5756,12 @@
domicile.getIdClass = cajaVM.def(function () {
return idClass;
});
- // enforce id class on element
- bridal.setAttribute(containerNode, "class",
- bridal.getAttribute(containerNode, "class")
- + " " + idClass + " vdoc-container___");
+ // enforce id class on container
+ if (containerNode.nodeType !== 9) { // not a document (top level)
+ bridal.setAttribute(containerNode, 'class',
+ bridal.getAttribute(containerNode, 'class')
+ + ' ' + idClass + ' vdoc-container___');
+ }
// bitmask of trace points
// 0x0001 plugin_dispatchEvent
=======================================
--- /trunk/tests/com/google/caja/plugin/JQueryTest.java Thu Nov 8 14:46:38
2012
+++ /trunk/tests/com/google/caja/plugin/JQueryTest.java Tue Nov 13 10:11:08
2012
@@ -22,7 +22,7 @@
public class JQueryTest extends QUnitTestCase {
public final void testCore() throws Exception {
- runQUnitTestCase("core", 1285);
+ runQUnitTestCase("core", 1286);
// Current modifications made to test suite:
// * Removed unnecessary octal literal.
// Current failure categories:
@@ -30,7 +30,6 @@
// * TODO(jasvir): window.eval is absent (this includes the
jQuery('html')
// failure)
// * We don't implement XML yet.
- // * We don't implement iframes yet.
// * We don't implement document.styleSheets.
// * We don't implement document.getElementsByName.
}
@@ -50,13 +49,13 @@
public final void testSupport() throws Exception {
runQUnitTestCase("support", 1);
// Current failure categories:
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
}
public final void testData() throws Exception {
runQUnitTestCase("data", 290);
// Current failure categories:
- // * We don't implement iframes yet (used incidentally).
+ // * We don't implement src= iframes yet.
}
public final void testQueue() throws Exception {
@@ -86,7 +85,7 @@
// live() and delegate() tests
// trigger() tests
// * We don't implement document.createEvent of other
than 'HTMLEvents'
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
// * jQuery reports leak in 'bind(name, false), unbind(name, false)'
// * submit listeners not firing in 'trigger(type, [data], [fn])'
// * "Object [domado object HTMLInputElement] has no method 'click'"
@@ -96,27 +95,27 @@
public final void testSelector() throws Exception {
runQUnitTestCase("selector", 25);
// Current failure categories:
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
}
public final void testTraversing() throws Exception {
- runQUnitTestCase("traversing", 286);
+ runQUnitTestCase("traversing", 292);
// Current failure categories:
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
}
public final void testManipulation() throws Exception {
- runQUnitTestCase("manipulation", 474);
+ runQUnitTestCase("manipulation", 474 /* Firefox; 488 Chrome */);
// Current modifications made to test suite:
// * Removed SES-incompatible Array.prototype modification; was only
for
// testing jQuery robustness.
// Current failure categories:
- // * Something wrong with checked radio buttons.
+ // * Something wrong with checked radio buttons. (Worse on Firefox)
// * Something wrong with jQuery's <script>-based ajax transport.
// * We don't make non-JS <script> elements readable/preserved.
// * We don't implement XML yet.
// * We don't support runtime-created <style> elements, even
virtualized?
- // * Something wrong with "jQuery.cleanData" test.
+ // * Something wrong with "jQuery.cleanData" test. (Firefox only)
// * We don't implement some case of dynamic <script> creation that
// "html() - execute scripts..." and "html() - script
exceptions..."
// are using.
@@ -150,7 +149,7 @@
public final void testOffset() throws Exception {
runQUnitTestCase("offset", 18);
// Current failure categories:
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
}
public final void testDimensions() throws Exception {
@@ -158,7 +157,7 @@
// Current modifications made to test suite:
// * Fixed nested function in strict mode.
// Current failure categories:
- // * We don't implement iframes yet.
+ // * We don't implement src= iframes yet.
}
public final void testExports() throws Exception {
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Thu
Nov 8 15:14:44 2012
+++ /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Tue
Nov 13 10:11:08 2012
@@ -2497,6 +2497,28 @@
});
</script>
+<div id="testIframeDocument" class="testcontainer">Test Iframe
Document</div>
+<script type="text/javascript">
+ jsunitRegister('testIframeDocument',
+ function testIframeDocument() {
+ var container = document.getElementById('testIframeDocument');
+ var ifr = document.createElement('iframe');
+ container.appendChild(ifr);
+
+ // existence
+ assertEvaluatesToTrue('contentDocument', ifr.contentDocument);
+ assertEvaluatesToTrue('contentWindow', ifr.contentWindow);
+
+ // functionality and distinctness from container
+ ifr.contentDocument.body.innerHTML =
+ '<p class="testcontainer">Hello framed world</p>';
+ var elems =
ifr.contentDocument.getElementsByClassName('testcontainer');
+ assertEquals(1, elems.length);
+
+ pass('testIframeDocument');
+ });
+</script>
+
<!-- this sequence of interleaved html/text/script triggers bug 1060 -->
<div id="testEmitterIe" class="testcontainer">
<div id="testEmitterIe-0">duck-0</div>duck-2