Adding SafeStyles interface that represents strings that are safe for use in a
style attribute (similar to SafeHtml in a text context). Currently, templates
generate a warning if an argument is used in a CSS context, which spams the
logger, but there is no way to prevent the warning.

Review at http://gwt-code-reviews.appspot.com/1384801

Review by: xtof@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9976 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
index bd4c872..4205f84 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
@@ -25,6 +25,8 @@
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -63,8 +65,8 @@
   }
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<input type=\"text\" value=\"{0}\" style=\"color:{1}\" tabindex=\"-1\"/>")
-    SafeHtml input(String value, String color);
+    @Template("<input type=\"text\" value=\"{0}\" style=\"{1}\" tabindex=\"-1\"/>")
+    SafeHtml input(String value, SafeStyles color);
   }
 
   /**
@@ -139,8 +141,9 @@
       String pendingValue = (viewData == null) ? null : viewData.getValue();
       boolean invalid = (viewData == null) ? false : viewData.isInvalid();
 
-      sb.append(template.input(pendingValue != null ? pendingValue : value,
-          pendingValue != null ? (invalid ? "red" : "blue") : ("black")));
+      String color = pendingValue != null ? (invalid ? "red" : "blue") : "black";
+      SafeStyles safeColor = SafeStylesUtils.fromTrustedString("color: " + color + ";");
+      sb.append(template.input(pendingValue != null ? pendingValue : value, safeColor));
 
       if (invalid) {
         sb.appendHtmlConstant("&nbsp;<span style='color:red;'>");
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
index dc00d9b..7fec729 100644
--- a/user/src/com/google/gwt/cell/client/IconCellDecorator.java
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -20,6 +20,9 @@
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesBuilder;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -38,28 +41,26 @@
 public class IconCellDecorator<C> implements Cell<C> {
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<div style=\"position:relative;padding-{0}:{1}px;zoom:1;\">{2}<div>{3}</div></div>")
-    SafeHtml outerDiv(String direction, int width, SafeHtml icon,
-        SafeHtml cellContents);
+    @Template("<div style=\"{0}position:relative;zoom:1;\">{1}<div>{2}</div></div>")
+    SafeHtml outerDiv(SafeStyles padding, SafeHtml icon, SafeHtml cellContents);
 
     /**
      * The wrapper around the image vertically aligned to the bottom.
      */
-    @Template("<div style=\"position:absolute;{0}:0px;bottom:0px;line-height:0px;\">{1}</div>")
-    SafeHtml imageWrapperBottom(String direction, SafeHtml image);
+    @Template("<div style=\"{0}position:absolute;bottom:0px;line-height:0px;\">{1}</div>")
+    SafeHtml imageWrapperBottom(SafeStyles styles, SafeHtml image);
 
     /**
      * The wrapper around the image vertically aligned to the middle.
      */
-    @Template("<div style=\"position:absolute;{0}:0px;top:50%;line-height:0px;"
-        + "margin-top:-{1}px;\">{2}</div>")
-    SafeHtml imageWrapperMiddle(String direction, int halfHeight, SafeHtml image);
+    @Template("<div style=\"{0}position:absolute;top:50%;line-height:0px;\">{1}</div>")
+    SafeHtml imageWrapperMiddle(SafeStyles styles, SafeHtml image);
 
     /**
      * The wrapper around the image vertically aligned to the top.
      */
-    @Template("<div style=\"position:absolute;{0}:0px;top:0px;line-height:0px;\">{1}</div>")
-    SafeHtml imageWrapperTop(String direction, SafeHtml image);
+    @Template("<div style=\"{0}position:absolute;top:0px;line-height:0px;\">{1}</div>")
+    SafeHtml imageWrapperTop(SafeStyles styles, SafeHtml image);
   }
 
   /**
@@ -78,6 +79,8 @@
 
   private final int imageWidth;
 
+  private final SafeStyles outerDivPadding;
+  
   private final SafeHtml placeHolderHtml;
 
   /**
@@ -99,8 +102,8 @@
    * @param valign the vertical alignment attribute of the contents
    * @param spacing the pixel space between the icon and the cell
    */
-  public IconCellDecorator(ImageResource icon, Cell<C> cell,
-      VerticalAlignmentConstant valign, int spacing) {
+  public IconCellDecorator(ImageResource icon, Cell<C> cell, VerticalAlignmentConstant valign,
+      int spacing) {
     if (template == null) {
       template = GWT.create(Template.class);
     }
@@ -108,6 +111,8 @@
     this.iconHtml = getImageHtml(icon, valign, false);
     this.imageWidth = icon.getWidth() + spacing;
     this.placeHolderHtml = getImageHtml(icon, valign, true);
+    this.outerDivPadding =
+        SafeStylesUtils.fromTrustedString("padding-" + direction + ": " + imageWidth + "px;");
   }
 
   public boolean dependsOnSelection() {
@@ -135,9 +140,8 @@
   public void render(Context context, C value, SafeHtmlBuilder sb) {
     SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
     cell.render(context, value, cellBuilder);
-
-    sb.append(template.outerDiv(direction, imageWidth, isIconUsed(value)
-        ? getIconHtml(value) : placeHolderHtml, cellBuilder.toSafeHtml()));
+    sb.append(template.outerDiv(outerDivPadding, isIconUsed(value) ? getIconHtml(value)
+        : placeHolderHtml, cellBuilder.toSafeHtml()));
   }
 
   public boolean resetFocus(Context context, Element parent, C value) {
@@ -179,8 +183,7 @@
    * @param isPlaceholder if true, do not include the background image
    * @return the rendered HTML
    */
-  SafeHtml getImageHtml(ImageResource res, VerticalAlignmentConstant valign,
-      boolean isPlaceholder) {
+  SafeHtml getImageHtml(ImageResource res, VerticalAlignmentConstant valign, boolean isPlaceholder) {
     // Get the HTML for the image.
     SafeHtml image;
     if (isPlaceholder) {
@@ -191,13 +194,16 @@
     }
 
     // Create the wrapper based on the vertical alignment.
+    SafeStylesBuilder cssStyles =
+        new SafeStylesBuilder().appendTrustedString(direction + ":0px;");
     if (HasVerticalAlignment.ALIGN_TOP == valign) {
-      return template.imageWrapperTop(direction, image);
+      return template.imageWrapperTop(cssStyles.toSafeStyles(), image);
     } else if (HasVerticalAlignment.ALIGN_BOTTOM == valign) {
-      return template.imageWrapperBottom(direction, image);
+      return template.imageWrapperBottom(cssStyles.toSafeStyles(), image);
     } else {
       int halfHeight = (int) Math.round(res.getHeight() / 2.0);
-      return template.imageWrapperMiddle(direction, halfHeight, image);
+      cssStyles.appendTrustedString("margin-top:-" + halfHeight + "px;");
+      return template.imageWrapperMiddle(cssStyles.toSafeStyles(), image);
     }
   }
 
diff --git a/user/src/com/google/gwt/safecss/SafeCss.gwt.xml b/user/src/com/google/gwt/safecss/SafeCss.gwt.xml
new file mode 100644
index 0000000..4060626
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/SafeCss.gwt.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2011 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.
+-->
+
+<!-- SafeCss - facilities for avoiding XSS attacks                         -->
+<!--                                                                       -->
+<module>
+  <source path="shared"/>
+</module>
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStyles.java b/user/src/com/google/gwt/safecss/shared/SafeStyles.java
new file mode 100644
index 0000000..62bd30b
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/shared/SafeStyles.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+import java.io.Serializable;
+
+/**
+ * An object that implements this interface encapsulates zero or more CSS
+ * properties that are guaranteed to be safe to use (with respect to potential
+ * Cross-Site-Scripting vulnerabilities) in a CSS (Cascading Style Sheet)
+ * attribute context. A CSS attribute context can be inside of a CSS rule in a
+ * {@code style} element, or inside the {@code style} attribute of a DOM
+ * element.
+ * 
+ * <p>
+ * Note on usage: {@link SafeStyles} should be used to ensure user input is not
+ * executed in the browser. {@link SafeStyles} should not be used to sanitize
+ * input before sending it to the server: The server cannot rely on the type
+ * contract of {@link SafeStyles} values received from clients, because a
+ * malicious client could provide maliciously crafted serialized forms of
+ * implementations of this type that violate the type contract.
+ * 
+ * <p>
+ * All implementing classes must maintain the class invariant (by design and
+ * implementation and/or convention of use), that invoking {@link #asString()}
+ * on any instance will return a string that is safe to assign to a CSS
+ * attribute in a browser, in the sense that doing so must not cause execution
+ * of script in the browser. Generally, {@link SafeStyles} should be of the form
+ * {@code cssPropertyName:value;}, where neither the name nor the value contain
+ * malicious scripts.
+ * 
+ * <p>
+ * {@link SafeStyles} may never contain literal angle brackets. Otherwise, it
+ * could be unsafe to place a {@link SafeStyles} into a &lt;style&gt; tag (where
+ * it can't be HTML escaped). For example, if the {@link SafeStyles} containing
+ * "<code>font: 'foo &lt;style&gt;&lt;script&gt;evil&lt;/script&gt;</code>'" is
+ * used in a style sheet in a &lt;style&gt; tag, this could then break out of
+ * the style context into HTML.
+ * 
+ * <p>
+ * {@link SafeStyles} may contain literal single or double quotes, and as such
+ * the entire style string must be escaped when used in a style attribute (if
+ * this were not the case, the string could contain a matching quote that would
+ * escape from the style attribute).
+ * 
+ * <p>
+ * Furthermore, values of this type must be composable, i.e. for any two values
+ * {@code A} and {@code B} of this type, {@code A.asString() + B.asString()}
+ * must itself be a value that satisfies the {@link SafeStyles} type constraint.
+ * This requirement implies that for any value {@code A} of this type,
+ * {@code A.asString()} must not end in a "CSS value" or "CSS name" context. For
+ * example, a value of {@code background:url("} or {@code font-} would not
+ * satisfy the {@link SafeStyles} contract. This is because concatenating such
+ * strings with a second value that itself does not contain unsafe CSS can
+ * result in an overall string that does. For example, if
+ * {@code javascript:evil())"} is appended to {@code background:url("}, the
+ * resulting string may result in the execution of a malicious script.
+ * 
+ * <p>
+ * The following example values comply with this type's contract:
+ * <ul>
+ * <li><code>width: 1em;</code></li>
+ * <li><code>height:1em;</code></li>
+ * <li><code>width: 1em;height: 1em;</code></li>
+ * <li><code>background:url('http://url');</code></li>
+ * </ul>
+ * In addition, the empty string is safe for use in a CSS attribute.
+ * 
+ * <p>
+ * The following example values do <em>not</em> comply with this type's contract:
+ * <ul>
+ * <li><code>background: red</code> (missing a trailing semi-colon)</li>
+ * <li><code>background:</code> (missing a value and a trailing semi-colon)</li>
+ * <li><code>1em</code> (missing an attribute name, which provides context for the value)</li>
+ * </ul>
+ * 
+ * <p>
+ * All implementations must implement equals() and hashCode() to behave
+ * consistently with the result of asString().equals() and asString.hashCode().
+ * 
+ * <p>
+ * Implementations must not return {@code null} from {@link #asString()}.
+ */
+public interface SafeStyles extends Serializable {
+  /*
+   * Notes regarding serialization:
+   * 
+   * - It may be reasonable to allow deserialization on the client of objects
+   * serialized on the server (i.e. RPC responses), based on the assumption that
+   * server code is trusted and would not provide a malicious serialized form
+   * (if a MitM were able to modify server responses, the client would be fully
+   * compromised in any case). However, the GWT RPC framework currently does not
+   * seem to provide a facility for restricting deserialization on the Server
+   * only (though this shouldn't be difficult to implement through a custom
+   * SerializationPolicy)
+   * 
+   * - Some implementations of SafeStyles would in principle be able to enforce
+   * their class invariant on deserialization. However, the GWT RPC framework
+   * does not provide for an equivalent of readResolve() to enforce the class
+   * invariant on deserialization.
+   */
+
+  /**
+   * Returns this object's contained CSS as a string.
+   * 
+   * <p>
+   * Based on this class' contract, the returned value will be non-null and a
+   * string that is safe to use in an CSS attribute context.
+   * 
+   * @return the contents as a String
+   */
+  String asString();
+
+  /**
+   * Compares this string to the specified object. Must be equal to
+   * asString().equals().
+   * 
+   * @param anObject the object to compare to
+   */
+  boolean equals(Object anObject);
+
+  /**
+   * Returns a hash code for this string. Must be equal to
+   * asString().hashCode().
+   */
+  int hashCode();
+}
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStylesBuilder.java b/user/src/com/google/gwt/safecss/shared/SafeStylesBuilder.java
new file mode 100644
index 0000000..a1d8c02
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/shared/SafeStylesBuilder.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+/**
+ * A builder that facilitates the building up of XSS-safe CSS attribute strings
+ * from {@link SafeStyles}. It is used essentially like a {@link StringBuilder},
+ * but access {@link SafeStyles} instead of Strings.
+ * 
+ * <p>
+ * The accumulated XSS-safe {@link SafeStyles} can be obtained in the form of a
+ * {@link SafeStyles} via the {@link #toSafeStyles()} method.
+ * 
+ * <p>
+ * This class is not thread-safe.
+ */
+public final class SafeStylesBuilder {
+
+  private final StringBuilder sb = new StringBuilder();
+
+  /**
+   * Constructs an empty {@link SafeStylesBuilder}.
+   */
+  public SafeStylesBuilder() {
+  }
+
+  /**
+   * Appends the contents of another {@link SafeStyles} object, without applying
+   * any escaping or sanitization to it.
+   * 
+   * @param styles the {@link SafeStyles} to append
+   * @return a reference to this object
+   */
+  public SafeStylesBuilder append(SafeStyles styles) {
+    sb.append(styles.asString());
+    return this;
+  }
+
+  /**
+   * <p>
+   * Appends {@link SafeStyles} constructed from a trusted string, i.e., without
+   * escaping the string. Only minimal checks are performed. The calling code
+   * should be carefully reviewed to ensure the argument meets the
+   * {@link SafeStyles} contract.
+   * 
+   * <p>
+   * Generally, {@link SafeStyles} should be of the form
+   * {@code cssPropertyName:value;}, where neither the name nor the value
+   * contain malicious scripts.
+   * 
+   * <p>
+   * {@link SafeStyles} may never contain literal angle brackets. Otherwise, it
+   * could be unsafe to place a {@link SafeStyles} into a &lt;style&gt; tag
+   * (where it can't be HTML escaped). For example, if the {@link SafeStyles}
+   * containing "
+   * <code>font: 'foo &lt;style&gt;&lt;script&gt;evil&lt;/script&gt;</code>'" is
+   * used in a style sheet in a &lt;style&gt; tag, this could then break out of
+   * the style context into HTML.
+   * 
+   * <p>
+   * The following example values comply with this type's contract:
+   * <ul>
+   * <li><code>width: 1em;</code></li>
+   * <li><code>height:1em;</code></li>
+   * <li><code>width: 1em;height: 1em;</code></li>
+   * <li><code>background:url('http://url');</code></li>
+   * </ul>
+   * In addition, the empty string is safe for use in a CSS attribute.
+   * 
+   * <p>
+   * The following example values do <em>not</em> comply with this type's contract:
+   * <ul>
+   * <li><code>background: red</code> (missing a trailing semi-colon)</li>
+   * <li><code>background:</code> (missing a value and a trailing semi-colon)</li>
+   * <li><code>1em</code> (missing an attribute name, which provides context for the value)</li>
+   * </ul>
+   * 
+   * @param styles the input String
+   * @return a {@link SafeStyles} instance
+   */
+  public SafeStylesBuilder appendTrustedString(String styles) {
+    SafeStylesUtils.verifySafeStylesConstraints(styles);
+    sb.append(styles);
+    return this;
+  }
+
+  /**
+   * Returns the safe CSS properties accumulated in the builder as a
+   * {@link SafeStyles}.
+   * 
+   * @return a {@link SafeStyles} instance
+   */
+  public SafeStyles toSafeStyles() {
+    return new SafeStylesString(sb.toString());
+  }
+}
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStylesString.java b/user/src/com/google/gwt/safecss/shared/SafeStylesString.java
new file mode 100644
index 0000000..fba7b5d
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/shared/SafeStylesString.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+/**
+ * A string wrapped as an object of type {@link SafeStyles}.
+ * 
+ * <p>
+ * This class is package-private and intended for internal use by the
+ * {@link com.google.gwt.safecss} package.
+ * 
+ * <p>
+ * All implementors must implement .equals and .hashCode so that they operate
+ * just like String.equals() and String.hashCode().
+ */
+class SafeStylesString implements SafeStyles {
+
+  private String css;
+
+  /**
+   * Constructs a {@link SafeStylesString} from a string. Callers are
+   * responsible for ensuring that the string passed as the argument to this
+   * constructor satisfies the constraints of the contract imposed by the
+   * {@link SafeStyles} interface.
+   * 
+   * @param css the string to be wrapped as a {@link SafeStyles}
+   */
+  SafeStylesString(String css) {
+    SafeStylesUtils.verifySafeStylesConstraints(css);
+    this.css = css;
+  }
+
+  /**
+   * No-arg constructor for compatibility with GWT serialization.
+   */
+  @SuppressWarnings("unused")
+  private SafeStylesString() {
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String asString() {
+    return css;
+  }
+
+  /**
+   * Compares this string to the specified object.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof SafeStyles)) {
+      return false;
+    }
+    return css.equals(((SafeStyles) obj).asString());
+  }
+
+  /**
+   * Returns a hash code for this string.
+   */
+  @Override
+  public int hashCode() {
+    return css.hashCode();
+  }
+}
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStylesUtils.java b/user/src/com/google/gwt/safecss/shared/SafeStylesUtils.java
new file mode 100644
index 0000000..34de5d6
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/shared/SafeStylesUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+/**
+ * Utility class containing static methods for creating {@link SafeStyles}.
+ */
+public final class SafeStylesUtils {
+  /*
+   * TODO(jlabanca): add context specific utility methods to create SafeStyles
+   * (ex. #forHeight(double height, Unit unit).
+   */
+
+  /**
+   * <p>
+   * Returns a {@link SafeStyles} constructed from a trusted string, i.e.,
+   * without escaping the string. No checks are performed. The calling code
+   * should be carefully reviewed to ensure the argument meets the
+   * {@link SafeStyles} contract.
+   * 
+   * <p>
+   * Generally, {@link SafeStyles} should be of the form
+   * {@code cssPropertyName:value;}, where neither the name nor the value
+   * contain malicious scripts.
+   * 
+   * <p>
+   * {@link SafeStyles} may never contain literal angle brackets. Otherwise, it
+   * could be unsafe to place a {@link SafeStyles} into a &lt;style&gt; tag
+   * (where it can't be HTML escaped). For example, if the {@link SafeStyles}
+   * containing "
+   * <code>font: 'foo &lt;style&gt;&lt;script&gt;evil&lt;/script&gt;</code>'" is
+   * used in a style sheet in a &lt;style&gt; tag, this could then break out of
+   * the style context into HTML.
+   * 
+   * <p>
+   * The following example values comply with this type's contract:
+   * <ul>
+   * <li><code>width: 1em;</code></li>
+   * <li><code>height:1em;</code></li>
+   * <li><code>width: 1em;height: 1em;</code></li>
+   * <li><code>background:url('http://url');</code></li>
+   * </ul>
+   * In addition, the empty string is safe for use in a CSS attribute.
+   * 
+   * <p>
+   * The following example values do <em>not</em> comply with this type's contract:
+   * <ul>
+   * <li><code>background: red</code> (missing a trailing semi-colon)</li>
+   * <li><code>background:</code> (missing a value and a trailing semi-colon)</li>
+   * <li><code>1em</code> (missing an attribute name, which provides context for the value)</li>
+   * </ul>
+   * 
+   * @param s the input String
+   * @return a {@link SafeStyles} instance
+   */
+  public static SafeStyles fromTrustedString(String s) {
+    return new SafeStylesString(s);
+  }
+
+  /**
+   * Verify that the basic constraints of a {@link SafeStyles} are met. This
+   * method is not a guarantee that the specified css is safe for use in a CSS
+   * style attribute. It is a minimal set of assertions to check for common
+   * errors.
+   * 
+   * @param styles the CSS properties string
+   * @throws NullPointerException if the css is null
+   * @throws AssertionError if the css does not meet the contraints
+   */
+  static void verifySafeStylesConstraints(String styles) {
+    if (styles == null) {
+      throw new NullPointerException("css is null");
+    }
+
+    // CSS properties must end in a semi-colon or they cannot be safely
+    // composed with other properties.
+    assert ((styles.trim().length() == 0) || styles.endsWith(";")) : "Invalid CSS Property: '"
+        + styles + "'. CSS properties must be an empty string or end with a semi-colon (;).";
+    assert !styles.contains("<") && !styles.contains(">") : "Invalid CSS Property: '" + styles
+        + "'. CSS should not contain brackets (< or >).";
+  }
+
+  // prevent instantiation
+  private SafeStylesUtils() {
+  }
+}
diff --git a/user/src/com/google/gwt/safecss/shared/package-info.java b/user/src/com/google/gwt/safecss/shared/package-info.java
new file mode 100644
index 0000000..7f085eb
--- /dev/null
+++ b/user/src/com/google/gwt/safecss/shared/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Shared classes for creating safe CSS style content.
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.safecss.shared;
diff --git a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
index 871ef53..7bab92a 100644
--- a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
+++ b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.safecss.shared.SafeStyles;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates.Template;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.HtmlContext;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.LiteralChunk;
@@ -46,9 +47,24 @@
   private static final String JAVA_LANG_STRING_FQCN = String.class.getName();
 
   /**
+   * Simple class name of the {@link SafeStyles} interface.
+   */
+  private static final String SAFE_STYLES_CN = SafeStyles.class.getSimpleName();
+
+  /**
+   * Fully-qualified class name of the {@link SafeStyles} interface.
+   */
+  private static final String SAFE_STYLES_FQCN = SafeStyles.class.getName();
+
+  /**
+   * Simple class name of the {@link SafeHtml} interface.
+   */
+  private static final String SAFE_HTML_CN = SafeHtml.class.getSimpleName();
+
+  /**
    * Fully-qualified class name of the {@link SafeHtml} interface.
    */
-  public static final String SAFE_HTML_FQCN = SafeHtml.class.getName();
+  private static final String SAFE_HTML_FQCN = SafeHtml.class.getName();
 
   /**
    * Fully-qualified class name of the StringBlessedAsSafeHtml class.
@@ -104,11 +120,13 @@
    * template method parameter:
    *
    * <ul>
-   *   <li>If the parameter is not of type {@link String}, it is first converted
-   *       to {@link String}.
-   *   <li>If the template parameter occurs at the start of a URI-valued
-   *       attribute within the template, it is sanitized to ensure that it
-   *       is safe in this context.  This is done by passing the value through
+   *   <li>If the parameter is of type {@link SafeStyles}, it is converted to a
+   *       string using {@link SafeStyles#asString()}.
+   *   <li>Otherwise, if the parameter is not of type {@link String}, it is first
+   *       converted to {@link String}.
+   *   <li>If the template parameter occurs at the start of a URI-valued attribute
+   *       within the template, it is sanitized to ensure that it is safe in this
+   *       context. This is done by passing the value through
    *       {@link UriUtils#sanitizeUri(String)}.
    *   <li>The result is then HTML-escaped by passing it through
    *       {@link SafeHtmlUtils#htmlEscape(String)}.
@@ -138,9 +156,13 @@
      */
     String expression = formalParameterName;
 
-    // The parameter's value must be explicitly converted to String unless it
-    // is already of that type.
-    if (!JAVA_LANG_STRING_FQCN.equals(parameterType.getQualifiedSourceName())) {
+    if (isSafeStyles(parameterType)) {
+      // SafeCss is safe in a CSS context, so we can use its string (but we still
+      // escape it).
+      expression = expression + ".asString()";
+    } else if (!JAVA_LANG_STRING_FQCN.equals(parameterType.getQualifiedSourceName())) {
+      // The parameter's value must be explicitly converted to String unless it
+      // is already of that type.
       expression = "String.valueOf(" + expression + ")";
     }
 
@@ -151,7 +173,6 @@
 
     // TODO(xtof): Handle EscapedString subtype of SafeHtml, once it's been
     //     introduced.
-    // TODO(xtof): Throw an exception if using SafeHtml within an attribute.
     expression = ESCAPE_UTILS_FQCN + ".htmlEscape(" + expression + ")";
 
     print(expression);
@@ -226,16 +247,57 @@
    *          parameter corresponding to the expression being emitted
    * @param parameterType the Java type of the corresponding template method's
    *          parameter
+   * @throws UnableToCompleteException if the parameterType is not valid for the
+   *           htmlContext
    */
-  private void emitParameterExpression(TreeLogger logger,
-      HtmlContext htmlContext, String formalParameterName,
-      JType parameterType) {
+  private void emitParameterExpression(TreeLogger logger, HtmlContext htmlContext,
+      String formalParameterName, JType parameterType) throws UnableToCompleteException {
+
+    /*
+     * Verify that the parameter type is used in the correct context. Safe
+     * expressions are only safe in specific contexts.
+     */
+    HtmlContext.Type contextType = htmlContext.getType();
+    if (isSafeHtml(parameterType) && HtmlContext.Type.TEXT != contextType) {
+      /*
+       * SafeHtml used in a non-text context. SafeHtml is escaped for a text
+       * context. In a non-text context, the string is not guaranteed to be
+       * safe.
+       */
+      throw error(logger, SAFE_HTML_CN + " used in a non-text context. Did you mean to use "
+          + JAVA_LANG_STRING_FQCN + " or " + SAFE_STYLES_CN + " instead?");
+    } else if (isSafeStyles(parameterType) && HtmlContext.Type.CSS_ATTRIBUTE_START != contextType) {
+      if (HtmlContext.Type.CSS_ATTRIBUTE == contextType) {
+        // SafeStyles can only be used at the start of a CSS attribute.
+        throw error(logger, SAFE_STYLES_CN + " cannot be used in the middle of a CSS attribute. "
+            + "It must be used at the start a CSS attribute.");
+      } else {
+        /*
+         * SafeStyles used in a non-css attribute context. SafeStyles is only
+         * safe in a CSS attribute context. We could treat it as a normal
+         * parameter and escape the string value of the parameter, but it almost
+         * definitely isn't what the developer intended to do.
+         */
+        throw error(logger, SAFE_STYLES_CN
+            + " used in a non-CSS attribute context. Did you mean to use " + JAVA_LANG_STRING_FQCN
+            + " or " + SAFE_HTML_CN + " instead?");
+      }
+    }
+
     print("sb.append(");
-    switch (htmlContext.getType()) {
+    switch (contextType) {
       case CSS:
-        // TODO(xtof): Improve support for CSS.
-        // The stream parser does not parse CSS; we could however improve
-        // safety via sub-formats that specify the in-css context.
+        /*
+         * TODO(jlabanca): Handle CSS in a text context.
+         * 
+         * The stream parser does not parse CSS; we could however improve safety
+         * via sub-formats that specify the in-css context.
+         * 
+         * SafeStyles is safe in a CSS context when used inside of a CSS style
+         * rule, but they are not always safe. We could implement SafeCssRules,
+         * which would consist of SafeStyles inside of a CSS style rules found
+         * in a style tag.
+         */
         logger.log(TreeLogger.WARN, "Template with variable in CSS context: "
             + "The template code generator cannot guarantee HTML-safety of "
             + "the template -- please inspect manually");
@@ -247,10 +309,18 @@
 
       case CSS_ATTRIBUTE:
       case CSS_ATTRIBUTE_START:
-        // TODO(xtof): Improve support for CSS.
-        logger.log(TreeLogger.WARN, "Template with variable in CSS context: "
-            + "The template code generator cannot guarantee HTML-safety of "
-            + "the template -- please inspect manually");
+        /*
+         * We already checked if the user tried to use SafeStyles in an invalid
+         * (non-CSS_ATTRIBUTE) context, but now we check if the user could have
+         * used SafeStyles in the current context.
+         */
+        if (!isSafeStyles(parameterType)) {
+          // Warn against using unsafe parameters in a CSS attribute context.  
+          logger.log(TreeLogger.WARN,
+              "Template with variable in CSS attribute context: The template code generator cannot"
+                  + " guarantee HTML-safety of the template -- please inspect manually or use "
+                  + SAFE_STYLES_CN + " to specify arguments in a CSS attribute context");
+        }
         emitAttributeContextParameterExpression(logger, htmlContext,
             formalParameterName, parameterType);
         break;
@@ -262,9 +332,8 @@
         break;
 
       default:
-          throw new IllegalStateException(
-              "unknown HTML context for formal template parameter "
-                  + formalParameterName + ": " + htmlContext);
+        throw error(logger, "unknown HTML context for formal template parameter "
+            + formalParameterName + ": " + htmlContext);
     }
     println(");");
   }
@@ -329,4 +398,24 @@
       print(")");
     }
   }
+
+  /**
+   * Check if the specified parameter type represents a {@link SafeHtml}.
+   * 
+   * @param parameterType the Java parameter type
+   * @return true if the type represents a {@link SafeHtml}
+   */
+  private boolean isSafeHtml(JType parameterType) {
+    return parameterType.getQualifiedSourceName().equals(SAFE_HTML_FQCN);
+  }
+
+  /**
+   * Check if the specified parameter type represents a {@link SafeStyles}.
+   * 
+   * @param parameterType the Java parameter type
+   * @return true if the type represents a {@link SafeStyles}
+   */
+  private boolean isSafeStyles(JType parameterType) {
+    return parameterType.getQualifiedSourceName().equals(SAFE_STYLES_FQCN);
+  }
 }
diff --git a/user/src/com/google/gwt/safehtml/shared/SafeHtml.java b/user/src/com/google/gwt/safehtml/shared/SafeHtml.java
index 7b3996e..9983120 100644
--- a/user/src/com/google/gwt/safehtml/shared/SafeHtml.java
+++ b/user/src/com/google/gwt/safehtml/shared/SafeHtml.java
@@ -60,15 +60,17 @@
  */
 public interface SafeHtml extends Serializable {
   /*
-   * Notes regarding serialization: - It may be reasonable to allow
-   * deserialization on the client of objects serialized on the server (i.e. RPC
-   * responses), based on the assumption that server code is trusted and would
-   * not provide a malicious serialized form (if a MitM were able to modify
-   * server responses, the client would be fully compromised in any case).
-   * However, the GWT RPC framework currently does not seem to provide a
-   * facility for restricting deserialization on the Server only (thought this
-   * shouldn't be difficult to implement through a custom SerializationPolicy)
-   *
+   * Notes regarding serialization:
+   * 
+   * - It may be reasonable to allow deserialization on the client of objects
+   * serialized on the server (i.e. RPC responses), based on the assumption that
+   * server code is trusted and would not provide a malicious serialized form
+   * (if a MitM were able to modify server responses, the client would be fully
+   * compromised in any case). However, the GWT RPC framework currently does not
+   * seem to provide a facility for restricting deserialization on the Server
+   * only (though this shouldn't be difficult to implement through a custom
+   * SerializationPolicy)
+   * 
    * - Some implementations of SafeHtml would in principle be able to enforce
    * their class invariant on deserialization (e.g., SimpleHtmlSanitizer could
    * apply HTML sanitization on deserialization). However, the GWT RPC framework
diff --git a/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java b/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
index d33714d..66afd65 100644
--- a/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
+++ b/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
@@ -22,7 +22,8 @@
  * This class is package-private and intended for internal use by the
  * {@link com.google.gwt.safehtml} package.
  *
- *  All implementors must implement .equals and .hashCode so that they operate
+ * <p>
+ * All implementors must implement .equals and .hashCode so that they operate
  * just like String.equals() and String.hashCode().
  */
 class SafeHtmlString implements SafeHtml {
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 6eaccff..78c8bff 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -54,6 +54,15 @@
    <inherits name="com.google.gwt.user.SplitPanel"/>
    <inherits name="com.google.gwt.user.TextBox"/>
    <inherits name="com.google.gwt.user.Tree"/>
+   <inherits name="com.google.gwt.user.Hyperlink"/>
+   <inherits name="com.google.gwt.user.FileUpload"/>
+   <inherits name="com.google.gwt.user.ResizeLayoutPanel"/>
+   <inherits name="com.google.gwt.user.ScrollPanel"/>
+   <inherits name="com.google.gwt.user.datepicker.DatePicker"/>
+   <inherits name="com.google.gwt.user.cellview.CellView"/>
+   <inherits name="com.google.gwt.safecss.SafeCss" />
+   <inherits name="com.google.gwt.safehtml.SafeHtml" />
+   <inherits name="com.google.gwt.storage.Storage" />
    <inherits name="com.google.gwt.user.Window" />
    <inherits name="com.google.gwt.widget.Widget"/>
 
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
index d92b913..26e53ee 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
@@ -38,6 +38,9 @@
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.ImageResource.ImageOptions;
 import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesBuilder;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -71,7 +74,7 @@
  * the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
  * declaration.
  * </p>
- *
+ * 
  * <p>
  * <h3>Example</h3>
  * <dl>
@@ -81,8 +84,8 @@
  * <dd>{@example com.google.gwt.examples.cellview.CellBrowserExample2}</dd>
  * </dl>
  */
-public class CellBrowser extends AbstractCellTree implements ProvidesResize,
-    RequiresResize, HasAnimation {
+public class CellBrowser extends AbstractCellTree implements ProvidesResize, RequiresResize,
+    HasAnimation {
 
   /**
    * A ClientBundle that provides images for this widget.
@@ -174,27 +177,28 @@
   }
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\">{3}<div>{4}</div></div>")
-    SafeHtml div(int idx, String classes, int imageWidth, SafeHtml imageHtml,
+    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\""
+        + " style=\"{2}position:relative;outline:none;\">{3}<div>{4}</div></div>")
+    SafeHtml div(int idx, String classes, SafeStyles padding, SafeHtml imageHtml,
         SafeHtml cellContents);
 
-    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\" tabindex=\"{3}\">{4}<div>{5}</div></div>")
-    SafeHtml divFocusable(int idx, String classes, int imageWidth,
-        int tabIndex, SafeHtml imageHtml, SafeHtml cellContents);
+    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\""
+        + " style=\"{2}position:relative;outline:none;\" tabindex=\"{3}\">{4}<div>{5}</div></div>")
+    SafeHtml divFocusable(int idx, String classes, SafeStyles padding, int tabIndex,
+        SafeHtml imageHtml, SafeHtml cellContents);
 
-    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\" tabindex=\"{3}\" accessKey=\"{4}\">{5}<div>{6}</div></div>")
-    SafeHtml divFocusableWithKey(int idx, String classes, int imageWidth,
-        int tabIndex, char accessKey, SafeHtml imageHtml, SafeHtml cellContents);
+    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\""
+        + " style=\"{2}position:relative;outline:none;\" tabindex=\"{3}\" accessKey=\"{4}\">{5}<div>{6}</div></div>")
+    SafeHtml divFocusableWithKey(int idx, String classes, SafeStyles padding, int tabIndex,
+        char accessKey, SafeHtml imageHtml, SafeHtml cellContents);
 
-    @Template("<div style=\"position:absolute;{0}:0px;width:{1}px;"
-        + "height:{2}px;\">{3}</div>")
-    SafeHtml imageWrapper(String direction, int width, int height,
-        SafeHtml image);
+    @Template("<div style=\"{0}position:absolute;\">{1}</div>")
+    SafeHtml imageWrapper(SafeStyles css, SafeHtml image);
   }
 
   /**
    * A custom version of cell list used by the browser. Visible for testing.
-   *
+   * 
    * @param <T> the data type of list items
    */
   class BrowserCellList<T> extends CellList<T> {
@@ -224,8 +228,7 @@
      */
     private final Element tmpElem = Document.get().createDivElement();
 
-    public BrowserCellList(final Cell<T> cell, int level,
-        ProvidesKey<T> keyProvider) {
+    public BrowserCellList(final Cell<T> cell, int level, ProvidesKey<T> keyProvider) {
       super(cell, cellListResources, keyProvider);
       this.level = level;
     }
@@ -274,11 +277,10 @@
     }
 
     @Override
-    protected void renderRowValues(SafeHtmlBuilder sb, List<T> values,
-        int start, SelectionModel<? super T> selectionModel) {
+    protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
+        SelectionModel<? super T> selectionModel) {
       Cell<T> cell = getCell();
-      String keyboardSelectedItem = " "
-          + style.cellBrowserKeyboardSelectedItem();
+      String keyboardSelectedItem = " " + style.cellBrowserKeyboardSelectedItem();
       String selectedItem = " " + style.cellBrowserSelectedItem();
       String openItem = " " + style.cellBrowserOpenItem();
       String evenItem = style.cellBrowserEvenItem();
@@ -288,8 +290,7 @@
       int end = start + length;
       for (int i = start; i < end; i++) {
         T value = values.get(i - start);
-        boolean isSelected = selectionModel == null ? false
-            : selectionModel.isSelected(value);
+        boolean isSelected = selectionModel == null ? false : selectionModel.isSelected(value);
         boolean isOpen = isOpen(i);
         StringBuilder classesBuilder = new StringBuilder();
         classesBuilder.append(i % 2 == 0 ? evenItem : oddItem);
@@ -314,6 +315,8 @@
           image = closedImageHtml;
         }
 
+        SafeStyles padding =
+            SafeStylesUtils.fromTrustedString("padding-right: " + imageWidth + "px;");
         if (i == keyboardSelectedRow) {
           // This is the focused item.
           if (isFocused) {
@@ -321,16 +324,15 @@
           }
           char accessKey = getAccessKey();
           if (accessKey != 0) {
-            sb.append(template.divFocusableWithKey(i,
-                classesBuilder.toString(), imageWidth, getTabIndex(),
-                getAccessKey(), image, cellBuilder.toSafeHtml()));
+            sb.append(template.divFocusableWithKey(i, classesBuilder.toString(), padding,
+                getTabIndex(), getAccessKey(), image, cellBuilder.toSafeHtml()));
           } else {
-            sb.append(template.divFocusable(i, classesBuilder.toString(),
-                imageWidth, getTabIndex(), image, cellBuilder.toSafeHtml()));
+            sb.append(template.divFocusable(i, classesBuilder.toString(), padding, getTabIndex(),
+                image, cellBuilder.toSafeHtml()));
           }
         } else {
-          sb.append(template.div(i, classesBuilder.toString(), imageWidth,
-              image, cellBuilder.toSafeHtml()));
+          sb.append(template.div(i, classesBuilder.toString(), padding, image, cellBuilder
+              .toSafeHtml()));
         }
       }
 
@@ -339,8 +341,7 @@
     }
 
     @Override
-    protected void setKeyboardSelected(int index, boolean selected,
-        boolean stealFocus) {
+    protected void setKeyboardSelected(int index, boolean selected, boolean stealFocus) {
       super.setKeyboardSelected(index, selected, stealFocus);
       if (!isRowWithinBounds(index)) {
         return;
@@ -362,8 +363,7 @@
         image = closedImageHtml;
       }
       tmpElem.setInnerHTML(image.asString());
-      elem.replaceChild(tmpElem.getFirstChildElement(),
-          elem.getFirstChildElement());
+      elem.replaceChild(tmpElem.getFirstChildElement(), elem.getFirstChildElement());
 
       // Update the open state.
       updateChildState(this, true);
@@ -417,7 +417,7 @@
 
   /**
    * A node in the tree.
-   *
+   * 
    * @param <C> the data type of the children of the node
    */
   class TreeNodeImpl<C> implements TreeNode {
@@ -429,14 +429,14 @@
 
     /**
      * Construct a new {@link TreeNodeImpl}.
-     *
+     * 
      * @param nodeInfo the nodeInfo for the children nodes
      * @param value the value of the node
      * @param display the display associated with the node
      * @param widget the widget that wraps the display
      */
-    public TreeNodeImpl(final NodeInfo<C> nodeInfo, Object value,
-        final BrowserCellList<C> display, Widget widget) {
+    public TreeNodeImpl(final NodeInfo<C> nodeInfo, Object value, final BrowserCellList<C> display,
+        Widget widget) {
       this.display = display;
       this.nodeInfo = nodeInfo;
       this.value = value;
@@ -498,9 +498,8 @@
     public boolean isChildOpen(int index) {
       assertNotDestroyed();
       checkChildBounds(index);
-      return (display.focusedKey == null || !display.isFocusedOpen)
-          ? false
-          : display.focusedKey.equals(display.getValueKey(getChildValue(index)));
+      return (display.focusedKey == null || !display.isFocusedOpen) ? false : display.focusedKey
+          .equals(display.getValueKey(getChildValue(index)));
     }
 
     public boolean isDestroyed() {
@@ -567,7 +566,7 @@
 
     /**
      * Check the child bounds.
-     *
+     * 
      * @param index the index of the child
      * @throws IndexOutOfBoundsException if the child is not in range
      */
@@ -591,7 +590,7 @@
 
     /**
      * Get the index of the open item.
-     *
+     * 
      * @return the index of the open item, or -1 if not found
      */
     private int getOpenIndex() {
@@ -726,7 +725,8 @@
   /**
    * The element used in place of an image when a node has no children.
    */
-  private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant("<div style='position:absolute;display:none;'></div>");
+  private static final SafeHtml LEAF_IMAGE =
+      SafeHtmlUtils.fromSafeConstant("<div style='position:absolute;display:none;'></div>");
 
   private static Template template;
 
@@ -794,7 +794,7 @@
 
   /**
    * Construct a new {@link CellBrowser}.
-   *
+   * 
    * @param <T> the type of data in the root node
    * @param viewModel the {@link TreeViewModel} that backs the tree
    * @param rootValue the hidden root value of the tree
@@ -805,14 +805,13 @@
 
   /**
    * Construct a new {@link CellBrowser} with the specified {@link Resources}.
-   *
+   * 
    * @param <T> the type of data in the root node
    * @param viewModel the {@link TreeViewModel} that backs the tree
    * @param rootValue the hidden root value of the tree
    * @param resources the {@link Resources} used for images
    */
-  public <T> CellBrowser(TreeViewModel viewModel, T rootValue,
-      Resources resources) {
+  public <T> CellBrowser(TreeViewModel viewModel, T rootValue, Resources resources) {
     super(viewModel);
     if (template == null) {
       template = GWT.create(Template.class);
@@ -856,7 +855,7 @@
 
   /**
    * Get the default width of new columns.
-   *
+   * 
    * @return the default width in pixels
    * @see #setDefaultColumnWidth(int)
    */
@@ -866,7 +865,7 @@
 
   /**
    * Get the minimum width of columns.
-   *
+   * 
    * @return the minimum width in pixels
    * @see #setMinimumColumnWidth(int)
    */
@@ -904,7 +903,7 @@
 
   /**
    * Set the default width of new columns.
-   *
+   * 
    * @param width the default width in pixels
    * @see #getDefaultColumnWidth()
    */
@@ -928,7 +927,7 @@
 
   /**
    * Set the minimum width of columns.
-   *
+   * 
    * @param minWidth the minimum width in pixels
    * @see #getMinimumColumnWidth()
    */
@@ -938,14 +937,13 @@
 
   /**
    * Create a pager to control the list view.
-   *
+   * 
    * @param <C> the item type in the list view
    * @param display the list view to add paging too
    * @return the pager
    */
   protected <C> Widget createPager(HasData<C> display) {
-    PageSizePager pager = new PageSizePager(
-        display.getVisibleRange().getLength());
+    PageSizePager pager = new PageSizePager(display.getVisibleRange().getLength());
     pager.setDisplay(display);
     return pager;
   }
@@ -967,7 +965,7 @@
   /**
    * Create a new {@link TreeNodeImpl} and append it to the end of the
    * LayoutPanel.
-   *
+   * 
    * @param <C> the data type of the children
    * @param nodeInfo the info about the node
    * @param value the value of the open node
@@ -997,8 +995,7 @@
     }
 
     // Create a TreeNode.
-    TreeNodeImpl<C> treeNode = new TreeNodeImpl<C>(nodeInfo, value, view,
-        scrollable);
+    TreeNodeImpl<C> treeNode = new TreeNodeImpl<C>(nodeInfo, value, view, scrollable);
     treeNodes.add(treeNode);
 
     /*
@@ -1022,15 +1019,15 @@
   /**
    * Create a {@link HasData} that will display items. The {@link HasData} must
    * extend {@link Widget}.
-   *
+   * 
    * @param <C> the item type in the list view
    * @param nodeInfo the node info with child data
    * @param level the level of the list
    * @return the {@link HasData}
    */
   private <C> BrowserCellList<C> createDisplay(NodeInfo<C> nodeInfo, int level) {
-    BrowserCellList<C> display = new BrowserCellList<C>(nodeInfo.getCell(),
-        level, nodeInfo.getProvidesKey());
+    BrowserCellList<C> display =
+        new BrowserCellList<C>(nodeInfo.getCell(), level, nodeInfo.getProvidesKey());
     display.setValueUpdater(nodeInfo.getValueUpdater());
 
     // Set the keyboard selection policy, but never disable it.
@@ -1041,7 +1038,7 @@
 
   /**
    * Get the HTML representation of an image.
-   *
+   * 
    * @param res the {@link ImageResource} to render as HTML
    * @return the rendered HTML
    */
@@ -1049,8 +1046,16 @@
     // Right-justify image if LTR, left-justify if RTL
     AbstractImagePrototype proto = AbstractImagePrototype.create(res);
     SafeHtml image = SafeHtmlUtils.fromTrustedString(proto.getHTML());
-    return template.imageWrapper((LocaleInfo.getCurrentLocale().isRTL()
-        ? "left" : "right"), res.getWidth(), res.getHeight(), image);
+
+    SafeStylesBuilder cssBuilder = new SafeStylesBuilder();
+    if (LocaleInfo.getCurrentLocale().isRTL()) {
+      cssBuilder.appendTrustedString("left:0px;");
+    } else {
+      cssBuilder.appendTrustedString("right:0px;");
+    }
+    cssBuilder.appendTrustedString("width: " + res.getWidth() + "px;");
+    cssBuilder.appendTrustedString("height: " + res.getHeight() + "px;");
+    return template.imageWrapper(cssBuilder.toSafeStyles(), image);
   }
 
   /**
@@ -1062,8 +1067,7 @@
    */
   private KeyboardSelectionPolicy getKeyboardSelectionPolicyForLists() {
     KeyboardSelectionPolicy policy = getKeyboardSelectionPolicy();
-    return KeyboardSelectionPolicy.DISABLED == policy
-        ? KeyboardSelectionPolicy.ENABLED : policy;
+    return KeyboardSelectionPolicy.DISABLED == policy ? KeyboardSelectionPolicy.ENABLED : policy;
   }
 
   /**
@@ -1077,7 +1081,7 @@
 
   /**
    * Reduce the number of {@link HasData}s down to the specified level.
-   *
+   * 
    * @param level the level to trim to
    */
   private void trimToLevel(int level) {
@@ -1111,11 +1115,10 @@
    * @param fireEvents true to fireEvents
    * @return the open {@link TreeNode}, or null if not opened
    */
-  private <C> TreeNode updateChildState(BrowserCellList<C> cellList,
-      boolean fireEvents) {
+  private <C> TreeNode updateChildState(BrowserCellList<C> cellList, boolean fireEvents) {
     /*
      * Verify that the specified list is still in the browser. It possible for
-     * the list to receive deferred updates after it has been removed 
+     * the list to receive deferred updates after it has been removed
      */
     if (cellList.isDestroyed) {
       return null;
@@ -1130,8 +1133,8 @@
     if (cellList.focusedKey != null && cellList.isFocusedOpen
         && !cellList.focusedKey.equals(newKey)) {
       // Get the node to close.
-      closedNode = (treeNodes.size() > cellList.level + 1)
-          ? treeNodes.get(cellList.level + 1) : null;
+      closedNode =
+          (treeNodes.size() > cellList.level + 1) ? treeNodes.get(cellList.level + 1) : null;
 
       // Close the node.
       trimToLevel(cellList.level);
@@ -1143,13 +1146,11 @@
     if (newKey != null) {
       if (newKey.equals(cellList.focusedKey)) {
         // The node is already open.
-        openNode = cellList.isFocusedOpen ? treeNodes.get(cellList.level + 1)
-            : null;
+        openNode = cellList.isFocusedOpen ? treeNodes.get(cellList.level + 1) : null;
       } else {
         // Add the child node.
         cellList.focusedKey = newKey;
-        NodeInfo<?> childNodeInfo = isLeaf(newValue) ? null
-            : getNodeInfo(newValue);
+        NodeInfo<?> childNodeInfo = isLeaf(newValue) ? null : getNodeInfo(newValue);
         if (childNodeInfo != null) {
           cellList.isFocusedOpen = true;
           justOpenedNode = true;
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.java b/user/src/com/google/gwt/user/cellview/client/CellTree.java
index a3a7de6..160aaa9 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -31,6 +31,8 @@
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.ImageResource.ImageOptions;
 import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesBuilder;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
@@ -439,23 +441,8 @@
   }
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<div class=\"{0}\" style=\"position:absolute;{1}:0px;"
-        + "width:{2}px;height:{3}px;\">{4}</div>")
-    SafeHtml imageWrapper(String classes, String direction, int width,
-        int height, SafeHtml image);
-  }
-
-  /**
-   * Implementation of {@link CellTree}.
-   */
-  private static class Impl {
-    /**
-     * Create an image wrapper.
-     */
-    public SafeHtml imageWrapper(String classes, String direction, int width,
-        int height, SafeHtml image) {
-      return template.imageWrapper(classes, direction, width, height, image);
-    }
+    @Template("<div class=\"{0}\" style=\"{1}position:absolute;\">{2}</div>")
+    SafeHtml imageWrapper(String classes, SafeStyles cssLayout, SafeHtml image);
   }
 
   /**
@@ -465,7 +452,6 @@
 
   private static Resources DEFAULT_RESOURCES;
 
-  private static Impl TREE_IMPL;
   private static Template template;
 
   private static Resources getDefaultResources() {
@@ -580,9 +566,6 @@
     if (template == null) {
       template = GWT.create(Template.class);
     }
-    if (TREE_IMPL == null) {
-      TREE_IMPL = GWT.create(Impl.class);
-    }
     this.style = resources.cellTreeStyle();
     this.style.ensureInjected();
     initWidget(new SimplePanel());
@@ -949,28 +932,32 @@
 
   /**
    * Get the HTML representation of an image.
-   *
+   * 
    * @param res the {@link ImageResource} to render as HTML
    * @param isTop true if the image is for a top level element.
    * @return the rendered HTML
    */
   private SafeHtml getImageHtml(ImageResource res, boolean isTop) {
+    // Build the classes.
     StringBuilder classesBuilder = new StringBuilder(style.cellTreeItemImage());
     if (isTop) {
       classesBuilder.append(" ").append(style.cellTreeTopItemImage());
     }
 
-    String direction;
+    // Build the css.
+    SafeStylesBuilder cssBuilder = new SafeStylesBuilder();
     if (LocaleInfo.getCurrentLocale().isRTL()) {
-      direction = "right";
+      cssBuilder.appendTrustedString("right: 0px;");
     } else {
-      direction = "left";
+      cssBuilder.appendTrustedString("left: 0px;");
     }
+    cssBuilder.appendTrustedString("width: " + res.getWidth() + "px;");
+    cssBuilder.appendTrustedString("height: " + res.getHeight() + "px;");
 
     AbstractImagePrototype proto = AbstractImagePrototype.create(res);
     SafeHtml image = SafeHtmlUtils.fromTrustedString(proto.getHTML());
-    return TREE_IMPL.imageWrapper(classesBuilder.toString(), direction,
-        res.getWidth(), res.getHeight(), image);
+    return template
+        .imageWrapper(classesBuilder.toString(), cssBuilder.toSafeStyles(), image);
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
index ae77b7c..c35ed19 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -33,6 +33,8 @@
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -68,14 +70,13 @@
 class CellTreeNodeView<T> extends UIObject {
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<div onclick=\"\" style=\"position:relative;padding-{0}:{1}px;"
-        + "\" class=\"{2}\">{3}<div class=\"{4}\">{5}</div></div>")
-    SafeHtml innerDiv(String paddingDirection, int imageWidth, String classes,
-        SafeHtml image, String itemValueStyle, SafeHtml cellContents);
+    @Template("<div onclick=\"\" style=\"{0}position:relative;\""
+        + " class=\"{1}\">{2}<div class=\"{3}\">{4}</div></div>")
+    SafeHtml innerDiv(SafeStyles cssString, String classes, SafeHtml image,
+        String itemValueStyle, SafeHtml cellContents);
 
-    @Template("<div><div style=\"padding-{0}:{1}px;\" class=\"{2}\">{3}</div></div>")
-    SafeHtml outerDiv(String paddingDirection, int paddingAmount,
-        String classes, SafeHtml content);
+    @Template("<div><div style=\"{0}\" class=\"{1}\">{2}</div></div>")
+    SafeHtml outerDiv(SafeStyles cssString, String classes, SafeHtml content);
   }
 
   private static final Template template = GWT.create(Template.class);
@@ -121,8 +122,7 @@
         SafeHtml openImage = nodeView.tree.getOpenImageHtml(isRootNode);
         SafeHtml closedImage = nodeView.tree.getClosedImageHtml(isRootNode);
         int imageWidth = nodeView.tree.getImageWidth();
-        String paddingDirection = LocaleInfo.getCurrentLocale().isRTL()
-            ? "right" : "left";
+        String paddingDirection = LocaleInfo.getCurrentLocale().isRTL() ? "right" : "left";
         int paddingAmount = imageWidth * nodeView.depth;
 
         // Create a set of currently open nodes.
@@ -176,13 +176,17 @@
           SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
           Context context = new Context(i, 0, key);
           cell.render(context, value, cellBuilder);
+          SafeStyles innerPadding =
+              SafeStylesUtils.fromTrustedString("padding-" + paddingDirection + ": " + imageWidth
+                  + "px;");
+          SafeHtml innerDiv =
+              template.innerDiv(innerPadding, innerClasses.toString(), image, itemValueStyle,
+                  cellBuilder.toSafeHtml());
 
-          SafeHtml innerDiv = template.innerDiv(paddingDirection, imageWidth,
-              innerClasses.toString(), image, itemValueStyle,
-              cellBuilder.toSafeHtml());
-
-          sb.append(template.outerDiv(paddingDirection, paddingAmount,
-              outerClasses.toString(), innerDiv));
+          SafeStyles outerPadding =
+              SafeStylesUtils.fromTrustedString("padding-" + paddingDirection + ": " + paddingAmount
+                  + "px;");
+          sb.append(template.outerDiv(outerPadding, outerClasses.toString(), innerDiv));
         }
       }
 
diff --git a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
index 3cb886a..ed413c5 100644
--- a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
+++ b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
@@ -54,7 +54,7 @@
     cell.render(context, "helloworld", sb);
 
     // Compare the expected render string.
-    String expected = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
+    String expected = "<div style=\"padding-left: 64px;position:relative;zoom:1;\">";
     expected += cell.getImageHtml(Resources.prettyPiccy(),
         HasVerticalAlignment.ALIGN_MIDDLE, true).asString();
     expected += "<div>helloworld</div>";
@@ -110,7 +110,7 @@
   @Override
   protected String getExpectedInnerHtml() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
+    String html = "<div style=\"padding-left: 64px;position:relative;zoom:1;\">";
     html += cell.getIconHtml("helloworld").asString();
     html += "<div>helloworld</div>";
     html += "</div>";
@@ -120,7 +120,7 @@
   @Override
   protected String getExpectedInnerHtmlNull() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
+    String html = "<div style=\"padding-left: 64px;position:relative;zoom:1;\">";
     html += cell.getIconHtml("helloworld").asString();
     html += "<div></div>";
     html += "</div>";
diff --git a/user/test/com/google/gwt/safecss/SafeCssGwtSuite.java b/user/test/com/google/gwt/safecss/SafeCssGwtSuite.java
new file mode 100644
index 0000000..b92c6df
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/SafeCssGwtSuite.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.gwt.safecss;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.safecss.shared.GwtSafeStylesBuilderTest;
+import com.google.gwt.safecss.shared.GwtSafeStylesStringTest;
+
+import junit.framework.Test;
+
+/**
+ * Test suite for SafeCss GWTTestCases.
+ */
+public class SafeCssGwtSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for safe css  GWTTestCases");
+
+    suite.addTestSuite(GwtSafeStylesBuilderTest.class);
+    suite.addTestSuite(GwtSafeStylesStringTest.class);
+
+    return suite;
+  }
+
+  private SafeCssGwtSuite() {
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/SafeCssJreSuite.java b/user/test/com/google/gwt/safecss/SafeCssJreSuite.java
new file mode 100644
index 0000000..c53d204
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/SafeCssJreSuite.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.gwt.safecss;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.safecss.shared.SafeStylesBuilderTest;
+import com.google.gwt.safecss.shared.SafeStylesStringTest;
+
+import junit.framework.Test;
+
+/**
+ * Test suite for SafeCss GWTTestCases.
+ */
+public class SafeCssJreSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for safe css tests that require the JRE");
+
+    suite.addTestSuite(SafeStylesBuilderTest.class);
+    suite.addTestSuite(SafeStylesStringTest.class);
+
+    return suite;
+  }
+
+  private SafeCssJreSuite() {
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/shared/GwtSafeStylesBuilderTest.java b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesBuilderTest.java
new file mode 100644
index 0000000..0d02a44
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesBuilderTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * GWT Unit tests for {@link SafeStylesBuilder}.
+ */
+public class GwtSafeStylesBuilderTest extends GWTTestCase {
+
+  private static final String CSS0 = "foo:bar;";
+  private static final String CSS1 = "baz:biz;";
+  private static final String INVALID_CSS = "baz:biz";
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.safecss.SafeCss";
+  }
+
+  public void testAppendSafeStyles() {
+    SafeStyles string0 = SafeStylesUtils.fromTrustedString(CSS0);
+    SafeStyles string1 = SafeStylesUtils.fromTrustedString(CSS1);
+
+    SafeStylesBuilder sb = new SafeStylesBuilder();
+    sb.append(string0);
+    sb.append(string1);
+    assertEquals("foo:bar;baz:biz;", sb.toSafeStyles().asString());
+  }
+
+  public void testAppendTrustedString() {
+    SafeStylesBuilder sb = new SafeStylesBuilder();
+    sb.appendTrustedString(CSS0);
+    sb.appendTrustedString(CSS1);
+    assertEquals("foo:bar;baz:biz;", sb.toSafeStyles().asString());
+  }
+
+  public void testAppendTrustedStringWithInvalidCss() {
+    if (GWT.isScript()) {
+      // Assertions are disabled in compiled scripts.
+      return;
+    }
+
+    SafeStylesBuilder sb = new SafeStylesBuilder();
+    sb.appendTrustedString(CSS0);
+
+    try {
+      sb.appendTrustedString(INVALID_CSS);
+      fail("Expected AssertionError");
+    } catch (AssertionError e) {
+      // Expected.
+    }
+  }
+
+  public void testEmpty() {
+    SafeStylesBuilder sb = new SafeStylesBuilder();
+    assertEquals("", sb.toSafeStyles().asString());
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/shared/GwtSafeStylesStringTest.java b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesStringTest.java
new file mode 100644
index 0000000..4019050
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesStringTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * GWT Unit tests for {@link SafeStylesString}.
+ */
+public class GwtSafeStylesStringTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.safecss.SafeCss";
+  }
+
+  /**
+   * Test that {@link SafeStyles} throws an assertion error if the string
+   * contains a bracket.
+   */
+  public void testBrackets() {
+    if (GWT.isScript()) {
+      // Assertions are disabled in compiled scripts.
+      return;
+    }
+
+    try {
+      new SafeStylesString("name:value<;");
+      fail("Expected AssertionError");
+    } catch (AssertionError e) {
+      // Expected.
+    }
+    try {
+      new SafeStylesString("name:value>;");
+      fail("Expected AssertionError");
+    } catch (AssertionError e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Test that {@link SafeStyles} throws an assertion error if the string
+   * contains a double quote.
+   */
+  public void testQuotes() {
+    if (GWT.isScript()) {
+      // Assertions are disabled in compiled scripts.
+      return;
+    }
+
+    // Verify that a string containing single quotes does not cause an
+    // exception.
+    new SafeStylesString("name:'value';");
+
+    // Verify that a string containing double quotes does not cause an
+    // exception.
+    new SafeStylesString("name:\"value\";");
+  }
+
+  public void testEquals() {
+    SafeStylesString safe1 = new SafeStylesString("string:same;");
+    SafeStylesString safe2 = new SafeStylesString("string:same;");
+    SafeStylesString safe3 = new SafeStylesString("string:diff;");
+    assertEquals(safe1, safe2);
+    assertFalse(safe1.equals(safe3));
+  }
+
+  public void testHashCode() {
+    SafeStylesString safe1 = new SafeStylesString("string:same;");
+    SafeStylesString safe3 = new SafeStylesString("string:diff;");
+    SafeStylesString safe2 = new SafeStylesString("string:same;");
+    assertEquals("string:same;".hashCode(), safe1.hashCode());
+    assertEquals(safe1.hashCode(), safe2.hashCode());
+    assertEquals("string:diff;".hashCode(), safe3.hashCode());
+  }
+
+  /**
+   * Test that {@link SafeStyles} throws an assertion error if the string is
+   * missing a semi-colon.
+   */
+  public void testMissingSemiColon() {
+    if (GWT.isScript()) {
+      // Assertions are disabled in compiled scripts.
+      return;
+    }
+
+    // Verify that the empty string is okay.
+    new SafeStylesString(""); // no error expected.
+    new SafeStylesString(" "); // no error expected.
+
+    try {
+      new SafeStylesString("string:same"); // missing a semi-colon.
+      fail("Expected AssertionError");
+    } catch (AssertionError e) {
+      // Expected.
+    }
+  }
+
+  public void testNull() {
+    try {
+      new SafeStylesString(null);
+      fail("Expected IllegalArgumentException");
+    } catch (NullPointerException e) {
+      // Expected.
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/shared/SafeStylesBuilderTest.java b/user/test/com/google/gwt/safecss/shared/SafeStylesBuilderTest.java
new file mode 100644
index 0000000..6ac8a22
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/shared/SafeStylesBuilderTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+/**
+ * Unit tests for {@link SafeStylesBuilder}.
+ */
+public class SafeStylesBuilderTest extends GwtSafeStylesBuilderTest {
+
+  // This forces a GWTTestCase to run as a vanilla JUnit TestCase.
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/shared/SafeStylesStringTest.java b/user/test/com/google/gwt/safecss/shared/SafeStylesStringTest.java
new file mode 100644
index 0000000..8e1009f
--- /dev/null
+++ b/user/test/com/google/gwt/safecss/shared/SafeStylesStringTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.gwt.safecss.shared;
+
+/**
+ * GWT Unit tests for {@link SafeStylesString}.
+ */
+public class SafeStylesStringTest extends GwtSafeStylesStringTest {
+
+  // This forces a GWTTestCase to run as a vanilla JUnit TestCase.
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+}
diff --git a/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java b/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java
index 28a4376..3a7e0a3 100644
--- a/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java
+++ b/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java
@@ -16,6 +16,8 @@
 package com.google.gwt.safehtml.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.junit.client.GWTTestCase;
@@ -60,6 +62,13 @@
     @Template("<div id=\"{0}\">{1}</div>")
     SafeHtml templateWithRegularAttribute(String id, SafeHtml html);
 
+    @Template("<div style=\"{0}\">{1}</div>")
+    SafeHtml templateWithSafeStyleAttributeComplete(SafeStyles styles, SafeHtml html);
+
+    @Template("<div style=\"{0}height:{1}px;\">{2}</div>")
+    SafeHtml templateWithSafeStyleAttributeStart(SafeStyles styles,
+        int height /* generates a compile time warning */, SafeHtml html);
+
     @Template("<span><img src=\"{0}/{1}\"/></span>")
     SafeHtml templateWithTwoPartUriAttribute(String baseUrl, String urlPart);
 
@@ -100,6 +109,20 @@
             BAD_URL, SafeHtmlUtils.fromSafeConstant(HTML_MARKUP)).asString());
   }
   
+  public void testTemplateWithSafeStyleAttributeComplete() {
+    Assert.assertEquals("<div style=\"width:10px;\">" + HTML_MARKUP + "</div>",
+        templates.templateWithSafeStyleAttributeComplete(
+            SafeStylesUtils.fromTrustedString("width:10px;"),
+            SafeHtmlUtils.fromSafeConstant(HTML_MARKUP)).asString());
+  }
+
+  public void testTemplateWithSafeStyleAttributeStart() {
+    Assert.assertEquals("<div style=\"width:10px;height:15px;\">" + HTML_MARKUP + "</div>",
+        templates.templateWithSafeStyleAttributeStart(
+            SafeStylesUtils.fromTrustedString("width:10px;"), 15,
+            SafeHtmlUtils.fromSafeConstant(HTML_MARKUP)).asString());
+  }
+
   public void testTemplateWithTwoPartUriAttribute() {
     Assert.assertEquals(
         "<span><img src=\"" + GOOD_URL_ESCAPED + "/x&amp;y\"/></span>",