Modify GLOBAL.window semantics and add relevant tests.
This patch removes special handling of the "window" namespace and
introduces a special, internal and undocumented "<window>" namespace.
When "<window>" namespace is used the actual namespace is the
unqualified top scope (which is normally the iframe and not the top
window).
Bug: #9423
Bug-Link: https://github.com/gwtproject/gwt/issues/9423
Change-Id: I57022ab3d832f686775b227d11866b5c6241b2a2
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
index e15b624..454e5f8 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
@@ -23,10 +23,14 @@
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
+import java.util.List;
+
/**
* Utility functions to interact with JDT classes for JsInterop.
*/
@@ -38,6 +42,26 @@
return "<global>".equals(jsNamespace);
}
+ public static boolean isWindow(String jsNamespace) {
+ return "<window>".equals(jsNamespace);
+ }
+
+ public static String normalizeQualifier(String qualifier) {
+ assert !qualifier.isEmpty();
+ List<String> components = Lists.newArrayList(qualifier.split("\\."));
+ if (isWindow(components.get(0))) {
+ // Emit unqualified if '<window>' namespace was specified.
+ components.remove(0);
+ } else if (isGlobal(components.get(0))) {
+ // Replace global with $wnd.
+ components.set(0, "$wnd");
+ } else {
+ // still emit $wnd as rest implicitly points to global.
+ components.add(0, "$wnd");
+ }
+ return Joiner.on('.').join(components);
+ }
+
public static void maybeSetJsInteropProperties(JDeclaredType type, Annotation[] annotations) {
AnnotationBinding jsType = getInteropAnnotation(annotations, "JsType");
String namespace = JdtUtil.getAnnotationParameterString(jsType, "namespace");
@@ -129,4 +153,5 @@
private static AnnotationBinding getInteropAnnotation(Annotation[] annotations, String name) {
return JdtUtil.getAnnotation(annotations, "jsinterop.annotations." + name);
}
+
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 511b10f..4071518 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -172,9 +172,9 @@
if (actualJsName.isEmpty()) {
assert !needsDynamicDispatch();
return namespace;
- } else if (JsInteropUtil.isGlobal(namespace)) {
+ } else if (JsInteropUtil.isGlobal(namespace) || JsInteropUtil.isWindow(namespace)) {
assert !needsDynamicDispatch();
- return actualJsName;
+ return namespace + "." + actualJsName;
} else {
return namespace + (isStatic() ? "." : ".prototype.") + actualJsName;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ClosureJsInteropExportsGenerator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ClosureJsInteropExportsGenerator.java
index 0ae3aa3..f8174eb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ClosureJsInteropExportsGenerator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ClosureJsInteropExportsGenerator.java
@@ -25,6 +25,7 @@
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStringLiteral;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
import java.util.HashSet;
import java.util.List;
@@ -91,6 +92,7 @@
private void generateExport(String exportNamespace, String qualifiedExportName,
JsExpression bridgeOrAlias, SourceInfo sourceInfo) {
+ assert !JsInteropUtil.isWindow(exportNamespace);
// goog.provide("a.b.c")
ensureGoogProvide(exportNamespace, sourceInfo);
// a.b.c = a_b_c_obf
@@ -115,7 +117,11 @@
}
private static JsExpression createExportQualifier(String namespace, SourceInfo sourceInfo) {
- return JsUtils.createQualifiedNameRef(
- JsInteropUtil.isGlobal(namespace) ? "window" : namespace, sourceInfo);
+ String components[] = namespace.split("\\.");
+ if (JsInteropUtil.isGlobal(components[0])) {
+ assert components.length != 0;
+ components[0] = null;
+ }
+ return JsUtils.createQualifiedNameRef(Joiner.on(".").skipNulls().join(components), sourceInfo);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DefaultJsInteropExportsGenerator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DefaultJsInteropExportsGenerator.java
index 97083fe..8cc5661 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DefaultJsInteropExportsGenerator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DefaultJsInteropExportsGenerator.java
@@ -86,6 +86,7 @@
private void ensureProvideNamespace(JMember member, JsExpression ctor) {
String namespace = member.getJsNamespace();
+ assert !JsInteropUtil.isWindow(namespace);
namespace = JsInteropUtil.isGlobal(namespace) ? "" : namespace;
if (namespace.equals(lastExportedNamespace)) {
return;
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 9612aeb..6a47400 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
@@ -25,6 +25,7 @@
import com.google.gwt.dev.PrecompileTaskOptions;
import com.google.gwt.dev.cfg.PermutationProperties;
import com.google.gwt.dev.common.InliningMode;
+import com.google.gwt.dev.javac.JsInteropUtil;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -899,7 +900,7 @@
JsNameRef methodNameRef;
if (method.isJsNative()) {
// Construct Constructor.prototype.jsname or Constructor.
- methodNameRef = createGlobalQualifier(method, sourceInfo);
+ methodNameRef = createGlobalQualifier(method.getQualifiedJsName(), sourceInfo);
} else if (method.isConstructor()) {
/*
* Constructor calls through {@code this} and {@code super} are always dispatched statically
@@ -981,7 +982,7 @@
JConstructor ctor = newInstance.getTarget();
JsName ctorName = names.get(ctor);
JsNameRef reference = ctor.isJsNative()
- ? createGlobalQualifier(ctor, sourceInfo)
+ ? createGlobalQualifier(ctor.getQualifiedJsName(), sourceInfo)
: ctorName.makeRef(sourceInfo);
List<JsExpression> arguments = transform(newInstance.getArgs());
@@ -1106,7 +1107,7 @@
JMethod method = jsniMethodRef.getTarget();
if (method.isJsNative()) {
// Construct Constructor.prototype.jsname or Constructor.
- return createGlobalQualifier(method, jsniMethodRef.getSourceInfo());
+ return createGlobalQualifier(method.getQualifiedJsName(), jsniMethodRef.getSourceInfo());
}
return names.get(method).makeRef(jsniMethodRef.getSourceInfo());
}
@@ -1721,7 +1722,7 @@
private JsNameRef createStaticReference(JMember member, SourceInfo sourceInfo) {
assert member.isStatic();
return member.isJsNative()
- ? createGlobalQualifier(member, sourceInfo)
+ ? createGlobalQualifier(member.getQualifiedJsName(), sourceInfo)
: names.get(member).makeRef(sourceInfo);
}
@@ -3075,24 +3076,7 @@
return names.get(program.getIndexedField(indexedName));
}
- private static final String WINDOW = "window";
-
- private static boolean isWindow(String jsNamespace) {
- return jsNamespace != null
- && (WINDOW.equals(jsNamespace) || jsNamespace.startsWith(WINDOW + "."));
- }
-
- private static JsNameRef createGlobalQualifier(JMember member, SourceInfo sourceInfo) {
- if (isWindow(member.getJsNamespace())) {
- return JsUtils.createQualifiedNameRef(
- member.getQualifiedJsName().substring(WINDOW.length() + 1), sourceInfo);
- }
- return createGlobalQualifier(member.getQualifiedJsName(), sourceInfo);
- }
-
private static JsNameRef createGlobalQualifier(String qualifier, SourceInfo sourceInfo) {
- assert !qualifier.isEmpty();
-
- return JsUtils.createQualifiedNameRef("$wnd." + qualifier, sourceInfo);
+ return JsUtils.createQualifiedNameRef(JsInteropUtil.normalizeQualifier(qualifier), sourceInfo);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
index a23d114..08cce1c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
@@ -575,11 +575,17 @@
}
}
- private <T extends HasJsName & HasSourceInfo> void checkJsNamespace(T item) {
+ private <T extends HasJsName & HasSourceInfo & CanBeJsNative> void checkJsNamespace(T item) {
if (JsInteropUtil.isGlobal(item.getJsNamespace())) {
return;
}
- if (item.getJsNamespace().isEmpty()) {
+ if (JsInteropUtil.isWindow(item.getJsNamespace())) {
+ if (item.isJsNative()) {
+ return;
+ }
+ logError(item, "'%s' can only be used as a namespace of native types and members.",
+ item.getJsNamespace());
+ } else if (item.getJsNamespace().isEmpty()) {
logError(item, "%s cannot have an empty namespace.", getDescription(item));
} else if (!JsUtils.isValidJsQualifiedName(item.getJsNamespace())) {
logError(item, "%s has invalid namespace '%s'.", getDescription(item), item.getJsNamespace());
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
index 7c53281..e4363b7 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
@@ -1209,6 +1209,10 @@
" @JsMethod(namespace = \"\") public static void o() {}",
" @JsProperty(namespace = \"\") public int p;",
" @JsMethod(namespace = \"a\") public void q() {}",
+ "}",
+ "@JsType(namespace = \"<window>\") public static class JsTypeOnWindow{",
+ " @JsProperty(namespace = \"<window>\") public static int r;",
+ " @JsMethod(namespace = \"<window>\") public static void s() {}",
"}");
assertBuggyFails(
@@ -1217,7 +1221,10 @@
"Line 8: 'int EntryPoint.Buggy.n' has invalid namespace 's^'.",
"Line 9: 'void EntryPoint.Buggy.o()' cannot have an empty namespace.",
"Line 10: Instance member 'int EntryPoint.Buggy.p' cannot declare a namespace.",
- "Line 11: Instance member 'void EntryPoint.Buggy.q()' cannot declare a namespace.");
+ "Line 11: Instance member 'void EntryPoint.Buggy.q()' cannot declare a namespace.",
+ "Line 13: '<window>' can only be used as a namespace of native types and members.",
+ "Line 14: '<window>' can only be used as a namespace of native types and members.",
+ "Line 15: '<window>' can only be used as a namespace of native types and members.");
}
public void testJsNameGlobalNamespacesSucceeds() throws Exception {
@@ -1232,10 +1239,16 @@
" @JsMethod(namespace = JsPackage.GLOBAL, name = \"a.b\") public static native void o();",
"}",
"@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = \"a.c\")",
- "public static class OtherBuggy {",
+ "public static class NativeOnGlobalNamespace {",
" @JsMethod(namespace = JsPackage.GLOBAL, name = \"a.d\") static native void o();",
" @JsMethod(namespace = JsPackage.GLOBAL, name = \"a.e\") static native void getP();",
" @JsProperty(namespace = JsPackage.GLOBAL, name = \"a.f\") public static int n;",
+ "}",
+ "@JsType(isNative = true, namespace = \"<window>\", name = \"a.g\")",
+ "public static class NativeOnWindowNamespace {",
+ " @JsMethod(namespace = \"<window>\", name = \"a.h\") static native void q();",
+ " @JsMethod(namespace = \"<window>\", name = \"a.i\") static native void getR();",
+ " @JsProperty(namespace = \"<window>\", name = \"a.j\") public static int s;",
"}");
assertBuggySucceeds();
diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java
index b7c9e99..d0e6bdb 100644
--- a/user/super/com/google/gwt/emul/java/lang/String.java
+++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -161,7 +161,7 @@
public native String apply(String thisContext, Object[] argsArray);
}
- @JsProperty(name = "String.fromCharCode", namespace = "window")
+ @JsProperty(name = "String.fromCharCode", namespace = "<window>")
private static native NativeFunction getFromCharCodeFunction();
public static String valueOf(char[] x) {
@@ -753,7 +753,7 @@
return start > 0 || end < length ? substring(start, end) : this;
}
- @JsType(isNative = true, name = "String", namespace = "window")
+ @JsType(isNative = true, name = "String", namespace = "<window>")
private static class NativeString {
public static native String fromCharCode(char x);
public int length;
diff --git a/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java b/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
index a40452f..8dc10c7 100644
--- a/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
+++ b/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
@@ -25,6 +25,7 @@
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
@@ -436,4 +437,35 @@
assertEquals(5, NativeClassWithStaticOverlayFields.initializedInt);
assertNull(NativeClassWithStaticOverlayFields.uninitializedString);
}
+
+ @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "window")
+ private static class MainWindow {
+ public static Object window;
+ }
+
+ // <window> is a special qualifier that allows referencing the iframe window instead of the main
+ // window.
+ @JsType(isNative = true, namespace = "<window>", name = "window")
+ private static class IFrameWindow {
+ public static Object window;
+ }
+
+ @JsType(isNative = true)
+ private static class AlsoMainWindow {
+ @JsProperty(namespace = JsPackage.GLOBAL)
+ public static Object window;
+ }
+
+ @JsType(isNative = true)
+ private static class AlsoIFrameWindow {
+ @JsProperty(namespace = "<window>")
+ public static Object window;
+ }
+
+ public void testMainWindowIsNotIFrameWindow() {
+ assertSame(IFrameWindow.window, AlsoIFrameWindow.window);
+ assertNotSame(AlsoIFrameWindow.window, AlsoMainWindow.window);
+ assertNotSame(IFrameWindow.window, MainWindow.window);
+ assertSame(MainWindow.window, AlsoMainWindow.window);
+ }
}