Do now qualify String, Array, Function, Object and Number with $wnd.

Bug: #9389
Bug-Link: https://github.com/gwtproject/gwt/issues/9389
Change-Id: I27496f43a4cba060e2622bdd4ded95a1496b5098
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 4f01054..d44f2d0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -898,7 +898,7 @@
       JsNameRef methodNameRef;
       if (method.isJsNative()) {
         // Construct Constructor.prototype.jsname or Constructor.
-        methodNameRef = createJsQualifier(method.getQualifiedJsName(), sourceInfo);
+        methodNameRef = createGlobalQualifier(method.getQualifiedJsName(), sourceInfo);
       } else if (method.isConstructor()) {
         /*
          * Constructor calls through {@code this} and {@code super} are always dispatched statically
@@ -980,7 +980,7 @@
       JConstructor ctor = newInstance.getTarget();
       JsName ctorName = names.get(ctor);
       JsNameRef  reference = ctor.isJsNative()
-          ? createJsQualifier(ctor.getQualifiedJsName(), sourceInfo)
+          ? createGlobalQualifier(ctor.getQualifiedJsName(), sourceInfo)
           : ctorName.makeRef(sourceInfo);
       List<JsExpression> arguments = transform(newInstance.getArgs());
 
@@ -1105,7 +1105,7 @@
       JMethod method = jsniMethodRef.getTarget();
       if (method.isJsNative()) {
         // Construct Constructor.prototype.jsname or Constructor.
-        return createJsQualifier(method.getQualifiedJsName(), jsniMethodRef.getSourceInfo());
+        return createGlobalQualifier(method.getQualifiedJsName(), jsniMethodRef.getSourceInfo());
       }
       return names.get(method).makeRef(jsniMethodRef.getSourceInfo());
     }
@@ -1720,7 +1720,7 @@
     private JsNameRef createStaticReference(JMember member, SourceInfo sourceInfo) {
       assert member.isStatic();
       return member.isJsNative()
-          ? createJsQualifier(member.getQualifiedJsName(), sourceInfo)
+          ? createGlobalQualifier(member.getQualifiedJsName(), sourceInfo)
           : names.get(member).makeRef(sourceInfo);
     }
 
@@ -1997,7 +1997,7 @@
 
       defineClassArguments.add(transform(getRuntimeTypeReference(type)));
       defineClassArguments.add(jsPrototype == null ? transform(superTypeId) :
-          createJsQualifier(jsPrototype, type.getSourceInfo()));
+          createGlobalQualifier(jsPrototype, type.getSourceInfo()));
       defineClassArguments.add(generateCastableTypeMap(type));
       defineClassArguments.addAll(constructorArgs);
 
@@ -2128,7 +2128,7 @@
       String jsPrototype = getSuperPrototype(type);
       SourceInfo info = type.getSourceInfo();
       JsNameRef parentCtor = jsPrototype != null ?
-          createJsQualifier(jsPrototype, info) :
+          createGlobalQualifier(jsPrototype, info) :
             superClass != null ?
               names.get(superClass).makeRef(info) :
               null;
@@ -2392,9 +2392,22 @@
       }
     }
 
-    public JsNameRef createJsQualifier(String qualifier, SourceInfo sourceInfo) {
+    public JsNameRef createGlobalQualifier(String qualifier, SourceInfo sourceInfo) {
       assert !qualifier.isEmpty();
-      return JsUtils.createQualifiedNameRef("$wnd." + qualifier, sourceInfo);
+
+      return JsUtils.createQualifiedNameRef(
+          isQualifiedThroughSpecialGlobalName(qualifier) ? qualifier : ("$wnd." + qualifier),
+          sourceInfo);
+    }
+
+    /*
+     * Some global names are considered special and should be used unqualified. Such names should
+     * not be emitted with the "$wnd." prefix as there seems to be runtime performance implications.
+     * {@see TypeCategory} for the full list.
+     */
+    private boolean isQualifiedThroughSpecialGlobalName(String qualifiedName) {
+      String topLevelName = qualifiedName.split("\\.")[0];
+      return TypeCategory.isSpecialGlobalName(topLevelName);
     }
 
     /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
index 4f3e702..6ae81a0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
@@ -22,6 +22,9 @@
 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.thirdparty.guava.common.collect.ImmutableMap;
+
+import java.util.Map;
 
 /**
  * TypeCategory classifies Java types into different categories. <p>
@@ -132,6 +135,19 @@
     return TypeCategory.TYPE_JAVA_OBJECT;
   }
 
+  public static boolean isSpecialGlobalName(String name) {
+    return specialGlobalNames.containsKey(name);
+  }
+
+  private static Map<String, TypeCategory> specialGlobalNames =
+      ImmutableMap.<String,TypeCategory>builder()
+          .put("Object", TYPE_JS_OBJECT)
+          .put("Function", TYPE_JS_FUNCTION)
+          .put("Array", TYPE_JS_ARRAY)
+          .put("Number", TYPE_JAVA_LANG_DOUBLE)
+          .put("String", TYPE_JAVA_LANG_STRING)
+          .build();
+
   private static TypeCategory getJsSpecialType(JType type) {
     if (!(type instanceof JClassType) || !type.isJsNative()) {
       return null;
@@ -142,19 +158,7 @@
       return null;
     }
 
-    switch (classType.getJsName()) {
-      case "Object":
-        return TypeCategory.TYPE_JS_OBJECT;
-      case "Function":
-        return TypeCategory.TYPE_JS_FUNCTION;
-      case "Array":
-        return TypeCategory.TYPE_JS_ARRAY;
-      case "Number":
-        return TypeCategory.TYPE_JAVA_LANG_DOUBLE;
-      case "String":
-        return TypeCategory.TYPE_JAVA_LANG_STRING;
-    }
-    return null;
+    return specialGlobalNames.get(classType.getJsName());
   }
 
   private static boolean isJsoArray(JType type) {
diff --git a/user/test/com/google/gwt/dev/jjs/optimized/StringOptimizationTest.java b/user/test/com/google/gwt/dev/jjs/optimized/StringOptimizationTest.java
index bc11f17..a42b7a9 100644
--- a/user/test/com/google/gwt/dev/jjs/optimized/StringOptimizationTest.java
+++ b/user/test/com/google/gwt/dev/jjs/optimized/StringOptimizationTest.java
@@ -27,6 +27,7 @@
   private static String createString() {
     return new String();
   }
+
   private static native String getGeneratedFunctionDefintionThatTriggersStringClinit() /*-{
     return function() {
       tmp = @StringOptimizationTest::createString()();
@@ -38,4 +39,19 @@
     assertFunctionMatches(functionDef, "tmp=''");
   }
 
+  private static native String getGeneratedFunctionDefintionThatCallsStringFromCharCode() /*-{
+    return function() {
+      tmp = @java.lang.String::valueOf(C)('c');
+    }.toString();
+  }-*/;
+
+  /*
+   * Makes sure that static String functions are emitted without the $wnd qualifier because doing
+   * so causes runtime performance degradation.
+   */
+  public void testNativeStringDoesNotUse$wnd() throws Exception {
+    String functionDef = getGeneratedFunctionDefintionThatCallsStringFromCharCode();
+    assertFunctionMatches(functionDef, "tmp=String.fromCharCode('c')");
+  }
+
 }