Use SafeHtml for Cell widgets

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8702 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
index 910b4b7..98dd347 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
@@ -44,6 +44,10 @@
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.sample.expenses.client.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.client.request.ExpenseRecord;
 import com.google.gwt.sample.expenses.client.request.ExpenseRecordChanged;
@@ -88,6 +92,20 @@
 public class ExpenseDetails extends Composite
     implements ExpenseRecordChanged.Handler, ReportRecordChanged.Handler {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<select style=\"background-color:white;border:1px solid "
+        + "#707172;width:10em;margin-right:10px;\" disabled=\"true\"><option>"
+        + "</option>{0}{1}</select>")
+    SafeHtml disabled(SafeHtml approvedOption, SafeHtml deniedOption);
+
+    @Template("<select style=\"background-color:white;border:1px solid "
+        + "#707172;width:10em;margin-right:10px;\"><option></option>{0}{1}"
+        + "</select>")
+    SafeHtml enabled(SafeHtml approvedOption, SafeHtml deniedOption);
+  }
+
+  private static Template template;
+
   /**
    * The maximum amount that can be approved for a given report.
    */
@@ -134,23 +152,26 @@
 
     private final String approvedText = Expenses.Approval.APPROVED.getText();
     private final String deniedText = Expenses.Approval.DENIED.getText();
-    private final String errorIconHtml;
-    private final String pendingIconHtml;
+    private final SafeHtml errorIconHtml;
+    private final SafeHtml pendingIconHtml;
 
     public ApprovalCell() {
       super("change", "click");
+      if (template == null) {
+        template = GWT.create(Template.class);
+      }
 
       // Cache the html string for the error icon.
       ImageResource errorIcon = Styles.resources().errorIcon();
       AbstractImagePrototype errorImg = AbstractImagePrototype.create(
           errorIcon);
-      errorIconHtml = errorImg.getHTML();
+      errorIconHtml = SafeHtmlUtils.fromTrustedString(errorImg.getHTML());
 
       // Cache the html string for the pending icon.
       ImageResource pendingIcon = Styles.resources().pendingCommit();
       AbstractImagePrototype pendingImg = AbstractImagePrototype.create(
           pendingIcon);
-      pendingIconHtml = pendingImg.getHTML();
+      pendingIconHtml = SafeHtmlUtils.fromTrustedString(pendingImg.getHTML());
     }
 
     @Override
@@ -166,7 +187,7 @@
         // Add the pending icon if it isn't already visible.
         if (viewData == null) {
           Element tmpElem = Document.get().createDivElement();
-          tmpElem.setInnerHTML(pendingIconHtml);
+          tmpElem.setInnerHTML(pendingIconHtml.asString());
           parent.appendChild(tmpElem.getFirstChildElement());
         }
 
@@ -198,7 +219,7 @@
     }
 
     @Override
-    public void render(String value, Object key, StringBuilder sb) {
+    public void render(String value, Object key, SafeHtmlBuilder sb) {
       // Get the view data.
       ApprovalViewData viewData = getViewData(key);
       if (viewData != null && viewData.getPendingApproval().equals(value)) {
@@ -223,42 +244,38 @@
       boolean isApproved = approvedText.equals(renderValue);
       boolean isDenied = deniedText.equals(renderValue);
 
+      SafeHtml approvedOption = createOption(isApproved, approvedText);
+      SafeHtml deniedOption = createOption(isDenied, deniedText);
       // Create the select element.
-      sb.append("<select style='background-color:white;");
-      sb.append("border:1px solid #707172;width:10em;margin-right:10px;'");
       if (isDisabled) {
-        sb.append(" disabled='true'");
+        sb.append(template.disabled(approvedOption, deniedOption));
+      } else {
+        sb.append(template.enabled(approvedOption, deniedOption));
       }
-      sb.append(">");
-      sb.append("<option></option>");
-
-      // Approved Option.
-      sb.append("<option");
-      if (isApproved) {
-        sb.append(" selected='selected'");
-      }
-      sb.append(">").append(approvedText).append("</option>");
-
-      // Denied Option.
-      sb.append("<option");
-      if (isDenied) {
-        sb.append(" selected='selected'");
-      }
-      sb.append(">").append(deniedText).append("</option>");
-
-      sb.append("</select>");
 
       // Add an icon indicating the commit state.
       if (isRejected) {
         // Add error icon if viewData does not match.
         sb.append(errorIconHtml);
-        sb.append(
+        sb.appendHtmlConstant(
             "<a style='padding-left:3px;color:red;' href='javascript:;'>Error!</a>");
       } else if (pendingValue != null) {
         // Add refresh icon if pending.
         sb.append(pendingIconHtml);
       }
     }
+
+    private SafeHtml createOption(boolean selected, String text) {
+      SafeHtmlBuilder builder = new SafeHtmlBuilder();
+      if (selected) {
+        builder.appendHtmlConstant("<option selected=\"selected\">");
+      } else {
+        builder.appendHtmlConstant("<option>");
+      }
+      builder.appendEscaped(text);
+      builder.appendHtmlConstant("</option>");
+      return builder.toSafeHtml();
+    }
   }
 
   /**
@@ -587,12 +604,7 @@
     view.addColumnStyleName(6, common.spacerColumn());
 
     // Spacer column.
-    view.addColumn(new Column<ExpenseRecord, String>(new TextCell()) {
-      @Override
-      public String getValue(ExpenseRecord object) {
-        return "<div style='display:none;'/>";
-      }
-    });
+    view.addColumn(new SpacerColumn<ExpenseRecord>());
 
     // Created column.
     GetValue<ExpenseRecord, Date> createdGetter = new GetValue<
@@ -679,12 +691,7 @@
     });
 
     // Spacer column.
-    view.addColumn(new Column<ExpenseRecord, String>(new TextCell()) {
-      @Override
-      public String getValue(ExpenseRecord object) {
-        return "<div style='display:none;'/>";
-      }
-    });
+    view.addColumn(new SpacerColumn<ExpenseRecord>());
 
     return view;
   }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
index 0f74bfd..7b22e64 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
@@ -36,6 +36,8 @@
 import com.google.gwt.requestfactory.shared.Property;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.sample.expenses.client.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.client.request.ReportRecord;
@@ -190,12 +192,15 @@
         "<span style='color:red;font-weight:bold;'>$1</span>";
 
     @Override
-    public void render(String value, Object viewData, StringBuilder sb) {
+    public void render(String value, Object viewData, SafeHtmlBuilder sb) {
       if (value != null) {
         if (searchRegExp != null) {
-          value = searchRegExp.replace(value, replaceString);
+          // The search regex has already been html-escaped
+          value = searchRegExp.replace(SafeHtmlUtils.htmlEscape(value), replaceString);
+          sb.append(SafeHtmlUtils.fromTrustedString(value));
+        } else {
+          sb.appendEscaped(value);
         }
-        sb.append(value);
       }
     }
   }
@@ -325,7 +330,7 @@
         }
 
         // Highlight as the user types.
-        String text = searchBox.getText();
+        String text = SafeHtmlUtils.htmlEscape(searchBox.getText());
         if (text.length() > 0) {
           searchRegExp = RegExp.compile("(" + text + ")", "ig");
         } else {
@@ -480,12 +485,7 @@
         });
 
     // Spacer column.
-    table.addColumn(new Column<ReportRecord, String>(new TextCell()) {
-      @Override
-      public String getValue(ReportRecord object) {
-        return "<div style='display:none;'/>";
-      }
-    });
+    table.addColumn(new SpacerColumn<ReportRecord>());
 
     // Purpose column.
     addColumn(
@@ -520,12 +520,7 @@
         }, ReportRecord.created);
 
     // Spacer column.
-    table.addColumn(new Column<ReportRecord, String>(new TextCell()) {
-      @Override
-      public String getValue(ReportRecord object) {
-        return "<div style='display:none;'/>";
-      }
-    });
+    table.addColumn(new SpacerColumn<ReportRecord>());
   }
 
   /**
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
index 28d49a6..815b742 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
@@ -23,6 +23,9 @@
 import com.google.gwt.dom.client.Style.Overflow;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.sample.expenses.client.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.client.style.Styles;
@@ -45,6 +48,13 @@
  */
 public class ExpenseTree extends Composite {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<span class=\"{0}\">{1}</span>")
+    SafeHtml span(String classes, String userName);
+  }
+
+  private static Template template;
+
   /**
    * Custom listener for this widget.
    */
@@ -66,7 +76,6 @@
 
     public EmployeeCell() {
       super(Styles.resources().userIcon(), new AbstractCell<EmployeeRecord>() {
-
         private final String usernameStyle = Styles.common().usernameTreeItem();
         private final String usernameStyleSelected =
             Styles.common().usernameTreeItemSelected();
@@ -78,20 +87,23 @@
 
         @Override
         public void render(
-            EmployeeRecord value, Object viewData, StringBuilder sb) {
+            EmployeeRecord value, Object viewData, SafeHtmlBuilder sb) {
           if (value != null) {
-            sb.append(value.getDisplayName()).append("<br>");
-            sb.append("<span class='").append(usernameStyle);
+            StringBuilder classesBuilder = new StringBuilder(usernameStyle);
             if (lastEmployee != null
                 && lastEmployee.getId().equals(value.getId())) {
-              sb.append(" ").append(usernameStyleSelected);
+              classesBuilder.append(" ").append(usernameStyleSelected);
             }
-            sb.append("'>");
-            sb.append(value.getUserName());
-            sb.append("</span>");
+
+            sb.appendEscaped(value.getDisplayName());
+            sb.appendHtmlConstant("<br>");
+            sb.append(template.span(classesBuilder.toString(), value.getUserName()));
           }
         }
       });
+      if (template == null) {
+        template = GWT.create(Template.class);
+      }
     }
   }
 
@@ -179,7 +191,9 @@
      * @return true if the object is a department
      */
     public boolean isDepartment(Object value) {
-      return departments.getList().contains(value.toString());
+      List<String> list = departments.getList();
+      String string = value.toString();
+      return list.contains(string);
     }
 
     public boolean isLeaf(Object value) {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/Expenses.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/Expenses.java
index 12fc4a3..9107852 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/Expenses.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/Expenses.java
@@ -25,6 +25,8 @@
 import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.UserInformationRecord;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.sample.expenses.client.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.client.request.ExpenseRecord;
 import com.google.gwt.sample.expenses.client.request.ExpenseRecordChanged;
@@ -69,20 +71,20 @@
     }
 
     private final String color;
-    private final String iconHtml;
+    private final SafeHtml iconHtml;
     private final String text;
 
     private Approval(String text, String color, ImageResource res) {
       this.text = text;
       this.color = color;
-      this.iconHtml = AbstractImagePrototype.create(res).getHTML();
+      this.iconHtml = SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(res).getHTML());
     }
 
     public String getColor() {
       return color;
     }
 
-    public String getIconHtml() {
+    public SafeHtml getIconHtml() {
       return iconHtml;
     }
 
@@ -101,7 +103,8 @@
   /**
    * The key provider for {@link EmployeeRecord}s.
    */
-  public static final ProvidesKey<EmployeeRecord> EMPLOYEE_RECORD_KEY_PROVIDER = new ProvidesKey<EmployeeRecord>() {
+  public static final ProvidesKey<EmployeeRecord> EMPLOYEE_RECORD_KEY_PROVIDER =
+    new ProvidesKey<EmployeeRecord>() {
     public Object getKey(EmployeeRecord item) {
       return item == null ? null : item.getId();
     }
@@ -110,7 +113,8 @@
   /**
    * The key provider for {@link ExpenseRecord}s.
    */
-  public static final ProvidesKey<ExpenseRecord> EXPENSE_RECORD_KEY_PROVIDER = new ProvidesKey<ExpenseRecord>() {
+  public static final ProvidesKey<ExpenseRecord> EXPENSE_RECORD_KEY_PROVIDER =
+    new ProvidesKey<ExpenseRecord>() {
     public Object getKey(ExpenseRecord item) {
       return item == null ? null : item.getId();
     }
@@ -119,7 +123,8 @@
   /**
    * The key provider for {@link ReportRecord}s.
    */
-  public static final ProvidesKey<ReportRecord> REPORT_RECORD_KEY_PROVIDER = new ProvidesKey<ReportRecord>() {
+  public static final ProvidesKey<ReportRecord> REPORT_RECORD_KEY_PROVIDER =
+    new ProvidesKey<ReportRecord>() {
     public Object getKey(ReportRecord item) {
       return item == null ? null : item.getId();
     }
@@ -157,7 +162,8 @@
     // Add a login widget to the page
     final LoginWidget login = shell.getLoginWidget();
     Receiver<UserInformationRecord> receiver = new Receiver<UserInformationRecord>() {
-      public void onSuccess(UserInformationRecord userInformationRecord, Set<SyncResult> syncResults) {
+      public void onSuccess(UserInformationRecord userInformationRecord,
+          Set<SyncResult> syncResults) {
         login.setUserInformation(userInformationRecord);
       }       
      };
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
index 49b919f..8169395 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
@@ -16,8 +16,12 @@
 package com.google.gwt.sample.expenses.client;
 
 import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.sample.expenses.client.request.ExpenseRecord;
 import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.client.request.ReportRecord;
@@ -39,6 +43,13 @@
  */
 public class MobileExpenseList extends Composite implements MobilePage {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div class=\"item\">{0}{1} ({2})</div>")
+    SafeHtml div(SafeHtml approvalIcon, String description, String amount);
+  }
+
+  private static Template template;
+
   /**
    * The auto refresh interval in milliseconds.
    */
@@ -60,33 +71,34 @@
    */
   private class ExpenseCell extends AbstractCell<ExpenseRecord> {
 
-    private final String approvedHtml;
+    private final SafeHtml approvedHtml;
     private final String approvedText = Expenses.Approval.APPROVED.getText();
-    private final String blankHtml;
-    private final String deniedHtml;
+    private final SafeHtml blankHtml;
+    private final SafeHtml deniedHtml;
     private final String deniedText = Expenses.Approval.DENIED.getText();
 
     public ExpenseCell() {
+      if (template == null) {
+        template = GWT.create(Template.class);
+      }
       approvedHtml = Expenses.Approval.APPROVED.getIconHtml();
       blankHtml = Expenses.Approval.BLANK.getIconHtml();
       deniedHtml = Expenses.Approval.DENIED.getIconHtml();
     }
 
     @Override
-    public void render(ExpenseRecord value, Object viewData, StringBuilder sb) {
-      sb.append("<div class='item'>");
+    public void render(ExpenseRecord value, Object viewData, SafeHtmlBuilder sb) {
       String approval = value.getApproval();
+      SafeHtml approvalIcon;
       if (approvedText.equals(approval)) {
-        sb.append(approvedHtml);
+        approvalIcon = approvedHtml;
       } else if (deniedText.equals(approval)) {
-        sb.append(deniedHtml);
+        approvalIcon = deniedHtml;
       } else {
-        sb.append(blankHtml);
+        approvalIcon = blankHtml;
       }
-      sb.append(value.getDescription());
-      sb.append(" (");
-      sb.append(ExpensesMobile.formatCurrency(value.getAmount()));
-      sb.append(")</div>");
+      sb.append(template.div(approvalIcon, value.getDescription(),
+          ExpensesMobile.formatCurrency(value.getAmount())));
     }
   }
 
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
index 5a3479d..e9817f1 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
@@ -18,6 +18,7 @@
 import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.sample.expenses.client.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.client.request.ReportRecord;
@@ -75,8 +76,10 @@
     reportList = new CellList<ReportRecord>(new AbstractCell<ReportRecord>() {
       @Override
       public void render(
-          ReportRecord value, Object viewData, StringBuilder sb) {
-        sb.append("<div class='item'>" + value.getPurpose() + "</div>");
+          ReportRecord value, Object viewData, SafeHtmlBuilder sb) {
+        sb.appendHtmlConstant("<div class='item'>");
+        sb.appendEscaped(value.getPurpose());
+        sb.appendHtmlConstant("</div>");
       }
     });
 
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SortableHeader.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SortableHeader.java
index 8a00755..5899923 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SortableHeader.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SortableHeader.java
@@ -19,6 +19,10 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.cellview.client.Header;
 import com.google.gwt.user.client.ui.AbstractImagePrototype;
 
@@ -28,6 +32,19 @@
  */
 public class SortableHeader extends Header<String> {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div style=\"position:relative;cursor:hand;cursor:pointer;"
+        + "padding-right:{0}px;\">{1}<div>{2}</div></div>")
+    SafeHtml sorted(int imageWidth, SafeHtml arrow, String text);
+
+    @Template("<div style=\"position:relative;cursor:hand;cursor:pointer;"
+        + "padding-right:{0}px;\"><div style=\"position:absolute;display:none;"
+        + "\"></div><div>{1}</div></div>")
+    SafeHtml unsorted(int imageWidth, String text);
+  }
+
+  private static Template template;
+
   /**
    * Image resources.
    */
@@ -40,13 +57,14 @@
 
   private static final Resources RESOURCES = GWT.create(Resources.class);
   private static final int IMAGE_WIDTH = 16;
-  private static final String DOWN_ARROW = makeImage(RESOURCES.downArrow());
-  private static final String UP_ARROW = makeImage(RESOURCES.upArrow());
+  private static final SafeHtml DOWN_ARROW = makeImage(RESOURCES.downArrow());
+  private static final SafeHtml UP_ARROW = makeImage(RESOURCES.upArrow());
 
-  private static String makeImage(ImageResource resource) {
+  private static SafeHtml makeImage(ImageResource resource) {
     AbstractImagePrototype proto = AbstractImagePrototype.create(resource);
-    return proto.getHTML().replace("style='",
+    String html = proto.getHTML().replace("style='",
         "style='position:absolute;right:0px;top:0px;");
+    return SafeHtmlUtils.fromTrustedString(html);
   }
 
   private boolean reverseSort = false;
@@ -55,6 +73,9 @@
 
   SortableHeader(String text) {
     super(new ClickableTextCell());
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.text = text;
   }
 
@@ -68,24 +89,12 @@
   }
 
   @Override
-  public void render(StringBuilder sb) {
-    int imageWidth = IMAGE_WIDTH;
-    sb.append("<div style='position:relative;cursor:hand;cursor:pointer;");
-    sb.append("padding-right:");
-    sb.append(imageWidth);
-    sb.append("px;'>");
+  public void render(SafeHtmlBuilder sb) {
     if (sorted) {
-      if (reverseSort) {
-        sb.append(DOWN_ARROW);
-      } else {
-        sb.append(UP_ARROW);
-      }
+      sb.append(template.sorted(IMAGE_WIDTH, reverseSort ? DOWN_ARROW : UP_ARROW, text));
     } else {
-      sb.append("<div style='position:absolute;display:none;'></div>");
+      sb.append(template.unsorted(IMAGE_WIDTH, text));
     }
-    sb.append("<div>");
-    sb.append(text);
-    sb.append("</div></div>");
   }
 
   public void setReverseSort(boolean reverseSort) {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SpacerColumn.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SpacerColumn.java
new file mode 100644
index 0000000..976931a
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/SpacerColumn.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.sample.expenses.client;
+
+import com.google.gwt.cell.client.SafeHtmlCell;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.cellview.client.Column;
+
+class SpacerColumn<T> extends Column<T, SafeHtml> {
+  
+  private static final SafeHtmlCell CELL = new SafeHtmlCell();
+
+  public SpacerColumn() {
+    super(CELL);
+  }
+  
+  @Override
+  public SafeHtml getValue(T object) {
+    return SafeHtmlUtils.fromSafeConstant("<div style='display:none;'/>");
+  }
+};
\ No newline at end of file
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
index 6df28dc..e760a7f 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactTreeViewModel.java
@@ -28,6 +28,7 @@
 import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.Category;
 import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.ContactInfo;
 import com.google.gwt.sample.showcase.client.content.cell.CwCellList.ContactCell;
@@ -70,10 +71,10 @@
     }
 
     @Override
-    public void render(Category value, Object key, StringBuilder sb) {
+    public void render(Category value, Object key, SafeHtmlBuilder sb) {
       if (value != null) {
-        sb.append(imageHtml).append(" ");
-        sb.append(value.getDisplayName());
+        sb.appendHtmlConstant(imageHtml).appendEscaped(" ");
+        sb.appendEscaped(value.getDisplayName());
       }
     }
   }
@@ -127,10 +128,9 @@
   private static class LetterCountCell extends AbstractCell<LetterCount> {
 
     @Override
-    public void render(LetterCount value, Object key, StringBuilder sb) {
+    public void render(LetterCount value, Object key, SafeHtmlBuilder sb) {
       if (value != null) {
-        sb.append(value.firstLetter);
-        sb.append(" (").append(value.count).append(")");
+        sb.appendEscaped(value.firstLetter + " (" + value.count + ")");
       }
     }
   }
@@ -211,10 +211,10 @@
       }
 
       @Override
-      public void render(ContactInfo value, Object key, StringBuilder sb) {
-        sb.append("<table><tbody><tr>");
+      public void render(ContactInfo value, Object key, SafeHtmlBuilder sb) {
+        sb.appendHtmlConstant("<table><tbody><tr>");
         super.render(value, key, sb);
-        sb.append("</tr></tbody></table>");
+        sb.appendHtmlConstant("</tr></tbody></table>");
       }
 
       @Override
@@ -224,12 +224,12 @@
       }
 
       @Override
-      protected <X> void render(ContactInfo value, Object key, StringBuilder sb,
+      protected <X> void render(ContactInfo value, Object key, SafeHtmlBuilder sb,
           HasCell<ContactInfo, X> hasCell) {
         Cell<X> cell = hasCell.getCell();
-        sb.append("<td>");
+        sb.appendHtmlConstant("<td>");
         cell.render(hasCell.getValue(value), key, sb);
-        sb.append("</td>");
+        sb.appendHtmlConstant("</td>");
       }
     };
   }
@@ -263,7 +263,9 @@
     } else if (value instanceof LetterCount) {
       // Return the contacts with the specified character and first name.
       LetterCount count = (LetterCount) value;
-          List<ContactInfo> contacts = ContactDatabase.get().queryContactsByCategoryAndFirstName(count.category, count.firstLetter + "");
+          List<ContactInfo> contacts =
+            ContactDatabase.get().queryContactsByCategoryAndFirstName(count.category,
+                count.firstLetter + "");
       ListDataProvider<ContactInfo> dataProvider = new ListDataProvider<
           ContactInfo>(contacts);
       dataProvider.setKeyProvider(ContactInfo.KEY_PROVIDER);
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
index 1a4a0ec..bf8cf43 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.RunAsyncCallback;
 import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.sample.showcase.client.ContentWidget;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseData;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseRaw;
@@ -203,7 +204,7 @@
         selectionModel.setSelected(object, value);
       }
     });
-    cellTable.addColumn(checkColumn, "<br>");
+    cellTable.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("<br>"));
 
     // First name.
     Column<ContactInfo, String> firstNameColumn = new Column<
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 9a6a528..5dff949 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,10 @@
 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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SimpleHtmlSanitizer;
 import com.google.gwt.sample.showcase.client.ContentWidget;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseData;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseRaw;
@@ -41,6 +45,7 @@
  */
 @ShowcaseRaw({"ContactDatabase.java"})
 public class CwCellValidation extends ContentWidget {
+
   /**
    * The constants used in this Content Widget.
    */
@@ -58,29 +63,9 @@
     String cwCellValidationName();
   }
 
-  /**
-   * The ViewData used by {@link ValidatableInputCell}.
-   */
-  @ShowcaseSource
-  private static class ValidationData {
-    private String value;
-    private boolean invalid;
-
-    public String getValue() {
-      return value;
-    }
-
-    public boolean isInvalid() {
-      return invalid;
-    }
-
-    public void setInvalid(boolean invalid) {
-      this.invalid = invalid;
-    }
-
-    public void setValue(String value) {
-      this.value = value;
-    }
+  interface Template extends SafeHtmlTemplates {
+    @Template("<input type=\"text\" value=\"{0}\" style=\"color:{1}\"/>")
+    SafeHtml input(String value, String color);
   }
 
   /**
@@ -90,11 +75,14 @@
   private static class ValidatableInputCell extends AbstractEditableCell<
       String, ValidationData> {
 
-    private String errorMessage;
+    private SafeHtml errorMessage;
 
     public ValidatableInputCell(String errorMessage) {
       super("change");
-      this.errorMessage = errorMessage;
+      if (template == null) {
+        template = GWT.create(Template.class);
+      }
+      this.errorMessage = SimpleHtmlSanitizer.sanitizeHtml(errorMessage);
     }
 
     @Override
@@ -124,7 +112,7 @@
     }
 
     @Override
-    public void render(String value, Object key, StringBuilder sb) {
+    public void render(String value, Object key, SafeHtmlBuilder sb) {
       // Get the view data.
       ValidationData viewData = getViewData(key);
       if (viewData != null && viewData.getValue().equals(value)) {
@@ -141,29 +129,46 @@
       String pendingValue = (viewData == null) ? null : viewData.getValue();
       boolean invalid = (viewData == null) ? false : viewData.isInvalid();
 
-      sb.append("<input type=\"text\" value=\"");
-      if (pendingValue != null) {
-        sb.append(pendingValue);
-      } else {
-        sb.append(value);
-      }
-      sb.append("\" style=\"color:");
-      if (pendingValue != null) {
-        sb.append(invalid ? "red" : "blue");
-      } else {
-        sb.append("black");
-      }
-      sb.append("\"></input>");
+      sb.append(template.input(pendingValue != null ? pendingValue : value,
+          pendingValue != null ? (invalid ? "red" : "blue") : ("black")));
 
       if (invalid) {
-        sb.append("&nbsp;<span style='color:red;'>");
+        sb.appendHtmlConstant("&nbsp;<span style='color:red;'>");
         sb.append(errorMessage);
-        sb.append("</span>");
+        sb.appendHtmlConstant("</span>");
       }
     }
   }
 
   /**
+   * The ViewData used by {@link ValidatableInputCell}.
+   */
+  @ShowcaseSource
+  private static class ValidationData {
+    private boolean invalid;
+    private String value;
+
+    public String getValue() {
+      return value;
+    }
+
+    public boolean isInvalid() {
+      return invalid;
+    }
+
+    public void setInvalid(boolean invalid) {
+      this.invalid = invalid;
+    }
+
+    public void setValue(String value) {
+      this.value = value;
+    }
+  }
+
+  // Used by ValidatableInputCell
+  private static Template template;
+
+  /**
    * Checks if an address is valid. A valid address consists of a number
    * followed by a street name, which may be composed of multiple words.
    *
diff --git a/user/src/com/google/gwt/cell/Cell.gwt.xml b/user/src/com/google/gwt/cell/Cell.gwt.xml
index ce4a59e..1a74ca0 100644
--- a/user/src/com/google/gwt/cell/Cell.gwt.xml
+++ b/user/src/com/google/gwt/cell/Cell.gwt.xml
@@ -1,12 +1,12 @@
 <!--
   Copyright 2010 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
@@ -15,5 +15,7 @@
 -->
 <module>
    <inherits name="com.google.gwt.dom.DOM"/>
+   <inherits name="com.google.gwt.safehtml.SafeHtml"/>
+   <inherits name="com.google.gwt.text.Text"/>
    <source path="client"/>
 </module>
diff --git a/user/src/com/google/gwt/cell/client/AbstractCell.java b/user/src/com/google/gwt/cell/client/AbstractCell.java
index b1f8f2b..88c0121 100644
--- a/user/src/com/google/gwt/cell/client/AbstractCell.java
+++ b/user/src/com/google/gwt/cell/client/AbstractCell.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -95,12 +96,12 @@
       NativeEvent event, ValueUpdater<C> valueUpdater) {
   }
 
-  public abstract void render(C value, Object key, StringBuilder sb);
+  public abstract void render(C value, Object key, SafeHtmlBuilder sb);
 
   public void setValue(Element parent, C value, Object key) {
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     render(value, key, sb);
-    parent.setInnerHTML(sb.toString());
+    parent.setInnerHTML(sb.toSafeHtml().asString());
   }
 
   /**
diff --git a/user/src/com/google/gwt/cell/client/AbstractSafeHtmlCell.java b/user/src/com/google/gwt/cell/client/AbstractSafeHtmlCell.java
new file mode 100644
index 0000000..7841260
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/AbstractSafeHtmlCell.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 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.cell.client;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+
+import java.util.Set;
+
+/**
+ * A superclass for {@link Cell}s that render or escape a String argument as
+ * HTML.
+ *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ *
+ * @param <C> the type that this Cell represents
+ */
+public abstract class AbstractSafeHtmlCell<C> extends AbstractCell<C> {
+
+  private final SafeHtmlRenderer<C> renderer;
+
+  public AbstractSafeHtmlCell(SafeHtmlRenderer<C> renderer,
+      String... consumedEvents) {
+    super(consumedEvents);
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
+    this.renderer = renderer;
+  }
+
+  public AbstractSafeHtmlCell(SafeHtmlRenderer<C> renderer,
+      Set<String> consumedEvents) {
+    super(consumedEvents);
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
+    this.renderer = renderer;
+  }
+
+  public SafeHtmlRenderer<C> getRenderer() {
+    return renderer;
+  }
+
+  @Override
+  public void render(C data, Object key, SafeHtmlBuilder sb) {
+    if (data == null) {
+      render((SafeHtml) null, key, sb);
+    } else {
+      render(renderer.render(data), key, sb);
+    }
+  }
+
+  protected abstract void render(SafeHtml data, Object key, SafeHtmlBuilder sb);
+}
diff --git a/user/src/com/google/gwt/cell/client/ActionCell.java b/user/src/com/google/gwt/cell/client/ActionCell.java
index 6bc4096..bcc09e5 100644
--- a/user/src/com/google/gwt/cell/client/ActionCell.java
+++ b/user/src/com/google/gwt/cell/client/ActionCell.java
@@ -17,6 +17,9 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 
 /**
  * A cell that renders a button and takes a delegate to perform actions on
@@ -39,7 +42,7 @@
     void execute(T object);
   }
 
-  private final String html;
+  private final SafeHtml html;
   private final Delegate<C> delegate;
 
   /**
@@ -48,10 +51,22 @@
    * @param message the message to display on the button
    * @param delegate the delegate that will handle events
    */
-  public ActionCell(String message, Delegate<C> delegate) {
+  public ActionCell(SafeHtml message, Delegate<C> delegate) {
     super("click");
     this.delegate = delegate;
-    this.html = "<button>" + message + "</button>";
+    this.html = new SafeHtmlBuilder().appendHtmlConstant("<button>").append(
+        message).appendHtmlConstant("</button>").toSafeHtml();
+  }
+
+  /**
+   * Construct a new {@link ActionCell} with a text String that does not contain
+   * HTML markup.
+   *
+   * @param text the text to display on the button
+   * @param delegate the delegate that will handle events
+   */
+  public ActionCell(String text, Delegate<C> delegate) {
+    this(SafeHtmlUtils.fromString(text), delegate);
   }
 
   @Override
@@ -63,7 +78,7 @@
   }
 
   @Override
-  public void render(C value, Object key, StringBuilder sb) {
+  public void render(C value, Object key, SafeHtmlBuilder sb) {
     sb.append(html);
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/ButtonCell.java b/user/src/com/google/gwt/cell/client/ButtonCell.java
index 423beb2..8372819 100644
--- a/user/src/com/google/gwt/cell/client/ButtonCell.java
+++ b/user/src/com/google/gwt/cell/client/ButtonCell.java
@@ -17,6 +17,10 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 /**
  * A {@link Cell} used to render a button.
@@ -25,10 +29,14 @@
  * Note: This class is new and its interface subject to change.
  * </p>
  */
-public class ButtonCell extends AbstractCell<String> {
+public class ButtonCell extends AbstractSafeHtmlCell<String> {
 
   public ButtonCell() {
-    super("mouseup");
+    super(SimpleSafeHtmlRenderer.getInstance(), "mouseup");
+  }
+
+  public ButtonCell(SafeHtmlRenderer<String> renderer) {
+    super(renderer, "mouseup");
   }
 
   @Override
@@ -40,11 +48,11 @@
   }
 
   @Override
-  public void render(String data, Object key, StringBuilder sb) {
-    sb.append("<button>");
+  public void render(SafeHtml data, Object key, SafeHtmlBuilder sb) {
+    sb.appendHtmlConstant("<button>");
     if (data != null) {
       sb.append(data);
     }
-    sb.append("</button>");
+    sb.appendHtmlConstant("</button>");
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/Cell.java b/user/src/com/google/gwt/cell/client/Cell.java
index 96d5310..d14d8d0 100644
--- a/user/src/com/google/gwt/cell/client/Cell.java
+++ b/user/src/com/google/gwt/cell/client/Cell.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 import java.util.Set;
 
@@ -66,7 +67,7 @@
    * given element and key. While a cell is editing, widgets containing the cell
    * may chooses to pass keystrokes directly to the cell rather than using them
    * for navigation purposes.
-   * 
+   *
    * @param parent the parent Element
    * @param value the value associated with the cell
    * @param key the unique key associated with the row object
@@ -91,15 +92,15 @@
    * {@link Element#setInnerHTML} on a container element.
    * @param value the cell value to be rendered
    * @param key the unique key associated with the row object
-   * @param sb the StringBuilder to be written to
+   * @param sb the {@link SafeHtmlBuilder} to be written to
    */
-  void render(C value, Object key, StringBuilder sb);
+  void render(C value, Object key, SafeHtmlBuilder sb);
 
   /**
    * This method may be used by cell containers to set the value on a single
    * cell directly, rather than using {@link Element#setInnerHTML(String)}. See
    * {@link AbstractCell#setValue(Element, Object, Object)} for a default
-   * implementation that uses {@link #render(Object, Object, StringBuilder)}.
+   * implementation that uses {@link #render(Object, Object, SafeHtmlBuilder)}.
    *
    * @param parent the parent Element
    * @param value the value associated with the cell
diff --git a/user/src/com/google/gwt/cell/client/CheckboxCell.java b/user/src/com/google/gwt/cell/client/CheckboxCell.java
index b355fa8..5c87972 100644
--- a/user/src/com/google/gwt/cell/client/CheckboxCell.java
+++ b/user/src/com/google/gwt/cell/client/CheckboxCell.java
@@ -19,6 +19,9 @@
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 
 /**
  * A {@link Cell} used to render a checkbox. The value of the checkbox may be
@@ -33,13 +36,14 @@
   /**
    * An html string representation of a checked input box.
    */
-  private static final String INPUT_CHECKED =
-      "<input type=\"checkbox\" checked/>";
+  private static final SafeHtml INPUT_CHECKED =
+    SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" checked/>");
 
   /**
    * An html string representation of an unchecked input box.
    */
-  private static final String INPUT_UNCHECKED = "<input type=\"checkbox\"/>";
+  private static final SafeHtml INPUT_UNCHECKED =
+    SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\"/>");
 
   private final boolean isSelectBox;
 
@@ -95,7 +99,7 @@
   }
 
   @Override
-  public void render(Boolean value, Object key, StringBuilder sb) {
+  public void render(Boolean value, Object key, SafeHtmlBuilder sb) {
     // Get the view data.
     Boolean viewData = getViewData(key);
     if (viewData != null && viewData.equals(value)) {
diff --git a/user/src/com/google/gwt/cell/client/ClickableTextCell.java b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
index a3e8521..cbda346 100644
--- a/user/src/com/google/gwt/cell/client/ClickableTextCell.java
+++ b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
@@ -17,6 +17,10 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 /**
  * A {@link Cell} used to render text. Clicking on the cell causes its
@@ -26,10 +30,14 @@
  * Note: This class is new and its interface subject to change.
  * </p>
  */
-public class ClickableTextCell extends AbstractCell<String> {
+public class ClickableTextCell extends AbstractSafeHtmlCell<String> {
 
   public ClickableTextCell() {
-    super("click");
+    super(SimpleSafeHtmlRenderer.getInstance(), "click");
+  }
+
+  public ClickableTextCell(SafeHtmlRenderer<String> renderer) {
+    super(renderer, "click");
   }
 
   @Override
@@ -42,7 +50,7 @@
   }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  protected void render(SafeHtml value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
       sb.append(value);
     }
diff --git a/user/src/com/google/gwt/cell/client/CompositeCell.java b/user/src/com/google/gwt/cell/client/CompositeCell.java
index 5a732e5..b86d66d 100644
--- a/user/src/com/google/gwt/cell/client/CompositeCell.java
+++ b/user/src/com/google/gwt/cell/client/CompositeCell.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -137,7 +138,7 @@
   }
 
   @Override
-  public void render(C value, Object key, StringBuilder sb) {
+  public void render(C value, Object key, SafeHtmlBuilder sb) {
     for (HasCell<C, ?> hasCell : hasCells) {
       render(value, key, sb, hasCell);
     }
@@ -165,11 +166,11 @@
   }
 
   protected <X> void render(
-      C value, Object key, StringBuilder sb, HasCell<C, X> hasCell) {
+      C value, Object key, SafeHtmlBuilder sb, HasCell<C, X> hasCell) {
     Cell<X> cell = hasCell.getCell();
-    sb.append("<span>");
+    sb.appendHtmlConstant("<span>");
     cell.render(hasCell.getValue(value), key, sb);
-    sb.append("</span>");
+    sb.appendHtmlConstant("</span>");
   }
 
   private <X> void onBrowserEventImpl(Element parent, final C object,
diff --git a/user/src/com/google/gwt/cell/client/DateCell.java b/user/src/com/google/gwt/cell/client/DateCell.java
index 9aeaf3c..c03e96c 100644
--- a/user/src/com/google/gwt/cell/client/DateCell.java
+++ b/user/src/com/google/gwt/cell/client/DateCell.java
@@ -17,6 +17,9 @@
 
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 import java.util.Date;
 
@@ -31,27 +34,61 @@
 
   private final DateTimeFormat format;
 
+  private final SafeHtmlRenderer<String> renderer;
+
   /**
    * Construct a new {@link DateCell} using the format
-   * {@link PredefinedFormat#DATE_FULL}.
+   * {@link PredefinedFormat#DATE_FULL} and a {@link SimpleSafeHtmlRenderer}.
    */
   public DateCell() {
-    this(DateTimeFormat.getFormat(PredefinedFormat.DATE_FULL));
+    this(DateTimeFormat.getFormat(PredefinedFormat.DATE_FULL),
+        SimpleSafeHtmlRenderer.getInstance());
   }
 
   /**
-   * Construct a new {@link DateCell} using the specified format.
+   * Construct a new {@link DateCell} using the format
+   * {@link PredefinedFormat#DATE_FULL} and a {@link SimpleSafeHtmlRenderer}.
+   *
+   * @param renderer a non-null {@link SafeHtmlRenderer} used to render the
+   *          formatted date as HTML
+   */
+  public DateCell(SafeHtmlRenderer<String> renderer) {
+    this(DateTimeFormat.getFormat(PredefinedFormat.DATE_FULL), renderer);
+  }
+
+  /**
+   * Construct a new {@link DateCell} using the specified format and a
+   * {@link SimpleSafeHtmlRenderer}.
    *
    * @param format the {@link DateTimeFormat} used to render the date
    */
   public DateCell(DateTimeFormat format) {
+    this(format, SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  /**
+   * Construct a new {@link DateCell} using the specified format and the given
+   * {@link SafeHtmlRenderer}.
+   *
+   * @param format the {@link DateTimeFormat} used to render the date
+   * @param renderer a non-null {@link SafeHtmlRenderer} used to render the
+   *          formatted date
+   */
+  public DateCell(DateTimeFormat format, SafeHtmlRenderer<String> renderer) {
+    if (format == null) {
+      throw new IllegalArgumentException("format == null");
+    }
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
     this.format = format;
+    this.renderer = renderer;
   }
 
   @Override
-  public void render(Date value, Object key, StringBuilder sb) {
+  public void render(Date value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
-      sb.append(format.format(value));
+      sb.append(renderer.render(format.format(value)));
     }
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/DatePickerCell.java b/user/src/com/google/gwt/cell/client/DatePickerCell.java
index c8386f3..532a084 100644
--- a/user/src/com/google/gwt/cell/client/DatePickerCell.java
+++ b/user/src/com/google/gwt/cell/client/DatePickerCell.java
@@ -20,6 +20,9 @@
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Event.NativePreviewEvent;
 import com.google.gwt.user.client.ui.PopupPanel;
@@ -60,6 +63,7 @@
   private Element lastParent;
   private Date lastValue;
   private PopupPanel panel;
+  private final SafeHtmlRenderer<String> renderer;
   private ValueUpdater<Date> valueUpdater;
 
   /**
@@ -68,15 +72,41 @@
    */
   @SuppressWarnings("deprecation")
   public DatePickerCell() {
-    this(DateTimeFormat.getFullDateFormat());
+    this(DateTimeFormat.getFullDateFormat(),
+        SimpleSafeHtmlRenderer.getInstance());
   }
 
   /**
-   * Constructs a new DatePickerCell that uses the given date/time format.
+   * Constructs a new DatePickerCell that uses the given date/time format and a
+   * {@link SimpleSafeHtmlRenderer}.
    */
   public DatePickerCell(DateTimeFormat format) {
+    this(format, SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  /**
+   * Constructs a new DatePickerCell that uses the date/time format given by
+   * {@link DateTimeFormat#getFullDateFormat} and the given
+   * {@link SafeHtmlRenderer}.
+   */
+  public DatePickerCell(SafeHtmlRenderer<String> renderer) {
+    this(DateTimeFormat.getFullDateFormat(), renderer);
+  }
+
+  /**
+   * Constructs a new DatePickerCell that uses the given date/time format and
+   * {@link SafeHtmlRenderer}.
+   */
+  public DatePickerCell(DateTimeFormat format, SafeHtmlRenderer<String> renderer) {
     super("click");
+    if (format == null) {
+      throw new IllegalArgumentException("format == null");
+    }
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
     this.format = format;
+    this.renderer = renderer;
 
     this.datePicker = new DatePicker();
     this.panel = new PopupPanel(true, true) {
@@ -129,7 +159,7 @@
   }
 
   @Override
-  public void render(Date value, Object key, StringBuilder sb) {
+  public void render(Date value, Object key, SafeHtmlBuilder sb) {
     // Get the view data.
     Date viewData = getViewData(key);
     if (viewData != null && viewData.equals(value)) {
@@ -137,10 +167,14 @@
       viewData = null;
     }
 
+    String s = null;
     if (viewData != null) {
-      sb.append(format.format(viewData));
+      s = format.format(viewData);
     } else if (value != null) {
-      sb.append(format.format(value));
+      s = format.format(value);
+    }
+    if (s != null) {
+      sb.append(renderer.render(s));
     }
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/EditTextCell.java b/user/src/com/google/gwt/cell/client/EditTextCell.java
index 8ec75af..e008190 100644
--- a/user/src/com/google/gwt/cell/client/EditTextCell.java
+++ b/user/src/com/google/gwt/cell/client/EditTextCell.java
@@ -15,11 +15,17 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 /**
  * An editable text cell. Click to edit, escape to cancel, return to commit.
@@ -35,6 +41,11 @@
 public class EditTextCell extends AbstractEditableCell<
     String, EditTextCell.ViewData> {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<input type=\"text\" value=\"{0}\"></input>")
+    SafeHtml input(String value);
+  }
+
   /**
    * The view data object used by this cell. We need to store both the text and
    * the state because this cell is rendered differently in edit mode. If we did
@@ -43,12 +54,7 @@
    * string.
    */
   static class ViewData {
-    /**
-     * Keep track of the original value at the start of the edit, which might be
-     * the edited value from the previous edit and NOT the actual value.
-     */
-    private String original;
-    private String text;
+
     private boolean isEditing;
 
     /**
@@ -57,6 +63,14 @@
     private boolean isEditingAgain;
 
     /**
+     * Keep track of the original value at the start of the edit, which might be
+     * the edited value from the previous edit and NOT the actual value.
+     */
+    private String original;
+
+    private String text;
+
+    /**
      * Construct a new ViewData in editing mode.
      *
      * @param text the text to edit
@@ -122,8 +136,23 @@
     }
   }
 
+  private static Template template;
+
+  private final SafeHtmlRenderer<String> renderer;
+
   public EditTextCell() {
+    this(SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  public EditTextCell(SafeHtmlRenderer<String> renderer) {
     super("click", "keyup", "keydown", "blur");
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
+    this.renderer = renderer;
   }
 
   @Override
@@ -158,7 +187,7 @@
   }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(String value, Object key, SafeHtmlBuilder sb) {
     // Get the view data.
     ViewData viewData = getViewData(key);
     if (viewData != null && !viewData.isEditing() && value != null
@@ -168,15 +197,18 @@
     }
 
     if (viewData != null) {
+      String text = viewData.getText();
+      SafeHtml html = renderer.render(text);
       if (viewData.isEditing()) {
-        sb.append(
-            "<input type='text' value='" + viewData.getText() + "'></input>");
+        // Note the template will not treat SafeHtml specially
+        sb.append(template.input(html.asString()));
       } else {
         // The user pressed enter, but view data still exists.
-        sb.append(viewData.getText());
+        sb.append(html);
       }
     } else if (value != null) {
-      sb.append(value);
+      SafeHtml html = renderer.render(value);
+      sb.append(html);
     }
   }
 
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
index 457eec8e..83b6484 100644
--- a/user/src/com/google/gwt/cell/client/IconCellDecorator.java
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -15,10 +15,15 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
 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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.ui.HasVerticalAlignment;
 import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
 
@@ -35,11 +40,25 @@
  */
 public class IconCellDecorator<C> implements Cell<C> {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div style=\"position:relative;padding-{0}:{1}px;\">{2}<div>{3}</div></div>")
+    SafeHtml outerDiv(String direction, int width, SafeHtml icon, SafeHtml cellContents);
+
+    @Template("<div style=\"position:absolute;{0}:0px;top:0px;height:100%;width:{1}px;\"></div>")
+    SafeHtml imagePlaceholder(String direction, int width);
+  }
+
+  private static Template template;
+
   private final Cell<C> cell;
-  private final String iconHtml;
+
+  private final String direction = LocaleInfo.getCurrentLocale().isRTL() ? "right" : "left";
+
+  private final SafeHtml iconHtml;
+
   private final int imageWidth;
-  private final String outerDivHtml;
-  private final String placeHolderHtml;
+
+  private final SafeHtml placeHolderHtml;
 
   /**
    * Construct a new {@link IconCellDecorator}. The icon and the content will be
@@ -62,20 +81,13 @@
    */
   public IconCellDecorator(ImageResource icon, Cell<C> cell,
       VerticalAlignmentConstant valign, int spacing) {
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.cell = cell;
     this.iconHtml = getImageHtml(icon, valign, false);
     this.imageWidth = icon.getWidth() + 6;
     this.placeHolderHtml = getImageHtml(icon, valign, true);
-
-    // Cache the HTML for the outer div.
-    String theOuterDivHtml = "<div style='position:relative;";
-    if (LocaleInfo.getCurrentLocale().isRTL()) {
-      theOuterDivHtml += "padding-right:";
-    } else {
-      theOuterDivHtml += "padding-left:";
-    }
-    theOuterDivHtml += imageWidth + "px;'>";
-    this.outerDivHtml = theOuterDivHtml;
   }
 
   public boolean dependsOnSelection() {
@@ -99,16 +111,12 @@
     cell.onBrowserEvent(getCellParent(parent), value, key, event, valueUpdater);
   }
 
-  public void render(C value, Object key, StringBuilder sb) {
-    sb.append(outerDivHtml);
-    if (isIconUsed(value)) {
-      sb.append(getIconHtml(value));
-    } else {
-      sb.append(placeHolderHtml);
-    }
-    sb.append("<div>");
-    cell.render(value, key, sb);
-    sb.append("</div></div>");
+  public void render(C value, Object key, SafeHtmlBuilder sb) {
+    SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+    cell.render(value, key, cellBuilder);
+
+    sb.append(template.outerDiv(direction, imageWidth, isIconUsed(value)
+        ? getIconHtml(value) : placeHolderHtml, cellBuilder.toSafeHtml()));
   }
 
   public void setValue(Element parent, C value, Object key) {
@@ -116,13 +124,13 @@
   }
 
   /**
-   * Get the HTML string that represents the icon. Override this method to
+   * Get the safe HTML string that represents the icon. Override this method to
    * change the icon based on the value.
    *
    * @param value the value being rendered
    * @return the HTML string that represents the icon
    */
-  protected String getIconHtml(C value) {
+  protected SafeHtml getIconHtml(C value) {
     return iconHtml;
   }
 
@@ -147,30 +155,20 @@
    * @return the rendered HTML
    */
   // TODO(jlabanca): Move this to a Utility class.
-  String getImageHtml(ImageResource res, VerticalAlignmentConstant valign,
+  SafeHtml getImageHtml(ImageResource res, VerticalAlignmentConstant valign,
       boolean isPlaceholder) {
-    // Add the position and dimensions.
-    StringBuilder sb = new StringBuilder();
-    sb.append("<div style=\"position:absolute;top:0px;height:100%;");
-    if (LocaleInfo.getCurrentLocale().isRTL()) {
-      sb.append("right:0px;");
+    if (isPlaceholder) {
+      return template.imagePlaceholder(direction, res.getWidth());
     } else {
-      sb.append("left:0px;");
+      String vert = valign == HasVerticalAlignment.ALIGN_MIDDLE ? "center"
+          : valign.getVerticalAlignString();
+      // Templates are having problems with url('data:image/png;base64,...')
+      return SafeHtmlUtils.fromTrustedString("<div style=\"position:absolute;"
+          + direction + ":0px;top:0px;height:100%;width:" + res.getWidth()
+          + "px;background:url('" + res.getURL() + "') no-repeat scroll "
+          + SafeHtmlUtils.htmlEscape(vert) // for safety
+          + " center transparent;\"></div>");
     }
-    sb.append("width:").append(res.getWidth()).append("px;");
-
-    // Add the background, vertically centered.
-    if (!isPlaceholder) {
-      String vert = valign == HasVerticalAlignment.ALIGN_MIDDLE
-          ? "center" : valign.getVerticalAlignString();
-      sb.append("background:url('").append(res.getURL()).append("') ");
-      sb.append("no-repeat scroll ").append(vert).append(
-          " center transparent;");
-    }
-
-    // Close the div and return.
-    sb.append("\"></div>");
-    return sb.toString();
   }
 
   /**
diff --git a/user/src/com/google/gwt/cell/client/ImageCell.java b/user/src/com/google/gwt/cell/client/ImageCell.java
index 23be4ca..42ee802 100644
--- a/user/src/com/google/gwt/cell/client/ImageCell.java
+++ b/user/src/com/google/gwt/cell/client/ImageCell.java
@@ -15,6 +15,11 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
 /**
  * <p>
  * A {@link AbstractCell} used to render an image. The String value is the url
@@ -29,10 +34,24 @@
  */
 public class ImageCell extends AbstractCell<String> {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<img src=\"{0}\"/>")
+    SafeHtml img(String url);
+  }
+
+  private static Template template;
+
+  public ImageCell() {
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
+  }
+
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(String value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
-      sb.append("<img src='").append(value).append("'/>");
+      // The template will sanitize the URI.
+      sb.append(template.img(value));
     }
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/ImageLoadingCell.java b/user/src/com/google/gwt/cell/client/ImageLoadingCell.java
index a230ec3..607213c 100644
--- a/user/src/com/google/gwt/cell/client/ImageLoadingCell.java
+++ b/user/src/com/google/gwt/cell/client/ImageLoadingCell.java
@@ -22,8 +22,12 @@
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.text.shared.AbstractRenderer;
-import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
 import com.google.gwt.user.client.ui.AbstractImagePrototype;
 
 /**
@@ -43,7 +47,7 @@
      *
      * @return the {@link Renderer} used when the image doesn't load
      */
-    Renderer<String> getErrorRenderer();
+    SafeHtmlRenderer<String> getErrorRenderer();
 
     /**
      * Get the renderer used to render the image. This renderer must render an
@@ -52,7 +56,7 @@
      *
      * @return the {@link Renderer} used to render the image
      */
-    Renderer<String> getImageRenderer();
+    SafeHtmlRenderer<String> getImageRenderer();
 
     /**
      * Get the renderer used to render a loading message. By default, an
@@ -60,22 +64,35 @@
      *
      * @return the {@link Renderer} used to render the loading html
      */
-    Renderer<String> getLoadingRenderer();
+    SafeHtmlRenderer<String> getLoadingRenderer();
   }
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div style='height:0px;width:0px;overflow:hidden;'>{0}</div>")
+    SafeHtml image(SafeHtml imageHtml);
+
+    @Template("<img src=\"{0}\"/>")
+    SafeHtml img(String url);
+
+    @Template("<div>{0}</div>")
+    SafeHtml loading(SafeHtml loadingHtml);
+  }
+
+  private static Template template;
+
   /**
    * The default {@link Renderers}.
    */
   public static class DefaultRenderers implements Renderers {
 
-    private static Renderer<String> IMAGE_RENDERER;
-    private static Renderer<String> LOADING_RENDERER;
+    private static SafeHtmlRenderer<String> IMAGE_RENDERER;
+    private static SafeHtmlRenderer<String> LOADING_RENDERER;
 
     public DefaultRenderers() {
       if (IMAGE_RENDERER == null) {
-        IMAGE_RENDERER = new AbstractRenderer<String>() {
-          public String render(String object) {
-            return "<img src='" + object + "'/>";
+        IMAGE_RENDERER = new AbstractSafeHtmlRenderer<String>() {
+          public SafeHtml render(String object) {
+            return template.img(object);
           }
         };
       }
@@ -83,24 +100,24 @@
         Resources resources = GWT.create(Resources.class);
         ImageResource res = resources.loading();
         final String loadingHtml = AbstractImagePrototype.create(res).getHTML();
-        LOADING_RENDERER = new AbstractRenderer<String>() {
-          public String render(String object) {
-            return loadingHtml;
+        LOADING_RENDERER = new AbstractSafeHtmlRenderer<String>() {
+          public SafeHtml render(String object) {
+            return SafeHtmlUtils.fromSafeConstant(loadingHtml);
           }
         };
       }
     }
 
-    public Renderer<String> getErrorRenderer() {
+    public SafeHtmlRenderer<String> getErrorRenderer() {
       // Show the broken image on error.
       return getImageRenderer();
     }
 
-    public Renderer<String> getImageRenderer() {
+    public SafeHtmlRenderer<String> getImageRenderer() {
       return IMAGE_RENDERER;
     }
 
-    public Renderer<String> getLoadingRenderer() {
+    public SafeHtmlRenderer<String> getLoadingRenderer() {
       return LOADING_RENDERER;
     }
   }
@@ -112,9 +129,9 @@
     ImageResource loading();
   }
 
-  private final Renderer<String> errorRenderer;
-  private final Renderer<String> imageRenderer;
-  private final Renderer<String> loadingRenderer;
+  private final SafeHtmlRenderer<String> errorRenderer;
+  private final SafeHtmlRenderer<String> imageRenderer;
+  private final SafeHtmlRenderer<String> loadingRenderer;
 
   /**
    * <p>
@@ -137,6 +154,9 @@
    */
   public ImageLoadingCell(Renderers renderers) {
     super("load", "error");
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.errorRenderer = renderers.getErrorRenderer();
     this.imageRenderer = renderers.getImageRenderer();
     this.loadingRenderer = renderers.getLoadingRenderer();
@@ -159,20 +179,18 @@
       imgWrapper.getStyle().setProperty("overflow", "auto");
     } else if ("error".equals(type) && eventOccurredOnImage(event, parent)) {
       // Replace the loading indicator with an error message.
-      parent.getFirstChildElement().setInnerHTML(errorRenderer.render(value));
+      parent.getFirstChildElement().setInnerHTML(
+          errorRenderer.render(value).asString());
     }
   }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(String value, Object key, SafeHtmlBuilder sb) {
     // We can't use ViewData because we don't know the caching policy of the
     // browser. The browser may fetch the image every time we render.
     if (value != null) {
-      sb.append("<div>");
-      sb.append(loadingRenderer.render(value));
-      sb.append("</div><div style='height:0px;width:0px;overflow:hidden;'>");
-      sb.append(imageRenderer.render(value));
-      sb.append("</div>");
+      sb.append(template.loading(loadingRenderer.render(value)));
+      sb.append(template.image(imageRenderer.render(value)));
     }
   }
 
diff --git a/user/src/com/google/gwt/cell/client/ImageResourceCell.java b/user/src/com/google/gwt/cell/client/ImageResourceCell.java
index 8922ce0..e5eec17 100644
--- a/user/src/com/google/gwt/cell/client/ImageResourceCell.java
+++ b/user/src/com/google/gwt/cell/client/ImageResourceCell.java
@@ -16,17 +16,29 @@
 package com.google.gwt.cell.client;
 
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.ui.AbstractImagePrototype;
 
 /**
  * A {@link AbstractCell} used to render an {@link ImageResource}.
+ *
+ * <p>
+ * This class assumes that the URL returned from ImageResource is safe from
+ * script attacks. If you do not generate the ImageResource from a
+ * {@link com.google.gwt.resources.client.ClientBundle ClientBundle}, you should
+ * use {@link com.google.gwt.safehtml.shared.UriUtils UriUtils} to sanitize the
+ * URL before returning it from {@link ImageResource#getURL()}.
  */
 public class ImageResourceCell extends AbstractCell<ImageResource> {
 
   @Override
-  public void render(ImageResource value, Object key, StringBuilder sb) {
+  public void render(ImageResource value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
-      sb.append(AbstractImagePrototype.create(value).getHTML());
+      SafeHtml html = SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(
+          value).getHTML());
+      sb.append(html);
     }
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/NumberCell.java b/user/src/com/google/gwt/cell/client/NumberCell.java
index 27fab7e..14b301b 100644
--- a/user/src/com/google/gwt/cell/client/NumberCell.java
+++ b/user/src/com/google/gwt/cell/client/NumberCell.java
@@ -16,6 +16,9 @@
 package com.google.gwt.cell.client;
 
 import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 /**
  * A {@link Cell} used to render formatted numbers.
@@ -28,25 +31,62 @@
   private final NumberFormat format;
 
   /**
-   * Construct a new {@link NumberCell} using decimal format.
+   * The {@link SafeHtmlRenderer} used to render the formatted number as HTML.
+   */
+  private final SafeHtmlRenderer<String> renderer;
+
+  /**
+   * Construct a new {@link NumberCell} using decimal format and a
+   * {@link SimpleSafeHtmlRenderer}.
    */
   public NumberCell() {
-    this(NumberFormat.getDecimalFormat());
+    this(NumberFormat.getDecimalFormat(), SimpleSafeHtmlRenderer.getInstance());
   }
 
   /**
-   * Construct a new {@link NumberCell}.
+   * Construct a new {@link NumberCell} using the given {@link NumberFormat} and
+   * a {@link SimpleSafeHtmlRenderer}.
    *
    * @param format the {@link NumberFormat} used to render the number
    */
   public NumberCell(NumberFormat format) {
+    this(format, SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  /**
+   * Construct a new {@link NumberCell} using decimal format and the given
+   * {@link SafeHtmlRenderer}.
+   *
+   * @param renderer the {@link SafeHtmlRenderer} used to render the formatted
+   *          number as HTML
+   */
+  public NumberCell(SafeHtmlRenderer<String> renderer) {
+    this(NumberFormat.getDecimalFormat(), renderer);
+  }
+
+  /**
+   * Construct a new {@link NumberCell} using the given {@link NumberFormat} and
+   * a {@link SafeHtmlRenderer}.
+   *
+   * @param format the {@link NumberFormat} used to render the number
+   * @param renderer the {@link SafeHtmlRenderer} used to render the formatted
+   *          number as HTML
+   */
+  public NumberCell(NumberFormat format, SafeHtmlRenderer<String> renderer) {
+    if (format == null) {
+      throw new IllegalArgumentException("format == null");
+    }
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
     this.format = format;
+    this.renderer = renderer;
   }
 
   @Override
-  public void render(Number value, Object key, StringBuilder sb) {
+  public void render(Number value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
-      sb.append(format.format(value));
+      sb.append(renderer.render(format.format(value)));
     }
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/SafeHtmlCell.java b/user/src/com/google/gwt/cell/client/SafeHtmlCell.java
new file mode 100644
index 0000000..b8c06d4
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/SafeHtmlCell.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 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.cell.client;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
+/**
+ * A {@link Cell} used to render safe HTML markup.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ */
+public class SafeHtmlCell extends AbstractCell<SafeHtml> {
+
+  @Override
+  public void render(SafeHtml value, Object key, SafeHtmlBuilder sb) {
+    if (value != null) {
+      sb.append(value);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/cell/client/SelectionCell.java b/user/src/com/google/gwt/cell/client/SelectionCell.java
index 982e0f2..6612f5c 100644
--- a/user/src/com/google/gwt/cell/client/SelectionCell.java
+++ b/user/src/com/google/gwt/cell/client/SelectionCell.java
@@ -15,9 +15,13 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -32,8 +36,17 @@
  */
 public class SelectionCell extends AbstractEditableCell<String, String> {
 
-  private HashMap<String, Integer> indexForOption = new HashMap<
-      String, Integer>();
+  interface Template extends SafeHtmlTemplates {
+    @Template("<option value=\"{0}\">{0}</option>")
+    SafeHtml deselected(String option);
+    @Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
+    SafeHtml selected(String option);
+  }
+
+  private static Template template;
+
+  private HashMap<String, Integer> indexForOption = new HashMap<String, Integer>();
+
   private final List<String> options;
 
   /**
@@ -43,6 +56,9 @@
    */
   public SelectionCell(List<String> options) {
     super("change");
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.options = new ArrayList<String>(options);
     int index = 0;
     for (String option : options) {
@@ -65,7 +81,7 @@
   }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(String value, Object key, SafeHtmlBuilder sb) {
     // Get the view data.
     String viewData = getViewData(key);
     if (viewData != null && viewData.equals(value)) {
@@ -74,18 +90,16 @@
     }
 
     int selectedIndex = getSelectedIndex(viewData == null ? value : viewData);
-    sb.append("<select>");
+    sb.appendHtmlConstant("<select>");
     int index = 0;
     for (String option : options) {
-      sb.append("<option value='").append(option).append("'");
       if (index++ == selectedIndex) {
-        sb.append(" selected='selected'");
+        sb.append(template.selected(option));
+      } else {
+        sb.append(template.deselected(option));
       }
-      sb.append(">");
-      sb.append(option);
-      sb.append("</option>");
     }
-    sb.append("</select>");
+    sb.appendHtmlConstant("</select>");
   }
 
   private int getSelectedIndex(String value) {
diff --git a/user/src/com/google/gwt/cell/client/TextCell.java b/user/src/com/google/gwt/cell/client/TextCell.java
index 57a4eb5..4701e40 100644
--- a/user/src/com/google/gwt/cell/client/TextCell.java
+++ b/user/src/com/google/gwt/cell/client/TextCell.java
@@ -15,21 +15,38 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
+
 /**
  * A {@link Cell} used to render text.
  *
  * <p>
  * Note: This class is new and its interface subject to change.
  * </p>
- *
- * Important TODO: This cell treats its value as HTML. We need to properly treat
- * its value as a raw string, so that it's safe to use with unsanitized data.
- * See the related comment in {@link EditTextCell}.
  */
-public class TextCell extends AbstractCell<String> {
+public class TextCell extends AbstractSafeHtmlCell<String> {
+
+  /**
+   * Constructs a TextCell that uses a {@link SimpleSafeHtmlRenderer} to render
+   * its text.
+   */
+  public TextCell() {
+    super(SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  /**
+   * Constructs a TextCell that uses the provided {@link SafeHtmlRenderer} to
+   * render its text.
+   */
+  public TextCell(SafeHtmlRenderer<String> renderer) {
+    super(renderer);
+  }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(SafeHtml value, Object key, SafeHtmlBuilder sb) {
     if (value != null) {
       sb.append(value);
     }
diff --git a/user/src/com/google/gwt/cell/client/TextInputCell.java b/user/src/com/google/gwt/cell/client/TextInputCell.java
index 811369e..e117f78 100644
--- a/user/src/com/google/gwt/cell/client/TextInputCell.java
+++ b/user/src/com/google/gwt/cell/client/TextInputCell.java
@@ -15,9 +15,15 @@
  */
 package com.google.gwt.cell.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.text.shared.SafeHtmlRenderer;
+import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
 
 /**
  * A {@link AbstractCell} used to render a text input.
@@ -28,8 +34,37 @@
  */
 public class TextInputCell extends AbstractEditableCell<String, String> {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<input type=\"text\" value=\"{0}\"></input>")
+    SafeHtml input(String value);
+  }
+
+  private static Template template;
+
+  private final SafeHtmlRenderer<String> renderer;
+
+  /**
+   * Constructs a TextInputCell that renders its text without HTML markup.
+   */
   public TextInputCell() {
+    this(SimpleSafeHtmlRenderer.getInstance());
+  }
+
+  /**
+   * Constructs a TextInputCell that renders its text using the
+   * given {@link SafeHtmlRenderer}.
+   *
+   * @param renderer a non-null SafeHtmlRenderer
+   */
+  public TextInputCell(SafeHtmlRenderer<String> renderer) {
     super("change", "keyup");
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
+    if (renderer == null) {
+      throw new IllegalArgumentException("renderer == null");
+    }
+    this.renderer = renderer;
   }
 
   @Override
@@ -51,7 +86,7 @@
   }
 
   @Override
-  public void render(String value, Object key, StringBuilder sb) {
+  public void render(String value, Object key, SafeHtmlBuilder sb) {
     // Get the view data.
     String viewData = getViewData(key);
     if (viewData != null && viewData.equals(value)) {
@@ -61,9 +96,11 @@
 
     String s = (viewData != null) ? viewData : (value != null ? value : null);
     if (s != null) {
-      sb.append("<input type='text' value='").append(s).append("'></input>");
+      SafeHtml html = renderer.render(s);
+      // Note: template will not treat SafeHtml specially
+      sb.append(template.input(html.asString()));
     } else {
-      sb.append("<input type='text'></input>");
+      sb.appendHtmlConstant("<input type=\"text\"></input>");
     }
   }
 }
diff --git a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
index efaea94..56a2ad3 100644
--- a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
+++ b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -65,7 +65,7 @@
    * Fully-qualified class name of the {@link SafeHtmlUtils} class.
    */
   private static final String ESCAPE_UTILS_FQCN = SafeHtmlUtils.class.getName();
-  
+
   /**
    * Fully-qualified class name of the {@link UriUtils} class.
    */
@@ -81,7 +81,7 @@
   private static final Set<String> URI_VALUED_ATTRIBUTES;
 
   static {
-    URI_VALUED_ATTRIBUTES = new TreeSet();
+    URI_VALUED_ATTRIBUTES = new TreeSet<String>();
     URI_VALUED_ATTRIBUTES.addAll(Arrays.asList(
           "action",
           "archive",
@@ -249,7 +249,7 @@
     outdent();
     println("return new " + BLESSED_STRING_FQCN + "(sb.toString());");
   }
-  
+
   /**
    * Emits an expression corresponding to a template parameter.
    *
diff --git a/user/src/com/google/gwt/safehtml/shared/OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml.java b/user/src/com/google/gwt/safehtml/shared/OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml.java
index 301a3c2..4d7a03d 100644
--- a/user/src/com/google/gwt/safehtml/shared/OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml.java
+++ b/user/src/com/google/gwt/safehtml/shared/OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 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
@@ -17,10 +17,10 @@
 
 /**
  * A string wrapped as an object of type {@link SafeHtml}.
- * 
+ *
  * <p>
- * This class is intended only for use in generated code where the code 
- * generator guarantees that instances of this type will adhere to the 
+ * This class is intended only for use in generated code where the code
+ * generator guarantees that instances of this type will adhere to the
  * {@link SafeHtml} contract (hence the purposely unwieldy class name).
  */
 public class OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml
@@ -40,10 +40,11 @@
   public String asString() {
     return html;
   }
-  
+
   /**
    * Compares this string to the specified object.
    */
+  @Override
   public boolean equals(Object obj) {
     if (!(obj instanceof SafeHtml)) {
       return false;
@@ -54,6 +55,7 @@
   /**
    * Returns a hash code for this string.
    */
+  @Override
   public int hashCode() {
     return html.hashCode();
   }
diff --git a/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java b/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
index 2d7762a..d33714d 100644
--- a/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
+++ b/user/src/com/google/gwt/safehtml/shared/SafeHtmlString.java
@@ -60,6 +60,7 @@
   /**
    * Compares this string to the specified object.
    */
+  @Override
   public boolean equals(Object obj) {
     if (!(obj instanceof SafeHtml)) {
       return false;
@@ -70,6 +71,7 @@
   /**
    * Returns a hash code for this string.
    */
+  @Override
   public int hashCode() {
     return html.hashCode();
   }
diff --git a/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java b/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java
index f359a86..51a0e51 100644
--- a/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java
+++ b/user/src/com/google/gwt/safehtml/shared/SafeHtmlUtils.java
@@ -24,9 +24,9 @@
 
   private static final String HTML_ENTITY_REGEX =
       "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+";
-  
+
   public static final SafeHtml EMPTY_SAFE_HTML = new SafeHtmlString("");
-  
+
   private static final RegExp AMP_RE = RegExp.compile("&", "g");
   private static final RegExp GT_RE = RegExp.compile(">", "g");
   private static final RegExp LT_RE = RegExp.compile("<", "g");
@@ -65,6 +65,15 @@
   }
 
   /**
+   * Returns a SafeHtml 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 SafeHtml contract.
+   */
+  public static SafeHtml fromTrustedString(String s) {
+    return new SafeHtmlString(s);
+  }
+
+  /**
    * HTML-escapes a string.
    *
    *  Note: The following variants of this function were profiled on FF36,
@@ -73,7 +82,7 @@
    * #2) for each case, check indexOf, then use s.replaceAll()
    * #3) check if any metachar is present using a regex, then use #1
    * #4) for each case, use s.replace(regex, string)
-   * 
+   *
    * #1 was found to be the fastest, and is used below.
    *
    * @param s the string to be escaped
diff --git a/user/src/com/google/gwt/text/shared/AbstractSafeHtmlRenderer.java b/user/src/com/google/gwt/text/shared/AbstractSafeHtmlRenderer.java
new file mode 100644
index 0000000..521fcb3
--- /dev/null
+++ b/user/src/com/google/gwt/text/shared/AbstractSafeHtmlRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 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.text.shared;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * <span style="color:red">Experimental API: This class is still under rapid
+ * development, and is very likely to be deleted. Use it at your own risk.
+ * </span>
+ * <p>
+ * Abstract implementation of a safe HTML renderer to make implementation of
+ * rendering simpler.
+ *
+ * @param <T> the type to render
+ */
+public abstract class AbstractSafeHtmlRenderer<T> implements
+    SafeHtmlRenderer<T> {
+
+  private static final SafeHtml EMPTY_STRING = SafeHtmlUtils.fromSafeConstant("");
+
+  public void render(T object, SafeHtmlBuilder appendable) {
+    appendable.append(render(object));
+  }
+
+  protected SafeHtml toSafeHtml(Object obj) {
+    return obj == null ? EMPTY_STRING
+        : SafeHtmlUtils.fromString(String.valueOf(obj));
+  }
+}
diff --git a/user/src/com/google/gwt/text/shared/SafeHtmlRenderer.java b/user/src/com/google/gwt/text/shared/SafeHtmlRenderer.java
new file mode 100644
index 0000000..f244e4f
--- /dev/null
+++ b/user/src/com/google/gwt/text/shared/SafeHtmlRenderer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 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.text.shared;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
+/**
+ * <span style="color:red">Experimental API: This class is still under rapid
+ * development, and is very likely to be deleted. Use it at your own risk.
+ * </span>
+ * <p>
+ * An object that can render other objects of a particular type into safe HTML
+ * form. Allows decoupling that is useful for a dependency-injection
+ * architecture.
+ *
+ * @param <T> the type to render
+ */
+public interface SafeHtmlRenderer<T> {
+
+  /**
+   * Renders {@code object} as safe HTML.
+   */
+  SafeHtml render(T object);
+
+  /**
+   * Renders {@code object} as safe HTML, appended directly to {@code builder}.
+   */
+  void render(T object, SafeHtmlBuilder builder);
+}
diff --git a/user/src/com/google/gwt/text/shared/SimpleSafeHtmlRenderer.java b/user/src/com/google/gwt/text/shared/SimpleSafeHtmlRenderer.java
new file mode 100644
index 0000000..343e23b
--- /dev/null
+++ b/user/src/com/google/gwt/text/shared/SimpleSafeHtmlRenderer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 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.text.shared;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * A simple {@link SafeHtmlRenderer} implementation that calls
+ * {@link SafeHtmlUtils#fromString(String)) to escape its arguments.
+ */
+public class SimpleSafeHtmlRenderer implements SafeHtmlRenderer<String> {
+
+  private static SimpleSafeHtmlRenderer instance;
+
+  public static SimpleSafeHtmlRenderer getInstance() {
+    if (instance == null) {
+      instance = new SimpleSafeHtmlRenderer();
+    }
+    return instance;
+  }
+
+  private SimpleSafeHtmlRenderer() {
+  }
+
+  public SafeHtml render(String object) {
+    return SafeHtmlUtils.fromString(object);
+  }
+
+  public void render(String object, SafeHtmlBuilder appendable) {
+    appendable.append(SafeHtmlUtils.fromString(object));
+  }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
index 13ca6d0..ff8cbf6 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
@@ -22,6 +22,8 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent.Type;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.client.DOM;
@@ -80,17 +82,17 @@
       hasData.onUpdateSelection();
     }
 
-    public void render(StringBuilder sb, List<T> values, int start,
+    public void render(SafeHtmlBuilder sb, List<T> values, int start,
         SelectionModel<? super T> selectionModel) {
       hasData.renderRowValues(sb, values, start, selectionModel);
     }
 
-    public void replaceAllChildren(List<T> values, String html) {
+    public void replaceAllChildren(List<T> values, SafeHtml html) {
       hasData.replaceAllChildren(values, html);
       fireValueChangeEvent();
     }
 
-    public void replaceChildren(List<T> values, int start, String html) {
+    public void replaceChildren(List<T> values, int start, SafeHtml html) {
       hasData.replaceChildren(values, start, html);
       fireValueChangeEvent();
     }
@@ -140,12 +142,12 @@
    * @return the parent element
    */
   static Element convertToElements(
-      Widget widget, com.google.gwt.user.client.Element tmpElem, String html) {
+      Widget widget, com.google.gwt.user.client.Element tmpElem, SafeHtml html) {
     // Attach an event listener so we can catch synchronous load events from
     // cached images.
     DOM.setEventListener(tmpElem, widget);
 
-    tmpElem.setInnerHTML(html);
+    tmpElem.setInnerHTML(html.asString());
 
     // Detach the event listener.
     DOM.setEventListener(tmpElem, null);
@@ -161,7 +163,7 @@
    * @param html the html to set
    */
   static void replaceAllChildren(
-      Widget widget, Element childContainer, String html) {
+      Widget widget, Element childContainer, SafeHtml html) {
     // If the widget is not attached, attach an event listener so we can catch
     // synchronous load events from cached images.
     if (!widget.isAttached()) {
@@ -169,7 +171,7 @@
     }
 
     // Render the HTML.
-    childContainer.setInnerHTML(CellBasedWidgetImpl.get().processHtml(html));
+    childContainer.setInnerHTML(CellBasedWidgetImpl.get().processHtml(html).asString());
 
     // Detach the event listener.
     if (!widget.isAttached()) {
@@ -190,7 +192,7 @@
    * @param html the HTML to convert
    */
   static void replaceChildren(Widget widget, Element childContainer,
-      Element newChildren, int start, String html) {
+      Element newChildren, int start, SafeHtml html) {
     // Get the first element to be replaced.
     int childCount = childContainer.getChildCount();
     Element toReplace = null;
@@ -384,14 +386,14 @@
   }
 
   /**
-   * Render all row values into the specified {@link StringBuilder}.
+   * Render all row values into the specified {@link SafeHtmlBuilder}.
    *
-   * @param sb the {@link StringBuilder} to render into
+   * @param sb the {@link SafeHtmlBuilder} to render into
    * @param values the row values
    * @param start the start index of the values
    * @param selectionModel the {@link SelectionModel}
    */
-  protected abstract void renderRowValues(StringBuilder sb, List<T> values,
+  protected abstract void renderRowValues(SafeHtmlBuilder sb, List<T> values,
       int start, SelectionModel<? super T> selectionModel);
 
   /**
@@ -415,7 +417,7 @@
    * @return the parent element
    */
   // TODO(jlabanca): Which of the following methods should we expose.
-  Element convertToElements(String html) {
+  Element convertToElements(SafeHtml html) {
     return convertToElements(this, getTmpElem(), html);
   }
 
@@ -479,7 +481,7 @@
    * @param values the values of the new children
    * @param html the html to render in the child
    */
-  void replaceAllChildren(List<T> values, String html) {
+  void replaceAllChildren(List<T> values, SafeHtml html) {
     replaceAllChildren(this, getChildContainer(), html);
   }
 
@@ -493,7 +495,7 @@
    * @param start the start index to be replaced
    * @param html the HTML to convert
    */
-  void replaceChildren(List<T> values, int start, String html) {
+  void replaceChildren(List<T> values, int start, SafeHtml html) {
     Element newChildren = convertToElements(html);
     replaceChildren(this, getChildContainer(), newChildren, start, html);
   }
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
index 6d15349..3de6640 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.cellview.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -61,7 +62,7 @@
    * @param html the html string to process
    * @return the processed html string
    */
-  public String processHtml(String html) {
+  public SafeHtml processHtml(SafeHtml html) {
     return html;
   }
 
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
index 5af54b0..0ef49c7 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
@@ -23,6 +23,8 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.InputElement;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.EventListener;
@@ -279,15 +281,20 @@
   }
 
   @Override
-  public String processHtml(String html) {
+  public SafeHtml processHtml(SafeHtml html) {
     // If the widget is listening for load events, we modify the HTML to add the
     // load/error listeners.
     if (loadEventsInitialized && html != null) {
       String moduleName = GWT.getModuleName();
       String listener = "__gwt_CellBasedWidgetImplLoadListeners[\"" + moduleName
           + "\"]();";
-      html = html.replaceAll("(<img)([\\s/>])",
+
+      String htmlString = html.asString();
+      htmlString = htmlString.replaceAll("(<img)([\\s/>])",
           "<img onload='" + listener + "' onerror='" + listener + "'$2");
+
+      // We assert that the resulting string is safe
+      html = SafeHtmlUtils.fromTrustedString(htmlString);
     }
     return html;
   }
@@ -346,7 +353,7 @@
    */
   private native void initLoadEvents(String moduleName) /*-{
     // Initialize an array of listeners. Each module gets its own entry in the
-    // array to prevent conflicts on pages with multiple modules. 
+    // array to prevent conflicts on pages with multiple modules.
     if (!$wnd.__gwt_CellBasedWidgetImplLoadListeners) {
       $wnd.__gwt_CellBasedWidgetImplLoadListeners = new Array();
     }
@@ -363,7 +370,9 @@
    * @param elem the element that will receive the events
    */
   private native void sinkFocusEvents(Element elem) /*-{
-    elem.attachEvent('onfocusin', @com.google.gwt.user.cellview.client.CellBasedWidgetImplTrident::dispatchFocusEvent);
-    elem.attachEvent('onfocusout', @com.google.gwt.user.cellview.client.CellBasedWidgetImplTrident::dispatchFocusEvent);
+    elem.attachEvent('onfocusin',
+        @com.google.gwt.user.cellview.client.CellBasedWidgetImplTrident::dispatchFocusEvent);
+    elem.attachEvent('onfocusout',
+        @com.google.gwt.user.cellview.client.CellBasedWidgetImplTrident::dispatchFocusEvent);
   }-*/;
 }
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 e27f251..6365d2d 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
@@ -37,6 +37,10 @@
 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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.FlowPanel;
@@ -70,6 +74,14 @@
 public class CellBrowser extends AbstractCellTree
     implements ProvidesResize, RequiresResize, HasAnimation {
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div style=\"position:relative;padding-right:{0}px;\" class="
+        + "\"{1}\">{2}<div>{3}</div></div>")
+    SafeHtml div(int imageWidth, String classes, SafeHtml image, SafeHtml cellContents);
+  }
+
+  private static Template template;
+
   /**
    * A ClientBundle that provides images for this widget.
    */
@@ -238,32 +250,34 @@
       }
     }
 
-    public void render(C value, Object viewData, StringBuilder sb) {
+    public void render(C value, Object viewData, SafeHtmlBuilder sb) {
       boolean isOpen = (openKey == null) ? false : openKey.equals(
           getValueKey(value));
       boolean isSelected = (selectionModel == null)
           ? false : selectionModel.isSelected(value);
-      sb.append("<div style='position:relative;padding-right:");
-      sb.append(imageWidth);
-      sb.append("px;'");
-      sb.append(" class='").append(style.item());
+
+      StringBuilder classesBuilder = new StringBuilder();
+      classesBuilder.append(style.item());
       if (isOpen) {
-        sb.append(" ").append(style.openItem());
+        classesBuilder.append(" ").append(style.openItem());
       }
       if (isSelected) {
-        sb.append(" ").append(style.selectedItem());
+        classesBuilder.append(" ").append(style.selectedItem());
       }
-      sb.append("'>");
+      String classes = classesBuilder.toString();
+
+      SafeHtml image;
       if (isOpen) {
-        sb.append(openImageHtml);
+        image = openImageHtml;
       } else if (isLeaf(value)) {
-        sb.append(LEAF_IMAGE);
+        image = LEAF_IMAGE;
       } else {
-        sb.append(closedImageHtml);
+        image = closedImageHtml;
       }
-      sb.append("<div>");
-      cell.render(value, viewData, sb);
-      sb.append("</div></div>");
+      SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+      cell.render(value, viewData, cellBuilder);
+      sb.append(template.div(imageWidth, classes, image,
+          cellBuilder.toSafeHtml()));
     }
 
     public void setValue(Element parent, C value, Object viewData) {
@@ -486,8 +500,8 @@
   /**
    * The element used in place of an image when a node has no children.
    */
-  private static final String LEAF_IMAGE =
-      "<div style='position:absolute;display:none;'></div>";
+  private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant(
+      "<div style='position:absolute;display:none;'></div>");
 
   private static Resources DEFAULT_RESOURCES;
 
@@ -526,7 +540,7 @@
   /**
    * The HTML used to generate the closed image.
    */
-  private final String closedImageHtml;
+  private final SafeHtml closedImageHtml;
 
   /**
    * A boolean indicating whether or not animations are enabled.
@@ -546,7 +560,7 @@
   /**
    * The HTML used to generate the open image.
    */
-  private final String openImageHtml;
+  private final SafeHtml openImageHtml;
 
   /**
    * The styles used by this widget.
@@ -586,6 +600,9 @@
   public <T> CellBrowser(
       TreeViewModel viewModel, T rootValue, Resources resources) {
     super(viewModel);
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.style = resources.cellBrowserStyle();
     this.style.ensureInjected();
     initWidget(new SplitLayoutPanel());
@@ -791,24 +808,23 @@
    * @param res the {@link ImageResource} to render as HTML
    * @return the rendered HTML
    */
-  private String getImageHtml(ImageResource res) {
-    // Add the position and dimensions.
-    StringBuilder sb = new StringBuilder();
-    sb.append("<div style=\"position:absolute;top:0px;height:100%;");
-    if (LocaleInfo.getCurrentLocale().isRTL()) {
-      sb.append("left:0px;");
-    } else {
-      sb.append("right:0px;");
-    }
-    sb.append("width:").append(res.getWidth()).append("px;");
+  private SafeHtml getImageHtml(ImageResource res) {
+    // Right-justify image if LTR, left-justify if RTL
 
-    // Add the background, vertically centered.
-    sb.append("background:url('").append(res.getURL()).append("') ");
-    sb.append("no-repeat scroll center center transparent;");
+    // Note: templates can't handle the URL currently
 
-    // Close the div and return.
-    sb.append("\"></div>");
-    return sb.toString();
+    // Note: closing the tag with /> causes tests to fail
+    // in dev mode with HTMLUnit -- the close tag is lost
+    // when calling setInnerHTML on an Element.
+    // TODO(rice) find and fix the root cause of this failure
+
+    // CHECKSTYLE_OFF
+    return SafeHtmlUtils.fromTrustedString("<div style=\"position:absolute;"
+        + (LocaleInfo.getCurrentLocale().isRTL() ? "left" : "right")
+        + ":0px;top:0px;height:100%;width:" + res.getWidth()
+        + "px;background:url('" + res.getURL()
+        + "') no-repeat scroll center center transparent;\"></div>");
+    // CHECKSTYLE_ON
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.java b/user/src/com/google/gwt/user/cellview/client/CellList.java
index 2509f3e..015dab5 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellList.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellList.java
@@ -27,6 +27,10 @@
 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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.view.client.ProvidesKey;
@@ -82,6 +86,11 @@
     String selectedItem();
   }
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\">{2}</div>")
+    SafeHtml div(int idx, String classes, SafeHtml cellContents);
+  }
+
   /**
    * The default page size.
    */
@@ -89,19 +98,21 @@
 
   private static Resources DEFAULT_RESOURCES;
 
+  private static final Template TEMPLATE = GWT.create(Template.class);
   private static Resources getDefaultResources() {
     if (DEFAULT_RESOURCES == null) {
       DEFAULT_RESOURCES = GWT.create(Resources.class);
     }
     return DEFAULT_RESOURCES;
   }
-
   private final Cell<T> cell;
   private final Element childContainer;
-  private String emptyListMessage = "";
+
+  private SafeHtml emptyListMessage = SafeHtmlUtils.fromSafeConstant("");
   private final Element emptyMessageElem;
 
   private final Style style;
+
   private ValueUpdater<T> valueUpdater;
 
   /**
@@ -150,7 +161,7 @@
    *
    * @return the empty message
    */
-  public String getEmptyListMessage() {
+  public SafeHtml getEmptyListMessage() {
     return emptyListMessage;
   }
 
@@ -209,9 +220,9 @@
    *
    * @param html the message to display when there are no results
    */
-  public void setEmptyListMessage(String html) {
+  public void setEmptyListMessage(SafeHtml html) {
     this.emptyListMessage = html;
-    emptyMessageElem.setInnerHTML(html);
+    emptyMessageElem.setInnerHTML(html.asString());
   }
 
   /**
@@ -224,7 +235,7 @@
   }
 
   @Override
-  protected void renderRowValues(StringBuilder sb, List<T> values, int start,
+  protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
       SelectionModel<? super T> selectionModel) {
     int length = values.size();
     int end = start + length;
@@ -233,15 +244,16 @@
       boolean isSelected = selectionModel == null
           ? false : selectionModel.isSelected(value);
       // TODO(jlabanca): Factor out __idx because rows can move.
-      sb.append("<div onclick='' __idx='").append(i).append("'");
-      sb.append(" class='");
-      sb.append(i % 2 == 0 ? style.evenItem() : style.oddItem());
+      StringBuilder classesBuilder = new StringBuilder();
+      classesBuilder.append(i % 2 == 0 ? style.evenItem() : style.oddItem());
       if (isSelected) {
-        sb.append(" ").append(style.selectedItem());
+        classesBuilder.append(" ").append(style.selectedItem());
       }
-      sb.append("'>");
-      cell.render(value, null, sb);
-      sb.append("</div>");
+
+      SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+      cell.render(value, null, cellBuilder);
+
+      sb.append(TEMPLATE.div(i, classesBuilder.toString(), cellBuilder.toSafeHtml()));
     }
   }
 
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.java b/user/src/com/google/gwt/user/cellview/client/CellTable.java
index fa65d4a..f5663ea 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -33,6 +33,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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
@@ -197,6 +200,29 @@
     String selectedRow();
   }
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div class=\"{0}\"/>")
+    SafeHtml loading(String loading);
+
+    @Template("<table><tbody>{0}</tbody></table>")
+    SafeHtml tbody(SafeHtml rowHtml);
+
+    @Template("<td class=\"{0}\"><div>{1}</div></td>")
+    SafeHtml td(String classes, SafeHtml contents);
+
+    @Template("<table><tfoot>{0}</tfoot></table>")
+    SafeHtml tfoot(SafeHtml rowHtml);
+
+    @Template("<th class=\"{0}\">{1}</th>")
+    SafeHtml th(String classes, SafeHtml contents);
+
+    @Template("<table><thead>{0}</thead></table>")
+    SafeHtml thead(SafeHtml rowHtml);
+
+    @Template("<tr onclick=\"\" class=\"{0}\">{1}</tr>")
+    SafeHtml tr(String classes, SafeHtml contents);
+  }
+
   /**
    * Implementation of {@link CellTable}.
    */
@@ -214,7 +240,7 @@
      * @return the section element
      */
     protected TableSectionElement convertToSectionElement(
-        CellTable<?> table, String sectionTag, String rowHtml) {
+        CellTable<?> table, String sectionTag, SafeHtml rowHtml) {
       // Attach an event listener so we can catch synchronous load events from
       // cached images.
       DOM.setEventListener(tmpElem, table);
@@ -223,9 +249,16 @@
       // IE doesn't support innerHtml on a TableSection or Table element, so we
       // generate the entire table.
       sectionTag = sectionTag.toLowerCase();
-      String innerHtml = "<table><" + sectionTag + ">" + rowHtml + "</"
-          + sectionTag + "></table>";
-      tmpElem.setInnerHTML(innerHtml);
+      if ("tbody".equals(sectionTag)) {
+        tmpElem.setInnerHTML(template.tbody(rowHtml).asString());
+      } else if ("thead".equals(sectionTag)) {
+        tmpElem.setInnerHTML(template.thead(rowHtml).asString());
+      } else if ("tfoot".equals(sectionTag)) {
+        tmpElem.setInnerHTML(template.tfoot(rowHtml).asString());
+      } else {
+        throw new IllegalArgumentException(
+            "Invalid table section tag: " + sectionTag);
+      }
       TableElement tableElem = tmpElem.getFirstChildElement().cast();
 
       // Detach the event listener.
@@ -238,9 +271,10 @@
         return tableElem.getTHead();
       } else if ("tfoot".equals(sectionTag)) {
         return tableElem.getTFoot();
+      } else {
+        throw new IllegalArgumentException(
+            "Invalid table section tag: " + sectionTag);
       }
-      throw new IllegalArgumentException(
-          "Invalid table section tag: " + sectionTag);
     }
 
     /**
@@ -251,7 +285,7 @@
      * @param html the html to render
      */
     protected void replaceAllRows(
-        CellTable<?> table, TableSectionElement section, String html) {
+        CellTable<?> table, TableSectionElement section, SafeHtml html) {
       // If the widget is not attached, attach an event listener so we can catch
       // synchronous load events from cached images.
       if (!table.isAttached()) {
@@ -259,7 +293,7 @@
       }
 
       // Render the html.
-      section.setInnerHTML(html);
+      section.setInnerHTML(html.asString());
 
       // Detach the event listener.
       if (!table.isAttached()) {
@@ -281,7 +315,7 @@
      */
     @Override
     protected void replaceAllRows(
-        CellTable<?> table, TableSectionElement section, String html) {
+        CellTable<?> table, TableSectionElement section, SafeHtml html) {
       // Remove all children.
       Element child = section.getFirstChildElement();
       while (child != null) {
@@ -314,6 +348,8 @@
    */
   private static Impl TABLE_IMPL;
 
+  private static Template template;
+
   private static Resources getDefaultResources() {
     if (DEFAULT_RESOURCES == null) {
       DEFAULT_RESOURCES = GWT.create(CleanResources.class);
@@ -322,6 +358,7 @@
   }
 
   private boolean cellIsEditing;
+
   private final TableColElement colgroup;
 
   private final List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
@@ -364,17 +401,17 @@
           redraw();
         }
       };
-
   /**
    * Indicates whether or not a redraw is scheduled.
    */
   private boolean redrawScheduled;
-
   private final Style style;
   private final TableElement table;
   private final TableSectionElement tbody;
   private final TableSectionElement tbodyLoading;
+
   private final TableSectionElement tfoot;
+
   private final TableSectionElement thead;
 
   /**
@@ -405,6 +442,9 @@
     if (TABLE_IMPL == null) {
       TABLE_IMPL = GWT.create(Impl.class);
     }
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.style = resources.cellTableStyle();
     this.style.ensureInjected();
 
@@ -425,7 +465,7 @@
       tbodyLoading.appendChild(tr);
       tr.appendChild(td);
       td.setAlign("center");
-      td.setInnerHTML("<div class='" + style.loading() + "'></div>");
+      td.setInnerHTML(template.loading(style.loading()).asString());
       setLoadingIconVisible(false);
     }
 
@@ -490,6 +530,13 @@
   public void addColumn(Column<T, ?> col, String headerString) {
     addColumn(col, new TextHeader(headerString), null);
   }
+  
+  /**
+   * Adds a column to the table with an associated SafeHtml header.
+   */
+  public void addColumn(Column<T, ?> col, SafeHtml headerHtml) {
+    addColumn(col, new SafeHtmlHeader(headerHtml), null);
+  }
 
   /**
    * Adds a column to the table with an associated String header and footer.
@@ -498,6 +545,14 @@
       Column<T, ?> col, String headerString, String footerString) {
     addColumn(col, new TextHeader(headerString), new TextHeader(footerString));
   }
+  
+  /**
+   * Adds a column to the table with an associated SafeHtml header and footer.
+   */
+  public void addColumn(
+      Column<T, ?> col, SafeHtml headerHtml, SafeHtml footerHtml) {
+    addColumn(col, new SafeHtmlHeader(headerHtml), new SafeHtmlHeader(footerHtml));
+  }
 
   /**
    * Add a style name to the {@link TableColElement} at the specified index,
@@ -677,7 +732,7 @@
   }
 
   @Override
-  protected void renderRowValues(StringBuilder sb, List<T> values, int start,
+  protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
       SelectionModel<? super T> selectionModel) {
     createHeadersAndFooters();
 
@@ -695,35 +750,38 @@
       T value = values.get(i - start);
       boolean isSelected = (selectionModel == null || value == null)
           ? false : selectionModel.isSelected(value);
-      sb.append("<tr onclick='' class='");
-      sb.append(i % 2 == 0 ? evenRowStyle : oddRowStyle);
+      String trClasses = i % 2 == 0 ? evenRowStyle : oddRowStyle;
       if (isSelected) {
-        sb.append(selectedRowStyle);
+        trClasses += selectedRowStyle;
       }
-      sb.append("'>");
+
+      SafeHtmlBuilder trBuilder = new SafeHtmlBuilder();
       int curColumn = 0;
       for (Column<T, ?> column : columns) {
-        sb.append("<td class='").append(cellStyle);
+        String tdClasses = cellStyle;
         if (curColumn == 0) {
-          sb.append(firstColumnStyle);
+          tdClasses += firstColumnStyle;
         }
         // The first and last column could be the same column.
         if (curColumn == columnCount - 1) {
-          sb.append(lastColumnStyle);
+          tdClasses += lastColumnStyle;
         }
-        sb.append("'><div>");
+
+        SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
         if (value != null) {
-          column.render(value, keyProvider, sb);
+          column.render(value, keyProvider, cellBuilder);
         }
-        sb.append("</div></td>");
+
+        trBuilder.append(template.td(tdClasses, cellBuilder.toSafeHtml()));
         curColumn++;
       }
-      sb.append("</tr>");
+
+      sb.append(template.tr(trClasses, trBuilder.toSafeHtml()));
     }
   }
 
   @Override
-  Element convertToElements(String html) {
+  Element convertToElements(SafeHtml html) {
     return TABLE_IMPL.convertToSectionElement(CellTable.this, "tbody", html);
   }
 
@@ -757,7 +815,7 @@
   }
 
   @Override
-  void replaceAllChildren(List<T> values, String html) {
+  void replaceAllChildren(List<T> values, SafeHtml html) {
     // Cancel any pending redraw.
     if (redrawScheduled) {
       redrawCancelled = true;
@@ -816,35 +874,37 @@
     String className = isFooter ? style.footer() : style.header();
 
     boolean hasHeader = false;
-    StringBuilder sb = new StringBuilder();
-    sb.append("<tr>");
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
+    sb.appendHtmlConstant("<tr>");
     int columnCount = columns.size();
     int curColumn = 0;
     for (Header<?> header : theHeaders) {
-      sb.append("<th class='").append(className);
+      StringBuilder classesBuilder = new StringBuilder(className);
       if (curColumn == 0) {
-        sb.append(" ");
-        sb.append(
+        classesBuilder.append(" ");
+        classesBuilder.append(
             isFooter ? style.firstColumnFooter() : style.firstColumnHeader());
       }
       // The first and last columns could be the same column.
       if (curColumn == columnCount - 1) {
-        sb.append(" ");
-        sb.append(
+        classesBuilder.append(" ");
+        classesBuilder.append(
             isFooter ? style.lastColumnFooter() : style.lastColumnHeader());
       }
-      sb.append("'>");
+
+      SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
       if (header != null) {
         hasHeader = true;
-        header.render(sb);
+        header.render(headerBuilder);
       }
-      sb.append("</th>");
+
+      sb.append(template.th(classesBuilder.toString(), headerBuilder.toSafeHtml()));
       curColumn++;
     }
-    sb.append("</tr>");
+    sb.appendHtmlConstant("</tr>");
 
     // Render the section contents.
-    TABLE_IMPL.replaceAllRows(this, section, sb.toString());
+    TABLE_IMPL.replaceAllRows(this, section, sb.toSafeHtml());
 
     // If the section isn't used, hide it.
     setVisible(section, hasHeader);
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 20dcbbf..b1221b9 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -28,6 +28,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.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.HasAnimation;
 import com.google.gwt.user.client.ui.SimplePanel;
@@ -380,6 +382,13 @@
     String topItemImageValue();
   }
 
+  interface Template extends SafeHtmlTemplates {
+    @Template("<div class=\"{0}\" style=\"position:absolute;{1}:0px;top:0px;"
+        + "height:{2}px;width:{3}px;background:url('{4}') no-repeat scroll "
+        + "center center transparent;\"></div>")
+    SafeHtml image(String classes, String direction, int height, int width, String url);
+  }
+
   /**
    * The default number of children to show under a tree node.
    */
@@ -387,6 +396,8 @@
 
   private static Resources DEFAULT_RESOURCES;
 
+  private static Template template;
+
   private static Resources getDefaultResources() {
     if (DEFAULT_RESOURCES == null) {
       DEFAULT_RESOURCES = GWT.create(Resources.class);
@@ -404,12 +415,12 @@
   /**
    * The HTML used to generate the closed image.
    */
-  private final String closedImageHtml;
+  private final SafeHtml closedImageHtml;
 
   /**
    * The HTML used to generate the closed image for the top items.
    */
-  private final String closedImageTopHtml;
+  private final SafeHtml closedImageTopHtml;
 
   /**
    * The default number of children to display under each node.
@@ -435,17 +446,17 @@
   /**
    * The HTML used to generate the loading image.
    */
-  private final String loadingImageHtml;
+  private final SafeHtml loadingImageHtml;
 
   /**
    * The HTML used to generate the open image.
    */
-  private final String openImageHtml;
+  private final SafeHtml openImageHtml;
 
   /**
    * The HTML used to generate the open image for the top items.
    */
-  private final String openImageTopHtml;
+  private final SafeHtml openImageTopHtml;
 
   /**
    * The hidden root node in the tree.
@@ -479,6 +490,9 @@
   public <T> CellTree(
       TreeViewModel viewModel, T rootValue, Resources resources) {
     super(viewModel);
+    if (template == null) {
+      template = GWT.create(Template.class);
+    }
     this.style = resources.cellTreeStyle();
     this.style.ensureInjected();
     initWidget(new SimplePanel());
@@ -645,7 +659,7 @@
    * @param isTop true if the top element, false if not
    * @return the HTML string
    */
-  String getClosedImageHtml(boolean isTop) {
+  SafeHtml getClosedImageHtml(boolean isTop) {
     return isTop ? closedImageTopHtml : closedImageHtml;
   }
 
@@ -661,7 +675,7 @@
   /**
    * @return the HTML to render the loading image.
    */
-  String getLoadingImageHtml() {
+  SafeHtml getLoadingImageHtml() {
     return loadingImageHtml;
   }
 
@@ -671,7 +685,7 @@
    * @param isTop true if the top element, false if not
    * @return the HTML string
    */
-  String getOpenImageHtml(boolean isTop) {
+  SafeHtml getOpenImageHtml(boolean isTop) {
     return isTop ? openImageTopHtml : openImageHtml;
   }
 
@@ -735,31 +749,20 @@
    * @param isTop true if the image is for a top level element.
    * @return the rendered HTML
    */
-  private String getImageHtml(ImageResource res, boolean isTop) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("<div class='").append(style.itemImage());
+  private SafeHtml getImageHtml(ImageResource res, boolean isTop) {
+    StringBuilder classesBuilder = new StringBuilder(style.itemImage());
     if (isTop) {
-      sb.append(" ").append(style.topItemImage());
+      classesBuilder.append(" ").append(style.topItemImage());
     }
-    sb.append("' ");
 
-    // Add the position and dimensions.
-    sb.append("style=\"position:absolute;top:0px;");
+    String direction;
     if (LocaleInfo.getCurrentLocale().isRTL()) {
-      sb.append("right:0px;");
+      direction = "right";
     } else {
-      sb.append("left:0px;");
+      direction = "left";
     }
-    sb.append("height:").append(res.getHeight()).append("px;");
-    sb.append("width:").append(res.getWidth()).append("px;");
-
-    // Add the background, vertically centered.
-    sb.append("background:url('").append(res.getURL()).append("') ");
-    sb.append("no-repeat scroll center center transparent;");
-
-    // Close the div and return.
-    sb.append("\"></div>");
-    return sb.toString();
+    return template.image(classesBuilder.toString(), direction,
+        res.getHeight(), res.getWidth(), res.getURL());
   }
 
   /**
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 3e10452..ea56b4d 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.cellview.client;
 
 import com.google.gwt.cell.client.Cell;
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
@@ -31,6 +32,10 @@
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.event.shared.GwtEvent.Type;
 import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.client.ui.UIObject;
@@ -57,6 +62,18 @@
  */
 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><div style=\"padding-{0}:{1}px;\" class=\"{2}\">{3}</div></div>")
+    SafeHtml outerDiv(String paddingDirection, int paddingAmount, String classes, SafeHtml content);
+  }
+
+  private static final Template template = GWT.create(Template.class);
+
   /**
    * The {@link com.google.gwt.view.client.HasData} used to show children. This
    * class is intentionally static because we might move it to a new
@@ -78,8 +95,8 @@
         this.childContainer = childContainer;
       }
 
-      public <H extends EventHandler> HandlerRegistration addHandler(
-          H handler, Type<H> type) {
+      public <H extends EventHandler> HandlerRegistration addHandler(H handler,
+          Type<H> type) {
         return handlerManger.addHandler(type, handler);
       }
 
@@ -92,29 +109,31 @@
       }
 
       public ElementIterator getChildIterator() {
-        return new HasDataPresenter.DefaultElementIterator(
-            this, childContainer.getFirstChildElement());
+        return new HasDataPresenter.DefaultElementIterator(this,
+            childContainer.getFirstChildElement());
       }
 
       public void onUpdateSelection() {
       }
 
-      public void render(StringBuilder sb, List<C> values, int start,
+      public void render(SafeHtmlBuilder sb, List<C> values, int start,
           SelectionModel<? super C> selectionModel) {
         // Cache the style names that will be used for each child.
         CellTree.Style style = nodeView.tree.getStyle();
+        String itemValueStyle = style.itemValue();
         String selectedStyle = " " + style.selectedItem();
         String itemStyle = style.item();
-        String itemImageValueStyle = style.itemImageValue();
-        String itemValueStyle = style.itemValue();
+        String itemImageValueStyle = " " + style.itemImageValue();
         String openStyle = " " + style.openItem();
         String topStyle = " " + style.topItem();
         String topImageValueStyle = " " + style.topItemImageValue();
         boolean isRootNode = nodeView.isRootNode();
-        String openImage = nodeView.tree.getOpenImageHtml(isRootNode);
-        String closedImage = nodeView.tree.getClosedImageHtml(isRootNode);
+        SafeHtml openImage = nodeView.tree.getOpenImageHtml(isRootNode);
+        SafeHtml closedImage = nodeView.tree.getClosedImageHtml(isRootNode);
         int imageWidth = nodeView.tree.getImageWidth();
-        int paddingLeft = imageWidth * nodeView.depth;
+        String paddingDirection = LocaleInfo.getCurrentLocale().isRTL()
+            ? "right" : "left";
+        int paddingAmount = imageWidth * nodeView.depth;
 
         // Create a set of currently open nodes.
         Set<Object> openNodes = new HashSet<Object>();
@@ -129,66 +148,53 @@
         }
 
         // Render the child nodes.
-        boolean isRtl = LocaleInfo.getCurrentLocale().isRTL();
         ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
         TreeViewModel model = nodeView.tree.getTreeViewModel();
         for (C value : values) {
           Object key = providesKey.getKey(value);
           boolean isOpen = openNodes.contains(key);
 
-          // Outer div contains image, value, and children (when open).
-          sb.append("<div>");
-
-          // The selection pads the content based on the depth.
-          if (isRtl) {
-            sb.append("<div style='padding-right:");
-          } else {
-            sb.append("<div style='padding-left:");
-          }
-          sb.append(paddingLeft);
-          sb.append("px;' class='").append(itemStyle);
+          // Outer div contains image, value, and children (when open)
+          StringBuilder outerClasses = new StringBuilder(itemStyle);
           if (isOpen) {
-            sb.append(openStyle);
+            outerClasses.append(openStyle);
           }
           if (isRootNode) {
-            sb.append(topStyle);
+            outerClasses.append(topStyle);
           }
           if (selectionModel != null && selectionModel.isSelected(value)) {
-            sb.append(selectedStyle);
+            outerClasses.append(selectedStyle);
           }
-          sb.append("'>");
 
-          // Inner div contains image and value.
-          if (isRtl) {
-            sb.append(
-                "<div onclick='' style='position:relative;padding-right:");
-          } else {
-            sb.append("<div onclick='' style='position:relative;padding-left:");
-          }
-          sb.append(imageWidth);
-          sb.append("px;' class='").append(itemImageValueStyle);
+          // Inner div contains image and value
+          StringBuilder innerClasses = new StringBuilder(itemStyle);
+          innerClasses.append(itemImageValueStyle);
           if (isRootNode) {
-            sb.append(topImageValueStyle);
+            innerClasses.append(topImageValueStyle);
           }
-          sb.append("'>");
-
           // Add the open/close icon.
+          SafeHtml image;
           if (isOpen) {
-            sb.append(openImage);
+            image = openImage;
           } else if (model.isLeaf(value)) {
-            sb.append(LEAF_IMAGE);
+            image = LEAF_IMAGE;
           } else {
-            sb.append(closedImage);
+            image = closedImage;
           }
+          // Render cell contents
+          SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+          cell.render(value, null, cellBuilder);
 
-          // Content div contains value.
-          sb.append("<div class='").append(itemValueStyle).append("'>");
-          cell.render(value, null, sb);
-          sb.append("</div></div></div></div>");
+          SafeHtml innerDiv = template.innerDiv(paddingDirection,
+              imageWidth, innerClasses.toString(), image, itemValueStyle,
+              cellBuilder.toSafeHtml());
+
+          sb.append(template.outerDiv(paddingDirection,
+              paddingAmount, outerClasses.toString(), innerDiv));
         }
       }
 
-      public void replaceAllChildren(List<C> values, String html) {
+      public void replaceAllChildren(List<C> values, SafeHtml html) {
         // Hide the child container so we can animate it.
         if (nodeView.tree.isAnimationEnabled()) {
           nodeView.ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
@@ -215,21 +221,21 @@
         }
       }
 
-      public void replaceChildren(List<C> values, int start, String html) {
+      public void replaceChildren(List<C> values, int start, SafeHtml html) {
         Map<Object, CellTreeNodeView<?>> savedViews = saveChildState(values, 0);
 
-        Element newChildren = AbstractHasData.convertToElements(
-            nodeView.tree, getTmpElem(), html);
-        AbstractHasData.replaceChildren(
-            nodeView.tree, childContainer, newChildren, start, html);
+        Element newChildren = AbstractHasData.convertToElements(nodeView.tree,
+            getTmpElem(), html);
+        AbstractHasData.replaceChildren(nodeView.tree, childContainer,
+            newChildren, start, html);
 
         loadChildState(values, 0, savedViews);
       }
 
       public void resetFocus() {
         if (nodeView.keyboardSelectedIndex != -1) {
-          nodeView.keyboardEnter(
-              nodeView.keyboardSelectedIndex, nodeView.keyboardFocused);
+          nodeView.keyboardEnter(nodeView.keyboardSelectedIndex,
+              nodeView.keyboardFocused);
         }
       }
 
@@ -261,10 +267,9 @@
         Element childElem = container.getFirstChildElement();
         for (int i = start; i < end; i++) {
           C childValue = values.get(i - start);
-          CellTreeNodeView<C> child = nodeView.createTreeNodeView(
-              nodeInfo, childElem, childValue, null);
-          CellTreeNodeView<?> savedChild = savedViews.remove(
-              providesKey.getKey(childValue));
+          CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
+              childElem, childValue, null);
+          CellTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
           // Copy the saved child's state into the new child
           if (savedChild != null) {
             child.animationFrame = savedChild.animationFrame;
@@ -313,8 +318,8 @@
        * @param start the start index
        * @return the map of open nodes
        */
-      private Map<Object, CellTreeNodeView<?>> saveChildState(
-          List<C> values, int start) {
+      private Map<Object, CellTreeNodeView<?>> saveChildState(List<C> values,
+          int start) {
         // Ensure that we have a children array.
         if (nodeView.children == null) {
           nodeView.children = new ArrayList<CellTreeNodeView<?>>();
@@ -324,8 +329,7 @@
         int len = values.size();
         int end = start + len;
         int childCount = nodeView.getChildCount();
-        Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<
-            Object, CellTreeNodeView<?>>();
+        Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<Object, CellTreeNodeView<?>>();
         for (int i = start; i < end && i < childCount; i++) {
           CellTreeNodeView<?> child = nodeView.getChildNode(i);
           if (child.isOpen()) {
@@ -339,8 +343,7 @@
 
         // Trim the saved views down to the children that still exists.
         ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
-        Map<Object, CellTreeNodeView<?>> savedViews = new HashMap<
-            Object, CellTreeNodeView<?>>();
+        Map<Object, CellTreeNodeView<?>> savedViews = new HashMap<Object, CellTreeNodeView<?>>();
         for (C childValue : values) {
           // Remove any child elements that correspond to prior children
           // so the call to setInnerHtml will not destroy them
@@ -376,8 +379,8 @@
       this.nodeView = nodeView;
       cell = nodeInfo.getCell();
 
-      presenter = new HasDataPresenter<C>(
-          this, new View(nodeView.ensureChildContainer()), pageSize);
+      presenter = new HasDataPresenter<C>(this, new View(
+          nodeView.ensureChildContainer()), pageSize);
 
       // Use a pager to update buttons.
       presenter.addRowCountChangeHandler(new RowCountChangeEvent.Handler() {
@@ -443,8 +446,7 @@
       presenter.setRowData(start, values);
     }
 
-    public void setSelectionModel(
-        final SelectionModel<? super C> selectionModel) {
+    public void setSelectionModel(final SelectionModel<? super C> selectionModel) {
       presenter.setSelectionModel(selectionModel);
     }
 
@@ -456,8 +458,8 @@
       presenter.setVisibleRange(range);
     }
 
-    public void setVisibleRangeAndClearData(
-        Range range, boolean forceRangeChangeEvent) {
+    public void setVisibleRangeAndClearData(Range range,
+        boolean forceRangeChangeEvent) {
       presenter.setVisibleRangeAndClearData(range, forceRangeChangeEvent);
     }
   }
@@ -489,8 +491,8 @@
 
     public int getIndex() {
       assertNotDestroyed();
-      return (nodeView.parentNode == null)
-          ? 0 : nodeView.parentNode.children.indexOf(nodeView);
+      return (nodeView.parentNode == null) ? 0
+          : nodeView.parentNode.children.indexOf(nodeView);
     }
 
     public TreeNode getParent() {
@@ -554,8 +556,8 @@
   /**
    * The element used in place of an image when a node has no children.
    */
-  private static final String LEAF_IMAGE =
-      "<div style='position:absolute;display:none;'></div>";
+  private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant(
+      "<div style='position:absolute;display:none;'></div>");
 
   /**
    * The temporary element used to render child items.
@@ -569,8 +571,7 @@
    * @return the cell parent within the node
    */
   private static Element getCellParent(Element nodeElem) {
-    return getSelectionElement(nodeElem).getFirstChildElement().getChild(
-        1).cast();
+    return getSelectionElement(nodeElem).getFirstChildElement().getChild(1).cast();
   }
 
   /**
@@ -894,8 +895,8 @@
    * @param viewData view data associated with the node
    * @return a TreeNodeView of suitable type
    */
-  protected <C> CellTreeNodeView<C> createTreeNodeView(
-      NodeInfo<C> nodeInfo, Element childElem, C childValue, Object viewData) {
+  protected <C> CellTreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
+      Element childElem, C childValue, Object viewData) {
     return new CellTreeNodeView<C>(tree, this, nodeInfo, childElem, childValue);
   }
 
@@ -908,7 +909,7 @@
     if (parentNodeInfo != null) {
       Cell<T> parentCell = parentNodeInfo.getCell();
       String eventType = event.getType();
-          SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+      SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
 
       // Update selection.
       if (selectionModel != null && "click".equals(eventType)
@@ -922,8 +923,8 @@
       Object key = getValueKey();
       Set<String> consumedEvents = parentCell.getConsumedEvents();
       if (consumedEvents != null && consumedEvents.contains(eventType)) {
-        parentCell.onBrowserEvent(
-            cellParent, value, key, event, parentNodeInfo.getValueUpdater());
+        parentCell.onBrowserEvent(cellParent, value, key, event,
+            parentNodeInfo.getValueUpdater());
       }
     }
   }
@@ -960,8 +961,8 @@
    * @param <C> the child data type of the node
    */
   protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
-    NodeCellList<C> view = new NodeCellList<C>(
-        nodeInfo, this, tree.getDefaultNodeSize());
+    NodeCellList<C> view = new NodeCellList<C>(nodeInfo, this,
+        tree.getDefaultNodeSize());
     listView = view;
     view.setSelectionModel(nodeInfo.getSelectionModel());
     nodeInfo.setDataDisplay(view);
@@ -1135,7 +1136,6 @@
     if (keyboardSelection == null) {
       return;
     }
-
     Element parent = keyboardSelection.getFirstChildElement();
     Element child = parent.getFirstChildElement();
     child.removeAttribute("tabIndex");
@@ -1175,8 +1175,7 @@
   void showFewer() {
     Range range = listView.getVisibleRange();
     int defaultPageSize = listView.getDefaultPageSize();
-    int maxSize = Math.max(
-        defaultPageSize, range.getLength() - defaultPageSize);
+    int maxSize = Math.max(defaultPageSize, range.getLength() - defaultPageSize);
     listView.setVisibleRange(range.getStart(), maxSize);
   }
 
@@ -1212,16 +1211,16 @@
 
     // Replace the image element with a new one.
     boolean isTopLevel = parentNode.isRootNode();
-    String html = tree.getClosedImageHtml(isTopLevel);
+    SafeHtml html = tree.getClosedImageHtml(isTopLevel);
     if (open) {
-      html = isLoading ? tree.getLoadingImageHtml() : tree.getOpenImageHtml(
-          isTopLevel);
+      html = isLoading ? tree.getLoadingImageHtml()
+          : tree.getOpenImageHtml(isTopLevel);
     }
     if (nodeInfoLoaded && nodeInfo == null) {
       html = LEAF_IMAGE;
     }
     Element tmp = Document.get().createDivElement();
-    tmp.setInnerHTML(html);
+    tmp.setInnerHTML(html.asString());
     Element imageElem = tmp.getFirstChildElement();
 
     Element oldImg = getImageElement();
diff --git a/user/src/com/google/gwt/user/cellview/client/Column.java b/user/src/com/google/gwt/user/cellview/client/Column.java
index 6da6a4c..89ec9da 100644
--- a/user/src/com/google/gwt/user/cellview/client/Column.java
+++ b/user/src/com/google/gwt/user/cellview/client/Column.java
@@ -21,6 +21,7 @@
 import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.view.client.ProvidesKey;
 
 /**
@@ -74,7 +75,7 @@
    * @param keyProvider the {@link ProvidesKey} for the object
    * @param sb the buffer to render into
    */
-  public void render(T object, ProvidesKey<T> keyProvider, StringBuilder sb) {
+  public void render(T object, ProvidesKey<T> keyProvider, SafeHtmlBuilder sb) {
     Object key = getKey(object, keyProvider);
     cell.render(getValue(object), key, sb);
   }
diff --git a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
index 0f793c4..c922a17 100644
--- a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
+++ b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
@@ -19,6 +19,8 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.view.client.HasData;
 import com.google.gwt.view.client.Range;
 import com.google.gwt.view.client.RangeChangeEvent;
@@ -165,12 +167,12 @@
      * Construct the HTML that represents the list of values, taking the
      * selection state into account.
      *
-     * @param sb the {@link StringBuilder} to build into
+     * @param sb the {@link SafeHtmlBuilder} to build into
      * @param values the values to render
      * @param start the start index that is being rendered
      * @param selectionModel the {@link SelectionModel}
      */
-    void render(StringBuilder sb, List<T> values, int start,
+    void render(SafeHtmlBuilder sb, List<T> values, int start,
         SelectionModel<? super T> selectionModel);
 
     /**
@@ -179,7 +181,7 @@
      * @param values the values of the new children
      * @param html the html to render in the child
      */
-    void replaceAllChildren(List<T> values, String html);
+    void replaceAllChildren(List<T> values, SafeHtml html);
 
     /**
      * Convert the specified HTML into DOM elements and replace the existing
@@ -191,7 +193,7 @@
      * @param start the start index to be replaced
      * @param html the HTML to convert
      */
-    void replaceChildren(List<T> values, int start, String html);
+    void replaceChildren(List<T> values, int start, SafeHtml html);
 
     /**
      * Re-establish focus on an element within the view if desired.
@@ -221,7 +223,7 @@
    * the contents do not change the next time we render, then we don't have to
    * set inner html.
    */
-  private String lastContents = null;
+  private SafeHtml lastContents = null;
 
   private int pageSize;
   private int pageStart = 0;
@@ -428,7 +430,7 @@
     List<T> boundedValues = rowData.subList(
         boundedStart - pageStart, boundedEnd - pageStart);
     int boundedSize = boundedValues.size();
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     view.render(sb, boundedValues, boundedStart, selectionModel);
 
     // Update the loading state.
@@ -440,7 +442,7 @@
         && (boundedSize >= childCount || boundedSize >= getCurrentPageSize()
             || rowData.size() < childCount)) {
       // If the contents have not changed, we're done.
-      String newContents = sb.toString();
+      SafeHtml newContents = sb.toSafeHtml();
       if (!newContents.equals(lastContents)) {
         lastContents = newContents;
         view.replaceAllChildren(boundedValues, newContents);
@@ -448,7 +450,7 @@
     } else {
       lastContents = null;
       view.replaceChildren(
-          boundedValues, boundedStart - pageStart, sb.toString());
+          boundedValues, boundedStart - pageStart, sb.toSafeHtml());
     }
 
     // Allow the view to reestablish focus after being re-rendered
diff --git a/user/src/com/google/gwt/user/cellview/client/Header.java b/user/src/com/google/gwt/user/cellview/client/Header.java
index 17f4f70..8e4b694 100644
--- a/user/src/com/google/gwt/user/cellview/client/Header.java
+++ b/user/src/com/google/gwt/user/cellview/client/Header.java
@@ -19,6 +19,7 @@
 import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 /**
  * A table column header or footer.
@@ -46,7 +47,7 @@
     cell.onBrowserEvent(elem, value, getKey(), event, updater);
   }
 
-  public void render(StringBuilder sb) {
+  public void render(SafeHtmlBuilder sb) {
     cell.render(getValue(), getKey(), sb);
   }
 
diff --git a/user/src/com/google/gwt/user/cellview/client/SafeHtmlHeader.java b/user/src/com/google/gwt/user/cellview/client/SafeHtmlHeader.java
new file mode 100644
index 0000000..df7ed30
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/SafeHtmlHeader.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 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.user.cellview.client;
+
+import com.google.gwt.cell.client.SafeHtmlCell;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * A Header containing safe HTML data rendered by a SafeHtmlCell.
+ */
+public class SafeHtmlHeader extends Header<SafeHtml> {
+
+  private SafeHtml text;
+
+  public SafeHtmlHeader(SafeHtml text) {
+    super(new SafeHtmlCell());
+    this.text = text;
+  }
+
+  @Override
+  public SafeHtml getValue() {
+    return text;
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/ValuePicker.java b/user/src/com/google/gwt/user/client/ui/ValuePicker.java
index a433144..27b8e00 100644
--- a/user/src/com/google/gwt/user/client/ui/ValuePicker.java
+++ b/user/src/com/google/gwt/user/client/ui/ValuePicker.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 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
@@ -19,6 +19,7 @@
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.text.shared.Renderer;
 import com.google.gwt.user.cellview.client.CellList;
 import com.google.gwt.view.client.SelectionChangeEvent;
@@ -34,22 +35,22 @@
  * </span>
  * </p>
  * Allows the user to pick a single value from a list.
- * 
+ *
  * @param <T> the type of value
  */
 public class ValuePicker<T> extends Composite
     implements HasConstrainedValue<T> {
-  
+
   private static class DefaultCell<T> extends AbstractCell<T> {
     private final Renderer<T> renderer;
-    
+
     DefaultCell(Renderer<T> renderer) {
-      this.renderer = renderer; 
+      this.renderer = renderer;
     }
-    
+
     @Override
-    public void render(T value, Object viewData, StringBuilder sb) {
-      sb.append(renderer.render(value));
+    public void render(T value, Object viewData, SafeHtmlBuilder sb) {
+      sb.appendEscaped(renderer.render(value));
     }
   }
 
@@ -68,7 +69,7 @@
       }
     });
   }
-  
+
   public ValuePicker(Renderer<T> renderer) {
     this(new CellList<T>(new DefaultCell<T>(renderer)));
   }
@@ -91,7 +92,7 @@
   public T getValue() {
     return value;
   }
-  
+
   public void setAcceptableValues(Collection<T> places) {
     cellList.setRowData(0, new ArrayList<T>(places));
   }
diff --git a/user/test/com/google/gwt/cell/client/CellTestBase.java b/user/test/com/google/gwt/cell/client/CellTestBase.java
index f01f481..c9b0cc7 100644
--- a/user/test/com/google/gwt/cell/client/CellTestBase.java
+++ b/user/test/com/google/gwt/cell/client/CellTestBase.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 import java.util.Set;
 
@@ -30,11 +31,6 @@
 public abstract class CellTestBase<T> extends GWTTestCase {
 
   /**
-   * The default row value key used for all tests.
-   */
-  protected static final Object DEFAULT_KEY = new Object();
-
-  /**
    * A mock cell used for testing.
    *
    * @param <T> the cell type
@@ -76,9 +72,9 @@
     }
 
     @Override
-    public void render(T value, Object key, StringBuilder sb) {
+    public void render(T value, Object key, SafeHtmlBuilder sb) {
       if (value != null) {
-        sb.append(value);
+        sb.appendEscaped(String.valueOf(value));
       }
     }
   }
@@ -102,6 +98,11 @@
     }
   }
 
+  /**
+   * The default row value key used for all tests.
+   */
+  protected static final Object DEFAULT_KEY = new Object();
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.cell.Cell";
@@ -146,9 +147,9 @@
   public void testRender() {
     Cell<T> cell = createCell();
     T value = createCellValue();
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render(value, null, sb);
-    assertEquals(getExpectedInnerHtml(), sb.toString());
+    assertEquals(getExpectedInnerHtml(), sb.toSafeHtml().asString());
   }
 
   /**
@@ -156,19 +157,12 @@
    */
   public void testRenderNull() {
     Cell<T> cell = createCell();
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render(null, null, sb);
-    assertEquals(getExpectedInnerHtmlNull(), sb.toString());
+    assertEquals(getExpectedInnerHtmlNull(), sb.toSafeHtml().asString());
   }
 
   /**
-   * Get the expected events that the cell should consume.
-   *
-   * @return the consumed events.
-   */
-  protected abstract String[] getConsumedEvents();
-
-  /**
    * Create a new cell to test.
    *
    * @return the new cell
@@ -190,6 +184,13 @@
   protected abstract boolean dependsOnSelection();
 
   /**
+   * Get the expected events that the cell should consume.
+   *
+   * @return the consumed events.
+   */
+  protected abstract String[] getConsumedEvents();
+
+  /**
    * Get the expected inner HTML value of the rendered cell.
    *
    * @return the expected string
diff --git a/user/test/com/google/gwt/cell/client/EditTextCellTest.java b/user/test/com/google/gwt/cell/client/EditTextCellTest.java
index d4619fe..5c213ed 100644
--- a/user/test/com/google/gwt/cell/client/EditTextCellTest.java
+++ b/user/test/com/google/gwt/cell/client/EditTextCellTest.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 /**
  * Tests for {@link EditTextCell}.
@@ -128,9 +129,9 @@
     viewData.setText("newValue");
     viewData.setEditing(false);
     cell.setViewData(DEFAULT_KEY, viewData);
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render("originalValue", DEFAULT_KEY, sb);
-    assertEquals("newValue", sb.toString());
+    assertEquals("newValue", sb.toSafeHtml().asString());
   }
 
   public void testViewData() {
@@ -200,6 +201,6 @@
 
   @Override
   protected String getExpectedInnerHtmlViewData() {
-    return "<input type='text' value='newValue'></input>";
+    return "<input type=\"text\" value=\"newValue\"></input>";
   }
 }
diff --git a/user/test/com/google/gwt/cell/client/EditableCellTestBase.java b/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
index 19fe41c..cf576bd 100644
--- a/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
+++ b/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 /**
  * Base class for testing {@link AbstractEditableCell}s that can be modified.
@@ -34,9 +35,11 @@
     AbstractEditableCell<T, V> cell = createCell();
     T value = createCellValue();
     cell.setViewData(DEFAULT_KEY, createCellViewData());
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render(value, DEFAULT_KEY, sb);
-    assertEquals(getExpectedInnerHtmlViewData(), sb.toString());
+    String expectedInnerHtmlViewData = getExpectedInnerHtmlViewData();
+    String asString = sb.toSafeHtml().asString();
+    assertEquals(expectedInnerHtmlViewData, asString);
   }
 
   @Override
diff --git a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
index 18a25fe..eacbf3a 100644
--- a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
+++ b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.client.ui.HasVerticalAlignment;
 
 /**
@@ -59,16 +60,16 @@
     };
 
     // Render the cell.
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render("helloworld", null, sb);
 
     // Compare the expected render string.
-    String expected = "<div style='position:relative;padding-left:64px;'>";
+    String expected = "<div style=\"position:relative;padding-left:64px;\">";
     expected += cell.getImageHtml(
-        images.prettyPiccy(), HasVerticalAlignment.ALIGN_MIDDLE, true);
+        images.prettyPiccy(), HasVerticalAlignment.ALIGN_MIDDLE, true).asString();
     expected += "<div>helloworld</div>";
     expected += "</div>";
-    assertEquals(expected, sb.toString());
+    assertEquals(expected, sb.toSafeHtml().asString());
   }
 
   public void testSelectableDelegate() {
@@ -118,8 +119,8 @@
   @Override
   protected String getExpectedInnerHtml() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style='position:relative;padding-left:64px;'>";
-    html += cell.getIconHtml("helloworld");
+    String html = "<div style=\"position:relative;padding-left:64px;\">";
+    html += cell.getIconHtml("helloworld").asString();
     html += "<div>helloworld</div>";
     html += "</div>";
     return html;
@@ -128,8 +129,8 @@
   @Override
   protected String getExpectedInnerHtmlNull() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style='position:relative;padding-left:64px;'>";
-    html += cell.getIconHtml("helloworld");
+    String html = "<div style=\"position:relative;padding-left:64px;\">";
+    html += cell.getIconHtml("helloworld").asString();
     html += "<div></div>";
     html += "</div>";
     return html;
diff --git a/user/test/com/google/gwt/cell/client/ImageCellTest.java b/user/test/com/google/gwt/cell/client/ImageCellTest.java
index abe71fd..cd1d48d 100644
--- a/user/test/com/google/gwt/cell/client/ImageCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ImageCellTest.java
@@ -42,7 +42,7 @@
 
   @Override
   protected String getExpectedInnerHtml() {
-    return "<img src='test.png'/>";
+    return "<img src=\"test.png\"></img>";
   }
 
   @Override
diff --git a/user/test/com/google/gwt/cell/client/ImageLoadingCellTest.java b/user/test/com/google/gwt/cell/client/ImageLoadingCellTest.java
index 2b15c79..8d0d606 100644
--- a/user/test/com/google/gwt/cell/client/ImageLoadingCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ImageLoadingCellTest.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 
 /**
  * Tests for {@link TextCell}.
@@ -28,12 +29,12 @@
   public void testRender() {
     Cell<String> cell = createCell();
     String value = createCellValue();
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     cell.render(value, null, sb);
 
     // Render the html.
     Element elem = Document.get().createDivElement();
-    elem.setInnerHTML(sb.toString());
+    elem.setInnerHTML(sb.toSafeHtml().asString());
 
     // Verify the image.
     assertEquals(2, elem.getChildCount());
diff --git a/user/test/com/google/gwt/cell/client/SelectionCellTest.java b/user/test/com/google/gwt/cell/client/SelectionCellTest.java
index ecacfe7..010ac8e 100644
--- a/user/test/com/google/gwt/cell/client/SelectionCellTest.java
+++ b/user/test/com/google/gwt/cell/client/SelectionCellTest.java
@@ -63,22 +63,22 @@
 
   @Override
   protected String getExpectedInnerHtml() {
-    return "<select><option value='option 0'>option 0</option>"
-        + "<option value='option 1' selected='selected'>option 1</option>"
-        + "<option value='option 2'>option 2</option></select>";
+    return "<select><option value=\"option 0\">option 0</option>"
+        + "<option value=\"option 1\" selected=\"selected\">option 1</option>"
+        + "<option value=\"option 2\">option 2</option></select>";
   }
 
   @Override
   protected String getExpectedInnerHtmlNull() {
-    return "<select><option value='option 0'>option 0</option>"
-        + "<option value='option 1'>option 1</option>"
-        + "<option value='option 2'>option 2</option></select>";
+    return "<select><option value=\"option 0\">option 0</option>"
+        + "<option value=\"option 1\">option 1</option>"
+        + "<option value=\"option 2\">option 2</option></select>";
   }
 
   @Override
   protected String getExpectedInnerHtmlViewData() {
-    return "<select><option value='option 0'>option 0</option>"
-        + "<option value='option 1'>option 1</option>"
-        + "<option value='option 2' selected='selected'>option 2</option></select>";
+    return "<select><option value=\"option 0\">option 0</option>"
+        + "<option value=\"option 1\">option 1</option>"
+        + "<option value=\"option 2\" selected=\"selected\">option 2</option></select>";
   }
 }
diff --git a/user/test/com/google/gwt/cell/client/TextInputCellTest.java b/user/test/com/google/gwt/cell/client/TextInputCellTest.java
index cee86c2..b5eee3a 100644
--- a/user/test/com/google/gwt/cell/client/TextInputCellTest.java
+++ b/user/test/com/google/gwt/cell/client/TextInputCellTest.java
@@ -63,16 +63,16 @@
 
   @Override
   protected String getExpectedInnerHtml() {
-    return "<input type='text' value='hello'></input>";
+    return "<input type=\"text\" value=\"hello\"></input>";
   }
 
   @Override
   protected String getExpectedInnerHtmlNull() {
-    return "<input type='text'></input>";
+    return "<input type=\"text\"></input>";
   }
 
   @Override
   protected String getExpectedInnerHtmlViewData() {
-    return "<input type='text' value='newValue'></input>";
+    return "<input type=\"text\" value=\"newValue\"></input>";
   }
 }
diff --git a/user/test/com/google/gwt/safehtml/shared/SimpleHtmlSanitizerTest.java b/user/test/com/google/gwt/safehtml/shared/SimpleHtmlSanitizerTest.java
index 32983e2..1f5d8c7 100644
--- a/user/test/com/google/gwt/safehtml/shared/SimpleHtmlSanitizerTest.java
+++ b/user/test/com/google/gwt/safehtml/shared/SimpleHtmlSanitizerTest.java
@@ -18,7 +18,7 @@
 import junit.framework.TestCase;
 
 /**
- * Unit tests for SanitizedHtml
+ * Unit tests for SanitizedHtml.
  */
 public class SimpleHtmlSanitizerTest extends TestCase {
 
diff --git a/user/test/com/google/gwt/user/cellview/client/ColumnTest.java b/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
index d40eace..1b92c2b 100644
--- a/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
@@ -23,6 +23,7 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.client.Timer;
 
 /**
@@ -182,8 +183,8 @@
     TextCell cell = new TextCell();
     Column<String, String> column = new IdentityColumn<String>(cell);
 
-    StringBuilder sb = new StringBuilder();
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
     column.render("test", null, sb);
-    assertEquals("test", sb.toString());
+    assertEquals("test", sb.toSafeHtml().asString());
   }
 }
diff --git a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
index f5a7098..06aa83c 100644
--- a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
@@ -19,6 +19,8 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent.Type;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.cellview.client.HasDataPresenter.View;
@@ -97,7 +99,7 @@
 
     private int childCount;
     private boolean dependsOnSelection;
-    private String lastHtml;
+    private SafeHtml lastHtml;
     private LoadingState loadingState;
     private boolean onUpdateSelectionFired;
     private boolean replaceAllChildrenCalled;
@@ -110,7 +112,11 @@
     }
 
     public void assertLastHtml(String html) {
-      assertEquals(html, lastHtml);
+      if (html == null) {
+        assertNull(lastHtml);
+      } else {
+        assertEquals(html, lastHtml.asString());
+      }
       lastHtml = null;
     }
 
@@ -163,19 +169,19 @@
       onUpdateSelectionFired = true;
     }
 
-    public void render(StringBuilder sb, List<T> values, int start,
+    public void render(SafeHtmlBuilder sb, List<T> values, int start,
         SelectionModel<? super T> selectionModel) {
-      sb.append("start=").append(start);
-      sb.append(",size=").append(values.size());
+      sb.appendHtmlConstant("start=").append(start);
+      sb.appendHtmlConstant(",size=").append(values.size());
     }
 
-    public void replaceAllChildren(List<T> values, String html) {
+    public void replaceAllChildren(List<T> values, SafeHtml html) {
       childCount = values.size();
       replaceAllChildrenCalled = true;
       lastHtml = html;
     }
 
-    public void replaceChildren(List<T> values, int start, String html) {
+    public void replaceChildren(List<T> values, int start, SafeHtml html) {
       childCount = Math.max(childCount, start + values.size());
       replaceChildrenCalled = true;
       lastHtml = html;