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); + } }