At application startup, assert that the specified user.agent selection property value indeed matches the expected value for the executing browser / user agent, thus avoid long hours debugging strange error messages when a single user agent compile, typically created for testing purposes, ends up being executed in the wrong browser. In other words, this patches saves you from pulling your hair out.
Fixes isuess: 5861
Review at http://gwt-code-reviews.appspot.com/1278801
Review by: jat@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9643 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/dom/client/DOMImplIE8.java b/user/src/com/google/gwt/dom/client/DOMImplIE8.java
index 0c29ccc..294aa49 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplIE8.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplIE8.java
@@ -34,7 +34,7 @@
return isIE8;
}
- // Stolen and modified from UserAgent.gwt.xml.
+ // Stolen and modified from UserAgentPropertyGenerator
private static native boolean isIE8Impl() /*-{
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("msie") != -1 && $doc.documentMode == 8) {
diff --git a/user/src/com/google/gwt/user/UserAgent.gwt.xml b/user/src/com/google/gwt/user/UserAgent.gwt.xml
index 1e52cdc..5ae3b6a 100644
--- a/user/src/com/google/gwt/user/UserAgent.gwt.xml
+++ b/user/src/com/google/gwt/user/UserAgent.gwt.xml
@@ -21,33 +21,14 @@
<!-- Browser-sensitive code should use the 'user.agent' property -->
<define-property name="user.agent" values="ie6,ie8,gecko1_8,safari,opera"/>
- <property-provider name="user.agent"><![CDATA[
- var ua = navigator.userAgent.toLowerCase();
- var makeVersion = function(result) {
- return (parseInt(result[1]) * 1000) + parseInt(result[2]);
- };
+ <property-provider name="user.agent" generator="com.google.gwt.user.rebind.UserAgentPropertyGenerator"/>
- if (ua.indexOf("opera") != -1) {
- return "opera";
- } else if (ua.indexOf("webkit") != -1) {
- return "safari";
- } else if (ua.indexOf("msie") != -1) {
- if (document.documentMode >= 8) {
- return "ie8";
- } else {
- var result = /msie ([0-9]+)\.([0-9]+)/.exec(ua);
- if (result && result.length == 3) {
- var v = makeVersion(result);
- if (v >= 6000) {
- return "ie6";
- }
- }
- }
- } else if (ua.indexOf("gecko") != -1) {
- return "gecko1_8";
- }
- return "unknown";
- ]]></property-provider>
+ <!-- Asserts the the compile time value matches the runtime user.agent value -->
+ <entry-point class="com.google.gwt.user.client.UserAgentAsserter" />
+
+ <generate-with class="com.google.gwt.user.rebind.UserAgentGenerator">
+ <when-type-assignable class="com.google.gwt.user.client.UserAgentAsserter.UserAgentProperty" />
+ </generate-with>
<!-- Deferred binding to optimize JRE classes based on user agent. -->
<inherits name="com.google.gwt.emul.EmulationWithUserAgent"/>
diff --git a/user/src/com/google/gwt/user/client/UserAgentAsserter.java b/user/src/com/google/gwt/user/client/UserAgentAsserter.java
new file mode 100644
index 0000000..8a87992
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/UserAgentAsserter.java
@@ -0,0 +1,64 @@
+/*
+ * 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 obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.user.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Helper class, which, during startup, asserts that the specified user.agent
+ * selection property value indeed matches the expected value for this browser /
+ * user agent, thus avoid long hours debugging strange error messages when a
+ * single user agent compile, typically created for testing purposes, ends up
+ * being executed in the wrong browser.
+ */
+public class UserAgentAsserter implements EntryPoint {
+
+ /**
+ * Interface to provide both the compile time and runtime
+ * <code>user.agent</code> selection property value.
+ */
+ interface UserAgentProperty {
+ String getCompileTimeValue();
+
+ String getRuntimeValue();
+ }
+
+ @Override
+ public void onModuleLoad() {
+ UserAgentProperty impl = GWT.create(UserAgentProperty.class);
+ String compileTimeValue = impl.getCompileTimeValue();
+ String runtimeValue = impl.getRuntimeValue();
+
+ if (!compileTimeValue.equals(runtimeValue)) {
+ displayMismatchWarning(runtimeValue, compileTimeValue);
+ }
+ }
+
+ /**
+ * Implemented as a JSNI method to avoid potentially using any user agent
+ * specific deferred binding code, since this method is called precisely when
+ * we're somehow executing code from the wrong user.agent permutation.
+ */
+ private native void displayMismatchWarning(String runtimeValue,
+ String compileTimeValue) /*-{
+ $wnd.alert("ERROR: Possible problem with your *.gwt.xml module file."
+ + "\nThe compile time user.agent value (" + compileTimeValue
+ + ") does not match the runtime user.agent value (" + runtimeValue
+ + "). Expect more errors.\n");
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
index 0c1dfef..dda18b7 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
@@ -59,7 +59,7 @@
var ua = navigator.userAgent;
if (ua.indexOf("Macintosh") != -1) {
- // Version logic taken from UserAgent.gwt.xml.
+ // Version logic taken from UserAgentPropertyGenerator
var result = /rv:([0-9]+)\.([0-9]+)/.exec(ua);
if (result && result.length == 3) {
// Gecko 1.8 and earlier had the scrollbar bug on OS X.
diff --git a/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java b/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java
new file mode 100644
index 0000000..13ea375
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java
@@ -0,0 +1,110 @@
+/*
+ * 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 obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.user.rebind;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+
+import java.io.PrintWriter;
+
+/**
+ * Generator for {@link com.google.gwt.user.client.UserAgentAsserter}.
+ */
+public class UserAgentGenerator extends Generator {
+ private static final String PROPERTY_USER_AGENT = "user.agent";
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ JClassType userType;
+ try {
+ userType = typeOracle.getType(typeName);
+ } catch (NotFoundException e) {
+ logger.log(TreeLogger.ERROR, "OOPS", e);
+ throw new UnableToCompleteException();
+ }
+ String packageName = userType.getPackage().getName();
+ String className = userType.getName();
+ className = className.replace('.', '_');
+
+ JClassType remoteService = typeOracle.findType(typeName);
+ if (remoteService == null) {
+ logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
+ + typeName + "'", null);
+ throw new UnableToCompleteException();
+ }
+
+ if (remoteService.isInterface() == null) {
+ logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName()
+ + " is not an interface", null);
+ throw new UnableToCompleteException();
+ }
+
+ PropertyOracle propertyOracle = context.getPropertyOracle();
+ String userAgentValue;
+ try {
+ SelectionProperty userAgentProperty = propertyOracle.getSelectionProperty(
+ logger, PROPERTY_USER_AGENT);
+ userAgentValue = userAgentProperty.getCurrentValue();
+ } catch (BadPropertyValueException e) {
+ logger.log(TreeLogger.ERROR, "Unable to find value for '"
+ + PROPERTY_USER_AGENT + "'", e);
+ throw new UnableToCompleteException();
+ }
+
+ String userAgentValueInitialCap = userAgentValue.substring(0, 1).toUpperCase()
+ + userAgentValue.substring(1);
+ className = className + "Impl" + userAgentValueInitialCap;
+
+ ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
+ packageName, className);
+ composerFactory.addImplementedInterface(remoteService.getQualifiedSourceName());
+
+ PrintWriter pw = context.tryCreate(logger, packageName, className);
+ if (pw != null) {
+ SourceWriter sw = composerFactory.createSourceWriter(context, pw);
+
+ sw.println();
+ sw.println("public native String getRuntimeValue() /*-{");
+ sw.indent();
+ UserAgentPropertyGenerator.writeUserAgentPropertyJavaScript(sw);
+ sw.outdent();
+ sw.println("}-*/;");
+ sw.println();
+
+ sw.println();
+ sw.println("public String getCompileTimeValue() {");
+ sw.indent();
+ sw.println("return \"" + userAgentValue.trim() + "\";");
+ sw.outdent();
+ sw.println("}");
+
+ sw.commit(logger);
+ }
+ return composerFactory.getCreatedClassName();
+ }
+}
diff --git a/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java
new file mode 100644
index 0000000..329a3c9
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java
@@ -0,0 +1,115 @@
+/*
+ * 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 obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.user.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Generator which writes out the JavaScript for determining the value of the
+ * <code>user.agent</code> selection property.
+ */
+public class UserAgentPropertyGenerator implements PropertyProviderGenerator {
+
+ /**
+ * List of valid user agent selection property values, which helps ensure that
+ * UserAgent.gwt.xml stays in sync with the
+ * {@link #writeUserAgentPropertyJavaScript(SourceWriter)} method body of this
+ * class.
+ */
+ private static final List<String> VALID_VALUES = Arrays.asList(new String[]{
+ "ie6", "ie8", "gecko1_8", "safari", "opera"});
+
+ /**
+ * Writes out the JavaScript function body for determining the value of the
+ * <code>user.agent</code> selection property. This method is used to create
+ * the selection script and by {@link UserAgentGenerator} to assert at runtime
+ * that the correct user agent permutation is executing. The list of
+ * <code>user.agent</code> values listed here should be kept in sync with
+ * {@link #VALID_VALUES} and <code>UserAgent.gwt.xml</code>.
+ */
+ static void writeUserAgentPropertyJavaScript(SourceWriter body) {
+ body.println("var ua = navigator.userAgent.toLowerCase();");
+ body.println("var makeVersion = function(result) {");
+ body.indent();
+ body.println("return (parseInt(result[1]) * 1000) + parseInt(result[2]);");
+ body.outdent();
+ body.println("};");
+ body.println("if (ua.indexOf('opera') != -1) {");
+ body.indent();
+ body.println("return 'opera';");
+ body.outdent();
+ body.println("} else if (ua.indexOf('webkit') != -1) {");
+ body.indent();
+ body.println("return 'safari';");
+ body.outdent();
+ body.println("} else if (ua.indexOf('msie') != -1) {");
+ body.indent();
+ body.println("if ($doc.documentMode >= 8) {");
+ body.indent();
+ body.println("return 'ie8';");
+ body.outdent();
+ body.println("} else {");
+ body.indent();
+ body.println("var result = /msie ([0-9]+)\\.([0-9]+)/.exec(ua);");
+ body.println("if (result && result.length == 3) {");
+ body.indent();
+ body.println("var v = makeVersion(result);");
+ body.println("if (v >= 6000) {");
+ body.indent();
+ body.println("return 'ie6';");
+ body.outdent();
+ body.println("}");
+ body.outdent();
+ body.println("}");
+ body.outdent();
+ body.println("}");
+ body.outdent();
+ body.println("} else if (ua.indexOf('gecko') != -1) {");
+ body.indent();
+ body.println("return 'gecko1_8';");
+ body.outdent();
+ body.println("}");
+ body.println("return 'unknown';");
+ }
+
+ @Override
+ public String generate(TreeLogger logger, SortedSet<String> possibleValues,
+ String fallback, SortedSet<ConfigurationProperty> configProperties) {
+ for (String value : possibleValues) {
+ if (!VALID_VALUES.contains(value)) {
+ logger.log(TreeLogger.ERROR, "Unrecognized user.agent property value '"
+ + value + "', possibly due to UserAgent.gwt.xml and "
+ + UserAgentPropertyGenerator.class.getName() + " being out of sync");
+ }
+ }
+
+ StringSourceWriter body = new StringSourceWriter();
+ body.println("{");
+ body.indent();
+ writeUserAgentPropertyJavaScript(body);
+ body.outdent();
+ body.println("}");
+
+ return body.toString();
+ }
+}
diff --git a/user/test/com/google/gwt/dom/client/ElementTest.java b/user/test/com/google/gwt/dom/client/ElementTest.java
index 3dc2ec5..ba87b94 100644
--- a/user/test/com/google/gwt/dom/client/ElementTest.java
+++ b/user/test/com/google/gwt/dom/client/ElementTest.java
@@ -612,11 +612,11 @@
return {};
}-*/;
- // Stolen from UserAgent.gwt.xml.
+ // Stolen from UserAgentPropertyGenerator
private native boolean isIE6or7() /*-{
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("msie") != -1) {
- if (document.documentMode >= 8) {
+ if ($doc.documentMode >= 8) {
return false;
}
return true;
diff --git a/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java b/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java
index e9f188b..d1fc9c3 100644
--- a/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java
+++ b/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java
@@ -51,9 +51,10 @@
* com/google/gwt/resources/Resources.gwt.xml
*/
private static native boolean isPreIe8() /*-{
+ // Stolen from UserAgentPropertyGenerator
var ua = navigator.userAgent.toLowerCase();
return ua.indexOf("msie") != -1
- && !(document.documentMode >= 8);
+ && !($doc.documentMode >= 8);
}-*/;
@Override
diff --git a/user/test/com/google/gwt/user/client/ui/HistoryTest.java b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
index 3400bf6..9706aa5 100644
--- a/user/test/com/google/gwt/user/client/ui/HistoryTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
@@ -47,12 +47,12 @@
}-*/;
/*
- * Copied from UserAgent.gwt.xml and HistoryImplSafari.
+ * Copied from UserAgentPropertyGenerator and HistoryImplSafari.
*/
private static native boolean isSafari2() /*-{
var ua = navigator.userAgent;
- // copied from UserAgent.gwt.xml
+ // copied from UserAgentPropertyGenerator
if (ua.indexOf("webkit") == -1) {
return false;
}