Home > GWT > Patching Google Web Toolkit for compiling chrome permutation

Patching Google Web Toolkit for compiling chrome permutation

Google Web Toolkit is awesome tool for creating Rich Internet Applications (RIA). It has a lot of different features to simplify work of developer and create a really reliable and fast internet application. One of the features that widely used in gwt is Deferred binding.┬áIn few words, this feature doing next thing: it provide a possibility to create browser related code and avoid any runtime checks for what is current browser. By default permutations is created for this browsers: opera, ie6, ie8, ie9 (from gwt2.3), webkits (chrome, safari), firefox. Because of very fast chrome growth sometimes renders for chrome and webkit are different. From my experience I have following problems for chrome: while parsing JSON chrome don’t save the order of keys from server (it puts keys starts with numbers on the first place), sometimes some paddings different from safari (especially when you use a lot of wrapper divs)


Important note: See update at the page below, I’ve simplified the patching.
First of all, before I start describing technical part of this decision I want to outline all disadvantages of this way. The one and only big cons of this approach that you need to create a patch every time when gwt updates. Before gwt 2.3 patch was simple but now applying patch become more complex and can be even harder in following versions (but mb gwt dev team decides to add permutation for chrome by themselves, I read about this on some forum, but I don’t know if this is true). The pros of this approach you don’t need to put a runtime-if in program when you faced the difference between chrome and safari.

If you agree with this pros and cons than read article till end and I’ll describe how to patch gwt. Let’s begin.

From the version of gwt-2.3 dev team improve the user agent definition code with goal to avoid fails when your config have wrong settings(for dev-mode dev) and you can’t realize why the behavior of browser is weird. Thats why you need to recompile gwt after corrections in code we’ll made. Initial idea – that we just split safari permutation to two – chrome and safari. All implementations of different components for chrome – we use them the same as for safari.

1. Download gwt source code:

svn checkout http://google-web-toolkit.googlecode.com/svn/tools/ tools
svn checkout http://google-web-toolkit.googlecode.com/svn/trunk/ trunk
export GWT_TOOLS=~/gwt/tools
cd ~/gwt/trunk

More details about this step on gwt official page.

2. In versions before 2.3 all you need to apply this patch is just add some lines to all xml files where safari specific implementations included and fix UserAgent.gwt.xml file. Now, you need to apply some patches to java code. First of all let’s change the user agent code (See highlighted lines, they were changed) :

Add the following lines into user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * 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 VALID_VALUES = Arrays.asList(new String[]{
      "ie6", "ie8", "gecko1_8", "safari", "opera", "ie9", "chrome"}); 
  /**
   * List of predicates to identify user agent.
   * The order of evaluation is from top to bottom, i.e., the first matching
   * predicate will have the associated ua token returned.
   * ua is defined in an outer scope and is therefore visible in
   * the predicate javascript fragment.
   */
  private static UserAgentPropertyGeneratorPredicate[] predicates =
    new UserAgentPropertyGeneratorPredicate[] {
 
      // opera
      new UserAgentPropertyGeneratorPredicate("opera")
      .getPredicateBlock()
        .println("return (ua.indexOf('opera') != -1);")
      .returns("'opera'"),
 
     // webkit family      new UserAgentPropertyGeneratorPredicate("safari")      .getPredicateBlock()        .println("return ((ua.indexOf('webkit') != -1) && (ua.indexOf('chrome') == -1));")      .returns("'safari'"),       new UserAgentPropertyGeneratorPredicate("chrome")        .getPredicateBlock()          .println("return ((ua.indexOf('webkit') != -1) && (ua.indexOf('chrome') != -1));")        .returns("'chrome'"), 
      // IE9
      new UserAgentPropertyGeneratorPredicate("ie9")
      .getPredicateBlock()
        .println("return (ua.indexOf('msie') != -1 && ($doc.documentMode &gt;= 9));")
      .returns("'ie9'"),
 
      // IE8
      new UserAgentPropertyGeneratorPredicate("ie8")
      .getPredicateBlock()
        .println("return (ua.indexOf('msie') != -1 && ($doc.documentMode &gt;= 8));")
      .returns("'ie8'"),
 
      // IE6
      new UserAgentPropertyGeneratorPredicate("ie6")
      .getPredicateBlock()
        .println("var result = /msie ([0-9]+)\\.([0-9]+)/.exec(ua);")
        .println("if (result && result.length == 3)")
        .indent()
          .println("return (makeVersion(result) >= 6000);")
        .outdent()
      .returns("'ie6'"),
 
      // gecko family
      new UserAgentPropertyGeneratorPredicate("gecko1_8")
      .getPredicateBlock()
        .println("return (ua.indexOf('gecko') != -1);")
      .returns("'gecko1_8'"),
  };
 
  /**
   * 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,
      SortedSet possibleValues) {
 
    // write preamble
    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("};");
 
    // write only selected user agents
    for (int i = 0; i &lt; predicates.length; i++) {
      if (possibleValues.contains(predicates[i].getUserAgent())) {
        body.println("if ((function() { ");
        body.indent();
        body.print(predicates[i].toString());
        body.outdent();
        body.println("})()) return " + predicates[i].getReturnValue() + ";");
      }
    }
 
    // default return
    body.println("return 'unknown';");
  }
 
  public String generate(TreeLogger logger, SortedSet possibleValues,
      String fallback, SortedSet configProperties) {
    for (String value : possibleValues) {
      if (!VALID_VALUES.contains(value)) {
        logger.log(TreeLogger.WARN, "Unrecognized "
            + UserAgentGenerator.PROPERTY_USER_AGENT + " property value '"
            + value + "', possibly due to UserAgent.gwt.xml and "
            + UserAgentPropertyGenerator.class.getName()
            + " being out of sync." + " Use  to suppress this warning message.");
      }
    }
    // make sure that the # of ua in VALID_VALUES
    // is the same of predicates. maybe should iterate
    // to make sure each one has a match.
    assert predicates.length == VALID_VALUES.size();
    StringSourceWriter body = new StringSourceWriter();
    body.println("{");
    body.indent();
    writeUserAgentPropertyJavaScript(body, possibleValues);
    body.outdent();
    body.println("}");
 
    return body.toString();
  }
}

What we’ve done here? We just add a new property and extend generator with some code that will distinguish chrome and safari. Why so weird strings for defining user agent? Because strings of user agent for safari and chrome are similar, but in the chrome user agent string word chrome included but in safari user agent line not.

3. Now we need to add all components in gwt for using chrome permutation. We will copy implementation for safari and use it for chrome (Actually how it works now – the components for chrome the same as for safari). For find all places do the following command in trunk/user/src directory:

grep -R "safari" *

And simply copy paste in all xml safari strings and replace safari with chrome. Let’s give an example:

Was:

  <replace-with class="com.google.gwt.user.client.impl.DOMImplWebkit">
    <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
    <when-property-is name="user.agent" value="safari"/>
  </replace-with>

Become:

  <replace-with class="com.google.gwt.user.client.impl.DOMImplWebkit">
    <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
    <when-property-is name="user.agent" value="safari"/>
  </replace-with>
 <replace-with class="com.google.gwt.user.client.impl.DOMImplWebkit">    <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>    <when-property-is name="user.agent" value="chrome"/> </replace-with>

Quite long and boring operation but nothing hard.

4. Now we need to change main xml file where all permutations specified.
Open trunk/user/src/com/google/gwt/user/UserAgent.gwt.xml file and add chrome as property(line 9):

1
2
3
4
5
6
7
8
9
10
11
<module>
 
  <!-- Browser-sensitive code should use the 'user.agent' property -->
  <define-property name="user.agent" values="ie6" />
  <extend-property name="user.agent" values="ie8" />
  <extend-property name="user.agent" values="gecko1_8" />
  <extend-property name="user.agent" values="safari" />
  <extend-property name="user.agent" values="opera" />
  <extend-property name="user.agent" values="chrome" />  <extend-property name="user.agent" values="ie9" fallback-value="ie8" />
  <property-provider name="user.agent" generator="com.google.gwt.user.rebind.UserAgentPropertyGenerator"/>

5. Now we all done and we can compile gwt with our code modifications.
Go back to trunk directory (NOTE: you must have GWT_TOOLS exported, see step1) and just type:

ant

In some time you will have a new version of gwt builded and ready to use.
Packed version in dist/gwt-0.0.0.zip
And ready to copy version (unpacked) in staging/gwt-0.0.0

How to use this permutation?
In your application.gwt.xml file add user.agent to existing, something like this:

  <set-property name="user.agent" value="gecko1_8, chrome, safari"/>

In code you can use it like this:

@if user.agent chrome AND user.os win {
/* provide specific css values for chrome */
}

Thanks for reading and provide any questions if you want.

UPD. By the way I think that you don’t need to modify all xml files. Instead of this you can set up fallback-value for chrome (see IE9 example) and this will be enough, but I don’t have a chance to check this now.
The hint is next: skip step3 and on step4 replace line 9 with this:

 <extend-property name="user.agent" values="chrome" fallback-value="safari"/>

If anyone check this please write a comment if this solution works for you.

UPD2 The approach in the UPD1 works perfect, so it’s better way to do this.

PS. Initial idea of patching gwt was provided by Andrew Bananos, but I’ve created a patch for gwt-2.3

Categories: GWT Tags: , , , ,
  1. Bognekadje
    November 27th, 2011 at 08:40 | #1

    Hi,
    I have problem with gwt compilation through maven using archetype provides by codehaus.
    during compilation of gwt-maven-plugin, i get error with following message:

    [INFO] [ERROR] Unexpected error while processing XML
    [INFO] java.lang.NoClassDefFoundError: com/google/gwt/core/ext/linker/PropertyProviderGenerator

    I unzip gwt-user jar and do not find this class or module. I don’t know if my jar file is corrupted or not.

    can somebody help me to solve this problem.

    Thanks.

  1. No trackbacks yet.