Merging releases/1.5@r3125:r3170 into trunk.

svn merge -r3125:3170 https://google-web-toolkit.googlecode.com/svn/releases/1.5 .



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3173 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/DelegateMembers.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/DelegateMembers.java
index d595bd5..3ae97b1 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/DelegateMembers.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/DelegateMembers.java
@@ -65,8 +65,7 @@
   protected Map<String, JField> doGetFields() {
     if (lazyFields != null) {
       /*
-       * Return if the fields are being initialized or have been
-       * initialized.
+       * Return if the fields are being initialized or have been initialized.
        */
       return lazyFields;
     }
@@ -86,8 +85,7 @@
   protected Map<String, List<JMethod>> doGetMethods() {
     if (lazyMethods != null) {
       /*
-       * Return if the methods are being initialized or have been
-       * initialized.
+       * Return if the methods are being initialized or have been initialized.
        */
       return lazyMethods;
     }
@@ -98,12 +96,21 @@
       JMethod newMethod = new JMethod(getParentType(), baseMethod);
       initializeParams(baseMethod, newMethod);
       newMethod.setReturnType(substitution.getSubstitution(baseMethod.getReturnType()));
+      initializeExceptions(baseMethod, newMethod);
       addMethod(newMethod);
     }
 
     return lazyMethods;
   }
 
+  private void initializeExceptions(JAbstractMethod srcMethod,
+      JAbstractMethod newMethod) {
+    for (JType thrown : srcMethod.getThrows()) {
+      // exceptions cannot be parameterized; just copy them over
+      newMethod.addThrows(thrown);
+    }
+  }
+
   private void initializeParams(JAbstractMethod srcMethod,
       JAbstractMethod newMethod) {
     for (JParameter srcParam : srcMethod.getParameters()) {
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
index 177eaa9..f304cff 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
@@ -413,6 +413,9 @@
   // Script resources are injected here
 // __MODULE_SCRIPTS_END__
 
+  // The 'defer' attribute here is a workaround for strange IE behavior where
+  // <script> tags that are doc.writ()en execute *immediately*, rather than
+  // in document-order, as they should. It has no effect on other browsers.
   $doc.write('<script defer="defer">__MODULE_FUNC__.onInjectionDone(\'__MODULE_NAME__\')</script>');
 }
 
diff --git a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
index 826d553..2d5153f 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
@@ -353,17 +353,26 @@
   // Script resources are injected here
 // __MODULE_SCRIPTS_END__
 
-  $doc.write('<script defer="defer">'
+  // This is a bit ugly, but serves a purpose. We need to ensure that the stats
+  // script runs before the compiled script. If they are both doc.write()n in
+  // sequence, that should be the effect. Except on IE it turns out that a
+  // script injected via doc.write() can execute immediately! Adding 'defer'
+  // attributes to both seemed to fix this, but caused startup problems for
+  // some apps. The final solution was simply to inject the compiled script
+  // from *within* the stats script, guaranteeing order at the expense of near
+  // total inscrutability :(
+  var compiledScriptTag = '"<script src=\\"' + base + strongName + '\\"></scr" + "ipt>"';
+  $doc.write('<script><!--\n'
     + 'window.__gwtStatsEvent && window.__gwtStatsEvent({'
-    + 'moduleName:\'__MODULE_NAME__\', subSystem:\'startup\','
-    + 'evtGroup: \'loadExternalRefs\', millis:(new Date()).getTime(),'
-    + 'type: \'end\'});'
+    + 'moduleName:"__MODULE_NAME__", subSystem:"startup",'
+    + 'evtGroup: "loadExternalRefs", millis:(new Date()).getTime(),'
+    + 'type: "end"});'
     + 'window.__gwtStatsEvent && window.__gwtStatsEvent({'
-    + 'moduleName:\'__MODULE_NAME__\', subSystem:\'startup\','
-    + 'evtGroup: \'moduleStartup\', millis:(new Date()).getTime(),'
-    + 'type: \'moduleRequested\'});'
-    + '</script>'
-    + '<script defer="defer" src="' + base + strongName + '"></script>');
+    + 'moduleName:"__MODULE_NAME__", subSystem:"startup",'
+    + 'evtGroup: "moduleStartup", millis:(new Date()).getTime(),'
+    + 'type: "moduleRequested"});'
+    + 'document.write(' + compiledScriptTag + ');'
+    + '\n--></script>');
 }
 
 // Called from compiled code to hook the window's resize & load events (the
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeRefVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/TypeRefVisitor.java
index df9510e..d72f031 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/TypeRefVisitor.java
@@ -66,6 +66,16 @@
 
   @Override
   public void endVisit(MessageSend messageSend, BlockScope scope) {
+    /*
+     * Counterintuitive: there's a reason we only need to record a reference for
+     * static method calls. For any non-static method call, there must be a
+     * qualifying instance expression. When that instance expression is visited,
+     * the type reference to its declared type will be recorded. Thus, recording
+     * for each instance call is unnecessary.
+     * 
+     * Note: when we tried recording for instance calls, we would get a null
+     * scope in some cases, which would cause compiler errors.
+     */
     if (messageSend.binding != null && messageSend.binding.isStatic()) {
       maybeDispatch(scope, messageSend, messageSend.actualReceiverType);
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 4ec5616..5b18dcb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -36,7 +36,9 @@
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 
 import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Stack;
 
 /**
@@ -57,12 +59,12 @@
  * 
  * <p>
  * If the <code>reuseTemps</code> constructor parameter is set to
- * <code>false</code>, then temps will be reused aggressively, even when
- * their types do not match the values assigned to them. To determine when a
- * temp can be reused, the current implementation uses a notion of "temp usage
- * scopes". Every time a temporary variable is allocated, it is recorded in the
- * current temp usage scope. Once the current temp usage scope is exited, all of
- * its temps become available for use for other purposes.
+ * <code>true</code>, then temps of the correct type will be reused once they
+ * become available. To determine when a temp can be reused, the current
+ * implementation uses a notion of "temp usage scopes". Every time a temporary
+ * variable is allocated, it is recorded in the current temp usage scope. Once
+ * the current temp usage scope is exited, all of its temps become available for
+ * use for other purposes.
  * </p>
  */
 public abstract class CompoundAssignmentNormalizer {
@@ -290,16 +292,87 @@
     }
   }
 
-  private static int localCounter;
+  /**
+   * For some particular type, tracks all usage of temporary local variables of
+   * that type within the current method.
+   */
+  private static class TempLocalTracker {
+    /**
+     * Temps previously created in nested usage scopes that are now available
+     * for reuse by subsequent code.
+     */
+    private List<JLocal> reusable = new ArrayList<JLocal>();
+
+    /**
+     * Temps in use in active scopes. They cannot currently be reused; however,
+     * any time a scope is exited, the set of locals at the top of stack is
+     * popped off and all contained locals become reusable. The top of stack
+     * correlates to the current scope.
+     */
+    private Stack<List<JLocal>> usageStack = new Stack<List<JLocal>>();
+
+    public TempLocalTracker() {
+      /*
+       * Due to the lazy-creation nature, we must assume upon creation that
+       * we're already in a valid scope (and thus create the initial empty scope
+       * ourselves).
+       */
+      enterScope();
+    }
+
+    public void enterScope() {
+      usageStack.push(new ArrayList<JLocal>());
+    }
+
+    public void exitScope() {
+      /*
+       * Due to the lazy-creation nature, the program flow might be several
+       * levels deep already when this object is created. Since we don't know
+       * how many exitScope() calls to accept, we must be empty-tolerant. In
+       * other words, this isn't naively defensive programming. :)
+       */
+      if (!usageStack.isEmpty()) {
+        List<JLocal> freed = usageStack.pop();
+        reusable.addAll(freed);
+      }
+    }
+
+    public JLocal tryGetReusableLocal() {
+      if (!reusable.isEmpty()) {
+        return reusable.remove(reusable.size() - 1);
+      }
+      return null;
+    }
+
+    public void useLocal(JLocal local) {
+      usageStack.peek().add(local);
+    }
+  }
+
   protected final JProgram program;
   private final CloneExpressionVisitor cloner;
 
   private JMethodBody currentMethodBody;
 
+  /**
+   * Counter to generate a unique name for each temporary within the current
+   * method.
+   */
+  private int localCounter;
+
+  /**
+   * Map of type onto lazily-created local tracker.
+   */
+  private Map<JType, TempLocalTracker> localTrackers = new IdentityHashMap<JType, TempLocalTracker>();
+
+  /**
+   * If <code>true</code>, reuse temps. The pre-optimization subclass does
+   * not reuse temps because doing so can defeat optimizations because different
+   * uses impact each other and we do nothing to disambiguate usage. After
+   * optimizations, it makes sense to reuse temps to reduce code size and memory
+   * consumption of the output.
+   */
   private final boolean reuseTemps;
-  private List<JLocal> tempLocals;
-  private int tempLocalsIndex;
-  private Stack<Integer> usageScopeStarts;
 
   protected CompoundAssignmentNormalizer(JProgram program, boolean reuseTemps) {
     this.program = program;
@@ -322,6 +395,8 @@
     return lhs;
   }
 
+  protected abstract String getTempPrefix();
+
   protected abstract boolean shouldBreakUp(JBinaryOperation x);
 
   protected abstract boolean shouldBreakUp(JPostfixOperation x);
@@ -329,32 +404,47 @@
   protected abstract boolean shouldBreakUp(JPrefixOperation x);
 
   private void clearLocals() {
-    tempLocals = new ArrayList<JLocal>();
-    tempLocalsIndex = 0;
-    usageScopeStarts = new Stack<Integer>();
+    localCounter = 0;
+    localTrackers.clear();
   }
 
   private void enterTempUsageScope() {
-    usageScopeStarts.push(tempLocalsIndex);
+    for (TempLocalTracker tracker : localTrackers.values()) {
+      tracker.enterScope();
+    }
   }
 
   private void exitTempUsageScope() {
-    tempLocalsIndex = usageScopeStarts.pop();
+    for (TempLocalTracker tracker : localTrackers.values()) {
+      tracker.exitScope();
+    }
   }
 
   /**
    * Allocate a temporary local variable.
    */
   private JLocal getTempLocal(JType type) {
-    if (reuseTemps) {
-      if (tempLocalsIndex < tempLocals.size()) {
-        return tempLocals.get(tempLocalsIndex++);
-      }
+    TempLocalTracker tracker = localTrackers.get(type);
+    if (tracker == null) {
+      tracker = new TempLocalTracker();
+      localTrackers.put(type, tracker);
     }
 
-    JLocal newTemp = program.createLocal(null,
-        ("$t" + localCounter++).toCharArray(), type, false, currentMethodBody);
-    tempLocals.add(newTemp);
-    return newTemp;
+    JLocal temp = null;
+    if (reuseTemps) {
+      /*
+       * If the return is non-null, we now "own" the returned JLocal; it's
+       * important to call tracker.useLocal() on the returned value (below).
+       */
+      temp = tracker.tryGetReusableLocal();
+    }
+
+    if (temp == null) {
+      temp = program.createLocal(null,
+          (getTempPrefix() + localCounter++).toCharArray(), type, false,
+          currentMethodBody);
+    }
+    tracker.useLocal(temp);
+    return temp;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index b179a14..5de3ae9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -993,6 +993,23 @@
       }
     }
 
+    /**
+     * If the effect of <code>statement</code> is to immediately do a break,
+     * then return the {@link JBreakStatement} corresponding to that break.
+     */
+    private JBreakStatement findUnconditionalBreak(JStatement statement) {
+      if (statement instanceof JBreakStatement) {
+        return (JBreakStatement) statement;
+      } else if (statement instanceof JBlock) {
+        JBlock block = (JBlock) statement;
+        List<JStatement> blockStmts = block.statements;
+        if (blockStmts.size() > 0 && isUnconditionalBreak(blockStmts.get(0))) {
+          return (JBreakStatement) blockStmts.get(0);
+        }
+      }
+      return null;
+    }
+
     private boolean hasNoDefaultCase(JSwitchStatement x) {
       JBlock body = x.getBody();
       boolean inDefault = false;
@@ -1002,7 +1019,7 @@
           if (caseStmt.getExpr() == null) {
             inDefault = true;
           }
-        } else if (isUnconditionalBreak(statement)) {
+        } else if (isUnconditionalUnlabeledBreak(statement)) {
           inDefault = false;
         } else {
           // We have some code to execute other than a break.
@@ -1134,15 +1151,16 @@
     }
 
     private boolean isUnconditionalBreak(JStatement statement) {
-      if (statement instanceof JBreakStatement) {
-        return true;
-      } else if (statement instanceof JBlock) {
-        JBlock block = (JBlock) statement;
-        List<JStatement> blockStmts = block.statements;
-        return blockStmts.size() > 0 && isUnconditionalBreak(blockStmts.get(0));
-      } else {
-        return false;
-      }
+      return findUnconditionalBreak(statement) != null;
+    }
+
+    private boolean isUnconditionalUnlabeledBreak(JStatement statement) {
+      JBreakStatement breakStat = findUnconditionalBreak(statement);
+      return (breakStat != null) && (breakStat.getLabel() == null);
+    }
+
+    private <T> T last(List<T> statements) {
+      return statements.get(statements.size() - 1);
     }
 
     private Class<?> mapType(JType type) {
@@ -1192,7 +1210,8 @@
       }
 
       // Remove a trailing break statement from a case block
-      if (lastWasBreak && body.statements.size() > 0) {
+      if (body.statements.size() > 0
+          && isUnconditionalUnlabeledBreak(last(body.statements))) {
         body.statements.remove(body.statements.size() - 1);
         didChange = true;
       }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java
index a5f9d9d..130d511 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java
@@ -62,6 +62,11 @@
     }
 
     @Override
+    protected String getTempPrefix() {
+      return "$tunbox";
+    }
+
+    @Override
     protected boolean shouldBreakUp(JBinaryOperation x) {
       return isUnboxExpression(x.getLhs());
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
index e67dd23..271f9f5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
@@ -36,6 +36,11 @@
   }
 
   @Override
+  protected String getTempPrefix() {
+    return "$t";
+  }
+
+  @Override
   protected boolean shouldBreakUp(JBinaryOperation x) {
     if (x.getType() == program.getTypePrimitiveLong()) {
       return true;
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
index b663f76..c381889 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
@@ -298,12 +298,12 @@
           if (onModuleLoad == null) {
             module = rebindAndCreate(entryPointTypeName);
             onModuleLoad = module.getClass().getMethod("onModuleLoad");
+            // Record the rebound name of the class for stats (below).
+            entryPointTypeName = module.getClass().getName().replace('$', '.');
           }
           onModuleLoad.setAccessible(true);
-          String reboundSourceName = module.getClass().getName().replace('$',
-              '.');
           invokeNativeVoid("fireOnModuleLoadStart", null,
-              new Class[] {String.class}, new Object[] {reboundSourceName});
+              new Class[] {String.class}, new Object[] {entryPointTypeName});
           onModuleLoad.invoke(module);
         }
       } else {
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
index 2c3e989..da56a75 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
@@ -68,6 +68,10 @@
       assertEquals(substitution.getSubstitution(preSubParam.getType()),
           postSubParam.getType());
     }
+
+    JType[] preSubThrows = preSubMethod.getThrows();
+    JType[] postSubThrows = postSubMethod.getThrows();
+    assertArraysEqual(preSubThrows, postSubThrows);
   }
 
   protected static void validateAnnotations(Annotation[] expected,
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/GenericClass.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/GenericClass.java
index f328c00..b9b39bb 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/GenericClass.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/GenericClass.java
@@ -29,7 +29,8 @@
  * definition of GenericClass is as follows: class GenericClass<T extends
  * Serializable & Comparable<T>> implements Comparable<T> { ... }
  */
-public class GenericClass<T extends Serializable> implements Comparable<T>, Serializable {
+public class GenericClass<T extends Serializable> implements Comparable<T>,
+    Serializable {
   /**
    * Non-static, generic inner class.
    * 
@@ -63,8 +64,7 @@
    * NOTE: The following is disabled because it violates an assumption in TOB
    * line 1228.
    */
-//  GenericClass.NonGenericInnerClass rawNonGenericInnerClassField;
-
+  // GenericClass.NonGenericInnerClass rawNonGenericInnerClassField;
   Class rawClazzField;
 
   /**
@@ -112,7 +112,7 @@
     return collection.iterator().next();
   }
 
-  public void setT(T t) {
+  public void setT(T t) throws Exception {
     this.typeParameterField = t;
   }
 }
\ No newline at end of file
diff --git a/servlet/build.xml b/servlet/build.xml
index 0a830ff..2b8cb39 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -15,11 +15,10 @@
 				<exclude name="**/tools/**" />
 				<exclude name="com/google/gwt/json/**" />
 				<exclude name="com/google/gwt/junit/*" />
-				<exclude name="com/google/gwt/junit/benchmarks/**" />
-				<exclude name="com/google/gwt/junit/client/Benchmark.*" />
 				<exclude name="com/google/gwt/junit/client/GWTTestCase.*" />
 				<exclude name="com/google/gwt/junit/remote/**" />
 				<exclude name="com/google/gwt/junit/server/**" />
+				<exclude name="com/google/gwt/benchmarks/*" />
 			</fileset>
 		</gwt.jar>
 	</target>
diff --git a/user/src/com/google/gwt/dom/client/DOMImpl.java b/user/src/com/google/gwt/dom/client/DOMImpl.java
index 3369ea2..2c0cb1e 100644
--- a/user/src/com/google/gwt/dom/client/DOMImpl.java
+++ b/user/src/com/google/gwt/dom/client/DOMImpl.java
@@ -166,6 +166,10 @@
     select.add(option, before);
   }-*/;
 
+  public native void selectClear(SelectElement select) /*-{
+    select.options.length = 0;
+  }-*/;
+
   public native int selectGetLength(SelectElement select) /*-{
     return select.options.length;
   }-*/;
diff --git a/user/src/com/google/gwt/dom/client/DOMImplSafari.java b/user/src/com/google/gwt/dom/client/DOMImplSafari.java
index 098d70c..1a5d930 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplSafari.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplSafari.java
@@ -124,6 +124,10 @@
    * not work. However, this bug does not cause problems in the case of <SELECT>
    * elements, because their descendent elements are only one level deep.
    */
+  @Override
+  public native void selectClear(SelectElement select) /*-{
+    select.innerText = '';
+  }-*/;
 
   @Override
   public native int selectGetLength(SelectElement select) /*-{
diff --git a/user/src/com/google/gwt/dom/client/SelectElement.java b/user/src/com/google/gwt/dom/client/SelectElement.java
index 7ca4952..0755722 100644
--- a/user/src/com/google/gwt/dom/client/SelectElement.java
+++ b/user/src/com/google/gwt/dom/client/SelectElement.java
@@ -60,6 +60,13 @@
   }-*/;
 
   /**
+   * Removes all OPTION elements from this SELECT.
+   */
+  public final void clear() {
+    DOMImpl.impl.selectClear(this);
+  }
+
+  /**
    * Gives keyboard focus to this element.
    */
   public final native void focus() /*-{
diff --git a/user/src/com/google/gwt/dom/client/TableCellElement.java b/user/src/com/google/gwt/dom/client/TableCellElement.java
index bc5c3c8..b78851b 100644
--- a/user/src/com/google/gwt/dom/client/TableCellElement.java
+++ b/user/src/com/google/gwt/dom/client/TableCellElement.java
@@ -27,7 +27,8 @@
    * automatically typecast it.
    */
   public static TableCellElement as(Element elem) {
-    assert elem.getTagName().equalsIgnoreCase("td");
+    assert elem.getTagName().equalsIgnoreCase("td")
+        || elem.getTagName().equalsIgnoreCase("th");
     return (TableCellElement) elem;
   }
 
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index 03036b4..71b44e2 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -24,6 +24,7 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.i18n.client.LocalizableResource.Generate;
 import com.google.gwt.i18n.client.LocalizableResource.Key;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
 import com.google.gwt.i18n.rebind.format.MessageCatalogFormat;
 import com.google.gwt.i18n.rebind.keygen.KeyGenerator;
@@ -86,14 +87,14 @@
       throw error(logger, name + " must be an interface");
     }
 
-    AbstractResource resource = null;
+    ResourceList resourceList = null;
     try {
-      resource = ResourceFactory.getBundle(logger, targetClass, locale,
+      resourceList = ResourceFactory.getBundle(logger, targetClass, locale,
           assignableToConstants);
     } catch (MissingResourceException e) {
       throw error(
           logger,
-          "Localization failed; there must be at least one properties file accessible through"
+          "Localization failed; there must be at least one resource accessible through"
               + " the classpath in package '"
               + packageName
               + "' whose base name is '"
@@ -121,17 +122,17 @@
       // Now that we have all the information set up, process the class
       if (constantsWithLookupClass.isAssignableFrom(targetClass)) {
         ConstantsWithLookupImplCreator c = new ConstantsWithLookupImplCreator(
-            logger, deprecatedLogger, writer, targetClass, resource,
+            logger, deprecatedLogger, writer, targetClass, resourceList,
             context.getTypeOracle());
         c.emitClass(logger, locale);
       } else if (constantsClass.isAssignableFrom(targetClass)) {
         ConstantsImplCreator c = new ConstantsImplCreator(logger,
-            deprecatedLogger, writer, targetClass, resource,
+            deprecatedLogger, writer, targetClass, resourceList,
             context.getTypeOracle());
         c.emitClass(logger, locale);
       } else {
         MessagesImplCreator messages = new MessagesImplCreator(logger,
-            deprecatedLogger, writer, targetClass, resource,
+            deprecatedLogger, writer, targetClass, resourceList,
             context.getTypeOracle());
         messages.emitClass(logger, locale);
       }
@@ -207,7 +208,7 @@
               throw error(logger, e.getMessage());
             }
             try {
-              msgWriter.write(branch, resource, out, targetClass);
+              msgWriter.write(branch, locale, resourceList, out, targetClass);
               out.flush();
               context.commitResource(logger, outStr).setPrivate(true);
             } catch (UnableToCompleteException e) {
@@ -234,7 +235,7 @@
   /**
    * The Dictionary/value bindings used to determine message contents.
    */
-  private AbstractResource messageBindings;
+  private ResourceList resourceList;
 
   /**
    * Logger to use for deprecated warnings.
@@ -252,14 +253,14 @@
    * @param deprecatedLogger logger to use for deprecated warnings.
    * @param writer writer
    * @param targetClass current target
-   * @param messageBindings backing resource
+   * @param resourceList backing resource
    */
   public AbstractLocalizableImplCreator(TreeLogger logger,
       TreeLogger deprecatedLogger, SourceWriter writer, JClassType targetClass,
-      AbstractResource messageBindings, boolean isConstants) {
+      ResourceList resourceList, boolean isConstants) {
     super(writer, targetClass);
     this.deprecatedLogger = deprecatedLogger;
-    this.messageBindings = messageBindings;
+    this.resourceList = resourceList;
     this.isConstants = isConstants;
     try {
       keyGenerator = AnnotationsResource.getKeyGenerator(targetClass);
@@ -274,8 +275,8 @@
    * 
    * @return the resource
    */
-  public AbstractResource getResourceBundle() {
-    return messageBindings;
+  public ResourceList getResourceBundle() {
+    return resourceList;
   }
 
   @Override
@@ -301,7 +302,7 @@
           + method.getName(), null);
       throw new UnableToCompleteException();
     }
-    methodCreator.createMethodFor(logger, method, key, messageBindings, locale);
+    methodCreator.createMethodFor(logger, method, key, resourceList, locale);
   }
 
   /**
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
index 260cd82..a572764 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.i18n.rebind;
 
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 
@@ -41,20 +42,11 @@
   }
 
   /**
-   * Gets the associated locale.
-   * 
-   * @return the locale
-   */
-  protected String getLocaleName() {
-    return getResources().getLocaleName();
-  }
-
-  /**
    * Get the resources associated with this class.
    * 
    * @return associated resources.
    */
-  protected AbstractResource getResources() {
+  protected ResourceList getResources() {
     return ((ConstantsImplCreator) currentCreator).getResourceBundle();
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractResource.java b/user/src/com/google/gwt/i18n/rebind/AbstractResource.java
index bd3f0fc..dea4cb7 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractResource.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractResource.java
@@ -18,10 +18,12 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.i18n.client.PluralRule.PluralForm;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -43,8 +45,9 @@
  * parent is associated with a separate resource tree.
  */
 public abstract class AbstractResource {
+
   /**
-   * Exception indicating a require resource was not found.
+   * Exception indicating a required resource was not found.
    */
   public static class MissingResourceException extends RuntimeException {
     private String key;
@@ -85,6 +88,211 @@
   }
 
   /**
+   * Encapsulates an ordered set of resources to search for translations.
+   */
+  public static class ResourceList extends AbstractList<AbstractResource>
+      implements List<AbstractResource>, Set<AbstractResource> {
+
+    private List<AbstractResource> list = new ArrayList<AbstractResource>();
+
+    private Set<AbstractResource> set = new HashSet<AbstractResource>();
+    
+    private Map<String, PluralForm[]> pluralForms = new HashMap<String, PluralForm[]>();
+
+    @Override
+    public boolean add(AbstractResource element) {
+      if (set.contains(element)) {
+        return false;
+      }
+      set.add(element);
+      return list.add(element);
+    }
+
+    @Override
+    public void add(int index, AbstractResource element) {
+      if (set.contains(element)) {
+        throw new IllegalArgumentException("Duplicate element");
+      }
+      set.add(element);
+      list.add(index, element);
+    }
+
+    /**
+     * Add all keys known by this ResourceList to the specified set.
+     * 
+     * @param s set to add keys to
+     */
+    public void addToKeySet(Set<String> s) {
+      for (AbstractResource resource : list) {
+        resource.addToKeySet(s);
+      }
+    }
+
+    @Override
+    public AbstractResource get(int index) {
+      return list.get(index);
+    }
+
+    /**
+     * Return the first AnnotationsResource containing a specified key.
+     * 
+     * @param logger
+     * @param key
+     * @return first AnnotationsResource containing key, or null if none
+     */
+    public AnnotationsResource getAnnotationsResource(TreeLogger logger,
+        String key) {
+      for (AbstractResource resource : list) {
+        if (resource instanceof AnnotationsResource
+            && resource.keySet.contains(key)) {
+          return (AnnotationsResource) resource;
+        }
+      }
+      return null;
+    }
+
+    /**
+     * Return the list of extensions available for a given key.
+     * 
+     * @param key
+     * @return collection of extensions for the given key
+     */
+    public Collection<String> getExtension(String key) {
+      Set<String> extensions = new HashSet<String>();
+      for (AbstractResource resource : list) {
+        extensions.addAll(resource.getExtensions(key));
+      }
+      return extensions;
+    }
+
+    /**
+     * Return the list of plural forms for a given key.
+     *
+     * @param key
+     * @return array of plural forms.
+     */
+    public PluralForm[] getPluralForms(String key) {
+      return pluralForms.get(key);
+    }
+
+    /**
+     * Return a translation for a key, or throw an exception.
+     * 
+     * @param key
+     * @return translated string for key
+     * @throws MissingResourceException
+     */
+    public String getRequiredString(String key)
+        throws MissingResourceException {
+      String val = getString(key);
+      if (val == null) {
+        throw new MissingResourceException(key, list);
+      }
+      return val;
+    }
+
+    /**
+     * Return a translation for a key/extension, or throw an exception.
+     * 
+     * @param key
+     * @param ext key extension, null if none
+     * @return translated string for key
+     * @throws MissingResourceException
+     */
+    public String getRequiredStringExt(String key, String ext)
+        throws MissingResourceException {
+      String val = getStringExt(key, ext);
+      if (val == null) {
+        throw new MissingResourceException(getExtendedKey(key, ext), list);
+      }
+      return val;
+    }
+
+    /**
+     * Return a translation for a key, or null if not found.
+     * 
+     * @param key
+     * @return translated string for key
+     */
+    public String getString(String key) {
+      for (AbstractResource resource : list) {
+        String s = resource.getStringExt(key, null);
+        if (s != null) {
+          return s;
+        }
+      }
+      return null;
+    }
+
+    /**
+     * Return a translation for a key/extension, or null if not found.
+     * 
+     * @param key
+     * @param extension key extension, null if none
+     * @return translated string for key
+     */
+    public String getStringExt(String key, String extension) {
+      for (AbstractResource resource : list) {
+        String s = resource.getStringExt(key, extension);
+        if (s != null) {
+          return s;
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public int indexOf(Object o) {
+      return list.indexOf(o);
+    }
+
+    @Override
+    public Iterator<AbstractResource> iterator() {
+      return list.iterator();
+    }
+
+    /**
+     * @return set of keys present across all resources
+     */
+    public Set<String> keySet() {
+      Set<String> keySet = new HashSet<String>();
+      for (AbstractResource resource : list) {
+        keySet.addAll(resource.keySet());
+      }
+      return keySet;
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+      return list.lastIndexOf(o);
+    }
+
+    @Override
+    public AbstractResource remove(int index) {
+      AbstractResource element = list.remove(index);
+      set.remove(element);
+      return element;
+    }
+
+    /**
+     * Set the plural forms associated with a given message.
+     * 
+     * @param key
+     * @param forms
+     */
+    public void setPluralForms(String key, PluralForm[] forms) {
+      if (!pluralForms.containsKey(key)) {
+        pluralForms.put(key, forms);
+      }
+    }
+
+    @Override
+    public int size() {
+      return list.size();
+    }
+  }
+
+  /**
    * Error messages concerning missing keys should include the defined keys if
    * the number of keys is below this threshold.
    */
@@ -97,82 +305,46 @@
     return key;
   }
 
-  private final List<AbstractResource> alternativeParents = new ArrayList<AbstractResource>();
-
   private Set<String> keySet;
 
-  private Map<String, PluralForm[]> pluralFormMap = new HashMap<String, PluralForm[]>();
-
-  private String localeName;
-
   private String path;
 
-  private AbstractResource primaryParent;
-
-  /**
-   * Walk up the resource inheritance tree until we find one which is an
-   * instance of AnnotationsResource.
-   * 
-   * This is needed so the original Annotations metadata can be used even in
-   * inherited resources, such as from a properties file for a specific locale.
-   * 
-   * TODO(jat): really bad code smell -- the superclass should not know about a
-   * particular implementation, but I couldn't think of a decent way around it.
-   * Long term, this whole structure needs to be be redone to make use of
-   * resources that fundamentally don't look like property files to be used
-   * effectively. I don't think it is feasible to do that in time for 1.5, so I
-   * am continuing with this hacky solution but we need to look at this after
-   * 1.5 is done.
-   */
-  public AnnotationsResource getAnnotationsResource() {
-    AbstractResource resource = this;
-    while (resource != null && !(resource instanceof AnnotationsResource)) {
-      resource = resource.primaryParent;
-    }
-    return (AnnotationsResource) resource;
-  }
-
   public Collection<String> getExtensions(String key) {
     return new ArrayList<String>();
   }
 
   /**
-   * @see java.util.ResourceBundle#getLocale()
+   * Get a string and fail if not present.
+   * 
+   * @param key
+   * @return the requested string
    */
-  public String getLocaleName() {
-    return localeName;
-  }
-
-  /**
-   * @see java.util.ResourceBundle#getObject(java.lang.String)
-   */
-  public final Object getObject(String key) {
-    Object s = getObjectAux(key, true, true);
-    return s;
-  }
-
-  public PluralForm[] getPluralForms(String key) {
-    return pluralFormMap.get(key);
+  public final String getRequiredString(String key) {
+    return getRequiredStringExt(key, null);
   }
 
   /**
    * Get a string (with optional extension) and fail if not present.
    * 
-   * @param logger
    * @param key
    * @param extension
    * @return the requested string
    */
-  public final String getRequiredStringExt(TreeLogger logger, String key,
-      String extension) {
-    return extension == null ? (String) getObjectAux(key, true, true) : null;
+  public final String getRequiredStringExt(String key, String extension) {
+    String s = getStringExt(key, extension);
+    if (s == null) {
+      ArrayList<AbstractResource> list = new ArrayList<AbstractResource>();
+      list.add(this);
+      throw new MissingResourceException(key, list);
+    }
+    return s;
   }
 
   /**
    * @see java.util.ResourceBundle#getString(java.lang.String)
    */
   public final String getString(String key) {
-    return (String) getObjectAux(key, true);
+    return getStringExt(key, null);
   }
 
   /**
@@ -182,11 +354,7 @@
    * @param extension extension of the key, nullable
    * @return string or null
    */
-  public String getStringExt(String key, String extension) {
-    return extension == null ? getString(key) : null;
-  }
-
-  public abstract Object handleGetObject(String key);
+  public abstract String getStringExt(String key, String extension);
 
   /**
    * Keys associated with this resource.
@@ -197,19 +365,15 @@
     if (keySet == null) {
       keySet = new HashSet<String>();
       addToKeySet(keySet);
-      if (primaryParent != null) {
-        primaryParent.addToKeySet(keySet);
-      }
-      for (int i = 0; i < alternativeParents.size(); i++) {
-        AbstractResource element = alternativeParents.get(i);
-        keySet.addAll(element.keySet());
-      }
     }
     return keySet;
   }
 
-  public void setPluralForms(String key, PluralForm[] pluralForms) {
-    pluralFormMap.put(key, pluralForms);
+  /**
+   * @return true if this resource has any keys
+   */
+  public boolean notEmpty() {
+    return !keySet.isEmpty();
   }
 
   @Override
@@ -228,98 +392,16 @@
     return b.toString();
   }
 
-  void addAlternativeParent(AbstractResource parent) {
-    if (parent != null) {
-      alternativeParents.add(parent);
-    }
-  }
-
   abstract void addToKeySet(Set<String> s);
 
-  void checkKeys() {
-    // If I don't have a parent, then I am a default node so do not need to
-    // conform
-    if (primaryParent == null) {
-      return;
-    }
-    for (String key : keySet()) {
-      if (primaryParent.getObjectAux(key, true) == null) {
-        for (int i = 0; i < alternativeParents.size(); i++) {
-          AbstractResource alt = alternativeParents.get(i);
-          if (alt.getObjectAux(key, true) != null) {
-            break;
-          }
-        }
-
-        throw new IllegalArgumentException(
-            key
-                + " is not a valid resource key as it does not occur in the default version of "
-                + this + " nor in any of " + alternativeParents);
-      }
-    }
-  }
-
-  final Object getObjectAux(String key, boolean useAlternativeParents) {
-    try {
-      return getObjectAux(key, useAlternativeParents, false);
-    } catch (MissingResourceException e) {
-      // Can't happen since we pass required=false
-      throw new RuntimeException("Unexpected MissingResourceException", e);
-    }
-  }
-
-  final Object getObjectAux(String key, boolean useAlternativeParents,
-      boolean required) throws MissingResourceException {
-    ArrayList<AbstractResource> searched = new ArrayList<AbstractResource>();
-    searched.add(this);
-    Object s = handleGetObject(key);
-    if (s != null) {
-      return s;
-    }
-    AbstractResource parent = this.getPrimaryParent();
-    if (parent != null) {
-      // Primary parents should not look at their alternative parents
-      searched.add(parent);
-      s = parent.getObjectAux(key, false);
-    }
-    if ((s == null) && (alternativeParents.size() > 0)
-        && (useAlternativeParents)) {
-      for (int i = 0; (i < alternativeParents.size()) && (s == null); i++) {
-        // Alternate parents may look at their alternative parents.
-        AbstractResource altParent = alternativeParents.get(i);
-        searched.add(altParent);
-        s = altParent.getObjectAux(key, true);
-      }
-    }
-    if (s == null && required) {
-      throw new MissingResourceException(key, searched);
-    }
-    return s;
-  }
-
   String getPath() {
     return path;
   }
 
-  AbstractResource getPrimaryParent() {
-    return primaryParent;
-  }
-
-  void setLocaleName(String locale) {
-    this.localeName = locale;
-  }
-
   void setPath(String path) {
     this.path = path;
   }
 
-  void setPrimaryParent(AbstractResource primaryParent) {
-    if (primaryParent == null) {
-      return;
-    }
-    this.primaryParent = primaryParent;
-  }
-
   private void newLine(int indent, StringBuffer buf) {
     buf.append("\n");
     for (int i = 0; i < indent; i++) {
@@ -330,16 +412,5 @@
   private void toVerboseStringAux(int indent, StringBuffer buf) {
     newLine(indent, buf);
     buf.append(toString());
-    if (primaryParent != null) {
-      newLine(indent, buf);
-      buf.append("Primary Parent: ");
-      primaryParent.toVerboseStringAux(indent + 1, buf);
-    }
-    for (int i = 0; i < alternativeParents.size(); i++) {
-      newLine(indent, buf);
-      buf.append("Alternate Parent: ");
-      AbstractResource element = alternativeParents.get(i);
-      element.toVerboseStringAux(indent + 1, buf);
-    }
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
index 9c21deb..87b9d05 100644
--- a/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
+++ b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
@@ -25,6 +25,7 @@
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JRawType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.i18n.client.PluralRule;
 import com.google.gwt.i18n.client.Constants.DefaultBooleanValue;
 import com.google.gwt.i18n.client.Constants.DefaultDoubleValue;
 import com.google.gwt.i18n.client.Constants.DefaultFloatValue;
@@ -32,6 +33,7 @@
 import com.google.gwt.i18n.client.Constants.DefaultStringArrayValue;
 import com.google.gwt.i18n.client.Constants.DefaultStringMapValue;
 import com.google.gwt.i18n.client.Constants.DefaultStringValue;
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
 import com.google.gwt.i18n.client.LocalizableResource.Description;
 import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
 import com.google.gwt.i18n.client.LocalizableResource.Key;
@@ -76,6 +78,7 @@
     public boolean isPluralCount;
     public String name;
     public boolean optional;
+    public Class<? extends PluralRule> pluralRuleClass;
 
     public ArgumentInfo(String name) {
       this.name = name;
@@ -364,14 +367,23 @@
    * 
    * @param logger
    * @param clazz
+   * @param locale
    * @param isConstants
    * @throws AnnotationsError if there is a fatal error while processing
    *           annotations
    */
   public AnnotationsResource(TreeLogger logger, JClassType clazz,
-      boolean isConstants) throws AnnotationsError {
+      String locale, boolean isConstants) throws AnnotationsError {
     KeyGenerator keyGenerator = getKeyGenerator(clazz);
     map = new HashMap<String, MethodEntry>();
+    setPath(clazz.getQualifiedSourceName());
+    DefaultLocale defLocale = clazz.getAnnotation(DefaultLocale.class);
+    if (defLocale != null && !ResourceFactory.DEFAULT_TOKEN.equals(locale)
+        && !locale.equalsIgnoreCase(defLocale.value())) {
+      logger.log(TreeLogger.WARN, "@DefaultLocale on "
+          + clazz.getQualifiedSourceName() + " doesn't match " + locale);  
+      return;
+    }
     for (JMethod method : clazz.getMethods()) {
       String meaningString = null;
       Meaning meaning = method.getAnnotation(Meaning.class);
@@ -409,10 +421,10 @@
         if ((pluralForms.length & 1) != 0) {
           throw new AnnotationsError(
               "Odd number of strings supplied to @PluralText: must be"
-                  + " pairs of form names and strings");
+              + " pairs of form names and strings");
         }
         for (int i = 0; i + 1 < pluralForms.length; i += 2) {
-          entry.pluralText.put(pluralForms[i], pluralForms[i + 1]);
+          entry.addPluralText(pluralForms[i], pluralForms[i + 1]);
         }
       }
       for (JParameter param : method.getParameters()) {
@@ -431,7 +443,6 @@
         }
       }
     }
-    setPath(clazz.getQualifiedSourceName());
   }
 
   @Override
@@ -462,27 +473,22 @@
 
   @Override
   public String getStringExt(String key, String extension) {
-    if (extension == null) {
-      return getString(key);
-    }
     MethodEntry entry = map.get(key);
-    return entry == null ? null : entry.pluralText.get(extension);
+    if (entry == null) {
+      return null;
+    }
+    if (extension != null) {
+      return entry.pluralText.get(extension);
+    } else {
+      return entry.text;
+    }
   }
 
   @Override
-  public Object handleGetObject(String key) {
-    MethodEntry entry = map.get(key);
-    return entry == null ? null : entry.text;
-  }
-
   public boolean notEmpty() {
     return !map.isEmpty();
   }
 
-  public void setParentResource(AnnotationsResource parent) {
-    setPrimaryParent(parent);
-  }
-
   @Override
   public String toString() {
     return "Annotations from class " + getPath();
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
index 7d601f6..f189751 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -26,6 +26,7 @@
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.SourceWriter;
 
 import java.util.Map;
@@ -47,14 +48,14 @@
    * @param deprecatedLogger logger to use for deprecated warnings
    * @param writer <code>Writer</code> to print to
    * @param localizableClass class/interface to conform to
-   * @param messageBindings resource bundle used to generate the class
+   * @param resourceList resource bundle used to generate the class
    * @param oracle types
    * @throws UnableToCompleteException
    */
   public ConstantsImplCreator(TreeLogger logger, TreeLogger deprecatedLogger, SourceWriter writer,
-      JClassType localizableClass, AbstractResource messageBindings,
+      JClassType localizableClass, ResourceList resourceList,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(logger, deprecatedLogger, writer, localizableClass, messageBindings, true);
+    super(logger, deprecatedLogger, writer, localizableClass, resourceList, true);
     try {
       JClassType stringClass = oracle.getType(String.class.getName());
       JClassType mapClass = oracle.getType(Map.class.getName());
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
index 2176733..e18625c 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.i18n.client.impl.ConstantMap;
 import com.google.gwt.i18n.rebind.AbstractResource.MissingResourceException;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
 /**
@@ -40,12 +41,12 @@
    * @param logger TreeLogger instance for logging
    * @param method method body to create
    * @param key value to create map from
-   * @param resource AbstractResource for key lookup
+   * @param resourceList AbstractResource for key lookup
    * @param locale locale to use for localized string lookup
    */
   @Override
   public void createMethodFor(TreeLogger logger, JMethod method, String key,
-      AbstractResource resource, String locale) {
+      ResourceList resourceList, String locale) {
     String methodName = method.getName();
     if (method.getParameters().length > 0) {
       error(
@@ -66,7 +67,7 @@
     println("args = new " + constantMapClassName + "();");
     String value;
     try {
-      value = resource.getRequiredStringExt(logger, key, null);
+      value = resourceList.getRequiredStringExt(key, null);
     } catch (MissingResourceException e) {
       e.setDuring("getting key list");
       throw e;
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
index 1cd2e33..358010e 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
 /**
@@ -60,7 +61,7 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod method, String key,
-      AbstractResource resource, String locale) {
+      ResourceList resource, String locale) {
     String methodName = method.getName();
     // Make sure cache exists.
     enableCache();
@@ -71,7 +72,7 @@
     indent();
     println("String [] writer= {");
     indent();
-    String template = resource.getRequiredStringExt(logger, key, null);
+    String template = resource.getRequiredStringExt(key, null);
     String[] args = split(template);
     for (int i = 0; i < args.length; i++) {
       String toPrint = args[i].replaceAll("\\,", ",");
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
index 73958ba..433f698 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -24,6 +24,7 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
 import com.google.gwt.i18n.client.impl.ConstantMap;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -43,15 +44,15 @@
    * @param deprecatedLogger logger to use for deprecated warnings
    * @param writer <code>Writer</code> to print to
    * @param localizableClass class/interface to conform to
-   * @param messageBindings resource bundle used to generate the class
+   * @param resourceList resource bundle used to generate the class
    * @param oracle types
    * @throws UnableToCompleteException
    */
   ConstantsWithLookupImplCreator(TreeLogger logger,
       TreeLogger deprecatedLogger, SourceWriter writer,
-      JClassType localizableClass, AbstractResource messageBindings,
+      JClassType localizableClass, ResourceList resourceList,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(logger, deprecatedLogger, writer, localizableClass, messageBindings,
+    super(logger, deprecatedLogger, writer, localizableClass, resourceList,
         oracle);
     try {
 
diff --git a/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java b/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
index 8ccd20a..22d9f17 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -69,23 +69,17 @@
   @Override
   public String getStringExt(String key, String extension) {
     if (extension != null) {
-      key += '[' + extension + ']';
-      /*
-       * Do not look up key extensions in other files, as they may not be
-       * relevant.  For example, if you are looking up the "one" French
-       * plural form and it isn't present, using the default fallback (which
-       * will typically be English) will result in an incorrect translation.
-       * Better to give a warning about a missing plural form and use the
-       * default rather than silently use an incorrect one.
-       */
-      return (String) getObjectAux(key, false);
+      String s = getStringExt(getExtendedKey(key, extension), null);
+      if (s != null) {
+        return s;
+      }
     }
-    return getString(key);
+    return props.getProperty(key);
   }
 
   @Override
-  public Object handleGetObject(String key) {
-    return props.getProperty(key);
+  public boolean notEmpty() {
+    return props != null && !props.getPropertyMap().isEmpty();
   }
 
   @Override
diff --git a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
index 9f6d2bf..cb59124 100644
--- a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 import com.google.gwt.user.rebind.AbstractSourceCreator;
@@ -45,7 +46,7 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String key, AbstractResource resource, String locale) {
+      String key, ResourceList resourceList, String locale) {
     createMethodFor(targetMethod);
   }
 
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
index 883df33..3332695 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.SourceWriter;
 
 /**
@@ -34,16 +35,16 @@
    * 
    * @param writer <code>Writer</code> to print to
    * @param localizableClass Class/Interface to conform to
-   * @param messageBindings resource bundle used to generate the class
+   * @param resourceList resource bundle used to generate the class
    * @param oracle types
    * @param logger logger to print errors
    * @param deprecatedLogger logger for deprecated metadata warnings
    * @throws UnableToCompleteException
    */
   public MessagesImplCreator(TreeLogger logger, TreeLogger deprecatedLogger, SourceWriter writer,
-      JClassType localizableClass, AbstractResource messageBindings,
+      JClassType localizableClass, ResourceList resourceList,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(logger, deprecatedLogger, writer, localizableClass, messageBindings, false);
+    super(logger, deprecatedLogger, writer, localizableClass, resourceList, false);
     try {
       JClassType stringClass = oracle.getType(String.class.getName());
       register(stringClass, new MessagesMethodCreator(this));
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
index ff59e9f..25b7dbc 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -26,11 +26,12 @@
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.i18n.client.NumberFormat;
 import com.google.gwt.i18n.client.PluralRule;
-import com.google.gwt.i18n.client.PluralRule.PluralForm;
 import com.google.gwt.i18n.client.Messages.Optional;
 import com.google.gwt.i18n.client.Messages.PluralCount;
 import com.google.gwt.i18n.client.Messages.PluralText;
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
 import com.google.gwt.i18n.client.impl.plurals.DefaultRule;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 
@@ -289,7 +290,6 @@
    * Constructor for <code>MessagesMethodCreator</code>.
    * 
    * @param classCreator associated class creator
-   * @param localizableClass class we are generating methods for
    */
   public MessagesMethodCreator(AbstractGeneratorClassCreator classCreator) {
     super(classCreator);
@@ -297,7 +297,7 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod m, String key,
-      AbstractResource resource, String locale)
+      ResourceList resourceList, String locale)
       throws UnableToCompleteException {
     JParameter[] params = m.getParameters();
     int pluralParamIndex = -1;
@@ -343,12 +343,15 @@
           + "();\n");
       generated.append("switch (rule.select(arg" + pluralParamIndex + ")) {\n");
       PluralForm[] pluralForms = rule.pluralForms();
-      resource.setPluralForms(key, pluralForms);
+      resourceList.setPluralForms(key, pluralForms);
       // Skip default plural form (index 0); the fall-through case will handle
       // it.
       for (int i = 1; i < pluralForms.length; ++i) {
-        String template = resource.getStringExt(key, pluralForms[i].getName());
+        String template = resourceList.getStringExt(key,
+            pluralForms[i].getName());
         if (template != null) {
+          generated.append("  // " + pluralForms[i].getName() + " - "
+              + pluralForms[i].getDescription() + "\n");
           generated.append("  case " + i + ": return ");
           generateString(logger, template, params, seenFlags, generated);
           generated.append(";\n");
@@ -362,7 +365,7 @@
       generated.append("}\n");
     }
     generated.append("return ");
-    String template = resource.getRequiredStringExt(logger, key, null);
+    String template = resourceList.getRequiredStringExt(key, null);
     generateString(logger, template, params, seenFlags, generated);
 
     // Generate an error if any required parameter was not used somewhere.
@@ -447,6 +450,7 @@
    * @param outputBuf
    * @throws UnableToCompleteException
    */
+  @SuppressWarnings("fallthrough")
   private void generateString(TreeLogger logger, String template,
       JParameter[] params, boolean[] seenFlag, StringBuffer outputBuf)
       throws UnableToCompleteException {
diff --git a/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
index c249d3a..ee5f501 100644
--- a/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
+++ b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
@@ -18,14 +18,15 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
 
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.MissingResourceException;
 import java.util.Set;
 
 /**
@@ -135,20 +136,7 @@
   public static final String DEFAULT_TOKEN = "default";
   public static final char LOCALE_SEPARATOR = '_';
 
-  public static final AbstractResource NOT_FOUND = new AbstractResource() {
-
-    @Override
-    public Object handleGetObject(String key) {
-      throw new IllegalStateException("Not found resource");
-    }
-
-    @Override
-    void addToKeySet(Set<String> s) {
-      throw new IllegalStateException("Not found resource");
-    }
-  };
-
-  private static Map<String, AbstractResource> cache = new HashMap<String, AbstractResource>();
+  private static Map<String, ResourceList> cache = new HashMap<String, ResourceList>();
 
   private static List<ResourceFactory> loaders = new ArrayList<ResourceFactory>();
   static {
@@ -162,48 +150,11 @@
     cache.clear();
   }
 
-  public static AbstractResource getAnnotations(TreeLogger logger, JClassType targetClass,
-      String locale, boolean isConstants) throws UnableToCompleteException {
-    Map<String, JClassType> matchingClasses
-        = LocalizableLinkageCreator.findDerivedClasses(logger, targetClass);
-    matchingClasses.put(ResourceFactory.DEFAULT_TOKEN, targetClass);
-    String localeSuffix = locale;
-    JClassType currentClass = null;
-    AnnotationsResource previous = null;
-    AbstractResource result = null;
-    while (true) {
-      currentClass = matchingClasses.get(localeSuffix);
-      if (currentClass != null) {
-        AnnotationsResource resource;
-        try {
-          resource = new AnnotationsResource(logger, currentClass, isConstants);
-        } catch (AnnotationsError e) {
-          logger.log(TreeLogger.ERROR, e.getMessage(), e);
-          throw new UnableToCompleteException();
-        }
-        if (resource.notEmpty()) {
-          if (result == null) {
-            result = resource;
-          }
-          if (previous != null) {
-            previous.setParentResource(resource);
-          }
-          previous = resource;
-        }
-      }
-      if (localeSuffix.equals(ResourceFactory.DEFAULT_TOKEN)) {
-        return result;
-      }
-      
-      localeSuffix = ResourceFactory.getParentLocaleName(localeSuffix);
-    }
-  }
-
-  public static AbstractResource getBundle(Class<?> clazz, String locale, boolean isConstants) {
+  public static ResourceList getBundle(Class<?> clazz, String locale, boolean isConstants) {
     return getBundle(TreeLogger.NULL, clazz, locale, isConstants);
   }
 
-  public static AbstractResource getBundle(String path, String locale, boolean isConstants) {
+  public static ResourceList getBundle(String path, String locale, boolean isConstants) {
     return getBundle(TreeLogger.NULL, path, locale, isConstants);
   }
 
@@ -214,7 +165,7 @@
    * @param locale locale name
    * @return the resource
    */
-  public static AbstractResource getBundle(TreeLogger logger, Class<?> javaInterface,
+  public static ResourceList getBundle(TreeLogger logger, Class<?> javaInterface,
       String locale, boolean isConstants) {
     if (javaInterface.isInterface() == false) {
       throw new IllegalArgumentException(javaInterface
@@ -231,7 +182,7 @@
    * @param locale locale name
    * @return the resource
    */
-  public static AbstractResource getBundle(TreeLogger logger, JClassType javaInterface,
+  public static ResourceList getBundle(TreeLogger logger, JClassType javaInterface,
       String locale, boolean isConstants) {
     return getBundleAux(logger, new JClassTypePathTree(javaInterface), javaInterface, locale,
         true, isConstants);
@@ -244,7 +195,7 @@
    * @param locale locale name
    * @return the resource
    */
-  public static AbstractResource getBundle(TreeLogger logger, String path, String locale,
+  public static ResourceList getBundle(TreeLogger logger, String path, String locale,
       boolean isConstants) {
     return getBundleAux(logger, new SimplePathTree(path), null, locale, true, isConstants);
   }
@@ -281,44 +232,49 @@
     return name;
   }
 
-  private static List<AbstractResource> findAlternativeParents(TreeLogger logger,
+  private static void addAlternativeParents(TreeLogger logger,
       ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
-      boolean isConstants) {
-    List<AbstractResource> altParents = null;
+      boolean useAlternativeParents, boolean isConstants,
+      ResourceList resources, Set<String> seenPaths) {
     if (tree != null) {
-      altParents = new ArrayList<AbstractResource>();
       for (int i = 0; i < tree.numChildren(); i++) {
         ResourceFactory.AbstractPathTree child = tree.getChild(i);
-        AbstractResource altParent = getBundleAux(logger, child, child.getJClassType(clazz),
-            locale, false, isConstants);
-        if (altParent != null) {
-          altParents.add(altParent);
-        }
+        addResources(logger, child, child.getJClassType(clazz),
+            locale, useAlternativeParents, isConstants, resources, seenPaths);
       }
     }
-    return altParents;
   }
 
-  private static AbstractResource findPrimaryParent(TreeLogger logger,
+  private static void addPrimaryParent(TreeLogger logger,
       ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
-      boolean isConstants) {
+      boolean isConstants, ResourceList resources, Set<String> seenPaths) {
 
     // If we are not in the default case, calculate parent
     if (!DEFAULT_TOKEN.equals(locale)) {
-      return getBundleAux(logger, tree, clazz, getParentLocaleName(locale), false, isConstants);
+      addResources(logger, tree, clazz, getParentLocaleName(locale),
+          false, isConstants, resources, seenPaths);
     }
-    return null;
   }
 
-  private static AbstractResource getBundleAux(TreeLogger logger,
-      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale, boolean required,
-      boolean isConstants) {
+  private static void addResources(TreeLogger logger,
+      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
+      boolean useAlternateParents, boolean isConstants,
+      ResourceList resources, Set<String> seenPaths) {
     String targetPath = tree.getPath();
+    String localizedPath = targetPath;
+    if (!DEFAULT_TOKEN.equals(locale)) {
+      localizedPath = targetPath + LOCALE_SEPARATOR + locale;
+    }
+    if (seenPaths.contains(localizedPath)) {
+      return;
+    }
+    seenPaths.add(localizedPath);
     ClassLoader loader = AbstractResource.class.getClassLoader();
     Map<String, JClassType> matchingClasses = null;
     if (clazz != null) {
       try {
-        matchingClasses = LocalizableLinkageCreator.findDerivedClasses(logger, clazz);
+        matchingClasses = LocalizableLinkageCreator.findDerivedClasses(logger,
+            clazz);
         /* 
          * In this case, we specifically want to be able to look at the interface
          * instead of just implementations.
@@ -344,76 +300,68 @@
       locale = DEFAULT_TOKEN;
     }
 
-    // Calculate baseName
-    String localizedPath = targetPath;
-    if (!DEFAULT_TOKEN.equals(locale)) {
-      localizedPath = targetPath + LOCALE_SEPARATOR + locale;
-    }
-    AbstractResource result = cache.get(localizedPath);
-    if (result != null) {
-      if (result == NOT_FOUND) {
-        return null;
-      } else {
-        return result;
+    // Check for file-based resources.
+    String partialPath = localizedPath.replace('.', '/');
+    for (int i = 0; i < loaders.size(); i++) {
+      ResourceFactory element = loaders.get(i);
+      String path = partialPath + "." + element.getExt();
+      InputStream m = loader.getResourceAsStream(path);
+      if (m != null) {
+        AbstractResource found = element.load(m);
+        found.setPath(path);
+        resources.add(found);
       }
     }
-    String partialPath = localizedPath.replace('.', '/');
-    AbstractResource parent = findPrimaryParent(logger, tree, clazz, locale, isConstants);
-    List<AbstractResource> altParents = findAlternativeParents(logger, tree, clazz, locale, isConstants);
 
-    AbstractResource found = null;
+    // Check for annotations
     JClassType currentClass = matchingClasses.get(locale);
     if (currentClass != null) {
       AnnotationsResource resource;
       try {
-        resource = new AnnotationsResource(logger, currentClass, isConstants);
+        resource = new AnnotationsResource(logger, currentClass, locale,
+            isConstants);
         if (resource.notEmpty()) {
-          found = resource;
-          found.setPath(currentClass.getQualifiedSourceName());
+          resource.setPath(currentClass.getQualifiedSourceName());
+          resources.add(resource);
         }
       } catch (AnnotationsError e) {
         logger.log(TreeLogger.ERROR, e.getMessage(), e);
       }
     }
-    for (int i = 0; found == null && i < loaders.size(); i++) {
-      ResourceFactory element = loaders.get(i);
-      String path = partialPath + "." + element.getExt();
-      InputStream m = loader.getResourceAsStream(path);
-      if (m != null) {
-        found = element.load(m);
-        found.setPath(path);
-      }
-    }
-    if (found == null) {
-      if (parent != null) {
-        found = parent;
-      } else {
-        found = NOT_FOUND;
-      }
-    } else {
-      found.setPrimaryParent(parent);
-      found.setLocaleName(locale);
-      for (int j = 0; j < altParents.size(); j++) {
-        AbstractResource altParent = altParents.get(j);
-        found.addAlternativeParent(altParent);
-      }
-      found.checkKeys();
-    }
+    
+    // Add our parent, if any
+    addPrimaryParent(logger, tree, clazz, locale, isConstants, resources,
+        seenPaths);
 
-    cache.put(localizedPath, found);
-
-    if (found == NOT_FOUND) {
-      if (required) {
-        throw new MissingResourceException(
-          "Could not find any resource associated with " + tree.getPath(),
-            null, null);
-      } else {
-        return null;
-      }
+    // Add our alternate parents
+    if (useAlternateParents) {
+      addAlternativeParents(logger, tree, clazz, locale, useAlternateParents,
+          isConstants, resources, seenPaths);
     }
+  }
 
-    // At this point, found cannot be equal to null or NOT_FOUND
-    return found;
+  private static ResourceList getBundleAux(TreeLogger logger,
+      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
+      boolean required, boolean isConstants) {
+    String cacheKey = tree.getPath() + "_" + locale;
+    if (cache.containsKey(cacheKey)) {
+      return cache.get(cacheKey);
+    }
+    Set<String> seenPaths = new HashSet<String>();
+    final ResourceList resources = new ResourceList();
+    addResources(logger, tree, clazz, locale, true, isConstants, resources,
+        seenPaths);
+    String className = tree.getPath();
+    if (clazz != null) {
+      className = clazz.getQualifiedSourceName();
+    }
+    TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resource search order for "
+        + className + ", locale " + locale);
+    for (AbstractResource resource : resources) {
+      branch.log(TreeLogger.SPAM, resource.toString());
+    }
+    cache.put(cacheKey, resources);
+    return resources;
   }
 
   abstract String getExt();
diff --git a/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
index 00db1d4..b28b2a5 100644
--- a/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
 /**
@@ -88,8 +89,8 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String key, AbstractResource resource, String locale) throws UnableToCompleteException {
-    String value = resource.getRequiredStringExt(logger, key, null);
+      String key, ResourceList resource, String locale) throws UnableToCompleteException {
+    String value = resource.getRequiredStringExt(key, null);
     try {
       String translatedValue = valueCreator.getValue(value);
       println("return " + translatedValue + ";");
diff --git a/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
index c61ac17..5693c39 100644
--- a/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
+++ b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
@@ -18,7 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.i18n.rebind.AbstractResource;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 
 import java.io.PrintWriter;
 
@@ -40,7 +40,8 @@
    * Write a message catalog file.
    * 
    * @param logger TreeLogger for logging errors/etc
-   * @param resource the contents to write
+   * @param locale locale of this output file
+   * @param resourceList the contents to write
    * @param out the PrintWriter to generate output on
    * @param messageInterface the interface to create (so additional
    *     annotations may be accessed)
@@ -48,8 +49,9 @@
    *     the output file.  In this case, the implementation must have already
    *     logged an appropriate ERROR message to the logger.
    */
-  void write(TreeLogger logger, AbstractResource resource, PrintWriter out,
-      JClassType messageInterface) throws UnableToCompleteException;
+  void write(TreeLogger logger, String locale, ResourceList resourceList,
+      PrintWriter out, JClassType messageInterface)
+      throws UnableToCompleteException;
   
   /**
    * @return the extension to use for this file type, including the dot
diff --git a/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java b/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java
index 88add3c..043b659 100644
--- a/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java
+++ b/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java
@@ -18,8 +18,8 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.i18n.client.PluralRule.PluralForm;
-import com.google.gwt.i18n.rebind.AbstractResource;
 import com.google.gwt.i18n.rebind.AnnotationsResource;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 import com.google.gwt.i18n.rebind.AnnotationsResource.ArgumentInfo;
 
 import java.io.PrintWriter;
@@ -46,42 +46,44 @@
    * @see com.google.gwt.i18n.rebind.format.MessageCatalogFormat#write(com.google.gwt.i18n.rebind.util.AbstractResource,
    *      java.io.File, com.google.gwt.core.ext.typeinfo.JClassType)
    */
-  public void write(TreeLogger logger, AbstractResource resource, PrintWriter out,
-      JClassType messageInterface) {
-    AnnotationsResource annotResource = resource.getAnnotationsResource();
+  public void write(TreeLogger logger, String locale,
+      ResourceList resourceList, PrintWriter out, JClassType messageInterface) {
     writeComment(out, "Generated from "
         + messageInterface.getQualifiedSourceName());
-    String localeName = resource.getLocaleName();
-    if (localeName != null) {
-      writeComment(out, "for locale " + localeName);
+    if (locale != null) {
+      writeComment(out, "for locale " + locale);
     }
     // Sort keys for deterministic output.
-    Set<String> keySet = resource.keySet();
+    Set<String> keySet = resourceList.keySet();
     String[] sortedKeys = keySet.toArray(new String[keySet.size()]);
     Arrays.sort(sortedKeys);
     for (String key : sortedKeys) {
       out.println();
+      AnnotationsResource annotResource = resourceList.getAnnotationsResource(
+          logger, key);
       if (annotResource != null) {
         // Write comments from the annotations.
         writeAnnotComments(out, annotResource, key);
       }
       
       // Collect plural forms for this locale.
-      PluralForm[] pluralForms = resource.getPluralForms(key);
+      PluralForm[] pluralForms = resourceList.getPluralForms(key);
       if (pluralForms != null) {
         for (PluralForm form : pluralForms) {
           String name = form.getName();
           if ("other".equals(name)) {
             // write the "other" description here, and the default message
             writeComment(out, "- " + form.getDescription());
-            write(out, key, resource.getString(key));
+            write(out, key, resourceList.getString(key));
           } else {
-            String comment = "- plural form '" + form.getName() + "': " + form.getDescription();
+            String comment = "- plural form '" + form.getName() + "': "
+                + form.getDescription();
             if (!form.getWarnIfMissing()) {
               comment += " (optional)";
             }
             writeComment(out, comment);
-            String translated = resource.getStringExt(key, form.getName());
+            String translated = resourceList.getStringExt(key,
+                form.getName());
             if (translated == null) {
               translated = "";
             }
@@ -89,7 +91,7 @@
           }
         }
       } else {
-        write(out, key, resource.getString(key));
+        write(out, key, resourceList.getString(key));
       }
     }
   }
diff --git a/user/src/com/google/gwt/junit/GWTMockUtilities.java b/user/src/com/google/gwt/junit/GWTMockUtilities.java
index b9fe106..906aea4 100644
--- a/user/src/com/google/gwt/junit/GWTMockUtilities.java
+++ b/user/src/com/google/gwt/junit/GWTMockUtilities.java
@@ -54,9 +54,9 @@
    * }
    * 
    * public void testSomething() {
-   *   MyWidget mock = EasyMock.createMock(MyWidget.class);
-   *   mock.setText("expected text");
-   *   mock.replay();
+   *   MyStatusWidget mock = EasyMock.createMock(MyStatusWidget.class);
+   *   EasyMock.expect(mock.setText("expected text"));
+   *   EasyMock.replay(mock);
    *   
    *   StatusController controller = new StatusController(mock);
    *   controller.setStatus("expected text");
@@ -65,7 +65,7 @@
    * }
    * </pre>
    */
-  public static void disarm(/* TearDownAccepter test */) {
+  public static void disarm() {
     GWTBridge bridge = new GWTDummyBridge();
     setGwtBridge(bridge);
   }
diff --git a/user/src/com/google/gwt/user/ListBox.gwt.xml b/user/src/com/google/gwt/user/ListBox.gwt.xml
deleted file mode 100644
index f245f28..0000000
--- a/user/src/com/google/gwt/user/ListBox.gwt.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<!--                                                                        -->
-<!-- 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 ListBox's implementation.                   -->
-<!--                                                                        -->
-<!-- This module is typically inherited via com.google.gwt.user.User        -->
-<!--                                                                        -->
-<module>
-  <inherits name="com.google.gwt.core.Core"/>
-  <inherits name="com.google.gwt.user.UserAgent"/>
-  <inherits name="com.google.gwt.user.DOM"/>
-
-	<!-- Fall through to this rule if the browser isn't Safari -->
-	<replace-with class="com.google.gwt.user.client.ui.ListBox.Impl">
-		<when-type-is class="com.google.gwt.user.client.ui.ListBox.Impl"/>
-	</replace-with>
-
-	<!-- Safari requires a different ListBox implementation -->
-	<replace-with class="com.google.gwt.user.client.ui.ListBox.ImplSafari">
-		<when-type-is class="com.google.gwt.user.client.ui.ListBox.Impl"/>
-		<when-property-is name="user.agent" value="safari"/>
-	</replace-with>
-</module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 707e33c..99baf1c 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -35,7 +35,6 @@
    <inherits name="com.google.gwt.user.ClippedImage"/>
    <inherits name="com.google.gwt.user.RichText"/>
    <inherits name="com.google.gwt.user.SplitPanel"/>
-   <inherits name="com.google.gwt.user.ListBox" />
    <inherits name="com.google.gwt.user.CaptionPanel" />
    <inherits name="com.google.gwt.user.Window" />
    <inherits name="com.google.gwt.user.Accessibility"/>
diff --git a/user/src/com/google/gwt/user/client/ui/Anchor.java b/user/src/com/google/gwt/user/client/ui/Anchor.java
index f7669f5..8910305 100644
--- a/user/src/com/google/gwt/user/client/ui/Anchor.java
+++ b/user/src/com/google/gwt/user/client/ui/Anchor.java
@@ -33,16 +33,16 @@
     HasName, HasText, HasHTML, HasWordWrap, HasDirection {
 
   /**
-   * Creates an Anchor widget that wraps an existing &lt;div&gt; or &lt;span&gt;
-   * element.
+   * Creates an Anchor widget that wraps an existing &lt;a&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
   public static Anchor wrap(Element element) {
-    // Assert that the element is of the correct type and is attached.
-    AnchorElement.as(element);
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
     Anchor anchor = new Anchor(element);
@@ -147,7 +147,14 @@
     this(text, false, href, target);
   }
 
-  private Anchor(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;a&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected Anchor(Element element) {
+    AnchorElement.as(element);
     setElement(element);
   }
 
diff --git a/user/src/com/google/gwt/user/client/ui/Button.java b/user/src/com/google/gwt/user/client/ui/Button.java
index a29a69b..bce6eac 100644
--- a/user/src/com/google/gwt/user/client/ui/Button.java
+++ b/user/src/com/google/gwt/user/client/ui/Button.java
@@ -17,8 +17,7 @@
 
 import com.google.gwt.dom.client.ButtonElement;
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * A standard push-button widget.
@@ -33,7 +32,8 @@
  * </ul>
  * 
  * <p>
- * <h3>Example</h3> {@example com.google.gwt.examples.ButtonExample}
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.ButtonExample}
  * </p>
  */
 public class Button extends ButtonBase {
@@ -41,16 +41,17 @@
   /**
    * Creates a Button widget that wraps an existing &lt;button&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
   public static Button wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    ButtonElement.as(element);
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    Button button = new Button((Element) element);
+    Button button = new Button(element);
 
     // Mark it attached and remember it for cleanup.
     button.onAttach();
@@ -77,7 +78,7 @@
    * Creates a button with no caption.
    */
   public Button() {
-    super(DOM.createButton());
+    super(Document.get().createButtonElement());
     adjustType(getElement());
     setStyleName("gwt-Button");
   }
@@ -103,8 +104,15 @@
     addClickListener(listener);
   }
 
-  private Button(Element element) {
-    super(element);
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be a &lt;button&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected Button(com.google.gwt.dom.client.Element element) {
+    super(element.<Element>cast());
+    ButtonElement.as(element);
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/ui/ButtonBase.java b/user/src/com/google/gwt/user/client/ui/ButtonBase.java
index 7aaf631..1e6d99d 100644
--- a/user/src/com/google/gwt/user/client/ui/ButtonBase.java
+++ b/user/src/com/google/gwt/user/client/ui/ButtonBase.java
@@ -15,8 +15,7 @@
  */
 package com.google.gwt.user.client.ui;
 
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * Abstract base class for {@link com.google.gwt.user.client.ui.Button},
@@ -35,18 +34,18 @@
   }
 
   public String getHTML() {
-    return DOM.getInnerHTML(getElement());
+    return getElement().getInnerHTML();
   }
 
   public String getText() {
-    return DOM.getInnerText(getElement());
+    return getElement().getInnerText();
   }
 
   public void setHTML(String html) {
-    DOM.setInnerHTML(getElement(), html);
+    getElement().setInnerHTML(html);
   }
 
   public void setText(String text) {
-    DOM.setInnerText(getElement(), text);
+    getElement().setInnerText(text);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/CustomButton.java b/user/src/com/google/gwt/user/client/ui/CustomButton.java
index d08c817..270387f 100644
--- a/user/src/com/google/gwt/user/client/ui/CustomButton.java
+++ b/user/src/com/google/gwt/user/client/ui/CustomButton.java
@@ -583,26 +583,28 @@
     super.onBrowserEvent(event);
 
     // Synthesize clicks based on keyboard events AFTER the normal key handling.
-    char keyCode = (char) DOM.eventGetKeyCode(event);
-    switch (type) {
-      case Event.ONKEYDOWN:
-        if (keyCode == ' ') {
-          isFocusing = true;
-          onClickStart();
-        }
-        break;
-      case Event.ONKEYUP:
-        if (isFocusing && keyCode == ' ') {
-          isFocusing = false;
-          onClick();
-        }
-        break;
-      case Event.ONKEYPRESS:
-        if (keyCode == '\n' || keyCode == '\r') {
-          onClickStart();
-          onClick();
-        }
-        break;
+    if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
+      char keyCode = (char) DOM.eventGetKeyCode(event);
+      switch (type) {
+        case Event.ONKEYDOWN:
+          if (keyCode == ' ') {
+            isFocusing = true;
+            onClickStart();
+          }
+          break;
+        case Event.ONKEYUP:
+          if (isFocusing && keyCode == ' ') {
+            isFocusing = false;
+            onClick();
+          }
+          break;
+        case Event.ONKEYPRESS:
+          if (keyCode == '\n' || keyCode == '\r') {
+            onClickStart();
+            onClick();
+          }
+          break;
+      }
     }
   }
 
diff --git a/user/src/com/google/gwt/user/client/ui/FileUpload.java b/user/src/com/google/gwt/user/client/ui/FileUpload.java
index 966fbf3..f1bbf02 100644
--- a/user/src/com/google/gwt/user/client/ui/FileUpload.java
+++ b/user/src/com/google/gwt/user/client/ui/FileUpload.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -16,9 +16,8 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 
 /**
  * A widget that wraps the HTML &lt;input type='file'&gt; element. This widget
@@ -36,16 +35,17 @@
    * Creates a FileUpload widget that wraps an existing &lt;input
    * type='file'&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static FileUpload wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the right type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("file");
+  public static FileUpload wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    FileUpload fileUpload = new FileUpload((Element) element);
+    FileUpload fileUpload = new FileUpload(element);
 
     // Mark it attached and remember it for cleanup.
     fileUpload.onAttach();
@@ -58,12 +58,19 @@
    * Constructs a new file upload widget.
    */
   public FileUpload() {
-    setElement(DOM.createElement("input"));
-    DOM.setElementProperty(getElement(), "type", "file");
+    setElement(Document.get().createFileInputElement());
     setStyleName("gwt-FileUpload");
   }
 
-  private FileUpload(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is
+   * 'file'.
+   * 
+   * @param element the element to be used
+   */
+  protected FileUpload(Element element) {
+    assert InputElement.as(element).getType().equalsIgnoreCase("file");
     setElement(element);
   }
 
@@ -74,14 +81,18 @@
    * @return the widget's filename
    */
   public String getFilename() {
-    return DOM.getElementProperty(getElement(), "value");
+    return getInputElement().getValue();
   }
 
   public String getName() {
-    return DOM.getElementProperty(getElement(), "name");
+    return getInputElement().getName();
   }
 
   public void setName(String name) {
-    DOM.setElementProperty(getElement(), "name", name);
+    getInputElement().setName(name);
+  }
+
+  private InputElement getInputElement() {
+    return getElement().cast();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/FocusWidget.java b/user/src/com/google/gwt/user/client/ui/FocusWidget.java
index e1ea806..cb09062 100644
--- a/user/src/com/google/gwt/user/client/ui/FocusWidget.java
+++ b/user/src/com/google/gwt/user/client/ui/FocusWidget.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.impl.FocusImpl;
 
@@ -165,7 +165,7 @@
   }
 
   @Override
-  protected void setElement(Element elem) {
+  protected void setElement(com.google.gwt.user.client.Element elem) {
     super.setElement(elem);
 
     // Accessibility: setting tab index to be 0 by default, ensuring element
diff --git a/user/src/com/google/gwt/user/client/ui/FormPanel.java b/user/src/com/google/gwt/user/client/ui/FormPanel.java
index d873e14..85ab71a 100644
--- a/user/src/com/google/gwt/user/client/ui/FormPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/FormPanel.java
@@ -18,11 +18,10 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.FormElement;
 import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.impl.FormPanelImpl;
 import com.google.gwt.user.client.ui.impl.FormPanelImplHost;
@@ -39,7 +38,9 @@
  * <li>{@link com.google.gwt.user.client.ui.TextBox}</li>
  * <li>{@link com.google.gwt.user.client.ui.PasswordTextBox}</li>
  * <li>{@link com.google.gwt.user.client.ui.RadioButton}</li>
+ * <li>{@link com.google.gwt.user.client.ui.SimpleRadioButton}</li>
  * <li>{@link com.google.gwt.user.client.ui.CheckBox}</li>
+ * <li>{@link com.google.gwt.user.client.ui.SimpleCheckBox}</li>
  * <li>{@link com.google.gwt.user.client.ui.TextArea}</li>
  * <li>{@link com.google.gwt.user.client.ui.ListBox}</li>
  * <li>{@link com.google.gwt.user.client.ui.FileUpload}</li>
@@ -90,16 +91,17 @@
   /**
    * Creates a FormPanel that wraps an existing &lt;form&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static FormPanel wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    FormElement.as(element);
+  public static FormPanel wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    FormPanel formPanel = new FormPanel((Element) element);
+    FormPanel formPanel = new FormPanel(element);
 
     // Mark it attached and remember it for cleanup.
     formPanel.onAttach();
@@ -110,7 +112,7 @@
 
   private FormHandlerCollection formHandlers;
   private String frameName;
-  private Element iframe;
+  private Element synthesizedFrame;
 
   /**
    * Creates a new FormPanel. When created using this constructor, it will be
@@ -132,7 +134,7 @@
    *      cannot be made to work properly on all browsers.
    */
   public FormPanel() {
-    super(DOM.createForm());
+    super(Document.get().createFormElement());
 
     frameName = "FormPanel_" + (++formId);
     setTarget(frameName);
@@ -171,12 +173,19 @@
    *          page be replaced
    */
   public FormPanel(String target) {
-    super(DOM.createForm());
+    super(Document.get().createFormElement());
     setTarget(target);
   }
 
-  private FormPanel(Element elem) {
-    super(elem);
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be a &lt;form&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected FormPanel(Element element) {
+    super(element);
+    FormElement.as(element);
   }
 
   public void addFormHandler(FormHandler handler) {
@@ -193,7 +202,7 @@
    * @return the form's action
    */
   public String getAction() {
-    return DOM.getElementProperty(getElement(), "action");
+    return getFormElement().getAction();
   }
 
   /**
@@ -213,7 +222,7 @@
    * @return the form's method
    */
   public String getMethod() {
-    return DOM.getElementProperty(getElement(), "method");
+    return getFormElement().getMethod();
   }
 
   /**
@@ -224,7 +233,7 @@
    * @return the form's target.
    */
   public String getTarget() {
-    return DOM.getElementProperty(getElement(), "target");
+    return getFormElement().getTarget();
   }
 
   public boolean onFormSubmit() {
@@ -258,7 +267,7 @@
    * @param url the form's action
    */
   public void setAction(String url) {
-    DOM.setElementProperty(getElement(), "action", url);
+    getFormElement().setAction(url);
   }
 
   /**
@@ -278,7 +287,7 @@
    * @param method the form's method
    */
   public void setMethod(String method) {
-    DOM.setElementProperty(getElement(), "method", method);
+    getFormElement().setMethod(method);
   }
 
   /**
@@ -299,34 +308,44 @@
       }
     }
 
-    impl.submit(getElement(), iframe);
+    impl.submit(getElement(), synthesizedFrame);
   }
 
   @Override
   protected void onAttach() {
     super.onAttach();
 
-    // Create and attach a hidden iframe to the body element.
-    createFrame();
-    DOM.appendChild(RootPanel.getBodyElement(), iframe);
+    if (frameName != null) {
+      // Create and attach a hidden iframe to the body element.
+      createFrame();
+      Document.get().getBody().appendChild(synthesizedFrame);
 
-    // Hook up the underlying iframe's onLoad event when attached to the DOM.
-    // Making this connection only when attached avoids memory-leak issues.
-    // The FormPanel cannot use the built-in GWT event-handling mechanism
-    // because there is no standard onLoad event on iframes that works across
-    // browsers.
-    impl.hookEvents(iframe, getElement(), this);
+      // Hook up the underlying iframe's onLoad event when attached to the DOM.
+      // Making this connection only when attached avoids memory-leak issues.
+      // The FormPanel cannot use the built-in GWT event-handling mechanism
+      // because there is no standard onLoad event on iframes that works across
+      // browsers.
+      impl.hookEvents(synthesizedFrame, getElement(), this);
+    }
   }
 
   @Override
   protected void onDetach() {
     super.onDetach();
 
-    // Unhook the iframe's onLoad when detached.
-    impl.unhookEvents(iframe, getElement());
+    if (synthesizedFrame != null) {
+      // Unhook the iframe's onLoad when detached.
+      impl.unhookEvents(synthesizedFrame, getElement());
 
-    DOM.removeChild(RootPanel.getBodyElement(), iframe);
-    iframe = null;
+      // And remove it from the document.
+      Document.get().getBody().removeChild(synthesizedFrame);
+      synthesizedFrame = null;
+    }
+  }
+
+  // For unit-tests.
+  Element getSynthesizedIFrame() {
+    return synthesizedFrame;
   }
 
   private void createFrame() {
@@ -334,11 +353,15 @@
     // the form will be submitted. We have to create the iframe using innerHTML,
     // because setting an iframe's 'name' property dynamically doesn't work on
     // most browsers.
-    Element dummy = DOM.createDiv();
-    DOM.setInnerHTML(dummy, "<iframe src=\"javascript:''\" name='" + frameName
+    Element dummy = Document.get().createDivElement();
+    dummy.setInnerHTML("<iframe src=\"javascript:''\" name='" + frameName
         + "' style='position:absolute;width:0;height:0;border:0'>");
 
-    iframe = DOM.getFirstChild(dummy);
+    synthesizedFrame = dummy.getFirstChildElement();
+  }
+
+  private FormElement getFormElement() {
+    return getElement().cast();
   }
 
   private boolean onFormSubmitAndCatch(UncaughtExceptionHandler handler) {
@@ -375,13 +398,13 @@
       // 'infinite loading' state. See issue 916.
       DeferredCommand.addCommand(new Command() {
         public void execute() {
-          formHandlers.fireOnComplete(FormPanel.this, impl.getContents(iframe));
+          formHandlers.fireOnComplete(FormPanel.this, impl.getContents(synthesizedFrame));
         }
       });
     }
   }
 
   private void setTarget(String target) {
-    DOM.setElementProperty(getElement(), "target", target);
+    getFormElement().setTarget(target);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Frame.java b/user/src/com/google/gwt/user/client/ui/Frame.java
index c8a6069..c6b09da 100644
--- a/user/src/com/google/gwt/user/client/ui/Frame.java
+++ b/user/src/com/google/gwt/user/client/ui/Frame.java
@@ -16,9 +16,9 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.FrameElement;
 import com.google.gwt.dom.client.IFrameElement;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 
 /**
  * A widget that wraps an IFRAME element, which can contain an arbitrary web
@@ -44,16 +44,17 @@
   /**
    * Creates a Frame widget that wraps an existing &lt;frame&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static Frame wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    IFrameElement.as(element);
+  public static Frame wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    Frame frame = new Frame((Element) element);
+    Frame frame = new Frame(element);
 
     // Mark it attached and remember it for cleanup.
     frame.onAttach();
@@ -66,7 +67,7 @@
    * Creates an empty frame.
    */
   public Frame() {
-    setElement(DOM.createIFrame());
+    setElement(Document.get().createIFrameElement());
     setStyleName(DEFAULT_STYLENAME);
   }
 
@@ -80,7 +81,14 @@
     setUrl(url);
   }
 
-  private Frame(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;iframe&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected Frame(Element element) {
+    IFrameElement.as(element);
     setElement(element);
   }
 
@@ -90,7 +98,7 @@
    * @return the frame's URL
    */
   public String getUrl() {
-    return DOM.getElementProperty(getElement(), "src");
+    return getFrameElement().getSrc();
   }
 
   /**
@@ -99,6 +107,10 @@
    * @param url the frame's new URL
    */
   public void setUrl(String url) {
-    DOM.setElementProperty(getElement(), "src", url);
+    getFrameElement().setSrc(url);
+  }
+
+  private FrameElement getFrameElement() {
+    return getElement().cast();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/HTML.java b/user/src/com/google/gwt/user/client/ui/HTML.java
index 5cebe0a..d6959a0 100644
--- a/user/src/com/google/gwt/user/client/ui/HTML.java
+++ b/user/src/com/google/gwt/user/client/ui/HTML.java
@@ -16,8 +16,7 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * A widget that can contain arbitrary HTML.
@@ -48,17 +47,17 @@
    * Creates an HTML widget that wraps an existing &lt;div&gt; or &lt;span&gt;
    * element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static HTML wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert element.getTagName().equalsIgnoreCase("div")
-        || element.getTagName().equalsIgnoreCase("span");
+  public static HTML wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    HTML html = new HTML((Element) element);
+    HTML html = new HTML(element);
 
     // Mark it attached and remember it for cleanup.
     html.onAttach();
@@ -71,7 +70,7 @@
    * Creates an empty HTML widget.
    */
   public HTML() {
-    super(DOM.createDiv());
+    super(Document.get().createDivElement());
     setStyleName("gwt-HTML");
   }
 
@@ -97,15 +96,23 @@
     setWordWrap(wordWrap);
   }
 
-  HTML(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be either a &lt;div&gt; or &lt;span&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected HTML(Element element) {
     super(element);
+    assert element.getTagName().equalsIgnoreCase("div")
+        || element.getTagName().equalsIgnoreCase("span");
   }
 
   public String getHTML() {
-    return DOM.getInnerHTML(getElement());
+    return getElement().getInnerHTML();
   }
 
   public void setHTML(String html) {
-    DOM.setInnerHTML(getElement(), html);
+    getElement().setInnerHTML(html);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Hidden.java b/user/src/com/google/gwt/user/client/ui/Hidden.java
index b667197..fcd978b 100644
--- a/user/src/com/google/gwt/user/client/ui/Hidden.java
+++ b/user/src/com/google/gwt/user/client/ui/Hidden.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -13,13 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 
 /**
  * Represents a hidden field in an HTML form.
@@ -30,16 +28,17 @@
    * Creates a Hidden widget that wraps an existing &lt;input type='hidden'&gt;
    * element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static Hidden wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("hidden");
+  public static Hidden wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    Hidden hidden = new Hidden((Element) element);
+    Hidden hidden = new Hidden(element);
 
     // Mark it attached and remember it for cleanup.
     hidden.onAttach();
@@ -52,9 +51,7 @@
    * Constructor for <code>Hidden</code>.
    */
   public Hidden() {
-    Element e = DOM.createElement("input");
-    setElement(e);
-    DOM.setElementProperty(e, "type", "hidden");
+    setElement(Document.get().createHiddenInputElement());
   }
 
   /**
@@ -78,7 +75,15 @@
     setValue(value);
   }
 
-  private Hidden(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is
+   * 'hidden'.
+   * 
+   * @param element the element to be used
+   */
+  protected Hidden(Element element) {
+    assert InputElement.as(element).getType().equalsIgnoreCase("hidden");
     setElement(element);
   }
 
@@ -88,7 +93,7 @@
    * @return the default value
    */
   public String getDefaultValue() {
-    return DOM.getElementProperty(getElement(), "defaultValue");
+    return getInputElement().getDefaultValue();
   }
 
   /**
@@ -97,7 +102,7 @@
    * @return the id
    */
   public String getID() {
-    return DOM.getElementProperty(getElement(), "id");
+    return getElement().getId();
   }
 
   /**
@@ -107,7 +112,7 @@
    */
 
   public String getName() {
-    return DOM.getElementProperty(getElement(), "name");
+    return getInputElement().getName();
   }
 
   /**
@@ -116,7 +121,7 @@
    * @return the value
    */
   public String getValue() {
-    return DOM.getElementProperty(getElement(), "value");
+    return getInputElement().getValue();
   }
 
   /**
@@ -125,7 +130,7 @@
    * @param defaultValue default value to set
    */
   public void setDefaultValue(String defaultValue) {
-    DOM.setElementProperty(getElement(), "defaultValue", defaultValue);
+    getInputElement().setDefaultValue(defaultValue);
   }
 
   /**
@@ -134,7 +139,7 @@
    * @param id id to set
    */
   public void setID(String id) {
-    DOM.setElementProperty(getElement(), "id", id);
+    getElement().setId(id);
   }
 
   /**
@@ -148,7 +153,8 @@
     } else if (name.equals("")) {
       throw new IllegalArgumentException("Name cannot be an empty string.");
     }
-    DOM.setElementProperty(getElement(), "name", name);
+
+    getInputElement().setName(name);
   }
 
   /**
@@ -157,6 +163,10 @@
    * @param value value to set
    */
   public void setValue(String value) {
-    DOM.setElementProperty(getElement(), "value", value);
+    getInputElement().setValue(value);
+  }
+
+  private InputElement getInputElement() {
+    return getElement().cast();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Image.java b/user/src/com/google/gwt/user/client/ui/Image.java
index a6feb7e..8d0e256 100644
--- a/user/src/com/google/gwt/user/client/ui/Image.java
+++ b/user/src/com/google/gwt/user/client/ui/Image.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -17,11 +17,10 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.ImageElement;
 import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.impl.ClippedImageImpl;
 
@@ -224,12 +223,12 @@
   private static class UnclippedState extends State {
 
     UnclippedState(Element element) {
-      DOM.sinkEvents(element, Event.ONCLICK | Event.MOUSEEVENTS | Event.ONLOAD
-          | Event.ONERROR | Event.ONMOUSEWHEEL);
+      Event.sinkEvents(element, Event.ONCLICK | Event.MOUSEEVENTS
+          | Event.ONLOAD | Event.ONERROR | Event.ONMOUSEWHEEL);
     }
 
     UnclippedState(Image image) {
-      image.replaceElement(DOM.createImg());
+      image.replaceElement(Document.get().createImageElement());
       image.sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.ONLOAD
           | Event.ONERROR | Event.ONMOUSEWHEEL);
     }
@@ -241,7 +240,7 @@
 
     @Override
     public int getHeight(Image image) {
-      return DOM.getElementPropertyInt(image.getElement(), "height");
+      return image.getImageElement().getHeight();
     }
 
     @Override
@@ -256,17 +255,17 @@
 
     @Override
     public String getUrl(Image image) {
-      return DOM.getImgSrc(image.getElement());
+      return image.getImageElement().getSrc();
     }
 
     @Override
     public int getWidth(Image image) {
-      return DOM.getElementPropertyInt(image.getElement(), "width");
+      return image.getImageElement().getWidth();
     }
 
     @Override
     public void setUrl(Image image, String url) {
-      DOM.setImgSrc(image.getElement(), url);
+      image.getImageElement().setSrc(url);
     }
 
     @Override
@@ -302,25 +301,26 @@
    * @param url the URL of the image to be prefetched
    */
   public static void prefetch(String url) {
-    Element img = DOM.createImg();
-    DOM.setImgSrc(img, url);
+    ImageElement img = Document.get().createImageElement();
+    img.setSrc(url);
     prefetchImages.put(url, img);
   }
 
   /**
    * Creates a Image widget that wraps an existing &lt;img&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static Image wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    ImageElement.as(element);
+  public static Image wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    Image image = new Image((Element) element);
-    image.changeState(new UnclippedState((Element) element));
+    Image image = new Image(element);
+    image.changeState(new UnclippedState(element));
 
     // Mark it attached and remember it for cleanup.
     image.onAttach();
@@ -378,7 +378,14 @@
     setStyleName("gwt-Image");
   }
 
-  private Image(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;img&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected Image(Element element) {
+    ImageElement.as(element);
     setElement(element);
   }
 
@@ -471,7 +478,7 @@
 
   @Override
   public void onBrowserEvent(Event event) {
-    switch (DOM.eventGetType(event)) {
+    switch (event.getTypeInt()) {
       case Event.ONCLICK: {
         if (clickListeners != null) {
           clickListeners.fireClick(this);
@@ -589,4 +596,8 @@
   private void changeState(State newState) {
     state = newState;
   }
+
+  private ImageElement getImageElement() {
+    return getElement().cast();
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHTML.java b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
index 36c885a..c870c4b 100644
--- a/user/src/com/google/gwt/user/client/ui/InlineHTML.java
+++ b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
@@ -16,8 +16,7 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * A widget that can contain arbitrary HTML.
@@ -43,17 +42,17 @@
    * Creates an InlineHTML widget that wraps an existing &lt;div&gt; or
    * &lt;span&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static InlineHTML wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert element.getTagName().equalsIgnoreCase("div")
-        || element.getTagName().equalsIgnoreCase("span");
+  public static InlineHTML wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    InlineHTML html = new InlineHTML((Element) element);
+    InlineHTML html = new InlineHTML(element);
 
     // Mark it attached and remember it for cleanup.
     html.onAttach();
@@ -66,7 +65,7 @@
    * Creates an empty HTML widget.
    */
   public InlineHTML() {
-    super(DOM.createSpan());
+    super(Document.get().createSpanElement());
     setStyleName("gwt-InlineHTML");
   }
 
@@ -80,7 +79,15 @@
     setHTML(html);
   }
 
-  private InlineHTML(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be either a &lt;div&gt; &lt;span&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected InlineHTML(Element element) {
     super(element);
+    assert element.getTagName().equalsIgnoreCase("div")
+        || element.getTagName().equalsIgnoreCase("span");
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/InlineLabel.java b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
index d69affa..08f3583 100644
--- a/user/src/com/google/gwt/user/client/ui/InlineLabel.java
+++ b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
@@ -16,8 +16,7 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
@@ -36,17 +35,17 @@
    * Creates a InlineLabel widget that wraps an existing &lt;div&gt; or
    * &lt;span&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static InlineLabel wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert element.getTagName().equalsIgnoreCase("div")
-        || element.getTagName().equalsIgnoreCase("span");
+  public static InlineLabel wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    InlineLabel label = new InlineLabel((Element) element);
+    InlineLabel label = new InlineLabel(element);
 
     // Mark it attached and remember it for cleanup.
     label.onAttach();
@@ -59,7 +58,7 @@
    * Creates an empty label.
    */
   public InlineLabel() {
-    super(DOM.createSpan());
+    super(Document.get().createSpanElement());
     setStyleName("gwt-InlineLabel");
   }
 
@@ -73,7 +72,15 @@
     setText(text);
   }
 
-  private InlineLabel(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be either a &lt;div&gt; &lt;span&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected InlineLabel(Element element) {
     super(element);
+    assert element.getTagName().equalsIgnoreCase("div")
+        || element.getTagName().equalsIgnoreCase("span");
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Label.java b/user/src/com/google/gwt/user/client/ui/Label.java
index c985697..07faf0b 100644
--- a/user/src/com/google/gwt/user/client/ui/Label.java
+++ b/user/src/com/google/gwt/user/client/ui/Label.java
@@ -16,17 +16,16 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 
 /**
  * A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
  * 
- * This widget uses a &lt;div&gt; element, causing it to be displayed with
- * block layout.
+ * This widget uses a &lt;div&gt; element, causing it to be displayed with block
+ * layout.
  * 
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
@@ -46,17 +45,17 @@
    * Creates a Label widget that wraps an existing &lt;div&gt; or &lt;span&gt;
    * element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static Label wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert element.getTagName().equalsIgnoreCase("div")
-        || element.getTagName().equalsIgnoreCase("span");
+  public static Label wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    Label label = new Label((Element) element);
+    Label label = new Label(element);
 
     // Mark it attached and remember it for cleanup.
     label.onAttach();
@@ -74,7 +73,7 @@
    * Creates an empty label.
    */
   public Label() {
-    setElement(DOM.createDiv());
+    setElement(Document.get().createDivElement());
     setStyleName("gwt-Label");
   }
 
@@ -100,12 +99,15 @@
   }
 
   /**
-   * This constructor is used to let the HTML constructors avoid work.
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be either a &lt;div&gt; or &lt;span&gt; element.
    * 
-   * @param element element
+   * @param element the element to be used
    */
-  Label(Element element) {
+  protected Label(Element element) {
     setElement(element);
+    assert element.getTagName().equalsIgnoreCase("div")
+        || element.getTagName().equalsIgnoreCase("span");
   }
 
   public void addClickListener(ClickListener listener) {
@@ -135,22 +137,22 @@
   public Direction getDirection() {
     return BidiUtils.getDirectionOnElement(getElement());
   }
-  
+
   public HorizontalAlignmentConstant getHorizontalAlignment() {
     return horzAlign;
   }
 
   public String getText() {
-    return DOM.getInnerText(getElement());
+    return getElement().getInnerText();
   }
 
   public boolean getWordWrap() {
-    return !DOM.getStyleAttribute(getElement(), "whiteSpace").equals("nowrap");
+    return getElement().getStyle().getProperty("whiteSpace").equals("nowrap");
   }
 
   @Override
   public void onBrowserEvent(Event event) {
-    switch (DOM.eventGetType(event)) {
+    switch (event.getTypeInt()) {
       case Event.ONCLICK:
         if (clickListeners != null) {
           clickListeners.fireClick(this);
@@ -192,22 +194,22 @@
       mouseWheelListeners.remove(listener);
     }
   }
-  
+
   public void setDirection(Direction direction) {
     BidiUtils.setDirectionOnElement(getElement(), direction);
   }
-  
+
   public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
     horzAlign = align;
-    DOM.setStyleAttribute(getElement(), "textAlign", align.getTextAlignString());
+    getElement().getStyle().setProperty("textAlign", align.getTextAlignString());
   }
 
   public void setText(String text) {
-    DOM.setInnerText(getElement(), text);
+    getElement().setInnerText(text);
   }
 
   public void setWordWrap(boolean wrap) {
-    DOM.setStyleAttribute(getElement(), "whiteSpace", wrap ? "normal"
-        : "nowrap");
+    getElement().getStyle().setProperty("whiteSpace",
+        wrap ? "normal" : "nowrap");
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/ListBox.java b/user/src/com/google/gwt/user/client/ui/ListBox.java
index a403499..ed82a9b 100644
--- a/user/src/com/google/gwt/user/client/ui/ListBox.java
+++ b/user/src/com/google/gwt/user/client/ui/ListBox.java
@@ -15,11 +15,10 @@
  */
 package com.google.gwt.user.client.ui;
 
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.OptionElement;
 import com.google.gwt.dom.client.SelectElement;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 
 /**
@@ -43,127 +42,22 @@
 public class ListBox extends FocusWidget implements SourcesChangeEvents,
     HasName {
 
-  /**
-   * ListBox implementation for all browsers except Safari. This implementation
-   * relies on the JavaScript Select object and its 'options' array.
-   */
-  private static class Impl {
-
-    public native void clear(Element select) /*-{
-      select.options.length = 0;
-    }-*/;
-
-    public native int getItemCount(Element select) /*-{
-      return select.options.length;
-    }-*/;
-
-    public native String getItemText(Element select, int index) /*-{
-      return select.options[index].text;
-    }-*/;
-
-    public native String getItemValue(Element select, int index) /*-{
-      return select.options[index].value;
-    }-*/;
-
-    public native boolean isItemSelected(Element select, int index) /*-{
-      return select.options[index].selected;
-    }-*/;
-
-    public native void removeItem(Element select, int index) /*-{
-      select.options[index] = null;
-    }-*/;
-
-    public native void setItemSelected(Element select, int index,
-                                       boolean selected) /*-{
-      select.options[index].selected = selected;
-    }-*/;
-
-    public native void setValue(Element select, int index, String value) /*-{
-      select.options[index].value = value;
-    }-*/;
-
-    protected native Element getItemElement(Element select, int index) /*-{
-      return select.options[index];
-    }-*/;
-  }
-
-  /**
-   * ListBox implementation for Safari. The 'options' array cannot be used
-   * due to a bug in the version of WebKit that ships with GWT
-   * (http://bugs.webkit.org/show_bug.cgi?id=10472).
-   * The 'children' array, which is common for all DOM elements in Safari,
-   * does not suffer from the same problem. Ideally, the 'children'
-   * array should be used in all of the traversal methods in the DOM classes.
-   * Unfortunately, due to a bug in Safari 2
-   * (http://bugs.webkit.org/show_bug.cgi?id=3330), this will not work.
-   * However, this bug does not cause problems in the case of <SELECT>
-   * elements, because their descendent elements are only one level deep.
-   */
-  private static class ImplSafari extends Impl {
-
-    @Override
-    public native void clear(Element select) /*-{
-      select.innerText = '';
-    }-*/;
-
-    @Override
-    public native int getItemCount(Element select) /*-{
-      return select.children.length;
-    }-*/;
-
-    @Override
-    public native String getItemText(Element select, int index) /*-{
-      return select.children[index].text;
-    }-*/;
-
-    @Override
-    public native String getItemValue(Element select, int index) /*-{
-      return select.children[index].value;
-    }-*/;
-
-    @Override
-    public native boolean isItemSelected(Element select, int index) /*-{
-      return select.children[index].selected;
-    }-*/;
-
-    @Override
-    public native void removeItem(Element select, int index) /*-{
-      select.removeChild(select.children[index]);
-    }-*/;
-
-    @Override
-    public native void setItemSelected(Element select, int index,
-                                       boolean selected) /*-{
-      select.children[index].selected = selected;
-    }-*/;
-
-    @Override
-    public native void setValue(Element select, int index, String value) /*-{
-      select.children[index].value = value;
-    }-*/;
-
-    @Override
-    protected native Element getItemElement(Element select, int index) /*-{
-      return select.children[index];
-    }-*/;
-  }
-
   private static final int INSERT_AT_END = -1;
-  private static final Impl impl = GWT.create(Impl.class);
 
   /**
    * Creates a ListBox widget that wraps an existing &lt;select&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static ListBox wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    SelectElement.as(element);
+  public static ListBox wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    ListBox listBox = new ListBox((Element) element);
+    ListBox listBox = new ListBox(element);
 
     // Mark it attached and remember it for cleanup.
     listBox.onAttach();
@@ -188,12 +82,19 @@
    * @param isMultipleSelect specifies if multiple selection is enabled
    */
   public ListBox(boolean isMultipleSelect) {
-    super(DOM.createSelect(isMultipleSelect));
+    super(Document.get().createSelectElement(isMultipleSelect));
     setStyleName("gwt-ListBox");
   }
 
-  private ListBox(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be a &lt;select&gt; element.
+   * 
+   * @param element the element to be used
+   */
+  protected ListBox(Element element) {
     super(element);
+    SelectElement.as(element);
   }
 
   public void addChangeListener(ChangeListener listener) {
@@ -232,7 +133,7 @@
    * Removes all items from the list box.
    */
   public void clear() {
-    impl.clear(getElement());
+    getSelectElement().clear();
   }
 
   /**
@@ -241,7 +142,7 @@
    * @return the number of items
    */
   public int getItemCount() {
-    return impl.getItemCount(getElement());
+    return getSelectElement().getOptions().getLength();
   }
 
   /**
@@ -253,11 +154,11 @@
    */
   public String getItemText(int index) {
     checkIndex(index);
-    return impl.getItemText(getElement(), index);
+    return getSelectElement().getOptions().getItem(index).getText();
   }
 
   public String getName() {
-    return DOM.getElementProperty(getElement(), "name");
+    return getSelectElement().getName();
   }
 
   /**
@@ -268,7 +169,7 @@
    * @return the selected index, or <code>-1</code> if none is selected
    */
   public int getSelectedIndex() {
-    return DOM.getElementPropertyInt(getElement(), "selectedIndex");
+    return getSelectElement().getSelectedIndex();
   }
 
   /**
@@ -280,7 +181,7 @@
    */
   public String getValue(int index) {
     checkIndex(index);
-    return impl.getItemValue(getElement(), index);
+    return getSelectElement().getOptions().getItem(index).getValue();
   }
 
   /**
@@ -290,7 +191,7 @@
    * @return the visible item count
    */
   public int getVisibleItemCount() {
-    return DOM.getElementPropertyInt(getElement(), "size");
+    return getSelectElement().getSize();
   }
 
   /**
@@ -319,7 +220,17 @@
    * @param index the index at which to insert it
    */
   public void insertItem(String item, String value, int index) {
-    DOM.insertListItem(getElement(), item, value, index);
+    SelectElement select = getSelectElement();
+    OptionElement option = Document.get().createOptionElement();
+    option.setText(item);
+    option.setValue(value);
+
+    if ((index == -1) || (index == select.getLength())) {
+      select.add(option, null);
+    } else {
+      OptionElement before = select.getOptions().getItem(index);
+      select.add(option, before);
+    }
   }
 
   /**
@@ -331,7 +242,7 @@
    */
   public boolean isItemSelected(int index) {
     checkIndex(index);
-    return impl.isItemSelected(getElement(), index);
+    return getSelectElement().getOptions().getItem(index).isSelected();
   }
 
   /**
@@ -340,12 +251,12 @@
    * @return <code>true</code> if multiple selection is allowed
    */
   public boolean isMultipleSelect() {
-    return DOM.getElementPropertyBoolean(getElement(), "multiple");
+    return getSelectElement().isMultiple();
   }
 
   @Override
   public void onBrowserEvent(Event event) {
-    if (DOM.eventGetType(event) == Event.ONCHANGE) {
+    if (event.getTypeInt() == Event.ONCHANGE) {
       if (changeListeners != null) {
         changeListeners.fireChange(this);
       }
@@ -368,7 +279,7 @@
    */
   public void removeItem(int index) {
     checkIndex(index);
-    impl.removeItem(getElement(), index);
+    getSelectElement().remove(index);
   }
 
   /**
@@ -385,7 +296,7 @@
    */
   public void setItemSelected(int index, boolean selected) {
     checkIndex(index);
-    impl.setItemSelected(getElement(), index, selected);
+    getSelectElement().getOptions().getItem(index).setSelected(selected);
   }
 
   /**
@@ -400,7 +311,7 @@
     if (text == null) {
       throw new NullPointerException("Cannot set an option to have null text");
     }
-    DOM.setOptionText(getElement(), text, index);
+    getSelectElement().getOptions().getItem(index).setText(text);
   }
 
   /**
@@ -413,11 +324,11 @@
    */
   public void setMultipleSelect(boolean multiple) {
     // TODO: we can remove the above doc admonition once we address issue 1007
-    DOM.setElementPropertyBoolean(getElement(), "multiple", multiple);
+    getSelectElement().setMultiple(multiple);
   }
 
   public void setName(String name) {
-    DOM.setElementProperty(getElement(), "name", name);
+    getSelectElement().setName(name);
   }
 
   /**
@@ -435,7 +346,7 @@
    * @param index the index of the item to be selected
    */
   public void setSelectedIndex(int index) {
-    DOM.setElementPropertyInt(getElement(), "selectedIndex", index);
+    getSelectElement().setSelectedIndex(index);
   }
 
   /**
@@ -449,7 +360,7 @@
    */
   public void setValue(int index, String value) {
     checkIndex(index);   
-    impl.setValue(getElement(), index, value);
+    getSelectElement().getOptions().getItem(index).setValue(value);
   }
 
   /**
@@ -459,7 +370,7 @@
    * @param visibleItems the visible item count
    */
   public void setVisibleItemCount(int visibleItems) {
-    DOM.setElementPropertyInt(getElement(), "size", visibleItems);
+    getSelectElement().setSize(visibleItems);
   }
 
   /**
@@ -475,10 +386,9 @@
     super.onEnsureDebugId(baseID);
 
     // Set the id of each option
-    Element selectElem = getElement();
     int numItems = getItemCount();
     for (int i = 0; i < numItems; i++) {
-      ensureDebugId(impl.getItemElement(selectElem, i), baseID, "item" + i);
+      ensureDebugId(getSelectElement().getOptions().getItem(i), baseID, "item" + i);
     }
   }
 
@@ -487,4 +397,8 @@
       throw new IndexOutOfBoundsException();
     }
   }
+
+  private SelectElement getSelectElement() {
+    return getElement().cast();
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java b/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
index de63755..f2a80b1 100644
--- a/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
+++ b/user/src/com/google/gwt/user/client/ui/PasswordTextBox.java
@@ -16,9 +16,8 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 
 /**
  * A text box that visually masks its input to prevent eavesdropping.
@@ -30,11 +29,13 @@
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-PasswordTextBox { primary style }</li>
- * <li>.gwt-PasswordTextBox-readonly { dependent style set when the password text box is read-only }</li>
+ * <li>.gwt-PasswordTextBox-readonly { dependent style set when the password
+ * text box is read-only }</li>
  * </ul>
  * 
  * <p>
- * <h3>Example</h3> {@example com.google.gwt.examples.TextBoxExample}
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.TextBoxExample}
  * </p>
  */
 public class PasswordTextBox extends TextBox {
@@ -43,16 +44,17 @@
    * Creates a PasswordTextBox widget that wraps an existing &lt;input
    * type='password'&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static PasswordTextBox wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("password");
+  public static PasswordTextBox wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    PasswordTextBox textBox = new PasswordTextBox((Element) element);
+    PasswordTextBox textBox = new PasswordTextBox(element);
 
     // Mark it attached and remember it for cleanup.
     textBox.onAttach();
@@ -65,11 +67,18 @@
    * Creates an empty password text box.
    */
   public PasswordTextBox() {
-    super(DOM.createInputPassword());
-    setStyleName("gwt-PasswordTextBox");
+    super(Document.get().createPasswordInputElement(), "gwt-PasswordTextBox");
   }
 
-  private PasswordTextBox(Element element) {
-    super(element);
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is
+   * 'password'.
+   * 
+   * @param element the element to be used
+   */
+  protected PasswordTextBox(Element element) {
+    super(element, null);
+    assert InputElement.as(element).getType().equalsIgnoreCase("password");
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index cc64a7a..b928838 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -71,7 +71,7 @@
   /**
    * An {@link Animation} used to enlarge the popup into view.
    */
-  private static class ResizeAnimation extends Animation {
+  static class ResizeAnimation extends Animation {
     /**
      * The offset height and width of the current {@link PopupPanel}.
      */
@@ -695,7 +695,18 @@
       }
     }
   }
-
+  
+  /**
+   * Sets the animation used to animate this popup. Used by gwt-incubator to
+   * allow DropDownPanel to override the default popup animation. Not protected
+   * because the exact API may change in gwt 1.6.
+   * 
+   * @param animation the animation to use for this popup
+   */
+  void setAnimation(ResizeAnimation animation) {
+    resizeAnimation = animation;
+  }
+  
   /**
    * Enable or disable animation of the {@link PopupPanel}.
    * 
diff --git a/user/src/com/google/gwt/user/client/ui/RootPanel.java b/user/src/com/google/gwt/user/client/ui/RootPanel.java
index ea02702..62af479 100644
--- a/user/src/com/google/gwt/user/client/ui/RootPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/RootPanel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -15,17 +15,18 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection;
+import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.WindowCloseListener;
-import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.i18n.client.BidiUtils;
-import com.google.gwt.i18n.client.HasDirection;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * The panel to which all other widgets must ultimately be added. RootPanels are
@@ -38,24 +39,59 @@
  */
 public class RootPanel extends AbsolutePanel {
 
-  private static HashMap<String, RootPanel> rootPanels = new HashMap<String, RootPanel>();
-  private static List<Widget> widgetsToDetach = new ArrayList<Widget>();
+  private static Map<String, RootPanel> rootPanels = new HashMap<String, RootPanel>();
+  private static Set<Widget> widgetsToDetach = new HashSet<Widget>();
 
   /**
-   * Adds a widget to the list of widgets to be detached when the page unloads.
+   * Adds a widget to the detach list. This is the list of widgets to be
+   * detached when the page unloads.
    * 
    * This method must be called for all widgets that have no parent widgets.
    * These are most commonly {@link RootPanel RootPanels}, but can also be any
-   * widget used to wrap an existing element on the page. Failing to do this
-   * may cause these widgets to leak memory.
+   * widget used to wrap an existing element on the page. Failing to do this may
+   * cause these widgets to leak memory. This method is called automatically by
+   * widgets' wrap methods (e.g.
+   * {@link Button#wrap(com.google.gwt.dom.client.Element)}).
    * 
    * @param widget the widget to be cleaned up when the page closes
+   * @see #detachNow(Widget)
    */
   public static void detachOnWindowClose(Widget widget) {
+    assert !widgetsToDetach.contains(widget) : "detachOnUnload() called twice "
+        + "for the same widget";
+
     widgetsToDetach.add(widget);
   }
 
   /**
+   * Marks a widget as detached and removes it from the detach list.
+   * 
+   * If an element belonging to a widget originally passed to
+   * {@link #detachOnWindowClose(Widget)} has been removed from the document, calling
+   * this method will cause it to be marked as detached immediately. Failure to
+   * do so will keep the widget from being garbage collected until the page is
+   * unloaded.
+   * 
+   * This method may only be called per widget, and only for widgets that were
+   * originally passed to {@link #detachOnWindowClose(Widget)}. Any widget in the
+   * detach list, whose element is no longer in the document when the page
+   * unloads, will cause an assertion error.
+   * 
+   * @param widget the widget that no longer needs to be cleaned up when the
+   *          page closes
+   * @see #detachOnWindowClose(Widget)
+   */
+  public static void detachNow(Widget widget) {
+    assert !getBodyElement().isOrHasChild(widget.getElement()) : "detachNow() "
+        + "called on a widget whose element is still attached to the document";
+    assert widgetsToDetach.contains(widget) : "detachNow() called on a widget "
+        + "not currently in the detach list";
+
+    widget.onDetach();
+    widgetsToDetach.remove(widget);
+  }
+
+  /**
    * Gets the default root panel. This panel wraps body of the browser's
    * document. This root panel can contain any number of widgets, which will be
    * laid out in their natural HTML ordering. Many applications, however, will
@@ -90,18 +126,18 @@
       }
     }
 
-    // Note that the code in this if block only happens once - 
-    // on the first RootPanel.get(String) or RootPanel.get() 
+    // Note that the code in this if block only happens once -
+    // on the first RootPanel.get(String) or RootPanel.get()
     // call.
-    
     if (rootPanels.size() == 0) {
       hookWindowClosing();
-      
+
       // If we're in a RTL locale, set the RTL directionality
       // on the entire document.
       if (LocaleInfo.getCurrentLocale().isRTL()) {
-        BidiUtils.setDirectionOnElement(getRootElement(), HasDirection.Direction.RTL);
-      }      
+        BidiUtils.setDirectionOnElement(getRootElement(),
+            HasDirection.Direction.RTL);
+      }
     }
 
     // Create the panel and put it in the map.
@@ -110,7 +146,7 @@
       elem = getBodyElement();
     }
     rootPanels.put(id, rp = new RootPanel(elem));
-    widgetsToDetach.add(rp);
+    detachOnWindowClose(rp);
     return rp;
   }
 
@@ -123,10 +159,30 @@
     return $doc.body;
   }-*/;
 
+  // Package-protected for use by unit tests. Do not call this method directly.
+  static void detachWidgets() {
+    // When the window is closing, detach all widgets that need to be
+    // cleaned up. This will cause all of their event listeners
+    // to be unhooked, which will avoid potential memory leaks.
+    for (Widget widget : widgetsToDetach) {
+      if (widget.isAttached()) {
+        widget.onDetach();
+      }
+
+      // Assert that each widget's element is actually attached to the
+      // document. If not, then it was probably wrapped and removed, but not
+      // properly detached.
+      assert getBodyElement().isOrHasChild(widget.getElement()) : "A "
+          + "widget in the detach list was found not attached to the "
+          + "document. The is likely caused by wrapping an existing "
+          + "element and removing it from the document without calling "
+          + "RootPanel.detachNow().";
+    }
+  }
+
   /**
-   * Convenience method for getting the document's root 
-   * (<html>) element.
-   *
+   * Convenience method for getting the document's root (<html>) element.
+   * 
    * @return the document's root element
    */
   private static native Element getRootElement() /*-{
@@ -137,15 +193,7 @@
     // Catch the window closing event.
     Window.addWindowCloseListener(new WindowCloseListener() {
       public void onWindowClosed() {
-        // When the window is closing, detach all widgets that need to be
-        // cleaned up. This will cause all of their event listeners
-        // to be unhooked, which will avoid potential memory leaks.
-        for (int i = 0; i < widgetsToDetach.size(); ++i) {
-          Widget widget = widgetsToDetach.get(i);
-          if (widget.isAttached()) {
-            widget.onDetach();
-          }
-        }
+        detachWidgets();
       }
 
       public String onWindowClosing() {
diff --git a/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java b/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java
index d241e1115..602ffe5 100644
--- a/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java
+++ b/user/src/com/google/gwt/user/client/ui/SimpleCheckBox.java
@@ -34,13 +34,14 @@
    * Creates a SimpleCheckBox widget that wraps an existing &lt;input
    * type='checkbox'&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
   public static SimpleCheckBox wrap(Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("checkbox");
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
     SimpleCheckBox checkBox = new SimpleCheckBox(element);
@@ -56,14 +57,28 @@
    * Creates a new simple checkbox.
    */
   public SimpleCheckBox() {
-    setElement(Document.get().createCheckInputElement());
-    setStyleName("gwt-SimpleCheckBox");
+    this(Document.get().createCheckInputElement(), "gwt-SimpleCheckBox");
   }
 
-  SimpleCheckBox(Element element) {
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is either
+   * 'checkbox'.
+   * 
+   * @param element the element to be used
+   */
+  protected SimpleCheckBox(Element element) {
+    assert InputElement.as(element).getType().equalsIgnoreCase("checkbox");
     setElement(element);
   }
 
+  SimpleCheckBox(Element element, String styleName) {
+    setElement(element);
+    if (styleName != null) {
+      setStyleName(styleName);
+    }
+  }
+
   public String getName() {
     return getInputElement().getName();
   }
diff --git a/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java b/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java
index aac907b..5d415fe 100644
--- a/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java
+++ b/user/src/com/google/gwt/user/client/ui/SimpleRadioButton.java
@@ -34,13 +34,14 @@
    * Creates a SimpleRadioButton widget that wraps an existing &lt;input
    * type='radio'&gt; element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
   public static SimpleRadioButton wrap(Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("radio");
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
     SimpleRadioButton radioButton = new SimpleRadioButton(element);
@@ -63,11 +64,18 @@
    * @param name the group name with which to associate the radio button
    */
   public SimpleRadioButton(String name) {
-    this(Document.get().createRadioInputElement(name));
-    setStyleName("gwt-SimpleRadioButton");
+    super(Document.get().createRadioInputElement(name), "gwt-SimpleRadioButton");
   }
 
-  private SimpleRadioButton(Element element) {
-    super(element);
+  /**
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is
+   * 'radio'.
+   * 
+   * @param element the element to be used
+   */
+  protected SimpleRadioButton(Element element) {
+    super(element, null);
+    assert InputElement.as(element).getType().equalsIgnoreCase("radio");
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/TextBox.java b/user/src/com/google/gwt/user/client/ui/TextBox.java
index a3ff602..ea77484 100644
--- a/user/src/com/google/gwt/user/client/ui/TextBox.java
+++ b/user/src/com/google/gwt/user/client/ui/TextBox.java
@@ -16,11 +16,10 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 
 /**
  * A standard single-line text box.
@@ -32,7 +31,8 @@
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-TextBox { primary style }</li>
- * <li>.gwt-TextBox-readonly { dependent style set when the text box is read-only }</li>
+ * <li>.gwt-TextBox-readonly { dependent style set when the text box is
+ * read-only }</li>
  * </ul>
  * 
  * <p>
@@ -46,16 +46,17 @@
    * Creates a TextBox widget that wraps an existing &lt;input type='text'&gt;
    * element.
    * 
-   * This element must already be attached to the document.
+   * This element must already be attached to the document. If the element is
+   * removed from the document, you must call
+   * {@link RootPanel#detachNow(Widget)}.
    * 
    * @param element the element to be wrapped
    */
-  public static TextBox wrap(com.google.gwt.dom.client.Element element) {
-    // Assert that the element is of the correct type and is attached.
-    assert InputElement.as(element).getType().equalsIgnoreCase("text");
+  public static TextBox wrap(Element element) {
+    // Assert that the element is attached.
     assert Document.get().getBody().isOrHasChild(element);
 
-    TextBox textBox = new TextBox((Element) element);
+    TextBox textBox = new TextBox(element);
 
     // Mark it attached and remember it for cleanup.
     textBox.onAttach();
@@ -68,29 +69,39 @@
    * Creates an empty text box.
    */
   public TextBox() {
-    super(DOM.createInputText());
-    setStyleName("gwt-TextBox");
+    this(Document.get().createTextInputElement(), "gwt-TextBox");
   }
 
   /**
-   * Protected constructor for use by subclasses.
-   * @param element element
+   * This constructor may be used by subclasses to explicitly use an existing
+   * element. This element must be an &lt;input&gt; element whose type is
+   * 'text'.
+   * 
+   * @param element the element to be used
    */
-  TextBox(Element element) {
-    super(element); 
+  protected TextBox(Element element) {
+    super(element);
+    assert InputElement.as(element).getType().equalsIgnoreCase("text");
+  }
+
+  TextBox(Element element, String styleName) {
+    super(element);
+    if (styleName != null) {
+      setStyleName(styleName);
+    }
   }
 
   public Direction getDirection() {
-    return BidiUtils.getDirectionOnElement(getElement());    
+    return BidiUtils.getDirectionOnElement(getElement());
   }
-  
+
   /**
    * Gets the maximum allowable length of the text box.
    * 
    * @return the maximum length, in characters
    */
   public int getMaxLength() {
-    return DOM.getElementPropertyInt(getElement(), "maxLength");
+    return getInputElement().getMaxLength();
   }
 
   /**
@@ -99,20 +110,20 @@
    * @return the number of visible characters
    */
   public int getVisibleLength() {
-    return DOM.getElementPropertyInt(getElement(), "size");
+    return getInputElement().getSize();
   }
 
   public void setDirection(Direction direction) {
     BidiUtils.setDirectionOnElement(getElement(), direction);
   }
-    
+
   /**
    * Sets the maximum allowable length of the text box.
    * 
    * @param length the maximum length, in characters
    */
   public void setMaxLength(int length) {
-    DOM.setElementPropertyInt(getElement(), "maxLength", length);
+    getInputElement().setMaxLength(length);
   }
 
   /**
@@ -121,6 +132,10 @@
    * @param length the number of visible characters
    */
   public void setVisibleLength(int length) {
-    DOM.setElementPropertyInt(getElement(), "size", length);
+    getInputElement().setSize(length);
+  }
+
+  private InputElement getInputElement() {
+    return getElement().cast();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/TextBoxBase.java b/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
index 665e8f9..5011cac 100644
--- a/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
+++ b/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
@@ -16,8 +16,8 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.impl.TextBoxImpl;
 
diff --git a/user/src/com/google/gwt/user/client/ui/impl/FormPanelImpl.java b/user/src/com/google/gwt/user/client/ui/impl/FormPanelImpl.java
index 959c66e..27ecc4d 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/FormPanelImpl.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/FormPanelImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.user.client.ui.impl;
 
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * Implementation class used by {@link com.google.gwt.user.client.ui.FormPanel}.
diff --git a/user/src/com/google/gwt/user/client/ui/impl/FormPanelImplIE6.java b/user/src/com/google/gwt/user/client/ui/impl/FormPanelImplIE6.java
index ecb0fb9..bb52b3e 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/FormPanelImplIE6.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/FormPanelImplIE6.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.user.client.ui.impl;
 
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 
 /**
  * IE6 implementation of {@link com.google.gwt.user.client.ui.impl.FormPanelImpl}.
diff --git a/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java b/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
index e116c6c..b81e7eb 100644
--- a/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
+++ b/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -18,7 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.i18n.rebind.AbstractResource;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 
 /**
  * Creates method factories depending upon the method type. Includes the core
@@ -47,11 +47,11 @@
    * 
    * @param logger TreeLogger for logging
    * @param targetMethod Method
-   * @param resource base resource to use for lookup
+   * @param resourceList base resource to use for lookup
    * @throws UnableToCompleteException
    */
   public abstract void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String key, AbstractResource resource, String locale) throws UnableToCompleteException;
+      String key, ResourceList resourceList, String locale) throws UnableToCompleteException;
 
   /**
    * Prints to the current <code>AbstractGeneratorClassCreator</code>.
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index f7c24ee..f1a9e0b 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -32,6 +32,7 @@
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.user.client.rpc.IsSerializable;
 import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
+import com.google.gwt.user.rebind.rpc.TypePaths.TypePath;
 
 import java.io.OutputStream;
 import java.io.PrintWriter;
@@ -82,12 +83,6 @@
  */
 public class SerializableTypeOracleBuilder {
 
-  interface Path {
-    Path getParent();
-
-    String toString();
-  }
-
   private class TypeInfoComputed {
 
     /**
@@ -127,7 +122,7 @@
     /**
      * Path used to discover this type.
      */
-    private final Path path;
+    private final TypePath path;
 
     /**
      * The state that this type is currently in.
@@ -139,7 +134,7 @@
      */
     private final JClassType type;
 
-    public TypeInfoComputed(JClassType type, Path path) {
+    public TypeInfoComputed(JClassType type, TypePath path) {
       this.type = type;
       this.path = path;
       autoSerializable = SerializableTypeOracleBuilder.isAutoSerializable(type);
@@ -151,7 +146,7 @@
       return manualSerializer;
     }
 
-    public Path getPath() {
+    public TypePath getPath() {
       return path;
     }
 
@@ -471,100 +466,6 @@
     return true;
   }
 
-  private static Path createArrayComponentPath(final JArrayType arrayType,
-      final Path parent) {
-    return new Path() {
-      public Path getParent() {
-        return parent;
-      }
-
-      @Override
-      public String toString() {
-        return "Type '"
-            + arrayType.getComponentType().getParameterizedQualifiedSourceName()
-            + "' is reachable from array type '"
-            + arrayType.getParameterizedQualifiedSourceName() + "'";
-      }
-    };
-  }
-
-  private static Path createFieldPath(final Path parent, final JField field) {
-    return new Path() {
-      public Path getParent() {
-        return parent;
-      }
-
-      @Override
-      public String toString() {
-        JType type = field.getType();
-        JClassType enclosingType = field.getEnclosingType();
-        return "'" + type.getParameterizedQualifiedSourceName()
-            + "' is reachable from field '" + field.getName() + "' of type '"
-            + enclosingType.getParameterizedQualifiedSourceName() + "'";
-      }
-    };
-  }
-
-  private static Path createRootPath(final JType type) {
-    return new Path() {
-      public Path getParent() {
-        return null;
-      }
-
-      @Override
-      public String toString() {
-        return "Started from '" + type.getParameterizedQualifiedSourceName()
-            + "'";
-      }
-    };
-  }
-
-  private static Path createSubtypePath(final Path parent, final JType type,
-      final JClassType supertype) {
-    return new Path() {
-      public Path getParent() {
-        return parent;
-      }
-
-      @Override
-      public String toString() {
-        return "'" + type.getParameterizedQualifiedSourceName()
-            + "' is reachable as a subtype of type '" + supertype + "'";
-      }
-    };
-  }
-
-  private static Path createSupertypePath(final Path parent, final JType type,
-      final JClassType subtype) {
-    return new Path() {
-      public Path getParent() {
-        return parent;
-      }
-
-      @Override
-      public String toString() {
-        return "'" + type.getParameterizedQualifiedSourceName()
-            + "' is reachable as a supertype of type '" + subtype + "'";
-      }
-    };
-  }
-
-  private static Path createTypeArgumentPath(final Path parent,
-      final JClassType type, final int typeArgIndex, final JClassType typeArg) {
-    return new Path() {
-      public Path getParent() {
-        return parent;
-      }
-
-      @Override
-      public String toString() {
-        return "'" + typeArg.getParameterizedQualifiedSourceName()
-            + "' is reachable from type argument " + typeArgIndex
-            + " of type '" + type.getParameterizedQualifiedSourceName() + "'";
-      }
-    };
-  }
-
   private static boolean directlyImplementsMarkerInterface(JClassType type) {
     try {
       return TypeHierarchyUtils.directlyImplementsInterface(type,
@@ -754,7 +655,7 @@
     boolean allSucceeded = true;
     for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
       allSucceeded &= checkTypeInstantiable(entry.getValue(), entry.getKey(),
-          false, createRootPath(entry.getKey()));
+          false, TypePaths.createRootPath(entry.getKey()));
     }
 
     if (!allSucceeded) {
@@ -827,18 +728,18 @@
    * The method is exposed using default access to enable testing.
    */
   final boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative, Path path) {
+      boolean isSpeculative, TypePath path) {
     return checkTypeInstantiable(logger, type, isSpeculative, path,
         new HashSet<JClassType>());
   }
 
   /**
    * Same as
-   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean, com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder.Path)},
+   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean, com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder.TypePath)},
    * except that returns the set of instantiable subtypes.
    */
   boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative, Path path, Set<JClassType> instSubtypes) {
+      boolean isSpeculative, TypePath path, Set<JClassType> instSubtypes) {
     assert (type != null);
     if (type.isPrimitive() != null) {
       return true;
@@ -856,8 +757,7 @@
       if (typeParametersInRootTypes.contains(isTypeParameter)) {
         return checkTypeInstantiable(localLogger,
             isTypeParameter.getFirstBound(), isSpeculative,
-            createTypeArgumentPath(path, isTypeParameter.getDeclaringClass(),
-                isTypeParameter.getOrdinal(), isTypeParameter.getFirstBound()),
+            TypePaths.createTypeParameterInRootPath(path, isTypeParameter),
             instSubtypes);
       }
 
@@ -948,7 +848,7 @@
    * 
    * @param logger
    */
-  private void checkAllSubtypesOfObject(TreeLogger logger, Path parent) {
+  private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent) {
     if (alreadyCheckedObject) {
       return;
     }
@@ -965,14 +865,15 @@
     JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
     for (JClassType cls : allTypes) {
       if (isDeclaredSerializable(cls)) {
-        checkTypeInstantiable(localLogger, cls, true, createSubtypePath(parent,
-            cls, typeOracle.getJavaLangObject()));
+        checkTypeInstantiable(localLogger, cls, true,
+            TypePaths.createSubtypePath(parent, cls,
+                typeOracle.getJavaLangObject()));
       }
     }
   }
 
   private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
-      boolean isSpeculative, Path path) {
+      boolean isSpeculative, TypePath path) {
 
     JType leafType = array.getLeafType();
     JWildcardType leafWild = leafType.isWildcard();
@@ -1007,7 +908,8 @@
     Set<JClassType> instantiableTypes = new HashSet<JClassType>();
 
     boolean succeeded = checkTypeInstantiable(branch, array.getComponentType(),
-        isSpeculative, createArrayComponentPath(array, path), instantiableTypes);
+        isSpeculative, TypePaths.createArrayComponentPath(array, path),
+        instantiableTypes);
     if (succeeded && leafClass != null) {
       TreeLogger covariantArrayLogger = logger.branch(TreeLogger.DEBUG,
           "Covariant array types");
@@ -1047,7 +949,7 @@
    * necessary types.
    */
   private boolean checkDeclaredFields(TreeLogger logger,
-      TypeInfoComputed typeInfo, boolean isSpeculative, Path parent) {
+      TypeInfoComputed typeInfo, boolean isSpeculative, TypePath parent) {
 
     JClassType classOrInterface = typeInfo.getType();
     if (classOrInterface.isEnum() != null) {
@@ -1079,7 +981,7 @@
             field.toString(), null);
         JType fieldType = field.getType();
 
-        Path path = createFieldPath(parent, field);
+        TypePath path = TypePaths.createFieldPath(parent, field);
         if (typeInfo.isManuallySerializable()
             && fieldType.getLeafType() == typeOracle.getJavaLangObject()) {
           checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN,
@@ -1101,7 +1003,7 @@
   }
 
   private boolean checkSubtype(TreeLogger logger, JClassType classOrInterface,
-      JClassType originalType, boolean isSpeculative, Path parent) {
+      JClassType originalType, boolean isSpeculative, TypePath parent) {
     if (classOrInterface.isEnum() != null) {
       // The fields of an enum are never serialized; they are always okay.
       return true;
@@ -1145,7 +1047,7 @@
       boolean superTypeOk = false;
       if (superType != null) {
         superTypeOk = checkSubtype(logger, superType, originalType,
-            isSpeculative, createSupertypePath(parent, superType,
+            isSpeculative, TypePaths.createSupertypePath(parent, superType,
                 classOrInterface));
       }
 
@@ -1169,7 +1071,7 @@
    * instantiable relative to a known base type.
    */
   private boolean checkSubtypes(TreeLogger logger, JClassType originalType,
-      Set<JClassType> instSubtypes, Path path) {
+      Set<JClassType> instSubtypes, TypePath path) {
     JClassType baseType = getBaseType(originalType);
     TreeLogger computationLogger = logger.branch(TreeLogger.DEBUG,
         "Finding possibly instantiable subtypes");
@@ -1195,7 +1097,8 @@
         continue;
       }
 
-      Path subtypePath = createSubtypePath(path, candidate, originalType);
+      TypePath subtypePath = TypePaths.createSubtypePath(path, candidate,
+          originalType);
       TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath);
       if (tic.isDone()) {
         anySubtypes |= tic.isInstantiable();
@@ -1247,7 +1150,7 @@
    *         <code>typeArg</code>.
    */
   private boolean checkTypeArgument(TreeLogger logger, JGenericType baseType,
-      int paramIndex, JClassType typeArg, boolean isSpeculative, Path parent) {
+      int paramIndex, JClassType typeArg, boolean isSpeculative, TypePath parent) {
     JWildcardType isWildcard = typeArg.isWildcard();
     if (isWildcard != null) {
       return checkTypeArgument(logger, baseType, paramIndex,
@@ -1279,7 +1182,8 @@
       }
     }
 
-    Path path = createTypeArgumentPath(parent, baseType, paramIndex, typeArg);
+    TypePath path = TypePaths.createTypeArgumentPath(parent, baseType, paramIndex,
+        typeArg);
     int exposure = getTypeParameterExposure(baseType, paramIndex);
     switch (exposure) {
       case TypeParameterExposureComputer.EXPOSURE_DIRECT: {
@@ -1370,7 +1274,7 @@
     return possiblyInstantiableTypes;
   }
 
-  private TypeInfoComputed getTypeInfoComputed(JClassType type, Path path) {
+  private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path) {
     TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
     if (tic == null) {
       tic = new TypeInfoComputed(type, path);
@@ -1400,7 +1304,7 @@
     return false;
   }
 
-  private void logPath(TreeLogger logger, Path path) {
+  private void logPath(TreeLogger logger, TypePath path) {
     if (path == null) {
       return;
     }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypePaths.java b/user/src/com/google/gwt/user/rebind/rpc/TypePaths.java
new file mode 100644
index 0000000..ede05da
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypePaths.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+
+/**
+ * Paths of types and utility methods for creating them. These are used by
+ * {@link SerializableTypeOracleBuilder} to record why it visits the types it
+ * does.
+ */
+class TypePaths {
+  /**
+   * A path of types. This interface does not currently expose the type itself,
+   * because these are currently only used for logging.
+   */
+  interface TypePath {
+    /**
+     * Get the previous element on this type path, or <code>null</code> if
+     * this is a one-element path.
+     */
+    TypePath getParent();
+
+    String toString();
+  }
+
+  static TypePaths.TypePath createArrayComponentPath(
+      final JArrayType arrayType, final TypePaths.TypePath parent) {
+    assert (arrayType != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "Type '"
+            + arrayType.getComponentType().getParameterizedQualifiedSourceName()
+            + "' is reachable from array type '"
+            + arrayType.getParameterizedQualifiedSourceName() + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createFieldPath(final TypePaths.TypePath parent,
+      final JField field) {
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        JType type = field.getType();
+        JClassType enclosingType = field.getEnclosingType();
+        return "'" + type.getParameterizedQualifiedSourceName()
+            + "' is reachable from field '" + field.getName() + "' of type '"
+            + enclosingType.getParameterizedQualifiedSourceName() + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createRootPath(final JType type) {
+    assert (type != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return null;
+      }
+
+      @Override
+      public String toString() {
+        return "Started from '" + type.getParameterizedQualifiedSourceName()
+            + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createSubtypePath(final TypePaths.TypePath parent,
+      final JType type, final JClassType supertype) {
+    assert (type != null);
+    assert (supertype != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "'" + type.getParameterizedQualifiedSourceName()
+            + "' is reachable as a subtype of type '" + supertype + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createSupertypePath(
+      final TypePaths.TypePath parent, final JType type,
+      final JClassType subtype) {
+    assert (type != null);
+    assert (subtype != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "'" + type.getParameterizedQualifiedSourceName()
+            + "' is reachable as a supertype of type '" + subtype + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createTypeArgumentPath(
+      final TypePaths.TypePath parent, final JGenericType baseType,
+      final int typeArgIndex, final JClassType typeArg) {
+    assert (baseType != null);
+    assert (typeArg != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "'" + typeArg.getParameterizedQualifiedSourceName()
+            + "' is reachable from type argument " + typeArgIndex
+            + " of type '" + baseType.getParameterizedQualifiedSourceName()
+            + "'";
+      }
+    };
+  }
+
+  static TypePaths.TypePath createTypeParameterInRootPath(
+      final TypePaths.TypePath parent, final JTypeParameter typeParameter) {
+    assert (typeParameter != null);
+
+    return new TypePaths.TypePath() {
+      public TypePaths.TypePath getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        String parameterString = typeParameter.getName();
+        if (typeParameter.getDeclaringClass() != null) {
+          parameterString += " of class "
+              + typeParameter.getDeclaringClass().getQualifiedSourceName();
+        }
+        return "'"
+            + typeParameter.getFirstBound().getParameterizedQualifiedSourceName()
+            + "' is reachable as an upper bound of type parameter "
+            + parameterString + ", which appears in a root type";
+      }
+    };
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/Arrays.java b/user/super/com/google/gwt/emul/java/util/Arrays.java
index 97a1ce1..a12e24e 100644
--- a/user/super/com/google/gwt/emul/java/util/Arrays.java
+++ b/user/super/com/google/gwt/emul/java/util/Arrays.java
@@ -882,8 +882,8 @@
       return 0;
     }
     int hashCode = 1;
-    for (int i = 0, n = a.length; i < n; ++i) {
-      hashCode = (31 * hashCode + a[i].hashCode()) | 0;
+    for (Object e : a) {
+      hashCode = (31 * hashCode + (e == null ? 0 : e.hashCode())) | 0;
     }
 
     return hashCode;
diff --git a/user/super/com/google/gwt/emul/java/util/TreeMap.java b/user/super/com/google/gwt/emul/java/util/TreeMap.java
index abaf942..fa3cb12 100644
--- a/user/super/com/google/gwt/emul/java/util/TreeMap.java
+++ b/user/super/com/google/gwt/emul/java/util/TreeMap.java
@@ -159,7 +159,7 @@
     public int size() {
       return TreeMap.this.size();
     }
-  };
+  }
 
   /**
    * Tree node.
@@ -506,13 +506,16 @@
   // The number of nodes in the tree.
   private int size = 0;
 
-  @SuppressWarnings("unchecked")
   public TreeMap() {
-    this((Comparator<? super K>) DEFAULT_COMPARATOR);
+    this((Comparator<? super K>) null);
   }
 
+  @SuppressWarnings("unchecked")
   public TreeMap(Comparator<? super K> c) {
     root = null;
+    if (c == null) {
+      c = (Comparator<? super K>) DEFAULT_COMPARATOR;
+    }
     cmp = c;
   }
 
@@ -523,10 +526,7 @@
 
   @SuppressWarnings("unchecked")
   public TreeMap(SortedMap<K, ? extends V> map) {
-    cmp = map.comparator();
-    if (cmp == null) {
-      cmp = (Comparator<? super K>) DEFAULT_COMPARATOR;
-    }
+    this(map.comparator());
     putAll(map); // TODO(jat): more efficient init from sorted map
   }
 
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
index 27e904e..74a017b 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -143,6 +143,8 @@
 
   private static int sideEffectChecker;
 
+  private static volatile int THREE = 3;
+
   private static volatile boolean TRUE = true;
 
   private static volatile int volatileInt;
@@ -557,6 +559,21 @@
     }
     assertEquals(1, i);
     assertEquals(1, j);
+
+    /*
+     * Issue 2069: a default with a break should not be stripped unless the
+     * break has no label.
+     */
+    outer : while (true) {
+      switch (THREE) {
+        case 0:
+        case 1:
+          break;
+        default:
+          break outer;
+      }
+      fail("should not be reached");
+    }
   }
 
   public void testLocalClasses() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java b/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
index a01dc69..ab2868e 100644
--- a/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/NativeLongTest.java
@@ -23,6 +23,20 @@
  * LongLibTest.
  */
 public class NativeLongTest extends GWTTestCase {
+  private static class RequestIdFactory {
+    static RequestIdFactory instance = new RequestIdFactory();
+
+    static RequestIdFactory getInstance() {
+      return instance;
+    }
+
+    long nextId;
+
+    long getNextId() {
+      return nextId++;
+    }
+  }
+
   /*
    * These constants are done as volatile fields so that the compiler will not
    * constant fold them. The problem is that if you write assertEquals(2L,
@@ -30,6 +44,7 @@
    */
   private static volatile byte BYTE_FOUR = (byte) 4;
   private static volatile char CHAR_FOUR = (char) 4;
+  private static volatile boolean FALSE = false;
   private static volatile int INT_FOUR = 4;
   private static volatile long LONG_100 = 100L;
   private static volatile long LONG_1234 = 0x1234123412341234L;
@@ -177,4 +192,57 @@
   public void testToString() {
     assertEquals("1234123412341234", "" + LONG_1234_DECIMAL);
   }
+
+  /**
+   * It's important when allocating a new temporary that it is marked as in use.
+   */
+  public void testVariableReuseInCompoundAssignmentNormalizer1() {
+    if (FALSE) {
+      // Prevent inlining, so that CAN allocates temporaries predictably.
+      testVariableReuseInCompoundAssignmentNormalizer1();
+    }
+
+    assertEquals("0",
+        Long.toHexString(RequestIdFactory.getInstance().getNextId()));
+  }
+
+  /**
+   * Using differently typed temp variables can cause JArrayType to throw a
+   * class cast exception.
+   */
+  public void testVariableReuseInCompoundAssignmentNormalizer2() {
+    if (FALSE) {
+      // Prevent inlining, so that CAN allocates temporaries predictably.
+      testVariableReuseInCompoundAssignmentNormalizer2();
+    }
+    long ary[][] = new long[10][10];
+    long i = 3, j = 3;
+    assertEquals(0L, ary[(int) i++][(int) j++]++);
+    assertEquals(4L, i);
+    assertEquals(4L, j);
+    assertEquals(1L, ary[3][3]);
+  }
+
+  /**
+   * Using differently typed temp variables can cause LongEmulationNormalizer to
+   * fail an assertion.
+   */
+  public void testVariableReuseInCompoundAssignmentNormalizer3() {
+    if (FALSE) {
+      // Prevent inlining, so that CAN allocates temporaries predictably.
+      testVariableReuseInCompoundAssignmentNormalizer3();
+    }
+
+    long ary[] = new long[10];
+    int i = 3;
+    long j = 5;
+
+    assertEquals(0L, ary[i++]++);
+    assertEquals(4, i);
+    assertEquals(1L, ary[3]);
+
+    assertEquals(0L, ary[(int) j++]++);
+    assertEquals(6L, j);
+    assertEquals(1L, ary[5]);
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/java/util/ArraysTest.java b/user/test/com/google/gwt/emultest/java/util/ArraysTest.java
index 3592513..9a0f70f 100644
--- a/user/test/com/google/gwt/emultest/java/util/ArraysTest.java
+++ b/user/test/com/google/gwt/emultest/java/util/ArraysTest.java
@@ -62,6 +62,16 @@
   }
 
   /**
+   * Verifies that calling Arrays.hashCode(Object[]) with an array with
+   * embedded null references works properly (and most importantly doesn't
+   * throw an NPE).
+   */
+  public void testArraysHashCodeWithNullElements() {
+    String[] a = new String[] { "foo", null, "bar", "baz" };
+    Arrays.hashCode(a);
+  }
+
+  /**
    * Tests {@link Arrays#asList(Object[])}.
    */
   @SuppressWarnings("unchecked")
diff --git a/user/test/com/google/gwt/i18n/I18NSuite.java b/user/test/com/google/gwt/i18n/I18NSuite.java
index 77155de..d8ebab6 100644
--- a/user/test/com/google/gwt/i18n/I18NSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NSuite.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.i18n;
 
+import com.google.gwt.i18n.client.AnnotationsTest;
 import com.google.gwt.i18n.client.ArabicPluralsTest;
 import com.google.gwt.i18n.client.DateTimeFormat_de_Test;
 import com.google.gwt.i18n.client.DateTimeParse_en_Test;
@@ -42,6 +43,7 @@
     // $JUnit-BEGIN$
     suite.addTestSuite(AbstractResourceTest.class);
     suite.addTestSuite(ArabicPluralsTest.class);
+    suite.addTestSuite(AnnotationsTest.class);
     suite.addTestSuite(ConstantMapTest.class);
     suite.addTestSuite(DateTimeFormat_de_Test.class);
     suite.addTestSuite(DateTimeParse_en_Test.class);
diff --git a/user/test/com/google/gwt/i18n/client/AnnotationsTest.java b/user/test/com/google/gwt/i18n/client/AnnotationsTest.java
new file mode 100644
index 0000000..a26792a
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/AnnotationsTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2008 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.i18n.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests annotations not covered elsewhere.
+ */
+public class AnnotationsTest extends GWTTestCase {
+  
+  /**
+   * First grandparent for test.
+   */
+  public interface GP1 extends TestConstants {
+    @DefaultStringValue("gp1 annot")
+    String gp1();
+
+    @DefaultStringValue("gp1 shared annot")
+    String shared();
+  }
+  
+  /**
+   * Second grandparent for test.
+   */
+  public interface GP2 extends TestConstants {
+    @DefaultStringValue("gp2 annot")
+    String gp2();
+   
+    @DefaultStringValue("gp2 shared annot")
+    String shared();
+  }
+  
+  /**
+   * Test interface for P1 before P2.
+   */
+  public interface Inherit1 extends P1, P2 {
+  }
+  
+  /**
+   * Test interface for P2 before P1.
+   */
+  public interface Inherit2 extends P2, P1 {
+    @DefaultStringValue("def")
+    String def();
+  }
+  
+  /**
+   * Used to verify that we can explicitly localize messages in annotations.
+   */
+  @DefaultLocale("en")
+  public interface Inherit2_en extends Inherit2 {
+    @DefaultStringValue("en def")
+    String def();
+  }
+
+  /**
+   * First parent interface for test.
+   */
+  public interface P1 extends TestConstants {
+    @DefaultStringValue("p1 annot")
+    String p1();
+
+    @DefaultStringValue("p1 shared annot")
+    String shared();
+  }
+  
+  /**
+   * Second parent interface for test.
+   */
+  public interface P2 extends GP1, GP2 {
+    @DefaultStringValue("p2 annot")
+    String p2();
+
+    String shared();
+  }
+  
+  /**
+   * Basic test message.
+   */
+  public interface Msg1 extends Messages {
+    @DefaultMessage("Test {0}")
+    String getTest(String testName);
+  }
+  
+  /**
+   * Plural form test message.
+   */
+  public interface Msg2 extends Messages {
+    @DefaultMessage("You have {0} widgets.")
+    @PluralText({"one", "You have a widget."})
+    String getWidgetCount(@PluralCount int count);
+  }
+
+  /**
+   * Aggregate messages into one interface.
+   */
+  public interface AllMessages extends Msg1, Msg2 {
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.i18n.I18NTest_en";
+  }
+
+  public void testInheritance() {
+    Inherit1 i1 = GWT.create(Inherit1.class);
+    assertEquals("p1 annot", i1.p1());
+    assertEquals("gp2 annot", i1.gp2());
+    assertEquals("p1 shared annot", i1.shared());
+    Inherit2 i2 = GWT.create(Inherit2.class);
+    assertEquals("p1 annot", i2.p1());
+    assertEquals("gp2 annot", i2.gp2());
+    assertEquals("gp1 shared annot", i2.shared());
+    
+    // TODO(jat): this doesn't work because findDerivedClasses only
+    // looks for concrete classes, not other interfaces -- commenting
+    // out for now, revisit later.
+    // assertEquals("en def", i2.def());
+  }
+
+  public void testIssue2359() {
+    AllMessages m = GWT.create(AllMessages.class);
+    assertEquals("Test foo", m.getTest("foo"));
+    assertEquals("You have 2 widgets.", m.getWidgetCount(2));
+    assertEquals("You have a widget.", m.getWidgetCount(1));
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java b/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java
index 4144016..dadb7d2 100644
--- a/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java
+++ b/user/test/com/google/gwt/i18n/client/ArabicPluralsTest.java
@@ -23,6 +23,7 @@
  */
 public class ArabicPluralsTest extends GWTTestCase {
 
+  @Override
   public String getModuleName() {
     return "com.google.gwt.i18n.I18NTest_ar";
   }
diff --git a/user/test/com/google/gwt/i18n/client/I18N2Test.java b/user/test/com/google/gwt/i18n/client/I18N2Test.java
index 1c73374..3cc843b 100644
--- a/user/test/com/google/gwt/i18n/client/I18N2Test.java
+++ b/user/test/com/google/gwt/i18n/client/I18N2Test.java
@@ -113,8 +113,16 @@
 
   public void testCheckColorsAndShapes() {
     ColorsAndShapes s = (ColorsAndShapes) GWT.create(ColorsAndShapes.class);
-    // should not have changed, because we included no shapesAndColor info.
-    assertEquals("ýéļļöŵ", s.yellow());
+    // Red comes from Colors_b_C_d
+    assertEquals("red_b_C_d", s.red());
+    // Blue comes from Colors_b_C
+    assertEquals("blue_b_C", s.blue());
+    // Yellow comes from Colors_b
+    assertEquals("yellow_b", s.yellow());
+    // RedSquare comes from ColorsAndShapes
+    assertEquals("red square", s.redSquare());
+    // Circle comes from Shapes
+    assertEquals("a circle", s.circle());
   }
 
   public void testWalkUpColorTree() {
diff --git a/user/test/com/google/gwt/i18n/client/I18NTest.java b/user/test/com/google/gwt/i18n/client/I18NTest.java
index bf5e6b1..1e5e2a7 100644
--- a/user/test/com/google/gwt/i18n/client/I18NTest.java
+++ b/user/test/com/google/gwt/i18n/client/I18NTest.java
@@ -45,77 +45,23 @@
  * Tests Internationalization. Assumes locale is set to piglatin_UK
  */
 public class I18NTest extends GWTTestCase {
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.i18n.I18NTest";
   }
 
-  public void testLocalizableInner() {
-    // Check simple inner
-    LocalizableSimpleInner s = (LocalizableSimpleInner) GWT.create(Inners.LocalizableSimpleInner.class);
-    assertEquals("getLocalizableInner", s.getLocalizableInner());
-
-    LocalizableInnerInner localizableInnerInner = (LocalizableInnerInner) GWT.create(Inners.InnerClass.LocalizableInnerInner.class);
-    assertEquals("localizableInnerInner", localizableInnerInner.string());
-
-    // Check success of finding embedded
-    OuterLoc lock = (OuterLoc) GWT.create(OuterLoc.class);
-    assertEquals("piglatin", lock.string());
-
-    assertEquals("InnerLoc", Inners.testInnerLoc());
-  }
-
-  public void testLocalizableInterfaceInner() {
-    Inners inner = new Inners();
-
-    // Simple Inner
-    SimpleInner simpleInner = (SimpleInner) GWT.create(Inners.SimpleInner.class);
-    assertEquals(0, simpleInner.intZero());
-    assertEquals("Simple Inner", simpleInner.simpleInner());
-    assertTrue(inner.testProtectedInner());
-
-    // Has Inner
-    HasInner hasInner = (HasInner) GWT.create(Inners.HasInner.class);
-    assertEquals("Has Inner", hasInner.hasInner());
-    assertEquals(0, hasInner.floatZero(), .0001);
-
-    // Is Inner
-    IsInner isInner = (IsInner) GWT.create(IsInner.class);
-    assertEquals(2, isInner.isInner());
-
-    // Inner Inner
-    InnerInner innerInner = (InnerInner) GWT.create(InnerInner.class);
-    assertEquals(4.321, innerInner.innerInner(), .0001);
-    assertEquals("outer", innerInner.outer());
-
-    // Inner Inner Message
-    InnerInnerMessages innerInnerMessages = (InnerInnerMessages) GWT.create(InnerInnerMessages.class);
-    assertEquals("I am a person",
-        innerInnerMessages.innerClassMessages("person"));
-
-    // Extends Inner Inner
-    ExtendsInnerInner extendsInnerInner = (ExtendsInnerInner) GWT.create(ExtendsInnerInner.class);
-    assertEquals("Extends Inner Inner", extendsInnerInner.extendsInnerInner());
-
-    // Protected InnerClass
-    InnerClass innerClass = new Inners.InnerClass();
-    String extendsAnotherInner = innerClass.testExtendsAnotherInner();
-    assertEquals("{innerInner=4.321, outer=outer}", extendsAnotherInner);
-
-    // ExtendProtectedInner
-    String extendProtectedInner = innerClass.testExtendsProtectedInner();
-    assertEquals("Extend Protected Inner", extendProtectedInner);
-  }
-
   public void testAnnotatedConstants() {
     TestAnnotatedConstants c = GWT.create(TestAnnotatedConstants.class);
     assertEquals(14, c.fourteen());
     assertFalse(c.isFalse());
     assertTrue(c.isTrue());
-    assertArrayEquals(new String[] {"String array with one string"}, c.singleString());
-    assertArrayEquals(new String[] {"One", "Two", "Three,Comma"}, c.threeStrings());
+    assertArrayEquals(new String[] {"String array with one string"},
+        c.singleString());
+    assertArrayEquals(new String[] {"One", "Two", "Three,Comma"},
+        c.threeStrings());
     assertEquals("Properties value #s need quoting!", c.propertiesQuoting());
-    Map<String,String> stringMap = c.stringMap();
+    Map<String, String> stringMap = c.stringMap();
     assertTrue(stringMap.containsKey("key1"));
     assertTrue(stringMap.containsKey("key2"));
     assertEquals("value1", stringMap.get("key1"));
@@ -132,15 +78,16 @@
     assertEquals(3.14, c.threePointOneFour());
     assertEquals("Once more, with meaning", c.withMeaning());
   }
-  
+
   public void testAnnotatedConstantsGenMD5() {
     TestAnnotatedConstantsGenMD5 c = GWT.create(TestAnnotatedConstantsGenMD5.class);
     assertEquals(14, c.fourteen());
     assertFalse(c.isFalse());
     assertTrue(c.isTrue());
-    assertArrayEquals(new String[] {"String array with one string"}, c.singleString());
+    assertArrayEquals(new String[] {"String array with one string"},
+        c.singleString());
     assertArrayEquals(new String[] {"One", "Two"}, c.twoStrings());
-    Map<String,String> stringMap = c.stringMap();
+    Map<String, String> stringMap = c.stringMap();
     assertTrue(stringMap.containsKey("key1"));
     assertTrue(stringMap.containsKey("key2"));
     assertEquals("value1", stringMap.get("key1"));
@@ -151,7 +98,7 @@
     assertEquals(3.14, c.threePointOneFour());
     assertEquals("Once more, with meaning", c.withMeaning());
   }
-  
+
   public void testAnnotatedMessages() {
     TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
     assertEquals("Estay emay", m.basicText());
@@ -161,16 +108,20 @@
         m.optionalArgument("where am I?"));
     assertEquals("Two arguments, second and first, inverted",
         m.invertedArguments("first", "second")); // from default locale
-    assertEquals("PL: Don't tell me I can't {quote things in braces}", m.quotedText());
-    assertEquals("PL: This {0} would be an argument if not quoted", m.quotedArg());
+    assertEquals("PL: Don't tell me I can't {quote things in braces}",
+        m.quotedText());
+    assertEquals("PL: This {0} would be an argument if not quoted",
+        m.quotedArg());
     assertEquals("PL: Total is US$11,305.01", m.currencyFormat(11305.01));
-    assertEquals("PL: Default number format is 1,017.1", m.defaultNumberFormat(1017.1));
+    assertEquals("PL: Default number format is 1,017.1",
+        m.defaultNumberFormat(1017.1));
     assertEquals("PL: It is 12:01 PM on Saturday, December 1, 2007",
         m.getTimeDate(new Date(107, 11, 1, 12, 1, 2)));
     assertEquals("PL: 13 widgets", m.pluralWidgetsOther(13));
-    assertEquals("Too many widgets to count (150) in pig-latin", m.pluralWidgetsOther(150));
+    assertEquals("Too many widgets to count (150) in pig-latin",
+        m.pluralWidgetsOther(150));
   }
-  
+
   public void testBindings() {
     TestBinding b = (TestBinding) GWT.create(TestBinding.class);
     assertEquals("default", b.a());
@@ -453,28 +404,6 @@
     }
   }
 
-  // Uncomment for desk tests
-  // /**
-  // * Tests focus on correctness of entries, since ABCD exercises the map.
-  // */
-  // public void testConstantMapEmpty() {
-  // TestConstants types = (TestConstants) GWT.create(TestConstants.class);
-  //
-  // ConstantMap map = (ConstantMap) types.mapEmpty();
-  //
-  // assertEquals(0, map.size());
-  //
-  // Set keys = map.keySet();
-  // assertEquals(0, keys.size());
-  // Iterator keyIter = keys.iterator();
-  // assertFalse(keyIter.hasNext());
-  //
-  // Collection values = map.values();
-  // assertEquals(0, values.size());
-  // Iterator valueIter = values.iterator();
-  // assertFalse(valueIter.hasNext());
-  // }
-
   public void testDictionary() {
     createDummyDictionaries();
     Dictionary d = Dictionary.getDictionary("testDic");
@@ -511,6 +440,85 @@
     assertEquals(Integer.MIN_VALUE, types.intMin());
   }
 
+  // Uncomment for desk tests
+  // /**
+  // * Tests focus on correctness of entries, since ABCD exercises the map.
+  // */
+  // public void testConstantMapEmpty() {
+  // TestConstants types = (TestConstants) GWT.create(TestConstants.class);
+  //
+  // ConstantMap map = (ConstantMap) types.mapEmpty();
+  //
+  // assertEquals(0, map.size());
+  //
+  // Set keys = map.keySet();
+  // assertEquals(0, keys.size());
+  // Iterator keyIter = keys.iterator();
+  // assertFalse(keyIter.hasNext());
+  //
+  // Collection values = map.values();
+  // assertEquals(0, values.size());
+  // Iterator valueIter = values.iterator();
+  // assertFalse(valueIter.hasNext());
+  // }
+
+  public void testLocalizableInner() {
+    // Check simple inner
+    LocalizableSimpleInner s = (LocalizableSimpleInner) GWT.create(Inners.LocalizableSimpleInner.class);
+    assertEquals("getLocalizableInner", s.getLocalizableInner());
+
+    LocalizableInnerInner localizableInnerInner = (LocalizableInnerInner) GWT.create(Inners.InnerClass.LocalizableInnerInner.class);
+    assertEquals("localizableInnerInner", localizableInnerInner.string());
+
+    // Check success of finding embedded
+    OuterLoc lock = (OuterLoc) GWT.create(OuterLoc.class);
+    assertEquals("piglatin", lock.string());
+
+    assertEquals("InnerLoc", Inners.testInnerLoc());
+  }
+
+  public void testLocalizableInterfaceInner() {
+    Inners inner = new Inners();
+
+    // Simple Inner
+    SimpleInner simpleInner = (SimpleInner) GWT.create(Inners.SimpleInner.class);
+    assertEquals(0, simpleInner.intZero());
+    assertEquals("Simple Inner", simpleInner.simpleInner());
+    assertTrue(inner.testProtectedInner());
+
+    // Has Inner
+    HasInner hasInner = (HasInner) GWT.create(Inners.HasInner.class);
+    assertEquals("Has Inner", hasInner.hasInner());
+    assertEquals(0, hasInner.floatZero(), .0001);
+
+    // Is Inner
+    IsInner isInner = (IsInner) GWT.create(IsInner.class);
+    assertEquals(2, isInner.isInner());
+
+    // Inner Inner
+    InnerInner innerInner = (InnerInner) GWT.create(InnerInner.class);
+    assertEquals(4.321, innerInner.innerInner(), .0001);
+    assertEquals("outer", innerInner.outer());
+
+    // Inner Inner Message
+    InnerInnerMessages innerInnerMessages = (InnerInnerMessages) GWT.create(InnerInnerMessages.class);
+    assertEquals("I am a person",
+        innerInnerMessages.innerClassMessages("person"));
+
+    // Extends Inner Inner
+    ExtendsInnerInner extendsInnerInner = (ExtendsInnerInner) GWT.create(ExtendsInnerInner.class);
+    assertEquals("Extends Inner Inner", extendsInnerInner.extendsInnerInner());
+
+    // Protected InnerClass
+    InnerClass innerClass = new Inners.InnerClass();
+    String extendsAnotherInner = innerClass.testExtendsAnotherInner();
+    assertEquals("{innerInner=4.321, outer=outer}", extendsAnotherInner);
+
+    // ExtendProtectedInner
+    String extendProtectedInner = innerClass.testExtendsProtectedInner();
+    assertEquals("Extend Protected Inner", extendProtectedInner);
+  }
+
   public void testShapesFamily() {
     Shapes shapes = (Shapes) GWT.create(Shapes.class);
     // test overload
@@ -540,8 +548,8 @@
   public void testTypedMessages() {
     TestTypedMessages typed = (TestTypedMessages) GWT.create(TestTypedMessages.class);
     String expected = "int(0) float(1.2), long(0), boolean(true), Object([], char(a), byte(127), short(-32768);";
-    assertEquals(expected, typed.testAllTypes(0, (float) 1.2, 0, true, new ArrayList<String>(),
-        'a', Byte.MAX_VALUE, Short.MIN_VALUE));
+    assertEquals(expected, typed.testAllTypes(0, (float) 1.2, 0, true,
+        new ArrayList<String>(), 'a', Byte.MAX_VALUE, Short.MIN_VALUE));
     String lotsOfInts = typed.testLotsOfInts(1, 2, 3, 4);
     assertEquals("1, 2,3,4 ", lotsOfInts);
     String oneFloat = typed.simpleMessageTest((float) 2.3);
@@ -552,7 +560,7 @@
         new StringBuffer("hello"), new Integer("34"), null);
     assertEquals(
         "this(null(com.google.gwt.i18n.client.I18NTest)), StringBuffer(hello), Integer(34), "
-        + "null(null);", testSomeObjectTypes);
+            + "null(null);", testSomeObjectTypes);
   }
 
   private void assertArrayEquals(String[] shouldBe, String[] test) {
diff --git a/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java b/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
index 572d6ca..4645e10 100644
--- a/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
+++ b/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -18,6 +18,7 @@
 import com.google.gwt.i18n.client.ColorsAndShapes;
 import com.google.gwt.i18n.client.ColorsAndShapesAndConcepts;
 import com.google.gwt.i18n.client.gen.Colors;
+import com.google.gwt.i18n.rebind.AbstractResource.ResourceList;
 
 import junit.framework.TestCase;
 
@@ -38,23 +39,21 @@
   public void testBundle() {
     // simple test
     String s = Colors.class.getName();
-    AbstractResource resource = ResourceFactory.getBundle(s, null, true);
-    assertNotNull(resource);
-    AbstractResource pigLatinResource =
+    ResourceList resourceList = ResourceFactory.getBundle(s, null, true);
+    assertNotNull(resourceList);
+    ResourceList pigLatinResourceList =
         ResourceFactory.getBundle(s, LOCALE_NAME_PIGLATIN, true);
-    assertEquals(LOCALE_NAME_PIGLATIN, pigLatinResource.getLocaleName());
-    assertNotNull(pigLatinResource);
-    assertEquals("ueblay", pigLatinResource.getString("blue"));
-    assertEquals("ĝréý", pigLatinResource.getString("grey"));
+    assertNotNull(pigLatinResourceList);
+    assertEquals("ueblay", pigLatinResourceList.getString("blue"));
+    assertEquals("ĝréý", pigLatinResourceList.getString("grey"));
   }
 
   public void testInheritence() {
     ResourceFactory.clearCache();
-    AbstractResource resource = ResourceFactory.getBundle(
+    ResourceList resourceList = ResourceFactory.getBundle(
       ColorsAndShapes.class, LOCALE_NAME_PIGLATIN, true);
-    assertEquals(LOCALE_NAME_PIGLATIN, resource.getLocaleName());
-    assertEquals("ueblay", resource.getString("blue"));
-    assertEquals("ĝréý", resource.getString("grey"));
+    assertEquals("ueblay", resourceList.getString("blue"));
+    assertEquals("ĝréý", resourceList.getString("grey"));
   }
 
   public void testByteStreamBehavior() throws UnsupportedEncodingException {
@@ -68,11 +67,11 @@
   }
 
   public void testDoubleInherits() {
-    AbstractResource resource = ResourceFactory.getBundle(
+    ResourceList resourceList = ResourceFactory.getBundle(
       ColorsAndShapesAndConcepts.class, LOCALE_NAME_PIGLATIN_UK, true);
-    String s = resource.getString("internationalization");
+    String s = resourceList.getString("internationalization");
     assertEquals("Îñţérñåţîöñåļîžåţîöñ", s);
-    assertTrue(resource.keySet().size() > 5);
+    assertTrue(resourceList.keySet().size() > 5);
   }
 
   public void testCharArrayBehavior() {
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index b34b49c..fe04a70 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -70,6 +70,7 @@
 import com.google.gwt.user.client.ui.WidgetCollectionTest;
 import com.google.gwt.user.client.ui.WidgetIteratorsTest;
 import com.google.gwt.user.client.ui.WidgetOnLoadTest;
+import com.google.gwt.user.client.ui.WidgetSubclassingTest;
 import com.google.gwt.user.client.ui.impl.ClippedImagePrototypeTest;
 import com.google.gwt.user.rebind.ui.ImageBundleGeneratorTest;
 import com.google.gwt.xml.client.XMLTest;
@@ -141,6 +142,7 @@
     suite.addTestSuite(WidgetCollectionTest.class);
     suite.addTestSuite(WidgetIteratorsTest.class);
     suite.addTestSuite(WidgetOnLoadTest.class);
+    suite.addTestSuite(WidgetSubclassingTest.class);
     suite.addTestSuite(WindowTest.class);
     suite.addTestSuite(XMLTest.class);
     suite.addTestSuite(ClassInitTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java b/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
index 7c927ef..fd3a5d9 100644
--- a/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.junit.client.GWTTestCase;
@@ -126,21 +128,24 @@
 
   public void testPasswordTextBox() {
     ensureDiv().setInnerHTML("<input type='password' id='foo'></input>");
-    PasswordTextBox textBox = PasswordTextBox.wrap(Document.get().getElementById("foo"));
+    PasswordTextBox textBox = PasswordTextBox.wrap(Document.get().getElementById(
+        "foo"));
 
     assertExistsAndAttached(textBox);
   }
 
   public void testSimpleCheckBox() {
     ensureDiv().setInnerHTML("<input type='checkbox' id='foo'></input>");
-    SimpleCheckBox checkBox = SimpleCheckBox.wrap(Document.get().getElementById("foo"));
+    SimpleCheckBox checkBox = SimpleCheckBox.wrap(Document.get().getElementById(
+        "foo"));
 
     assertExistsAndAttached(checkBox);
   }
 
   public void testSimpleRadioButton() {
     ensureDiv().setInnerHTML("<input type='radio' id='foo'></input>");
-    SimpleRadioButton radio = SimpleRadioButton.wrap(Document.get().getElementById("foo"));
+    SimpleRadioButton radio = SimpleRadioButton.wrap(Document.get().getElementById(
+        "foo"));
 
     assertExistsAndAttached(radio);
   }
@@ -152,6 +157,78 @@
     assertExistsAndAttached(textBox);
   }
 
+  public void testDetachOnUnloadTwiceFails() {
+    // Testing hosted-mode-only assertion.
+    if (!GWT.isScript()) {
+      try {
+        // Trying to pass the same widget to RootPanel.detachOnUnload() twice
+        // should fail an assertion (the first call is implicit through
+        // Anchor.wrap()).
+        ensureDiv().setInnerHTML(
+            "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+        Anchor a = Anchor.wrap(Document.get().getElementById("foo")); // pass
+        RootPanel.detachOnWindowClose(a); // fail
+        fail("Expected assertion failure calling detachOnLoad() twice");
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testDetachNowTwiceFails() {
+    // Testing hosted-mode-only assertion.
+    if (!GWT.isScript()) {
+      try {
+        // Trying to pass the same widget to RootPanel.detachNow() twice
+        // should fail an assertion.
+        ensureDiv().setInnerHTML(
+            "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+        Anchor a = Anchor.wrap(Document.get().getElementById("foo"));
+        RootPanel.detachNow(a); // pass
+        RootPanel.detachNow(a); // fail
+        fail("Expected assertion failure calling detachNow() twice");
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testWrapUnattachedFails() {
+    // Testing hosted-mode-only assertion.
+    if (!GWT.isScript()) {
+      try {
+        // Trying to wrap an unattached element should fail an assertion.
+        // We only test this for one element/widget type, because they
+        // all call RootPanel.detachOnUnload(), where the actual assertion
+        // occurs.
+        AnchorElement aElem = Document.get().createAnchorElement();
+        Anchor.wrap(aElem);
+        fail("Expected assertion failure wrapping unattached element");
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testOnUnloadAssertions() {
+    // Testing hosted-mode-only assertion.
+    if (!GWT.isScript()) {
+      try {
+        // When a wrap()ed element is detached from the document without being
+        // properly unwrapped, there will be an assertion to catch this run on
+        // unload.
+        ensureDiv().setInnerHTML(
+            "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+        Element aElem = Document.get().getElementById("foo");
+        Anchor.wrap(aElem);
+        aElem.getParentElement().removeChild(aElem);
+
+        // Fake an unload by telling the RootPanel to go ahead and detach all
+        // of its widgets.
+        RootPanel.detachWidgets();
+        fail("Assertion expected for orphaned wrap()ed widgets");
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
   private void assertExistsAndAttached(Widget widget) {
     assertNotNull(widget);
     assertTrue(widget.isAttached());
diff --git a/user/test/com/google/gwt/user/client/ui/FormPanelTest.java b/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
index 850d90b..2ab69df 100644
--- a/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/FormPanelTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -16,10 +16,12 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.HasWidgetsTester.WidgetAdder;
+import com.google.gwt.user.server.ui.FormPanelTestServlet;
 
 /**
  * Tests the FormPanel.
@@ -29,6 +31,10 @@
 public class FormPanelTest extends GWTTestCase {
   public static boolean clicked = false;
 
+  private native boolean isHappyDivPresent(Element iframe) /*-{
+    return !!iframe.contentWindow.document.getElementById(':)');
+  }-*/;
+
   public String getModuleName() {
     return "com.google.gwt.user.FormPanelTest";
   }
@@ -213,14 +219,42 @@
         assertTrue(isHappyDivPresent(frame.getElement()));
         finishTest();
       }
-
-      private native boolean isHappyDivPresent(Element iframe) /*-{
-        return !!iframe.contentWindow.document.getElementById(':)');
-      }-*/;
     };
 
     // Wait 5 seconds before checking the results.
     t.schedule(5000);
     form.submit();
   }
+
+  public void testWrappedForm() {
+    // Create a form and frame in the document we can wrap.
+    HTML formAndFrame = new HTML(
+        "<form id='wrapMe' method='post' target='targetFrame' action='"
+            + GWT.getModuleBaseURL() + "formHandler?sendHappyHtml'>"
+            + "<input type='hidden' name='foo' value='bar'></input></form>"
+            + "<iframe src='javascript:\'\'' id='targetMe' name='targetFrame'></iframe>");
+    RootPanel.get().add(formAndFrame);
+
+    // Wrap the form and make sure its target frame is intact.
+    FormPanel form = FormPanel.wrap(Document.get().getElementById("wrapMe"));
+    assertEquals("targetFrame", form.getTarget());
+
+    // Ensure that no synthesized iframe was created.
+    assertNull(form.getSynthesizedIFrame());
+
+    // Submit the form and make sure the target frame comes back with the right
+    // stuff (give it 2.5 s to complete).
+    delayTestFinish(5000);
+    new Timer() {
+      @Override
+      public void run() {
+        // Get the targeted frame and make sure it contains the requested
+        // 'happyElem'.
+        Frame frame = Frame.wrap(Document.get().getElementById("targetMe"));
+        assertTrue(isHappyDivPresent(frame.getElement()));
+        finishTest();
+      }
+    }.schedule(2500);
+    form.submit();
+  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/HiddenTest.java b/user/test/com/google/gwt/user/client/ui/HiddenTest.java
index 7c1a814..378fe15 100644
--- a/user/test/com/google/gwt/user/client/ui/HiddenTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HiddenTest.java
@@ -34,7 +34,7 @@
       // Expected
     }
     try {
-      Hidden d = new Hidden(null);
+      Hidden d = new Hidden((String)null);
       fail("expected null pointer exception");
     } catch (NullPointerException e) {
       // Expected
diff --git a/user/test/com/google/gwt/user/client/ui/ImageTest.java b/user/test/com/google/gwt/user/client/ui/ImageTest.java
index 7083f4e..2874c8f 100644
--- a/user/test/com/google/gwt/user/client/ui/ImageTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ImageTest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.dom.client.Document;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.Timer;
 
@@ -90,7 +91,7 @@
    * Tests the transition from the clipped state to the unclipped state.
    */
   public void testChangeClippedImageToUnclipped() {
-    final ArrayList onloadEventFireCounter = new ArrayList();
+    final ArrayList<Object> onloadEventFireCounter = new ArrayList<Object>();
     final Image image = new Image("counting-forwards.png",
         12, 13, 8, 8);
     assertEquals("clipped", getCurrentImageStateName(image));
@@ -167,7 +168,7 @@
    * Tests the creation of an image in clipped mode.
    */
   public void testCreateClippedImage() {
-    final ArrayList onloadEventFireCounter = new ArrayList();
+    final ArrayList<Object> onloadEventFireCounter = new ArrayList<Object>();
     final Image image = new Image("counting-forwards.png",
         16, 16, 16, 16);
 
@@ -246,7 +247,7 @@
    * on a clipped image.
    */
   public void testSetUrlAndVisibleRectOnClippedImage() {
-    final ArrayList onloadEventFireCounter = new ArrayList();
+    final ArrayList<Object> onloadEventFireCounter = new ArrayList<Object>();
     final Image image = new Image("counting-backwards.png",
         12, 12, 12, 12);
 
@@ -335,7 +336,7 @@
    * on a clipped image.
    */
   public void testSetVisibleRectAndLoadEventsOnClippedImage() {
-    final ArrayList onloadEventFireCounter = new ArrayList();
+    final ArrayList<Object> onloadEventFireCounter = new ArrayList<Object>();
     final Image image = new Image("counting-backwards.png",
         16, 16, 16, 16);
 
@@ -366,4 +367,37 @@
 
     t.schedule(1000);
   }
+
+  /**
+   * Tests that wrapping an existing DOM element works if you call
+   * setUrlAndVisibleRect() on it.
+   */
+  public void testWrapThenSetUrlAndVisibleRect() {
+    String uid = Document.get().createUniqueId();
+    HTML html = new HTML("<img id='" + uid + "' src='counting-backwards.png' width='16' height='16'>");
+    RootPanel.get().add(html);
+    final Image image = Image.wrap(Document.get().getElementById(uid));
+
+    delayTestFinish(20000);
+
+    assertEquals(0, image.getOriginLeft());
+    assertEquals(0, image.getOriginTop());
+    assertEquals(16, image.getWidth());
+    assertEquals(16, image.getHeight());
+    assertEquals("unclipped", getCurrentImageStateName(image));
+
+    image.setUrlAndVisibleRect("counting-forwards.png", 0, 16, 16, 16);
+    Timer t = new Timer() {
+      public void run() {
+        assertEquals(0, image.getOriginLeft());
+        assertEquals(16, image.getOriginTop());
+        assertEquals(16, image.getWidth());
+        assertEquals(16, image.getHeight());
+        assertEquals("clipped", getCurrentImageStateName(image));
+        finishTest();
+      }
+    };
+
+    t.schedule(1000);
+  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/WidgetSubclassingTest.java b/user/test/com/google/gwt/user/client/ui/WidgetSubclassingTest.java
new file mode 100644
index 0000000..9432fdb
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/WidgetSubclassingTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * A series of tests to ensure that widgets with a wrap() method properly assert
+ * their element types.
+ */
+public class WidgetSubclassingTest extends GWTTestCase {
+
+  private static final String ASSERTION_ERROR = "Should have received an assertion error trying to use the wrong element type";
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.UserTest";
+  }
+
+  // Correct subclasses.
+  public static class TestAnchor extends Anchor {
+    public TestAnchor() {
+      super(Document.get().createAnchorElement());
+    }
+  }
+
+  public static class TestButton extends Button {
+    public TestButton() {
+      super(Document.get().createButtonElement());
+    }
+  }
+
+  public static class TestFileUpload extends FileUpload {
+    public TestFileUpload() {
+      super(Document.get().createFileInputElement());
+    }
+  }
+
+  public static class TestFormPanel extends FormPanel {
+    public TestFormPanel() {
+      super(Document.get().createFormElement());
+    }
+  }
+
+  public static class TestFrame extends Frame {
+    public TestFrame() {
+      super(Document.get().createIFrameElement());
+    }
+  }
+
+  public static class TestHidden extends Hidden {
+    public TestHidden() {
+      super(Document.get().createHiddenInputElement());
+    }
+  }
+
+  public static class TestHTML extends HTML {
+    public TestHTML() {
+      super(Document.get().createDivElement());
+    }
+  }
+
+  public static class TestImage extends Image {
+    public TestImage() {
+      super(Document.get().createImageElement());
+    }
+  }
+
+  public static class TestInlineHTML extends InlineHTML {
+    public TestInlineHTML() {
+      super(Document.get().createSpanElement());
+    }
+  }
+
+  public static class TestInlineLabel extends InlineLabel {
+    public TestInlineLabel() {
+      super(Document.get().createSpanElement());
+    }
+  }
+
+  public static class TestLabel extends Label {
+    public TestLabel() {
+      super(Document.get().createSpanElement());
+    }
+  }
+
+  public static class TestListBox extends ListBox {
+    public TestListBox() {
+      super(Document.get().createSelectElement());
+    }
+  }
+
+  public static class TestPasswordTextBox extends PasswordTextBox {
+    public TestPasswordTextBox() {
+      super(Document.get().createPasswordInputElement());
+    }
+  }
+
+  public static class TestSimpleCheckBox extends SimpleCheckBox {
+    public TestSimpleCheckBox() {
+      super(Document.get().createCheckInputElement());
+    }
+  }
+
+  public static class TestSimpleRadioButton extends SimpleRadioButton {
+    public TestSimpleRadioButton() {
+      super(Document.get().createRadioInputElement("group"));
+    }
+  }
+
+  public static class TestTextBox extends TextBox {
+    public TestTextBox() {
+      super(Document.get().createTextInputElement());
+    }
+  }
+
+  // Broken subclasses.
+  public static class BrokenAnchor extends Anchor {
+    public BrokenAnchor() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenButton extends Button {
+    public BrokenButton() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenFileUpload extends FileUpload {
+    public BrokenFileUpload() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenFormPanel extends FormPanel {
+    public BrokenFormPanel() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenFrame extends Frame {
+    public BrokenFrame() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenHidden extends Hidden {
+    public BrokenHidden() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenHTML extends HTML {
+    public BrokenHTML() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenImage extends Image {
+    public BrokenImage() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenInlineHTML extends InlineHTML {
+    public BrokenInlineHTML() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenInlineLabel extends InlineLabel {
+    public BrokenInlineLabel() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenLabel extends Label {
+    public BrokenLabel() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenListBox extends ListBox {
+    public BrokenListBox() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenPasswordTextBox extends PasswordTextBox {
+    public BrokenPasswordTextBox() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenSimpleCheckBox extends SimpleCheckBox {
+    public BrokenSimpleCheckBox() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenSimpleRadioButton extends SimpleRadioButton {
+    public BrokenSimpleRadioButton() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public static class BrokenTextBox extends TextBox {
+    public BrokenTextBox() {
+      super(Document.get().createBRElement());
+    }
+  }
+
+  public void testAnchor() {
+    // Make sure the normal case works.
+    new TestAnchor();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenAnchor();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testButton() {
+    // Make sure the normal case works.
+    new TestButton();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenButton();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testFileUpload() {
+    // Make sure the normal case works.
+    new TestFileUpload();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenFileUpload();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testFormPanel() {
+    // Make sure the normal case works.
+    new TestFormPanel();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenFormPanel();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testFrame() {
+    // Make sure the normal case works.
+    new TestFrame();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenFrame();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testHidden() {
+    // Make sure the normal case works.
+    new TestHidden();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenHidden();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testHTML() {
+    // Make sure the normal case works.
+    new TestHTML();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenHTML();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testImage() {
+    // Make sure the normal case works.
+    new TestImage();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenImage();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testInlineHTML() {
+    // Make sure the normal case works.
+    new TestInlineHTML();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenInlineHTML();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testInlineLabel() {
+    // Make sure the normal case works.
+    new TestInlineLabel();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenInlineLabel();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testLabel() {
+    // Make sure the normal case works.
+    new TestLabel();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenLabel();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testListBox() {
+    // Make sure the normal case works.
+    new TestListBox();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenListBox();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testPasswordTextBox() {
+    // Make sure the normal case works.
+    new TestPasswordTextBox();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenPasswordTextBox();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testSimpleCheckBox() {
+    // Make sure the normal case works.
+    new TestSimpleCheckBox();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenSimpleCheckBox();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testSimpleRadioButton() {
+    // Make sure the normal case works.
+    new TestSimpleRadioButton();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenSimpleRadioButton();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+
+  public void testTextBox() {
+    // Make sure the normal case works.
+    new TestTextBox();
+
+    // And the wrong element type doesn't.
+    if (!GWT.isScript()) {
+      try {
+        new BrokenTextBox();
+        fail(ASSERTION_ERROR);
+      } catch (AssertionError e) {
+      }
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index 5915a67..972c2b1 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -2113,8 +2113,8 @@
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
-  public void testTypeParametersInRootTypes() throws UnableToCompleteException,
-      NotFoundException {
+  public void testTypeParametersInRootTypes1()
+      throws UnableToCompleteException, NotFoundException {
     Set<CompilationUnit> units = new HashSet<CompilationUnit>();
     addStandardClasses(units);
 
@@ -2160,6 +2160,63 @@
     assertSerializableTypes(so, rawA, b);
   }
 
+  /**
+   * Tests root types that <em>are</em> type parameters.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testTypeParametersInRootTypes2()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public abstract class A<U extends B> implements Serializable {\n");
+      code.append("  <V extends C>  V getFoo() { return null; }\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class C implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("C", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JClassType b = to.getType("B");
+    JClassType c = to.getType("C");
+    JTypeParameter u = a.getTypeParameters()[0];
+    JTypeParameter v = a.getMethod("getFoo", makeArray()).getReturnType().isTypeParameter();
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, u);
+    sob.addRootType(logger, v);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertSerializableTypes(so, b, c);
+    assertInstantiable(so, b);
+    assertInstantiable(so, c);
+    assertNotInstantiableOrFieldSerializable(so, a.getRawType());
+  }
+
   private JClassType[] makeArray(JClassType... elements) {
     return elements;
   }