manual sync to P2 @20180037

git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.3@9874 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 19bbba7..cfc2998 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -82,7 +82,6 @@
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
 import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
 import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields;
-import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.jjs.impl.JsFunctionClusterer;
 import com.google.gwt.dev.jjs.impl.JsIEBlockTextTransformer;
@@ -620,9 +619,6 @@
       // Resolve entry points, rebinding non-static entry points.
       findEntryPoints(logger, rpo, declEntryPts, jprogram);
 
-      // Replace references to JSO subtypes with JSO itself.
-      JavaScriptObjectNormalizer.exec(jprogram);
-
       ImplementClassLiteralsAsFields.exec(jprogram);
       
       /*
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 09291fb..6be467b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -25,6 +25,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -280,6 +281,19 @@
     this.program = program;
   }
 
+  /**
+   * True if the type is a JSO or interface implemented by JSO..
+   * @param type
+   * @return
+   */
+  public boolean canBeJavaScriptObject(JType type) {
+    if (type instanceof JNonNullType) {
+      type = ((JNonNullType) type).getUnderlyingType();
+    }
+    return program.isJavaScriptObject(type)
+        || program.typeOracle.isSingleJsoImpl(type);
+  }
+
   public boolean canTheoreticallyCast(JReferenceType type, JReferenceType qType) {
     if (!type.canBeNull() && qType == program.getTypeNull()) {
       // Cannot cast non-nullable to null
@@ -295,6 +309,14 @@
       return true;
     }
 
+    /**
+     * Cross-cast allowed in theory, prevents TypeTightener from turning
+     * cross-casts into null-casts.
+     */
+    if (canBeJavaScriptObject(type) && canBeJavaScriptObject(qType)) {
+      return true;
+    }
+    
     if (canTriviallyCast(type, qType)) {
       return true;
     }
@@ -401,12 +423,6 @@
         if (isSuperClass(cType, qcType)) {
           return true;
         }
-        // All JavaScriptObject types can be freely cast to each other.
-        JClassType jsoType = program.getJavaScriptObject();
-        if (jsoType != null) {
-          return isSameOrSuper(cType, jsoType)
-              && isSameOrSuper(qcType, jsoType);
-        }
       } else if (qType instanceof JInterfaceType) {
         return implementsInterface(cType, (JInterfaceType) qType);
       }
@@ -458,20 +474,17 @@
       }
     }
 
-    /*
-     * Now that the basic type hierarchy is computed, move all interfaces that
-     * are implemented by overlay types onto JavaScriptObject itself before
-     * building the full maps.
+    /*                                                                                                 
+     * Now that the basic type hierarchy is computed, compute which
+     * JSOs implement interfaces singlely or dually.
      */
     JClassType jsoType = program.getJavaScriptObject();
     List<JClassType> jsoSubTypes = Lists.create();
     if (jsoType != null) {
-      assert jsoType.getImplements().size() == 0;
       jsoSubTypes = new ArrayList<JClassType>(get(subClassMap, jsoType));
       Collections.sort(jsoSubTypes, new HasNameSort());
       for (JClassType jsoSubType : jsoSubTypes) {
         for (JInterfaceType intf : jsoSubType.getImplements()) {
-          jsoType.addImplements(intf);
           jsoSingleImpls.put(intf, jsoSubType);
           for (JInterfaceType superIntf : get(superInterfaceMap, intf)) {
             if (!jsoSingleImpls.containsKey(superIntf)) {
@@ -499,14 +512,13 @@
     }
 
     // Create dual mappings for any jso interface with a Java implementor.
-    int totalJsoTypes = jsoSubTypes.size() + 1;
     for (JInterfaceType jsoIntf : jsoSingleImpls.keySet()) {
       Set<JClassType> implementors = get(isImplementedMap, jsoIntf);
-      if (implementors.size() == totalJsoTypes) {
-        assert implementors.contains(jsoType);
-      } else {
-        assert implementors.size() > totalJsoTypes;
-        dualImpls.add(jsoIntf);
+      for (JClassType implementor : implementors) {
+        if (!program.isJavaScriptObject(implementor)) {
+          dualImpls.add(jsoIntf);
+          break;
+        }
       }
     }
   }
@@ -538,6 +550,10 @@
     return results;
   }
 
+  public Set<JClassType> getAllSubClasses(JReferenceType type) {
+    return subClassMap.get(type);
+  }
+
   /**
    * Returns the set of methods the given method virtually overrides. A virtual
    * override is an association between a concrete method and an unrelated
@@ -574,10 +590,33 @@
     return dualImpls.contains(maybeDualImpl.getUnderlyingType());
   }
 
+  /**
+    * True if either a JSO, or is an interface that is ONLY
+    * implemented by a JSO.
+    */
+   public boolean isEffectivelyJavaScriptObject(JType type) {
+     if (type instanceof JReferenceType) {
+       JReferenceType refType = (JReferenceType) type;
+       return program.isJavaScriptObject(refType) ||
+         (isSingleJsoImpl(refType) && !isDualJsoInterface(refType));
+     } else {
+       return false;
+     }
+   }
+
   public boolean isInstantiatedType(JReferenceType type) {
     return isInstantiatedType(type, instantiatedTypes);
   }
 
+  public boolean isSameOrSuper(JClassType type, JClassType qType) {
+     return (type == qType || isSuperClass(type, qType));
+  }
+
+  public boolean isSingleJsoImpl(JType type) {
+    return type instanceof JReferenceType
+        && getSingleJsoImpl((JReferenceType) type) != null;
+  }
+
   /**
    * Returns true if qType is a subclass of type, directly or indirectly.
    */
@@ -598,9 +637,32 @@
    */
   public void recomputeAfterOptimizations() {
     Set<JDeclaredType> computed = new IdentityHashSet<JDeclaredType>();
+
     for (JDeclaredType type : program.getDeclaredTypes()) {
       computeClinitTarget(type, computed);
     }
+    nextDual: for (Iterator<JInterfaceType> it = dualImpls.iterator();
+        it.hasNext();) {
+      JInterfaceType dualIntf = it.next();
+      Set<JClassType> implementors = get(isImplementedMap, dualIntf);
+      for (JClassType implementor : implementors) {
+        if (isInstantiatedType(implementor)
+            && !program.isJavaScriptObject(implementor)) {
+          // This dual is still implemented by a Java class.
+          continue nextDual;
+        }
+      }
+      // No Java implementors.
+      it.remove();
+    }
+
+    // Prune jsoSingleImpls when implementor isn't live
+    Iterator<JClassType> jit = jsoSingleImpls.values().iterator();
+    while (jit.hasNext()) {
+      if (!isInstantiatedType(jit.next())) {
+        jit.remove();
+      }
+    }
   }
 
   public void setInstantiatedTypes(Set<JReferenceType> instantiatedTypes) {
@@ -912,10 +974,6 @@
     return instantiatedTypes.contains(type);
   }
 
-  private boolean isSameOrSuper(JClassType type, JClassType qType) {
-    return (type == qType || isSuperClass(type, qType));
-  }
-
   /**
    * Record the all of my super classes (and myself as a subclass of them).
    */
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 9cbbf1a..be297b4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -191,6 +191,24 @@
       recordCast(x.getTestType(), x.getExpr());
     }
 
+    private boolean canTriviallyCastJsoSemantics(JReferenceType type,
+        JReferenceType qType) {
+      type = type.getUnderlyingType();
+      qType = qType.getUnderlyingType();
+
+      if (type instanceof JArrayType && qType instanceof JArrayType) {
+        JArrayType aType = (JArrayType) type;
+        JArrayType aqType = (JArrayType) qType;
+        return program.typeOracle.canTriviallyCast(type, qType)
+            || (program.isJavaScriptObject(aType.getLeafType())
+                && program.isJavaScriptObject(aqType.getLeafType()));
+      }
+
+      return program.typeOracle.canTriviallyCast(type, qType)
+            || (program.isJavaScriptObject(type)
+                 && program.isJavaScriptObject(qType));
+    }
+    
     /**
      * Create the data for JSON table to capture the mapping from a class to its
      * query types.
@@ -221,11 +239,16 @@
       for (JReferenceType qType : queriedTypes.keySet()) {
 
         Set<JReferenceType> querySet = queriedTypes.get(qType);
-        if (program.typeOracle.canTriviallyCast(type, qType)) {
+        /**
+         * Handles JSO[] -> JSO[] case now that canCastTrivially doesn't deal
+         * with JSO cross-casts anymore.
+         */
+        if (canTriviallyCastJsoSemantics(type, qType)) {
 
           for (JReferenceType argType : querySet) {
 
-            if (program.typeOracle.canTriviallyCast(type, argType)) {
+            if (canTriviallyCastJsoSemantics(type, argType) ||
+                program.isJavaScriptObject(qType)) {
               if (yesSet == null) {
                 yesSet = new HashSet<JReferenceType>();
               }
@@ -422,13 +445,16 @@
         JExpression curExpr = expr;
         JReferenceType refType = ((JReferenceType) toType).getUnderlyingType();
         JReferenceType argType = (JReferenceType) expr.getType();
-        if (program.typeOracle.canTriviallyCast(argType, refType)) {
+        if (program.typeOracle.canTriviallyCast(argType, refType)
+            || (program.typeOracle.isEffectivelyJavaScriptObject(argType)
+            &&  program.typeOracle.isEffectivelyJavaScriptObject(refType))) {
           // just remove the cast
           replaceExpr = curExpr;
         } else {
 
           JMethod method;
-          boolean isJsoCast = program.isJavaScriptObject(refType);
+          boolean isJsoCast =
+              program.typeOracle.isEffectivelyJavaScriptObject(refType);
           if (isJsoCast) {
             // A cast to a concrete JSO subtype
             method = program.getIndexedMethod("Cast.dynamicCastJso");
@@ -537,7 +563,10 @@
       JReferenceType toType = x.getTestType();
       // Only tests on run-time types are supported
       assert (toType == toType.getUnderlyingType());
-      if (program.typeOracle.canTriviallyCast(argType, toType)) {
+      if (program.typeOracle.canTriviallyCast(argType, toType)
+          // don't depend on type-tightener having run
+          || (program.typeOracle.isEffectivelyJavaScriptObject(argType)
+          && program.typeOracle.isEffectivelyJavaScriptObject(toType))) {
         // trivially true if non-null; replace with a null test
         JNullLiteral nullLit = program.getLiteralNull();
         JBinaryOperation eq = new JBinaryOperation(x.getSourceInfo(),
@@ -547,9 +576,9 @@
       } else {
         JMethod method;
         boolean isJsoCast = false;
-        if (program.typeOracle.getSingleJsoImpl(toType) != null) {
+        if (program.typeOracle.isDualJsoInterface(toType)) {
           method = program.getIndexedMethod("Cast.instanceOfOrJso");
-        } else if (program.isJavaScriptObject(toType)) {
+        } else if (program.typeOracle.isEffectivelyJavaScriptObject(toType)) {
           isJsoCast = true;
           method = program.getIndexedMethod("Cast.instanceOfJso");
         } else {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 1ebe42e..d66ca40 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -182,10 +182,25 @@
     public boolean visit(JCastOperation x, Context ctx) {
       // Rescue any JavaScriptObject type that is the target of a cast.
       JType targetType = x.getCastType();
-      if (program.isJavaScriptObject(targetType)) {
+      if (program.typeOracle.canBeJavaScriptObject(targetType)) {
         rescue((JReferenceType) targetType, true, true);
+        JType exprType = x.getExpr().getType();
+        if (program.typeOracle.isSingleJsoImpl(targetType)) {
+          /*
+            * It's a JSO interface, check if the source expr can be a live JSO
+            * 1) source is java.lang.Object (JSO could have been assigned to it)
+            * 2) source is JSO
+            * 3) source is SingleJSO interface whose implementor is live
+            */
+          if (program.getTypeJavaLangObject() == exprType
+              || program.typeOracle.canBeJavaScriptObject(exprType)) {
+            // source is JSO or SingleJso interface whose implementor is live
+            JClassType jsoImplementor =
+                program.typeOracle.getSingleJsoImpl((JReferenceType) targetType);
+            rescue(jsoImplementor, true, true);
+          }
+        }
       }
-
       return true;
     }
 
@@ -294,7 +309,6 @@
       }
 
       rescueMethodsIfInstantiable(type);
-
       return false;
     }
 
@@ -486,7 +500,8 @@
      */
     private void maybeRescueJavaScriptObjectPassingIntoJava(JType type) {
       boolean doIt = false;
-      if (program.isJavaScriptObject(type) || program.isJavaLangString(type)) {
+      if (program.typeOracle.canBeJavaScriptObject(type)
+          || program.isJavaLangString(type)) {
         doIt = true;
       } else if (type instanceof JArrayType) {
         /*
@@ -497,12 +512,17 @@
         JType elementType = arrayType.getElementType();
         if (elementType instanceof JPrimitiveType
             || program.isJavaLangString(elementType)
-            || program.isJavaScriptObject(elementType)) {
+            || program.typeOracle.canBeJavaScriptObject(elementType)) {
           doIt = true;
         }
       }
       if (doIt) {
         rescue((JReferenceType) type, true, true);
+        if (program.typeOracle.isSingleJsoImpl(type)) {
+          // Cast of JSO into SingleJso interface, rescue the implementor
+          rescue(program.typeOracle.getSingleJsoImpl((JReferenceType) type),
+              true, true);
+        }
       }
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
index 017e69b..a02032c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
@@ -273,6 +273,23 @@
     return typeName;
   }
 
+
+  private JType normalizeJsoType(JType type) {
+    if (program.isJavaScriptObject(type)) {
+      return program.getJavaScriptObject();
+    }
+
+    if (type instanceof JArrayType) {
+      JArrayType aType = (JArrayType) type;
+      if (program.isJavaScriptObject(aType.getLeafType())) {
+        return program.getTypeArray(program.getJavaScriptObject(),
+            aType.getDims());
+      }
+    }
+
+    return type;
+  }
+
   /**
    * Takes the form:
    * 
@@ -285,6 +302,7 @@
    */
   private JField resolveClassLiteralField(JClassLiteral classLiteral) {
     JType type = classLiteral.getRefType();
+    type = normalizeJsoType(type);
     JField field = classLiteralFields.get(type);
     if (field == null) {
       // Create the allocation expression FIRST since this may be recursive on
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
deleted file mode 100644
index 743b6b9..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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.dev.jjs.impl;
-
-import com.google.gwt.dev.jjs.SourceInfo;
-import com.google.gwt.dev.jjs.ast.Context;
-import com.google.gwt.dev.jjs.ast.JArrayType;
-import com.google.gwt.dev.jjs.ast.JCastOperation;
-import com.google.gwt.dev.jjs.ast.JClassLiteral;
-import com.google.gwt.dev.jjs.ast.JClassType;
-import com.google.gwt.dev.jjs.ast.JConditional;
-import com.google.gwt.dev.jjs.ast.JDeclaredType;
-import com.google.gwt.dev.jjs.ast.JExpression;
-import com.google.gwt.dev.jjs.ast.JField;
-import com.google.gwt.dev.jjs.ast.JInstanceOf;
-import com.google.gwt.dev.jjs.ast.JLocal;
-import com.google.gwt.dev.jjs.ast.JLocalRef;
-import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JMethodBody;
-import com.google.gwt.dev.jjs.ast.JMethodCall;
-import com.google.gwt.dev.jjs.ast.JModVisitor;
-import com.google.gwt.dev.jjs.ast.JNewArray;
-import com.google.gwt.dev.jjs.ast.JNonNullType;
-import com.google.gwt.dev.jjs.ast.JParameter;
-import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JType;
-import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
-import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
-import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
-import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
-
-import java.util.Stack;
-
-/**
- * Replace references to JSO subtypes with JSO itself.
- */
-public class JavaScriptObjectNormalizer {
-  /**
-   * Map types from JSO subtypes to JSO itself.
-   */
-  private class NormalizeVisitor extends JModVisitor {
-
-    private final Stack<JMethodBody> currentMethodBody = new Stack<JMethodBody>();
-
-    @Override
-    public void endVisit(JCastOperation x, Context ctx) {
-      JType newType = translate(x.getCastType());
-      if (newType != x.getCastType()) {
-        ctx.replaceMe(new JCastOperation(x.getSourceInfo(), newType,
-            x.getExpr()));
-      }
-    }
-
-    @Override
-    public void endVisit(JClassLiteral x, Context ctx) {
-      JType newType = translate(x.getRefType());
-      if (newType != x.getRefType()) {
-        ctx.replaceMe(new JClassLiteral(x.getSourceInfo(), newType));
-      }
-    }
-
-    @Override
-    public void endVisit(JField x, Context ctx) {
-      x.setType(translate(x.getType()));
-    }
-
-    @Override
-    public void endVisit(JInstanceOf x, Context ctx) {
-      JReferenceType newType = (JReferenceType) translate(x.getTestType());
-      if (newType != x.getTestType()) {
-        ctx.replaceMe(new JInstanceOf(x.getSourceInfo(), newType, x.getExpr()));
-      }
-    }
-
-    @Override
-    public void endVisit(JLocal x, Context ctx) {
-      x.setType(translate(x.getType()));
-    }
-
-    @Override
-    public void endVisit(JMethod x, Context ctx) {
-      x.setType(translate(x.getType()));
-    }
-
-    @Override
-    public void endVisit(JMethodBody x, Context ctx) {
-      if (currentMethodBody.pop() != x) {
-        throw new RuntimeException("Unexpected JMethodBody popped");
-      }
-    }
-
-    /**
-     * Polymorphic dispatches to interfaces implemented by both a JSO and a
-     * regular type require special dispatch handling. If the instance is not a
-     * Java-derived object, the method from the single JSO implementation will
-     * be invoked. Otherwise, a polymorphic dispatch to the Java-derived object
-     * is made.
-     */
-    @Override
-    public void endVisit(JMethodCall x, Context ctx) {
-      JDeclaredType targetClass = x.getTarget().getEnclosingType();
-      if (program.typeOracle.getSingleJsoImpl(targetClass) != null) {
-
-        SourceInfo info = x.getSourceInfo();
-
-        // Find the method in the JSO type
-        JMethod jsoMethod = findJsoMethod(x.getTarget());
-        assert jsoMethod != null;
-
-        if (program.typeOracle.isDualJsoInterface(targetClass)) {
-          /*
-           * This is the special-case code to handle interfaces.
-           */
-          JMultiExpression multi = new JMultiExpression(info);
-          JExpression instance = maybeMakeTempAssignment(multi, x.getInstance());
-
-          // instance.method(arg, arg)
-          JMethodCall localCall = new JMethodCall(info, instance, x.getTarget());
-          localCall.addArgs(x.getArgs());
-
-          // We need a second copy of the arguments for the else expression
-          CloneExpressionVisitor cloner = new CloneExpressionVisitor();
-
-          // instance.jsoMethod(arg, arg)
-          JMethodCall jsoCall = new JMethodCall(info,
-              cloner.cloneExpression(instance), jsoMethod);
-          jsoCall.addArgs(cloner.cloneExpressions(x.getArgs()));
-
-          // Cast.isJavaScriptObject() ? instance.jsoMethod() :
-          // instance.method();
-          JConditional newExpr = makeIsJsoConditional(info,
-              cloner.cloneExpression(instance), x.getType(), jsoCall, localCall);
-
-          multi.exprs.add(newExpr);
-          // We may only have the ternary operation if there's no side-effect
-          ctx.replaceMe(multi.exprs.size() == 1 ? multi.exprs.get(0) : multi);
-        } else {
-          /*
-           * ... otherwise, if there's only a JSO implementation, we'll just
-           * call that directly.
-           */
-          JMethodCall jsoCall = new JMethodCall(info, x.getInstance(),
-              jsoMethod);
-          jsoCall.addArgs(x.getArgs());
-          ctx.replaceMe(jsoCall);
-        }
-      }
-    }
-
-    @Override
-    public void endVisit(JNewArray x, Context ctx) {
-      x.setType((JNonNullType) translate(x.getType()));
-    }
-
-    @Override
-    public void endVisit(JParameter x, Context ctx) {
-      x.setType(translate(x.getType()));
-    }
-
-    @Override
-    public boolean visit(JMethodBody x, Context ctx) {
-      currentMethodBody.push(x);
-      return true;
-    }
-
-    private JMethod findConcreteImplementation(JMethod method,
-        JClassType concreteType) {
-      /*
-       * Search supertypes for virtual overrides via subclass. See the javadoc
-       * on JTypeOracle.getAllVirtualOverrides for an example.
-       */
-      while (concreteType != null) {
-        for (JMethod m : concreteType.getMethods()) {
-          if (program.typeOracle.getAllOverrides(m).contains(method)) {
-            if (!m.isAbstract()) {
-              return m;
-            }
-          }
-        }
-        concreteType = concreteType.getSuperClass();
-      }
-
-      return null;
-    }
-
-    private JMethod findJsoMethod(JMethod interfaceMethod) {
-      JClassType jsoClass = program.typeOracle.getSingleJsoImpl(interfaceMethod.getEnclosingType());
-      assert program.isJavaScriptObject(jsoClass);
-      assert jsoClass != null;
-
-      JMethod toReturn = findConcreteImplementation(interfaceMethod, jsoClass);
-      assert toReturn != null;
-      assert !toReturn.isAbstract();
-      assert jsoClass.isFinal() || toReturn.isFinal();
-
-      return toReturn;
-    }
-
-    private JConditional makeIsJsoConditional(SourceInfo info,
-        JExpression instance, JType conditionalType, JExpression isJsoExpr,
-        JExpression notJsoExpr) {
-      // Cast.isJavaScriptObjectOrString(instance)
-      JMethod isJavaScriptObjectMethod = program.getIndexedMethod("Cast.isJavaScriptObjectOrString");
-      JMethodCall isJavaScriptObjectExpr = new JMethodCall(info, null,
-          isJavaScriptObjectMethod);
-      isJavaScriptObjectExpr.addArg(instance);
-      return new JConditional(info, conditionalType, isJavaScriptObjectExpr,
-          isJsoExpr, notJsoExpr);
-    }
-
-    private JExpression maybeMakeTempAssignment(JMultiExpression multi,
-        JExpression instance) {
-      if (instance.hasSideEffects()) {
-        /*
-         * It may be necessary to save off the instance expression into a local
-         * variable if its evaluation would produce side-effects. The
-         * multi-expression is used for this purpose.
-         */
-        SourceInfo info = instance.getSourceInfo();
-        JLocal local = JProgram.createLocal(info, "maybeJsoInvocation",
-            instance.getType(), true, currentMethodBody.peek());
-        multi.exprs.add(JProgram.createAssignmentStmt(info,
-            new JLocalRef(info, local), instance).getExpr());
-
-        instance = new JLocalRef(info, local);
-      }
-      return instance;
-    }
-
-    private JType translate(JType type) {
-      if (!(type instanceof JReferenceType)) {
-        return type;
-      }
-      JReferenceType refType = (JReferenceType) type;
-      boolean canBeNull = refType.canBeNull();
-      refType = refType.getUnderlyingType();
-
-      if (program.isJavaScriptObject(refType)) {
-        refType = program.getJavaScriptObject();
-      } else if (program.typeOracle.getSingleJsoImpl(refType) != null
-          && !program.typeOracle.isDualJsoInterface(refType)) {
-        // Optimization: narrow to JSO if it's not a dual impl.
-        refType = program.getJavaScriptObject();
-      } else if (refType instanceof JArrayType) {
-        JArrayType arrayType = (JArrayType) refType;
-        JType leafType = arrayType.getLeafType();
-        JType replacement = translate(leafType);
-        if (leafType != replacement) {
-          refType = program.getTypeArray(replacement, arrayType.getDims());
-        }
-      }
-      return canBeNull ? refType : refType.getNonNull();
-    }
-  }
-
-  public static void exec(JProgram program) {
-    Event normalizerEvent = SpeedTracerLogger.start(CompilerEventType.NORMALIZER);
-    new JavaScriptObjectNormalizer(program).execImpl();
-    normalizerEvent.end();
-  }
-
-  private final JProgram program;
-
-  private JavaScriptObjectNormalizer(JProgram program) {
-    this.program = program;
-  }
-
-  private void execImpl() {
-    NormalizeVisitor visitor = new NormalizeVisitor();
-    visitor.accept(program);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
index 84cc483..c399579 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
@@ -20,6 +20,10 @@
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JConditional;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
@@ -28,12 +32,13 @@
 import com.google.gwt.dev.jjs.ast.JParameterRef;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReturnStatement;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JTypeOracle;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 import com.google.gwt.dev.jjs.impl.MakeCallsStatic.CreateStaticImplsVisitor;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * All call sites that might result in virtual dispatch to a JSO must be
@@ -52,18 +57,66 @@
     public void endVisit(JMethodCall x, Context ctx) {
       JMethod method = x.getTarget();
       JMethod newMethod;
-      if (virtualJsoMethods.contains(method)) {
-        /*
-         * Force a JSO call to be static. Really, this should never be necessary
-         * as long as MakeCallsStatic runs. However, we would rather not insist
-         * that normalization depends on optimization having been done.
-         */
-        newMethod = program.getStaticImpl(method);
-      } else if (objectMethodToJsoMethod.keySet().contains(method)) {
-        /*
-         * Map the object call to its appropriate devirtualizing method.
-         */
-        newMethod = objectMethodToJsoMethod.get(method);
+      /**
+       * A method call at this point can be one of5 things:
+       * 1) a dual dispatch interface
+       * 2) a single dispatch trough single-jso interface
+       * 3) a java.lang.Object override from JavaScriptObject
+       * 4) a regular dispatch (no JSOs involved or static JSO call)
+       * 5) in draftMode, a 'static' virtual JSO call that hasn't been
+       * made static yet.
+       */
+      JDeclaredType targetType = method.getEnclosingType();
+
+      if (targetType == null) {
+        return;
+      }
+      if (!method.needsVtable()) {
+        return;
+      }
+
+      JType instanceType = x.getInstance().getType();
+      // if the instance can't possibly be a JSO, don't devirtualize
+      if (instanceType != program.getTypeJavaLangObject()
+         && !program.typeOracle.canBeJavaScriptObject(instanceType)) {
+        return;
+      }
+
+      if (polyMethodToJsoMethod.containsKey(method)) {
+        // already did this one before
+        newMethod = polyMethodToJsoMethod.get(method);
+      } else if (program.typeOracle.isDualJsoInterface(targetType)) {
+        JMethod overridingMethod = findOverridingMethod(method,
+            program.typeOracle.getSingleJsoImpl(targetType));
+        assert overridingMethod != null;
+
+        JMethod jsoStaticImpl = getStaticImpl(overridingMethod);
+        newMethod = getOrCreateDevirtualMethod(x, jsoStaticImpl);
+        polyMethodToJsoMethod.put(method, newMethod);
+      } else if (program.isJavaScriptObject(targetType)) {
+        // It's a virtual JSO dispatch, usually occurs in draftCompile
+        newMethod = getStaticImpl(method);
+        polyMethodToJsoMethod.put(method, newMethod);
+      } else if (program.typeOracle.isSingleJsoImpl(targetType))  {
+        // interface dispatch with single implementing JSO concrete type
+        JMethod overridingMethod = findOverridingMethod(method,
+        program.typeOracle.getSingleJsoImpl(targetType));
+        assert overridingMethod != null;
+        newMethod = getStaticImpl(overridingMethod);
+        polyMethodToJsoMethod.put(method, newMethod);
+      } else if (targetType == program.getTypeJavaLangObject()) {
+        // it's a java.lang.Object overriden method in JSO
+        JMethod overridingMethod = findOverridingMethod(method,
+            program.getJavaScriptObject());
+        if (overridingMethod != null) {
+          JMethod jsoStaticImpl = getStaticImpl(overridingMethod);
+          newMethod = getOrCreateDevirtualMethod(x, jsoStaticImpl);
+          polyMethodToJsoMethod.put(method, newMethod);
+        } else {
+          // else this method isn't overriden by JavaScriptObject
+          assert false : "Object method not overriden by JavaScriptObject";
+          return;
+        }
       } else {
         return;
       }
@@ -74,7 +127,7 @@
     @Override
     public boolean visit(JMethod x, Context ctx) {
       // Don't rewrite the polymorphic call inside of the devirtualizing method!
-      if (objectMethodToJsoMethod.values().contains(x)) {
+      if (polyMethodToDevirtualMethods.containsValue(x)) {
         return false;
       }
       return true;
@@ -89,19 +142,13 @@
    * Maps each Object instance methods (ie, {@link Object#equals(Object)}) onto
    * its corresponding devirtualizing method.
    */
-  protected Map<JMethod, JMethod> objectMethodToJsoMethod = new HashMap<JMethod, JMethod>();
-
-  /**
-   * Contains the set of live instance methods on JavaScriptObject; typically
-   * overrides of Object methods.
-   */
-  protected Set<JMethod> virtualJsoMethods = new HashSet<JMethod>();
+  protected Map<JMethod, JMethod> polyMethodToJsoMethod = new HashMap<JMethod, JMethod>();
 
   /**
    * Contains the set of devirtualizing methods that replace polymorphic calls
    * to Object methods.
    */
-  private Set<JMethod> devirtualMethods = new HashSet<JMethod>();
+  private Map<JMethod, JMethod> polyMethodToDevirtualMethods = new HashMap<JMethod, JMethod>();
 
   /**
    * Contains the Cast.isJavaObject method.
@@ -110,67 +157,12 @@
 
   private final JProgram program;
 
+  private final CreateStaticImplsVisitor staticImplCreator;
+
   private JsoDevirtualizer(JProgram program) {
     this.program = program;
     this.isJavaObjectMethod = program.getIndexedMethod("Cast.isJavaObject");
-  }
-
-  /**
-   * Create a conditional method to discriminate between static and virtual
-   * dispatch.
-   * 
-   * <pre>
-   * static boolean equals__devirtual$(Object this, Object other) {
-   *   return Cast.isJavaObject(this) ? this.equals(other) : JavaScriptObject.equals$(this, other);
-   * }
-   * </pre>
-   */
-  private JMethod createDevirtualMethod(JMethod objectMethod, JMethod jsoImpl) {
-    JClassType jsoType = program.getJavaScriptObject();
-    SourceInfo sourceInfo = jsoType.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
-
-    // Create the new method.
-    String name = objectMethod.getName() + "__devirtual$";
-    JMethod newMethod = program.createMethod(sourceInfo, name, jsoType,
-        objectMethod.getType(), false, true, true, false, false);
-    newMethod.setSynthetic();
-
-    // Setup parameters.
-    JParameter thisParam = JProgram.createParameter(sourceInfo, "this$static",
-        program.getTypeJavaLangObject(), true, true, newMethod);
-    for (JParameter oldParam : objectMethod.getParams()) {
-      JProgram.createParameter(sourceInfo, oldParam.getName(),
-          oldParam.getType(), true, false, newMethod);
-    }
-    newMethod.freezeParamTypes();
-    newMethod.addThrownExceptions(objectMethod.getThrownExceptions());
-    sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(newMethod));
-
-    // Build from bottom up.
-    JMethodCall condition = new JMethodCall(sourceInfo, null,
-        isJavaObjectMethod);
-    condition.addArg(new JParameterRef(sourceInfo, thisParam));
-
-    JMethodCall thenValue = new JMethodCall(sourceInfo, new JParameterRef(
-        sourceInfo, thisParam), objectMethod);
-    for (JParameter param : newMethod.getParams()) {
-      if (param != thisParam) {
-        thenValue.addArg(new JParameterRef(sourceInfo, param));
-      }
-    }
-
-    JMethodCall elseValue = new JMethodCall(sourceInfo, null, jsoImpl);
-    for (JParameter param : newMethod.getParams()) {
-      elseValue.addArg(new JParameterRef(sourceInfo, param));
-    }
-
-    JConditional conditional = new JConditional(sourceInfo, objectMethod.getType(),
-        condition, thenValue, elseValue);
-
-    JReturnStatement returnStatement = new JReturnStatement(sourceInfo,
-        conditional);
-    ((JMethodBody) newMethod.getBody()).getBlock().addStmt(returnStatement);
-    return newMethod;
+    staticImplCreator = new CreateStaticImplsVisitor(program);
   }
 
   private void execImpl() {
@@ -179,54 +171,136 @@
       return;
     }
 
-    for (JMethod method : jsoType.getMethods()) {
-      if (method.needsVtable()) {
-        virtualJsoMethods.add(method);
-      }
-    }
-
-    if (virtualJsoMethods.isEmpty()) {
-      return;
-    }
-
-    CreateStaticImplsVisitor creator = new CreateStaticImplsVisitor(program);
-    for (JMethod method : virtualJsoMethods) {
-      // Ensure staticImpls exist for any instance methods.
-      JMethod jsoStaticImpl = program.getStaticImpl(method);
-      if (jsoStaticImpl == null) {
-        creator.accept(method);
-        jsoStaticImpl = program.getStaticImpl(method);
-        assert (jsoStaticImpl != null);
-      }
-
-      // Find the object method this instance method overrides.
-      JMethod objectOverride = findObjectOverride(method);
-      if (objectOverride != null) {
-        JMethod devirtualizer = createDevirtualMethod(objectOverride,
-            jsoStaticImpl);
-        devirtualMethods.add(devirtualizer);
-        objectMethodToJsoMethod.put(objectOverride, devirtualizer);
-      }
-    }
-
     RewriteVirtualDispatches rewriter = new RewriteVirtualDispatches();
     rewriter.accept(program);
     assert (rewriter.didChange());
   }
 
   /**
-   * Finds the object method this method overrides.
+   * Finds the method that overrides this method, starting with the target
+   * class.
    */
-  private JMethod findObjectOverride(JMethod method) {
-    Set<JMethod> overrides = program.typeOracle.getAllRealOverrides(method);
-    JMethod objectOverride = null;
-    for (JMethod override : overrides) {
-      if (override.getEnclosingType() == program.getTypeJavaLangObject()) {
-        objectOverride = override;
-        break;
+  private JMethod findOverridingMethod(JMethod method, JClassType target) {
+    if (target == null) {
+      return null;
+    }
+    for (JMethod overridingMethod : target.getMethods()) {
+      if (JTypeOracle.methodsDoMatch(method, overridingMethod)) {
+        return overridingMethod;
       }
     }
-    return objectOverride;
+    return findOverridingMethod(method, target.getSuperClass());
   }
 
+
+  /**
+    * Create a conditional method to discriminate between static and virtual
+    * dispatch.
+    *
+    * <pre>
+    * static boolean equals__devirtual$(Object this, Object other) {
+    *   return Cast.isJavaObject(this) ? this.equals(other) : JavaScriptObject.equals$(this, other);
+    * }
+    * </pre>
+    */
+  private JMethod getOrCreateDevirtualMethod(JMethodCall polyMethodCall,
+      JMethod jsoImpl) {
+    JMethod polyMethod = polyMethodCall.getTarget();
+    /**
+     * TODO(cromwellian) generate a inlined expression instead of Method
+     * Because devirtualization happens after optimization, the devirtual
+     * methods don't optimize well in the JS pass. Consider "inlining" a
+     * hand optimized devirtual method at callsites instead of a JMethodCall.
+     * As a bonus, the inlined code can be specialized for each callsite, for
+     * example, if there are no side effects, then there's no need for a
+     * temporary. Or, if the instance can't possibly be java.lang.String,
+     * then the JSO check becomes a cheaper check for typeMarker.
+     */
+    if (polyMethodToDevirtualMethods.containsKey(polyMethod)) {
+      return polyMethodToDevirtualMethods.get(polyMethod);
+    }
+
+
+    JClassType jsoType = program.getJavaScriptObject();
+    SourceInfo sourceInfo = jsoType.getSourceInfo()
+        .makeChild(SourceOrigin.UNKNOWN);
+
+    // Create the new method.
+    String name = polyMethod.getName() + "__devirtual$";
+    JMethod newMethod = program.createMethod(sourceInfo, name, jsoType,
+        polyMethod.getType(), false, true, true, false, false);
+    newMethod.setSynthetic();
+
+    // Setup parameters.
+    JParameter thisParam = JProgram.createParameter(sourceInfo, "this$static",
+        program.getTypeJavaLangObject(), true, true, newMethod);
+    for (JParameter oldParam : polyMethod.getParams()) {
+      JProgram.createParameter(sourceInfo, oldParam.getName(),
+          oldParam.getType(), true, false, newMethod);
+    }
+    newMethod.freezeParamTypes();
+    newMethod.addThrownExceptions(polyMethod.getThrownExceptions());
+    sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(newMethod));
+
+    // maybeJsoInvocation = this$static
+    JExpression instance = polyMethodCall.getInstance();
+    JLocal temp = JProgram.createLocal(sourceInfo, "maybeJsoInvocation",
+        thisParam.getType(), true, (JMethodBody) newMethod.getBody());
+    JMultiExpression multi = new JMultiExpression(sourceInfo);
+
+    // (maybeJsoInvocation = this$static, )
+    multi.exprs.add(JProgram.createAssignmentStmt(sourceInfo,
+        new JLocalRef(sourceInfo, temp),
+        new JParameterRef(sourceInfo, thisParam)).getExpr());
+
+    // Build from bottom up.
+    // isJavaObject(temp)
+    JMethodCall condition = new JMethodCall(sourceInfo, null,
+        isJavaObjectMethod);
+    condition.addArg(new JLocalRef(sourceInfo, temp));
+
+    // temp.method(args)
+    JMethodCall thenValue = new JMethodCall(sourceInfo, new JLocalRef(
+        sourceInfo, temp), polyMethod);
+    for (JParameter param : newMethod.getParams()) {
+      if (param != thisParam) {
+        thenValue.addArg(new JParameterRef(sourceInfo, param));
+      }
+    }
+
+    // jso$method(temp, args)
+    JMethodCall elseValue = new JMethodCall(sourceInfo, null, jsoImpl);
+    elseValue.addArg(new JLocalRef(sourceInfo, temp));
+    for (JParameter param : newMethod.getParams()) {
+      if (param != thisParam) {
+        elseValue.addArg(new JParameterRef(sourceInfo, param));
+      }
+    }
+
+    // isJavaObject(temp) ? temp.method(args) : jso$method(temp, args)
+    JConditional conditional = new JConditional(sourceInfo,
+        polyMethod.getType(),
+        condition, thenValue, elseValue);
+
+
+    multi.exprs.add(conditional);
+
+    JReturnStatement returnStatement = new JReturnStatement(sourceInfo,
+        multi);
+    ((JMethodBody) newMethod.getBody()).getBlock().addStmt(returnStatement);
+    polyMethodToDevirtualMethods.put(polyMethod, newMethod);
+    
+    return newMethod;
+  }
+
+  private JMethod getStaticImpl(JMethod method) {
+    assert !method.isStatic();
+    JMethod staticImpl = program.getStaticImpl(method);
+    if (staticImpl == null) {
+      staticImplCreator.accept(method);
+      staticImpl = program.getStaticImpl(method);
+    }
+    return staticImpl;
+  }
 }
+
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
index f96623d..10db7b3 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -33,7 +33,6 @@
 import com.google.gwt.dev.jjs.impl.TypeLinker;
 import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox;
 import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
-import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.TypeMap;
 import com.google.gwt.dev.js.ast.JsProgram;
 
@@ -273,8 +272,6 @@
         }
       }
     }
-    // Replace references to JSO subtypes with JSO itself.
-    JavaScriptObjectNormalizer.exec(jprogram);
 
     ImplementClassLiteralsAsFields.exec(jprogram);
 
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
index 03d7460..21f979d 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
@@ -60,6 +60,7 @@
   private JInterfaceType intfI;
   private JInterfaceType intfIBase;
   private JInterfaceType intfJ;
+  private JInterfaceType intfK;
   private JProgram program;
   private SourceInfo synthSource;
   private JReferenceType typeNull;
@@ -136,11 +137,16 @@
     assertTrue(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfObject));
     assertFalse(typeOracle.canTriviallyCast(arrayOfObject, arrayOfArrayOfB));
 
-    assertTrue(typeOracle.canTriviallyCast(classJso1, classJso2));
-    assertTrue(typeOracle.canTriviallyCast(classJso2, classJso1));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso1, classJso2));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso2, classJso1));
 
-    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
-    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso1, intfK));
+    assertTrue(typeOracle.canTheoreticallyCast(intfK, classJso1));
+
+    assertTrue(typeOracle.canTheoreticallyCast(intfJ, intfK));
+    assertTrue(typeOracle.canTheoreticallyCast(intfK, intfJ));
+
+    assertTrue(typeOracle.canTriviallyCast(classJso1, classJso));
 
     assertTrue(typeOracle.canTriviallyCast(arrayOfA, intfSerializable));
     assertFalse(typeOracle.canTriviallyCast(intfSerializable, arrayOfA));
@@ -150,15 +156,13 @@
 
     /*
      * Test that two types cannot both be trivially castable to each other,
-     * unless they are the same type. Or, unless they are both JSOs.
+     * unless they are the same type.
      */
     for (JReferenceType type1 : severalTypes()) {
       for (JReferenceType type2 : severalTypes()) {
         if (type1 != type2) {
-          if (!isJso(type1) || !isJso(type2)) {
-            assertFalse(typeOracle.canTriviallyCast(type1, type2)
-                && typeOracle.canTriviallyCast(type2, type1));
-          }
+          assertFalse(typeOracle.canTriviallyCast(type1, type2)
+              && typeOracle.canTriviallyCast(type2, type1));
         }
       }
     }
@@ -246,6 +250,7 @@
     intfI.addImplements(intfIBase);
 
     intfJ = createInterface("J");
+    intfK = createInterface("K");
 
     classBase = createClass("Base", classObject, false, false);
 
@@ -260,8 +265,10 @@
     classBSub = createClass("BSub", classB, false, false);
 
     classJso1 = createClass("Jso1", classJso, false, false);
+    classJso1.addImplements(intfJ);
     classJso2 = createClass("Jso2", classJso, false, false);
-
+    classJso2.addImplements(intfK);
+    
     program.typeOracle.computeBeforeAST();
 
     // Save off some miscellaneous types to test against
@@ -289,10 +296,6 @@
     return program.generalizeTypes(types);
   }
 
-  private boolean isJso(JReferenceType type) {
-    return typeOracle.canTriviallyCast(type, classJso);
-  }
-
   /**
    * Return several types, for exhaustively testing basic properties.
    */
@@ -313,6 +316,7 @@
     types.add(classString);
     types.add(intfI);
     types.add(intfJ);
+    types.add(intfK);
     types.add(intfIBase);
     types.add(classJso1);
     types.add(classJso2);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
index 15406c0..6291398 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
@@ -19,6 +19,8 @@
 import com.google.gwt.dev.javac.impl.MockJavaResource;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JNode;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.util.Empty;
 
@@ -63,6 +65,21 @@
       assertEquals(expectedSet, cfa.getInstantiatedTypes());
     }
 
+    public void assertOnlyLiveFieldsAndMethods(String... expected) {
+      Set<JNode> expectedSet = new HashSet<JNode>();
+      for (String expectedRef : expected) {
+        JField field = findField(program, expectedRef);
+        if (field != null) {
+          expectedSet.add(field);
+        } else {
+          JMethod method = findQualifiedMethod(program, expectedRef);
+          assertNotNull(method);
+          expectedSet.add(method);
+        }
+      }
+      assertEquals(expectedSet, cfa.getLiveFieldsAndMethods());
+    }
+
     public void assertOnlyLiveStrings(String... expectedStrings) {
       Set<String> expectedSet = new HashSet<String>();
       Collections.addAll(expectedSet, expectedStrings);
@@ -86,6 +103,76 @@
    * circumstances where values can pass from JS into Java.
    */
   public void testRescueJavaScriptObjectFromJsni() throws Exception {
+      sourceOracle.addOrReplace(new MockJavaResource("test.JsoIntf") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public interface JsoIntf {");
+        code.append("  public int getAny();");
+        code.append("}");
+        return code;
+      }
+    });
+
+      sourceOracle.addOrReplace(new MockJavaResource("test.UpRefIntf") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public interface UpRefIntf {");
+        code.append("  public int getFoo();");
+        code.append("}");
+        return code;
+      }
+    });
+
+     sourceOracle.addOrReplace(new MockJavaResource("test.NonImplementor") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public class NonImplementor extends JavaScriptObject {");
+        code.append("  protected NonImplementor() {}");
+        code.append("  final public native int getFoo() /*-{ return 0; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
+     sourceOracle.addOrReplace(new MockJavaResource("test.VirtualUpRef") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("final public class VirtualUpRef extends NonImplementor implements UpRefIntf {");
+        code.append("  protected VirtualUpRef() {}");
+        code.append("  public static native VirtualUpRef create() /*-{ return  {}; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.SingleJso") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("final public class SingleJso extends JavaScriptObject implements JsoIntf {");
+        code.append("  protected SingleJso() {}");
+        code.append("  public native int getAny() /*-{ return 1; }-*/;");
+        code.append("  public static native JsoIntf returnsJsoIntf() /*-{ return {}; }-*/;");
+        code.append("  public static native SingleJso returnsJso() /*-{ return {}; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
     sourceOracle.addOrReplace(new MockJavaResource("test.Foo") {
       @Override
       protected CharSequence getContent() {
@@ -122,6 +209,28 @@
     // Passing a parameter from JS to Java rescues.
     analyzeSnippet("Foo.passesJsoParam();").assertOnlyInstantiatedTypes(
         "JavaScriptObject", "Object");
+
+    // Returning a JSO subType instantiates it
+    analyzeSnippet("SingleJso.returnsJso();").assertOnlyInstantiatedTypes(
+        "SingleJso", "JavaScriptObject", "Object", "JsoIntf");
+
+    // Returning a JSO SingleJsoImpl instantiates it and the implementor
+    analyzeSnippet("SingleJso.returnsJsoIntf();").assertOnlyInstantiatedTypes(
+        "SingleJso", "JavaScriptObject", "Object", "JsoIntf");
+
+    // A virtual upref should still be rescued
+    analyzeSnippet("VirtualUpRef.create().getFoo();").assertOnlyInstantiatedTypes(
+        "VirtualUpRef", "NonImplementor", "JavaScriptObject", "Object", "UpRefIntf");
+    
+    // and its methods
+    analyzeSnippet("VirtualUpRef.create().getFoo();").assertOnlyLiveFieldsAndMethods(
+        "VirtualUpRef.$clinit", "VirtualUpRef.create",
+        "NonImplementor.$clinit","NonImplementor.getFoo",
+        "UpRefIntf.$clinit",
+        "JavaScriptObject.$clinit",
+        "EntryPoint.$clinit",
+        "EntryPoint.onModuleLoad",
+        "Object.$clinit");
   }
 
   private Result analyzeSnippet(String codeSnippet)
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index ce1328b..82f673f 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -109,6 +109,15 @@
     return findMethod(mainType, methodName);
   }
 
+  public static JMethod findQualifiedMethod(JProgram program, String methodName) {
+    int pos = methodName.lastIndexOf('.');
+    assertTrue(pos > 0);
+    String typeName = methodName.substring(0, pos);
+    String unqualMethodName = methodName.substring(pos + 1);
+    JDeclaredType type = findType(program, typeName);
+    return findMethod(type, unqualMethodName);
+  }
+
   /**
    * Finds a type by name. The type name may be short, e.g. <code>"Foo"</code>,
    * or fully-qualified, e.g. <code>"com.google.example.Foo"</code>. If a short
diff --git a/plugins/webkit/English.lproj/InfoPlist.strings b/plugins/webkit/English.lproj/InfoPlist.strings
index 92beb1a..6260754 100644
--- a/plugins/webkit/English.lproj/InfoPlist.strings
+++ b/plugins/webkit/English.lproj/InfoPlist.strings
Binary files differ
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml b/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
index f954c64..e429c36 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/Showcase.gwt.xml
@@ -25,4 +25,5 @@
   <extend-property name="locale" values="zh"/>
   <set-property-fallback name="locale" value="en"/>
   <set-configuration-property name="locale.cookie" value="SHOWCASE_LOCALE"/>
+  <set-configuration-property name="locale.useragent" value="Y"/>
 </module>
diff --git a/user/src/com/google/gwt/event/dom/DomEvent.gwt.xml b/user/src/com/google/gwt/event/dom/DomEvent.gwt.xml
index 1e761c5..12d921e 100644
--- a/user/src/com/google/gwt/event/dom/DomEvent.gwt.xml
+++ b/user/src/com/google/gwt/event/dom/DomEvent.gwt.xml
@@ -1,5 +1,6 @@
 <module>
-	<inherits name="com.google.gwt.event.EventBase" />
-	<inherits name="com.google.gwt.dom.DOM" />
+  <inherits name="com.google.gwt.dom.DOM" />
+  <inherits name="com.google.gwt.event.EventBase" />
+  <inherits name="com.google.gwt.event.dom.TouchEvent" />
   <source path="client"/>
 </module>
diff --git a/user/src/com/google/gwt/event/dom/TouchEvent.gwt.xml b/user/src/com/google/gwt/event/dom/TouchEvent.gwt.xml
new file mode 100644
index 0000000..e2beb0b
--- /dev/null
+++ b/user/src/com/google/gwt/event/dom/TouchEvent.gwt.xml
@@ -0,0 +1,37 @@
+<!--                                                                        -->
+<!-- Copyright 2011 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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.                                         -->
+<module>
+  <inherits name="com.google.gwt.user.User" />
+
+  <!-- Define the support property -->
+  <define-property name="touchEventSupport" values="maybe,no" />
+
+  <!--
+    Modern browsers either support touch events or will probably add touch
+    support in the future.
+  -->
+  <set-property name="touchEventSupport" value="maybe" />
+
+  <!-- Older browsers do not support touch events. -->
+  <set-property name="touchEventSupport" value="no">
+    <any>
+      <when-property-is name="user.agent" value="ie6" />
+    </any>
+  </set-property>
+
+  <replace-with class="com.google.gwt.event.dom.TouchEvent.TouchSupportDetectorNo">
+    <when-type-is class="com.google.gwt.event.dom.TouchEvent.TouchSupportDetector" />
+    <when-property-is name="touchEventSupport" value="no" />
+  </replace-with>
+</module>
diff --git a/user/src/com/google/gwt/event/dom/client/TouchEvent.java b/user/src/com/google/gwt/event/dom/client/TouchEvent.java
index e59c69d..556c3cb 100644
--- a/user/src/com/google/gwt/event/dom/client/TouchEvent.java
+++ b/user/src/com/google/gwt/event/dom/client/TouchEvent.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.event.dom.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.dom.client.Touch;
 import com.google.gwt.event.shared.EventHandler;
@@ -29,6 +30,53 @@
     extends HumanInputEvent<H> {
 
   /**
+   * Dectector for browser support for touch events.
+   */
+  private static class TouchSupportDetector {
+
+    private final boolean isSupported = detectTouchSupport();
+
+    public boolean isSupported() {
+      return isSupported;
+    }
+
+    private native boolean detectTouchSupport() /*-{
+      var elem = document.createElement('div');
+      elem.setAttribute('ontouchstart', 'return;');
+      return (typeof elem.ontouchstart) == "function";
+    }-*/;
+  }
+
+  /**
+   * Detector for browsers that do not support touch events.
+   */
+  @SuppressWarnings("unused")
+  private static class TouchSupportDetectorNo extends TouchSupportDetector {
+    @Override
+    public boolean isSupported() {
+      return false;
+    }
+  }
+
+  /**
+   * The implementation singleton.
+   */
+  private static TouchSupportDetector impl;
+
+  /**
+   * Runtime check for whether touch scrolling is supported in this browser. Returns true if touch
+   * events are supported but touch based scrolling is not natively supported.
+   * 
+   * @return true if touch events are supported, false if not
+   */
+  public static boolean isSupported() {
+    if (impl == null) {
+      impl = GWT.create(TouchSupportDetector.class);
+    }
+    return impl.isSupported();
+  }
+
+  /**
    * Get an array of {@link Touch touches} which have changed since the last
    * touch event fired. Note, that for {@link TouchEndEvent touch end events},
    * the touch which has just ended will not be present in the array. Moreover,
diff --git a/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java b/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java
index d3a5eb8..cea76a1 100644
--- a/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java
+++ b/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java
@@ -280,9 +280,15 @@
         + "navigator.browserLanguage : navigator.language;");
     body.println("if (language) {");
     body.indent();
-    body.println("locale = language.replace(/-/g, \"_\");");
+    body.println("var parts = language.split(/[-_]/);");
+    body.println("if (parts.length > 1) {");
+    body.indent();
+    body.println("parts[1] = parts[1].toUpperCase();");
     body.outdent();
-    body.println();
+    body.println("}");
+    body.println("locale = parts.join(\"_\");");
+    body.outdent();
+    body.println("}");
   }
 
   /**
diff --git a/user/src/com/google/gwt/touch/Touch.gwt.xml b/user/src/com/google/gwt/touch/Touch.gwt.xml
index 959061d..4e27ae4 100644
--- a/user/src/com/google/gwt/touch/Touch.gwt.xml
+++ b/user/src/com/google/gwt/touch/Touch.gwt.xml
@@ -13,25 +13,5 @@
 <!-- limitations under the License.                                         -->
 <module>
   <inherits name="com.google.gwt.user.User" />
-
-  <!-- Define the support property -->
-  <define-property name="touchEventSupport" values="maybe,no" />
-
-  <!--
-    Modern browsers either support touch events or will probably add touch
-    support in the future.
-  -->
-  <set-property name="touchEventSupport" value="maybe" />
-
-  <!-- Older browsers do not support touch events. -->
-  <set-property name="touchEventSupport" value="no">
-    <any>
-      <when-property-is name="user.agent" value="ie6" />
-    </any>
-  </set-property>
-
-  <replace-with class="com.google.gwt.touch.client.TouchScroller.TouchSupportDetectorNo">
-    <when-type-is class="com.google.gwt.touch.client.TouchScroller.TouchSupportDetector" />
-    <when-property-is name="touchEventSupport" value="no" />
-  </replace-with>
+  <source path="client"/>
 </module>
diff --git a/user/src/com/google/gwt/touch/client/TouchScroller.java b/user/src/com/google/gwt/touch/client/TouchScroller.java
index fd9967a..5c05a36 100644
--- a/user/src/com/google/gwt/touch/client/TouchScroller.java
+++ b/user/src/com/google/gwt/touch/client/TouchScroller.java
@@ -16,7 +16,6 @@
 package com.google.gwt.touch.client;
 
 import com.google.gwt.core.client.Duration;
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
@@ -43,6 +42,11 @@
 
 /**
  * Adds touch based scrolling to a scroll panel.
+ * 
+ * <p>
+ * Touch based scrolling is only supported on devices that support touch events
+ * and do not implement native touch based scrolling.
+ * </p>
  */
 @PartialSupport
 public class TouchScroller {
@@ -138,35 +142,6 @@
   }
 
   /**
-   * Dectector for browser support for touch events.
-   */
-  private static class TouchSupportDetector {
-
-    private final boolean isSupported = detectTouchSupport();
-
-    public boolean isSupported() {
-      return isSupported;
-    }
-
-    private native boolean detectTouchSupport() /*-{
-      var elem = document.createElement('div');
-      elem.setAttribute('ontouchstart', 'return;');
-      return (typeof elem.ontouchstart) == "function";
-    }-*/;
-  }
-
-  /**
-   * Detector for browsers that do not support touch events.
-   */
-  @SuppressWarnings("unused")
-  private static class TouchSupportDetectorNo extends TouchSupportDetector {
-    @Override
-    public boolean isSupported() {
-      return false;
-    }
-  }
-
-  /**
    * The number of frames per second the animation should run at.
    */
   private static final double FRAMES_PER_SECOND = 60;
@@ -193,9 +168,10 @@
   private static final int MS_PER_FRAME = (int) (1000 / FRAMES_PER_SECOND);
 
   /**
-   * The implementation singleton.
+   * A cached boolean indicating whether or not touch scrolling is supported.
+   * Set to a non-null value the first time {@link #isSupported()} is called.
    */
-  private static TouchSupportDetector impl;
+  private static Boolean isSupported;
 
   /**
    * Return a new {@link TouchScroller}.
@@ -222,25 +198,35 @@
   }
 
   /**
-   * Runtime check for whether touch events are supported in this browser.
+   * Runtime check for whether touch scrolling is supported in this browser.
+   * Returns true if touch events are supported but touch based scrolling is not
+   * natively supported.
    * 
-   * @return true if touch events are is supported, false it not
+   * @return true if touch scrolling is supported, false if not
    */
   public static boolean isSupported() {
-    return impl().isSupported();
+    if (isSupported == null) {
+      /*
+       * Android 3.0 devices support touch scrolling natively.
+       * 
+       * TODO(jlabanca): Find a more reliable way to detect if native touch
+       * scrolling is supported.
+       */
+      isSupported = TouchEvent.isSupported() && !isAndroid3();
+    }
+    return isSupported;
   }
 
   /**
-   * Get the implementation of this widget.
+   * Check if the user agent is android 3.0 or greater.
    * 
-   * @return the implementation
+   * @return true if android 3.0+
+   * 
    */
-  private static TouchSupportDetector impl() {
-    if (impl == null) {
-      impl = GWT.create(TouchSupportDetector.class);
-    }
-    return impl;
-  }
+  private static native boolean isAndroid3() /*-{
+    var ua = navigator.userAgent.toLowerCase();
+    return /android ([3-9]+)\.([0-9]+)/.exec(ua) != null;
+  }-*/;
 
   /**
    * The registration for the preview handler used to bust click events.
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
index 7bed0d0..36b1b26 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
@@ -31,6 +31,9 @@
   private static JavaScriptObject callDispatchDblClickEvent;
 
   @SuppressWarnings("unused")
+  private static JavaScriptObject callDispatchOnLoadEvent;
+
+  @SuppressWarnings("unused")
   private static JavaScriptObject callDispatchUnhandledEvent;
 
   /**
@@ -168,15 +171,18 @@
     $wnd['__gwt_dispatchEvent_' + moduleName] = dispatchEvent;
     @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchEvent = new Function('w',
       'return function() { w.__gwt_dispatchEvent_' + moduleName + '.call(this) }')($wnd);
- 
+
     $wnd['__gwt_dispatchDblClickEvent_' + moduleName] = dispatchDblClickEvent;
     @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchDblClickEvent = new Function('w',
       'return function() { w.__gwt_dispatchDblClickEvent_' + moduleName + '.call(this)}')($wnd);
- 
+
     $wnd['__gwt_dispatchUnhandledEvent_' + moduleName] = dispatchUnhandledEvent;
     @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchUnhandledEvent = new Function('w',
       'return function() { w.__gwt_dispatchUnhandledEvent_' + moduleName + '.call(this)}')($wnd);
 
+    @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchOnLoadEvent = new Function('w',
+      'return function() { w.__gwt_dispatchUnhandledEvent_' + moduleName + '.call(w.event.srcElement)}')($wnd);
+
     // We need to create these delegate functions to fix up the 'this' context.
     // Normally, 'this' is the firing element, but this is only true for
     // 'onclick = ...' event handlers, not for handlers setup via attachEvent().
@@ -268,8 +274,18 @@
         @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchEvent : null;
     if (chMask & 0x04000) elem.onscroll      = (bits & 0x04000) ?
         @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchEvent : null;
-    if (chMask & 0x08000) elem.onload        = (bits & 0x08000) ?
-        @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchUnhandledEvent : null;
+    if (chMask & 0x08000) {
+      if (elem.nodeName == "IFRAME") {
+        if (bits & 0x08000) {
+          elem.attachEvent('onload', @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchOnLoadEvent);
+        } else {
+          elem.detachEvent('onload', @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchOnLoadEvent);
+        }
+      } else {
+       elem.onload = (bits & 0x08000) ?
+          @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchUnhandledEvent : null;
+      }
+    }
     if (chMask & 0x10000) elem.onerror       = (bits & 0x10000) ?
         @com.google.gwt.user.client.impl.DOMImplTrident::callDispatchEvent : null;
     if (chMask & 0x20000) elem.onmousewheel  = (bits & 0x20000) ? 
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 c6b09da..cdd8b48 100644
--- a/user/src/com/google/gwt/user/client/ui/Frame.java
+++ b/user/src/com/google/gwt/user/client/ui/Frame.java
@@ -19,6 +19,10 @@
 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.event.dom.client.HasLoadHandlers;
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
 
 /**
  * A widget that wraps an IFRAME element, which can contain an arbitrary web
@@ -37,7 +41,7 @@
  * <h3>Example</h3> {@example com.google.gwt.examples.FrameExample}
  * </p>
  */
-public class Frame extends Widget {
+public class Frame extends Widget implements HasLoadHandlers {
 
   static final String DEFAULT_STYLENAME = "gwt-Frame";
 
@@ -93,6 +97,17 @@
   }
 
   /**
+   * Adds a {@link LoadEvent} load handler which will be called when the frame
+   * loads.
+   * 
+   * @param handler the load handler
+   * @return {@link HandlerRegistration} that can be used to remove this handler
+   */
+  public HandlerRegistration addLoadHandler(LoadHandler handler) {
+    return addDomHandler(handler, LoadEvent.getType());
+  }
+
+  /**
    * Gets the URL of the frame's resource.
    * 
    * @return the frame's URL
diff --git a/user/test/com/google/gwt/dom/DOMSuite.java b/user/test/com/google/gwt/dom/DOMSuite.java
index 42d0be6..231c3d8 100644
--- a/user/test/com/google/gwt/dom/DOMSuite.java
+++ b/user/test/com/google/gwt/dom/DOMSuite.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dom.client.DocumentTest;
 import com.google.gwt.dom.client.ElementTest;
 import com.google.gwt.dom.client.FormTests;
+import com.google.gwt.dom.client.FrameTests;
 import com.google.gwt.dom.client.MapTests;
 import com.google.gwt.dom.client.NodeTest;
 import com.google.gwt.dom.client.SelectTests;
@@ -40,6 +41,7 @@
     suite.addTestSuite(NodeTest.class);
     suite.addTestSuite(ElementTest.class);
     suite.addTestSuite(FormTests.class);
+    suite.addTestSuite(FrameTests.class);
     suite.addTestSuite(MapTests.class);
     suite.addTestSuite(SelectTests.class);
     suite.addTestSuite(StyleInjectorTest.class);
diff --git a/user/test/com/google/gwt/dom/client/FrameTests.java b/user/test/com/google/gwt/dom/client/FrameTests.java
index 183ea9d..cb6226a 100644
--- a/user/test/com/google/gwt/dom/client/FrameTests.java
+++ b/user/test/com/google/gwt/dom/client/FrameTests.java
@@ -16,12 +16,20 @@
 package com.google.gwt.dom.client;
 
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Frame;
+import com.google.gwt.user.client.ui.RootPanel;
+
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
 
 /**
  * Tests for the FrameElement and IFrameElement classes.
  */
 public class FrameTests extends GWTTestCase {
 
+  private static final int FRAME_LOAD_DELAY = 3000;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dom.DOMTest";
@@ -34,4 +42,52 @@
     doc.getBody().appendChild(iframe);
     assertNotNull(iframe.getContentDocument());
   }
+
+  public void testOnLoadEventFiresWithBrowerEvent() {
+    delayTestFinish(FRAME_LOAD_DELAY);
+
+    Frame frame = new Frame() {
+      public void onBrowserEvent(Event event) {
+        if (event.getTypeInt() == Event.ONLOAD) {
+          finishTest();
+        }
+        super.onBrowserEvent(event);
+      }
+    };
+
+    frame.sinkEvents(Event.ONLOAD);
+    RootPanel.get().add(frame);
+    frame.setUrl("iframetest.html");
+  }
+
+  public void testOnLoadEventFiresWithLoadHandler() {
+    delayTestFinish(FRAME_LOAD_DELAY);
+
+    Frame frame = new Frame();
+    frame.addLoadHandler(new LoadHandler() {
+      public void onLoad(LoadEvent event) {
+        finishTest();
+      }
+    });
+
+    RootPanel.get().add(frame);
+    frame.setUrl("iframetest.html");
+  }
+
+  public void testOnLoadEventFiresWithDomLoadHandler() {
+    delayTestFinish(FRAME_LOAD_DELAY);
+
+    Frame frame = new Frame() {
+      {
+        addDomHandler(new LoadHandler() {
+          public void onLoad(LoadEvent event) {
+            finishTest();
+          }
+        }, LoadEvent.getType());
+      }
+    };
+
+    RootPanel.get().add(frame);
+    frame.setUrl("iframetest.html");
+  }
 }
diff --git a/user/test/com/google/gwt/dom/public-test/iframetest.html b/user/test/com/google/gwt/dom/public-test/iframetest.html
new file mode 100644
index 0000000..0ebc59c
--- /dev/null
+++ b/user/test/com/google/gwt/dom/public-test/iframetest.html
@@ -0,0 +1,10 @@
+<html>
+  <head>
+    <title>
+      IFRAME TEST
+    </title>
+  </head>
+  <body>
+    IFRAME TEST
+  </body>
+</html>
diff --git a/user/test/com/google/gwt/touch/client/TouchScrollTest.java b/user/test/com/google/gwt/touch/client/TouchScrollTest.java
index 6b07f39..bcf035a 100644
--- a/user/test/com/google/gwt/touch/client/TouchScrollTest.java
+++ b/user/test/com/google/gwt/touch/client/TouchScrollTest.java
@@ -257,7 +257,7 @@
   public void testCreateIfSupported() {
     // createIfSupported()
     TouchScroller scroller = TouchScroller.createIfSupported();
-    if (isTouchSupported()) {
+    if (TouchScroller.isSupported()) {
       assertNotNull("TouchScroll not created, but touch is supported", scroller);
       assertNull(scroller.getTargetWidget());
 
@@ -268,7 +268,7 @@
     // createIfSupported(HasScrolling)
     HasScrolling target = new ScrollPanel();
     scroller = TouchScroller.createIfSupported(target);
-    if (isTouchSupported()) {
+    if (TouchScroller.isSupported()) {
       assertNotNull("TouchScroll not created, but touch is supported", scroller);
       assertEquals(target, scroller.getTargetWidget());
 
@@ -511,10 +511,4 @@
     scrollPanel = null;
     scroller = null;
   }
-
-  private native boolean isTouchSupported() /*-{
-    var elem = document.createElement('div');
-    elem.setAttribute('ontouchstart', 'return;');
-    return (typeof elem.ontouchstart) == "function";
-  }-*/;
 }
diff --git a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionCompileTest.java b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionCompileTest.java
index f4637a8..338b25a 100644
--- a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionCompileTest.java
+++ b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionCompileTest.java
@@ -24,6 +24,7 @@
 
 import org.hibernate.jsr303.tck.util.TckCompileTestCase;
 
+import javax.validation.ConstraintDefinitionException;
 import javax.validation.UnexpectedTypeException;
 
 /**
@@ -53,4 +54,30 @@
         MustBeApplicableValidatorFactory.MustBeApplicableValidator.class,
         Shoe.class);
   }
+
+  /**
+   * Replacement for
+   * {@link ConstraintCompositionTest#testOverriddenAttributesMustMatchInType()}
+   * 
+   * @throws UnableToCompleteException
+   */
+  public void testOverriddenAttributesMustMatchInType()
+      throws UnableToCompleteException {
+    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+    builder.expect(Type.ERROR, "Unable to create a validator for "
+        + "org.hibernate.jsr303.tck.tests.constraints.constraintcomposition."
+        + "ConstraintCompositionTest.DummyEntityWithZipCode "
+        + "because The overriding type of a composite constraint must be "
+        + "identical to the overridden one. "
+        + "Expected int found class java.lang.String",
+        ConstraintDefinitionException.class);
+    builder.setLowestLogLevel(Type.INFO);
+    UnitTestTreeLogger testLogger = builder.createLogger();
+    assertModuleFails(
+        testLogger,
+        getFullyQaulifiedModuleName(getClass(),
+            "OverriddenAttributesMustMatchInTypeTest"),
+        OverriddenAttributesMustMatchInTypeValidatorFactory.OverriddenAttributesMustMatchInTypeValidator.class);
+  }
+
 }
diff --git a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionGwtTest.java b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionGwtTest.java
index 5fee2ac..859183a 100644
--- a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionGwtTest.java
+++ b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/ConstraintCompositionGwtTest.java
@@ -56,11 +56,6 @@
     delegate.testOnlySingleConstraintViolation();
   }
 
-  @Failing(issue = 5799)
-  public void testOverriddenAttributesMustMatchInType() {
-    delegate.testOverriddenAttributesMustMatchInType();
-  }
-
   public void testPayloadPropagationInComposedConstraints() {
     delegate.testPayloadPropagationInComposedConstraints();
   }
diff --git a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeTest.gwt.xml b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeTest.gwt.xml
new file mode 100644
index 0000000..95895a8
--- /dev/null
+++ b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeTest.gwt.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+  Copyright 2010 Google Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  use this file except in compliance with the License. You may obtain a copy of
+  the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  License for the specific language governing permissions and limitations under
+  the License.
+-->
+<module>
+  <inherits name="org.hibernate.jsr303.tck.tests.ValidationTck" />
+  <source path="">
+    <include name="*.java" />
+    <exclude name="*CompileTest.java" />
+  </source>
+  <replace-with class="org.hibernate.jsr303.tck.tests.constraints.constraintcomposition.OverriddenAttributesMustMatchInTypeValidatorFactory">
+    <when-type-is class="javax.validation.ValidatorFactory"/>
+  </replace-with>
+</module>
\ No newline at end of file
diff --git a/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeValidatorFactory.java b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeValidatorFactory.java
new file mode 100644
index 0000000..04be602
--- /dev/null
+++ b/user/test/org/hibernate/jsr303/tck/tests/constraints/constraintcomposition/OverriddenAttributesMustMatchInTypeValidatorFactory.java
@@ -0,0 +1,32 @@
+package org.hibernate.jsr303.tck.tests.constraints.constraintcomposition;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.validation.client.AbstractGwtValidatorFactory;
+import com.google.gwt.validation.client.GwtValidation;
+import com.google.gwt.validation.client.impl.AbstractGwtValidator;
+
+import org.hibernate.jsr303.tck.tests.constraints.constraintcomposition.ConstraintCompositionTest.DummyEntityWithZipCode;
+
+import javax.validation.Validator;
+
+/**
+ * {@link AbstractGwtValidatorFactory} implementation that uses
+ * {@link com.google.gwt.validation.client.GwtValidation GwtValidation}.
+ */
+public final class OverriddenAttributesMustMatchInTypeValidatorFactory extends
+    AbstractGwtValidatorFactory {
+
+  /**
+   * Validator for
+   * {@link ConstraintCompositionTest#testOverriddenAttributesMustMatchInType()}
+   */
+  @GwtValidation(value = {DummyEntityWithZipCode.class})
+  public static interface OverriddenAttributesMustMatchInTypeValidator extends
+      Validator {
+  }
+
+  @Override
+  public AbstractGwtValidator createValidator() {
+    return GWT.create(OverriddenAttributesMustMatchInTypeValidator.class);
+  }
+}
\ No newline at end of file
diff --git a/user/test/org/hibernate/jsr303/tck/util/TckCompileTestCase.java b/user/test/org/hibernate/jsr303/tck/util/TckCompileTestCase.java
index 71740ac..dd7f4da 100644
--- a/user/test/org/hibernate/jsr303/tck/util/TckCompileTestCase.java
+++ b/user/test/org/hibernate/jsr303/tck/util/TckCompileTestCase.java
@@ -39,7 +39,7 @@
 
   @Override
   protected void gwtTearDown() throws Exception {
-   BeanHelper.clearBeanHelpersForTests();
+    BeanHelper.clearBeanHelpersForTests();
     super.gwtTearDown();
   }