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(" <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 <style> tag (where
+ * it can't be HTML escaped). For example, if the {@link SafeStyles} containing
+ * "<code>font: 'foo <style><script>evil</script></code>'" is
+ * used in a style sheet in a <style> 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 <style> tag
+ * (where it can't be HTML escaped). For example, if the {@link SafeStyles}
+ * containing "
+ * <code>font: 'foo <style><script>evil</script></code>'" is
+ * used in a style sheet in a <style> 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 <style> tag
+ * (where it can't be HTML escaped). For example, if the {@link SafeStyles}
+ * containing "
+ * <code>font: 'foo <style><script>evil</script></code>'" is
+ * used in a style sheet in a <style> 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 <!DOCTYPE>
* 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&y\"/></span>",