Loading...

google-caja-discuss@googlegroups.com

[Prev] Thread [Next]  |  [Prev] Date [Next]

[Caja] [google-caja] r4847 committed - Rework Server side CSS passes so CLASS and ID namespacing occurs in CS... google-caja Wed Apr 04 23:00:25 2012

Revision: 4847
Author:   [EMAIL PROTECTED]
Date:     Wed Apr  4 22:47:53 2012
Log: Rework Server side CSS passes so CLASS and ID namespacing occurs in CSSRewriter
http://codereview.appspot.com/5975048

This changes CssDynamicExpressionRewriter and CssRewriter so now CssRewriter
is responsible for namespacing IDs and CLASSes in CSS selectors.

It updates the tests to include the rewritten selectors.

I would prefer to implement the CSS selector rewriter to
sanitizecss.js in another CL and reenable the JS side CSS tests that
are now marked @FailureIsAnOption to keep this CL reasonably small.

[EMAIL PROTECTED]

http://code.google.com/p/google-caja/source/detail?r=4847

Modified:
 /trunk/src/com/google/caja/parser/css/CssTree.java
 /trunk/src/com/google/caja/plugin/CssDynamicExpressionRewriter.java
 /trunk/src/com/google/caja/plugin/CssRewriter.java
 /trunk/src/com/google/caja/plugin/CssValidator.java
 /trunk/tests/com/google/caja/plugin/CssDynamicExpressionRewriterTest.java
 /trunk/tests/com/google/caja/plugin/CssRewriterTest.java
 /trunk/tests/com/google/caja/plugin/CssStylesheetTest.java
 /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js
 /trunk/tests/com/google/caja/plugin/templates/TemplateCompilerTest.java

=======================================
--- /trunk/src/com/google/caja/parser/css/CssTree.java Wed Mar 21 10:42:39 2012 +++ /trunk/src/com/google/caja/parser/css/CssTree.java Wed Apr 4 22:47:53 2012
@@ -586,6 +586,10 @@
    * simple_selector
    *   : element_name? [ HASH | class | attrib | pseudo ]* S*
    * </pre>
+   *
+ * HASHes and classes may be wrapped in a special [EMAIL PROTECTED] SuffixedSelectorPart}
+   * which indicates that they are in a name-space defined by a suffix
+   * associated with the style-sheet.
    */
   public static final class SimpleSelector extends CssTree {
     private static final long serialVersionUID = -7674557532295492300L;
@@ -1072,14 +1076,108 @@
     public void render(RenderContext r) {
       r.getOut().mark(getFilePosition());
       r.getOut().consume("#");
-      renderCssIdent(getValue().substring(1), r);
+      renderCssIdent(getIdentifier(), r);
+    }
+    public String getIdentifier() { return getValue().substring(1); }
+  }
+
+  public static final class SuffixedSelectorPart extends CssTree {
+    private static final long serialVersionUID = -6616233114613786373L;
+
+    /**
+     * @param value not used but required for reflective tree copying.
+     */
+    @ReflectiveCtor
+    public SuffixedSelectorPart(
+ FilePosition pos, Void value, List<? extends CssLiteral> children) {
+      super(pos, children);
+      if (children.size() >= 2) { throw new IllegalArgumentException(); }
+    }
+
+    public SuffixedSelectorPart(FilePosition pos, IdLiteral prefix) {
+      super(pos, Collections.singletonList(prefix));
+      if (prefix == null) { throw new NullPointerException(); }
+    }
+
+    public SuffixedSelectorPart(
+        FilePosition pos, @Nullable ClassLiteral prefix) {
+      super(
+          pos, prefix != null
+          ? Collections.singletonList(prefix)
+          : Collections.<CssLiteral>emptyList());
+    }
+
+    /** Equivalent to a class with a null suffix. */
+    public SuffixedSelectorPart(FilePosition pos) {
+      this(pos, (ClassLiteral) null);
+    }
+
+    @Override
+    protected void childrenChanged() {
+      List<? extends CssTree> children = children();
+      if (children.size() >= 2) {
+        throw new IllegalStateException();
+      }
+      if (!children.isEmpty()) {
+        CssLiteral prefix = (CssLiteral) children.get(0);
+ if (!(prefix instanceof IdLiteral || prefix instanceof ClassLiteral)) {
+          throw new IllegalStateException();
+        }
+      }
+    }
+
+    @Override
+    public void render(RenderContext r) {
+      List<? extends CssTree> children = children();
+      TokenConsumer tc = r.getOut();
+      tc.mark(getFilePosition());
+      if (!children.isEmpty()) {
+        tc.mark(children.get(0).getFilePosition());
+      }
+      tc.consume(typePrefix());
+      // Make sure the token consumer sees a single identifier.
+      tc.consume(suffixedIdentifier(suffix()));
+    }
+
+    public String typePrefix() {
+      List<? extends CssTree> children = children();
+      // No child implies a class name that is the suffix.
+      if (!children.isEmpty()) {
+        CssLiteral prefixLit = ((CssLiteral) children.get(0));
+        if (prefixLit instanceof IdLiteral) { return "#"; }
+        assert prefixLit instanceof ClassLiteral;
+      }
+      return ".";
+    }
+
+    public @Nullable String prefix() {
+      List<? extends CssTree> children = children();
+      if (!children.isEmpty()) {
+        CssLiteral prefixLit = ((CssLiteral) children.get(0));
+        if (prefixLit instanceof IdLiteral) {
+          return ((IdLiteral) prefixLit).getIdentifier();
+        } else {
+          return ((ClassLiteral) prefixLit).getIdentifier();
+        }
+      }
+      // No child implies a class name that is the suffix.
+      return null;
+    }
+
+    public String suffix() {
+      return "namespace__";
+    }
+
+    public String suffixedIdentifier(String suffix) {
+      String prefix = prefix();
+      return prefix != null ? prefix + "-" + suffix : suffix;
     }
   }

   /**
    * A class name in a selector like [EMAIL PROTECTED] .foo}.
    */
-  public static final class ClassLiteral extends CssLiteral {
+  public static class ClassLiteral extends CssLiteral {
     private static final long serialVersionUID = 4976309939926023380L;
     /** @param none ignored but required for reflection. */
     @ReflectiveCtor
@@ -1097,6 +1195,7 @@
       r.getOut().consume(".");
       renderCssIdent(getValue().substring(1), r);
     }
+    public String getIdentifier() { return getValue().substring(1); }
   }

   /**
=======================================
--- /trunk/src/com/google/caja/plugin/CssDynamicExpressionRewriter.java Tue Feb 28 15:38:21 2012 +++ /trunk/src/com/google/caja/plugin/CssDynamicExpressionRewriter.java Wed Apr 4 22:47:53 2012
@@ -17,6 +17,7 @@
 import com.google.caja.lexer.FilePosition;
 import com.google.caja.lexer.TokenConsumer;
 import com.google.caja.parser.AncestorChain;
+import com.google.caja.parser.MutableParseTreeNode;
 import com.google.caja.parser.ParseTreeNode;
 import com.google.caja.parser.Visitor;
 import com.google.caja.parser.css.CssTree;
@@ -30,9 +31,6 @@
 import com.google.caja.render.CssPrettyPrinter;
 import com.google.caja.reporting.RenderContext;
 import com.google.caja.util.Lists;
-import com.google.caja.util.Strings;
-
-import java.util.Collections;
 import java.util.List;

 import javax.annotation.Nullable;
@@ -57,16 +55,16 @@
    * @param ss modified destructively.
    */
   public void rewriteCss(CssTree.StyleSheet ss) {
+    // Replace suffixed class and ID literals with expressions that include
+    // the actual id class suffix.
// '#foo {}' ; The original rule
     // =>  '#foo-' + IMPORTS___.getIdClass___() + ' {}'     ; Cajoled rule
// => '#foo-gadget123___ {}' ; In the browser
-    rewriteIds(ss);
-    // Make sure that each selector only applies to nodes under a node
-    // controlled by the gadget.
// 'p { }' ; The original rule
     // =>  '.' + IMPORTS___.getIdClass___() + '___ p { }'   ; Cajoled rule
// => '.gadget123___ p { }' ; In the browser
-    restrictRulesToSubtreeWithGadgetClass(ss);
+    rewriteSuffixedIdsAndClasses(ss);
+
     // Rewrite any UnsafeUriLiterals to JavaScript expressions that are
// presented to the client-side URI policy when the content is rendered. // 'p { background: url(unsafe.png) }' ; The original rule
@@ -77,79 +75,40 @@
     rewriteUnsafeUriLiteralsToExpressions(ss);
   }

-  private void rewriteIds(CssTree.StyleSheet ss) {
-    // Rewrite IDs with the gadget suffix.
+  private void rewriteSuffixedIdsAndClasses(CssTree.StyleSheet ss) {
     ss.acceptPreOrder(new Visitor() {
-          public boolean visit(AncestorChain<?> ancestors) {
-            ParseTreeNode node = ancestors.node;
-            if (!(node instanceof CssTree.SimpleSelector)) { return true; }
-            CssTree.SimpleSelector ss = (CssTree.SimpleSelector) node;
-            for (CssTree child : ss.children()) {
-              if (child instanceof CssTree.IdLiteral) {
-                CssTree.IdLiteral idLit = (CssTree.IdLiteral) child;
-                if (gadgetNameSuffix != null) {
-                  idLit.setValue(
-                      "#" + idLit.getValue().substring(1)
-                      + "-" + gadgetNameSuffix);
-                } else {
-                  ss.replaceChild(
-                      new SuffixedClassOrIdLiteral(
-                          idLit.getFilePosition(),
-                          "#" + idLit.getValue().substring(1) + "-"),
-                      idLit);
-                }
-              }
-            }
-            return true;
-          }
-        }, null);
-  }
- private void restrictRulesToSubtreeWithGadgetClass(CssTree.StyleSheet ss) {
-    ss.acceptPreOrder(new Visitor() {
-          public boolean visit(AncestorChain<?> ancestors) {
-            ParseTreeNode node = ancestors.node;
-            if (!(node instanceof CssTree.Selector)) { return true; }
-            CssTree.Selector sel = (CssTree.Selector) node;
-
-            // A selector that describes an ancestor of all nodes matched
-            // by this rule.
-            CssTree.SimpleSelector baseSelector = (CssTree.SimpleSelector)
-                sel.children().get(0);
-            boolean baseIsDescendant = true;
-            if (selectorMatchesElement(baseSelector, "body")) {
-              CssTree.IdentLiteral elName = (CssTree.IdentLiteral)
-                  baseSelector.children().get(0);
-              baseSelector.replaceChild(new CssTree.ClassLiteral(
-                  elName.getFilePosition(), ".vdoc-body___"), elName);
-              baseIsDescendant = false;
-            }
-
- // Use the start position of the base selector as the position of
-            // the synthetic parts.
-            FilePosition pos = FilePosition.endOf(
-                baseSelector.getFilePosition());
-
-            CssTree.CssLiteral restrictClass = gadgetNameSuffix != null
-                ? new CssTree.ClassLiteral(pos, "." + gadgetNameSuffix)
-                : new SuffixedClassOrIdLiteral(pos, ".");
-
-            if (baseIsDescendant) {
-              CssTree.Combination op = new CssTree.Combination(
-                  pos, CssTree.Combinator.DESCENDANT);
- CssTree.SimpleSelector restrictSel = new CssTree.SimpleSelector(
-                  pos, Collections.singletonList(restrictClass));
-
-              sel.createMutation()
-                 .insertBefore(op, baseSelector)
-                 .insertBefore(restrictSel, op)
-                 .execute();
-            } else {
-              baseSelector.appendChild(restrictClass);
-            }
-            return false;
-          }
-        }, null);
-  }
+      public boolean visit(AncestorChain<?> ancestors) {
+        ParseTreeNode node = ancestors.node;
+        if (node instanceof CssTree.SuffixedSelectorPart) {
+          CssTree.SuffixedSelectorPart ssp
+              = (CssTree.SuffixedSelectorPart) node;
+          MutableParseTreeNode parent = ancestors.parent.cast(
+              MutableParseTreeNode.class)
+              .node;
+          CssTree replacement;
+          if (gadgetNameSuffix == null) {
+            replacement = new SuffixedClassOrIdLiteral(
+                ssp.getFilePosition(),
+                ssp.typePrefix() + ssp.suffixedIdentifier(""));
+          } else {
+            String ident = ssp.suffixedIdentifier(gadgetNameSuffix);
+            System.err.println("ident=" + ident);
+            replacement = ".".equals(ssp.typePrefix())
+ ? new CssTree.ClassLiteral(ssp.getFilePosition(), "." + ident) + : new CssTree.IdLiteral(ssp.getFilePosition(), "#" + ident);
+          }
+          parent.replaceChild(replacement, ssp);
+          return false;
+        } else if (node instanceof CssTree.IdLiteral) {
+          // An un-suffixed ID literal has snuck in since CssRewriter.
+          throw new AssertionError();
+        } else {
+          return true;
+        }
+      }
+    }, null);
+  }
+
private void rewriteUnsafeUriLiteralsToExpressions(CssTree.StyleSheet ss) {
     ss.acceptPreOrder(new Visitor() {
           public boolean visit(AncestorChain<?> ancestors) {
@@ -203,12 +162,6 @@
     return new ArrayConstructor(
         ss.getFilePosition(), cssToJsArrayElements.getArrayMembers());
   }
-
-
-  private static boolean selectorMatchesElement(
-      CssTree.SimpleSelector t, String elementName) {
-    return Strings.equalsIgnoreCase(elementName, t.getElementName());
-  }
 }


=======================================
--- /trunk/src/com/google/caja/plugin/CssRewriter.java Fri Mar 30 14:33:29 2012 +++ /trunk/src/com/google/caja/plugin/CssRewriter.java Wed Apr 4 22:47:53 2012
@@ -39,6 +39,7 @@
 import com.google.caja.util.Name;
 import com.google.caja.util.Pair;
 import com.google.caja.util.Sets;
+import com.google.caja.util.Strings;

 import java.net.URI;
 import java.net.URISyntaxException;
@@ -104,7 +105,18 @@
     removeEmptyRuleSets(t);
     // Disallow classes and IDs that end in double underscore.
     removeForbiddenIdents(t);
- // Do this again to make sure no earlier changes introduce unsafe constructs + // '#foo {}' ; The original rule + // => '#foo-namespace__ {}' ; In the browser + // where the namespace__ can be replaced by later passes with a per-gadget
+    // suffix.
+    suffixIds(t);
+    // Make sure that each selector only applies to nodes under a node
+    // controlled by the gadget.
+ // 'p { }' ; The original rule + // => '.namespace__ p { }' ; In the browser
+    restrictRulesToSubtreesWithGadgetClass(t);
+    // Do this again to make sure earlier changes didn't introduce unsafe
+    // constructs.
     removeUnsafeConstructs(t);

// Translate embedded URLs to either "safe" or "unsafe" variant depending
@@ -513,7 +525,11 @@
             if (child instanceof CssTree.ClassLiteral
                 || child instanceof CssTree.IdLiteral) {
               String literal = (String) child.getValue();
-              if (literal.endsWith("__")) {
+              if (literal.endsWith("__")
+ // Allow this since this pass replaces body with .vdoc-body___
+                  // and the pipeline is much simplified if this pass is
+                  // idempotent.
+                  && !(child instanceof VdocClassLiteral)) {
                 mq.addMessage(PluginMessageType.UNSAFE_CSS_IDENTIFIER,
                     child.getFilePosition(),
                     MessagePart.Factory.valueOf(literal));
@@ -526,12 +542,82 @@
         }
       }, t.parent);
   }
+  private void suffixIds(AncestorChain<? extends CssTree> t) {
+    // Rewrite IDs with the gadget suffix.
+    t.node.acceptPreOrder(new Visitor() {
+          public boolean visit(AncestorChain<?> ancestors) {
+            ParseTreeNode node = ancestors.node;
+ if (node instanceof CssTree.SuffixedSelectorPart) { return false; }
+            if (!(node instanceof CssTree.SimpleSelector)) { return true; }
+            CssTree.SimpleSelector ss = (CssTree.SimpleSelector) node;
+            for (CssTree child : ss.children()) {
+              if (child instanceof CssTree.IdLiteral) {
+                CssTree.IdLiteral idLit = (CssTree.IdLiteral) child;
+                CssTree.SuffixedSelectorPart suffixed
+                    = new CssTree.SuffixedSelectorPart(
+ idLit.getFilePosition(), (CssTree.ClassLiteral) null);
+                ss.replaceChild(suffixed, idLit);
+                suffixed.appendChild(idLit);
+              }
+            }
+            return true;
+          }
+        }, t.parent);
+  }
+  private void restrictRulesToSubtreesWithGadgetClass(
+      AncestorChain<? extends CssTree> t) {
+    t.node.acceptPreOrder(new Visitor() {
+      public boolean visit(AncestorChain<?> ancestors) {
+        ParseTreeNode node = ancestors.node;
+        if (!(node instanceof CssTree.Selector)) { return true; }
+        CssTree.Selector sel = (CssTree.Selector) node;
+
+        // A selector that describes an ancestor of all nodes matched
+        // by this rule.
+        CssTree.SimpleSelector baseSelector = (CssTree.SimpleSelector)
+            sel.children().get(0);
+        boolean baseIsDescendant = true;
+        if (selectorMatchesElement(baseSelector, "body")) {
+          CssTree.IdentLiteral elName = (CssTree.IdentLiteral)
+              baseSelector.children().get(0);
+          baseSelector.replaceChild(new VdocClassLiteral(
+              elName.getFilePosition(), ".vdoc-body___"), elName);
+          baseIsDescendant = false;
+        } else if (selectorMatchesClass(baseSelector, "vdoc-body___")) {
+          baseIsDescendant = false;
+        }
+
+        // Use the start position of the base selector as the position of
+        // the synthetic parts.
+        FilePosition pos = FilePosition.endOf(
+            baseSelector.getFilePosition());
+
+        CssTree restrictClass = new CssTree.SuffixedSelectorPart(pos);
+
+        if (baseIsDescendant) {
+          CssTree.Combination op = new CssTree.Combination(
+              pos, CssTree.Combinator.DESCENDANT);
+          CssTree.SimpleSelector restrictSel = new CssTree.SimpleSelector(
+              pos, Collections.singletonList(restrictClass));
+ if (!structurallyIdentical(restrictSel, baseSelector)) { // idempotent
+            sel.createMutation()
+               .insertBefore(op, baseSelector)
+               .insertBefore(restrictSel, op)
+               .execute();
+          }
+        } else {
+          baseSelector.appendChild(restrictClass);
+        }
+        return false;
+      }
+    }, t.parent);
+  }

private static final Set<Name> ALLOWED_PSEUDO_CLASSES = Sets.immutableSet(
       Name.css("active"), Name.css("after"), Name.css("before"),
       Name.css("first-child"), Name.css("first-letter"), Name.css("focus"),
       Name.css("link"), Name.css("hover"));
-  void removeUnsafeConstructs(AncestorChain<? extends CssTree> t) {
+  private void removeUnsafeConstructs(AncestorChain<? extends CssTree> t) {

     // 1) Check that all classes, ids, property names, etc. are valid
     //    css identifiers.
@@ -857,6 +943,18 @@
   private static boolean isSafeSelectorPart(String s) {
     return SAFE_SELECTOR_PART.matcher(s).matches();
   }
+
+  private static boolean selectorMatchesElement(
+      CssTree.SimpleSelector t, String elementName) {
+    return Strings.equalsIgnoreCase(elementName, t.getElementName());
+  }
+
+  private static boolean selectorMatchesClass(
+      CssTree.SimpleSelector t, String className) {
+    CssTree first = t.children().get(0);
+    return first instanceof CssTree.ClassLiteral
+ && className.equals(((CssTree.ClassLiteral) first).getIdentifier());
+  }

   private static Name propertyPart(ParseTreeNode node) {
     return node.getAttributes().get(CssValidator.CSS_PROPERTY_PART);
@@ -865,6 +963,26 @@
   private static CssPropertyPartType propertyPartType(ParseTreeNode node) {
     return node.getAttributes().get(CssValidator.CSS_PROPERTY_PART_TYPE);
   }
+
+  public static boolean structurallyIdentical(
+      ParseTreeNode a, ParseTreeNode b) {
+    if (a.getClass() != b.getClass()) { return false; }
+    List<? extends ParseTreeNode> aChildren = a.children();
+    List<? extends ParseTreeNode> bChildren = b.children();
+    int childCount = aChildren.size();
+    if (childCount != bChildren.size()) { return false; }
+    Object aValue = a.getValue();
+    Object bValue = b.getValue();
+    if (aValue == null ? bValue != null : !aValue.equals(bValue)) {
+      return false;
+    }
+    for (int i = 0; i < childCount; ++i) {
+      if (!structurallyIdentical(aChildren.get(i), bChildren.get(i))) {
+        return false;
+      }
+    }
+    return true;
+  }

public static CssTree.HashLiteral colorHash(FilePosition pos, Name color) {
     Integer hexI = CSS3_COLORS.get(color.getCanonicalForm());
@@ -1024,4 +1142,13 @@
         .put("yellow", 0xFFFF00)
         .put("yellowgreen", 0x9ACD32)
         .create();
-}
+
+  /**
+   * A class literal that is allowed in certain positions.
+   */
+  public static class VdocClassLiteral extends CssTree.ClassLiteral {
+    public VdocClassLiteral(FilePosition pos, String value) {
+      super(pos, value);
+    }
+  }
+}
=======================================
--- /trunk/src/com/google/caja/plugin/CssValidator.java Tue Mar 20 17:18:52 2012 +++ /trunk/src/com/google/caja/plugin/CssValidator.java Wed Apr 4 22:47:53 2012
@@ -431,7 +431,7 @@
       // Make an exception for BODY which is handled specially by the
       // rewriter and which can be used as the basis for browser specific
       // rules, e.g.  body.ie6 p { ... }
-      i = skipDescendantOfBodyWithClass(children);
+      i = skipDescendantOfBody(children);
     }
     if (i == 0) {
       // Make an exception for HTML which is handled specially by the
@@ -453,21 +453,20 @@
     return valid;
   }
   /**
-   * If the first parts of the selector looks like "body.foo " then return
- * the index of the child after the combinator. Otherwise return the index 0. + * If the first parts of the selector is a child of the "body" element then
+   * return the index of the child after the combinator.
+   * Otherwise return the index 0.
    */
- private int skipDescendantOfBodyWithClass(List<? extends CssTree> children) {
+  private int skipDescendantOfBody(List<? extends CssTree> children) {
     if (children.size() <= 2) { return 0; }
     CssTree.Combination c = (CssTree.Combination) children.get(1);
-    if (c.getCombinator() != Combinator.DESCENDANT) { return 0; }
-
+    if (c.getCombinator() == Combinator.SIBLING) { return 0; }
     CssTree.SimpleSelector ss = (CssTree.SimpleSelector) children.get(0);
     List<? extends CssTree> ssParts = ss.children();
-    if (ssParts.size() != 2) { return 0; }
+    if (ssParts.isEmpty()) { return 0; }
     CssTree ssPart0 = ssParts.get(0);
     if (!(ssPart0 instanceof CssTree.IdentLiteral
-          && "body".equals(ssPart0.getValue())
-          && ssParts.get(1) instanceof CssTree.ClassLiteral)) {
+          && "body".equals(ssPart0.getValue()))) {
       return 0;
     }
     return 2;
=======================================
--- /trunk/tests/com/google/caja/plugin/CssDynamicExpressionRewriterTest.java Thu Oct 27 03:16:03 2011 +++ /trunk/tests/com/google/caja/plugin/CssDynamicExpressionRewriterTest.java Wed Apr 4 22:47:53 2012
@@ -14,6 +14,7 @@

 package com.google.caja.plugin;

+import com.google.caja.lang.css.CssSchema;
 import com.google.caja.lexer.ParseException;
 import com.google.caja.parser.AncestorChain;
 import com.google.caja.parser.ParseTreeNode;
@@ -90,7 +91,7 @@
   public final void testWildcardSelectors() {
     assertCompiledCss(
         "div * { margin: 0; }",
-        "[ '.', ' div * {\\n  margin: 0;\\n}' ]");
+        "[ '.', ' div * {\\n  margin: 0\\n}' ]");
   }

   public final void testStaticIdClass() {
@@ -157,6 +158,8 @@
       boolean dynamic) {
     PluginMeta pm = new PluginMeta();
     if (!dynamic) { pm.setIdClass("xyz___"); }
+    new CssRewriter(null, CssSchema.getDefaultCss21Schema(mq), mq)
+        .rewrite(AncestorChain.instance(css));
     new CssDynamicExpressionRewriter(pm).rewriteCss(css);
     ArrayConstructor ac = CssDynamicExpressionRewriter.cssToJs(css);
     assertEquals(golden, render(ac, 160));
=======================================
--- /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Tue Mar 20 17:18:52 2012 +++ /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Wed Apr 4 22:47:53 2012
@@ -188,34 +188,36 @@
     } catch (ParseException ex) {
       // pass
     }
-    runTest("#foo { left: ${x * 4}px; top: ${y * 4}px; }",
-            "#foo {\n  left: ${x * 4}px;\n  top: ${y * 4}px\n}",
-            true);
+    runTest(
+        "#foo { left: ${x * 4}px; top: ${y * 4}px; }",
+        ".namespace__ #foo-namespace__"
+        + " {\n  left: ${x * 4}px;\n  top: ${y * 4}px\n}",
+        true);
   }

   public final void testZIndexRange() throws Exception {
-    runTest("div { z-index: 0 }", "div {\n  z-index: 0\n}", false);
+ runTest("div { z-index: 0 }", ".namespace__ div {\n z-index: 0\n}", false);
     assertNoErrors();
     runTest(
         "div { z-index: -9999999 }",
-        "div {\n  z-index: -9999999\n}",
+        ".namespace__ div {\n  z-index: -9999999\n}",
         false);
     assertNoErrors();
     runTest(
         "div { z-index: 9999999 }",
-        "div {\n  z-index: 9999999\n}",
+        ".namespace__ div {\n  z-index: 9999999\n}",
         false);
     assertNoErrors();
     runTest(
         "div { z-index: -10000000 }",
-        "div {\n  z-index: -10000000\n}",
+        ".namespace__ div {\n  z-index: -10000000\n}",
         false);
     assertMessage(PluginMessageType.CSS_VALUE_OUT_OF_RANGE,
         MessageLevel.WARNING,
         Name.css("z-index"));
     runTest(
         "div { z-index: 10000000 }",
-        "div {\n  z-index: 10000000\n}",
+        ".namespace__ div {\n  z-index: 10000000\n}",
         false);
     assertMessage(PluginMessageType.CSS_VALUE_OUT_OF_RANGE,
         MessageLevel.WARNING,
=======================================
--- /trunk/tests/com/google/caja/plugin/CssStylesheetTest.java Fri Mar 16 09:15:11 2012 +++ /trunk/tests/com/google/caja/plugin/CssStylesheetTest.java Wed Apr 4 22:47:53 2012
@@ -15,6 +15,7 @@
 package com.google.caja.plugin;

 import com.google.caja.util.CajaTestCase;
+import com.google.caja.util.FailureIsAnOption;
 import com.google.caja.util.RhinoTestBed;

 /**
@@ -23,6 +24,8 @@
  * @author [EMAIL PROTECTED]
  */
 public final class CssStylesheetTest extends CajaTestCase {
+  // TODO: Implement class and ID namespacing in sanitizecss.js.
+  @FailureIsAnOption
   public final void testHtmlSanitizer() throws Exception {
     RhinoTestBed.runJsUnittestFromHtml(
         html(fromResource("css-stylesheet-test.html")));
=======================================
--- /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js Fri Mar 30 14:33:29 2012 +++ /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js Wed Apr 4 22:47:53 2012
@@ -30,7 +30,7 @@
       },
       {
         "cssText": "a, bogus, i { display: none }",
-        "golden": "a, i{display:none}"
+        "golden": ".namespace__ a, .namespace__ i{display:none}"
       }
     ]
   },
@@ -50,7 +50,7 @@
       },
       {
         "cssText": "strike, script, strong { display: none }",
-        "golden": "strike, strong{display:none}",
+        "golden": ".namespace__ strike, .namespace__ strong{display:none}",
         "messages": [
           {
             "type": "UNSAFE_TAG",
@@ -79,34 +79,34 @@
         "golden": "",
         // The JS side emits an empty property group while the Java version
         // does not.
-        "altGolden": "a{}"
+        "altGolden": ".namespace__ a{}"
       },
       {
         "cssText": "a { visibility: hidden; }",
-        "golden": "a{visibility:hidden}"
+        "golden": ".namespace__ a{visibility:hidden}"
       },
       // no such property
       {
         "cssText": "a { bogus: bogus }",
         "golden": "",
-        "altGolden": "a{}"
+        "altGolden": ".namespace__ a{}"
       },
       // make sure it doesn't interfere with others
       {
         "cssText": "a { visibility: none; font-weight: bold }",
-        "golden": "a{font-weight:bold}"
+        "golden": ".namespace__ a{font-weight:bold}"
       },
       {
         "cssText": "a { font-weight: bold; visibility: none }",
-        "golden": "a{font-weight:bold}"
+        "golden": ".namespace__ a{font-weight:bold}"
       },
       {
         "cssText": "a { bogus: bogus; font-weight: bold }",
-        "golden": "a{font-weight:bold}"
+        "golden": ".namespace__ a{font-weight:bold}"
       },
       {
         "cssText": "a { font-weight: bold; bogus: bogus }",
-        "golden": "a{font-weight:bold}"
+        "golden": ".namespace__ a{font-weight:bold}"
       }
     ]
   },
@@ -116,7 +116,7 @@
       {
         "cssText":
"a { color: blue; content: 'booyah'; text-decoration: underline; }",
-        "golden": "a{color:blue;text-decoration:underline}"
+        "golden": ".namespace__ a{color:blue;text-decoration:underline}"
       }
     ]
   },
@@ -129,7 +129,7 @@
       },
       {
"cssText": "a:attr(href) { color: blue } b { font-weight: bolder }",
-        "golden": "b{font-weight:bolder}"
+        "golden": ".namespace__ b{font-weight:bolder}"
       }
     ]
   },
@@ -139,15 +139,15 @@
       {
         "cssText":
"a { font:12pt Times New Roman, Times,\"Times Old Roman\",serif }",
-        "golden": "a{font:12pt 'Times New Roman', 'Times',"
+        "golden": ".namespace__ a{font:12pt 'Times New Roman', 'Times',"
             + " 'Times Old Roman', serif}",
-        "altGolden": 'a{font:12pt "times new roman" , "times" ,'
+ "altGolden": '.namespace__ a{font:12pt "times new roman" , "times" ,'
             + ' "times old roman" , serif}'
       },
       {
         "cssText": "a { font:bold 12pt Arial Black }",
-        "golden": "a{font:bold 12pt 'Arial Black'}",
-        "altGolden": 'a{font:bold 12pt "arial black"}'
+        "golden": ".namespace__ a{font:bold 12pt 'Arial Black'}",
+        "altGolden": '.namespace__ a{font:bold 12pt "arial black"}'
       }
     ]
   },
@@ -156,27 +156,27 @@
     "tests": [
       {
         "cssText": "a.foo { color:blue }",
-        "golden": "a.foo{color:blue}"
+        "golden": ".namespace__ a.foo{color:blue}"
       },
       {
         "cssText": "#foo { color: blue }",
-        "golden": "#foo{color:blue}"
+        "golden": ".namespace__ #foo-namespace__{color:blue}"
       },
       {
         "cssText": "body.ie6 p { color: blue }",
-        "golden": "body.ie6 p{color:blue}"
+        "golden": ".vdoc-body___.ie6.namespace__ p { color: blue }"
       },
       {
         "cssText": "body { margin: 0; }",
         "golden": ""
-      },  // Not allowed
+      },  // Not allowed.
       {
         "cssText": "body.ie6 { margin: 0; }",
         "golden": ""
-      },  // Not allowed
+      },  // Not allowed.
       {
         "cssText": "* html p { margin: 0; }",
-        "golden": "* html p{margin:0}"
+        "golden": ".namespace__ * html p{margin:0}"
       },
       {
         "cssText": "* html { margin: 0; }",
@@ -188,11 +188,11 @@
       },  // Not allowed
       {
         "cssText": "#foo > #bar { color: blue }",
-        "golden": "#foo > #bar{color:blue}"
+ "golden": ".namespace__ #foo-namespace__ > #bar-namespace__{color:blue}"
       },
       {
         "cssText": "#foo .bar { color: blue }",
-        "golden": "#foo .bar{color:blue}"
+        "golden": ".namespace__ #foo-namespace__ .bar{color:blue}"
       }
     ]
   },
@@ -201,15 +201,15 @@
     "tests": [
       {
"cssText": "a.foo, b#c\\2c d, .e { color:blue }", // "\\2c " -> ","
-        "golden": "a.foo, .e{color:blue}"
+        "golden": ".namespace__ a.foo, .namespace__ .e{color:blue}"
       },
       {
         "cssText": "a.foo, .b_c {color: blue}",
-        "golden": "a.foo, .b_c{color:blue}"
+        "golden": ".namespace__ a.foo, .namespace__ .b_c{color:blue}"
       },
       {
         "cssText": "a.foo, ._c {color: blue}",
-        "golden": "a.foo{color:blue}"
+        "golden": ".namespace__ a.foo{color:blue}"
       },
       {
         "cssText": "a._c {_color: blue; margin:0;}",
@@ -234,11 +234,11 @@
     "tests": [
       {
         "cssText": "a:link, a:badness { color:blue }",
-        "golden": "a:link{color:blue}"
+        "golden": ".namespace__ a:link{color:blue}"
       },
       {
         "cssText": "a:visited { color:blue }",
-        "golden": "a:visited{color:blue}",
+        "golden": ".namespace__ a:visited{color:blue}",
         "messages": []
       },

@@ -249,7 +249,7 @@
       {
         "cssText":
           "a:visited { color:blue; float:left; _float:left; *float:left }",
-        "golden": "a:visited{color:blue}",
+        "golden": ".namespace__ a:visited{color:blue}",
         "messages": [
           {
             "type": "DISALLOWED_CSS_PROPERTY_IN_SELECTOR",
@@ -283,20 +283,20 @@
       {
         "cssText":
           "a:visited { COLOR:blue; FLOAT:left; _FLOAT:left; *FLOAT:left }",
-        "golden": "a:visited{color:blue}"
+        "golden": ".namespace__ a:visited{color:blue}"
       },

       {
         "cssText": "*:visited { color: blue; }",
-        "golden": "a:visited{color:blue}"
+        "golden": ".namespace__ a:visited{color:blue}"
       },
       {
         "cssText": "#foo:visited { color: blue; }",
-        "golden": "a#foo:visited{color:blue}"
+        "golden": ".namespace__ a#foo-namespace__:visited{color:blue}"
       },
       {
         "cssText": ".foo:link { color: blue; }",
-        "golden": "a.foo:link{color:blue}"
+        "golden": ".namespace__ a.foo:link{color:blue}"
       },

       {
@@ -306,19 +306,19 @@
         + "  color: blue;\n"
         + "}",
         "golden": ""
-        + "a#foo:visited, a.bar:link{"
+ + ".namespace__ a#foo-namespace__:visited, .namespace__ a.bar:link{"
         +   "color:blue\n"
         + "}"
-        + "div, p{"
+        + ".namespace__ div, .namespace__ p{"
         +   "padding:1px;"
         +   "color:blue"
         + "}",
         "altGolden": ""  // TODO: Fix difference in order in Java.
-        + "div, p{"
+        + ".namespace__ div, .namespace__ p{"
         +   "padding:1px;"
         +   "color:blue"
         + "}"
-        + "a#foo:visited, a.bar:link{"
+ + ".namespace__ a#foo-namespace__:visited, .namespace__ a.bar:link{"
         +   "color:blue"
         + "}"
       },
@@ -330,7 +330,7 @@
         + "  color: purple"
         + "}",
         "golden": ""
-        + "a#foo-bank{"
+        + ".namespace__ a#foo-bank-namespace__{"
         +   "background:url(\"http://whitelisted-host.com/?bank=X&u=Al\");"
         +   "color:purple"
         + "}",
@@ -346,7 +346,7 @@
         + "  background-image: 'http://whitelisted-host.com/?bank=X&u=Al';"
         + "  color: purple"
         + "}",
-        "golden": "a#foo-bank:visited{color:purple}"
+ "golden": ".namespace__ a#foo-bank-namespace__:visited{color:purple}"
       }
     ]
   },
@@ -356,34 +356,37 @@
       // ok
       {
         "cssText": "#foo { background: url(/bar.png) }",
-        "golden": "#foo{background:url(\"/foo/bar.png\")}"
+        "golden": ".namespace__ #foo-namespace__"
+          + "{background:url(\"/foo/bar.png\")}"
       },
       {
         "cssText": "#foo { background: url('/bar.png') }",
-        "golden": "#foo{background:url(\"/foo/bar.png\")}",
-        "altGolden": '#foo{}'
+        "golden": ".namespace__ #foo-namespace__"
+          + "{background:url(\"/foo/bar.png\")}"
       },
       {
         "cssText": "#foo { background: '/bar.png' }",
-        "golden": "#foo{background:url(\"/foo/bar.png\")}"
+        "golden": ".namespace__ #foo-namespace__"
+          + "{background:url(\"/foo/bar.png\")}"
       },
       {
         "cssText":
           "#foo { background: 'http://whitelisted-host.com/blinky.gif' }",
         "golden":
-          "#foo{background:url(\"http://whitelisted-host.com/blinky.gif\")}"
+          ".namespace__ #foo-namespace__"
+          + "{background:url(\"http://whitelisted-host.com/blinky.gif\")}"
       },

       // disallowed
       {
         "cssText": "#foo { background: url('http://cnn.com/bar.png') }",
         "golden": "",
-        "altGolden": "#foo{}"
+        "altGolden": ".namespace__ #foo-namespace__{}"
       },
       {
         "cssText": "#foo { background: 'http://cnn.com/bar.png' }",
         "golden": "",
-        "altGolden": "#foo{}"
+        "altGolden": ".namespace__ #foo-namespace__{}"
       }
     ]
   },
@@ -394,7 +397,7 @@
     "tests": [
       {
         "cssText": "div * { margin: 0; }",
-        "golden": "div *{margin:0}"
+        "golden": ".namespace__ div *{margin:0}"
       }
     ]
   },
@@ -403,13 +406,13 @@
     "tests": [
       {
         "cssText": "div { padding: 10 0 5.0 4 }",
-        "golden": "div{padding:10px 0 5.0px 4px}",
-        "altGolden": "div{padding:10 0 5.0 4}"
+        "golden": ".namespace__ div{padding:10px 0 5.0px 4px}",
+        "altGolden": ".namespace__ div{padding:10 0 5.0 4}"
       },
       {
         "cssText": "div { margin: -5 5; z-index: 2 }",
-        "golden": "div{margin:-5px 5px;z-index:2}",
-        "altGolden": "div{margin:-5 5;z-index:2}"
+        "golden": ".namespace__ div{margin:-5px 5px;z-index:2}",
+        "altGolden": ".namespace__ div{margin:-5 5;z-index:2}"
       }
     ]
   },
@@ -426,7 +429,7 @@
         + "  font-weight: bold\n"
         + "}",
         "golden": ""
-        + "p{"
+        + ".namespace__ p{"
         +   "color:blue;"
         +   "*color:red;"  // Good user agent hack
         +   "background-color:green;"
@@ -434,7 +437,7 @@
         +   "font-weight:bold"
         + "}",
         "altGolden": ""
-        + "p{"
+        + ".namespace__ p{"
         +   "color:blue;"
         // TODO: Implement support for user-agent hacks.
         //+   "*color:red;"  // Good user agent hack
@@ -455,9 +458,9 @@
       },
       {
         "cssText": "a.c {_color: blue; margin:0;}",
-        "golden": "a.c{_color:blue;margin:0}",
+        "golden": ".namespace__ a.c{_color:blue;margin:0}",
         // TODO: implement user agent hacks
-        "altGolden": "a.c{margin:0}",
+        "altGolden": ".namespace__ a.c{margin:0}",
         "messages": []
       }
     ]
@@ -467,9 +470,9 @@
     "tests": [
       {
         "cssText": "a.c { color: LightSlateGray; background: ivory; }",
-        "golden": "a.c {\n  color: #789;\n  background: #fffff0\n}",
+ "golden": ".namespace__ a.c {\n color: #789;\n background: #fffff0\n}",
         // TODO: see if special color names work when quoted.
-        "altGolden": "a.c{color:lightslategray;background:ivory}",
+ "altGolden": ".namespace__ a.c{color:lightslategray;background:ivory}",
         "messages": [
           {
             "type": "NON_STANDARD_COLOR",
@@ -496,12 +499,12 @@
     "tests": [
       {
         "cssText": "#foo { position: absolute; left: 0px; top: 0px }",
-        "golden": "#foo{position:absolute;left:0px;top:0px}",
+ "golden": ".namespace__ #foo-namespace__{position:absolute;left:0px;top:0px}",
         "messages": []
       },
       {
         "cssText": "#foo { position: fixed; left: 0px; top: 0px }",
-        "golden": "#foo{left:0px;top:0px}",
+        "golden": ".namespace__ #foo-namespace__{left:0px;top:0px}",
         "messages": [
           // TODO(mikesamuel): fix message.
           // "fixed" is well-formed but disallowed.
=======================================
--- /trunk/tests/com/google/caja/plugin/templates/TemplateCompilerTest.java Tue Feb 28 15:48:15 2012 +++ /trunk/tests/com/google/caja/plugin/templates/TemplateCompilerTest.java Wed Apr 4 22:47:53 2012
@@ -35,6 +35,7 @@
 import com.google.caja.parser.js.Identifier;
 import com.google.caja.parser.js.TranslatedCode;
 import com.google.caja.plugin.CssDynamicExpressionRewriter;
+import com.google.caja.plugin.CssRewriter;
 import com.google.caja.plugin.JobEnvelope;
 import com.google.caja.plugin.LoaderType;
 import com.google.caja.plugin.Placeholder;
@@ -861,6 +862,9 @@
       if (n.getFirstChild() != null) {
         String text = n.getFirstChild().getNodeValue();
         CssTree.StyleSheet css = css(fromString(text, pos));
+        CssRewriter rw = new CssRewriter(
+            null, CssSchema.getDefaultCss21Schema(mq), mq);
+        rw.rewrite(AncestorChain.instance(css));
         CssDynamicExpressionRewriter rrw =
             new CssDynamicExpressionRewriter(meta);
         rrw.rewriteCss(css);