Author: [email protected]
Date: Tue Jun  2 12:09:40 2009
New Revision: 5496

Added:
    trunk/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java    
(contents, props changed)
     
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
    
(contents, props changed)
    trunk/dev/core/test/com/google/gwt/core/linker/
    trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java    
(contents, props changed)
Modified:
    trunk/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
     
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
     
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
     
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
    trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
    trunk/dev/core/src/com/google/gwt/dev/PermutationResult.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java
    trunk/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
    trunk/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
    trunk/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
    trunk/dev/core/src/com/google/gwt/dev/util/TextOutput.java
    trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
    trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml
    trunk/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml

Log:
This patch modifies the IFrame linker to use multiple script tags instead  
of one
giant one.  This improves parsing time on Firefox, and it should improve  
overall
loading latency on any browser whenever the network latency is significant.

Review by: scottb


Modified:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java        
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java        
 
Tue Jun  2 12:09:40 2009
@@ -50,6 +50,12 @@
    public abstract SortedSet<SortedMap<SelectionProperty, String>>  
getPropertyMap();

    /**
+   * Returns the statement ranges for the JavaScript returned by
+   * {...@link #getJavaScript()}.
+   */
+  public abstract StatementRanges[] getStatementRanges();
+
+  /**
     * Return a string that uniquely identifies this compilation result.  
Typically
     * this is a cryptographic hash of the compiled data.
     */

Added:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java
==============================================================================
--- (empty file)
+++ trunk/dev/core/src/com/google/gwt/core/ext/linker/StatementRanges.java      
 
Tue Jun  2 12:09:40 2009
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.core.ext.linker;
+
+/**
+ * Describes the source-code positions of top-level statements in a string  
of
+ * JavaScript.
+ */
+public interface StatementRanges {
+  /**
+   * The end of the ith statement.
+   */
+  int end(int i);
+
+  /**
+   * The number of statements in the associated JavaScript.
+   */
+  int numStatements();
+
+  /**
+   * The start of the ith statement.
+   */
+  int start(int i);
+}

Modified:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
        
(original)
+++  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
        
Tue Jun  2 12:09:40 2009
@@ -117,8 +117,7 @@
        throws UnableToCompleteException {
      String[] js = result.getJavaScript();
      byte[][] bytes = new byte[js.length][];
-    bytes[0] = generatePrimaryFragment(logger, context, js[0],
-        result.getStrongName(), js.length);
+    bytes[0] = generatePrimaryFragment(logger, context, result, js);
      for (int i = 1; i < js.length; i++) {
        bytes[i] = Util.getBytes(js[i]);
      }
@@ -159,6 +158,21 @@
          + ".nocache.js", lastModified);
    }

+  /**
+   * Generate the primary fragment. The default implementation is based on
+   * {...@link #getModulePrefix(TreeLogger, LinkerContext, String, int)} and
+   * {...@link #getModuleSuffix(TreeLogger, LinkerContext)}.
+   */
+  protected byte[] generatePrimaryFragment(TreeLogger logger,
+      LinkerContext context, CompilationResult result, String[] js)
+      throws UnableToCompleteException {
+    StringBuffer b = new StringBuffer();
+    b.append(getModulePrefix(logger, context, result.getStrongName(),  
js.length));
+    b.append(js[0]);
+    b.append(getModuleSuffix(logger, context));
+    return Util.getBytes(b.toString());
+  }
+
    protected String generatePropertyProvider(SelectionProperty prop) {
      StringBuffer toReturn = new StringBuffer();

@@ -385,14 +399,4 @@

    protected abstract String getSelectionScriptTemplate(TreeLogger logger,
        LinkerContext context) throws UnableToCompleteException;
-
-  private byte[] generatePrimaryFragment(TreeLogger logger,
-      LinkerContext context, String js, String strongName, int  
numFragments)
-      throws UnableToCompleteException {
-    StringBuffer b = new StringBuffer();
-    b.append(getModulePrefix(logger, context, strongName, numFragments));
-    b.append(js);
-    b.append(getModuleSuffix(logger, context));
-    return Util.getBytes(b.toString());
-  }
  }

Modified:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
    
(original)
+++  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
    
Tue Jun  2 12:09:40 2009
@@ -17,6 +17,7 @@

  import com.google.gwt.core.ext.linker.CompilationResult;
  import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.StatementRanges;
  import com.google.gwt.core.ext.linker.SymbolData;
  import com.google.gwt.dev.util.DiskCache;

@@ -74,12 +75,14 @@
    private final SortedSet<SortedMap<SelectionProperty, String>>  
propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
        MAP_COMPARATOR);

+  private final StatementRanges[] statementRanges;
+
    private final String strongName;

    private final long symbolToken;

    public StandardCompilationResult(String strongName, byte[][] js,
-      byte[] serializedSymbolMap) {
+      byte[] serializedSymbolMap, StatementRanges[] statementRanges) {
      super(StandardLinkerContext.class);
      this.strongName = strongName;
      jsToken = new long[js.length];
@@ -87,6 +90,7 @@
        jsToken[i] = diskCache.writeByteArray(js[i]);
      }
      symbolToken = diskCache.writeByteArray(serializedSymbolMap);
+    this.statementRanges = statementRanges;
    }

    /**
@@ -112,6 +116,11 @@
    @Override
    public SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap() {
      return Collections.unmodifiableSortedSet(propertyValues);
+  }
+
+  @Override
+  public StatementRanges[] getStatementRanges() {
+    return statementRanges;
    }

    @Override

Modified:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
        
(original)
+++  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
        
Tue Jun  2 12:09:40 2009
@@ -350,7 +350,8 @@
      StandardCompilationResult result = resultsByStrongName.get(strongName);
      if (result == null) {
        result = new StandardCompilationResult(strongName, js,
-          permutationResult.getSerializedSymbolMap());
+          permutationResult.getSerializedSymbolMap(),
+          permutationResult.getStatementRanges());
        resultsByStrongName.put(result.getStrongName(), result);
        artifacts.add(result);


Added:  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
==============================================================================
--- (empty file)
+++  
trunk/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStatementRanges.java
      
Tue Jun  2 12:09:40 2009
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2009 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.linker.StatementRanges;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * The standard implementation of {...@link StatementRanges}.
+ */
+public class StandardStatementRanges implements StatementRanges,  
Serializable {
+  private static int[] toArray(ArrayList<Integer> list) {
+    int[] ary = new int[list.size()];
+    for (int i = 0; i < list.size(); i++) {
+      ary[i] = list.get(i);
+    }
+    return ary;
+  }
+
+  private final int[] ends;
+  private final int[] starts;
+
+  public StandardStatementRanges(ArrayList<Integer> starts,  
ArrayList<Integer> ends) {
+    assert starts.size() == ends.size();
+    this.starts = toArray(starts);
+    this.ends = toArray(ends);
+  }
+
+  public int end(int i) {
+    return ends[i];
+  }
+
+  public int numStatements() {
+    return starts.length;
+  }
+
+  public int start(int i) {
+    return starts[i];
+  }
+}

Modified: trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java      
(original)
+++ trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java     Tue  
Jun  2 12:09:40 2009
@@ -19,18 +19,23 @@
  import com.google.gwt.core.ext.TreeLogger;
  import com.google.gwt.core.ext.UnableToCompleteException;
  import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
  import com.google.gwt.core.ext.linker.EmittedArtifact;
  import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.StatementRanges;
  import com.google.gwt.core.ext.linker.LinkerOrder.Order;
  import com.google.gwt.core.ext.linker.impl.HostedModeLinker;
  import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
  import com.google.gwt.dev.About;
  import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.Util;

  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URL;
  import java.net.URLConnection;
+import java.util.SortedSet;

  /**
   * Implements the canonical GWT bootstrap sequence that loads the GWT  
module in
@@ -38,6 +43,62 @@
   */
  @LinkerOrder(Order.PRIMARY)
  public class IFrameLinker extends SelectionScriptLinker {
+  /**
+   * This string is inserted between script chunks. It is made default  
access
+   * for testing.
+   */
+  static final String SCRIPT_CHUNK_SEPARATOR  
= "--></script>\n<script><!--\n";
+
+  /**
+   * A configuration property indicating how large each script tag should  
be.
+   */
+  private static final String CHUNK_SIZE_PROPERTY  
= "iframe.linker.script.chunk.size";
+
+  /**
+   * Split a JavaScript string into multiple chunks, at statement  
boundaries.
+   * Insert and end-script tag and a start-script tag in between each  
chunk.
+   * This method is made default access for testing.
+   */
+  static String splitPrimaryJavaScript(StatementRanges ranges, String js,
+      int charsPerChunk) {
+    if (charsPerChunk < 0) {
+      return js;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    int bytesInCurrentTag = 0;
+
+    for (int i = 0; i < ranges.numStatements(); i++) {
+      int start = ranges.start(i);
+      int end = ranges.end(i);
+      int length = end - start;
+      if (bytesInCurrentTag > 0
+          && bytesInCurrentTag + length > charsPerChunk) {
+        if (lastChar(sb) != '\n') {
+          sb.append('\n');
+        }
+        sb.append(SCRIPT_CHUNK_SEPARATOR);
+        bytesInCurrentTag = 0;
+      }
+      if (bytesInCurrentTag > 0) {
+        char lastChar = lastChar(sb);
+        if (lastChar != '\n' && lastChar != ';' && lastChar != '}') {
+          /*
+           * Make sure this statement has a separator from the last one.
+           */
+          sb.append(";");
+        }
+      }
+      sb.append(js, start, end);
+      bytesInCurrentTag += length;
+    }
+    return sb.toString();
+  }
+
+  private static char lastChar(StringBuilder sb) {
+    return sb.charAt(sb.length() - 1);
+  }
+
    @Override
    public String getDescription() {
      return "Standard";
@@ -87,6 +148,26 @@
      return toReturn;
    }

+  /**
+   * This implementation divides the code of the initial fragment into  
multiple
+   * script tags. These chunked script tags loads faster on Firefox even  
when
+   * the data is cached. Additionally, having the script tags separated  
means
+   * that the early ones can be evaluated before the later ones have  
finished
+   * downloading. As a result of this parallelism, the overall time to get  
the
+   * JavaScript downloaded and evaluated can lower.
+   */
+  @Override
+  protected byte[] generatePrimaryFragment(TreeLogger logger,
+      LinkerContext context, CompilationResult result, String[] js)
+      throws UnableToCompleteException {
+    StringBuffer b = new StringBuffer();
+    b.append(getModulePrefix(logger, context, result.getStrongName(),  
js.length));
+    b.append(splitPrimaryJavaScript(result.getStatementRanges()[0], js[0],
+        charsPerChunk(context, logger)));
+    b.append(getModuleSuffix(logger, context));
+    return Util.getBytes(b.toString());
+  }
+
    @Override
    protected String getCompilationExtension(TreeLogger logger,
        LinkerContext context) {
@@ -132,11 +213,33 @@

      return out.toString();
    }
-
+
    @Override
    protected String getSelectionScriptTemplate(TreeLogger logger,
        LinkerContext context) {
      return "com/google/gwt/core/linker/IFrameTemplate.js";
+  }
+
+  protected String modifyPrimaryJavaScript(String js) {
+    return js;
+  }
+
+  /**
+   * Extract via {...@link #CHUNK_SIZE_PROPERTY} the number of characters to  
be
+   * included in each script tag.
+   */
+  private int charsPerChunk(LinkerContext context, TreeLogger logger)
+      throws UnableToCompleteException {
+    SortedSet<ConfigurationProperty> configProps =  
context.getConfigurationProperties();
+    for (ConfigurationProperty prop : configProps) {
+      if (prop.getName().equals(CHUNK_SIZE_PROPERTY)) {
+        return Integer.parseInt(prop.getValues().get(0));
+      }
+    }
+
+    logger.log(TreeLogger.ERROR, "Unable to find configuration property "
+        + CHUNK_SIZE_PROPERTY);
+    throw new UnableToCompleteException();
    }

    /**

Modified: trunk/dev/core/src/com/google/gwt/dev/PermutationResult.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/PermutationResult.java        
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/PermutationResult.java        Tue Jun 
 2  
12:09:40 2009
@@ -16,6 +16,7 @@
  package com.google.gwt.dev;

  import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.StatementRanges;

  import java.io.Serializable;

@@ -38,4 +39,9 @@
     * The symbol map for the permutation.
     */
    byte[] getSerializedSymbolMap();
+
+  /**
+   * The statement ranges for the code returned by {...@link #getJs()}.
+   */
+  StatementRanges[] getStatementRanges();
  }

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java     
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java     
 
Tue Jun  2 12:09:40 2009
@@ -19,6 +19,7 @@
  import com.google.gwt.core.ext.TreeLogger;
  import com.google.gwt.core.ext.UnableToCompleteException;
  import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.StatementRanges;
  import com.google.gwt.core.ext.linker.SymbolData;
  import com.google.gwt.core.ext.linker.impl.StandardCompilationAnalysis;
  import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
@@ -95,7 +96,6 @@
  import com.google.gwt.dev.js.JsSymbolResolver;
  import com.google.gwt.dev.js.JsUnusedFunctionRemover;
  import com.google.gwt.dev.js.JsVerboseNamer;
-import com.google.gwt.dev.js.JsReportGenerationVisitor.CountingTextOutput;
  import com.google.gwt.dev.js.ast.JsName;
  import com.google.gwt.dev.js.ast.JsProgram;
  import com.google.gwt.dev.js.ast.JsStatement;
@@ -137,8 +137,10 @@
      private final ArtifactSet artifacts = new ArtifactSet();
      private final byte[][] js;
      private final byte[] serializedSymbolMap;
+    private final StatementRanges[] statementRanges;

-    public PermutationResultImpl(String[] js, SymbolData[] symbolMap) {
+    public PermutationResultImpl(String[] js, SymbolData[] symbolMap,
+        StatementRanges[] statementRanges) {
        byte[][] bytes = new byte[js.length][];
        for (int i = 0; i < js.length; ++i) {
          bytes[i] = Util.getBytes(js[i]);
@@ -152,6 +154,7 @@
          throw new RuntimeException("Should never happen with in-memory  
stream",
              e);
        }
+      this.statementRanges = statementRanges;
      }

      public ArtifactSet getArtifacts() {
@@ -165,6 +168,10 @@
      public byte[] getSerializedSymbolMap() {
        return serializedSymbolMap;
      }
+
+    public StatementRanges[] getStatementRanges() {
+      return statementRanges;
+    }
    }

    /**
@@ -303,28 +310,28 @@
        List<Map<Range, SourceInfo>> sourceInfoMaps = options.isSoycEnabled()
            ? new ArrayList<Map<Range,  
SourceInfo>>(jsProgram.getFragmentCount())
            : null;
-
+      StatementRanges[] ranges = new StatementRanges[js.length];
        for (int i = 0; i < js.length; i++) {
+        DefaultTextOutput out = new DefaultTextOutput(
+            options.getOutput().shouldMinimize());
+        JsSourceGenerationVisitor v;
          if (sourceInfoMaps != null) {
-          CountingTextOutput out = new CountingTextOutput(
-              options.getOutput().shouldMinimize());
-          JsReportGenerationVisitor v = new JsReportGenerationVisitor(out);
-          v.accept(jsProgram.getFragmentBlock(i));
-          js[i] = out.toString();
-          sourceInfoMaps.add(v.getSourceInfoMap());
+          v = new JsReportGenerationVisitor(out);
          } else {
-          DefaultTextOutput out = new DefaultTextOutput(
-              options.getOutput().shouldMinimize());
-          JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
-          v.accept(jsProgram.getFragmentBlock(i));
-          js[i] = out.toString();
+          v = new JsSourceGenerationVisitor(out);
+        }
+        v.accept(jsProgram.getFragmentBlock(i));
+        js[i] = out.toString();
+        if (sourceInfoMaps != null) {
+          sourceInfoMaps.add(((JsReportGenerationVisitor)  
v).getSourceInfoMap());
          }
+        ranges[i] = v.getStatementRanges();
        }

        PermutationResult toReturn = new PermutationResultImpl(js,
-          makeSymbolMap(symbolTable));
+          makeSymbolMap(symbolTable), ranges);

-      if (sourceInfoMaps != null) {
+      if (sourceInfoMaps != null) {
          long soycStart = System.currentTimeMillis();
          System.out.println("Computing SOYC output");
          // Free up memory.

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java       
 
Tue Jun  2 12:09:40 2009
@@ -1502,7 +1502,8 @@
        JsFunction nullFunc = new JsFunction(sourceInfo, topScope,
            nullMethodName, true);
        nullFunc.setBody(new JsBlock(sourceInfo));
-      globalStatements.add(nullFunc.makeStmt());
+      // Add it first, so that script-tag chunking in IFrameLinker works
+      globalStatements.add(0, nullFunc.makeStmt());
      }

      private void generateSeedFuncAndPrototype(JClassType x,

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java       
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/TextOutputVisitor.java       
 
Tue Jun  2 12:09:40 2009
@@ -30,6 +30,10 @@
      this.textOutput = textOutput;
    }

+  public int getPosition() {
+    return textOutput.getPosition();
+  }
+
    public void indentIn() {
      textOutput.indentIn();
    }

Modified:  
trunk/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java     
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java     
 
Tue Jun  2 12:09:40 2009
@@ -19,10 +19,8 @@
  import com.google.gwt.dev.jjs.HasSourceInfo;
  import com.google.gwt.dev.jjs.SourceInfo;
  import com.google.gwt.dev.js.ast.JsVisitable;
-import com.google.gwt.dev.util.AbstractTextOutput;
+import com.google.gwt.dev.util.TextOutput;

-import java.io.PrintWriter;
-import java.io.StringWriter;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
@@ -33,35 +31,10 @@
   * locations of SourceInfo objects in the output.
   */
  public class JsReportGenerationVisitor extends JsSourceGenerationVisitor {
-  /**
-   * A TextOutput that can return the total number of characters that have  
been
-   * printed.
-   */
-  public static class CountingTextOutput extends AbstractTextOutput {
-    private final StringWriter sw = new StringWriter();
-    private final PrintWriter out = new PrintWriter(sw);
-
-    public CountingTextOutput(boolean compact) {
-      super(compact);
-      setPrintWriter(out);
-    }
-
-    public int getCount() {
-      out.flush();
-      return sw.getBuffer().length();
-    }
-
-    @Override
-    public String toString() {
-      out.flush();
-      return sw.toString();
-    }
-  }
-
    private final Map<Range, SourceInfo> sourceInfoMap = new HashMap<Range,  
SourceInfo>();
-  private final CountingTextOutput out;
+  private final TextOutput out;

-  public JsReportGenerationVisitor(CountingTextOutput out) {
+  public JsReportGenerationVisitor(TextOutput out) {
      super(out);
      this.out = out;
    }
@@ -73,11 +46,11 @@
    @Override
    protected <T extends JsVisitable<T>> T doAccept(T node) {
      boolean addEntry = node instanceof HasSourceInfo;
-    int start = addEntry ? out.getCount() : 0;
+    int start = addEntry ? out.getPosition() : 0;
      T toReturn = super.doAccept(node);
      if (addEntry) {
        SourceInfo info = ((HasSourceInfo) node).getSourceInfo();
-      sourceInfoMap.put(new Range(start, out.getCount()), info);
+      sourceInfoMap.put(new Range(start, out.getPosition()), info);
      }
      return toReturn;
    }

Modified:  
trunk/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java       
 
Tue Jun  2 12:09:40 2009
@@ -15,6 +15,8 @@
   */
  package com.google.gwt.dev.js;

+import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.core.ext.linker.impl.StandardStatementRanges;
  import com.google.gwt.dev.js.ast.HasName;
  import com.google.gwt.dev.js.ast.JsArrayAccess;
  import com.google.gwt.dev.js.ast.JsArrayLiteral;
@@ -69,8 +71,11 @@
  import com.google.gwt.dev.js.ast.JsWhile;
  import com.google.gwt.dev.js.ast.JsVars.JsVar;
  import com.google.gwt.dev.util.TextOutput;
+import com.google.gwt.dev.util.collect.HashSet;

+import java.util.ArrayList;
  import java.util.Iterator;
+import java.util.Set;
  import java.util.regex.Pattern;

  /**
@@ -120,12 +125,26 @@
    private static final Pattern VALID_NAME_PATTERN =  
Pattern.compile("[a-zA-Z_$][\\w$]*");

    protected boolean needSemi = true;
+
+  /**
+   * "Global" blocks are either the global block of a fragment, or a block
+   * nested directly within some other global block. This definition  
matters
+   * because the statements designated by statementEnds and  
statementStarts are
+   * those that appear directly within these global blocks.
+   */
+  private Set<JsBlock> globalBlocks = new HashSet<JsBlock>();
    private final TextOutput p;
+  private ArrayList<Integer> statementEnds = new ArrayList<Integer>();
+  private ArrayList<Integer> statementStarts = new ArrayList<Integer>();

    public JsToStringGenerationVisitor(TextOutput out) {
      this.p = out;
    }

+  public StatementRanges getStatementRanges() {
+    return new StandardStatementRanges(statementStarts, statementEnds);
+  }
+
    @Override
    public boolean visit(JsArrayAccess x, JsContext<JsExpression> ctx) {
      JsExpression arrayExpr = x.getArrayExpr();
@@ -816,6 +835,8 @@

      int count = 0;
      for (Iterator<JsStatement> iter = x.getStatements().iterator();  
iter.hasNext(); ++count) {
+      boolean isGlobal = x.isGlobalBlock() || globalBlocks.contains(x);
+
        if (truncate && count > JSBLOCK_LINES_TO_PRINT) {
          p.print("[...]");
          _newlineOpt();
@@ -823,7 +844,22 @@
        }
        JsStatement stmt = iter.next();
        needSemi = true;
+      boolean shouldRecordPositions = isGlobal && !(stmt instanceof  
JsBlock);
+      boolean stmtIsGlobalBlock = false;
+      if (isGlobal) {
+        if (stmt instanceof JsBlock) {
+          // A block inside a global block is still considered global
+          stmtIsGlobalBlock = true;
+          globalBlocks.add((JsBlock) stmt);
+        }
+      }
+      if (shouldRecordPositions) {
+        statementStarts.add(p.getPosition());
+      }
        accept(stmt);
+      if (stmtIsGlobalBlock) {
+        globalBlocks.remove(stmt);
+      }
        if (needSemi) {
          /*
           * Special treatment of function decls: If they are the only item  
in a
@@ -852,6 +888,10 @@
            }
            _newlineOpt();
          }
+      }
+      if (shouldRecordPositions) {
+        assert (statementStarts.size() == statementEnds.size() + 1);
+        statementEnds.add(p.getPosition());
        }
      }


Modified: trunk/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java   
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java  Tue  
Jun  2 12:09:40 2009
@@ -28,11 +28,16 @@
    private char[][] indents = new char[][] {new char[0]};
    private boolean justNewlined;
    private PrintWriter out;
+  private int position = 0;

    protected AbstractTextOutput(boolean compact) {
      this.compact = compact;
    }

+  public int getPosition() {
+    return position;
+  }
+
    public void indentIn() {
      ++identLevel;
      if (identLevel >= indents.length) {
@@ -55,14 +60,16 @@
      if (compact) {
        out.print('\n');
      } else {
-      out.println();
+      out.print('\n');
      }
+    position++;
      justNewlined = true;
    }

    public void newlineOpt() {
      if (!compact) {
-      out.println();
+      out.print('\n');
+      position++;
        justNewlined = true;
      }
    }
@@ -70,18 +77,19 @@
    public void print(char c) {
      maybeIndent();
      out.print(c);
+    position++;
      justNewlined = false;
    }

    public void print(char[] s) {
      maybeIndent();
-    out.print(s);
+    printAndCount(s);
      justNewlined = false;
    }

    public void print(String s) {
      maybeIndent();
-    out.print(s);
+    printAndCount(s.toCharArray());
      justNewlined = false;
    }

@@ -89,20 +97,21 @@
      if (!compact) {
        maybeIndent();
        out.print(c);
+      position += 1;
      }
    }

    public void printOpt(char[] s) {
      if (!compact) {
        maybeIndent();
-      out.print(s);
+      printAndCount(s);
      }
    }

    public void printOpt(String s) {
      if (!compact) {
        maybeIndent();
-      out.print(s);
+      printAndCount(s.toCharArray());
      }
    }

@@ -112,8 +121,13 @@

    private void maybeIndent() {
      if (justNewlined && !compact) {
-      out.print(indents[identLevel]);
+      printAndCount(indents[identLevel]);
        justNewlined = false;
      }
+  }
+
+  private void printAndCount(char[] chars) {
+    position += chars.length;
+    out.print(chars);
    }
  }

Modified: trunk/dev/core/src/com/google/gwt/dev/util/TextOutput.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/util/TextOutput.java  (original)
+++ trunk/dev/core/src/com/google/gwt/dev/util/TextOutput.java  Tue Jun  2  
12:09:40 2009
@@ -19,6 +19,8 @@
   * Interface used for printing text output.
   */
  public interface TextOutput {
+  int getPosition();
+
    void indentIn();

    void indentOut();

Added:  
trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java
==============================================================================
--- (empty file)
+++ trunk/dev/core/test/com/google/gwt/core/linker/ScriptChunkingTest.java      
 
Tue Jun  2 12:09:40 2009
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2009 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.core.linker;
+
+import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.core.ext.linker.impl.StandardStatementRanges;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/**
+ * Tests the script chunking in the {...@link IFrameLinker}.
+ */
+public class ScriptChunkingTest extends TestCase {
+  /**
+   * A class for building up JavaScript that has statements and  
non-statement
+   * code interleaved.
+   */
+  private static class ScriptWithRangesBuilder {
+    private final ArrayList<Integer> ends = new ArrayList<Integer>();
+    private final StringBuffer script = new StringBuffer();
+    private final ArrayList<Integer> starts = new ArrayList<Integer>();
+
+    public void addNonStatement(String string) {
+      script.append(string);
+    }
+
+    public void addStatement(String string) {
+      starts.add(script.length());
+      script.append(string);
+      ends.add(script.length());
+    }
+
+    public String getJavaScript() {
+      return script.toString();
+    }
+
+    public StatementRanges getRanges() {
+      return new StandardStatementRanges(starts, ends);
+    }
+  }
+
+  public void testBasics() {
+    ScriptWithRangesBuilder builder = new ScriptWithRangesBuilder();
+    builder.addNonStatement("{");
+    String stmt1 = "x=1;";
+    builder.addStatement(stmt1);
+    String stmt2 = "function x(){x = 2}\n";
+    builder.addStatement(stmt2);
+    String stmt3 = "x=3";
+    /*
+     * This one has no terminator, so the chunker must add one
+     */
+    builder.addStatement(stmt3);
+    builder.addNonStatement("}\n{");
+    String stmt4 = "x=5";
+    builder.addStatement(stmt4);
+    builder.addNonStatement("}");
+
+    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), stmt1.length() + stmt2.length());
+    assertEquals(stmt1 + stmt2 + IFrameLinker.SCRIPT_CHUNK_SEPARATOR +  
stmt3
+        + ';' + stmt4, split);
+  }
+
+  /**
+   * Test a chunk size large enough that no splitting happens.
+   */
+  public void testLongChunkSize() {
+    ScriptWithRangesBuilder builder = new ScriptWithRangesBuilder();
+    builder.addNonStatement("{");
+    builder.addNonStatement("{");
+    String stmt1 = "x=1;";
+    builder.addStatement(stmt1);
+    String stmt2 = "function x(){x = 2}\n";
+    builder.addStatement(stmt2);
+    String stmt3 = "x=3";
+    builder.addStatement(stmt3);
+    builder.addNonStatement("}\n{");
+    String stmt4 = "x=5";
+    builder.addStatement(stmt4);
+    builder.addNonStatement("}");
+
+    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), 10000);
+    assertEquals(stmt1 + stmt2 + stmt3 + ';' + stmt4, split);
+  }
+
+  /**
+   * Test with chunking disabled.
+   */
+  public void testNegativeChunkSize() {
+    ScriptWithRangesBuilder builder = new ScriptWithRangesBuilder();
+    builder.addNonStatement("{");
+    builder.addNonStatement("{");
+    builder.addStatement("x=1;");
+    builder.addStatement("function x(){x = 2}\n");
+    builder.addStatement("x=3");
+    builder.addNonStatement("}\n{");
+    builder.addStatement("x=5");
+    builder.addNonStatement("}");
+
+    String split = IFrameLinker.splitPrimaryJavaScript(builder.getRanges(),
+        builder.getJavaScript(), -1);
+    assertEquals(builder.getJavaScript(), split);
+  }
+}

Modified: trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
==============================================================================
--- trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml       
(original)
+++ trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml       Tue Jun 
  
2 12:09:40 2009
@@ -16,11 +16,23 @@
   
<!--                                                                         
-->
  <module>
    <!--
-    This is the maximum number of variables in any var statement GWT
-    will emit. This avoids a bug in some browsers including
-    the initial beta of Safari 4. See Issue 3455. If it is set to -1,
-    then there is no limit.
+    This is the maximum number of variables in any var statement GWT will  
emit. This avoids a bug in
+    some browsers including the initial beta of Safari 4. See Issue 3455.  
If it is set to -1, then
+    there is no limit.
    -->
-  <define-configuration-property name='compiler.max.vars.per.var'  
is-multi-valued='false' />
+  <define-configuration-property name='compiler.max.vars.per.var'
+    is-multi-valued='false' />
    <set-configuration-property name='compiler.max.vars.per.var'  
value='2400' />
+
+  <!--
+    The iframe linker chunks its output into multiple <script> tags. The  
default size is fine for
+    production use; it is overridable mainly for test cases. This size  
must be small enough that
+    block-size restrictions in IE are satisfied, because the script tag  
chunking undoes
+    JsIEBlockSizeVisitor.  If it's set to -1, then no chunking is  
performed and
+    JsIEBlockSizeVisitor has its usual effect.
+  -->
+  <define-configuration-property name="iframe.linker.script.chunk.size"
+    is-multi-valued="false" />
+  <set-configuration-property name="iframe.linker.script.chunk.size"
+    value="30000" />
  </module>

Modified: trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml
==============================================================================
--- trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml        
(original)
+++ trunk/user/test/com/google/gwt/dev/jjs/CompilerSuite.gwt.xml        Tue Jun 
 2  
12:09:40 2009
@@ -13,9 +13,18 @@
  <!-- limitations under the  
License.                                         -->

  <module>
+       <inherits name="com.google.gwt.core.Core"/>
+
        <source path='test' />
        <generate-with class="com.google.gwt.dev.jjs.UnstableGenerator">
                <when-type-assignable
                         
class="com.google.gwt.dev.jjs.test.UnstableGeneratorTest.UnstableResult" />
        </generate-with>
+
+       <!--
+         Specify some tighter compiler settings so that those compiler paths
+         are tested by the normal GWT regression suite.
+       -->
+       <set-configuration-property name='compiler.max.vars.per.var' value='10' 
/>
+       <set-configuration-property name='iframe.linker.script.chunk.size'  
value='100'/>
  </module>

Modified: trunk/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml
==============================================================================
--- trunk/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml   (original)
+++ trunk/user/test/com/google/gwt/emultest/EmulSuite.gwt.xml   Tue Jun  2  
12:09:40 2009
@@ -16,10 +16,4 @@
    <inherits name='com.google.gwt.junit.JUnit' />
    <inherits name='org.apache.commons.Collections' />
    <source path='java' />
-
-  <!--
-    Specify some tighter compiler settings so that those compiler paths
-    are tested by the normal GWT regression suite
-  -->
-  <set-configuration-property name='compiler.max.vars.per.var' value='10'  
/>
  </module>

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to