Revision: 4809
Author:   [email protected]
Date:     Fri Mar 16 09:15:11 2012
Log:      Integrate CSS rewriter tests with client side CSS rewriter
http://codereview.appspot.com/5836053

This fixes a number of bugs in the client side CSS rewriter and
wires up tests.

Following CLs will:
1. add another parameter to sanitizeStylesheet to take a URL policy
   and re-enable the background-image tests for the client side rewriter
2. remove trivial differences in the output between the two rewriters
   to deal with ordering of split rulesets, choice of quotes,
   whether font-names are case-preserved
3. implement case-sensitive rule bodies

[email protected]

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

Added:
 /trunk/tests/com/google/caja/plugin/CssStylesheetTest.java
 /trunk/tests/com/google/caja/plugin/css-stylesheet-test.html
 /trunk/tests/com/google/caja/plugin/css-stylesheet-test.js
Modified:
 /trunk/src/com/google/caja/plugin/cssparser.js
 /trunk/src/com/google/caja/plugin/sanitizecss.js
 /trunk/tests/com/google/caja/plugin/CssRewriterTest.java
 /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js

=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/plugin/CssStylesheetTest.java Fri Mar 16 09:15:11 2012
@@ -0,0 +1,30 @@
+// Copyright (C) 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.caja.plugin;
+
+import com.google.caja.util.CajaTestCase;
+import com.google.caja.util.RhinoTestBed;
+
+/**
+ * JUnit wrapper for CSS stylesheet JSUnit unittests.
+ *
+ * @author [email protected]
+ */
+public final class CssStylesheetTest extends CajaTestCase {
+  public final void testHtmlSanitizer() throws Exception {
+    RhinoTestBed.runJsUnittestFromHtml(
+        html(fromResource("css-stylesheet-test.html")));
+  }
+}
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/plugin/css-stylesheet-test.html Fri Mar 16 09:15:11 2012
@@ -0,0 +1,32 @@
+<!doctype html>
+<!--
+ - Copyright (C) 2012 Google Inc.
+ -
+ - Licensed under the Apache License, Version 2.0 (the "License");
+ - you may not use this file except in compliance with the License.
+ - You may obtain a copy of the License at
+ -
+ -      http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing, software
+ - distributed under the License is distributed on an "AS IS" BASIS,
+ - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ - See the License for the specific language governing permissions and
+ - limitations under the License.
+-->
+<title>CSS Lexer Test</title>
+
+<style>
+p { font-family: monospace; white-space: pre-wrap }
+</style>
+<script type="text/javascript" src="../../../../js/jsunit/2.2/jsUnitCore.js"
+ ></script>
+<script type="text/javascript" src="html4-defs.js"></script>
+<script type="text/javascript" src="css-defs.js"></script>
+<script type="text/javascript" src="jsunit.js"></script>
+<script type="text/javascript" src="csslexer.js"></script>
+<script type="text/javascript" src="cssparser.js"></script>
+<script type="text/javascript" src="sanitizecss.js"></script>
+<script type="text/javascript" src="css-stylesheet-test.js"></script>
+<script type="text/javascript" src="css-stylesheet-tests.js"></script>
+<script>jsunitRun()</script>
=======================================
--- /dev/null
+++ /trunk/tests/com/google/caja/plugin/css-stylesheet-test.js Fri Mar 16 09:15:11 2012
@@ -0,0 +1,47 @@
+// Copyright (C) 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Called from css-stylesheet-tests.js in a JSONP style.
+function runCssSelectorTests(testGroups) {
+  function testCssStylesheets() {
+    for (var j = 0, m = testGroups.length; j < m; ++j) {
+      var testGroup = testGroups[j];
+      var name = testGroup.test_name;
+      assertEquals('testGroups[' + j + '].name', 'string', typeof name);
+      var tests = testGroup.tests;
+      for (var i = 0, n = tests.length; i < n; ++i) {
+        var test = tests[i];
+        var input = test.cssText;
+        var golden = test.golden;
+        assertEquals(
+            name + ' tests[' + i + '].cssText', 'string', typeof input);
+        assertEquals(
+            name + ' tests[' + i + '].golden', 'string', typeof golden);
+
+        var actual = sanitizeStylesheet(test.cssText);
+        // The Java version produces property groups without a trailing
+        // ';' since the semicolon is technically a separator in CSS.
+        // This JavaScript version does not because it is simpler to
+        // just treat it as a terminator.
+        actual = actual.replace(/;\}/g, '}');
+        if (golden !== actual && 'string' === typeof test.altGolden) {
+          golden = test.altGolden;
+        }
+ assertEquals('stylesheet test ' + i + ': ' + input, golden, actual);
+      }
+    }
+  }
+  // Create a test method that will be called by jsUnit.
+  jsunitRegister('testCssStylesheets', testCssStylesheets);
+}
=======================================
--- /trunk/src/com/google/caja/plugin/cssparser.js      Thu Jan 19 09:04:11 2012
+++ /trunk/src/com/google/caja/plugin/cssparser.js      Fri Mar 16 09:15:11 2012
@@ -173,9 +173,10 @@
     // Don't include any trailing space in the selector slice.
     if (e > s && toks[e-1] === ' ') { --e; }
     var tok = toks[i];
-    if (tok != '{') {
-      // Skip one token on a malformed declaration group.
-      return i+1;
+    ++i;  // Skip over '{'
+    if (tok !== '{') {
+      // Skips past the '{' when there is a malformed input.
+      return i;
     }
     if (handler.startRuleset) {
       handler.startRuleset(toks.slice(s, e));
=======================================
--- /trunk/src/com/google/caja/plugin/sanitizecss.js Thu Mar 15 12:38:53 2012 +++ /trunk/src/com/google/caja/plugin/sanitizecss.js Fri Mar 16 09:15:11 2012
@@ -308,7 +308,7 @@
       while (start < end) {
         tok = selectors[start];
         if (tok.charAt(0) === '#') {
-          if (/^#_|__$/.test(tok)) { return null; }
+          if (/^#_|__$|[^#0-9A-Za-z:_\-]/.test(tok)) { return null; }
           classId += tok;
         } else if (tok === '.') {
           if (++start < end
@@ -449,7 +449,7 @@
                   selector = 'head > html';
                   removeHistoryInsensitiveSelectors = true;
                 }
-                safeCss.push(selector);
+                safeCss.push(selector, '{');
               }
             }
             blockStack.push(
@@ -466,7 +466,7 @@
                 // history sensitive case.
                 : {
                     historySensitiveSelectors: historySensitiveSelectors,
-                    endOfSelecctors: safeCss.length,
+                    endOfSelectors: safeCss.length - 1,  // 1 is open curly
                     removeHistoryInsensitiveSelectors:
                        removeHistoryInsensitiveSelectors
                   });
@@ -474,17 +474,21 @@
           endRuleset: function () {
             var rules = blockStack.pop();
             var propertiesEnd = safeCss.length;
-            if (!elide && rules) {
-              var extraSelectors = rules.historySensitiveSelectors;
-              if (extraSelectors.length) {
- var propertyGroupTokens = safeCss.slice(rules.endOfSelectors);
-                safeCss.push(extraSelectors.join(', '));
-                safeCss.push.apply(
- safeCss, sanitizeHistorySensitive(propertyGroupTokens));
+            if (!elide) {
+              safeCss.push('}');
+              if (rules) {
+                var extraSelectors = rules.historySensitiveSelectors;
+                if (extraSelectors.length) {
+ var propertyGroupTokens = safeCss.slice(rules.endOfSelectors);
+                  safeCss.push(extraSelectors.join(', '),
+ sanitizeHistorySensitive(propertyGroupTokens));
+                }
               }
             }
             if (rules && rules.removeHistoryInsensitiveSelectors) {
-              safeCss.splice(rules.endOfSelectors - 1, propertiesEnd);
+              safeCss.splice(
+                // -1 and +1 account for curly braces.
+                rules.endOfSelectors - 1, propertiesEnd + 1);
             }
             checkElide();
           },
@@ -502,8 +506,8 @@
           }
         });
     function checkElide() {
-      elide = blockStack.length === 0
-          || blockStack[blockStack.length-1] !== null;
+      elide = blockStack.length !== 0
+          && blockStack[blockStack.length-1] !== null;
     }
     return safeCss.join('');
   };
=======================================
--- /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Thu Mar 15 12:38:53 2012 +++ /trunk/tests/com/google/caja/plugin/CssRewriterTest.java Fri Mar 16 09:15:11 2012
@@ -121,6 +121,8 @@
                   .getUnquotedValue();
             } else if ("messages".equals(pname)) {
               messages = (ArrayConstructor) prop.getValueExpr();
+            } else if ("altGolden".equals(pname)) {
+              // OK.
             } else {
               fail(
                   "Unrecognized testcase property " + pname + " in "
@@ -133,10 +135,13 @@
             throw ex;
           }
         }
+
+        String normalizedGolden = "".equals(golden)
+            ? "" : render(css(fromString(golden)));

         mq.getMessages().clear();
         try {
-          runTest(cssText, golden);
+          runTest(cssText, normalizedGolden);
           if (messages != null) {
             for (Expression message : messages.children()) {
               ObjectConstructor messageObj = (ObjectConstructor) message;
=======================================
--- /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js Thu Mar 15 12:38:53 2012 +++ /trunk/tests/com/google/caja/plugin/css-stylesheet-tests.js Fri Mar 16 09:15:11 2012
@@ -20,7 +20,7 @@
  * runtest call, you can parse the rest as a JSON object.
  */

-runTests([
+runCssSelectorTests([
   {
     "test_name": "UnknownTagsRemoved",
     "tests": [
@@ -30,7 +30,7 @@
       },
       {
         "cssText": "a, bogus, i { display: none }",
-        "golden": "a, i {\n  display: none\n}"
+        "golden": "a, i{display:none}"
       }
     ]
   },
@@ -50,7 +50,7 @@
       },
       {
         "cssText": "strike, script, strong { display: none }",
-        "golden": "strike, strong {\n  display: none\n}",
+        "golden": "strike, strong{display:none}",
         "messages": [
           {
             "type": "UNSAFE_TAG",
@@ -76,33 +76,37 @@
       // visibility takes "hidden", not "none"
       {
         "cssText": "a { visibility: none }",
-        "golden": ""
+        "golden": "",
+        // The JS side emits an empty property group while the Java version
+        // does not.
+        "altGolden": "a{}"
       },
       {
         "cssText": "a { visibility: hidden; }",
-        "golden": "a {\n  visibility: hidden\n}"
+        "golden": "a{visibility:hidden}"
       },
       // no such property
       {
         "cssText": "a { bogus: bogus }",
-        "golden": ""
+        "golden": "",
+        "altGolden": "a{}"
       },
       // make sure it doesn't interfere with others
       {
         "cssText": "a { visibility: none; font-weight: bold }",
-        "golden": "a {\n  font-weight: bold\n}"
+        "golden": "a{font-weight:bold}"
       },
       {
         "cssText": "a { font-weight: bold; visibility: none }",
-        "golden": "a {\n  font-weight: bold\n}"
+        "golden": "a{font-weight:bold}"
       },
       {
         "cssText": "a { bogus: bogus; font-weight: bold }",
-        "golden": "a {\n  font-weight: bold\n}"
+        "golden": "a{font-weight:bold}"
       },
       {
         "cssText": "a { font-weight: bold; bogus: bogus }",
-        "golden": "a {\n  font-weight: bold\n}"
+        "golden": "a{font-weight:bold}"
       }
     ]
   },
@@ -112,7 +116,7 @@
       {
         "cssText":
"a { color: blue; content: 'booyah'; text-decoration: underline; }",
-        "golden": "a {\n  color: blue;\n  text-decoration: underline\n}"
+        "golden": "a{color:blue;text-decoration:underline}"
       }
     ]
   },
@@ -125,7 +129,7 @@
       },
       {
"cssText": "a:attr(href) { color: blue } b { font-weight: bolder }",
-        "golden": "b {\n  font-weight: bolder\n}"
+        "golden": "b{font-weight:bolder}"
       }
     ]
   },
@@ -135,12 +139,15 @@
       {
         "cssText":
"a { font:12pt Times New Roman, Times,\"Times Old Roman\",serif }",
-        "golden": "a {\n  font: 12pt 'Times New Roman', 'Times',"
-            + " 'Times Old Roman', serif\n}"
+        "golden": "a{font:12pt 'Times New Roman', 'Times',"
+            + " 'Times Old Roman', serif}",
+        "altGolden": 'a{font:12pt "times new roman" , "times" ,'
+            + ' "times old roman" , serif}'
       },
       {
         "cssText": "a { font:bold 12pt Arial Black }",
-        "golden": "a {\n  font: bold 12pt 'Arial Black'\n}"
+        "golden": "a{font:bold 12pt 'Arial Black'}",
+        "altGolden": 'a{font:bold 12pt "arial black"}'
       }
     ]
   },
@@ -149,15 +156,15 @@
     "tests": [
       {
         "cssText": "a.foo { color:blue }",
-        "golden": "a.foo {\n  color: blue\n}"
+        "golden": "a.foo{color:blue}"
       },
       {
         "cssText": "#foo { color: blue }",
-        "golden": "#foo {\n  color: blue\n}"
+        "golden": "#foo{color:blue}"
       },
       {
         "cssText": "body.ie6 p { color: blue }",
-        "golden": "body.ie6 p {\n  color: blue\n}"
+        "golden": "body.ie6 p{color:blue}"
       },
       {
         "cssText": "body { margin: 0; }",
@@ -169,7 +176,7 @@
       },  // Not allowed
       {
         "cssText": "* html p { margin: 0; }",
-        "golden": "* html p {\n  margin: 0\n}"
+        "golden": "* html p{margin:0}"
       },
       {
         "cssText": "* html { margin: 0; }",
@@ -181,11 +188,11 @@
       },  // Not allowed
       {
         "cssText": "#foo > #bar { color: blue }",
-        "golden": "#foo > #bar {\n  color: blue\n}"
+        "golden": "#foo > #bar{color:blue}"
       },
       {
         "cssText": "#foo .bar { color: blue }",
-        "golden": "#foo .bar {\n  color: blue\n}"
+        "golden": "#foo .bar{color:blue}"
       }
     ]
   },
@@ -194,15 +201,15 @@
     "tests": [
       {
"cssText": "a.foo, b#c\\2c d, .e { color:blue }", // "\\2c " -> ","
-        "golden": "a.foo, .e {\n  color: blue\n}"
+        "golden": "a.foo, .e{color:blue}"
       },
       {
         "cssText": "a.foo, .b_c {color: blue}",
-        "golden": "a.foo, .b_c {\n  color: blue\n}"
+        "golden": "a.foo, .b_c{color:blue}"
       },
       {
         "cssText": "a.foo, ._c {color: blue}",
-        "golden": "a.foo {\n  color: blue\n}"
+        "golden": "a.foo{color:blue}"
       },
       {
         "cssText": "a._c {_color: blue; margin:0;}",
@@ -227,11 +234,13 @@
     "tests": [
       {
         "cssText": "a:link, a:badness { color:blue }",
-        "golden": "a:link {\n  color: blue\n}"
+        "golden": "a:link{color:blue}",
+        "altGolden": "a:link{}"  // TODO: Allow history sensitive in JS.
       },
       {
         "cssText": "a:visited { color:blue }",
-        "golden": "a:visited {\n  color: blue\n}",
+        "golden": "a:visited{color:blue}",
+ "altGolden": "a:visited{}", // TODO: Allow history sensitive in JS.
         "messages": []
       },

@@ -242,7 +251,8 @@
       {
         "cssText":
           "a:visited { color:blue; float:left; _float:left; *float:left }",
-        "golden": "a:visited {\n  color: blue\n}",
+        "golden": "a:visited{color:blue}",
+        "altGolden": "a:visited{}",
         "messages": [
           {
             "type": "DISALLOWED_CSS_PROPERTY_IN_SELECTOR",
@@ -276,20 +286,24 @@
       {
         "cssText":
           "a:visited { COLOR:blue; FLOAT:left; _FLOAT:left; *FLOAT:left }",
-        "golden": "a:visited {\n  color: blue\n}"
+        "golden": "a:visited{color:blue}",
+        "altGolden": "a:visited{}"  // TODO
       },

       {
         "cssText": "*:visited { color: blue; }",
-        "golden": "a:visited {\n  color: blue\n}"
+        "golden": "a:visited{color:blue}",
+        "altGolden": "a:visited{}"  // TODO
       },
       {
         "cssText": "#foo:visited { color: blue; }",
-        "golden": "a#foo:visited {\n  color: blue\n}"
+        "golden": "a#foo:visited{color:blue}",
+        "altGolden": "a#foo:visited{}"  // TODO
       },
       {
         "cssText": ".foo:link { color: blue; }",
-        "golden": "a.foo:link {\n  color: blue\n}"
+        "golden": "a.foo:link{color:blue}",
+        "altGolden": "a.foo:link{}"  // TODO
       },

       {
@@ -299,12 +313,20 @@
         + "  color: blue;\n"
         + "}",
         "golden": ""
-        + "a#foo:visited, a.bar:link {\n"
-        + "  color: blue\n"
-        + "}\n"
-        + "div, p {\n"
-        + "  padding: 1px;\n"
-        + "  color: blue\n"
+        + "a#foo:visited, a.bar:link{"
+        +   "color:blue\n"
+        + "}"
+        + "div, p{"
+        +   "padding:1px;"
+        +   "color:blue"
+        + "}",
+        "altGolden": ""  // TODO: Fix difference in order in Java.
+        + "div, p{"
+        +   "padding:1px;"
+        +   "color:blue"
+        + "}"
+        + "a#foo:visited, a.bar:link{"
+        //+   "color:blue\n"  // TODO
         + "}"
       },

@@ -315,10 +337,12 @@
         + "  color: purple"
         + "}",
         "golden": ""
-        + "a#foo-bank {\n"
- + " background: url('http://whitelisted-host.com/?bank=X&u=Al');\n"
-        + "  color: purple\n"
+        + "a#foo-bank{"
+        +   "background:url('http://whitelisted-host.com/?bank=X&u=Al');"
+        +   "color:purple"
         + "}",
+        // TODO: integrate URL policy into CSS sanitizer.
+        "altGolden": "a#foo-bank{color:purple}",
         "messages": []
       },
       // Differs from the previous only in that it has the :visited pseudo
@@ -331,10 +355,8 @@
         + "  background-image: 'http://whitelisted-host.com/?bank=X&u=Al';"
         + "  color: purple"
         + "}",
-        "golden": ""
-        + "a#foo-bank:visited {\n"
-        + "  color: purple\n"
-        + "}"
+        "golden": "a#foo-bank:visited{color:purple}",
+        "altGolden": "a#foo-bank:visited{}" // TODO
       }
     ]
   },
@@ -344,31 +366,42 @@
       // ok
       {
         "cssText": "#foo { background: url(/bar.png) }",
-        "golden": "#foo {\n  background: url('/foo/bar.png')\n}"
+        "golden": "#foo{background:url('/foo/bar.png')}",
+        //"altGolden": '#foo{backgroud:url("/foo/bar.png")}'  TODO
+        "altGolden": '#foo{}'
       },
       {
         "cssText": "#foo { background: url('/bar.png') }",
-        "golden": "#foo {\n  background: url('/foo/bar.png')\n}"
+        "golden": "#foo{background:url('/foo/bar.png')}",
+        //"altGolden": '#foo{background:url("/foo/bar.png")}'  TODO
+        "altGolden": '#foo{}'
       },
       {
         "cssText": "#foo { background: '/bar.png' }",
-        "golden": "#foo {\n  background: url('/foo/bar.png')\n}"
+        "golden": "#foo{background:url('/foo/bar.png')}",
+        //"altGolden": '#foo{background:url("/foo/bar.png")}'  TODO
+        "altGolden": '#foo{}'
       },
       {
         "cssText":
           "#foo { background: 'http://whitelisted-host.com/blinky.gif' }",
         "golden":
- "#foo {\n background: url('http://whitelisted-host.com/blinky.gif')\n}"
+          "#foo{background:url('http://whitelisted-host.com/blinky.gif')}",
+        "altGolden":
+//        '#foo{background:url("http://whitelisted-host.com/blinky.gif";)}'
+          '#foo{}'
       },

       // disallowed
       {
         "cssText": "#foo { background: url('http://cnn.com/bar.png') }",
-        "golden": ""
+        "golden": "",
+        "altGolden": "#foo{}"
       },
       {
         "cssText": "#foo { background: 'http://cnn.com/bar.png' }",
-        "golden": ""
+        "golden": "",
+        "altGolden": "#foo{}"
       }
     ]
   },
@@ -379,7 +412,7 @@
     "tests": [
       {
         "cssText": "div * { margin: 0; }",
-        "golden": "div * {\n  margin: 0\n}"
+        "golden": "div *{margin:0}"
       }
     ]
   },
@@ -388,11 +421,13 @@
     "tests": [
       {
         "cssText": "div { padding: 10 0 5.0 4 }",
-        "golden": "div {\n  padding: 10px 0 5.0px 4px\n}"
+        "golden": "div{padding:10px 0 5.0px 4px}",
+        "altGolden": "div{padding:10 0 5.0 4}"
       },
       {
         "cssText": "div { margin: -5 5; z-index: 2 }",
-        "golden": "div {\n  margin: -5px 5px;\n  z-index: 2\n}"
+        "golden": "div{margin:-5px 5px;z-index:2}",
+        "altGolden": "div{margin:-5 5;z-index:2}"
       }
     ]
   },
@@ -409,12 +444,21 @@
         + "  font-weight: bold\n"
         + "}",
         "golden": ""
-        + "p {\n"
-        + "  color: blue;\n"
-        + "  *color: red;\n"  // Good user agent hack
-        + "  background-color: green;\n"
+        + "p{"
+        +   "color:blue;"
+        +   "*color:red;"  // Good user agent hack
+        +   "background-color:green;"
         // Bad user-agent hack removed.
-        + "  font-weight: bold\n"
+        +   "font-weight:bold"
+        + "}",
+        "altGolden": ""
+        + "p{"
+        +   "color:blue;"
+        // TODO: Implement support for user-agent hacks.
+        //+   "*color:red;"  // Good user agent hack
+        +   "background-color:green;"
+        // Bad user-agent hack removed.
+        +   "font-weight:bold"
         + "}",
         "messages": [
           {
@@ -429,7 +473,9 @@
       },
       {
         "cssText": "a.c {_color: blue; margin:0;}",
-        "golden": "a.c {\n  _color: blue;\n  margin: 0\n}",
+        "golden": "a.c{_color:blue;margin:0}",
+        // TODO: implement user agent hacks
+        "altGolden": "a.c{margin:0}",
         "messages": []
       }
     ]
@@ -440,6 +486,8 @@
       {
         "cssText": "a.c { color: LightSlateGray; background: ivory; }",
         "golden": "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}",
         "messages": [
           {
             "type": "NON_STANDARD_COLOR",
@@ -466,12 +514,12 @@
     "tests": [
       {
         "cssText": "#foo { position: absolute; left: 0px; top: 0px }",
- "golden": "#foo {\n position: absolute;\n left: 0px;\n top: 0px\n}",
+        "golden": "#foo{position:absolute;left:0px;top:0px}",
         "messages": []
       },
       {
         "cssText": "#foo { position: fixed; left: 0px; top: 0px }",
-        "golden": "#foo {\n  left: 0px;\n  top: 0px\n}",
+        "golden": "#foo{left:0px;top:0px}",
         "messages": [
           // TODO(mikesamuel): fix message.
           // "fixed" is well-formed but disallowed.

Reply via email to