Adding a new ElementBuilder API to build DOM Elements using the builder pattern. There are two implementations: one that builds using DOM manipulation, and another that builds using String concatenation and innerHtml.  The latter can be user in a regular JVM to create an HTML string, which will hopefully help with server side rendering in the future. Both can be used on the client, which allows an app to optimize depending on which is faster. Once I incorporate this into Cell Widgets, I'll do performance metrics on real apps to compare the difference.

There are a lot of classes here, so I'll explain what they are. At the core, we have the ElementBuilderBase interface and its subclasses, one for each element type (ex. DivBuilder, SelectBuilder).  These interfaces mirror the Element JSO types, with short method names to reduce the verbosity of the builder. You can build an element by doing something like divBuilder.id("myDiv").title("my title").end(). If you start a sub element, you get a builder with methods specific to the new element.  For example, selectBuilder.startOption() returns an OptionBuilder, with methods like disabled() and defaultSelected(). Note that currently, the only builder subclasses are Div, Select, and Option.  There is also a StylesBuilder for building the style attribute (similar to SafeStylesBuilder, but generic enough to work for DOM or HTML).  I'll add the rest once we've finalized the design.  The return value of the end() method can be paraneterized with the builder class of the containing element, so you can continue chaining with the parent builder.

Next, we have the two implementations of these interfaces: one for DOM and one for HTML.  For example, DomDivBuilder is a concrete class to build a div using DOM manipulation, while HtmlDivBuilder is a concrete class to build a div using HTML manipulation.  The HTML versions expose asSafeHtml(), which allows users to access the string (useful for server side rendering).

Then, there are two impl clases: DomBuilderImpl and HtmlBuilderImpl.  In practice, the concrete implementations of the ElementBuilder interfaces actually just delegate to a shared instance of one of these impl classes.  That has the benefit that we don't create an object for every single element that is created (just one object for every element type).  The impl classes are responsible for builder the Element/String, and they maintain state to ensure that they fire IllegalStateExceptions in similar circumstances.  For example, technically DomBuilderImpl can set an attribute after setting innerHtml, but that wouldn't work in HtmlBuilderImpl, so both fire an exception in this case.

Finally, there are the ElementFactory classes, which return a new top level element builder.  ElementFactory#get() will return either DomBuilderFactory or HtmlBuilderFactory, depending on which is optimal for the platform.  Alternatively, user can access the DomBuilderFactory or HtmlBuilderFactory directly.  The factory has createXXXBuilder() methods to create a builder for each element.

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

Review by: skybrian@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10412 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/doc/build.xml b/doc/build.xml
index e190367..a66d058 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -109,7 +109,7 @@
       <arg value="-sourcepath" />
       <arg pathref="USER_SOURCE_PATH" />
       <arg value="-examplepackages" />
-      <arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client;com.google.gwt.examples.rpc.server;com.google.gwt.examples.benchmarks;com.google.gwt.examples.cell;com.google.gwt.examples.cellview;com.google.gwt.examples.view" />
+      <arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client;com.google.gwt.examples.rpc.server;com.google.gwt.examples.benchmarks;com.google.gwt.examples.cell;com.google.gwt.examples.cellview;com.google.gwt.examples.view;com.google.gwt.examples.cellview;com.google.gwt.examples.dom.builder" />
       <arg value="-packages" />
       <arg value="${USER_PKGS};${USER_CLASSES}" />
     </java>
diff --git a/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryChainingExample.java b/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryChainingExample.java
new file mode 100644
index 0000000..21936d6
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryChainingExample.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.examples.dom.builder;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderFactory;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * Example of using {@link ElementBuilderFactory} to construct an element by
+ * chaining methods.
+ */
+public class ElementBuilderFactoryChainingExample implements EntryPoint {
+
+  @Override
+  public void onModuleLoad() {
+    /*
+     * Create a builder for the outermost element. The initial state of the
+     * builder is a started element ready for attributes (eg. "<div").
+     */
+    DivBuilder divBuilder = ElementBuilderFactory.get().createDivBuilder();
+
+    /*
+     * Build the element.
+     * 
+     * First, we set the element's id to "myId", then set its title to
+     * "This is a div". Next, we set the background-color style property to
+     * "red". Finally, we set some inner text to "Hello World!". When we are
+     * finished, we end the div.
+     * 
+     * When building elements, the order of methods matters. Attributes and
+     * style properties must be added before setting inner html/text or
+     * appending children. This is because the string implementation cannot
+     * concatenate an attribute after child content has been added.
+     * 
+     * Note that endStyle() takes the builder type that we want to return, which
+     * must be the "parent" builder. endDiv() does not need the optional
+     * argument because we are finished building the element.
+     */
+    divBuilder.id("myId").title("This is a div").style().trustedBackgroundColor("red")
+        .<DivBuilder> endStyle().text("Hello World!").endDiv();
+
+    // Get the element out of the builder.
+    Element div = divBuilder.finish();
+
+    // Attach the element to the page.
+    Document.get().getBody().appendChild(div);
+  }
+}
\ No newline at end of file
diff --git a/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryNonChainingExample.java b/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryNonChainingExample.java
new file mode 100644
index 0000000..24ee073
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/dom/builder/ElementBuilderFactoryNonChainingExample.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.examples.dom.builder;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderFactory;
+import com.google.gwt.dom.builder.shared.OptionBuilder;
+import com.google.gwt.dom.builder.shared.SelectBuilder;
+import com.google.gwt.dom.builder.shared.StylesBuilder;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * Example of using {@link ElementBuilderFactory} to construct an element using
+ * each builder as an object.
+ */
+public class ElementBuilderFactoryNonChainingExample implements EntryPoint {
+
+  @Override
+  public void onModuleLoad() {
+    /*
+     * Create a builder for the outermost element. The initial state of the
+     * builder is a started element ready for attributes (eg. "<div").
+     */
+    DivBuilder divBuilder = ElementBuilderFactory.get().createDivBuilder();
+
+    // Add attributes to the div.
+    divBuilder.id("myId");
+    divBuilder.title("This is a div");
+
+    // Add style properties to the div.
+    StylesBuilder divStyle = divBuilder.style();
+    divStyle.trustedBackgroundColor("red");
+    divStyle.endStyle();
+
+    // Append a child select element to the div.
+    SelectBuilder selectBuilder = divBuilder.startSelect();
+
+    // Append three options to the select element.
+    for (int i = 0; i < 3; i++) {
+      OptionBuilder optionBuilder = selectBuilder.startOption();
+      optionBuilder.value("value" + i);
+      optionBuilder.text("Option " + i);
+      optionBuilder.endOption();
+    }
+
+    /*
+     * End the select and div elements. Note that ending the remaining elements
+     * before calling asElement() below is optional, but a good practice. If we
+     * did not call endOption() above, we would append each option element to
+     * the preceeding option element, which is not what we want.
+     * 
+     * In general, you must pay close attention to ensure that you close
+     * elements correctly.
+     */
+    selectBuilder.endSelect();
+    divBuilder.endDiv();
+
+    // Get the element out of the builder.
+    Element div = divBuilder.finish();
+
+    // Attach the element to the page.
+    Document.get().getBody().appendChild(div);
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/dom/builder/DomBuilder.gwt.xml b/user/src/com/google/gwt/dom/builder/DomBuilder.gwt.xml
new file mode 100644
index 0000000..db1b7ec
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/DomBuilder.gwt.xml
@@ -0,0 +1,40 @@
+<!--                                                                        -->
+<!-- Copyright 2007 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Deferred binding rules for browser selection.                          -->
+<!--                                                                        -->
+<!-- This module is typically inherited via com.google.gwt.user.User        -->
+<!--                                                                        -->
+<module>
+  <inherits name="com.google.gwt.core.Core"/>
+  <inherits name="com.google.gwt.dom.DOM"/>
+  <inherits name="com.google.gwt.safehtml.SafeHtml"/>
+
+  <!-- Default to string concatenation with inner HTML. -->
+  <!-- TODO(jlabanca): Research performance using different methods.  -->
+  <replace-with class="com.google.gwt.dom.builder.shared.HtmlBuilderFactory">
+    <when-type-is class="com.google.gwt.dom.builder.shared.ElementBuilderFactory" />
+  </replace-with>
+
+  <!-- Some browsers perform DOM manipulation faster. -->
+  <replace-with class="com.google.gwt.dom.builder.client.DomBuilderFactory">
+    <when-type-is class="com.google.gwt.dom.builder.shared.ElementBuilderFactory" />
+    <any>
+      <when-property-is name="user.agent" value="safari" />
+    </any>
+  </replace-with>
+
+  <source path="client"/>
+  <source path="shared"/>
+</module>
diff --git a/user/src/com/google/gwt/dom/builder/client/DomBuilderFactory.java b/user/src/com/google/gwt/dom/builder/client/DomBuilderFactory.java
new file mode 100644
index 0000000..72e7e33
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomBuilderFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderFactory;
+import com.google.gwt.dom.builder.shared.OptionBuilder;
+import com.google.gwt.dom.builder.shared.SelectBuilder;
+
+/**
+ * Factory for creating element builders that construct elements using DOM
+ * manipulation.
+ */
+public class DomBuilderFactory extends ElementBuilderFactory {
+
+  private static DomBuilderFactory instance;
+
+  /**
+   * Get the instance of the {@link DomBuilderFactory}.
+   * 
+   * <p>
+   * Use {@link ElementBuilderFactory#get()} to fetch a factory optimized for
+   * the browser client. However, you can use this factory directly if you want
+   * to force the builders to build elements use DOM manipulation.
+   * </p>
+   * 
+   * @return the {@link ElementBuilderFactory}
+   */
+  public static DomBuilderFactory get() {
+    if (instance == null) {
+      instance = new DomBuilderFactory();
+    }
+    return instance;
+  }
+
+  /**
+   * Created from static factory method.
+   */
+  public DomBuilderFactory() {
+  }
+
+  @Override
+  public DivBuilder createDivBuilder() {
+    return impl().startDiv();
+  }
+
+  @Override
+  public OptionBuilder createOptionBuilder() {
+    return impl().startOption();
+  }
+
+  @Override
+  public SelectBuilder createSelectBuilder() {
+    return impl().startSelect();
+  }
+
+  @Override
+  public ElementBuilder trustedCreate(String tagName) {
+    return impl().trustedStart(tagName);
+  }
+
+  private DomBuilderImpl impl() {
+    return new DomBuilderImpl();
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomBuilderImpl.java b/user/src/com/google/gwt/dom/builder/client/DomBuilderImpl.java
new file mode 100644
index 0000000..7b97fc0
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomBuilderImpl.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.ElementBuilderImpl;
+import com.google.gwt.dom.builder.shared.StylesBuilder;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of methods in
+ * {@link com.google.gwt.dom.builder.shared.ElementBuilderBase} used to render
+ * Elements using DOM manipulation.
+ */
+class DomBuilderImpl extends ElementBuilderImpl {
+
+  /*
+   * Common element builders are created on initialization to avoid null checks.
+   * Less common element builders are created lazily to avoid unnecessary object
+   * creation.
+   */
+  private final DomDivBuilder divElementBuilder = new DomDivBuilder(this);
+  private final DomElementBuilder elementBuilder = new DomElementBuilder(this);
+  private DomOptionBuilder optionElementBuilder;
+  private DomSelectBuilder selectElementBuilder;
+  private final StylesBuilder styleBuilder = new DomStylesBuilder(this);
+
+  private Element root;
+
+  /**
+   * The element at the top of the stack.
+   * 
+   * With normal usage, the current element will be accessed repeatedly to add
+   * attributes and styles. We maintain the current element outside of the stack
+   * to avoid a list access on each operation.
+   */
+  private Element currentElement;
+  private final List<Element> stackElements = new ArrayList<Element>();
+
+  @Override
+  public ElementBuilderBase<?> end() {
+    ElementBuilderBase<?> builder = super.end();
+    popElement();
+    return builder;
+  }
+
+  public DomDivBuilder startDiv() {
+    return start(Document.get().createDivElement(), divElementBuilder);
+  }
+
+  public DomOptionBuilder startOption() {
+    if (optionElementBuilder == null) {
+      optionElementBuilder = new DomOptionBuilder(this);
+    }
+    return start(Document.get().createOptionElement(), optionElementBuilder);
+  }
+
+  public DomSelectBuilder startSelect() {
+    if (selectElementBuilder == null) {
+      selectElementBuilder = new DomSelectBuilder(this);
+    }
+    return start(Document.get().createSelectElement(), selectElementBuilder);
+  }
+
+  @Override
+  public StylesBuilder style() {
+    return styleBuilder;
+  }
+
+  public DomElementBuilder trustedStart(String tagName) {
+    /*
+     * Validate the tag before trying to create the element, or the browser may
+     * throw a JS error and prevent us from triggering an
+     * IllegalArgumentException.
+     */
+    assertValidTagName(tagName);
+    return start(Document.get().createElement(tagName), elementBuilder);
+  }
+
+  @Override
+  protected void doCloseStartTagImpl() {
+    // No-op.
+  }
+
+  @Override
+  protected void doCloseStyleAttributeImpl() {
+    // No-op.
+  }
+
+  @Override
+  protected void doEndTagImpl(String tagName) {
+    // No-op.
+  }
+
+  @Override
+  protected Element doFinishImpl() {
+    return root;
+  }
+
+  @Override
+  protected void doHtmlImpl(SafeHtml html) {
+    getCurrentElement().setInnerHTML(html.asString());
+  }
+
+  @Override
+  protected void doOpenStyleImpl() {
+    // No-op.
+  }
+
+  @Override
+  protected void doTextImpl(String text) {
+    getCurrentElement().setInnerText(text);
+  }
+
+  /**
+   * Assert that the builder is in a state where an attribute can be added.
+   * 
+   * @return the element on which the attribute can be set
+   * @throw {@link IllegalStateException} if the start tag is closed
+   */
+  Element assertCanAddAttribute() {
+    assertCanAddAttributeImpl();
+    return getCurrentElement();
+  }
+
+  /**
+   * Assert that the builder is in a state where a style property can be added.
+   * 
+   * @return the {@link Style} on which the property can be set
+   * @throw {@link IllegalStateException} if the style is not accessible
+   */
+  Style assertCanAddStyleProperty() {
+    assertCanAddStylePropertyImpl();
+    return getCurrentElement().getStyle();
+  }
+
+  /**
+   * Get the element current being built.
+   */
+  Element getCurrentElement() {
+    if (currentElement == null) {
+      throw new IllegalStateException("There are no elements on the stack.");
+    }
+    return currentElement;
+  }
+
+  private void popElement() {
+    Element toRet = getCurrentElement();
+    int itemCount = stackElements.size(); // Greater than or equal to one.
+    stackElements.remove(itemCount - 1);
+    if (itemCount == 1) {
+      currentElement = null;
+    } else {
+      currentElement = stackElements.get(itemCount - 2);
+    }
+  }
+
+  private void pushElement(Element e) {
+    stackElements.add(e);
+    currentElement = e;
+  }
+
+  /**
+   * Start a child element.
+   * 
+   * @param element the element to start
+   * @param builder the builder used to builder the new element
+   */
+  private <B extends ElementBuilderBase<?>> B start(Element element, B builder) {
+    onStart(element.getTagName(), builder);
+
+    // Set the root element.
+    if (root == null) {
+      // This is the new root element.
+      root = element;
+    } else {
+      // Appending to the current element.
+      getCurrentElement().appendChild(element);
+    }
+
+    // Add the element to the stack.
+    pushElement(element);
+
+    return builder;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/dom/builder/client/DomDivBuilder.java b/user/src/com/google/gwt/dom/builder/client/DomDivBuilder.java
new file mode 100644
index 0000000..f0bb3e3
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomDivBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.client.DivElement;
+
+/**
+ * Implementation of {@link DivBuilder}.
+ */
+public class DomDivBuilder extends DomElementBuilderBase<DivBuilder, DivElement>
+    implements DivBuilder {
+
+  DomDivBuilder(DomBuilderImpl delegate) {
+    super(delegate);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomElementBuilder.java b/user/src/com/google/gwt/dom/builder/client/DomElementBuilder.java
new file mode 100644
index 0000000..88d9b76
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomElementBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilder;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * Implementation of {@link ElementBuilder}.
+ */
+public class DomElementBuilder extends DomElementBuilderBase<ElementBuilder, Element> implements
+    ElementBuilder {
+
+  DomElementBuilder(DomBuilderImpl delegate) {
+    super(delegate);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomElementBuilderBase.java b/user/src/com/google/gwt/dom/builder/client/DomElementBuilderBase.java
new file mode 100644
index 0000000..f1d07ba
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomElementBuilderBase.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.OptionBuilder;
+import com.google.gwt.dom.builder.shared.SelectBuilder;
+import com.google.gwt.dom.builder.shared.StylesBuilder;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * Implementation of {@link ElementBuilderBase} that delegates to a
+ * {@link DomBuilderImpl}.
+ * 
+ * <p>
+ * Subclasses of {@link DomElementBuilderBase} operate directly on the
+ * {@link Element} being built.
+ * </p>
+ * 
+ * @param <T> the builder type returned from build methods
+ * @param <E> the {@link Element} type
+ */
+public class DomElementBuilderBase<T extends ElementBuilderBase<?>, E extends Element> implements
+    ElementBuilderBase<T> {
+
+  private final DomBuilderImpl delegate;
+
+  /**
+   * Construct a new {@link DomElementBuilderBase}.
+   * 
+   * @param delegate the delegate that builds the element
+   */
+  DomElementBuilderBase(DomBuilderImpl delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public T attribute(String name, int value) {
+    return attribute(name, String.valueOf(value));
+  }
+
+  @Override
+  public T attribute(String name, String value) {
+    assertCanAddAttribute().setAttribute(name, value);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T className(String className) {
+    assertCanAddAttribute().setClassName(className);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T dir(String dir) {
+    assertCanAddAttribute().setDir(dir);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T draggable(String draggable) {
+    assertCanAddAttribute().setDraggable(draggable);
+    return getReturnBuilder();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B end() {
+    return (B) delegate.end();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B end(String tagName) {
+    return (B) delegate.end(tagName);
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endDiv() {
+    return end("div");
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endOption() {
+    return end("option");
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endSelect() {
+    return end("select");
+  }
+
+  @Override
+  public Element finish() {
+    return delegate.finish();
+  }
+
+  @Override
+  public T html(SafeHtml html) {
+    delegate.html(html);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T id(String id) {
+    assertCanAddAttribute().setId(id);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T lang(String lang) {
+    assertCanAddAttribute().setLang(lang);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public DivBuilder startDiv() {
+    return delegate.startDiv();
+  }
+
+  @Override
+  public OptionBuilder startOption() {
+    return delegate.startOption();
+  }
+
+  @Override
+  public SelectBuilder startSelect() {
+    return delegate.startSelect();
+  }
+
+  @Override
+  public StylesBuilder style() {
+    return delegate.style();
+  }
+
+  @Override
+  public T tabIndex(int tabIndex) {
+    assertCanAddAttribute().setTabIndex(tabIndex);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T text(String text) {
+    delegate.text(text);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public T title(String title) {
+    assertCanAddAttribute().setTitle(title);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public ElementBuilder trustedStart(String tagName) {
+    return delegate.trustedStart(tagName);
+  }
+
+  /**
+   * Assert that the builder is in a state where an attribute can be added.
+   * 
+   * @return the element on which the attribute can be set
+   */
+  protected E assertCanAddAttribute() {
+    return delegate.assertCanAddAttribute().cast();
+  }
+
+  /**
+   * Get the builder to return from build methods.
+   * 
+   * @return the return builder
+   */
+  @SuppressWarnings("unchecked")
+  private T getReturnBuilder() {
+    return (T) this;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomOptionBuilder.java b/user/src/com/google/gwt/dom/builder/client/DomOptionBuilder.java
new file mode 100644
index 0000000..cd5d2a7
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomOptionBuilder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.OptionBuilder;
+import com.google.gwt.dom.client.OptionElement;
+
+/**
+ * Implementation of {@link OptionBuilder}.
+ */
+public class DomOptionBuilder extends
+    DomElementBuilderBase<OptionBuilder, OptionElement> implements OptionBuilder {
+
+  DomOptionBuilder(DomBuilderImpl delegate) {
+    super(delegate);
+  }
+
+  @Override
+  public OptionBuilder defaultSelected() {
+    assertCanAddAttribute().setDefaultSelected(true);
+    return this;
+  }
+
+  @Override
+  public OptionBuilder disabled() {
+    assertCanAddAttribute().setDisabled(true);
+    return this;
+  }
+
+  @Override
+  public OptionBuilder label(String label) {
+    assertCanAddAttribute().setLabel(label);
+    return this;
+  }
+
+  @Override
+  public OptionBuilder selected() {
+    assertCanAddAttribute().setSelected(true);
+    return this;
+  }
+
+  @Override
+  public OptionBuilder value(String value) {
+    assertCanAddAttribute().setValue(value);
+    return this;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomSelectBuilder.java b/user/src/com/google/gwt/dom/builder/client/DomSelectBuilder.java
new file mode 100644
index 0000000..a94f2d6
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomSelectBuilder.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.SelectBuilder;
+import com.google.gwt.dom.client.SelectElement;
+
+/**
+ * Implementation of {@link SelectBuilder}.
+ */
+public class DomSelectBuilder extends
+    DomElementBuilderBase<SelectBuilder, SelectElement> implements SelectBuilder {
+
+  DomSelectBuilder(DomBuilderImpl delegate) {
+    super(delegate);
+  }
+
+  @Override
+  public SelectBuilder size(int size) {
+    assertCanAddAttribute().setSize(size);
+    return this;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/DomStylesBuilder.java b/user/src/com/google/gwt/dom/builder/client/DomStylesBuilder.java
new file mode 100644
index 0000000..8a24120
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/DomStylesBuilder.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.StylesBuilder;
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Float;
+import com.google.gwt.dom.client.Style.FontStyle;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.dom.client.Style.ListStyleType;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.TableLayout;
+import com.google.gwt.dom.client.Style.TextDecoration;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.VerticalAlign;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.safehtml.shared.SafeUri;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builds the style object.
+ */
+class DomStylesBuilder implements StylesBuilder {
+
+  /**
+   * A map of hyphenated style properties to their camelCase equivalents.
+   * 
+   * The set of style property names is limited, and common ones are reused
+   * frequently, so caching saves us from converting every style property name
+   * from hyphenated to camelCase form.
+   */
+  private static Map<String, String> hyphenatedMap;
+
+  /**
+   * Regex to match a word in a hyphenated phrase. A word starts with an a
+   * hyphen or a letter, followed by zero or more characters letters. For
+   * example, in the phrase background-url, the pattern matches "background" and
+   * "-url".
+   */
+  private static RegExp maybeHyphenatedWord;
+
+  /**
+   * Convert a hyphenated or camelCase string to a camelCase string.
+   * 
+   * @param name the hyphenated or camelCase string to convert
+   * @return the hyphenated string
+   */
+  // Visible for testing
+  static String toCamelCaseForm(String name) {
+    // Static initializers.
+    if (hyphenatedMap == null) {
+      hyphenatedMap = new HashMap<String, String>();
+      maybeHyphenatedWord = RegExp.compile("([-]?)([a-z])([a-z0-9]*)", "g");
+    }
+
+    // Early exit if already in camelCase form.
+    if (!name.contains("-")) {
+      return name;
+    }
+
+    // Check for the name in the cache.
+    String camelCase = hyphenatedMap.get(name);
+
+    // Convert the name to camelCase format if not in the cache.
+    if (camelCase == null) {
+      /*
+       * Strip of any leading hyphens, which are used in browser specified style
+       * properties such as "-webkit-border-radius". In this case, the first
+       * word "webkit" should remain lowercase.
+       */
+      if (name.startsWith("-") && name.length() > 1) {
+        name = name.substring(1);
+      }
+
+      camelCase = "";
+      MatchResult matches;
+      while ((matches = maybeHyphenatedWord.exec(name)) != null) {
+        String word = matches.getGroup(0);
+        if (!word.startsWith("-")) {
+          // The word is not hyphenated. Probably the first word.
+          camelCase += word;
+        } else {
+          // Remove hyphen and uppercase next letter.
+          camelCase += matches.getGroup(2).toUpperCase();
+          if (matches.getGroupCount() > 2) {
+            camelCase += matches.getGroup(3);
+          }
+        }
+      }
+      hyphenatedMap.put(name, camelCase);
+    }
+
+    return camelCase;
+  }
+
+  private final DomBuilderImpl delegate;
+
+  /**
+   * Construct a new {@link DomStylesBuilder}.
+   * 
+   * @param delegate the delegate that builds the style
+   */
+  DomStylesBuilder(DomBuilderImpl delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public StylesBuilder backgroundImage(SafeUri uri) {
+    delegate.assertCanAddStyleProperty().setBackgroundImage(uri.asString());
+    return this;
+  }
+
+  @Override
+  public StylesBuilder borderStyle(BorderStyle value) {
+    delegate.assertCanAddStyleProperty().setBorderStyle(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder borderWidth(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setBorderWidth(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder bottom(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setBottom(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder cursor(Cursor value) {
+    delegate.assertCanAddStyleProperty().setCursor(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder display(Display value) {
+    delegate.assertCanAddStyleProperty().setDisplay(value);
+    return this;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B endStyle() {
+    return (B) delegate.endStyle();
+  }
+
+  @Override
+  public StylesBuilder floatprop(Float value) {
+    delegate.assertCanAddStyleProperty().setFloat(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder fontSize(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setFontSize(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder fontStyle(FontStyle value) {
+    delegate.assertCanAddStyleProperty().setFontStyle(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder fontWeight(FontWeight value) {
+    delegate.assertCanAddStyleProperty().setFontWeight(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder height(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setHeight(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder left(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setLeft(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder listStyleType(ListStyleType value) {
+    delegate.assertCanAddStyleProperty().setListStyleType(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder margin(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setMargin(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder marginBottom(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setMarginBottom(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder marginLeft(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setMarginLeft(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder marginRight(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setMarginRight(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder marginTop(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setMarginTop(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder opacity(double value) {
+    delegate.assertCanAddStyleProperty().setOpacity(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder overflow(Overflow value) {
+    delegate.assertCanAddStyleProperty().setOverflow(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder overflowX(Overflow value) {
+    delegate.assertCanAddStyleProperty().setOverflowX(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder overflowY(Overflow value) {
+    delegate.assertCanAddStyleProperty().setOverflowY(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder padding(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setPadding(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder paddingBottom(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setPaddingBottom(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder paddingLeft(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setPaddingLeft(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder paddingRight(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setPaddingRight(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder paddingTop(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setPaddingTop(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder position(Position value) {
+    delegate.assertCanAddStyleProperty().setPosition(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder right(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setRight(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder tableLayout(TableLayout value) {
+    delegate.assertCanAddStyleProperty().setTableLayout(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder textDecoration(TextDecoration value) {
+    delegate.assertCanAddStyleProperty().setTextDecoration(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder top(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setTop(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedBackgroundColor(String value) {
+    delegate.assertCanAddStyleProperty().setBackgroundColor(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedBackgroundImage(String value) {
+    delegate.assertCanAddStyleProperty().setBackgroundImage(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedBorderColor(String value) {
+    delegate.assertCanAddStyleProperty().setBorderColor(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedColor(String value) {
+    delegate.assertCanAddStyleProperty().setColor(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedProperty(String name, double value, Unit unit) {
+    name = toCamelCaseForm(name);
+    delegate.assertCanAddStyleProperty().setProperty(name, value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder trustedProperty(String name, String value) {
+    name = toCamelCaseForm(name);
+    delegate.assertCanAddStyleProperty().setProperty(name, value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder verticalAlign(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setVerticalAlign(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder verticalAlign(VerticalAlign value) {
+    delegate.assertCanAddStyleProperty().setVerticalAlign(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder visibility(Visibility value) {
+    delegate.assertCanAddStyleProperty().setVisibility(value);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder width(double value, Unit unit) {
+    delegate.assertCanAddStyleProperty().setWidth(value, unit);
+    return this;
+  }
+
+  @Override
+  public StylesBuilder zIndex(int value) {
+    delegate.assertCanAddStyleProperty().setZIndex(value);
+    return this;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/client/package-info.java b/user/src/com/google/gwt/dom/builder/client/package-info.java
new file mode 100644
index 0000000..efb817a
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/client/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * Classes used to build DOM elements.
+ *
+ * @since GWT 2.4
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.dom.builder.client;
diff --git a/user/src/com/google/gwt/dom/builder/shared/DivBuilder.java b/user/src/com/google/gwt/dom/builder/shared/DivBuilder.java
new file mode 100644
index 0000000..4ee444d
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/DivBuilder.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Builds a div element.
+ */
+public interface DivBuilder extends ElementBuilderBase<DivBuilder> {
+
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/ElementBuilder.java b/user/src/com/google/gwt/dom/builder/shared/ElementBuilder.java
new file mode 100644
index 0000000..75d5153
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/ElementBuilder.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Builds an element.
+ */
+public interface ElementBuilder extends ElementBuilderBase<ElementBuilder> {
+
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/ElementBuilderBase.java b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderBase.java
new file mode 100644
index 0000000..9797326
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderBase.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * Base class for element builders used to builder DOM elements.
+ * 
+ * @param <T> the builder type returns from build methods
+ */
+public interface ElementBuilderBase<T extends ElementBuilderBase<?>> {
+
+  /**
+   * Add an integer attribute to the object.
+   * 
+   * @return this builder
+   */
+  T attribute(String name, int value);
+
+  /**
+   * Add a string attribute to the object.
+   * 
+   * @return this builder
+   */
+  T attribute(String name, String value);
+
+  /**
+   * The class attribute of the element. This attribute has been renamed due to
+   * conflicts with the "class" keyword exposed by many languages.
+   * 
+   * @return this builder
+   * @see <a
+   *      href="http://www.w3.org/TR/1999/REC-html401-19991224/struct/global.html#adef-class">W3C
+   *      HTML Specification</a>
+   */
+  T className(String className);
+
+  /**
+   * Specifies the base direction of directionally neutral text and the
+   * directionality of tables.
+   * 
+   * @return this builder
+   */
+  T dir(String dir);
+
+  /**
+   * Changes the draggable attribute to one of {@link Element#DRAGGABLE_AUTO},
+   * {@link Element#DRAGGABLE_FALSE}, or {@link Element#DRAGGABLE_TRUE}.
+   * 
+   * @param draggable a String constant
+   * @return this builder
+   */
+  T draggable(String draggable);
+
+  /**
+   * End the current element without checking its type.
+   * 
+   * <p>
+   * By default, this method returns the {@link ElementBuilderBase} instance for
+   * the parent element, or null if this is the root element.
+   * </p>
+   * 
+   * <pre>
+   * DivBuilder div = ElementBuilderFactory.get().createDivBuilder();
+   * SelectBuilder select = div.startSelect();
+   * ElementBuilderBase&lt;?&gt; sameAs_div = select.end();
+   * </pre>
+   * 
+   * <p>
+   * You can cast the return type by parameterizing the return value. If the
+   * parameterized type does not match the builder type of this element's
+   * parent, a {@link ClassCastException} is thrown.
+   * </p>
+   * 
+   * <pre>
+   * DivBuilder div = ElementBuilderFactory.get().createDivBuilder();
+   * SelectBuilder select = div.startSelect();
+   * DivBuilder sameAs_div = select.&lt;DivBuilder&gt;end();
+   * </pre>
+   * 
+   * @param <B> the type of the parent element
+   * @return the {@link ElementBuilderBase} for the parent element, or null if
+   *         the current element does not have a parent
+   * @throws ClassCastException if the return type does not match the builder
+   *           type of this element's parent
+   */
+  <B extends ElementBuilderBase<?>> B end();
+
+  /**
+   * End the current element after checking that its tag is the specified
+   * tagName.
+   * 
+   * @param tagName the expected tagName of the current element
+   * @return the {@link ElementBuilderBase} for the parent element, or null if
+   *         the current element does not have a parent
+   * @throws IllegalStateException if the current element does not match the
+   *           expected tagName
+   * @throws ClassCastException if the parent builder does not match the
+   *           specified class
+   * @see #end()
+   */
+  <B extends ElementBuilderBase<?>> B end(String tagName);
+
+  /**
+   * End the current element.
+   * 
+   * @param <B> the type of the parent element
+   * @return the {@link ElementBuilderBase} for the parent element, or null if
+   *         the current element does not have a parent
+   * @throws IllegalStateException if the current element has the wrong tag
+   * @throws ClassCastException if the parent builder does not match the
+   *           specified class
+   * @see #end()
+   */
+  <B extends ElementBuilderBase<?>> B endDiv();
+
+  /**
+   * End the current element.
+   * 
+   * @param <B> the type of the parent element
+   * @return the {@link ElementBuilderBase} for the parent element, or null if
+   *         the current element does not have a parent
+   * @throws IllegalStateException if the current element has the wrong tag
+   * @throws ClassCastException if the parent builder does not match the
+   *           specified class
+   * @see #end()
+   */
+  <B extends ElementBuilderBase<?>> B endOption();
+
+  /**
+   * End the current element.
+   * 
+   * @param <B> the type of the parent element
+   * @return the {@link ElementBuilderBase} for the parent element, or null if
+   *         the current element does not have a parent
+   * @throws IllegalStateException if the current element has the wrong tag
+   * @throws ClassCastException if the parent builder does not match the
+   *           specified class
+   * @see #end()
+   */
+  <B extends ElementBuilderBase<?>> B endSelect();
+
+  /**
+   * Return the built DOM as an {@link Element}.
+   * 
+   * <p>
+   * Any lingering open elements are automatically closed. Once you call
+   * {@link #finish()}, you can not longer call any other methods in this class.
+   * </p>
+   * 
+   * @return the {@link Element} that was built
+   * @throws IllegalStateException if called twice
+   */
+  Element finish();
+
+  /**
+   * Append html within the node.
+   * 
+   * <p>
+   * Once you append HTML to the element, you can no longer set attributes.
+   * 
+   * @param html the HTML to append
+   * @return this builder
+   */
+  T html(SafeHtml html);
+
+  /**
+   * Set the id.
+   * 
+   * @param id the id
+   * @return this builder
+   */
+  T id(String id);
+
+  /**
+   * Language code defined in RFC 1766.
+   * 
+   * @return this builder
+   */
+  T lang(String lang);
+
+  /**
+   * Append a div element.
+   * 
+   * @return the builder for the new element
+   */
+  DivBuilder startDiv();
+
+  /**
+   * Append an option element.
+   * 
+   * @return the builder for the new element
+   */
+  OptionBuilder startOption();
+
+  /**
+   * Append a select element.
+   * 
+   * @return the builder for the new element
+   */
+  SelectBuilder startSelect();
+
+  /**
+   * Start the {@link StylesBuilder} used to add style properties to the style
+   * attribute of the current element.
+   * 
+   * @return the {@link StylesBuilder}
+   */
+  StylesBuilder style();
+
+  /**
+   * Set the tab index.
+   * 
+   * @param tabIndex the tab index
+   * @return this builder
+   */
+  T tabIndex(int tabIndex);
+
+  /**
+   * Append text within the node.
+   * 
+   * <p>
+   * Once you append text to the element, you can no longer set attributes.
+   * </p>
+   * 
+   * <p>
+   * A string-based implementation will escape the text to prevent
+   * HTML/javascript code from executing. DOM based implementations are not
+   * required to escape the text if they directly set the innerText of an
+   * element.
+   * </p>
+   * 
+   * @param text the text to append
+   * @return this builder
+   */
+  T text(String text);
+
+  /**
+   * The element's advisory title.
+   * 
+   * @return this builder
+   */
+  T title(String title);
+
+  /**
+   * Append a new element with the specified trusted tag name. The tag name will
+   * will not be checked or escaped. The calling code should be carefully
+   * reviewed to ensure that the provided tag name will not cause a security
+   * issue if including in an HTML document. In general, this means limiting the
+   * code to HTML tagName constants supported by the HTML specification.
+   * 
+   * @param tagName the tag name
+   * @return the {@link ElementBuilder} for the new element
+   */
+  ElementBuilder trustedStart(String tagName);
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/ElementBuilderFactory.java b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderFactory.java
new file mode 100644
index 0000000..4126a2d
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderFactory.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Factory for creating element builders.
+ * 
+ * <p>
+ * Use {@link ElementBuilderFactory#get()} to fetch the builder factory
+ * optimized for the browser platform.
+ * </p>
+ * 
+ * <p>
+ * If you are using the builder on a server, use
+ * {@link HtmlBuilderFactory#get()} instead. {@link HtmlBuilderFactory} can
+ * construct a {@link com.google.gwt.safehtml.shared.SafeHtml} string and will
+ * work on the server. Other implementations may only work on a browser client.
+ * </p>
+ * 
+ * <p>
+ * Element builder methods can be chained together as with a traditional
+ * builder:
+ * </p>
+ * 
+ * <pre>
+ * DivBuilder divBuilder = ElementBuilderFactory.get().createDivBuilder();
+ * divBuilder.id("myId").text("Hello World!").endDiv();
+ * </pre>
+ * 
+ * <p>
+ * See this example: {@example
+ * com.google.gwt.examples.dom.builder.ElementBuilderFactoryChainingExample}.
+ * </p>
+ * 
+ * <p>
+ * Alternatively, builders can be used as separate objects and operated on
+ * individually. This may be the preferred method if you are creating a complex
+ * or dynamic element. The code below produces the same output as the code
+ * above.
+ * </p>
+ * 
+ * <pre>
+ * DivBuilder divBuilder = ElementBuilderFactory.get().createDivBuilder();
+ * divBuilder.id("myId");
+ * divBuilder.text("Hello World!");
+ * divBuilder.endDiv();
+ * </pre>
+ * 
+ * <p>
+ * See an example: {@example
+ * com.google.gwt.examples.dom.builder.ElementBuilderFactoryNonChainingExample}.
+ * </p>
+ * 
+ * <p>
+ * You can also mix chaining and non-chaining methods when appropriate. For
+ * example, you can add attributes to an element by chaining methods, but use a
+ * separate builder object for each separate element.
+ * </p>
+ * 
+ * <p>
+ * NOTE: Builders always operate on the current element. For example, in the
+ * code below, we create two divBuilders, one a child of the other. However,
+ * they are actually the same builder instance! Implementations of
+ * ElementBuilderFactory use a single instance of each builder type to improve
+ * performance. The implication is that all element builders operate on the
+ * current element, so the call to <code>divBuilder0.id("div1")</code> will set
+ * the "id" of the child div, and is functionally equivalent to
+ * <code>divBuilder1.id("div1")</code>. Its important to always call end()
+ * before resuming work on the previous element builder.
+ * </p>
+ * 
+ * <pre>
+ * DivBuilder divBuilder0 = ElementBuilderFactory.get().createDivBuilder();
+ * DivBuilder divBuilder1 = divBuilder0.startDiv();
+ * divBuilder0.id("div1"); // Operates on the first element!
+ * </pre>
+ */
+public abstract class ElementBuilderFactory {
+
+  private static ElementBuilderFactory instance;
+
+  /**
+   * Get the instance of the {@link ElementBuilderFactory}.
+   * 
+   * @return the {@link ElementBuilderFactory}
+   */
+  public static ElementBuilderFactory get() {
+    if (instance == null) {
+      if (GWT.isClient()) {
+        instance = GWT.create(ElementBuilderFactory.class);
+      } else {
+        // The DOM implementation will not work on the server.
+        instance = HtmlBuilderFactory.get();
+      }
+    }
+    return instance;
+  }
+
+  /**
+   * Created from static factory method.
+   */
+  protected ElementBuilderFactory() {
+  }
+
+  public abstract DivBuilder createDivBuilder();
+
+  public abstract OptionBuilder createOptionBuilder();
+
+  public abstract SelectBuilder createSelectBuilder();
+
+  /**
+   * Create an {@link ElementBuilder} for an arbitrary tag name. The tag name
+   * will will not be checked or escaped. The calling code should be carefully
+   * reviewed to ensure that the provided tag name will not cause a security
+   * issue if including in an HTML document. In general, this means limiting the
+   * code to HTML tagName constants supported by the HTML specification.
+   * 
+   * 
+   * @param tagName the tag name of the new element
+   * @return an {@link ElementBuilder} used to build the element
+   */
+  public abstract ElementBuilder trustedCreate(String tagName);
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/ElementBuilderImpl.java b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderImpl.java
new file mode 100644
index 0000000..bc25b5a
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/ElementBuilderImpl.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility implementation of {@link ElementBuilderBase} that handles state, but
+ * nothing else.
+ * 
+ * <p>
+ * DO NOT USE THIS CLASS. This class is an implementation class and may change
+ * in the future.
+ * </p>
+ * 
+ * This class is used to ensure that the HTML and DOM implementations throw the
+ * same exceptions, even if something is valid in one and not the other. For
+ * example, the DOM implementation supports changing an attribute after setting
+ * inner HTML, but the HTML version does not, so they should both throw an
+ * error. Otherwise, they would not be swappable.
+ */
+public abstract class ElementBuilderImpl {
+
+  /**
+   * A regex for matching valid HTML tags.
+   */
+  private static RegExp HTML_TAG_REGEX;
+
+  private boolean asElementCalled;
+
+  /**
+   * True if the top most element has not yet been added.
+   */
+  private boolean isEmpty = true;
+
+  /**
+   * True if HTML of text has been added.
+   */
+  private boolean isHtmlOrTextAdded;
+
+  /**
+   * True if the start tag is still open (open bracket '&lt;' but no close
+   * bracket '&gt;').
+   */
+  private boolean isStartTagOpen;
+
+  /**
+   * True if the style attribute has been opened and closed.
+   */
+  private boolean isStyleClosed;
+
+  /**
+   * True if the style attribute is open.
+   */
+  private boolean isStyleOpen;
+
+  /**
+   * The stack of element builders.
+   */
+  private final List<ElementBuilderBase<?>> stackBuilders = new ArrayList<ElementBuilderBase<?>>();
+
+  /**
+   * The stack of tag names.
+   */
+  private final List<String> stackTags = new ArrayList<String>();
+
+  public ElementBuilderImpl() {
+    if (HTML_TAG_REGEX == null) {
+      // Starts with a-z, plus 0 or more alphanumeric values (case insensitive).
+      HTML_TAG_REGEX = RegExp.compile("^[a-z][a-z0-9]*$", "i");
+    }
+  }
+
+  public ElementBuilderBase<?> end() {
+    // Get the top item (also verifies there is a top item).
+    String tagName = getCurrentTagName();
+
+    // Close the start tag.
+    maybeCloseStartTag();
+
+    /*
+     * End the tag. The tag name is safe because it comes from the stack, and
+     * tag names are checked before they are added to the stack.
+     */
+    doEndTagImpl(tagName);
+
+    // Popup the item off the top of the stack.
+    isStartTagOpen = false; // Closed because this element was added.
+    isStyleClosed = true; // Too late to add styles.
+    stackTags.remove(stackTags.size() - 1);
+    stackBuilders.remove(stackBuilders.size() - 1);
+
+    /*
+     * If this element was added, then we did not add html or text to the
+     * parent.
+     */
+    isHtmlOrTextAdded = false;
+
+    return getCurrentBuilder();
+  }
+
+  public ElementBuilderBase<?> end(String tagName) {
+    // Verify the tag name matches the expected tag.
+    String topItem = getCurrentTagName();
+    if (!topItem.equalsIgnoreCase(tagName)) {
+      throw new IllegalStateException("Specified tag \"" + tagName
+          + "\" does not match the current element \"" + topItem + "\"");
+    }
+
+    // End the element.
+    return end();
+  }
+
+  public ElementBuilderBase<?> endStyle() {
+    if (!isStyleOpen) {
+      throw new IllegalStateException(
+          "Attempting to close a style attribute, but the style attribute isn't open");
+    }
+    maybeCloseStyleAttribute();
+    return getCurrentBuilder();
+  }
+
+  /**
+   * Return the built DOM as an {@link Element}.
+   * 
+   * @return the {@link Element} that was built
+   */
+  public Element finish() {
+    if (!GWT.isClient()) {
+      throw new RuntimeException("asElement() can only be called from GWT client code.");
+    }
+    if (asElementCalled) {
+      throw new IllegalStateException("asElement() can only be called once.");
+    }
+    asElementCalled = true;
+
+    // End all open tags.
+    endAllTags();
+
+    return doFinishImpl();
+  }
+
+  public void html(SafeHtml html) {
+    assertStartTagOpen("html cannot be set on an element that already "
+        + "contains other content or elements.");
+    maybeCloseStartTag();
+    isHtmlOrTextAdded = true;
+    doHtmlImpl(html);
+  }
+
+  public void onStart(String tagName, ElementBuilderBase<?> builder) {
+    // Check that we aren't creating another top level element.
+    if (isEmpty) {
+      isEmpty = false;
+    } else if (stackTags.size() == 0) {
+      throw new IllegalStateException("You can only build one top level element.");
+    }
+
+    // Check that asElement hasn't already been called.
+    if (isHtmlOrTextAdded) {
+      throw new IllegalStateException("Cannot append an element after setting text of html.");
+    }
+
+    // Validate the tagName.
+    assertValidTagName(tagName);
+
+    maybeCloseStartTag();
+    stackTags.add(tagName);
+    stackBuilders.add(builder);
+    isStartTagOpen = true;
+    isStyleOpen = false;
+    isStyleClosed = false;
+    isHtmlOrTextAdded = false;
+  }
+
+  /**
+   * Get the {@link StylesBuilder} used to add style properties to the current
+   * element.
+   * 
+   * @return a {@link StylesBuilder}
+   */
+  public abstract StylesBuilder style();
+
+  public void text(String text) {
+    assertStartTagOpen("text cannot be set on an element that already "
+        + "contains other content or elements.");
+    maybeCloseStartTag();
+    isHtmlOrTextAdded = true;
+    doTextImpl(text);
+  }
+
+  /**
+   * Assert that the builder is in a state where an attribute can be added.
+   * 
+   * @throw {@link IllegalStateException} if the start tag is closed
+   */
+  protected void assertCanAddAttributeImpl() {
+    assertStartTagOpen("Attributes cannot be added after appending HTML or adding a child element.");
+    maybeCloseStyleAttribute();
+  }
+
+  /**
+   * Assert that a style property can be added, and setup the state as if one is
+   * about to be added.
+   */
+  protected void assertCanAddStylePropertyImpl() {
+    assertStartTagOpen("Style properties cannot be added after appending HTML or adding a child "
+        + "element.");
+
+    // Check if a style attribute already exists.
+    if (isStyleClosed) {
+      throw new IllegalStateException(
+          "Style properties must be added at the same time. If you already added style properties,"
+              + " you cannot add more after adding non-style attributes.");
+    }
+
+    // Open the style attribute.
+    if (!isStyleOpen) {
+      isStyleOpen = true;
+      doOpenStyleImpl();
+    }
+  }
+
+  /**
+   * Assert that the specified tag name is valid.
+   * 
+   * @throws IllegalArgumentException if not valid
+   */
+  protected void assertValidTagName(String tagName) {
+    if (!HTML_TAG_REGEX.test(tagName)) {
+      throw new IllegalArgumentException("The specified tag name is invalid: " + tagName);
+    }
+  }
+
+  /**
+   * Close the start tag.
+   */
+  protected abstract void doCloseStartTagImpl();
+
+  /**
+   * Close the style attribute.
+   */
+  protected abstract void doCloseStyleAttributeImpl();
+
+  /**
+   * End the specified tag.
+   * 
+   * @param tagName the name of the tag to end
+   */
+  protected abstract void doEndTagImpl(String tagName);
+
+  /**
+   * Return the build element.
+   * 
+   * @return the element
+   */
+  protected abstract Element doFinishImpl();
+
+  /**
+   * Set the specified html as the inner HTML of the current element.
+   * 
+   * @param html the HTML to set
+   */
+  protected abstract void doHtmlImpl(SafeHtml html);
+
+  /**
+   * Open the style attribute.
+   */
+  protected abstract void doOpenStyleImpl();
+
+  /**
+   * Set the specified text as the inner text of the current element.
+   * 
+   * @param text the text to set
+   */
+  protected abstract void doTextImpl(String text);
+
+  /**
+   * End all open tags, including the root element.
+   * 
+   * <p>
+   * Doing so also ensures that all builder methods will throw an exception
+   * because the stack is empty, and a new element cannot be started.
+   * </p>
+   */
+  protected void endAllTags() {
+    while (!stackTags.isEmpty()) {
+      end();
+    }
+  }
+
+  /**
+   * Assert that the start tag is still open.
+   * 
+   * @param message the error message if the start tag is not open
+   * @throw {@link IllegalStateException} if the start tag is closed
+   */
+  private void assertStartTagOpen(String message) {
+    if (!isStartTagOpen) {
+      throw new IllegalStateException(message);
+    }
+  }
+
+  /**
+   * Get the builder at the top of the stack.
+   * 
+   * @return an {@link ElementBuilderBase}, or null if there are non left
+   */
+  private ElementBuilderBase<?> getCurrentBuilder() {
+    int stackSize = stackBuilders.size();
+    return (stackSize == 0) ? null : stackBuilders.get(stackSize - 1);
+  }
+
+  /**
+   * Get the tag name of the element at the top of the stack.
+   * 
+   * @return a tag name
+   * @throws IllegalStateException if there are no elements on the stack
+   */
+  private String getCurrentTagName() {
+    // Verify there is something on the stack.
+    int stackSize = stackTags.size();
+    if (stackSize == 0) {
+      throw new IllegalStateException("There are no elements on the stack.");
+    }
+
+    return stackTags.get(stackSize - 1);
+  }
+
+  /**
+   * Close the start tag if it is still open.
+   */
+  private void maybeCloseStartTag() {
+    maybeCloseStyleAttribute();
+    if (isStartTagOpen) {
+      isStartTagOpen = false;
+      doCloseStartTagImpl();
+    }
+  }
+
+  /**
+   * Close the style attribute if it is still open.
+   */
+  private void maybeCloseStyleAttribute() {
+    if (isStyleOpen) {
+      isStyleOpen = false;
+      isStyleClosed = true;
+      doCloseStyleAttributeImpl();
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderFactory.java b/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderFactory.java
new file mode 100644
index 0000000..e044370
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Factory for creating element builders that use string concatenation to
+ * generate HTML.
+ */
+public class HtmlBuilderFactory extends ElementBuilderFactory {
+
+  private static HtmlBuilderFactory instance;
+
+  /**
+   * Get the instance of the {@link HtmlBuilderFactory}.
+   * 
+   * <p>
+   * Use {@link ElementBuilderFactory#get()} to fetch a factory optimized for
+   * the browser client. However, you can use this factory directly if you want
+   * to force the builders to builder elements using HTML string concatenation
+   * and innerHTML. You can also use this factory if you want access to the HTML
+   * string, such as when you are building HTML on a server.
+   * </p>
+   * 
+   * @return the {@link ElementBuilderFactory}
+   */
+  public static HtmlBuilderFactory get() {
+    if (instance == null) {
+      instance = new HtmlBuilderFactory();
+    }
+    return instance;
+  }
+
+  /**
+   * Created from static factory method.
+   */
+  protected HtmlBuilderFactory() {
+  }
+
+  @Override
+  public HtmlDivBuilder createDivBuilder() {
+    return impl().startDiv();
+  }
+
+  @Override
+  public HtmlOptionBuilder createOptionBuilder() {
+    return impl().startOption();
+  }
+
+  @Override
+  public HtmlSelectBuilder createSelectBuilder() {
+    return impl().startSelect();
+  }
+
+  @Override
+  public HtmlElementBuilder trustedCreate(String tagName) {
+    return impl().trustedStart(tagName);
+  }
+
+  private HtmlBuilderImpl impl() {
+    return new HtmlBuilderImpl();
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderImpl.java b/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderImpl.java
new file mode 100644
index 0000000..e21686d
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlBuilderImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safecss.shared.SafeStyles;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * Implementation of methods in {@link ElementBuilderBase} used to render HTML
+ * as a string, using innerHtml to generate an element.
+ */
+class HtmlBuilderImpl extends ElementBuilderImpl {
+
+  /*
+   * Common element builders are created on initialization to avoid null checks.
+   * Less common element builders are created lazily to avoid unnecessary object
+   * creation.
+   */
+  private final HtmlDivBuilder divElementBuilder = new HtmlDivBuilder(this);
+  private final HtmlElementBuilder elementBuilder = new HtmlElementBuilder(this);
+  private HtmlOptionBuilder optionElementBuilder;
+  private HtmlSelectBuilder selectElementBuilder;
+  private final StylesBuilder styleBuilder = new HtmlStylesBuilder(this);
+
+  /**
+   * Used to builder the HTML string. We cannot use
+   * {@link com.google.gwt.safehtml.shared.SafeHtmlBuilder} because it does some
+   * rudimentary checks that the HTML tags are complete. Instead, we escape
+   * values before appending them.
+   */
+  private final StringBuilder sb = new StringBuilder();
+
+  /**
+   * Return the HTML as a {@link SafeHtml} string.
+   */
+  public SafeHtml asSafeHtml() {
+    // End all open tags.
+    endAllTags();
+
+    /*
+     * sb is trusted because we only append trusted strings or escaped strings
+     * to it.
+     */
+    return SafeHtmlUtils.fromTrustedString(sb.toString());
+  }
+
+  public void attribute(String name, String value) {
+    assertCanAddAttributeImpl();
+    sb.append(" ").append(escape(name)).append("=\"").append(escape(value)).append("\"");
+  }
+
+  public HtmlDivBuilder startDiv() {
+    return trustedStart("div", divElementBuilder);
+  }
+
+  public HtmlOptionBuilder startOption() {
+    if (optionElementBuilder == null) {
+      optionElementBuilder = new HtmlOptionBuilder(this);
+    }
+    return trustedStart("option", optionElementBuilder);
+  }
+
+  public HtmlSelectBuilder startSelect() {
+    if (selectElementBuilder == null) {
+      selectElementBuilder = new HtmlSelectBuilder(this);
+    }
+    return trustedStart("select", selectElementBuilder);
+  }
+
+  @Override
+  public StylesBuilder style() {
+    return styleBuilder;
+  }
+
+  public StylesBuilder styleProperty(SafeStyles style) {
+    assertCanAddStylePropertyImpl();
+    sb.append(style.asString());
+    return style();
+  }
+
+  public HtmlElementBuilder trustedStart(String tagName) {
+    return trustedStart(tagName, elementBuilder);
+  }
+
+  @Override
+  protected void doCloseStartTagImpl() {
+    sb.append(">");
+  }
+
+  @Override
+  protected void doCloseStyleAttributeImpl() {
+    sb.append("\"");
+  }
+
+  @Override
+  protected void doEndTagImpl(String tagName) {
+    /*
+     * Add an end tag.
+     * 
+     * Some browsers do not behave correctly if you self close (ex <select />)
+     * certain tags, so we always add the end tag.
+     * 
+     * The tag name is safe because it comes from the stack, and tag names are
+     * checked before they are added to the stack.
+     */
+    sb.append("</").append(tagName).append(">");
+  }
+
+  @Override
+  protected Element doFinishImpl() {
+    Element tmp = Document.get().createDivElement();
+    tmp.setInnerHTML(asSafeHtml().asString());
+    return tmp.getFirstChildElement();
+  }
+
+  @Override
+  protected void doHtmlImpl(SafeHtml html) {
+    sb.append(html.asString());
+  }
+
+  @Override
+  protected void doOpenStyleImpl() {
+    sb.append(" style=\"");
+  }
+
+  @Override
+  protected void doTextImpl(String text) {
+    sb.append(escape(text));
+  }
+
+  /**
+   * Escape a string.
+   * 
+   * @param s the string to escape
+   */
+  private String escape(String s) {
+    return SafeHtmlUtils.htmlEscape(s);
+  }
+
+  /**
+   * Start a tag using the specified builder. The tagName is not checked or
+   * escaped.
+   * 
+   * @return the builder
+   */
+  private <B extends ElementBuilderBase<?>> B trustedStart(String tagName, B builder) {
+    onStart(tagName, builder);
+    sb.append("<").append(tagName);
+    return builder;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlDivBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlDivBuilder.java
new file mode 100644
index 0000000..7e0fa3a
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlDivBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Implementation of {@link DivBuilder}.
+ */
+public class HtmlDivBuilder extends HtmlElementBuilderBase<DivBuilder> implements
+    DivBuilder {
+
+  HtmlDivBuilder(HtmlBuilderImpl delegate) {
+    super(delegate);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilder.java
new file mode 100644
index 0000000..b0f538f
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Implementation of {@link ElementBuilder}.
+ */
+public class HtmlElementBuilder extends HtmlElementBuilderBase<ElementBuilder> implements
+    ElementBuilder {
+
+  HtmlElementBuilder(HtmlBuilderImpl delegate) {
+    super(delegate);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilderBase.java b/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilderBase.java
new file mode 100644
index 0000000..fcc9f99
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlElementBuilderBase.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * Implementation of {@link ElementBuilderBase} that delegates to an
+ * {@link HtmlBuilderImpl}.
+ * 
+ * <p>
+ * Subclasses of {@link HtmlElementBuilderBase} act as typed wrappers around a
+ * shared {@link ElementBuilderBase} implementation that handles the actual
+ * building. The wrappers merely delegate to the shared implementation, so
+ * wrapper instances can be reused, avoiding object creation. This approach is
+ * necessary so that the return value of common methods, such as
+ * {@link #id(String)}, return a typed builder instead of the generic
+ * {@link ElementBuilderBase}.
+ * </p>
+ * 
+ * @param <R> the builder type returned from build methods
+ */
+public class HtmlElementBuilderBase<R extends ElementBuilderBase<?>> implements
+    ElementBuilderBase<R> {
+
+  private final HtmlBuilderImpl delegate;
+
+  /**
+   * Construct a new {@link HtmlElementBuilderBase}.
+   * 
+   * @param delegate the delegate that builds the element
+   */
+  HtmlElementBuilderBase(HtmlBuilderImpl delegate) {
+    this.delegate = delegate;
+  }
+
+  /**
+   * Return the HTML as a {@link SafeHtml} string.
+   */
+  public SafeHtml asSafeHtml() {
+    return delegate.asSafeHtml();
+  }
+
+  @Override
+  public R attribute(String name, int value) {
+    return attribute(name, String.valueOf(value));
+  }
+
+  @Override
+  public R attribute(String name, String value) {
+    delegate.attribute(name, value);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public R className(String className) {
+    return attribute("class", className);
+  }
+
+  @Override
+  public R dir(String dir) {
+    return attribute("dir", dir);
+  }
+
+  @Override
+  public R draggable(String draggable) {
+    return attribute("draggable", draggable);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B end() {
+    return (B) delegate.end();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B end(String tagName) {
+    return (B) delegate.end(tagName);
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endDiv() {
+    return end("div");
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endOption() {
+    return end("option");
+  }
+
+  @Override
+  public <B extends ElementBuilderBase<?>> B endSelect() {
+    return end("select");
+  }
+
+  @Override
+  public Element finish() {
+    return delegate.finish();
+  }
+
+  @Override
+  public R html(SafeHtml html) {
+    delegate.html(html);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public R id(String id) {
+    return attribute("id", id);
+  }
+
+  @Override
+  public R lang(String lang) {
+    return attribute("lang", lang);
+  }
+
+  @Override
+  public DivBuilder startDiv() {
+    return delegate.startDiv();
+  }
+
+  @Override
+  public OptionBuilder startOption() {
+    return delegate.startOption();
+  }
+
+  @Override
+  public SelectBuilder startSelect() {
+    return delegate.startSelect();
+  }
+
+  @Override
+  public StylesBuilder style() {
+    return delegate.style();
+  }
+
+  @Override
+  public R tabIndex(int tabIndex) {
+    return attribute("tabIndex", tabIndex);
+  }
+
+  @Override
+  public R text(String text) {
+    delegate.text(text);
+    return getReturnBuilder();
+  }
+
+  @Override
+  public R title(String title) {
+    return attribute("title", title);
+  }
+
+  @Override
+  public ElementBuilder trustedStart(String tagName) {
+    return delegate.trustedStart(tagName);
+  }
+
+  /**
+   * Get the builder to return from build methods.
+   * 
+   * @return the return builder
+   */
+  @SuppressWarnings("unchecked")
+  private R getReturnBuilder() {
+    return (R) this;
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlOptionBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlOptionBuilder.java
new file mode 100644
index 0000000..a48f5e4
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlOptionBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Implementation of {@link OptionBuilder}.
+ */
+public class HtmlOptionBuilder extends HtmlElementBuilderBase<OptionBuilder>
+    implements OptionBuilder {
+
+  HtmlOptionBuilder(HtmlBuilderImpl delegate) {
+    super(delegate);
+  }
+
+  @Override
+  public OptionBuilder defaultSelected() {
+    return attribute("defaultSelected", "defaultSelected");
+  }
+
+  @Override
+  public OptionBuilder disabled() {
+    return attribute("disabled", "disabled");
+  }
+
+  @Override
+  public OptionBuilder label(String label) {
+    return attribute("label", label);
+  }
+
+  @Override
+  public OptionBuilder selected() {
+    return attribute("selected", "selected");
+  }
+
+  @Override
+  public OptionBuilder value(String value) {
+    return attribute("value", value);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlSelectBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlSelectBuilder.java
new file mode 100644
index 0000000..9b46ed2
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlSelectBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Implementation of {@link SelectBuilder}.
+ */
+public class HtmlSelectBuilder extends HtmlElementBuilderBase<SelectBuilder>
+    implements SelectBuilder {
+
+  HtmlSelectBuilder(HtmlBuilderImpl delegate) {
+    super(delegate);
+  }
+
+  @Override
+  public SelectBuilder size(int size) {
+    return attribute("size", size);
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlStylesBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlStylesBuilder.java
new file mode 100644
index 0000000..29b87e2
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlStylesBuilder.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Float;
+import com.google.gwt.dom.client.Style.FontStyle;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.dom.client.Style.ListStyleType;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.TableLayout;
+import com.google.gwt.dom.client.Style.TextDecoration;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.VerticalAlign;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.safecss.shared.SafeStylesUtils;
+import com.google.gwt.safehtml.shared.SafeUri;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builds the style object.
+ */
+class HtmlStylesBuilder implements StylesBuilder {
+
+  /**
+   * A map of camelCase style properties to their hyphenated equivalents.
+   * 
+   * The set of style property names is limited, and common ones are reused
+   * frequently, so caching saves us from converting every style property name
+   * from camelCase to hyphenated form.
+   */
+  private static Map<String, String> camelCaseMap;
+
+  /**
+   * Regex to match a word in a camelCase phrase. A word starts with an
+   * uppercase or lowercase letter, followed by zero or more non-uppercase
+   * letters. For example, in the camelCase phrase backgroundUrl, the pattern
+   * matches "background" and "Url".
+   * 
+   * This pattern is not used to validate the style property name.
+   * {@link SafeStylesUtils} performs a more detailed check.
+   */
+  private static RegExp camelCaseWord;
+
+  /**
+   * Regex to match a word that starts with an uppercase letter.
+   */
+  private static RegExp caseWord;
+
+  /**
+   * Convert a camelCase or hyphenated string to a hyphenated string.
+   * 
+   * @param name the camelCase or hyphenated string to convert
+   * @return the hyphenated string
+   */
+  // Visible for testing
+  static String toHyphenatedForm(String name) {
+    // Static initializers.
+    if (camelCaseWord == null) {
+      camelCaseMap = new HashMap<String, String>();
+      camelCaseWord = RegExp.compile("([A-Za-z])([^A-Z]*)", "g");
+      caseWord = RegExp.compile("[A-Z]([^A-Z]*)");
+    }
+
+    // Early exit if already in hyphenated form.
+    if (name.contains("-")) {
+      return name;
+    }
+
+    // Check for the name in the cache.
+    String hyphenated = camelCaseMap.get(name);
+
+    // Convert the name to hyphenated format if not in the cache.
+    if (hyphenated == null) {
+      hyphenated = "";
+      MatchResult matches;
+      while ((matches = camelCaseWord.exec(name)) != null) {
+        String word = matches.getGroup(0);
+        if (caseWord.exec(word) == null) {
+          // The first letter is already lowercase, probably the first word.
+          hyphenated += word;
+        } else {
+          // Hyphenate the first letter.
+          hyphenated += "-" + matches.getGroup(1).toLowerCase();
+          if (matches.getGroupCount() > 1) {
+            hyphenated += matches.getGroup(2);
+          }
+        }
+      }
+      camelCaseMap.put(name, hyphenated);
+    }
+
+    return hyphenated;
+  }
+
+  private final HtmlBuilderImpl delegate;
+
+  /**
+   * Construct a new {@link HtmlStylesBuilder}.
+   * 
+   * @param delegate the delegate that builds the style
+   */
+  HtmlStylesBuilder(HtmlBuilderImpl delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public StylesBuilder backgroundImage(SafeUri uri) {
+    return delegate.styleProperty(SafeStylesUtils.forBackgroundImage(uri));
+  }
+
+  @Override
+  public StylesBuilder borderStyle(BorderStyle value) {
+    return delegate.styleProperty(SafeStylesUtils.forBorderStyle(value));
+  }
+
+  @Override
+  public StylesBuilder borderWidth(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forBorderWidth(value, unit));
+  }
+
+  @Override
+  public StylesBuilder bottom(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forBottom(value, unit));
+  }
+
+  @Override
+  public StylesBuilder cursor(Cursor value) {
+    return delegate.styleProperty(SafeStylesUtils.forCursor(value));
+  }
+
+  @Override
+  public StylesBuilder display(Display value) {
+    return delegate.styleProperty(SafeStylesUtils.forDisplay(value));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <B extends ElementBuilderBase<?>> B endStyle() {
+    return (B) delegate.endStyle();
+  }
+
+  @Override
+  public StylesBuilder floatprop(Float value) {
+    return delegate.styleProperty(SafeStylesUtils.forFloat(value));
+  }
+
+  @Override
+  public StylesBuilder fontSize(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forFontSize(value, unit));
+  }
+
+  @Override
+  public StylesBuilder fontStyle(FontStyle value) {
+    return delegate.styleProperty(SafeStylesUtils.forFontStyle(value));
+  }
+
+  @Override
+  public StylesBuilder fontWeight(FontWeight value) {
+    return delegate.styleProperty(SafeStylesUtils.forFontWeight(value));
+  }
+
+  @Override
+  public StylesBuilder height(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forHeight(value, unit));
+  }
+
+  @Override
+  public StylesBuilder left(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forLeft(value, unit));
+  }
+
+  @Override
+  public StylesBuilder listStyleType(ListStyleType value) {
+    return delegate.styleProperty(SafeStylesUtils.forListStyleType(value));
+  }
+
+  @Override
+  public StylesBuilder margin(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forMargin(value, unit));
+  }
+
+  @Override
+  public StylesBuilder marginBottom(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forMarginBottom(value, unit));
+  }
+
+  @Override
+  public StylesBuilder marginLeft(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forMarginLeft(value, unit));
+  }
+
+  @Override
+  public StylesBuilder marginRight(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forMarginRight(value, unit));
+  }
+
+  @Override
+  public StylesBuilder marginTop(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forMarginTop(value, unit));
+  }
+
+  @Override
+  public StylesBuilder opacity(double value) {
+    return delegate.styleProperty(SafeStylesUtils.forOpacity(value));
+  }
+
+  @Override
+  public StylesBuilder overflow(Overflow value) {
+    return delegate.styleProperty(SafeStylesUtils.forOverflow(value));
+  }
+
+  @Override
+  public StylesBuilder overflowX(Overflow value) {
+    return delegate.styleProperty(SafeStylesUtils.forOverflowX(value));
+  }
+
+  @Override
+  public StylesBuilder overflowY(Overflow value) {
+    return delegate.styleProperty(SafeStylesUtils.forOverflowY(value));
+  }
+
+  @Override
+  public StylesBuilder padding(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forPadding(value, unit));
+  }
+
+  @Override
+  public StylesBuilder paddingBottom(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forPaddingBottom(value, unit));
+  }
+
+  @Override
+  public StylesBuilder paddingLeft(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forPaddingLeft(value, unit));
+  }
+
+  @Override
+  public StylesBuilder paddingRight(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forPaddingRight(value, unit));
+  }
+
+  @Override
+  public StylesBuilder paddingTop(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forPaddingTop(value, unit));
+  }
+
+  @Override
+  public StylesBuilder position(Position value) {
+    return delegate.styleProperty(SafeStylesUtils.forPosition(value));
+  }
+
+  @Override
+  public StylesBuilder right(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forRight(value, unit));
+  }
+
+  @Override
+  public StylesBuilder tableLayout(TableLayout value) {
+    return delegate.styleProperty(SafeStylesUtils.forTableLayout(value));
+  }
+
+  @Override
+  public StylesBuilder textDecoration(TextDecoration value) {
+    return delegate.styleProperty(SafeStylesUtils.forTextDecoration(value));
+  }
+
+  @Override
+  public StylesBuilder top(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forTop(value, unit));
+  }
+
+  @Override
+  public StylesBuilder trustedBackgroundColor(String value) {
+    return delegate.styleProperty(SafeStylesUtils.forTrustedBackgroundColor(value));
+  }
+
+  @Override
+  public StylesBuilder trustedBackgroundImage(String value) {
+    return delegate.styleProperty(SafeStylesUtils.forTrustedBackgroundImage(value));
+  }
+
+  @Override
+  public StylesBuilder trustedBorderColor(String value) {
+    return delegate.styleProperty(SafeStylesUtils.forTrustedBorderColor(value));
+  }
+
+  @Override
+  public StylesBuilder trustedColor(String value) {
+    return delegate.styleProperty(SafeStylesUtils.forTrustedColor(value));
+  }
+
+  @Override
+  public StylesBuilder trustedProperty(String name, double value, Unit unit) {
+    name = toHyphenatedForm(name);
+    return delegate.styleProperty(SafeStylesUtils.fromTrustedNameAndValue(name, value, unit));
+  }
+
+  @Override
+  public StylesBuilder trustedProperty(String name, String value) {
+    name = toHyphenatedForm(name);
+    return delegate.styleProperty(SafeStylesUtils.fromTrustedNameAndValue(name, value));
+  }
+
+  @Override
+  public StylesBuilder verticalAlign(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forVerticalAlign(value, unit));
+  }
+
+  @Override
+  public StylesBuilder verticalAlign(VerticalAlign value) {
+    return delegate.styleProperty(SafeStylesUtils.forVerticalAlign(value));
+  }
+
+  @Override
+  public StylesBuilder visibility(Visibility value) {
+    return delegate.styleProperty(SafeStylesUtils.forVisibility(value));
+  }
+
+  @Override
+  public StylesBuilder width(double value, Unit unit) {
+    return delegate.styleProperty(SafeStylesUtils.forWidth(value, unit));
+  }
+
+  @Override
+  public StylesBuilder zIndex(int value) {
+    return delegate.styleProperty(SafeStylesUtils.forZIndex(value));
+  }
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/OptionBuilder.java b/user/src/com/google/gwt/dom/builder/shared/OptionBuilder.java
new file mode 100644
index 0000000..124f427
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/OptionBuilder.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Builds an option element.
+ */
+public interface OptionBuilder extends ElementBuilderBase<OptionBuilder> {
+
+  /**
+   * Represents the value of the HTML selected attribute. The value of this
+   * attribute does not change if the state of the corresponding form control,
+   * in an interactive user agent, changes.
+   * 
+   * @see <a
+   *      href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-selected">W3C
+   *      HTML Specification</a>
+   */
+  OptionBuilder defaultSelected();
+
+  /**
+   * Prevents the user from selecting this option.
+   * 
+   * @see <a
+   *      href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-disabled">W3C
+   *      HTML Specification</a>
+   */
+  OptionBuilder disabled();
+
+  /**
+   * Option label for use in menus.
+   * 
+   * @see <a
+   *      href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-label-OPTION">W3C
+   *      HTML Specification</a>
+   */
+  OptionBuilder label(String label);
+
+  /**
+   * Represents the current state of the corresponding form control, in an
+   * interactive user agent. Changing this attribute changes the state of the
+   * form control, but does not change the value of the HTML selected attribute
+   * of the element.
+   */
+  OptionBuilder selected();
+
+  /**
+   * The current form control value.
+   * 
+   * @see <a
+   *      href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION">W3C
+   *      HTML Specification</a>
+   */
+  OptionBuilder value(String value);
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/SelectBuilder.java b/user/src/com/google/gwt/dom/builder/shared/SelectBuilder.java
new file mode 100644
index 0000000..3f4cb3d
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/SelectBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Builds a select element.
+ */
+public interface SelectBuilder extends ElementBuilderBase<SelectBuilder> {
+
+  /**
+   * Set the size of the list.
+   * 
+   * @param size the size
+   * @return this builder
+   */
+  SelectBuilder size(int size);
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/StylesBuilder.java b/user/src/com/google/gwt/dom/builder/shared/StylesBuilder.java
new file mode 100644
index 0000000..49b35ff
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/StylesBuilder.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Float;
+import com.google.gwt.dom.client.Style.FontStyle;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.dom.client.Style.ListStyleType;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.TableLayout;
+import com.google.gwt.dom.client.Style.TextDecoration;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.VerticalAlign;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.safehtml.shared.SafeUri;
+
+/**
+ * Builds the style attribute on an element.
+ * 
+ * <p>
+ * The HTML implementation of class appends the style properties to the HTML
+ * string. The DOM implementation of this class sets the element's styles
+ * directly.
+ * </p>
+ */
+public interface StylesBuilder {
+
+  /**
+   * Sets the background-image CSS property.
+   * 
+   * @param uri the URI of the background image
+   * @return this {@link StylesBuilder}
+   * @see #trustedBackgroundImage(String)
+   */
+  StylesBuilder backgroundImage(SafeUri uri);
+
+  /**
+   * Sets the border-style CSS property.
+   */
+  StylesBuilder borderStyle(BorderStyle value);
+
+  /**
+   * Set the border-width css property.
+   */
+  StylesBuilder borderWidth(double value, Unit unit);
+
+  /**
+   * Set the bottom css property.
+   */
+  StylesBuilder bottom(double value, Unit unit);
+
+  /**
+   * Sets the cursor CSS property.
+   */
+  StylesBuilder cursor(Cursor value);
+
+  /**
+   * Sets the display CSS property.
+   */
+  StylesBuilder display(Display value);
+
+  /**
+   * End the current style attribute.
+   * 
+   * <p>
+   * By default, this method returns the {@link ElementBuilderBase} instance for
+   * the element that contains the style attribute.
+   * </p>
+   * 
+   * <pre>
+   * DivBuilder div = ElementBuilderFactory.get().createDivBuilder();
+   * StylesBuilder styles = div.style();
+   * styles.fontSize(12.0, Unit.PT);
+   * ElementBuilderBase&lt;?&gt; sameAs_div = styles.endStyle();
+   * </pre>
+   * 
+   * <p>
+   * You can cast the return type by parameterizing the return value. If the
+   * parameterized type does not match the builder type of the element that
+   * contains this style, a {@link ClassCastException} is thrown.
+   * </p>
+   * 
+   * <pre>
+   * DivBuilder div = ElementBuilderFactory.get().createDivBuilder();
+   * StylesBuilder styles = div.style();
+   * styles.fontSize(12.0, Unit.PT);
+   * DivBuilder sameAsDiv = styles.&lt;DivBuilder&gt; endStyle();
+   * </pre>
+   * 
+   * @param <B> the type of the element that contains the styles
+   * @return the {@link ElementBuilderBase} for the containing element
+   * @throws IllegalStateException if the style attribute is already closed
+   * @throws ClassCastException if the parent builder does not match the
+   *           specified return type
+   */
+  <B extends ElementBuilderBase<?>> B endStyle();
+
+  /**
+   * Set the float css property.
+   */
+  StylesBuilder floatprop(Float value);
+
+  /**
+   * Set the font-size css property.
+   */
+  StylesBuilder fontSize(double value, Unit unit);
+
+  /**
+   * Sets the font-style CSS property.
+   */
+  StylesBuilder fontStyle(FontStyle value);
+
+  /**
+   * Sets the font-weight CSS property.
+   */
+  StylesBuilder fontWeight(FontWeight value);
+
+  /**
+   * Set the height css property.
+   */
+  StylesBuilder height(double value, Unit unit);
+
+  /**
+   * Set the left css property.
+   */
+  StylesBuilder left(double value, Unit unit);
+
+  /**
+   * Sets the list-style-type CSS property.
+   */
+  StylesBuilder listStyleType(ListStyleType value);
+
+  /**
+   * Set the margin css property.
+   */
+  StylesBuilder margin(double value, Unit unit);
+
+  /**
+   * Set the margin-bottom css property.
+   */
+  StylesBuilder marginBottom(double value, Unit unit);
+
+  /**
+   * Set the margin-left css property.
+   */
+  StylesBuilder marginLeft(double value, Unit unit);
+
+  /**
+   * Set the margin-right css property.
+   */
+  StylesBuilder marginRight(double value, Unit unit);
+
+  /**
+   * Set the margin-top css property.
+   */
+  StylesBuilder marginTop(double value, Unit unit);
+
+  /**
+   * Set the opacity css property.
+   */
+  StylesBuilder opacity(double value);
+
+  /**
+   * Sets the overflow CSS property.
+   */
+  StylesBuilder overflow(Overflow value);
+
+  /**
+   * Sets the overflow-x CSS property.
+   */
+  StylesBuilder overflowX(Overflow value);
+
+  /**
+   * Sets the overflow-y CSS property.
+   */
+  StylesBuilder overflowY(Overflow value);
+
+  /**
+   * Set the padding css property.
+   */
+  StylesBuilder padding(double value, Unit unit);
+
+  /**
+   * Set the padding-bottom css property.
+   */
+  StylesBuilder paddingBottom(double value, Unit unit);
+
+  /**
+   * Set the padding-left css property.
+   */
+  StylesBuilder paddingLeft(double value, Unit unit);
+
+  /**
+   * Set the padding-right css property.
+   */
+  StylesBuilder paddingRight(double value, Unit unit);
+
+  /**
+   * Set the padding-top css property.
+   */
+  StylesBuilder paddingTop(double value, Unit unit);
+
+  /**
+   * Sets the position CSS property.
+   */
+  StylesBuilder position(Position value);
+
+  /**
+   * Set the right css property.
+   */
+  StylesBuilder right(double value, Unit unit);
+
+  /**
+   * Set the table-layout CSS property.
+   */
+  StylesBuilder tableLayout(TableLayout value);
+
+  /**
+   * Sets the text-decoration CSS property.
+   */
+  StylesBuilder textDecoration(TextDecoration value);
+
+  /**
+   * Set the top css property.
+   */
+  StylesBuilder top(double value, Unit unit);
+
+  /**
+   * <p>
+   * Sets the "background-color" style property to the specified color string.
+   * Does not check or escape the color string. The calling code should be
+   * carefully reviewed to ensure that the provided color string won't cause a
+   * security issue if included in a style attribute.
+   * </p>
+   * 
+   * <p>
+   * For details and constraints, see
+   * {@link com.google.gwt.safecss.shared.SafeStyles}.
+   * </p>
+   * 
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedBackgroundColor(String value);
+
+  /**
+   * <p>
+   * Sets the "background-image" style property to the specified value. Does not
+   * check or escape the value. The calling code should be carefully reviewed to
+   * ensure that the provided value string won't cause a security issue if
+   * included in a style attribute.
+   * </p>
+   * 
+   * <p>
+   * For details and constraints, see
+   * {@link com.google.gwt.safecss.shared.SafeStyles}.
+   * </p>
+   * 
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedBackgroundImage(String value);
+
+  /**
+   * <p>
+   * Sets the "border-color" style property to the specified color string. Does
+   * not check or escape the color string. The calling code should be carefully
+   * reviewed to ensure that the provided color string won't cause a security
+   * issue if included in a style attribute.
+   * </p>
+   * 
+   * <p>
+   * For details and constraints, see
+   * {@link com.google.gwt.safecss.shared.SafeStyles}.
+   * </p>
+   * 
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedBorderColor(String value);
+
+  /**
+   * <p>
+   * Sets the "color" style property, which controls font color, to the
+   * specified color string. Does not check or escape the color string. The
+   * calling code should be carefully reviewed to ensure that the provided color
+   * string won't cause a security issue if included in a style attribute.
+   * </p>
+   * 
+   * <p>
+   * For details and constraints, see
+   * {@link com.google.gwt.safecss.shared.SafeStyles}.
+   * </p>
+   * 
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedColor(String value);
+
+  /**
+   * <p>
+   * Set a style property from a trusted name and a trusted value, i.e., without
+   * escaping the name and value. No checks are performed. The calling code
+   * should be carefully reviewed to ensure the argument will satisfy the
+   * {@link com.google.gwt.safecss.shared.SafeStyles} contract when they are
+   * composed into the form: "&lt;name&gt;:&lt;value&gt;;".
+   * 
+   * <p>
+   * SafeStyles may never contain literal angle brackets. Otherwise, it could be
+   * unsafe to place a SafeStyles into a &lt;style&gt; tag (where it can't be
+   * HTML escaped). For example, if the SafeStyles containing "
+   * <code>font: 'foo &lt;style&gt;&lt;script&gt;evil&lt;/script&gt;</code>'" is
+   * used in a style sheet in a &lt;style&gt; tag, this could then break out of
+   * the style context into HTML.
+   *
+   * @param unit the units of the value
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedProperty(String name, double value, Unit unit);
+
+  /**
+   * <p>
+   * Set a style property from a trusted name and a trusted value, i.e., without
+   * escaping the name and value. No checks are performed. The calling code
+   * should be carefully reviewed to ensure the argument will satisfy the
+   * {@link com.google.gwt.safecss.shared.SafeStyles} contract when they are
+   * composed into the form: "&lt;name&gt;:&lt;value&gt;;".
+   * 
+   * <p>
+   * SafeStyles may never contain literal angle brackets. Otherwise, it could be
+   * unsafe to place a SafeStyles into a &lt;style&gt; tag (where it can't be
+   * HTML escaped). For example, if the SafeStyles containing "
+   * <code>font: 'foo &lt;style&gt;&lt;script&gt;evil&lt;/script&gt;</code>'" is
+   * used in a style sheet in a &lt;style&gt; tag, this could then break out of
+   * the style context into HTML.
+   * 
+   * @return this {@link StylesBuilder}
+   */
+  StylesBuilder trustedProperty(String name, String value);
+
+  /**
+   * Sets the vertical-align CSS property.
+   */
+  StylesBuilder verticalAlign(double value, Unit unit);
+
+  /**
+   * Sets the vertical-align CSS property.
+   */
+  StylesBuilder verticalAlign(VerticalAlign value);
+
+  /**
+   * Sets the visibility CSS property.
+   */
+  StylesBuilder visibility(Visibility value);
+
+  /**
+   * Set the width css property.
+   */
+  StylesBuilder width(double value, Unit unit);
+
+  /**
+   * Set the z-index css property.
+   */
+  StylesBuilder zIndex(int value);
+}
diff --git a/user/src/com/google/gwt/dom/builder/shared/package-info.java b/user/src/com/google/gwt/dom/builder/shared/package-info.java
new file mode 100644
index 0000000..bc7dfb9
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * Classes used to build DOM elements.
+ *
+ * @since GWT 2.4
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.dom.builder.shared;
diff --git a/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java b/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
index 105532c..b347aaf 100644
--- a/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
+++ b/user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java
@@ -189,7 +189,7 @@
         // Found an open quote.
         inQuote = ch;
         inQuotePos = i;
-      } else if ((ch == 'u' || ch == 'U') && value.length() >= i + 3
+      } else if ((ch == 'u' || ch == 'U') && value.length() >= i + 4
           && value.substring(i, i + 4).equalsIgnoreCase("url(")) {
         // Starting a URL.
         inUrl = true;
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 7c32c1d..9b4f391 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -21,6 +21,7 @@
    <inherits name="com.google.gwt.animation.Animation"/>
    <inherits name="com.google.gwt.canvas.Canvas"/>
    <inherits name="com.google.gwt.core.Core"/>
+   <inherits name='com.google.gwt.dom.builder.DomBuilder'/>
    <inherits name="com.google.gwt.editor.Editor" />
    <inherits name="com.google.gwt.event.Event"/>
    <inherits name="com.google.gwt.geolocation.Geolocation" />
diff --git a/user/test/com/google/gwt/dom/builder/DomBuilderGwtSuite.java b/user/test/com/google/gwt/dom/builder/DomBuilderGwtSuite.java
new file mode 100644
index 0000000..c4b1726
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/DomBuilderGwtSuite.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder;
+
+import com.google.gwt.dom.builder.client.GwtDomBuilderImplTest;
+import com.google.gwt.dom.builder.client.GwtDomDivBuilderTest;
+import com.google.gwt.dom.builder.client.GwtDomOptionBuilderTest;
+import com.google.gwt.dom.builder.client.GwtDomSelectBuilderTest;
+import com.google.gwt.dom.builder.client.GwtDomStylesBuilderTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the dom implementation of element builders.
+ */
+public class DomBuilderGwtSuite {
+
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("GWT tests for all dom builders");
+
+    suite.addTestSuite(GwtDomBuilderImplTest.class);
+    suite.addTestSuite(GwtDomDivBuilderTest.class);
+    suite.addTestSuite(GwtDomOptionBuilderTest.class);
+    suite.addTestSuite(GwtDomSelectBuilderTest.class);
+    suite.addTestSuite(GwtDomStylesBuilderTest.class);
+
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/HtmlBuilderGwtSuite.java b/user/test/com/google/gwt/dom/builder/HtmlBuilderGwtSuite.java
new file mode 100644
index 0000000..f4e0935
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/HtmlBuilderGwtSuite.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder;
+
+import com.google.gwt.dom.builder.shared.GwtHtmlBuilderImplTest;
+import com.google.gwt.dom.builder.shared.GwtHtmlDivBuilderTest;
+import com.google.gwt.dom.builder.shared.GwtHtmlOptionBuilderTest;
+import com.google.gwt.dom.builder.shared.GwtHtmlSelectBuilderTest;
+import com.google.gwt.dom.builder.shared.GwtHtmlStylesBuilderTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the html implementation of element builders.
+ */
+public class HtmlBuilderGwtSuite {
+
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("GWT tests for all html builders");
+
+    suite.addTestSuite(GwtHtmlBuilderImplTest.class);
+    suite.addTestSuite(GwtHtmlDivBuilderTest.class);
+    suite.addTestSuite(GwtHtmlOptionBuilderTest.class);
+    suite.addTestSuite(GwtHtmlSelectBuilderTest.class);
+    suite.addTestSuite(GwtHtmlStylesBuilderTest.class);
+
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/HtmlBuilderJreSuite.java b/user/test/com/google/gwt/dom/builder/HtmlBuilderJreSuite.java
new file mode 100644
index 0000000..01d5464
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/HtmlBuilderJreSuite.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder;
+
+import com.google.gwt.dom.builder.shared.HtmlDivBuilderTest;
+import com.google.gwt.dom.builder.shared.HtmlOptionBuilderTest;
+import com.google.gwt.dom.builder.shared.HtmlSelectBuilderTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests of the html implementation of element builders.
+ */
+public class HtmlBuilderJreSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite("JRE tests for all html builders");
+
+    suite.addTestSuite(HtmlDivBuilderTest.class);
+    suite.addTestSuite(HtmlOptionBuilderTest.class);
+    suite.addTestSuite(HtmlSelectBuilderTest.class);
+
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/client/GwtDomBuilderImplTest.java b/user/test/com/google/gwt/dom/builder/client/GwtDomBuilderImplTest.java
new file mode 100644
index 0000000..5ca44d4
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/client/GwtDomBuilderImplTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilderFactory;
+import com.google.gwt.dom.builder.shared.GwtElementBuilderImplTestBase;
+
+/**
+ * Gwt tests for {@link DomBuilderImpl}.
+ */
+public class GwtDomBuilderImplTest extends GwtElementBuilderImplTestBase {
+
+  @Override
+  protected ElementBuilderFactory getElementBuilderFactory() {
+    return DomBuilderFactory.get();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/client/GwtDomDivBuilderTest.java b/user/test/com/google/gwt/dom/builder/client/GwtDomDivBuilderTest.java
new file mode 100644
index 0000000..74ea5b1
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/client/GwtDomDivBuilderTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.HtmlDivBuilderTest;
+
+/**
+ * Tests for {@link DomDivBuilder}.
+ */
+public class GwtDomDivBuilderTest extends HtmlDivBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+
+  @Override
+  protected DivBuilder createElementBuilder() {
+    return DomBuilderFactory.get().createDivBuilder();
+  }
+
+  @Override
+  protected DivBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startDiv();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/client/GwtDomOptionBuilderTest.java b/user/test/com/google/gwt/dom/builder/client/GwtDomOptionBuilderTest.java
new file mode 100644
index 0000000..6f986d4
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/client/GwtDomOptionBuilderTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.OptionBuilder;
+import com.google.gwt.dom.builder.shared.HtmlOptionBuilderTest;
+
+/**
+ * Tests for {@link DomOptionBuilder}.
+ */
+public class GwtDomOptionBuilderTest extends HtmlOptionBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+
+  @Override
+  protected OptionBuilder createElementBuilder() {
+    return DomBuilderFactory.get().createOptionBuilder();
+  }
+
+  @Override
+  protected OptionBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startOption();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/client/GwtDomSelectBuilderTest.java b/user/test/com/google/gwt/dom/builder/client/GwtDomSelectBuilderTest.java
new file mode 100644
index 0000000..0bc2fb6
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/client/GwtDomSelectBuilderTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.SelectBuilder;
+import com.google.gwt.dom.builder.shared.HtmlSelectBuilderTest;
+
+/**
+ * Tests for {@link DomSelectBuilder}.
+ */
+public class GwtDomSelectBuilderTest extends HtmlSelectBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+
+  @Override
+  protected SelectBuilder createElementBuilder() {
+    return DomBuilderFactory.get().createSelectBuilder();
+  }
+
+  @Override
+  protected SelectBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startSelect();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/client/GwtDomStylesBuilderTest.java b/user/test/com/google/gwt/dom/builder/client/GwtDomStylesBuilderTest.java
new file mode 100644
index 0000000..01e0d78
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/client/GwtDomStylesBuilderTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.client;
+
+import com.google.gwt.dom.builder.shared.GwtStylesBuilderTestBase;
+
+/**
+ * Gwt tests for {@link DomStylesBuilder}.
+ */
+public class GwtDomStylesBuilderTest extends GwtStylesBuilderTestBase {
+
+  public void testToCamelCaseForm() {
+    assertEquals("simple", DomStylesBuilder.toCamelCaseForm("simple"));
+    assertEquals("alreadycamelCase", DomStylesBuilder.toCamelCaseForm("alreadycamelCase"));
+    assertEquals("alreadyCamelCaseMultipleHumps", DomStylesBuilder
+        .toCamelCaseForm("alreadyCamelCaseMultipleHumps"));
+    assertEquals("wasHyphenated", DomStylesBuilder.toCamelCaseForm("was-hyphenated"));
+    assertEquals("wasHyphenatedTwice", DomStylesBuilder.toCamelCaseForm("was-hyphenated-twice"));
+    assertEquals("startsWithHyphen", DomStylesBuilder.toCamelCaseForm("-starts-with-hyphen"));
+  }
+
+  @Override
+  protected DomBuilderFactory getElementBuilderFactory() {
+    return DomBuilderFactory.get();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/ElementBuilderTestBase.java b/user/test/com/google/gwt/dom/builder/shared/ElementBuilderTestBase.java
new file mode 100644
index 0000000..575fb9f
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/ElementBuilderTestBase.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * Base tests for subclasses of {@link ElementBuilderBase}.
+ * 
+ * @param <T> the type of builder being tested
+ */
+public abstract class ElementBuilderTestBase<T extends ElementBuilderBase<?>> extends GWTTestCase {
+
+  /**
+   * The GWT module name when running tests on the client.
+   */
+  protected static final String GWT_MODULE_NAME = "com.google.gwt.dom.builder.DomBuilder";
+
+  /**
+   * A command that operates on a builder.
+   */
+  protected interface BuilderCommand<T extends ElementBuilderBase<?>> {
+    /**
+     * Execute an action.
+     * 
+     * @param builder the builder being tested
+     */
+    void execute(T builder);
+  }
+
+  @Override
+  public String getModuleName() {
+    // Default to JVM implementation.
+    return null;
+  }
+
+  /**
+   * Test that you cannot append text, html, or elements after setting the text.
+   */
+  public void testAppendAfterHtml() {
+    T builder = createElementBuilder();
+    builder.html(SafeHtmlUtils.fromString("Hello World"));
+
+    try {
+      builder.text("moretext");
+      fail("Expected IllegalStateException: setting text after setting html");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    try {
+      builder.html(SafeHtmlUtils.fromString("morehtml"));
+      fail("Expected IllegalStateException: setting html twice");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    try {
+      builder.startDiv();
+      fail("Expected IllegalStateException: appending a div after setting html");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Test that you cannot append text, html, or elements after setting the text.
+   */
+  public void testAppendAfterText() {
+    T builder = createElementBuilder();
+    builder.text("Hello World");
+
+    try {
+      builder.text("moretext");
+      fail("Expected IllegalStateException: setting text twice");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    try {
+      builder.html(SafeHtmlUtils.fromString("morehtml"));
+      fail("Expected IllegalStateException: setting html after setting text");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    try {
+      builder.startDiv();
+      fail("Expected IllegalStateException: appending a div after setting text");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  public void testAttributeAfterAppendHtml() {
+    // String value.
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.attribute("name", "value");
+      }
+    }, "Cannot add attribute after appending html");
+
+    // Int value.
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.attribute("name", 1);
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testAttributeAfterEnd() {
+    assertActionFailsAfterEnd(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.attribute("name", "value");
+      }
+    }, "Cannot add attribute after adding a child element");
+  }
+
+  public void testStylePropertyAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        // Accessing the style builder is allowed.
+        StylesBuilder style;
+        try {
+          style = builder.style();
+        } catch (RuntimeException e) {
+          fail("Accessing StyleBuilder should not trigger an error: " + e.getMessage());
+          throw e;
+        }
+
+        // Using it is not.
+        style.trustedProperty("name", "value");
+      }
+    }, "Cannot access StyleBuilder appending html");
+  }
+
+  public void testStylePropertyAfterEnd() {
+    assertActionFailsAfterEnd(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        // Accessing the style builder is allowed.
+        StylesBuilder style;
+        try {
+          style = builder.style();
+        } catch (RuntimeException e) {
+          fail("Accessing StyleBuilder should not trigger an error: " + e.getMessage());
+          throw e;
+        }
+
+        // Using it is not.
+        style.trustedProperty("name", "value");
+      }
+    }, "Cannot access StyleBuilder appending html");
+  }
+
+  public void testClassNameAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.className("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testDirNameAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.dir("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testDraggableNameAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.draggable("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testEndReturnType() {
+    T builder = createElementBuilder();
+    DivBuilder divBuilder0 = builder.startDiv();
+    DivBuilder divBuilder1 = divBuilder0.startDiv();
+    assertEquals(divBuilder0, divBuilder1.end());
+    assertEquals(builder, divBuilder0.end());
+    assertNull(builder.end());
+  }
+
+  public void testEndReturnTypeSpecified() {
+    T builder = createElementBuilder();
+    DivBuilder divBuilder0 = builder.startDiv();
+    DivBuilder divBuilder1 = divBuilder0.startDiv();
+    assertEquals(divBuilder0, divBuilder1.<DivBuilder> end());
+  }
+
+  public void testEndUnmatchedTagName() {
+    T builder = createElementBuilder();
+    builder.startDiv();
+
+    try {
+      builder.end("span");
+      fail("Expected IllegalStateException: Started div but ended span");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  public void testHtmlAfterAppend() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.html(SafeHtmlUtils.fromString("hello world"));
+      }
+    }, "Cannot set html after appending a child element");
+  }
+
+  public void testHtmlAfterEnd() {
+    assertActionFailsAfterEnd(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.html(SafeHtmlUtils.fromString("hello world"));
+      }
+    }, "Cannot set html after adding a child element");
+  }
+
+  /**
+   * Test that HTML can be set after ending one element and starting another.
+   */
+  public void testHtmlAfterRestart() {
+    T builder = createElementBuilder();
+    builder.startDiv().id("test").html(SafeHtmlUtils.fromString("test")).end();
+
+    // Should not cause any errors.
+    builder.startDiv().html(SafeHtmlUtils.fromString("hello"));
+  }
+
+  public void testIdNameAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.id("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testLangAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.lang("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testStartSecondTopLevelElement() {
+    T builder = createElementBuilder();
+    builder.end(); // Close the top level attribute.
+
+    try {
+      startElement(builder);
+      fail("Expected IllegalStateException: Cannot start multiple top level attributes");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Test that you cannot add style properties after interrupting them with an
+   * attribute.
+   */
+  public void testStyleTwice() {
+    T builder = createElementBuilder();
+
+    // Access style first time.
+    StylesBuilder style = builder.style().borderWidth(1.0, Unit.PX).fontSize(10.0, Unit.PX);
+
+    // Access style again, without interruption.
+    builder.style().trustedColor("red");
+
+    // Add an attribute.
+    builder.id("id");
+
+    // Accessing style after interruption is allowed.
+    StylesBuilder style0 = builder.style();
+
+    // Using it is not.
+    try {
+      style0.left(1.0, Unit.PX);
+      fail("Expected IllegalStateException: Cannot access StyleBuilder after interruption");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    // Reuse existing style after interruption.
+    try {
+      style.left(1.0, Unit.PX);
+      fail("Expected IllegalStateException: Cannot access StyleBuilder after interruption");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  public void testTabIndexAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.tabIndex(1);
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  public void testTextAfterAppend() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.text("hello world");
+      }
+    }, "Cannot set html after appending a child element");
+  }
+
+  public void testTextAfterEnd() {
+    assertActionFailsAfterEnd(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.text("hello world");
+      }
+    }, "Cannot set text after adding a child element");
+  }
+
+  /**
+   * Test that text can be set after ending one element and starting another.
+   */
+  public void testTextAfterRetart() {
+    T builder = createElementBuilder();
+    builder.startDiv().id("test").text("test").end();
+
+    // Should not cause any errors.
+    builder.startDiv().text("hello");
+  }
+
+  public void testTitleAfterAppendHtml() {
+    assertActionFailsAfterAppendHtml(new BuilderCommand<T>() {
+      @Override
+      public void execute(T builder) {
+        builder.title("value");
+      }
+    }, "Cannot add attribute after appending html");
+  }
+
+  /**
+   * Test that the specified command triggers an {@link IllegalStateException}
+   * after appending html to the element.
+   * 
+   * @param action the command to execute
+   * @param message the failure message if the test fails
+   */
+  protected void assertActionFailsAfterAppendHtml(BuilderCommand<T> action, String message) {
+    T builder = createElementBuilder();
+    builder.html(SafeHtmlUtils.EMPTY_SAFE_HTML);
+
+    try {
+      action.execute(builder);
+      fail("Expected IllegalStateException: " + message);
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Test that the specified command triggers an {@link IllegalStateException}
+   * after ending a child element.
+   * 
+   * @param action the command to execute
+   * @param message the failure message if the test fails
+   */
+  protected void assertActionFailsAfterEnd(BuilderCommand<T> action, String message) {
+    T builder = createElementBuilder();
+
+    // Add a child.
+    builder.startDiv().end();
+
+    try {
+      action.execute(builder);
+      fail("Expected IllegalStateException: " + message);
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Create an {@link ElementBuilderBase} to test.
+   */
+  protected abstract T createElementBuilder();
+
+  /**
+   * Start a new element within an existing builder.
+   * 
+   * @param builder the existing builder
+   * @return the builder for the new element
+   */
+  protected abstract T startElement(ElementBuilderBase<?> builder);
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtElementBuilderImplTestBase.java b/user/test/com/google/gwt/dom/builder/shared/GwtElementBuilderImplTestBase.java
new file mode 100644
index 0000000..48f1d06
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtElementBuilderImplTestBase.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Test for implementations of {@link ElementBuilderImpl}.
+ */
+public abstract class GwtElementBuilderImplTestBase extends GWTTestCase {
+
+  private ElementBuilderFactory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dom.builder.DomBuilder";
+  }
+
+  /**
+   * Attributes on an element.
+   * 
+   * <pre>
+   * <div id="myId" title="myTitle">hello world</div>
+   * </pre>
+   */
+  public void testAttributes() {
+    Element div =
+        factory.createDivBuilder().id("myId").title("myTitle").text("hello world").finish();
+    assertTrue("div".equalsIgnoreCase(div.getTagName()));
+    assertEquals("hello world", div.getInnerText());
+    assertEquals("myId", div.getId());
+    assertEquals("myTitle", div.getTitle());
+  }
+
+  public void testFinishTwice() {
+    DivBuilder builder = factory.createDivBuilder();
+    assertNotNull(builder.finish());
+
+    try {
+      builder.finish();
+      fail("Expected IllegalStateException: cannot call finish() twice");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Children nested at multiple levels.
+   * 
+   * <pre>
+   * <div id="root">
+   *   <div id="child">
+   *     <div id="grandchild">grandchild</div>
+   *   </div>
+   * </div>
+   * </pre>
+   */
+  public void testNestedChildElements() {
+    DivBuilder builder = factory.createDivBuilder().id("root");
+    builder.startDiv().id("child").startDiv().id("grandchild").text("grandchild");
+    Element div = builder.finish();
+    assertTrue("div".equalsIgnoreCase(div.getTagName()));
+    assertEquals("root", div.getId());
+    assertEquals(1, div.getChildCount());
+
+    Element child = div.getFirstChildElement();
+    assertTrue("div".equalsIgnoreCase(child.getTagName()));
+    assertEquals("child", child.getId());
+    assertEquals(1, child.getChildCount());
+
+    Element grandchild = child.getFirstChildElement();
+    assertTrue("div".equalsIgnoreCase(grandchild.getTagName()));
+    assertEquals("grandchild", grandchild.getId());
+    assertNull(grandchild.getFirstChildElement());
+  }
+
+  /**
+   * Multiple children beneath root.
+   * 
+   * <pre>
+   * <div>
+   *   <div>div0</div>
+   *   <div>div1</div>
+   *   <div>div2</div>
+   * </div>
+   * </pre>
+   */
+  public void testNestedSiblingElements() {
+    DivBuilder builder = factory.createDivBuilder();
+    for (int i = 0; i < 3; i++) {
+      builder.startDiv().text("div" + i).endDiv();
+    }
+    Element div = builder.finish();
+    assertTrue("div".equalsIgnoreCase(div.getTagName()));
+    assertEquals(3, div.getChildCount());
+
+    Element child = div.getFirstChildElement();
+    assertEquals("div0", child.getInnerText());
+    child = child.getNextSiblingElement();
+    assertEquals("div1", child.getInnerText());
+    child = child.getNextSiblingElement();
+    assertEquals("div2", child.getInnerText());
+  }
+
+  /**
+   * Single element with text.
+   * 
+   * <pre>
+   * <div>hello world</div>.
+   * </pre>
+   */
+  public void testSingleElement() {
+    Element div = factory.createDivBuilder().text("hello world").finish();
+    assertTrue("div".equalsIgnoreCase(div.getTagName()));
+    assertEquals("hello world", div.getInnerText());
+  }
+
+  /**
+   * Element with style properties.
+   * 
+   * <pre>
+   * <div style="color:red;position:absolute;">hello world</div>.
+   * </pre>
+   */
+  public void testStyleProperties() {
+    Element div =
+        factory.createDivBuilder().style().trustedColor("red").position(Position.ABSOLUTE)
+            .endStyle().text("hello world").finish();
+    assertTrue("div".equalsIgnoreCase(div.getTagName()));
+    assertEquals("hello world", div.getInnerText());
+    assertEquals("red", div.getStyle().getColor());
+    assertEquals("absolute", div.getStyle().getPosition());
+  }
+
+  public void testTrustedStart() {
+    {
+      DivBuilder div = factory.createDivBuilder();
+      div.trustedStart("lowercase");
+      div.trustedStart("UPPERCASE");
+      div.trustedStart("camelCase");
+      div.trustedStart("containsNumber0");
+      div.trustedStart("a");
+    }
+
+    // Empty tagName.
+    try {
+      DivBuilder div = factory.createDivBuilder();
+      div.trustedStart("");
+      fail("Expected IllegalArgumentException: Empty string is not a valid tag name");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+
+    try {
+      DivBuilder div = factory.createDivBuilder();
+      div.trustedStart("<containsbracket");
+      fail("Expected IllegalArgumentException: TagName cannot contain brackets (<)");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Get the element builder factory used to create the implementation.
+   */
+  protected abstract ElementBuilderFactory getElementBuilderFactory();
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    factory = getElementBuilderFactory();
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    factory = null;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtHtmlBuilderImplTest.java b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlBuilderImplTest.java
new file mode 100644
index 0000000..3b52891
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlBuilderImplTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Tests for {@link HtmlBuilderImpl}.
+ */
+public class GwtHtmlBuilderImplTest extends GwtElementBuilderImplTestBase {
+
+  @Override
+  protected ElementBuilderFactory getElementBuilderFactory() {
+    return HtmlBuilderFactory.get();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtHtmlDivBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlDivBuilderTest.java
new file mode 100644
index 0000000..e49c505
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlDivBuilderTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * GWT tests for {@link HtmlDivBuilder}.
+ */
+public class GwtHtmlDivBuilderTest extends HtmlDivBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtHtmlOptionBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlOptionBuilderTest.java
new file mode 100644
index 0000000..7fb5c6c
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlOptionBuilderTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * GWT tests for {@link HtmlOptionBuilder}.
+ */
+public class GwtHtmlOptionBuilderTest extends HtmlOptionBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtHtmlSelectBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlSelectBuilderTest.java
new file mode 100644
index 0000000..1a9a939
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlSelectBuilderTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Gwt tests for {@link HtmlSelectBuilder}.
+ */
+public class GwtHtmlSelectBuilderTest extends HtmlSelectBuilderTest {
+
+  @Override
+  public String getModuleName() {
+    return GWT_MODULE_NAME;
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtHtmlStylesBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlStylesBuilderTest.java
new file mode 100644
index 0000000..bbe1a25
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtHtmlStylesBuilderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Gwt tests for {@link HtmlStylesBuilder}.
+ */
+public class GwtHtmlStylesBuilderTest extends GwtStylesBuilderTestBase {
+
+  public void testToHyphenatedForm() {
+    assertEquals("simple", HtmlStylesBuilder.toHyphenatedForm("simple"));
+    assertEquals("camel-case", HtmlStylesBuilder.toHyphenatedForm("camelCase"));
+    assertEquals("camel-case-multiple-humps", HtmlStylesBuilder
+        .toHyphenatedForm("camelCaseMultipleHumps"));
+    assertEquals("already-hyphenated", HtmlStylesBuilder.toHyphenatedForm("already-hyphenated"));
+    assertEquals("already-hyphenated-twice", HtmlStylesBuilder
+        .toHyphenatedForm("already-hyphenated-twice"));
+  }
+
+  @Override
+  protected ElementBuilderFactory getElementBuilderFactory() {
+    return HtmlBuilderFactory.get();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/GwtStylesBuilderTestBase.java b/user/test/com/google/gwt/dom/builder/shared/GwtStylesBuilderTestBase.java
new file mode 100644
index 0000000..d759c5a
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/GwtStylesBuilderTestBase.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Base tests for {@link StylesBuilder}.
+ */
+public abstract class GwtStylesBuilderTestBase extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return ElementBuilderTestBase.GWT_MODULE_NAME;
+  }
+
+  public void testTrustedProperty() {
+    ElementBuilderFactory factory = getElementBuilderFactory();
+    DivBuilder div = factory.createDivBuilder();
+
+    StylesBuilder styles = div.style();
+    styles.trustedProperty("color", "red"); // simple name.
+    styles.trustedProperty("backgroundColor", "blue"); // camelCase name.
+    styles.trustedProperty("border-color", "black"); // hyphenated name.
+    styles.endStyle();
+
+    Element elem = div.finish();
+    assertEquals("red", elem.getStyle().getColor());
+    assertEquals("blue", elem.getStyle().getBackgroundColor());
+
+    /*
+     * Some browsers return "black black black black" for the for sides of the
+     * border.
+     */
+    assertTrue(elem.getStyle().getBorderColor().contains("black"));
+  }
+
+  /**
+   * Get the element builder factory used to create the implementation.
+   */
+  protected abstract ElementBuilderFactory getElementBuilderFactory();
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/HtmlDivBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/HtmlDivBuilderTest.java
new file mode 100644
index 0000000..dfb8bf1
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/HtmlDivBuilderTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Tests for {@link HtmlDivBuilder}.
+ */
+public class HtmlDivBuilderTest extends ElementBuilderTestBase<DivBuilder> {
+
+  @Override
+  protected DivBuilder createElementBuilder() {
+    return HtmlBuilderFactory.get().createDivBuilder();
+  }
+
+  @Override
+  protected DivBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startDiv();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/HtmlOptionBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/HtmlOptionBuilderTest.java
new file mode 100644
index 0000000..fc958b3
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/HtmlOptionBuilderTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Tests for {@link HtmlOptionBuilder}.
+ */
+public class HtmlOptionBuilderTest extends ElementBuilderTestBase<OptionBuilder> {
+
+  @Override
+  protected OptionBuilder createElementBuilder() {
+    return HtmlBuilderFactory.get().createOptionBuilder();
+  }
+
+  @Override
+  protected OptionBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startOption();
+  }
+}
diff --git a/user/test/com/google/gwt/dom/builder/shared/HtmlSelectBuilderTest.java b/user/test/com/google/gwt/dom/builder/shared/HtmlSelectBuilderTest.java
new file mode 100644
index 0000000..e0d57ae
--- /dev/null
+++ b/user/test/com/google/gwt/dom/builder/shared/HtmlSelectBuilderTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dom.builder.shared;
+
+/**
+ * Tests for {@link HtmlSelectBuilder}.
+ */
+public class HtmlSelectBuilderTest extends ElementBuilderTestBase<SelectBuilder> {
+
+  @Override
+  protected SelectBuilder createElementBuilder() {
+    return HtmlBuilderFactory.get().createSelectBuilder();
+  }
+
+  @Override
+  protected SelectBuilder startElement(ElementBuilderBase<?> builder) {
+    return builder.startSelect();
+  }
+}
diff --git a/user/test/com/google/gwt/safecss/shared/GwtSafeStylesUtilsTest.java b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesUtilsTest.java
index fa3b591..2c6a37b 100644
--- a/user/test/com/google/gwt/safecss/shared/GwtSafeStylesUtilsTest.java
+++ b/user/test/com/google/gwt/safecss/shared/GwtSafeStylesUtilsTest.java
@@ -54,7 +54,8 @@
       "escaped\\;semi-colon", "escaped\\:colon", "url(http://localhost)",
       "url('http://withSingleQuotes')", "url(\"http://withDoubleQuotes\")",
       "url(http://withSemiColon;)", "url(http://withUnmatchedBracket{[)",
-      "url(http://withUnmatchedCloseBracket}])", "end-in-escaped-backslash\\\\"};
+      "url(http://withUnmatchedCloseBracket}])", "end-in-escaped-backslash\\\\", "u-near-end-u",
+      "url-near-end-url", "absolute"};
 
   private static Boolean isAssertionEnabled;