|
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 2012Log: 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);