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

Reply via email to